diff --git a/.gitignore b/.gitignore
index ba3cf311e5a..1a7b071ef7e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
*.ignore
quest-list.csv
/keystore.properties
+/secrets.properties
/projectFilesBackup*/
app/release
*.iml
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 343a73776b5..a2f74ef2dd4 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -54,6 +54,14 @@ repositories {
maven { url = uri("https://www.jitpack.io") }
}
+/** KartaView access token: from env var, else root secrets.properties (git-ignored), else empty
+ * (build succeeds but KartaView photo uploads fail auth at runtime). Never commit the token. */
+val kartaViewAccessToken: String = System.getenv("KARTAVIEW_ACCESS_TOKEN")
+ ?: rootProject.file("secrets.properties").takeIf { it.exists() }?.let { file ->
+ Properties().apply { FileInputStream(file).use { load(it) } }.getProperty("kartaViewAccessToken")
+ }
+ ?: ""
+
buildkonfig {
packageName = "de.westnordost.streetcomplete"
objectName = "BuildConfig"
@@ -62,6 +70,7 @@ buildkonfig {
buildConfigField(BOOLEAN, "IS_FROM_MONOPOLISTIC_APP_STORE", properties["app.streetcomplete.monopolistic_app_store"]!!.toString())
buildConfigField(STRING, "VERSION_NAME", appVersionName)
buildConfigField(BOOLEAN, "DEBUG", "true")
+ buildConfigField(STRING, "KARTAVIEW_ACCESS_TOKEN", kartaViewAccessToken)
}
targetConfigs {
diff --git a/app/src/androidDebug/res/values/conf.xml b/app/src/androidDebug/res/values/conf.xml
index e0acbd8a56a..157e85a2a43 100644
--- a/app/src/androidDebug/res/values/conf.xml
+++ b/app/src/androidDebug/res/values/conf.xml
@@ -1,4 +1,4 @@
- de.westnordost.streetcomplete.debug.fileprovider
+ net.opentoall.aviv.scoutroute.debug.fileprovider
diff --git a/app/src/androidDebug/res/xml/file_paths.xml b/app/src/androidDebug/res/xml/file_paths.xml
deleted file mode 100644
index b9f955c5421..00000000000
--- a/app/src/androidDebug/res/xml/file_paths.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/ApplicationModule.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/ApplicationModule.kt
index f53aef239e0..7ab8599724b 100644
--- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/ApplicationModule.kt
+++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/ApplicationModule.kt
@@ -3,6 +3,7 @@ package de.westnordost.streetcomplete
import android.content.Intent
import android.content.res.AssetManager
import android.content.res.Resources
+import de.westnordost.streetcomplete.data.karta_view.KartaViewApiClient
import de.westnordost.streetcomplete.data.preferences.EnvironmentManager
import de.westnordost.streetcomplete.data.preferences.Preferences
import de.westnordost.streetcomplete.data.workspace.domain.model.LoginResponse
@@ -147,6 +148,8 @@ val appModule = module {
}
}
+ single { KartaViewApiClient(get(), get(named("kartaViewClient")), get()) }
+
single { Res }
single { SystemFileSystem }
single { DefaultResourceProvider(androidContext(), get()) }
diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/AbstractOsmQuestForm.kt b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/AbstractOsmQuestForm.kt
index ba10da5be55..3a0ab647c17 100644
--- a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/AbstractOsmQuestForm.kt
+++ b/app/src/androidMain/kotlin/de/westnordost/streetcomplete/quests/AbstractOsmQuestForm.kt
@@ -27,8 +27,7 @@ import com.google.android.material.snackbar.Snackbar
import de.westnordost.osmfeatures.Feature
import de.westnordost.osmfeatures.FeatureDictionary
import de.westnordost.streetcomplete.R
-import de.westnordost.streetcomplete.data.karta_view.domain.model.CreateSequenceResponse
-import de.westnordost.streetcomplete.data.karta_view.domain.model.PhotoLookupResponse
+import de.westnordost.streetcomplete.data.karta_view.KartaViewApiClient
import de.westnordost.streetcomplete.data.location.SurveyChecker
import de.westnordost.streetcomplete.data.osm.edits.AddElementEditsController
import de.westnordost.streetcomplete.data.osm.edits.ElementEditAction
@@ -43,6 +42,7 @@ import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry
import de.westnordost.streetcomplete.data.osm.geometry.ElementPolylinesGeometry
import de.westnordost.streetcomplete.data.osm.mapdata.Element
import de.westnordost.streetcomplete.data.osm.mapdata.ElementType
+import de.westnordost.streetcomplete.data.osm.mapdata.LatLon
import de.westnordost.streetcomplete.data.osm.mapdata.Node
import de.westnordost.streetcomplete.data.osm.mapdata.Way
import de.westnordost.streetcomplete.data.osm.osmquests.OsmElementQuestType
@@ -53,7 +53,6 @@ import de.westnordost.streetcomplete.data.quest.Quest
import de.westnordost.streetcomplete.data.quest.QuestKey
import de.westnordost.streetcomplete.data.visiblequests.HideQuestController
import de.westnordost.streetcomplete.data.visiblequests.QuestsHiddenController
-import de.westnordost.streetcomplete.data.preferences.Preferences
import de.westnordost.streetcomplete.osm.applyReplacePlaceTo
import de.westnordost.streetcomplete.quests.sidewalk_long_form.AddGenericLong
import de.westnordost.streetcomplete.screens.main.map.Compass
@@ -62,17 +61,6 @@ import de.westnordost.streetcomplete.util.ktx.isSplittable
import de.westnordost.streetcomplete.util.ktx.viewLifecycleScope
import de.westnordost.streetcomplete.view.add
import de.westnordost.streetcomplete.view.confirmIsSurvey
-import io.ktor.client.HttpClient
-import io.ktor.client.call.body
-import io.ktor.client.request.forms.MultiPartFormDataContent
-import io.ktor.client.request.forms.formData
-import io.ktor.client.request.get
-import io.ktor.client.request.parameter
-import io.ktor.client.request.post
-import io.ktor.client.request.setBody
-import io.ktor.http.Headers
-import io.ktor.http.HttpHeaders
-import io.ktor.http.HttpStatusCode
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -95,8 +83,7 @@ abstract class AbstractOsmQuestForm : AbstractQuestForm(), IsShowingQuestDeta
private val surveyChecker: SurveyChecker by inject()
protected val featureDictionary: FeatureDictionary get() = featureDictionaryLazy.value
- private val httpClient: HttpClient by inject(named("kartaViewClient"))
- private val prefs: Preferences by inject()
+ private val kartaViewApiClient: KartaViewApiClient by inject()
private lateinit var cameraLauncher: ActivityResultLauncher
// only used for testing / only used for ShowQuestFormsScreen! Found no better way to do this
@@ -169,14 +156,13 @@ abstract class AbstractOsmQuestForm : AbstractQuestForm(), IsShowingQuestDeta
if (getElement != null) {
element = getElement
}
- val displayedLocation = args.getParcelable(ARG_DISPLAYED_LOCATION)
cameraLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
showProgressbar()
// Handle the image capture result here
val bitmap = result.data?.extras?.getParcelable("data")
- startKartViewFlow(bitmap, displayedLocation)
+ startKartViewFlow(bitmap)
} else {
// Handle the error state here
@@ -446,109 +432,37 @@ abstract class AbstractOsmQuestForm : AbstractQuestForm(), IsShowingQuestDeta
}
}
- private fun startKartViewFlow(bitmap: Bitmap?, displayedLocation: Location?) {
+ private fun startKartViewFlow(bitmap: Bitmap?) {
viewLifecycleScope.launch {
- val sequenceId = createSequence() ?: return@launch
- val uploaded = uploadImageInSequence(sequenceId, bitmap, displayedLocation)
- if (uploaded) {
- closeSequence(sequenceId)
- val lthUrl = getPhotoLthUrl(sequenceId, sequenceIndex = 1)
- if (lthUrl != null) {
- Log.d("KartViewFlow", lthUrl)
- onImageUrlReceived(lthUrl)
- } else {
- hideProgressbar()
- Log.e("KartViewFlow", "Failed to retrieve photo URL")
- showSnackBar("Failed to retrieve photo URL. Please try again later.", view, requireActivity() as ComponentActivity)
- }
- } else {
+ val displayedLocation = listener?.displayedMapLocation
+ if (bitmap == null || displayedLocation == null) {
hideProgressbar()
- Log.e("KartViewFlow", "Image upload failed")
- showSnackBar("Image upload failed. Please try again later.", view, requireActivity() as ComponentActivity)
+ return@launch
}
- }
- }
-
- private suspend fun getPhotoLthUrl(sequenceId: String, sequenceIndex: Int): String? {
- val token = prefs.kartaViewAccessToken
- val response = httpClient.get("https://api.openstreetcam.org/2.0/photo/") {
- parameter("access_token", token)
- parameter("sequenceId", sequenceId)
- parameter("sequenceIndex", sequenceIndex)
- }
- if (response.status == HttpStatusCode.OK) {
- val photoResponse = response.body()
- return photoResponse.result?.data?.firstOrNull()?.imageLthUrl
- }
- Log.e("KartViewFlow", "Photo lookup failed: ${response.status}")
- return null
- }
-
- private suspend fun closeSequence(sequenceId: String) {
- val token = prefs.kartaViewAccessToken
- val response =
- httpClient.post("https://api.openstreetcam.org/1.0/sequence/finished-uploading/") {
- setBody(MultiPartFormDataContent(formData {
- append("access_token", token)
- append("sequenceId", sequenceId)
- }))
+ val bearing = if (displayedLocation.hasBearing() && displayedLocation.bearing != 0f) {
+ displayedLocation.bearing
+ } else {
+ compassBearing.toFloat()
+ }
+ val byteArrayOutputStream = ByteArrayOutputStream()
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)
+ try {
+ val urls = kartaViewApiClient.uploadImages(
+ listOf(byteArrayOutputStream.toByteArray()),
+ LatLon(displayedLocation.latitude, displayedLocation.longitude),
+ bearing
+ )
+ showSnackBar("Image Uploaded Successfully", view, requireActivity() as ComponentActivity)
+ onImageUrlReceived(urls.first())
+ } catch (e: Exception) {
+ Log.e("KartViewFlow", "KartaView upload failed", e)
+ showSnackBar(
+ e.message ?: "Image upload failed. Please try again later.",
+ view, requireActivity() as ComponentActivity
+ )
+ } finally {
+ hideProgressbar()
}
- if (response.status == HttpStatusCode.OK) {
- val sequence = response.body()
- Log.d("KartViewSequence", sequence.status.httpMessage)
- } else {
- showSnackBar(
- "Failed to close KartaView Sequence. Please try again later " + response.status,
- view, requireActivity() as ComponentActivity
- )
- }
- hideProgressbar()
- }
-
- private suspend fun uploadImageInSequence(
- sequenceId: String,
- bitmap: Bitmap?,
- location: Location?,
- ): Boolean {
- val displayedLocation = listener?.displayedMapLocation ?: return false
- bitmap ?: return false
-
- val byteArrayOutputStream = ByteArrayOutputStream()
- bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)
- val byteArray = byteArrayOutputStream.toByteArray()
-
- val finalBearing = if (displayedLocation.hasBearing() && displayedLocation.bearing != 0f) {
- displayedLocation.bearing
- } else {
- compassBearing.toFloat()
- }
-
- val response = httpClient.post("https://api.openstreetcam.org/1.0/photo/") {
- setBody(MultiPartFormDataContent(formData {
- append("access_token", prefs.kartaViewAccessToken)
- append("sequenceId", sequenceId)
- append("sequenceIndex", 1)
- append("coordinate", "${displayedLocation.latitude},${displayedLocation.longitude}")
- append("headers", finalBearing.toInt().toString())
- append("photo", byteArray, Headers.build {
- append(HttpHeaders.ContentType, "image/jpeg")
- append(HttpHeaders.ContentDisposition, "filename=\"wework-kartaview.jpg\"")
- })
- }))
- }
-
- return if (response.status == HttpStatusCode.OK) {
- showSnackBar("Image Uploaded Successfully", view, requireActivity() as ComponentActivity)
- Log.d("UploadImage", "Image uploaded successfully")
- true
- } else {
- Log.e("UploadImage", "Image upload failed: ${response.status}")
- showSnackBar(
- "Failed to upload image to KartaView. Please try again later " + response.status,
- view, requireActivity() as ComponentActivity
- )
- hideProgressbar()
- false
}
}
@@ -558,28 +472,6 @@ abstract class AbstractOsmQuestForm : AbstractQuestForm(), IsShowingQuestDeta
}
}
- private suspend fun createSequence(): String? {
- val token = prefs.kartaViewAccessToken
- val response = httpClient.post("https://api.openstreetcam.org/1.0/sequence/") {
- setBody(MultiPartFormDataContent(formData {
- append("access_token", token)
- }))
- }
- if (response.status == HttpStatusCode.OK) {
- val sequence = response.body()
- sequence.osv.sequence?.id?.let { Log.d("KartViewSequence", it) }
- Log.d("KartViewStatus", sequence.status.httpMessage)
- return sequence.osv.sequence?.id
- } else {
- hideProgressbar()
- showSnackBar(
- "Failed to create KartaView sequence. Image upload failed. Please try again later " + response.status,
- view, requireActivity() as ComponentActivity
- )
- return null
- }
- }
-
companion object {
private const val ARG_ELEMENT = "element"
private const val ARG_DISPLAYED_LOCATION = "displayedLocation"
diff --git a/app/src/androidMain/res/layout/form_leave_note.xml b/app/src/androidMain/res/layout/form_leave_note.xml
index efc9130b0d5..d92be965f84 100644
--- a/app/src/androidMain/res/layout/form_leave_note.xml
+++ b/app/src/androidMain/res/layout/form_leave_note.xml
@@ -24,4 +24,11 @@
android:background="@drawable/background_edittext_outline"
/>
+
+
diff --git a/app/src/androidMain/res/values/conf.xml b/app/src/androidMain/res/values/conf.xml
index c5e57139d76..2697885525a 100644
--- a/app/src/androidMain/res/values/conf.xml
+++ b/app/src/androidMain/res/values/conf.xml
@@ -1,4 +1,4 @@
- de.westnordost.streetcomplete.fileprovider
+ net.opentoall.aviv.scoutroute.fileprovider
diff --git a/app/src/androidMain/res/xml/file_paths.xml b/app/src/androidMain/res/xml/file_paths.xml
index 195dcc2cab0..bc55a97853d 100644
--- a/app/src/androidMain/res/xml/file_paths.xml
+++ b/app/src/androidMain/res/xml/file_paths.xml
@@ -1,4 +1,6 @@
-
+
+
diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/KartaViewApiClient.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/KartaViewApiClient.kt
new file mode 100644
index 00000000000..a4a6b708416
--- /dev/null
+++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/KartaViewApiClient.kt
@@ -0,0 +1,138 @@
+package de.westnordost.streetcomplete.data.karta_view
+
+import de.westnordost.streetcomplete.data.karta_view.domain.model.CreateSequenceResponse
+import de.westnordost.streetcomplete.data.karta_view.domain.model.PhotoLookupResponse
+import de.westnordost.streetcomplete.data.osm.mapdata.LatLon
+import de.westnordost.streetcomplete.data.preferences.Preferences
+import de.westnordost.streetcomplete.util.logs.Log
+import io.ktor.client.HttpClient
+import io.ktor.client.call.body
+import io.ktor.client.request.forms.MultiPartFormDataContent
+import io.ktor.client.request.forms.formData
+import io.ktor.client.request.get
+import io.ktor.client.request.parameter
+import io.ktor.client.request.post
+import io.ktor.client.request.setBody
+import io.ktor.http.Headers
+import io.ktor.http.HttpHeaders
+import io.ktor.http.HttpStatusCode
+import kotlinx.io.buffered
+import kotlinx.io.files.FileSystem
+import kotlinx.io.files.Path
+import kotlinx.io.readByteArray
+
+/** Uploads photos to KartaView (api.openstreetcam.org). */
+class KartaViewApiClient(
+ private val fileSystem: FileSystem,
+ private val httpClient: HttpClient,
+ private val prefs: Preferences,
+) {
+ /** Uploads the image files at [imagePaths] into one new KartaView sequence at [position],
+ * in list order. Paths that don't exist are skipped. Returns the lth photo URL for each
+ * uploaded image, in the same order.
+ *
+ * @throws KartaViewException naming the step that failed */
+ suspend fun upload(imagePaths: List, position: LatLon, bearing: Float = 0f): List {
+ val images = imagePaths.mapNotNull { path ->
+ val file = Path(path)
+ if (fileSystem.exists(file)) fileSystem.source(file).buffered().readByteArray() else null
+ }
+ return uploadImages(images, position, bearing)
+ }
+
+ /** Uploads [images] (JPEG-encoded) into one new KartaView sequence at [position], in list
+ * order (sequenceIndex 1..n), closes the sequence and returns the lth photo URL for each
+ * image, in the same order.
+ *
+ * @throws KartaViewException naming the step that failed */
+ suspend fun uploadImages(images: List, position: LatLon, bearing: Float = 0f): List {
+ if (images.isEmpty()) return emptyList()
+ val sequenceId = createSequence()
+ images.forEachIndexed { index, image ->
+ uploadPhoto(sequenceId, index + 1, image, position, bearing)
+ }
+ closeSequence(sequenceId)
+ return List(images.size) { index -> getPhotoLthUrl(sequenceId, index + 1) }
+ }
+
+ private suspend fun createSequence(): String {
+ val response = httpClient.post(BASE_URL + "1.0/sequence/") {
+ setBody(MultiPartFormDataContent(formData {
+ append("access_token", prefs.kartaViewAccessToken)
+ }))
+ }
+ if (response.status == HttpStatusCode.OK) {
+ val sequence = response.body()
+ Log.d(TAG, "Sequence created: ${sequence.status.httpMessage}")
+ sequence.osv.sequence?.id?.let { return it }
+ }
+ throw KartaViewException(
+ "Failed to create KartaView sequence. Image upload failed. Please try again later " + response.status
+ )
+ }
+
+ private suspend fun uploadPhoto(
+ sequenceId: String,
+ sequenceIndex: Int,
+ image: ByteArray,
+ position: LatLon,
+ bearing: Float,
+ ) {
+ val response = httpClient.post(BASE_URL + "1.0/photo/") {
+ setBody(MultiPartFormDataContent(formData {
+ append("access_token", prefs.kartaViewAccessToken)
+ append("sequenceId", sequenceId)
+ append("sequenceIndex", sequenceIndex)
+ append("coordinate", "${position.latitude},${position.longitude}")
+ append("headers", bearing.toInt().toString())
+ append("photo", image, Headers.build {
+ append(HttpHeaders.ContentType, "image/jpeg")
+ append(HttpHeaders.ContentDisposition, "filename=\"wework-kartaview.jpg\"")
+ })
+ }))
+ }
+ if (response.status != HttpStatusCode.OK) {
+ throw KartaViewException(
+ "Failed to upload image to KartaView. Please try again later " + response.status
+ )
+ }
+ Log.d(TAG, "Image $sequenceIndex uploaded to sequence $sequenceId")
+ }
+
+ private suspend fun closeSequence(sequenceId: String) {
+ val response = httpClient.post(BASE_URL + "1.0/sequence/finished-uploading/") {
+ setBody(MultiPartFormDataContent(formData {
+ append("access_token", prefs.kartaViewAccessToken)
+ append("sequenceId", sequenceId)
+ }))
+ }
+ if (response.status != HttpStatusCode.OK) {
+ throw KartaViewException(
+ "Failed to close KartaView Sequence. Please try again later " + response.status
+ )
+ }
+ Log.d(TAG, "Sequence closed: ${response.body().status.httpMessage}")
+ }
+
+ private suspend fun getPhotoLthUrl(sequenceId: String, sequenceIndex: Int): String {
+ val response = httpClient.get(BASE_URL + "2.0/photo/") {
+ parameter("access_token", prefs.kartaViewAccessToken)
+ parameter("sequenceId", sequenceId)
+ parameter("sequenceIndex", sequenceIndex)
+ }
+ if (response.status == HttpStatusCode.OK) {
+ val url = response.body().result?.data?.firstOrNull()?.imageLthUrl
+ if (url != null) return url
+ }
+ throw KartaViewException(
+ "Failed to retrieve photo URL. Please try again later " + response.status
+ )
+ }
+
+ companion object {
+ private const val TAG = "KartaViewApiClient"
+ private const val BASE_URL = "https://api.openstreetcam.org/"
+ }
+}
+
+class KartaViewException(message: String) : RuntimeException(message)
diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/CreateSequenceResponse.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/CreateSequenceResponse.kt
similarity index 100%
rename from app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/CreateSequenceResponse.kt
rename to app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/CreateSequenceResponse.kt
diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/ImageUploadResponse.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/ImageUploadResponse.kt
similarity index 100%
rename from app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/ImageUploadResponse.kt
rename to app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/ImageUploadResponse.kt
diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/Osv.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/Osv.kt
similarity index 100%
rename from app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/Osv.kt
rename to app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/Osv.kt
diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/OsvX.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/OsvX.kt
similarity index 100%
rename from app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/OsvX.kt
rename to app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/OsvX.kt
diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/Photo.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/Photo.kt
similarity index 100%
rename from app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/Photo.kt
rename to app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/Photo.kt
diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/PhotoLookupResponse.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/PhotoLookupResponse.kt
similarity index 100%
rename from app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/PhotoLookupResponse.kt
rename to app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/PhotoLookupResponse.kt
diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/Sequence.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/Sequence.kt
similarity index 100%
rename from app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/Sequence.kt
rename to app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/Sequence.kt
diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/Status.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/Status.kt
similarity index 100%
rename from app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/Status.kt
rename to app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/Status.kt
diff --git a/app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/StatusX.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/StatusX.kt
similarity index 100%
rename from app/src/androidMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/StatusX.kt
rename to app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/karta_view/domain/model/StatusX.kt
diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/osmnotes/NotesModule.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/osmnotes/NotesModule.kt
index 52ef0e7841c..dedf4d32675 100644
--- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/osmnotes/NotesModule.kt
+++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/osmnotes/NotesModule.kt
@@ -1,6 +1,5 @@
package de.westnordost.streetcomplete.data.osmnotes
-import de.westnordost.streetcomplete.ApplicationConstants
import org.koin.core.qualifier.named
import org.koin.dsl.module
@@ -9,7 +8,6 @@ val notesModule = module {
factory { AvatarsInNotesUpdater(get()) }
factory { NoteDao(get(), get()) }
factory { NotesDownloader(get(), get()) }
- factory { PhotoServiceApiClient(get(), get(), ApplicationConstants.SC_PHOTO_SERVICE_URL) }
single {
NoteController(get()).apply {
diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsUploader.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsUploader.kt
index f7166502eaf..b1dee408b47 100644
--- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsUploader.kt
+++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/osmnotes/edits/NoteEditsUploader.kt
@@ -2,9 +2,10 @@ package de.westnordost.streetcomplete.data.osmnotes.edits
import de.westnordost.streetcomplete.ApplicationConstants
import de.westnordost.streetcomplete.data.ConflictException
+import de.westnordost.streetcomplete.data.karta_view.KartaViewApiClient
+import de.westnordost.streetcomplete.data.osm.mapdata.LatLon
import de.westnordost.streetcomplete.data.osmnotes.NoteController
import de.westnordost.streetcomplete.data.osmnotes.NotesApiClient
-import de.westnordost.streetcomplete.data.osmnotes.PhotoServiceApiClient
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditAction.COMMENT
import de.westnordost.streetcomplete.data.osmnotes.edits.NoteEditAction.CREATE
import de.westnordost.streetcomplete.data.osmtracks.Trackpoint
@@ -30,7 +31,7 @@ class NoteEditsUploader(
private val userDataSource: UserDataSource,
private val notesApi: NotesApiClient,
private val tracksApi: TracksApiClient,
- private val imageUploader: PhotoServiceApiClient,
+ private val imageUploader: KartaViewApiClient,
private val fileSystem: FileSystem,
) {
var uploadedChangeListener: OnUploadedChangeListener? = null
@@ -51,13 +52,11 @@ class NoteEditsUploader(
} }
private suspend fun uploadMissedImageActivations() {
+ // KartaView photos need no activation step (their URLs are embedded in the note text at
+ // upload) - just clear any flags still set, e.g. from before the switch to KartaView
while (true) {
val edit = noteEditsController.getOldestNeedingImagesActivation() ?: break
- // see uploadEdits
- withContext(scope.coroutineContext) {
- imageUploader.activate(edit.noteId)
- noteEditsController.markImagesActivated(edit.id)
- }
+ noteEditsController.markImagesActivated(edit.id)
}
}
@@ -73,7 +72,7 @@ class NoteEditsUploader(
private suspend fun uploadEdit(edit: NoteEdit) {
// try to upload the image and track if we have them
- val imageText = uploadAndGetAttachedPhotosText(edit.imagePaths)
+ val imageText = uploadAndGetAttachedPhotosText(edit.imagePaths, edit.position)
val trackText = uploadAndGetAttachedTrackText(edit.track, edit.text)
val text = edit.text.orEmpty() + imageText + trackText
@@ -94,7 +93,6 @@ class NoteEditsUploader(
noteController.put(note)
if (edit.imagePaths.isNotEmpty()) {
- imageUploader.activate(note.id)
noteEditsController.markImagesActivated(note.id)
}
@@ -124,9 +122,9 @@ class NoteEditsUploader(
}
}
- private suspend fun uploadAndGetAttachedPhotosText(imagePaths: List): String {
+ private suspend fun uploadAndGetAttachedPhotosText(imagePaths: List, position: LatLon): String {
if (imagePaths.isNotEmpty()) {
- val urls = imageUploader.upload(imagePaths)
+ val urls = imageUploader.upload(imagePaths, position)
if (urls.isNotEmpty()) {
return "\n\nAttached photo(s):\n" + urls.joinToString("\n")
}
diff --git a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/preferences/Preferences.kt b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/preferences/Preferences.kt
index 8f48ecb427b..d5bba25c365 100644
--- a/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/preferences/Preferences.kt
+++ b/app/src/commonMain/kotlin/de/westnordost/streetcomplete/data/preferences/Preferences.kt
@@ -8,6 +8,7 @@ import com.russhwolf.settings.int
import com.russhwolf.settings.long
import com.russhwolf.settings.nullableString
import com.russhwolf.settings.set
+import de.westnordost.streetcomplete.BuildConfig
import de.westnordost.streetcomplete.data.osm.mapdata.LatLon
import de.westnordost.streetcomplete.util.ktx.putStringOrNull
import kotlinx.coroutines.flow.MutableStateFlow
@@ -214,7 +215,7 @@ class Preferences(private val prefs: ObservableSettings) {
var kartaViewAccessToken: String
set(value) { prefs.putString(KARTAVIEW_ACCESS_TOKEN, value) }
- get() = prefs.getString(KARTAVIEW_ACCESS_TOKEN, DEFAULT_KARTAVIEW_ACCESS_TOKEN)
+ get() = prefs.getString(KARTAVIEW_ACCESS_TOKEN, BuildConfig.KARTAVIEW_ACCESS_TOKEN)
var isFollowModeEnabled: Boolean
set(value) {
@@ -445,7 +446,5 @@ class Preferences(private val prefs: ObservableSettings) {
private const val FOLLOW_MODE_ENABLED = "follow.mode.enabled"
private const val DEBUG_MODE_ENABLED = "debug.mode.enabled"
private const val KARTAVIEW_ACCESS_TOKEN = "kartaview.access_token"
- private const val DEFAULT_KARTAVIEW_ACCESS_TOKEN =
- "5b7bfc9104ad198bdba4108ed3048f380f118cfb528d750a2bf31041b7266a99"
}
}
diff --git a/secrets.properties.example b/secrets.properties.example
new file mode 100644
index 00000000000..91b949ea791
--- /dev/null
+++ b/secrets.properties.example
@@ -0,0 +1,8 @@
+# Copy this file to secrets.properties (git-ignored) and fill in the values.
+# Alternatively, each value can be supplied via an environment variable of the
+# same name as noted below (the env var takes precedence).
+
+# KartaView (api.openstreetcam.org) access token used for photo uploads.
+# Obtain one from the KartaView account that owns the app's photo sequences.
+# Env var alternative: KARTAVIEW_ACCESS_TOKEN
+kartaViewAccessToken=