From 6d8723028188c47e60d782b68a6910202387bc3c Mon Sep 17 00:00:00 2001 From: shtrophic <47949835+shtrophic@users.noreply.github.com> Date: Sun, 1 Jun 2025 20:05:12 +0200 Subject: [PATCH 1/5] fix regressions in parseOpeningHours, ignore holidays (#1411) --- .../providers/openstreetmaps/OsmLocation.kt | 8 ++-- .../src/test/kotlin/OpeningHoursTest.kt | 39 +++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/data/locations/src/main/java/de/mm20/launcher2/locations/providers/openstreetmaps/OsmLocation.kt b/data/locations/src/main/java/de/mm20/launcher2/locations/providers/openstreetmaps/OsmLocation.kt index 7ead7b07..760f851e 100644 --- a/data/locations/src/main/java/de/mm20/launcher2/locations/providers/openstreetmaps/OsmLocation.kt +++ b/data/locations/src/main/java/de/mm20/launcher2/locations/providers/openstreetmaps/OsmLocation.kt @@ -388,6 +388,8 @@ internal fun parseOpeningSchedule( } } } + } else if (!rule.holidays.isNullOrEmpty()) { + continue // skip PH and SH entries } else if (!rule.times.isNullOrEmpty() || !rule.months.isNullOrEmpty()) { rulesMap.forEach { _, it -> it.add(rule) @@ -395,14 +397,12 @@ internal fun parseOpeningSchedule( } } - // Filter out rules that are not valid for the current year, month, and week. Hopefully, - // there is only one rule left per weekday. If not, skip that weekday. - val applicableRules = rulesMap.mapNotNull { (day, rules) -> + // Filter out rules that are not valid for the current year, month, and week. + val applicableRules = rulesMap.flatMap { (day, rules) -> rules.filterYears(localTime) .filterMonths(localTime) .filterNthDays(localTime) .map { it.copy(weekdays = listOf(day)) } - .singleOrNull() } val hours = mutableSetOf() diff --git a/data/locations/src/test/kotlin/OpeningHoursTest.kt b/data/locations/src/test/kotlin/OpeningHoursTest.kt index 59e413b7..37e94af6 100644 --- a/data/locations/src/test/kotlin/OpeningHoursTest.kt +++ b/data/locations/src/test/kotlin/OpeningHoursTest.kt @@ -302,4 +302,43 @@ class OpeningHoursTest { LocalDateTime.of(2025, Month.APRIL, 10, 0, 0) ) } + + @Test + fun regressTest0() { + OpeningSchedule.Hours( + setOf( + OpeningHours(dayOfWeek = DayOfWeek.MONDAY, startTime = LocalTime.of(8,30), duration = Duration.ofHours(3) + Duration.ofMinutes(30)), + OpeningHours(dayOfWeek = DayOfWeek.TUESDAY, startTime = LocalTime.of(8,30), duration = Duration.ofHours(3) + Duration.ofMinutes(30)), + OpeningHours(dayOfWeek = DayOfWeek.WEDNESDAY, startTime = LocalTime.of(8,30), duration = Duration.ofHours(3) + Duration.ofMinutes(30)), + OpeningHours(dayOfWeek = DayOfWeek.THURSDAY, startTime = LocalTime.of(8,30), duration = Duration.ofHours(3) + Duration.ofMinutes(30)), + OpeningHours(dayOfWeek = DayOfWeek.FRIDAY, startTime = LocalTime.of(8,30), duration = Duration.ofHours(3) + Duration.ofMinutes(30)), + + OpeningHours(dayOfWeek = DayOfWeek.MONDAY, startTime = LocalTime.of(14,0), duration = Duration.ofHours(2)), + OpeningHours(dayOfWeek = DayOfWeek.TUESDAY, startTime = LocalTime.of(14,0), duration = Duration.ofHours(2)), + OpeningHours(dayOfWeek = DayOfWeek.FRIDAY, startTime = LocalTime.of(14,0), duration = Duration.ofHours(2)), + + OpeningHours(dayOfWeek = DayOfWeek.THURSDAY, startTime = LocalTime.of(17,0), duration = Duration.ofHours(2)), + ) + ) assertEqualTo parseOpeningSchedule( + "Mo-Fr 08:30-12:00; Mo,Tu,Fr 14:00-16:00; Th 17:00-19:00" + ) + } + + @Test + fun regressTest1() { + OpeningSchedule.Hours( + setOf( + OpeningHours(dayOfWeek = DayOfWeek.TUESDAY, startTime = LocalTime.of(11,45), duration = Duration.ofHours(10) + Duration.ofMinutes(45)), + OpeningHours(dayOfWeek = DayOfWeek.WEDNESDAY, startTime = LocalTime.of(11,45), duration = Duration.ofHours(10) + Duration.ofMinutes(45)), + OpeningHours(dayOfWeek = DayOfWeek.THURSDAY, startTime = LocalTime.of(11,45), duration = Duration.ofHours(10) + Duration.ofMinutes(45)), + OpeningHours(dayOfWeek = DayOfWeek.FRIDAY, startTime = LocalTime.of(11,45), duration = Duration.ofHours(10) + Duration.ofMinutes(45)), + OpeningHours(dayOfWeek = DayOfWeek.SATURDAY, startTime = LocalTime.of(11,45), duration = Duration.ofHours(10) + Duration.ofMinutes(45)), + + OpeningHours(dayOfWeek = DayOfWeek.SUNDAY, startTime = LocalTime.of(11,30), duration = Duration.ofHours(11)), + // We don't track public holidays. Otherwise, we'd probably go mad! + ) + ) assertEqualTo parseOpeningSchedule( + "Tu-Sa 11:45-22:30; Su 11:30-22:30; PH 11:30-22:30" + ) + } } \ No newline at end of file From 534cbbbd30f2d5ee957b2f0e279f1f2dd001dfa4 Mon Sep 17 00:00:00 2001 From: Massimo Pissarello Date: Mon, 2 Jun 2025 01:05:23 +0000 Subject: [PATCH 2/5] Translated using Weblate (Italian) Currently translated at 100.0% (807 of 807 strings) Translation: Kvaesitso/i18n --- core/i18n/src/main/res/values-it/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/i18n/src/main/res/values-it/strings.xml b/core/i18n/src/main/res/values-it/strings.xml index b0bf4ccf..9111aad1 100644 --- a/core/i18n/src/main/res/values-it/strings.xml +++ b/core/i18n/src/main/res/values-it/strings.xml @@ -846,4 +846,11 @@ Widget Congratulazioni, ti sei chiuso fuori! Hai scoperto una combinazione di impostazioni che rende inaccessibili sia la ricerca sia le impostazioni, impedendoti di fatto di accedere al launcher. + Predefinito + Forme + Arrotondamento extra + Rettangolare + Forma di base + Vuoi davvero eliminare lo schema di forme %1$s? + Nuove forme \ No newline at end of file From 54ca58a17f2bfc90ae78ea19677f4a2d45e8c488 Mon Sep 17 00:00:00 2001 From: Bart Libert Date: Mon, 2 Jun 2025 06:54:25 +0000 Subject: [PATCH 3/5] Translated using Weblate (Dutch) Currently translated at 100.0% (807 of 807 strings) Translation: Kvaesitso/i18n --- core/i18n/src/main/res/values-nl/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/i18n/src/main/res/values-nl/strings.xml b/core/i18n/src/main/res/values-nl/strings.xml index 71e3a5a3..92799532 100644 --- a/core/i18n/src/main/res/values-nl/strings.xml +++ b/core/i18n/src/main/res/values-nl/strings.xml @@ -835,4 +835,11 @@ Widgets op het thuisscherm Proficiat, je hebt jezelf buiten gesloten! Je hebt een combinatie van instellingen ontdekt die zowel zoeken als de instellingen onbereikbaar maakt - je bent nu volledig uit de startschermapp gesloten. + Vormen + Standaard + Extra rond + Rechthoekig + Basisvorm + Weet je zeker dat je de vorm %1$s wil verwijderen? + Nieuwe vormen \ No newline at end of file From 0be42610c636fe567d2dc806bec29edaf4ac469d Mon Sep 17 00:00:00 2001 From: summoner001 Date: Sun, 1 Jun 2025 15:56:10 +0000 Subject: [PATCH 4/5] Translated using Weblate (Hungarian) Currently translated at 100.0% (807 of 807 strings) Translation: Kvaesitso/i18n --- core/i18n/src/main/res/values-hu/strings.xml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/core/i18n/src/main/res/values-hu/strings.xml b/core/i18n/src/main/res/values-hu/strings.xml index 7b41b874..d99b14e6 100644 --- a/core/i18n/src/main/res/values-hu/strings.xml +++ b/core/i18n/src/main/res/values-hu/strings.xml @@ -318,8 +318,8 @@ Internetes keresés Először be kell állítani ezt a bővítményt Beállítás - Időjárás-előrejelzés szolgáltató - Beállítás időjárás-előrejelzési szolgáltatónak + Időjáráselőrejelzési szolgáltató + Beállítás időjáráselőrejelzési szolgáltatónak Alulra Az akkumulátor jelenlegi töltöttségi szintjének megjelenítése, amikor az akkumulátor lemerült vagy töltődik Azon riasztások megjelenítése, amelyek a következő 8 órán belül megszólalnak @@ -379,7 +379,7 @@ Valutaváltó A billentyűzet automatikus megjelenítése az alkalmazásfiók megnyitásakor Szinkronizálja a modul tartalmát egy külső fájllal - Jelenleg időjárás-előrejelzés szolgáltatóként van beállítva + Jelenleg időjáráselőrejelzési szolgáltatóként van beállítva Weboldalak Egy weboldal előnézetének megjelenítése, ha a keresési lekérdezés egy webcím A kitűzött elemek sorrendjének módosítása @@ -835,4 +835,11 @@ Modulok Gratulálunk, kizárta saját magát! Felfedezte a beállítások olyan kombinációját, amely a keresést és a beállításokat is elérhetetlenné teszi — gyakorlatilag kizárja Önt az indítóból. + Alakzatok + Alapértelmezett + További lekerekítés + Négyszögletes + Alap alakzat + Biztosan törölni szeretné a(z) %1$s nevű alakzatsémát? + Új alakzatok \ No newline at end of file From 4bb092d98bf3b5bc0e93076918a8d1e7a2b1fc4c Mon Sep 17 00:00:00 2001 From: shtrophic <47949835+shtrophic@users.noreply.github.com> Date: Mon, 2 Jun 2025 20:33:30 +0200 Subject: [PATCH 5/5] DevicePoseProvider: be picky about locations (#1344) * Only take updated location when it is of better quality * Rename to AndroidLocation for clarification * be pedantic about licenses --- .../devicepose/DevicePoseProvider.kt | 32 ++++++++-------- .../de/mm20/launcher2/ktx/AndroidLocation.kt | 37 +++++++++++++++++++ .../java/de/mm20/launcher2/ktx/Boolean.kt | 13 ++++++- 3 files changed, 65 insertions(+), 17 deletions(-) create mode 100644 core/ktx/src/main/java/de/mm20/launcher2/ktx/AndroidLocation.kt diff --git a/core/devicepose/src/main/java/de/mm20/launcher2/devicepose/DevicePoseProvider.kt b/core/devicepose/src/main/java/de/mm20/launcher2/devicepose/DevicePoseProvider.kt index aa03f74d..7b13c408 100644 --- a/core/devicepose/src/main/java/de/mm20/launcher2/devicepose/DevicePoseProvider.kt +++ b/core/devicepose/src/main/java/de/mm20/launcher2/devicepose/DevicePoseProvider.kt @@ -18,6 +18,8 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.channelFlow +import de.mm20.launcher2.ktx.foldOrNull +import de.mm20.launcher2.ktx.isBetterThan import kotlinx.coroutines.flow.combine class DevicePoseProvider internal constructor( @@ -37,10 +39,16 @@ class DevicePoseProvider internal constructor( } fun getLocation(minTimeMs: Long = 1000, minDistanceM: Float = 1f) = channelFlow { + fun updateLocation(update: Location?) { + if (update == null) return + if (!update.isBetterThan(lastLocation)) return + lastLocation = update + updateDeclination(update) + trySend(update) + } + val locationCallback = LocationListenerCompat { - lastLocation = it - updateDeclination(it) - trySend(it) + updateLocation(it) } context.getSystemService() @@ -50,20 +58,14 @@ class DevicePoseProvider internal constructor( val hasCoarseAccess = context.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION) - val location = - (if (hasFineAccess) this@runCatching.getLastKnownLocation(LocationManager.GPS_PROVIDER) else null) - ?: if (hasCoarseAccess) this@runCatching.getLastKnownLocation( - LocationManager.NETWORK_PROVIDER - ) else null + val previousLocation = + hasFineAccess.foldOrNull { getLastKnownLocation(LocationManager.GPS_PROVIDER) } ?: + hasCoarseAccess.foldOrNull { getLastKnownLocation(LocationManager.NETWORK_PROVIDER) } - if (location != null) { - lastLocation = location - updateDeclination(location) - trySend(location) - } + updateLocation(previousLocation) if (hasFineAccess) { - this@runCatching.requestLocationUpdates( + requestLocationUpdates( LocationManager.GPS_PROVIDER, minTimeMs, minDistanceM, @@ -71,7 +73,7 @@ class DevicePoseProvider internal constructor( ) } if (hasCoarseAccess) { - this@runCatching.requestLocationUpdates( + requestLocationUpdates( LocationManager.NETWORK_PROVIDER, minTimeMs, minDistanceM, diff --git a/core/ktx/src/main/java/de/mm20/launcher2/ktx/AndroidLocation.kt b/core/ktx/src/main/java/de/mm20/launcher2/ktx/AndroidLocation.kt new file mode 100644 index 00000000..c1dbec4e --- /dev/null +++ b/core/ktx/src/main/java/de/mm20/launcher2/ktx/AndroidLocation.kt @@ -0,0 +1,37 @@ +package de.mm20.launcher2.ktx + +import kotlin.time.Duration.Companion.minutes +import kotlin.time.Duration.Companion.nanoseconds + +import android.location.Location as AndroidLocation + +/* https://github.com/streetcomplete/StreetComplete/blob/master/app/src/main/java/de/westnordost/streetcomplete/util/location/LocationUtils.kt + * GPL-3.0-or-later + */ +fun AndroidLocation.isBetterThan(previous: AndroidLocation?): Boolean { + if (longitude.isNaN() || latitude.isNaN()) return false + if (previous == null) return true + + val locationTimeDiff = elapsedRealtimeNanos.nanoseconds - previous.elapsedRealtimeNanos.nanoseconds + val isMuchNewer = locationTimeDiff > 2.minutes + val isMuchOlder = locationTimeDiff < (-2).minutes + val isNewer = locationTimeDiff.isPositive() + + val accuracyDelta = accuracy - previous.accuracy + val isLessAccurate = accuracyDelta > 0f + val isMoreAccurate = accuracyDelta < 0f + val isMuchLessAccurate = accuracyDelta > 200f + + val isFromSameProvider = provider == previous.provider + + return when { + // the user has likely moved + isMuchNewer -> true + // If the new location is more than two minutes older, it must be worse + isMuchOlder -> false + isMoreAccurate -> true + isNewer && !isLessAccurate -> true + isNewer && !isMuchLessAccurate && isFromSameProvider -> true + else -> false + } +} diff --git a/core/ktx/src/main/java/de/mm20/launcher2/ktx/Boolean.kt b/core/ktx/src/main/java/de/mm20/launcher2/ktx/Boolean.kt index 5c8196f6..6473e0a3 100644 --- a/core/ktx/src/main/java/de/mm20/launcher2/ktx/Boolean.kt +++ b/core/ktx/src/main/java/de/mm20/launcher2/ktx/Boolean.kt @@ -1,5 +1,14 @@ package de.mm20.launcher2.ktx -inline fun Boolean.toInt(): Int { +fun Boolean.toInt(): Int { return if (this) 1 else 0 -} \ No newline at end of file +} + +fun Boolean.fold( + whenTrue: () -> T, + otherwise: () -> T +): T = if (this) whenTrue() else otherwise() + +fun Boolean.foldOrNull( + whenTrue: () -> T +): T? = fold(whenTrue) { null }