diff --git a/app/src/main/java/org/scottishtecharmy/soundscape/utils/AndroidOfflineMapsManager.kt b/app/src/main/java/org/scottishtecharmy/soundscape/utils/AndroidOfflineMapsManager.kt index 5d2d559eb..696ad8f2c 100644 --- a/app/src/main/java/org/scottishtecharmy/soundscape/utils/AndroidOfflineMapsManager.kt +++ b/app/src/main/java/org/scottishtecharmy/soundscape/utils/AndroidOfflineMapsManager.kt @@ -27,7 +27,6 @@ import org.scottishtecharmy.soundscape.i18n.ComposeLocalizedStrings import org.scottishtecharmy.soundscape.network.DownloadStateCommon import org.scottishtecharmy.soundscape.screens.home.offlinemaps.NearbyExtractsState import java.io.File -import java.io.FileOutputStream private fun DownloadState.toCommon(): DownloadStateCommon = when (this) { is DownloadState.Idle -> DownloadStateCommon.Idle @@ -109,6 +108,7 @@ class AndroidOfflineMapsManager( private fun refreshDownloaded() { val dir = extractsDir() + deleteOrphanedSidecars(dir) _downloadedExtractsFc.value = findExtracts(dir.path) ?: FeatureCollection() } @@ -153,16 +153,16 @@ class AndroidOfflineMapsManager( // version's files once this one is published (see logicalBaseName below). val versionedFilename = "$logicalBase.v${System.currentTimeMillis()}.pmtiles" val path = "${extractsDir().path}/$versionedFilename" - try { - val moshi = GeoMoshi.registerAdapters(Moshi.Builder()).build() - val adapter = moshi.adapter(Feature::class.java) - FileOutputStream("$path.geojson").use { - it.write( - adapter.toJson(current).toByteArray() - ) - } + // The metadata sidecar is written by the downloader once the extract is saved, not here + // before it starts: a download that failed or was cancelled used to leave its sidecar + // behind, for an extract that was never there. + val metadataJson = try { + GeoMoshi.registerAdapters(Moshi.Builder()).build() + .adapter(Feature::class.java) + .toJson(current) } catch (e: Exception) { - Log.e(TAG, "Failed to write extract metadata", e) + Log.e(TAG, "Failed to encode extract metadata", e) + null } val extractSize = (current.properties?.get("extract-size") as? Number)?.toDouble() downloader.startDownload( @@ -170,6 +170,7 @@ class AndroidOfflineMapsManager( path, extractSize, logicalBaseName = logicalBase, + metadataJson = metadataJson, ) } diff --git a/app/src/main/java/org/scottishtecharmy/soundscape/utils/OfflineDownloader.kt b/app/src/main/java/org/scottishtecharmy/soundscape/utils/OfflineDownloader.kt index e7eba242b..64dc4d116 100644 --- a/app/src/main/java/org/scottishtecharmy/soundscape/utils/OfflineDownloader.kt +++ b/app/src/main/java/org/scottishtecharmy/soundscape/utils/OfflineDownloader.kt @@ -86,12 +86,16 @@ class OfflineDownloader(injectedFileDownloader: FileDownloader? = null) { * directory whose name starts with "." is deleted - this is what actually * retires a previous version of the same extract (see the comment above the rename below for * why in-place replacement isn't safe for pmtiles extracts that MapLibre may have opened). + * @param metadataJson If set, written to the ".geojson" sidecar once the + * download is published - and only then, so that a download which fails or is cancelled + * doesn't leave a sidecar behind for an extract that was never saved. */ fun startDownload( fileUrl: String, outputFilePath: String, extractSize: Double?, logicalBaseName: String? = null, + metadataJson: String? = null, ) { if (downloadJob?.isActive == true) { Log.w(TAG, "Download is already in progress.") @@ -162,6 +166,13 @@ class OfflineDownloader(injectedFileDownloader: FileDownloader? = null) { // are always in the same directory. if (tempFile.renameTo(finalFile)) { Log.i(TAG, "Download successful. File saved to: ${finalFile.path}") + if (metadataJson != null) { + try { + File("$outputFilePath.geojson").writeText(metadataJson) + } catch (e: Exception) { + Log.e(TAG, "Failed to write extract metadata", e) + } + } // Clean up old versions before publishing Success - an observer // reacting to Success (e.g. refreshing the extract list) should // never see a superseded version still on disk. diff --git a/app/src/main/java/org/scottishtecharmy/soundscape/utils/StorageUtils.kt b/app/src/main/java/org/scottishtecharmy/soundscape/utils/StorageUtils.kt index 356583168..ba0f7adca 100644 --- a/app/src/main/java/org/scottishtecharmy/soundscape/utils/StorageUtils.kt +++ b/app/src/main/java/org/scottishtecharmy/soundscape/utils/StorageUtils.kt @@ -155,6 +155,19 @@ fun getMetadata(pmtilesPath: String): Feature? { return null } +/** + * Delete the .geojson metadata sidecars in [dir] which have no extract next to them. Downloads + * used to write the sidecar before they started, and left it behind when they failed or were + * cancelled; those orphans are never listed, but they stay on the phone forever. + */ +fun deleteOrphanedSidecars(dir: File) { + dir.listFiles { file -> file.name.endsWith(".pmtiles.geojson") }?.forEach { sidecar -> + if (!File(dir, sidecar.name.removeSuffix(".geojson")).exists()) { + sidecar.delete() + } + } +} + fun findExtracts(path: String): FeatureCollection? { // Find any extracts that we have downloaded val extractsDir = File(path) diff --git a/app/src/test/java/org/scottishtecharmy/soundscape/OfflineDownloaderTest.kt b/app/src/test/java/org/scottishtecharmy/soundscape/OfflineDownloaderTest.kt index a7fbade0b..a336dc29c 100644 --- a/app/src/test/java/org/scottishtecharmy/soundscape/OfflineDownloaderTest.kt +++ b/app/src/test/java/org/scottishtecharmy/soundscape/OfflineDownloaderTest.kt @@ -16,6 +16,7 @@ import org.junit.Test import org.scottishtecharmy.soundscape.network.createFileDownloader import org.scottishtecharmy.soundscape.utils.DownloadState import org.scottishtecharmy.soundscape.utils.OfflineDownloader +import org.scottishtecharmy.soundscape.utils.deleteOrphanedSidecars import java.io.File /** @@ -114,12 +115,11 @@ class OfflineDownloaderTest { val unrelatedExtract = File(tempDir, "glasgow-gbx.bin").apply { writeText("unrelated") } val newOutputFile = File(tempDir, "glasgow-gb.v2000.bin") - // AndroidOfflineMapsManager.startDownload() writes this sidecar *before* calling - // startDownload - it must survive the post-publish cleanup, which previously deleted - // it (it starts with the logical prefix and its name isn't *exactly* - // newOutputFile.name), silently hiding every freshly downloaded extract from the - // offline-maps UI. - val newSidecar = File(tempDir, "glasgow-gb.v2000.bin.geojson").apply { writeText("{}") } + // The download writes its own sidecar once published, and it must survive the + // post-publish cleanup, which once deleted it (it starts with the logical prefix and + // its name isn't *exactly* newOutputFile.name), silently hiding every freshly + // downloaded extract from the offline-maps UI. + val newSidecar = File(tempDir, "glasgow-gb.v2000.bin.geojson") val fake = FakeResponses(listOf({ body(1000) })) val downloader = OfflineDownloader(fakeFileDownloader(fake)) @@ -128,6 +128,7 @@ class OfflineDownloaderTest { newOutputFile.path, extractSize = 1000.0, logicalBaseName = "glasgow-gb", + metadataJson = "{}", ) val state = runBlocking { @@ -164,7 +165,7 @@ class OfflineDownloaderTest { val unrelatedExtract = File(tempDir, "glasgow-gbx.bin").apply { writeText("unrelated") } val newOutputFile = File(tempDir, "glasgow-gb.v2000.bin") - val newSidecar = File(tempDir, "glasgow-gb.v2000.bin.geojson").apply { writeText("{}") } + val newSidecar = File(tempDir, "glasgow-gb.v2000.bin.geojson") val fake = FakeResponses(listOf({ body(1000) })) val downloader = OfflineDownloader(fakeFileDownloader(fake)) downloader.startDownload( @@ -172,6 +173,7 @@ class OfflineDownloaderTest { newOutputFile.path, extractSize = 1000.0, logicalBaseName = "glasgow-gb", + metadataJson = "{}", ) val state = runBlocking { @@ -256,4 +258,60 @@ class OfflineDownloaderTest { file.parentFile?.deleteRecursively() } } + + /** + * A download that fails must leave nothing behind - its metadata sidecar used to be written + * before the download started, and stayed on the phone forever with no extract next to it. + */ + @Test + fun failedDownloadLeavesNoSidecar() { + val tempDir = File(System.getProperty("java.io.tmpdir"), "offline-dl-${System.nanoTime()}") + tempDir.mkdirs() + try { + val outputFile = File(tempDir, "helsinki-fi.v2000.bin") + val fake = FakeResponses(listOf({ truncatedBody(advertised = 1000L, actual = 500) })) + val downloader = OfflineDownloader(fakeFileDownloader(fake)) + downloader.startDownload( + "https://example.test/extract", + outputFile.path, + extractSize = 1000.0, + logicalBaseName = "helsinki-fi", + metadataJson = "{}", + ) + + val state = runBlocking { + withTimeout(10_000) { + downloader.downloadState.first { + it is DownloadState.Success || it is DownloadState.Error + } + } + } + + assertTrue("expected Error but was $state", state is DownloadState.Error) + assertTrue("no sidecar for a failed download", tempDir.listFiles().isNullOrEmpty()) + } finally { + tempDir.deleteRecursively() + } + } + + @Test + fun orphanedSidecarsAreDeleted() { + val tempDir = File(System.getProperty("java.io.tmpdir"), "offline-dl-${System.nanoTime()}") + tempDir.mkdirs() + try { + val extract = File(tempDir, "glasgow-gb.v1000.pmtiles").apply { writeText("x") } + val sidecar = File(tempDir, "glasgow-gb.v1000.pmtiles.geojson").apply { writeText("{}") } + val orphan = File(tempDir, "helsinki-fi.v2000.pmtiles.geojson").apply { writeText("{}") } + val download = File(tempDir, "budapest-hu.v3000.pmtiles.downloading").apply { writeText("x") } + + deleteOrphanedSidecars(tempDir) + + assertTrue(extract.exists()) + assertTrue("an extract's own sidecar must survive", sidecar.exists()) + assertTrue("a sidecar with no extract should be deleted", !orphan.exists()) + assertTrue("a download in progress isn't touched", download.exists()) + } finally { + tempDir.deleteRecursively() + } + } } diff --git a/app/src/test/java/org/scottishtecharmy/soundscape/SearchTest.kt b/app/src/test/java/org/scottishtecharmy/soundscape/SearchTest.kt index caa62dcf3..d26a3a407 100644 --- a/app/src/test/java/org/scottishtecharmy/soundscape/SearchTest.kt +++ b/app/src/test/java/org/scottishtecharmy/soundscape/SearchTest.kt @@ -649,9 +649,10 @@ class SearchTest { @Test fun categorySearchNamesAnUnnamedPlaceByItsType() { runBlocking { - // The toilets by Milngavie station (OSM way 218224201) are an unnamed building. They - // used to be listed as "Milngavie" - their address - with nothing to say they were - // toilets, and twice, because the building crosses a tile boundary. + // The toilets near the James Gale Memorial in Milngavie (OSM way 218224201) are an + // unnamed building. They used to be listed as "Milngavie" - their address - with + // nothing to say they were toilets, and twice, because the building crosses a tile + // boundary. val currentLocation = LngLatAlt(-4.3092376, 55.9497288) val gridState = getGridStateForLocation(currentLocation, MAX_ZOOM_LEVEL, GRID_SIZE) val settlementState = getGridStateForLocation(currentLocation, 12, 3) diff --git a/shared/src/androidMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/PlatformMapContainer.kt b/shared/src/androidMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/PlatformMapContainer.kt index afb09244f..d22992e49 100644 --- a/shared/src/androidMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/PlatformMapContainer.kt +++ b/shared/src/androidMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/PlatformMapContainer.kt @@ -28,6 +28,7 @@ actual fun PlatformMapContainer( extractGeometry: Geometry?, forceOnlineTiles: Boolean, onInteractionChanged: (Boolean) -> Unit, + onBeaconLocationEdited: ((LngLatAlt) -> Unit)?, ) { val context = LocalContext.current @@ -109,5 +110,6 @@ actual fun PlatformMapContainer( baseStyle = baseStyle, extractGeometry = extractGeometry, onInteractionChanged = onInteractionChanged, + onBeaconLocationEdited = onBeaconLocationEdited, ) } diff --git a/shared/src/commonMain/composeResources/values-ar/strings.xml b/shared/src/commonMain/composeResources/values-ar/strings.xml index 3193c2e40..676113db1 100644 --- a/shared/src/commonMain/composeResources/values-ar/strings.xml +++ b/shared/src/commonMain/composeResources/values-ar/strings.xml @@ -560,7 +560,6 @@ عرض الخريطة بملء الشاشة الخروج من الخريطة بملء الشاشة عرض الخريطة بملء الشاشة وتعديل موقع العلامة - الخروج من وضع تعديل موقع العلامة بدء المسار إيقاف المسار تعديل المسار diff --git a/shared/src/commonMain/composeResources/values-bg/strings.xml b/shared/src/commonMain/composeResources/values-bg/strings.xml index 6362a3a7d..f1f71051a 100644 --- a/shared/src/commonMain/composeResources/values-bg/strings.xml +++ b/shared/src/commonMain/composeResources/values-bg/strings.xml @@ -80,7 +80,6 @@ Показване на картата на цял екран излезете от картата на цял екран Показване на картата на цял екран за редактиране на местоположението на маркера - Изход от режима за редактиране на местоположението на маркера Стартирай маршрута Спри маршрута Редактирай маршрута diff --git a/shared/src/commonMain/composeResources/values-bn/strings.xml b/shared/src/commonMain/composeResources/values-bn/strings.xml index 54f1797ab..59f202f67 100644 --- a/shared/src/commonMain/composeResources/values-bn/strings.xml +++ b/shared/src/commonMain/composeResources/values-bn/strings.xml @@ -1015,7 +1015,6 @@ মানচিত্রটি পূর্ণ পর্দা করুন পূর্ণ পর্দার মানচিত্র থেকে বের হতে মানচিত্রটি পূর্ণ পর্দা করুন এবং মার্কারের অবস্থান সম্পাদনা করুন - মার্কার অবস্থান সম্পাদনা মোড থেকে প্রস্থান করুন রুট শুরু করুন রুট বন্ধ করুন রুট সম্পাদনা করুন diff --git a/shared/src/commonMain/composeResources/values-ca/strings.xml b/shared/src/commonMain/composeResources/values-ca/strings.xml index d7ecea287..79c363afa 100644 --- a/shared/src/commonMain/composeResources/values-ca/strings.xml +++ b/shared/src/commonMain/composeResources/values-ca/strings.xml @@ -80,7 +80,6 @@ Mostra el mapa a pantalla completa sortir del mapa a pantalla completa Mostra el mapa a pantalla completa i edita la ubicació del marcador - Surt del mode d'edició de la ubicació del marcador Inicia la ruta Atura la ruta Edita la ruta diff --git a/shared/src/commonMain/composeResources/values-cs/strings.xml b/shared/src/commonMain/composeResources/values-cs/strings.xml index cc6addcb3..968b0c6d9 100644 --- a/shared/src/commonMain/composeResources/values-cs/strings.xml +++ b/shared/src/commonMain/composeResources/values-cs/strings.xml @@ -792,7 +792,6 @@ Zobrazit mapu na celou obrazovku ukončit zobrazení mapy na celou obrazovku Zobrazit mapu na celou obrazovku a upravit polohu značky - Ukončit režim úpravy polohy značky Spustit trasu Zastavit trasu Upravit trasu diff --git a/shared/src/commonMain/composeResources/values-da/strings.xml b/shared/src/commonMain/composeResources/values-da/strings.xml index 337e236eb..f473995bd 100644 --- a/shared/src/commonMain/composeResources/values-da/strings.xml +++ b/shared/src/commonMain/composeResources/values-da/strings.xml @@ -1257,7 +1257,6 @@ lukke popuppen og fortsætte tutorialen Start lydfyr ved dette mærke Vis kortet i fuld skærm, og rediger mærkets placering - Afslut redigering af mærkets placering På %1$s lige før %2$s På %1$s lige efter %2$s Nær krydset ved %1$s diff --git a/shared/src/commonMain/composeResources/values-de/strings.xml b/shared/src/commonMain/composeResources/values-de/strings.xml index 641a4762d..137e9553d 100644 --- a/shared/src/commonMain/composeResources/values-de/strings.xml +++ b/shared/src/commonMain/composeResources/values-de/strings.xml @@ -1257,7 +1257,6 @@ das Popup schließen und das Tutorial fortsetzen Audiobeacon an dieser Markierung starten Karte im Vollbild anzeigen und den Markierungsstandort bearbeiten - Bearbeitungsmodus für Markierungsstandort beenden Auf %1$s, kurz vor %2$s Auf %1$s, kurz nach %2$s In der Nähe der Kreuzung von %1$s diff --git a/shared/src/commonMain/composeResources/values-el/strings.xml b/shared/src/commonMain/composeResources/values-el/strings.xml index 068034ea1..7b7e34fd3 100644 --- a/shared/src/commonMain/composeResources/values-el/strings.xml +++ b/shared/src/commonMain/composeResources/values-el/strings.xml @@ -1257,7 +1257,6 @@ κλείσετε το αναδυόμενο παράθυρο και να συνεχίσετε τον οδηγό Έναρξη ηχητικού σήματος σε αυτόν τον δείκτη Μεγιστοποίηση του χάρτη σε πλήρη οθόνη και επεξεργασία της θέσης του δείκτη - Έξοδος από τη λειτουργία επεξεργασίας της θέσης του δείκτη Στην %1$s, λίγο πριν από %2$s Στην %1$s, λίγο μετά από %2$s Κοντά στη διασταύρωση %1$s diff --git a/shared/src/commonMain/composeResources/values-en-rGB/strings.xml b/shared/src/commonMain/composeResources/values-en-rGB/strings.xml index ffae1de79..4edb018d4 100644 --- a/shared/src/commonMain/composeResources/values-en-rGB/strings.xml +++ b/shared/src/commonMain/composeResources/values-en-rGB/strings.xml @@ -1257,7 +1257,6 @@ close the popup and continue the tutorial Start audio beacon at this marker Make the map full screen and edit the marker location - Exit marker location editing mode On %1$s, just before %2$s On %1$s, just after %2$s Near the intersection of %1$s diff --git a/shared/src/commonMain/composeResources/values-es/strings.xml b/shared/src/commonMain/composeResources/values-es/strings.xml index 299289e8e..4d96c70b6 100644 --- a/shared/src/commonMain/composeResources/values-es/strings.xml +++ b/shared/src/commonMain/composeResources/values-es/strings.xml @@ -1261,7 +1261,6 @@ cerrar la ventana emergente y continuar el tutorial Iniciar la señal de audio en este marcador Poner el mapa en pantalla completa y editar la ubicación del marcador - Salir del modo de edición de la ubicación del marcador {es:En %1$s}, justo antes {es:de %2$s} {es:En %1$s}, justo después {es:de %2$s} Cerca de la intersección de %1$s diff --git a/shared/src/commonMain/composeResources/values-et/strings.xml b/shared/src/commonMain/composeResources/values-et/strings.xml index 28e2494fc..f74d29c1f 100644 --- a/shared/src/commonMain/composeResources/values-et/strings.xml +++ b/shared/src/commonMain/composeResources/values-et/strings.xml @@ -79,7 +79,6 @@ Kuva kaart täisekraanil väljuda kaardi täisekraanvaatest Kuva kaart täisekraanil ja muuda markeri asukohta - Välju markeri asukoha muutmise režiimist Alusta marsruuti Peata marsruut Muuda marsruuti diff --git a/shared/src/commonMain/composeResources/values-fa/strings.xml b/shared/src/commonMain/composeResources/values-fa/strings.xml index f8e284cb3..cf7759b9d 100644 --- a/shared/src/commonMain/composeResources/values-fa/strings.xml +++ b/shared/src/commonMain/composeResources/values-fa/strings.xml @@ -1258,7 +1258,6 @@ پنجره‌ی پاپ‌آپ را ببندید و آموزش را ادامه دهید فعال‌سازی جهت‌نمای صوتی روی این نشانه نقشه را در حالت تمام‌صفحه قرار دهید و مکان نشانه را ویرایش کنید - از حالت ویرایش مکان نشانه خارج شوید در %1$s درست قبل از %2$s در %1$s درست بعد از %2$s نزدیک تقاطع %1$s diff --git a/shared/src/commonMain/composeResources/values-fi/strings.xml b/shared/src/commonMain/composeResources/values-fi/strings.xml index a3c638782..436774b85 100644 --- a/shared/src/commonMain/composeResources/values-fi/strings.xml +++ b/shared/src/commonMain/composeResources/values-fi/strings.xml @@ -1257,7 +1257,6 @@ sulje ponnahdusikkuna ja jatka tutoriaalia Käynnistä äänimajakka tälle merkitsimelle Näytä kartta koko näytöllä ja muokkaa merkitsimen sijaintia - Poistu merkitsimen sijainnin muokkaustilasta {Kadulla %1$s} juuri ennen kohdetta %2$s {Kadulla %1$s} juuri kohteen %2$s jälkeen Lähellä risteystä %1$s diff --git a/shared/src/commonMain/composeResources/values-fr-rCA/strings.xml b/shared/src/commonMain/composeResources/values-fr-rCA/strings.xml index ae0094778..b74224bfc 100644 --- a/shared/src/commonMain/composeResources/values-fr-rCA/strings.xml +++ b/shared/src/commonMain/composeResources/values-fr-rCA/strings.xml @@ -1261,7 +1261,6 @@ fermer la fenêtre contextuelle et continuer le tutoriel Démarrer la balise sonore sur ce marqueur Afficher la carte en plein écran et modifier l’emplacement du marqueur - Quitter le mode d’édition de l’emplacement du marqueur {fr:Sur %1$s} juste {fr:avant %2$s} {fr:Sur %1$s} juste {fr:après %2$s} Près de l’intersection de %1$s diff --git a/shared/src/commonMain/composeResources/values-fr/strings.xml b/shared/src/commonMain/composeResources/values-fr/strings.xml index e7f8c0a04..541573674 100644 --- a/shared/src/commonMain/composeResources/values-fr/strings.xml +++ b/shared/src/commonMain/composeResources/values-fr/strings.xml @@ -1261,7 +1261,6 @@ fermer la fenêtre contextuelle et continuer le tutoriel Démarrer la balise sonore sur ce marqueur Afficher la carte en plein écran et modifier l’emplacement du marqueur - Quitter le mode d’édition de l’emplacement du marqueur {fr:Sur %1$s} juste {fr:avant %2$s} {fr:Sur %1$s} juste {fr:après %2$s} Près de l’intersection de %1$s diff --git a/shared/src/commonMain/composeResources/values-ha/strings.xml b/shared/src/commonMain/composeResources/values-ha/strings.xml index ae7526441..69ab7174d 100644 --- a/shared/src/commonMain/composeResources/values-ha/strings.xml +++ b/shared/src/commonMain/composeResources/values-ha/strings.xml @@ -258,7 +258,6 @@ Faɗaɗa taswira zuwa cikakken allo fita daga taswirar cikakken allo Faɗaɗa taswira zuwa cikakken allo don gyara wurin alama - Fita daga yanayin gyara wurin alama Fara Tafarki Tsayar da Tafarki Gyara Tafarki diff --git a/shared/src/commonMain/composeResources/values-hi/strings.xml b/shared/src/commonMain/composeResources/values-hi/strings.xml index 3d25446b0..7f3506647 100644 --- a/shared/src/commonMain/composeResources/values-hi/strings.xml +++ b/shared/src/commonMain/composeResources/values-hi/strings.xml @@ -64,7 +64,6 @@ मानचित्र को पूर्ण स्क्रीन बनाएँ पूर्ण स्क्रीन मानचित्र से बाहर निकलें मानचित्र को पूर्ण स्क्रीन बनाएँ और मार्कर का स्थान संपादित करें - मार्कर स्थान संपादन मोड से बाहर निकलें मार्ग शुरू करें मार्ग रोकें मार्ग संपादित करें diff --git a/shared/src/commonMain/composeResources/values-hr/strings.xml b/shared/src/commonMain/composeResources/values-hr/strings.xml index 268074af0..9ce368c71 100644 --- a/shared/src/commonMain/composeResources/values-hr/strings.xml +++ b/shared/src/commonMain/composeResources/values-hr/strings.xml @@ -792,7 +792,6 @@ Prikaži kartu preko cijelog zaslona izašli iz karte preko cijelog zaslona Prikaži kartu preko cijelog zaslona i uredi lokaciju oznake - Izađi iz načina uređivanja lokacije oznake Pokreni rutu Zaustavi rutu Uredi rutu diff --git a/shared/src/commonMain/composeResources/values-hu/strings.xml b/shared/src/commonMain/composeResources/values-hu/strings.xml index 2a8090b93..f75b95cbf 100644 --- a/shared/src/commonMain/composeResources/values-hu/strings.xml +++ b/shared/src/commonMain/composeResources/values-hu/strings.xml @@ -792,7 +792,6 @@ Térkép teljes képernyőre állítása kilépj a teljes képernyős térképből Térkép teljes képernyőre állítása és a jelölő helyzetének szerkesztése - Kilépés a jelölő helyzetének szerkesztési módjából Útvonal indítása Útvonal leállítása Útvonal szerkesztése diff --git a/shared/src/commonMain/composeResources/values-in/strings.xml b/shared/src/commonMain/composeResources/values-in/strings.xml index ecd51a9e1..7a34fc05f 100644 --- a/shared/src/commonMain/composeResources/values-in/strings.xml +++ b/shared/src/commonMain/composeResources/values-in/strings.xml @@ -792,7 +792,6 @@ Menampilkan peta dalam layar penuh keluar dari peta layar penuh Menampilkan peta dalam layar penuh dan mengedit lokasi penanda - Keluar dari mode pengeditan lokasi penanda Mulai Rute Hentikan Rute Edit Rute diff --git a/shared/src/commonMain/composeResources/values-is/strings.xml b/shared/src/commonMain/composeResources/values-is/strings.xml index 693b7c39f..cce7a538f 100644 --- a/shared/src/commonMain/composeResources/values-is/strings.xml +++ b/shared/src/commonMain/composeResources/values-is/strings.xml @@ -1257,7 +1257,6 @@ loka sprettiglugga og halda áfram með leiðsögunámskeiðið Kveikja á hljóðvita á þessu merki Gera kortið fullskjá og breyta staðsetningu merkisins - Hætta að breyta staðsetningu merkisins Á %1$s rétt fyrir %2$s Á %1$s rétt eftir %2$s Nálægt gatnamótum %1$s diff --git a/shared/src/commonMain/composeResources/values-it/strings.xml b/shared/src/commonMain/composeResources/values-it/strings.xml index 7f5f6acde..c9cf03888 100644 --- a/shared/src/commonMain/composeResources/values-it/strings.xml +++ b/shared/src/commonMain/composeResources/values-it/strings.xml @@ -1261,7 +1261,6 @@ chiudi il popup e continua il tutorial Avviare l'audiofaro su questo indicatore Mettere la mappa a schermo intero e modificare la posizione dell'indicatore - Uscire dalla modalità di modifica della posizione dell'indicatore {it:Su %1$s} appena prima {it:di %2$s} {it:Su %1$s} appena dopo %2$s Nei pressi dell'incrocio tra %1$s diff --git a/shared/src/commonMain/composeResources/values-ja/strings.xml b/shared/src/commonMain/composeResources/values-ja/strings.xml index 7cd854029..8e3d6f77e 100644 --- a/shared/src/commonMain/composeResources/values-ja/strings.xml +++ b/shared/src/commonMain/composeResources/values-ja/strings.xml @@ -1253,7 +1253,6 @@ ポップアップを閉じてチュートリアルを続行します このマーカーで音声ビーコンを開始する 地図を全画面表示にしてマーカーの位置を編集する - マーカーの位置編集モードを終了する %1$s(%2$s の手前) %1$s(%2$s の直後) %1$s の交差点付近 diff --git a/shared/src/commonMain/composeResources/values-ko/strings.xml b/shared/src/commonMain/composeResources/values-ko/strings.xml index 63f4fdad0..f5cbd9ff9 100644 --- a/shared/src/commonMain/composeResources/values-ko/strings.xml +++ b/shared/src/commonMain/composeResources/values-ko/strings.xml @@ -80,7 +80,6 @@ 지도를 전체 화면으로 표시 전체 화면 지도 종료 지도를 전체 화면으로 표시하고 마커 위치 편집 - 마커 위치 편집 모드 종료 경로 시작 경로 중지 경로 편집 diff --git a/shared/src/commonMain/composeResources/values-mr/strings.xml b/shared/src/commonMain/composeResources/values-mr/strings.xml index 1354459e9..ec95762a6 100644 --- a/shared/src/commonMain/composeResources/values-mr/strings.xml +++ b/shared/src/commonMain/composeResources/values-mr/strings.xml @@ -1028,7 +1028,6 @@ नकाशा पूर्णस्क्रीन करा पूर्णस्क्रीन नकाशामधून बाहेर पडा नकाशा पूर्णस्क्रीन करा आणि मार्करचे स्थान संपादित करा - मार्कर स्थान संपादन मोडमधून बाहेर पडा मार्ग सुरू करा मार्ग थांबवा मार्ग संपादित करा diff --git a/shared/src/commonMain/composeResources/values-nb/strings.xml b/shared/src/commonMain/composeResources/values-nb/strings.xml index fbb2d3dff..2f7e5b34e 100644 --- a/shared/src/commonMain/composeResources/values-nb/strings.xml +++ b/shared/src/commonMain/composeResources/values-nb/strings.xml @@ -1257,7 +1257,6 @@ lukk popupen og fortsett gjennomgangen Start lydsignal på denne markøren Vis kartet i fullskjerm og rediger markørens plassering - Avslutt redigering av markørens plassering På %1$s rett før %2$s På %1$s rett etter %2$s Nær veikrysset %1$s diff --git a/shared/src/commonMain/composeResources/values-nl/strings.xml b/shared/src/commonMain/composeResources/values-nl/strings.xml index 7bfbae06f..a72ee5f35 100644 --- a/shared/src/commonMain/composeResources/values-nl/strings.xml +++ b/shared/src/commonMain/composeResources/values-nl/strings.xml @@ -1257,7 +1257,6 @@ de pop-up sluiten en doorgaan met de tutorial Audiobaken starten op deze markering De kaart op volledig scherm weergeven en de locatie van de markering bewerken - Bewerking van de markeringslocatie afsluiten Op %1$s net voor %2$s Op %1$s net na %2$s Bij het kruispunt van %1$s diff --git a/shared/src/commonMain/composeResources/values-pl/strings.xml b/shared/src/commonMain/composeResources/values-pl/strings.xml index fec17f5c8..9aeb9787c 100644 --- a/shared/src/commonMain/composeResources/values-pl/strings.xml +++ b/shared/src/commonMain/composeResources/values-pl/strings.xml @@ -1265,7 +1265,6 @@ zamknąć okienko i kontynuować samouczek Uruchom dźwięk naprowadzający na tym znaczniku wyświetlić mapę w trybie pełnoekranowym i edytować lokalizację znacznika - zakończ tryb edycji lokalizacji znacznika Na %1$s tuż przed %2$s Na %1$s tuż po %2$s W pobliżu skrzyżowania %1$s diff --git a/shared/src/commonMain/composeResources/values-pt-rBR/strings.xml b/shared/src/commonMain/composeResources/values-pt-rBR/strings.xml index aab01823e..60d931b96 100644 --- a/shared/src/commonMain/composeResources/values-pt-rBR/strings.xml +++ b/shared/src/commonMain/composeResources/values-pt-rBR/strings.xml @@ -1261,7 +1261,6 @@ fechar o pop‑up e continuar o tutorial Iniciar Sinalizador Sonoro neste Favorito Exibir o mapa em tela cheia e editar a localização do favorito - Sair do modo de edição da localização do favorito {pt:Em %1$s}, logo antes {pt:de %2$s} {pt:Em %1$s}, logo {pt:após %2$s} Perto do cruzamento de %1$s diff --git a/shared/src/commonMain/composeResources/values-pt/strings.xml b/shared/src/commonMain/composeResources/values-pt/strings.xml index eb1395576..141f4b9b2 100644 --- a/shared/src/commonMain/composeResources/values-pt/strings.xml +++ b/shared/src/commonMain/composeResources/values-pt/strings.xml @@ -1261,7 +1261,6 @@ fechar o pop‑up e continuar o tutorial Iniciar Sinal de Áudio neste Marco colocar o mapa em ecrã inteiro e editar a localização do marco - sair do modo de edição da localização do marco {pt:Em %1$s}, pouco antes {pt:de %2$s} {pt:Em %1$s}, pouco depois {pt:de %2$s} Perto do cruzamento de %1$s diff --git a/shared/src/commonMain/composeResources/values-ro/strings.xml b/shared/src/commonMain/composeResources/values-ro/strings.xml index 2abf83fbb..96ed049f4 100644 --- a/shared/src/commonMain/composeResources/values-ro/strings.xml +++ b/shared/src/commonMain/composeResources/values-ro/strings.xml @@ -64,7 +64,6 @@ Afișează harta pe tot ecranul ieși din harta pe tot ecranul Afișează harta pe tot ecranul și editează locația marcajului - Ieși din modul de editare a locației marcajului Pornește ruta Oprește ruta Editează ruta diff --git a/shared/src/commonMain/composeResources/values-ru/strings.xml b/shared/src/commonMain/composeResources/values-ru/strings.xml index 2d8722554..39ec7ede5 100644 --- a/shared/src/commonMain/composeResources/values-ru/strings.xml +++ b/shared/src/commonMain/composeResources/values-ru/strings.xml @@ -1265,7 +1265,6 @@ закрыть всплывающее окно и продолжить руководство Включить звуковой маяк на этой отметке Развернуть карту на весь экран и отредактировать местоположение отметки - Выйти из режима редактирования местоположения отметки На %1$s, прямо перед %2$s На %1$s, сразу после %2$s Рядом с перекрёстком %1$s diff --git a/shared/src/commonMain/composeResources/values-sk/strings.xml b/shared/src/commonMain/composeResources/values-sk/strings.xml index 83d47db8c..3f2e34a08 100644 --- a/shared/src/commonMain/composeResources/values-sk/strings.xml +++ b/shared/src/commonMain/composeResources/values-sk/strings.xml @@ -792,7 +792,6 @@ Zobraziť mapu na celú obrazovku ukončiť zobrazenie mapy na celú obrazovku Zobraziť mapu na celú obrazovku a upraviť polohu značky - Ukončiť režim úpravy polohy značky Spustiť trasu Zastaviť trasu Upraviť trasu diff --git a/shared/src/commonMain/composeResources/values-sl/strings.xml b/shared/src/commonMain/composeResources/values-sl/strings.xml index 486e929bc..935669764 100644 --- a/shared/src/commonMain/composeResources/values-sl/strings.xml +++ b/shared/src/commonMain/composeResources/values-sl/strings.xml @@ -80,7 +80,6 @@ Preklopi zemljevid v celozaslonski način zaprete celozaslonski zemljevid Preklopi zemljevid v celozaslonski način in uredi lokacijo oznake - Zapri način urejanja lokacije oznake Zaženi pot Zaustavi pot Uredi pot diff --git a/shared/src/commonMain/composeResources/values-sr/strings.xml b/shared/src/commonMain/composeResources/values-sr/strings.xml index ba3b8087a..4d1436034 100644 --- a/shared/src/commonMain/composeResources/values-sr/strings.xml +++ b/shared/src/commonMain/composeResources/values-sr/strings.xml @@ -436,7 +436,6 @@ Прикажи мапу преко целог екрана изађете из мапе преко целог екрана Прикажи мапу преко целог екрана и уреди локацију маркера - Изађи из режима уређивања локације маркера Покрени руту Заустави руту Уреди руту diff --git a/shared/src/commonMain/composeResources/values-sv/strings.xml b/shared/src/commonMain/composeResources/values-sv/strings.xml index 6079e8b37..efc38686c 100644 --- a/shared/src/commonMain/composeResources/values-sv/strings.xml +++ b/shared/src/commonMain/composeResources/values-sv/strings.xml @@ -1257,7 +1257,6 @@ stänga popupen och fortsätta självstudien Starta ljudfyr på den här platsmarkören Gör kartan till helskärm och redigera platsmarkörens läge - Avsluta redigering av platsmarkörens läge På %1$s precis innan %2$s På %1$s precis efter %2$s Nära korsningen vid %1$s diff --git a/shared/src/commonMain/composeResources/values-sw/strings.xml b/shared/src/commonMain/composeResources/values-sw/strings.xml index 6e97129ac..58e5e653b 100644 --- a/shared/src/commonMain/composeResources/values-sw/strings.xml +++ b/shared/src/commonMain/composeResources/values-sw/strings.xml @@ -837,7 +837,6 @@ Fanya ramani ionekane kwenye skrini nzima kutoka kwenye ramani ya skrini nzima Fanya ramani ionekane kwenye skrini nzima na uhariri mahali pa alama - Toka kwenye hali ya kuhariri mahali pa alama Anzisha Njia Simamisha Njia Hariri Njia diff --git a/shared/src/commonMain/composeResources/values-ta/strings.xml b/shared/src/commonMain/composeResources/values-ta/strings.xml index d7b8433e0..7cfc50792 100644 --- a/shared/src/commonMain/composeResources/values-ta/strings.xml +++ b/shared/src/commonMain/composeResources/values-ta/strings.xml @@ -80,7 +80,6 @@ வரைபடத்தை முழுத்திரையாக்கு முழுத்திரை வரைபடத்திலிருந்து வெளியேற வரைபடத்தை முழுத்திரையாக்கி குறிப்பானின் இருப்பிடத்தைத் திருத்து - குறிப்பான் இருப்பிடத் திருத்த முறையிலிருந்து வெளியேறு பாதையைத் தொடங்கு பாதையை நிறுத்து பாதையைத் திருத்து diff --git a/shared/src/commonMain/composeResources/values-te/strings.xml b/shared/src/commonMain/composeResources/values-te/strings.xml index b78d54bfd..8010bc0c4 100644 --- a/shared/src/commonMain/composeResources/values-te/strings.xml +++ b/shared/src/commonMain/composeResources/values-te/strings.xml @@ -80,7 +80,6 @@ మ్యాప్‌ను పూర్తి తెరగా చేయడానికి పూర్తి తెర మ్యాప్ నుండి నిష్క్రమించడానికి మ్యాప్‌ను పూర్తి తెరగా చేసి మార్కర్ స్థానాన్ని సవరించడానికి - మార్కర్ స్థాన సవరణ మోడ్ నుండి నిష్క్రమించడానికి రూట్‌ను ప్రారంభించు రూట్‌ను ఆపివేయి రూట్‌ను సవరించు diff --git a/shared/src/commonMain/composeResources/values-th/strings.xml b/shared/src/commonMain/composeResources/values-th/strings.xml index 7484e0e3a..34ea359b9 100644 --- a/shared/src/commonMain/composeResources/values-th/strings.xml +++ b/shared/src/commonMain/composeResources/values-th/strings.xml @@ -258,7 +258,6 @@ แสดงแผนที่แบบเต็มหน้าจอ ออกจากแผนที่แบบเต็มหน้าจอ แสดงแผนที่แบบเต็มหน้าจอและแก้ไขตำแหน่งหมุด - ออกจากโหมดแก้ไขตำแหน่งหมุด เริ่มเส้นทาง หยุดเส้นทาง แก้ไขเส้นทาง diff --git a/shared/src/commonMain/composeResources/values-tr/strings.xml b/shared/src/commonMain/composeResources/values-tr/strings.xml index ad943f216..637cd40f8 100644 --- a/shared/src/commonMain/composeResources/values-tr/strings.xml +++ b/shared/src/commonMain/composeResources/values-tr/strings.xml @@ -86,7 +86,6 @@ Haritayı tam ekran yapın Tam ekran haritadan çıkın Haritayı tam ekran yapın ve kayıtlı nokta konumunu düzenleyin - Kayıtlı nokta konumu düzenleme modundan çıkın Rotayı Başlat Rotayı Durdur Rotayı Düzenle diff --git a/shared/src/commonMain/composeResources/values-uk/strings.xml b/shared/src/commonMain/composeResources/values-uk/strings.xml index d894a0b54..414c4f2bc 100644 --- a/shared/src/commonMain/composeResources/values-uk/strings.xml +++ b/shared/src/commonMain/composeResources/values-uk/strings.xml @@ -1265,7 +1265,6 @@ закрити спливаюче вікно і продовжити інтерактивний тур Запустити звуковий маячок на цій мітці Розгорнути карту на весь екран та відредагувати місцезнаходження мітки - Вийти з режиму редагування місцезнаходження мітки На %1$s, одразу перед %2$s На %1$s, одразу після %2$s Поблизу перехрестя %1$s diff --git a/shared/src/commonMain/composeResources/values-ur/strings.xml b/shared/src/commonMain/composeResources/values-ur/strings.xml index 392d6d649..9509a7e50 100644 --- a/shared/src/commonMain/composeResources/values-ur/strings.xml +++ b/shared/src/commonMain/composeResources/values-ur/strings.xml @@ -614,7 +614,6 @@ نقشے کو مکمل اسکرین بنائیں مکمل اسکرین نقشے سے باہر نکلیں نقشے کو مکمل اسکرین بنائیں اور مارکر کا مقام ترمیم کریں - مارکر مقام ترمیمی موڈ سے باہر نکلیں روٹ شروع کریں روٹ روکیں روٹ میں ترمیم کریں diff --git a/shared/src/commonMain/composeResources/values-vi/strings.xml b/shared/src/commonMain/composeResources/values-vi/strings.xml index 0418b71c2..1f2566860 100644 --- a/shared/src/commonMain/composeResources/values-vi/strings.xml +++ b/shared/src/commonMain/composeResources/values-vi/strings.xml @@ -614,7 +614,6 @@ Xem bản đồ toàn màn hình Thoát bản đồ toàn màn hình Xem bản đồ toàn màn hình và chỉnh sửa vị trí điểm đánh dấu - Thoát chế độ chỉnh sửa vị trí điểm đánh dấu Bắt đầu lộ trình Dừng lộ trình Chỉnh sửa lộ trình diff --git a/shared/src/commonMain/composeResources/values-zh-rCN/strings.xml b/shared/src/commonMain/composeResources/values-zh-rCN/strings.xml index c0de04088..3f13110cd 100644 --- a/shared/src/commonMain/composeResources/values-zh-rCN/strings.xml +++ b/shared/src/commonMain/composeResources/values-zh-rCN/strings.xml @@ -64,7 +64,6 @@ 将地图全屏显示 退出全屏地图 将地图全屏显示并编辑标记点位置 - 退出标记点位置编辑模式 开始路线 停止路线 编辑路线 diff --git a/shared/src/commonMain/composeResources/values/strings.xml b/shared/src/commonMain/composeResources/values/strings.xml index 41a6f1420..2b46f504f 100644 --- a/shared/src/commonMain/composeResources/values/strings.xml +++ b/shared/src/commonMain/composeResources/values/strings.xml @@ -186,8 +186,10 @@ Exit the full-screen map Make the map full screen and edit the marker location - - Exit marker location editing mode + + Edit location + + Drag the map to move the marker's location. Tap on Done to keep the new location, or Cancel to leave it unchanged. Start Route diff --git a/shared/src/commonMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/MapContainerLibre.kt b/shared/src/commonMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/MapContainerLibre.kt index fd3898457..548c79603 100644 --- a/shared/src/commonMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/MapContainerLibre.kt +++ b/shared/src/commonMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/MapContainerLibre.kt @@ -11,7 +11,10 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap @@ -91,7 +94,8 @@ fun FullScreenMapFab( * @param beaconLocation An optional location to show a beacon marker * @param routeData Optional route data to display route waypoint markers * @param modifier Modifier for the map container - * @param editBeaconLocation If true, the beacon location tracks the camera center + * @param onBeaconLocationEdited If non-null, the beacon marker is pinned to the camera center so + * that panning the map moves it, and each new location is reported through this callback * @param currentBeaconWaypointIndex The waypoint index the beacon marker currently represents * (0-based); shown as a 1-based number label on the beacon marker, matching route waypoint markers * @param onMapLongClick Callback when the map is long-pressed, receives the location @@ -119,7 +123,7 @@ fun MapContainerLibre( beaconLocation: LngLatAlt?, routeData: RouteWithMarkers?, modifier: Modifier = Modifier, - editBeaconLocation: Boolean = false, + onBeaconLocationEdited: ((LngLatAlt) -> Unit)? = null, currentBeaconWaypointIndex: Int = 0, onMapLongClick: ((LngLatAlt) -> Boolean)? = null, baseStyle: BaseStyle, @@ -154,9 +158,20 @@ fun MapContainerLibre( } val styleState = rememberStyleState() - if (editBeaconLocation && (beaconLocation != null)) { - beaconLocation.longitude = cameraState.position.target.longitude - beaconLocation.latitude = cameraState.position.target.latitude + val editingBeacon = onBeaconLocationEdited != null + val currentOnBeaconLocationEdited by rememberUpdatedState(onBeaconLocationEdited) + if (editingBeacon) { + LaunchedEffect(cameraState) { + snapshotFlow { cameraState.position.target }.collect { target -> + currentOnBeaconLocationEdited?.invoke(LngLatAlt(target.longitude, target.latitude)) + } + } + } + val drawnBeaconLocation = if (editingBeacon) { + val target = cameraState.position.target + LngLatAlt(target.longitude, target.latitude) + } else { + beaconLocation } val navigationMarker = painterResource(Res.drawable.navigation) @@ -285,10 +300,10 @@ fun MapContainerLibre( } // Draw beacon location marker last to prevent waypoint marker being drawn on top. - if (beaconLocation != null) { + if (drawnBeaconLocation != null) { val position = Position( - latitude = beaconLocation.latitude, - longitude = beaconLocation.longitude + latitude = drawnBeaconLocation.latitude, + longitude = drawnBeaconLocation.longitude ) val beaconLocationGeoJson = rememberGeoJsonSource( diff --git a/shared/src/commonMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/PlatformMapContainer.kt b/shared/src/commonMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/PlatformMapContainer.kt index 1acf080ae..8d7364aaf 100644 --- a/shared/src/commonMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/PlatformMapContainer.kt +++ b/shared/src/commonMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/PlatformMapContainer.kt @@ -23,4 +23,5 @@ expect fun PlatformMapContainer( extractGeometry: Geometry? = null, forceOnlineTiles: Boolean = false, onInteractionChanged: (Boolean) -> Unit = {}, + onBeaconLocationEdited: ((LngLatAlt) -> Unit)? = null, ) diff --git a/shared/src/commonMain/kotlin/org/scottishtecharmy/soundscape/screens/home/locationDetails/SharedSaveAndEditMarkerScreen.kt b/shared/src/commonMain/kotlin/org/scottishtecharmy/soundscape/screens/home/locationDetails/SharedSaveAndEditMarkerScreen.kt index fc7ebf1fd..d865daa9b 100644 --- a/shared/src/commonMain/kotlin/org/scottishtecharmy/soundscape/screens/home/locationDetails/SharedSaveAndEditMarkerScreen.kt +++ b/shared/src/commonMain/kotlin/org/scottishtecharmy/soundscape/screens/home/locationDetails/SharedSaveAndEditMarkerScreen.kt @@ -11,7 +11,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.EditLocationAlt import androidx.compose.material3.AlertDialog +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -22,7 +26,9 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.backhandler.BackHandler import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import org.jetbrains.compose.resources.stringResource @@ -35,7 +41,8 @@ import org.scottishtecharmy.soundscape.resources.Res import org.scottishtecharmy.soundscape.resources.annotation_description_hint import org.scottishtecharmy.soundscape.resources.general_alert_cancel import org.scottishtecharmy.soundscape.resources.general_alert_done -import org.scottishtecharmy.soundscape.resources.location_detail_exit_full_screen_for_edit_hint +import org.scottishtecharmy.soundscape.resources.location_detail_adjust_marker_location +import org.scottishtecharmy.soundscape.resources.location_detail_adjust_marker_location_instructions import org.scottishtecharmy.soundscape.resources.location_detail_full_screen_for_edit_hint import org.scottishtecharmy.soundscape.resources.marker_name_description_hint import org.scottishtecharmy.soundscape.resources.markers_action_delete @@ -47,7 +54,6 @@ import org.scottishtecharmy.soundscape.resources.settings_reset_dialog_title import org.scottishtecharmy.soundscape.resources.ui_continue import org.scottishtecharmy.soundscape.resources.user_activity_save_marker_title import org.scottishtecharmy.soundscape.screens.home.data.LocationDescription -import org.scottishtecharmy.soundscape.screens.home.home.FullScreenMapFab import org.scottishtecharmy.soundscape.screens.home.home.PlatformMapContainer import org.scottishtecharmy.soundscape.screens.markers_routes.components.CustomButton import org.scottishtecharmy.soundscape.screens.markers_routes.components.CustomTextField @@ -60,6 +66,7 @@ import org.scottishtecharmy.soundscape.ui.theme.spacing * Shared screen for creating or editing a marker. * Shows name/annotation fields, a map preview, and save/delete controls. */ +@OptIn(ExperimentalComposeUiApi::class) @Composable fun SharedSaveAndEditMarkerScreen( locationDescription: LocationDescription, @@ -77,8 +84,21 @@ fun SharedSaveAndEditMarkerScreen( ) var name by rememberSaveable { mutableStateOf(locationDescription.name) } var annotation by rememberSaveable { mutableStateOf(locationDescription.description ?: "") } + // The marker location can be moved in adjust mode, where a full screen map keeps the marker + // pinned to its centre so that panning the map moves the marker. + var latitude by rememberSaveable { mutableStateOf(locationDescription.location.latitude) } + var longitude by rememberSaveable { mutableStateOf(locationDescription.location.longitude) } + val markerLocation = LngLatAlt(longitude, latitude) val isEditing = locationDescription.databaseId != 0L - val fullscreenMap = remember { mutableStateOf(false) } + var adjustingLocation by rememberSaveable { mutableStateOf(false) } + // Where the marker was when adjust mode was entered, so that Cancel can put it back. + var adjustStartLatitude by rememberSaveable { mutableStateOf(latitude) } + var adjustStartLongitude by rememberSaveable { mutableStateOf(longitude) } + val cancelAdjusting = { + latitude = adjustStartLatitude + longitude = adjustStartLongitude + adjustingLocation = false + } var mapInteracting by remember { mutableStateOf(false) } val contentScrollState = rememberScrollState() var showDeleteDialog by rememberSaveable { mutableStateOf(false) } @@ -110,28 +130,44 @@ fun SharedSaveAndEditMarkerScreen( ) } + // System back while adjusting behaves like Cancel rather than leaving the whole screen. + if (adjustingLocation) { + @Suppress("DEPRECATION") + BackHandler(enabled = true) { cancelAdjusting() } + } + Scaffold( modifier = Modifier.imePadding(), topBar = { - TextOnlyAppBar( - title = if (isEditing) stringResource(Res.string.markers_edit_screen_title_edit) - else stringResource(Res.string.user_activity_save_marker_title), - navigationButtonTitle = stringResource(Res.string.general_alert_cancel), - onNavigateUp = onCancel, - rightButtonTitle = stringResource(Res.string.general_alert_done), - onRightButton = { - val updated = LocationDescription( - name = name.ifBlank { locationDescription.name }, - description = annotation.ifBlank { null }, - location = locationDescription.location, - databaseId = locationDescription.databaseId, - ) - onSave(updated) - }, - ) + if (adjustingLocation) { + TextOnlyAppBar( + title = stringResource(Res.string.location_detail_adjust_marker_location), + navigationButtonTitle = stringResource(Res.string.general_alert_cancel), + onNavigateUp = cancelAdjusting, + rightButtonTitle = stringResource(Res.string.general_alert_done), + onRightButton = { adjustingLocation = false }, + ) + } else { + TextOnlyAppBar( + title = if (isEditing) stringResource(Res.string.markers_edit_screen_title_edit) + else stringResource(Res.string.user_activity_save_marker_title), + navigationButtonTitle = stringResource(Res.string.general_alert_cancel), + onNavigateUp = onCancel, + rightButtonTitle = stringResource(Res.string.general_alert_done), + onRightButton = { + val updated = LocationDescription( + name = name.ifBlank { locationDescription.name }, + description = annotation.ifBlank { null }, + location = markerLocation, + databaseId = locationDescription.databaseId, + ) + onSave(updated) + }, + ) + } }, bottomBar = { - if (isEditing && onDelete != null) { + if (isEditing && onDelete != null && !adjustingLocation) { Column(modifier = Modifier.smallPadding()) { CustomButton( onClick = { showDeleteDialog = true }, @@ -150,24 +186,60 @@ fun SharedSaveAndEditMarkerScreen( } }, floatingActionButton = { - if (showMap) FullScreenMapFab( - fullscreenMap = fullscreenMap, - modifier = Modifier.testTag("saveMarkerFullScreenMapFab"), - openMapHint = Res.string.location_detail_full_screen_for_edit_hint, - closeMapHint = Res.string.location_detail_exit_full_screen_for_edit_hint, - ) + if (showMap && !adjustingLocation) { + FloatingActionButton( + onClick = { + adjustStartLatitude = latitude + adjustStartLongitude = longitude + adjustingLocation = true + }, + containerColor = MaterialTheme.colorScheme.surfaceContainer, + contentColor = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.testTag("saveMarkerAdjustLocationButton"), + ) { + Icon( + imageVector = Icons.Rounded.EditLocationAlt, + tint = MaterialTheme.colorScheme.onSurface, + contentDescription = stringResource(Res.string.location_detail_full_screen_for_edit_hint), + ) + } + } }, ) { padding -> - if (fullscreenMap.value && showMap) { - PlatformMapContainer( - beaconLocation = locationDescription.location, - mapCenter = locationDescription.location, - allowScrolling = true, - userLocation = userLocation, - userSymbolRotation = heading, - routeData = null, - modifier = Modifier.fillMaxSize(), - ) + if (adjustingLocation && showMap) { + // Only the top padding is applied: the bottom padding still includes the Delete + // button's bar on the first frame, and the map's surface keeps that first, shorter + // size, leaving a black band at the bottom. + Column( + modifier = Modifier + .fillMaxSize() + .padding(top = padding.calculateTopPadding()) + ) { + Text( + text = stringResource(Res.string.location_detail_adjust_marker_location_instructions), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier + .fillMaxWidth() + .smallPadding() + .testTag("saveMarkerAdjustLocationInstructions"), + ) + // Fix the centre for this visit to adjust mode, otherwise every edit would + // re-centre the camera and fight the user's pan. + val editStartLocation = remember { markerLocation } + PlatformMapContainer( + beaconLocation = markerLocation, + mapCenter = editStartLocation, + allowScrolling = true, + userLocation = userLocation, + userSymbolRotation = heading, + routeData = null, + modifier = Modifier.fillMaxSize(), + onBeaconLocationEdited = { edited -> + latitude = edited.latitude + longitude = edited.longitude + }, + ) + } } else { Column( modifier = Modifier @@ -202,11 +274,11 @@ fun SharedSaveAndEditMarkerScreen( // Map showing the marker location if (showMap) { PlatformMapContainer( - mapCenter = locationDescription.location, + mapCenter = markerLocation, allowScrolling = false, userLocation = userLocation, userSymbolRotation = heading, - beaconLocation = locationDescription.location, + beaconLocation = markerLocation, routeData = null, modifier = Modifier .fillMaxWidth() diff --git a/shared/src/iosMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/IosMapContainer.kt b/shared/src/iosMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/IosMapContainer.kt index 73b98a8f6..11047639e 100644 --- a/shared/src/iosMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/IosMapContainer.kt +++ b/shared/src/iosMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/IosMapContainer.kt @@ -85,6 +85,7 @@ fun IosMapContainerLibre( extractGeometry: Geometry? = null, forceOnlineTiles: Boolean = false, onInteractionChanged: (Boolean) -> Unit = {}, + onBeaconLocationEdited: ((LngLatAlt) -> Unit)? = null, ) { val baseStyle = rememberIosMapBaseStyle(location = mapCenter, forceOnlineTiles = forceOnlineTiles) @@ -101,5 +102,6 @@ fun IosMapContainerLibre( baseStyle = baseStyle, extractGeometry = extractGeometry, onInteractionChanged = onInteractionChanged, + onBeaconLocationEdited = onBeaconLocationEdited, ) } diff --git a/shared/src/iosMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/PlatformMapContainer.kt b/shared/src/iosMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/PlatformMapContainer.kt index 9e243c2f7..b9dd2e163 100644 --- a/shared/src/iosMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/PlatformMapContainer.kt +++ b/shared/src/iosMain/kotlin/org/scottishtecharmy/soundscape/screens/home/home/PlatformMapContainer.kt @@ -19,6 +19,7 @@ actual fun PlatformMapContainer( extractGeometry: Geometry?, forceOnlineTiles: Boolean, onInteractionChanged: (Boolean) -> Unit, + onBeaconLocationEdited: ((LngLatAlt) -> Unit)?, ) { IosMapContainerLibre( mapCenter = mapCenter, @@ -32,5 +33,6 @@ actual fun PlatformMapContainer( extractGeometry = extractGeometry, forceOnlineTiles = forceOnlineTiles, onInteractionChanged = onInteractionChanged, + onBeaconLocationEdited = onBeaconLocationEdited, ) }