Compare commits

..
27 Commits
Author SHA1 Message Date
lun_admin 5f74a9d9b4 Merge branch 'main' of https://github.com/MM2-0/Kvaesitso
Deploy static content to Pages / deploy (push) Has been cancelled
Build Nightly APK / build (push) Has been cancelled
# Conflicts:
#	app/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/SearchVM.kt
#	services/widgets/src/main/java/de/mm20/launcher2/services/widgets/WidgetsService.kt
...
2025-08-14 18:25:00 +09:00
lun_admin b8b088c1ab ... 2025-08-13 10:17:00 +09:00
Sérgio MarquesandWeblate 51e8cdbfe0 Translated using Weblate (Portuguese)
Currently translated at 97.4% (807 of 828 strings)

Translation: Kvaesitso/i18n
2025-08-02 17:47:26 +00:00
Adhya DandGitHub f8e7bb99b5 Fixed typo in unsplash link text (#1576) 2025-08-02 12:55:48 +02:00
leekleakandGitHub 457a924a51 fix duplicate pins (#1566) 2025-07-27 16:51:49 +02:00
MM20 341259d09f Disallow extra widget page if widgets on home screen is enabled
Close #1569
2025-07-27 16:47:24 +02:00
leekleakandGitHub f0374a08c7 fix widget duplicates (#1536) 2025-07-24 20:13:46 +02:00
Sérgio MarquesandWeblate 64f309c144 Translated using Weblate (Portuguese)
Currently translated at 97.3% (806 of 828 strings)

Translation: Kvaesitso/i18n
2025-07-21 13:36:28 +00:00
chipechopandWeblate 4fc0de4518 Translated using Weblate (Italian)
Currently translated at 100.0% (828 of 828 strings)

Translation: Kvaesitso/i18n
2025-07-21 13:36:28 +00:00
MM20 c212d90408 Fix Nextcloud logout when server is no longer reachable
Close #1527
2025-07-19 19:11:03 +02:00
MM20 004fd7646b Merge branch 'main' of github.com:MM2-0/Kvaesitso 2025-07-19 18:35:48 +02:00
MM20 154a81cafa optimize/rename function 2025-07-19 18:35:33 +02:00
leekleakandGitHub 8f2573fcbb fix (#1537) 2025-07-19 17:59:24 +02:00
Oliver TzengandWeblate e6e7ada855 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 99.8% (827 of 828 strings)

Translation: Kvaesitso/i18n
2025-07-19 04:36:28 +00:00
92andWeblate c0f69160b9 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (121 of 121 strings)

Translation: Kvaesitso/units
2025-07-15 19:36:28 +00:00
92andWeblate 158ddee6c3 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (828 of 828 strings)

Translation: Kvaesitso/i18n
2025-07-15 19:36:28 +00:00
lun_admin 81e4c77644 ... 2025-07-15 17:18:53 +09:00
lun_admin 0f77c9cf10 ...
Deploy static content to Pages / deploy (push) Has been cancelled
2025-07-15 16:09:50 +09:00
VelcornandWeblate 109f91059f Translated using Weblate (German)
Currently translated at 100.0% (828 of 828 strings)

Translation: Kvaesitso/i18n
2025-07-14 05:40:06 +00:00
MM20 5aed3a06d9 Merge branch 'main' of github.com:MM2-0/Kvaesitso 2025-07-13 20:56:02 +02:00
MM20 8ae2fc93fc Bring back clock widget fill height option
Close #1493
2025-07-13 20:55:29 +02:00
lenifyandWeblate 265843f49d Translated using Weblate (Belarusian)
Currently translated at 99.8% (827 of 828 strings)

Translation: Kvaesitso/i18n
2025-07-12 19:36:28 +00:00
lenifyandWeblate 3072b8beb3 Translated using Weblate (Belarusian)
Currently translated at 94.3% (781 of 828 strings)

Translation: Kvaesitso/i18n
2025-07-09 19:23:01 +00:00
WariandWeblate e636b16b0e Translated using Weblate (Thai)
Currently translated at 100.0% (828 of 828 strings)

Translation: Kvaesitso/i18n
2025-07-09 19:23:01 +00:00
OlliesGudhandWeblate 45bd3b33ae Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (828 of 828 strings)

Translation: Kvaesitso/i18n
2025-07-09 19:23:01 +00:00
Amit BeckensteinandWeblate 1dd83c726b Translated using Weblate (Hebrew)
Currently translated at 100.0% (828 of 828 strings)

Translation: Kvaesitso/i18n
2025-07-08 06:23:01 +00:00
Bart LibertandWeblate 9b41912e65 Translated using Weblate (Dutch)
Currently translated at 100.0% (828 of 828 strings)

Translation: Kvaesitso/i18n
2025-07-08 06:23:00 +00:00
102 changed files with 2093 additions and 498 deletions
+1
View File
@@ -308,5 +308,6 @@ fabric.properties
.idea/deploymentTargetSelector.xml
.idea/copilot
.idea/other.xml
.idea/studiobot.xml
.kotlin
+1
View File
@@ -73,5 +73,6 @@
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="UsePropertyAccessSyntax" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
</profile>
</component>
+8 -8
View File
@@ -47,17 +47,17 @@ android {
buildTypes {
release {
applicationIdSuffix = ".release"
postprocessing {
isRemoveUnusedCode = true
isObfuscate = false
isOptimizeCode = true
}
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
applicationIdSuffix = ".debug"
isDebuggable = true
isMinifyEnabled = false
isShrinkResources = false
}
create("nightly") {
initWith(getByName("release"))
+2
View File
@@ -39,6 +39,8 @@
android:fullBackupContent="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
android:hardwareAccelerated="true"
android:requestLegacyExternalStorage="true"
android:resizeableActivity="true"
android:supportsRtl="true"
+3 -1
View File
@@ -1,2 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
<resources>
</resources>
+13 -5
View File
@@ -16,18 +16,26 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
// //consumerProguardFiles("consumer-rules.pro")
signingConfig = signingConfigs.getByName("debug")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
create("nightly") {
isMinifyEnabled = false
isShrinkResources = false
initWith(getByName("release"))
matchingFallbacks += "release"
}
+1
View File
@@ -15,6 +15,7 @@
android:stateNotNeeded="true"
android:theme="@style/LauncherTheme"
android:enableOnBackInvokedCallback="true"
android:configChanges="keyboard|keyboardHidden|screenLayout|layoutDirection|navigation|orientation|uiMode"
android:windowSoftInputMode="stateHidden|adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -7,8 +7,8 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
import de.mm20.launcher2.crashreporter.CrashReporter
import kotlinx.coroutines.awaitCancellation
@@ -24,7 +24,7 @@ fun ProvideAppWidgetHost(
val context = LocalContext.current
val widgetHost = remember { AppWidgetHost(context.applicationContext, 44203) }
LaunchedEffect(null) {
lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
widgetHost.startListening()
try {
awaitCancellation()
@@ -1,6 +1,8 @@
package de.mm20.launcher2.ui.launcher
import android.content.Intent
import android.content.res.Configuration
import android.util.Log
import com.android.launcher3.GestureNavContract
import de.mm20.launcher2.ui.launcher.scaffold.ScaffoldPage
@@ -19,6 +21,12 @@ class LauncherActivity: SharedLauncherActivity(LauncherActivityMode.Launcher) {
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
Log.e("LauncherActivity","newConfig.keyboard ${newConfig.keyboard}")
Log.e("LauncherActivity","newConfig.keyboardHidden ${newConfig.keyboardHidden}")
Log.e("LauncherActivity","newConfig.hardKeyboardHidden ${newConfig.hardKeyboardHidden}")
}
override fun onPause() {
super.onPause()
enterHomeTransitionManager.clear()
@@ -77,7 +77,6 @@ import de.mm20.launcher2.ui.locals.LocalWindowSize
import de.mm20.launcher2.ui.overlays.OverlayHost
import de.mm20.launcher2.ui.theme.LauncherTheme
import de.mm20.launcher2.ui.theme.wallpaperColorsAsState
import kotlin.math.pow
abstract class SharedLauncherActivity(
@@ -195,7 +194,10 @@ abstract class SharedLauncherActivity(
if (it != null) {
enterTransitionProgress.value = 0f
enterTransition = it
enterTransitionProgress.animateTo(100f, animationSpec = animMotionSpec)
enterTransitionProgress.animateTo(
100f,
animationSpec = animMotionSpec
)
enterTransition = null
}
}
@@ -275,10 +277,15 @@ abstract class SharedLauncherActivity(
},
)
is GestureAction.Widgets -> ScaffoldGesture(
is GestureAction.Widgets ->
if (widgetsOnHomeScreen == true) {
null
} else {
ScaffoldGesture(
component = widgetComponent,
animation = if (gesture.orientation == null) ScaffoldAnimation.ZoomIn else ScaffoldAnimation.Push,
)
}
is GestureAction.Notifications -> ScaffoldGesture(
component = NotificationsComponent,
@@ -1,18 +1,17 @@
package de.mm20.launcher2.ui.launcher.scaffold
import android.annotation.SuppressLint
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
@@ -26,6 +25,7 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
@@ -35,15 +35,16 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import de.mm20.launcher2.preferences.ui.ClockWidgetSettings
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.ktx.toDp
import de.mm20.launcher2.ui.launcher.widgets.WidgetColumn
import de.mm20.launcher2.ui.launcher.widgets.clock.ClockWidget
import kotlinx.coroutines.launch
import org.koin.compose.koinInject
internal object ClockAndWidgetsHomeComponent : ScaffoldComponent() {
private var editMode by mutableStateOf(false)
@@ -54,7 +55,7 @@ internal object ClockAndWidgetsHomeComponent: ScaffoldComponent() {
}
override val isAtBottom: State<Boolean?> = derivedStateOf {
!scrollState.canScrollForward
!scrollState.canScrollForward || scrollState.value == 0
}
override val drawBackground: Boolean = false
@@ -72,11 +73,19 @@ internal object ClockAndWidgetsHomeComponent: ScaffoldComponent() {
) {
val scope = rememberCoroutineScope()
val topPadding by animateDpAsState(if (editMode) 80.dp else 0.dp)
val clockWidgetSettings: ClockWidgetSettings = koinInject()
val fillHeight by clockWidgetSettings.fillHeight.collectAsState(null)
if (fillHeight == null) return
val topPadding by animateDpAsState(if (editMode) 80.dp else 0.dp)
val previousScroll = remember { mutableIntStateOf(scrollState.value) }
LaunchedEffect(scrollState.value, scrollState.canScrollForward, scrollState.canScrollBackward) {
LaunchedEffect(
scrollState.value,
scrollState.canScrollForward,
scrollState.canScrollBackward
) {
val delta = scrollState.value - previousScroll.intValue
previousScroll.intValue = scrollState.value
if (!editMode) {
@@ -92,8 +101,15 @@ internal object ClockAndWidgetsHomeComponent: ScaffoldComponent() {
.padding(insets),
) {
ClockWidget(
modifier = Modifier
.fillMaxWidth()
then if (fillHeight == true) Modifier.height(
state.size.height.toDp() - insets.calculateTopPadding()
).padding(
bottom = insets.calculateBottomPadding()
) else Modifier,
editMode = editMode,
fillScreenHeight = false,
fillScreenHeight = fillHeight == true,
)
WidgetColumn(
modifier = Modifier
@@ -126,7 +142,10 @@ internal object ClockAndWidgetsHomeComponent: ScaffoldComponent() {
scope.launch { state.unlock() }
}
) {
Icon(Icons.AutoMirrored.Rounded.ArrowBack, stringResource(R.string.action_done))
Icon(
Icons.AutoMirrored.Rounded.ArrowBack,
stringResource(R.string.action_done)
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
@@ -250,10 +250,10 @@ class SearchVM : ViewModel(), KoinComponent {
previousResults = SearchResults(apps = apps)
searchActionResults.clear()
appResults.mergeWith(apps)
workAppResults.mergeWith(workApps)
privateSpaceAppResults.mergeWith(privateApps)
hiddenResults.mergeWith(hiddenItems)
appResults.updateItems(apps)
workAppResults.updateItems(workApps)
privateSpaceAppResults.updateItems(privateApps)
hiddenResults.updateItems(hiddenItems)
}
} else {
@@ -273,19 +273,31 @@ class SearchVM : ViewModel(), KoinComponent {
workAppResults.clear()
privateSpaceAppResults.clear()
appResults.mergeWith(results.apps, hiddenKeys, query)
appShortcutResults.mergeWith(results.shortcuts, hiddenKeys, query)
fileResults.mergeWith(results.files, hiddenKeys, query)
appResults.updateItems(
results.apps
?.filterNot { hiddenKeys.contains(it.key) }
?.applyRanking(query)
)
appShortcutResults.updateItems(
results.shortcuts
?.filterNot { hiddenKeys.contains(it.key) }
?.applyRanking(query)
)
fileResults.updateItems(
results.files
?.filterNot { hiddenKeys.contains(it.key) }
?.applyRanking(query)
)
contactResults.mergeWith(
contactResults.updateItems(
results.contacts?.filterNot { hiddenKeys.contains(it.key) }
?.applyRanking(query)
)
calendarResults.mergeWith(
calendarResults.updateItems(
results.calendars?.filterNot { hiddenKeys.contains(it.key) }
?.applyRanking(query)
)
locationResults.mergeWith(
locationResults.updateItems(
results.locations?.filterNot { hiddenKeys.contains(it.key) }
?.let { locations ->
devicePoseProvider.lastCachedLocation?.let {
@@ -298,17 +310,17 @@ class SearchVM : ViewModel(), KoinComponent {
} ?: locations.applyRanking(query)
}
)
articleResults.mergeWith(
articleResults.updateItems(
results.wikipedia?.applyRanking(query)
)
websiteResults.mergeWith(
websiteResults.updateItems(
results.websites?.applyRanking(query)
)
calculatorResults.mergeWith(results.calculators)
unitConverterResults.mergeWith(results.unitConverters)
calculatorResults.updateItems(results.calculators)
unitConverterResults.updateItems(results.unitConverters)
if (results.searchActions != null) {
searchActionResults.mergeWith(results.searchActions!!)
searchActionResults.updateItems(results.searchActions!!)
}
if (launchOnEnter.value) {
@@ -429,26 +441,20 @@ class SearchVM : ViewModel(), KoinComponent {
return sorted.distinctBy { it.key }.toList()
}
private fun <T> SnapshotStateList<T>.mergeWith(newItems: List<T>?) {
val items = newItems ?: emptyList()
val diff = toSet() subtract items.toSet()
removeAll(diff)
for ((i, item) in items.withIndex()) {
if (i < size)
set(i, item)
else
add(item)
/**
* Merges a list of new items into the current SnapshotStateList.
* It removes items that are in the current list but not in the new list.
* Then, it updates existing items or adds new items from the new list.
*
* @param T The type of items in the list.
* @param newItems The list of new items to merge with. If null, an empty list is used.
*/
private fun <T> SnapshotStateList<T>.updateItems(newItems: List<T>?) {
clear()
addAll(newItems ?: emptyList())
}
}
private suspend fun <T : SavableSearchable> SnapshotStateList<T>.mergeWith(
newItems: List<T>?,
hiddenKeys: List<String>,
query: String
) = this.mergeWith((newItems ?: emptyList()).filterNot { hiddenKeys.contains(it.key) }
.applyRanking(query))
}
enum class SearchCategory {
Apps,
@@ -226,11 +226,19 @@ fun LocationItem(
this@AnimatedContent
)
)
// val rateing =
val formattedDistance =
distance?.metersToLocalizedString(context, imperialUnits)
val sublabel = listOf(location.category, formattedDistance)
val sublabel = if (location.category?.contains("|") == true) {
listOf(location.category!!.split("|")!!.first(), formattedDistance,location.userRating)
.fastFilterNotNull()
.joinToString("")
} else {
listOf(location.category, formattedDistance,location.userRating)
.fastFilterNotNull()
.joinToString("")
}
val isOpenString = location.openingSchedule?.isOpen()
?.let { stringResource(if (it) R.string.location_open else R.string.location_closed) }
@@ -293,7 +293,6 @@ fun ColumnScope.ConfigureAppWidget(
val widgetInfo = remember(widget.config.widgetId) {
AppWidgetManager.getInstance(context).getAppWidgetInfo(widget.config.widgetId)
}
if (widgetInfo == null) {
var replaceWidget by rememberSaveable {
mutableStateOf(false)
@@ -365,6 +364,7 @@ fun ColumnScope.ConfigureAppWidget(
}
AppWidgetHost(
widgetInfo = widgetInfo,
widgetId = widget.config.widgetId,
modifier = Modifier
@@ -119,10 +119,13 @@ fun EditFavoritesSheet(
.align(Alignment.Center)
)
}
} else if (createShortcutTarget != null) {
ShortcutPicker(viewModel, it)
} else {
ReorderFavoritesGrid(viewModel, it)
if (createShortcutTarget != null) {
BottomSheetDialog({viewModel.cancelPickShortcut()}) {
ShortcutPicker(viewModel, it)
}
}
}
}
}
@@ -66,7 +66,7 @@ fun WidgetColumn(
Column(
modifier = modifier
modifier = modifier.fillMaxWidth()
) {
val scope = rememberCoroutineScope()
Column {
@@ -24,10 +24,10 @@ import androidx.compose.material.icons.rounded.AlignVerticalBottom
import androidx.compose.material.icons.rounded.AlignVerticalCenter
import androidx.compose.material.icons.rounded.AlignVerticalTop
import androidx.compose.material.icons.rounded.AutoAwesome
import androidx.compose.material.icons.rounded.AvTimer
import androidx.compose.material.icons.rounded.BatteryFull
import androidx.compose.material.icons.rounded.ColorLens
import androidx.compose.material.icons.rounded.DarkMode
import androidx.compose.material.icons.rounded.Height
import androidx.compose.material.icons.rounded.HorizontalSplit
import androidx.compose.material.icons.rounded.LightMode
import androidx.compose.material.icons.rounded.MusicNote
@@ -363,6 +363,7 @@ fun ConfigureClockWidgetSheet(
val color by viewModel.color.collectAsState()
val style by viewModel.clockStyle.collectAsState()
val fillHeight by viewModel.fillHeight.collectAsState()
val widgetsOnHome by viewModel.widgetsOnHome.collectAsState()
val alignment by viewModel.alignment.collectAsState()
val showSeconds by viewModel.showSeconds.collectAsState()
val timeFormat by viewModel.timeFormat.collectAsState()
@@ -564,13 +565,21 @@ fun ConfigureClockWidgetSheet(
}
}
}
if (fillHeight == true) {
OutlinedCard(
modifier = Modifier.padding(top = 16.dp),
) {
Column(
modifier = Modifier.fillMaxWidth()
) {
SwitchPreference(
title = stringResource(R.string.preference_clock_widget_fill_height),
icon = Icons.Rounded.Height,
value = fillHeight == true || widgetsOnHome == false,
onValueChanged = {
viewModel.setFillHeight(it)
},
enabled = widgetsOnHome == true,
)
var showDropdown by remember { mutableStateOf(false) }
Preference(
title = stringResource(R.string.preference_clock_widget_alignment),
@@ -586,7 +595,8 @@ fun ConfigureClockWidgetSheet(
},
onClick = {
showDropdown = true
}
},
enabled = fillHeight == true,
)
DropdownMenu(
expanded = showDropdown,
@@ -631,7 +641,6 @@ fun ConfigureClockWidgetSheet(
}
}
}
}
Text(
modifier = Modifier.padding(top = 16.dp, bottom = 8.dp),
style = MaterialTheme.typography.titleSmall,
@@ -7,6 +7,7 @@ import de.mm20.launcher2.preferences.ClockWidgetColors
import de.mm20.launcher2.preferences.ClockWidgetStyle
import de.mm20.launcher2.preferences.TimeFormat
import de.mm20.launcher2.preferences.ui.ClockWidgetSettings
import de.mm20.launcher2.preferences.ui.UiSettings
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
@@ -15,6 +16,8 @@ import org.koin.core.component.inject
class ClockWidgetSettingsScreenVM : ViewModel(), KoinComponent {
private val settings: ClockWidgetSettings by inject()
private val uiSettings: UiSettings by inject()
val compact = settings.compact
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
fun setCompact(compact: Boolean) {
@@ -68,9 +71,16 @@ class ClockWidgetSettingsScreenVM : ViewModel(), KoinComponent {
settings.setUseThemeColor(boolean)
}
val widgetsOnHome = uiSettings.homeScreenWidgets
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val fillHeight = settings.fillHeight
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
fun setFillHeight(fillHeight: Boolean) {
settings.setFillHeight(fillHeight)
}
val parts = settings.parts
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
@@ -52,6 +52,7 @@ fun GestureSettingsScreen() {
val viewModel: GestureSettingsScreenVM = viewModel()
val hasPermission by viewModel.hasPermission.collectAsStateWithLifecycle(null)
val allowWidgetGesture by viewModel.allowWidgetGesture.collectAsStateWithLifecycle(null)
val options = buildList {
add(stringResource(R.string.gesture_action_none) to GestureAction.NoAction)
@@ -61,7 +62,7 @@ fun GestureSettingsScreen() {
add(stringResource(R.string.gesture_action_recents) to GestureAction.Recents)
add(stringResource(R.string.gesture_action_power_menu) to GestureAction.PowerMenu)
add(stringResource(R.string.gesture_action_open_search) to GestureAction.Search)
add(stringResource(R.string.gesture_action_widgets) to GestureAction.Widgets)
if (allowWidgetGesture == true) add(stringResource(R.string.gesture_action_widgets) to GestureAction.Widgets)
add(stringResource(R.string.gesture_action_launch_app) to GestureAction.Launch(null))
}
@@ -265,7 +266,7 @@ fun GesturePreference(
icon = icon,
items = options,
value = value,
summary = options.find { value?.javaClass == it.second.javaClass }?.first,
summary = options.find { value?.javaClass == it.second.javaClass }?.first ?: stringResource(R.string.gesture_action_none),
onValueChanged = { if (it != null) onValueChanged(it) }
)
}
@@ -34,6 +34,9 @@ class GestureSettingsScreenVM : ViewModel(), KoinComponent {
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Accessibility)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val allowWidgetGesture = uiSettings.homeScreenWidgets.map { it == false }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val swipeDown = gestureSettings.swipeDown
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val swipeLeft = gestureSettings.swipeLeft
@@ -157,38 +157,6 @@ class HomescreenSettingsScreenVM(
fun setWidgetsOnHomeScreen(widgetsOnHomeScreen: Boolean) {
uiSettings.setHomeScreenWidgets(widgetsOnHomeScreen)
viewModelScope.launch {
val gestures = gestureSettings.first()
if (widgetsOnHomeScreen) {
if (gestures.swipeUp is GestureAction.Widgets) {
gestureSettings.setSwipeUp(GestureAction.NoAction)
} else if (gestures.swipeRight is GestureAction.Widgets) {
gestureSettings.setSwipeUp(GestureAction.NoAction)
} else if (gestures.swipeLeft is GestureAction.Widgets) {
gestureSettings.setSwipeUp(GestureAction.NoAction)
} else if (gestures.swipeDown is GestureAction.Widgets) {
gestureSettings.setSwipeUp(GestureAction.NoAction)
} else if (gestures.longPress is GestureAction.Widgets) {
gestureSettings.setLongPress(GestureAction.NoAction)
} else if (gestures.doubleTap is GestureAction.Widgets) {
gestureSettings.setDoubleTap(GestureAction.NoAction)
}
} else {
if (gestures.swipeUp is GestureAction.NoAction || gestures.swipeUp is GestureAction.Widgets) {
gestureSettings.setSwipeUp(GestureAction.Widgets)
} else if (gestures.swipeRight is GestureAction.NoAction || gestures.swipeRight is GestureAction.Widgets) {
gestureSettings.setSwipeRight(GestureAction.Widgets)
} else if (gestures.swipeLeft is GestureAction.NoAction || gestures.swipeLeft is GestureAction.Widgets) {
gestureSettings.setSwipeLeft(GestureAction.Widgets)
} else if (gestures.swipeDown is GestureAction.NoAction || gestures.swipeDown is GestureAction.Widgets) {
gestureSettings.setSwipeDown(GestureAction.Widgets)
} else if (gestures.longPress is GestureAction.NoAction || gestures.longPress is GestureAction.Widgets) {
gestureSettings.setLongPress(GestureAction.Widgets)
} else if (gestures.doubleTap is GestureAction.NoAction || gestures.doubleTap is GestureAction.Widgets) {
gestureSettings.setDoubleTap(GestureAction.Widgets)
}
}
}
}
companion object : KoinComponent {
+11 -5
View File
@@ -11,15 +11,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
// //consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
create("nightly") {
initWith(getByName("release"))
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
// //consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
// //consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+10 -5
View File
@@ -11,16 +11,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
compileOptions {
@@ -20,7 +20,9 @@ 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.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.withContext
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
@@ -78,6 +80,7 @@ class DevicePoseProvider internal constructor(
}
if (hasFineAccess) {
withContext(Dispatchers.Main) {
requestLocationUpdates(
LocationManager.GPS_PROVIDER,
minTimeMs,
@@ -85,7 +88,9 @@ class DevicePoseProvider internal constructor(
locationCallback
)
}
}
if (hasCoarseAccess) {
withContext(Dispatchers.Main) {
requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
minTimeMs,
@@ -93,6 +98,7 @@ class DevicePoseProvider internal constructor(
locationCallback
)
}
}
}?.onFailure {
Log.e("DevicePoseProvider", "Failed to register location listener", it)
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+97 -15
View File
@@ -18,7 +18,7 @@
<string name="menu_customize">Дапасаваць</string>
<string name="menu_open_file">Адкрыць</string>
<string name="menu_map">Праглядзець на мапе</string>
<string name="menu_website">Адкрыць вэбсайт</string>
<string name="menu_website">Адкрыць вэб-сайт</string>
<string name="action_undo">Скасаваць</string>
<string name="menu_delete">Выдаліць</string>
<string name="menu_favorites_unpin">Адмацаваць</string>
@@ -188,13 +188,13 @@
<string name="weather_no_data">Няма даных аб надвор\'і.</string>
<string name="weather_widget_set_location">Абраць месцазнаходжанне</string>
<string name="weather_location_search_no_result">Месцазнаходжанне ня знойдзена.</string>
<string name="missing_permission_contact_search_settings">Доступ да кантактаў патрэбны для іх пошуку</string>
<string name="missing_permission_calendar_search_settings">Доступ да календара патрэбны для пошуку праз каляндар</string>
<string name="missing_permission_auto_location">Доступ да месцазнаходжання патрэбны для яго аўтаматычнага вызначэння</string>
<string name="missing_permission_music_widget">Доступ да апавяшчэнняў патрэбны для кіравання прайграваннем медыя</string>
<string name="missing_permission_contact_search_settings">Для пошуку кантактаў патрэбны доступ да кантактнай кнігі</string>
<string name="missing_permission_calendar_search_settings">Для пошуку праз каляндар патрэбны доступ да календара</string>
<string name="missing_permission_auto_location">Для аўтаматычнага вызначэння лакацыі патрэбны доступ да месцазнаходжання</string>
<string name="missing_permission_music_widget">Для кіравання прайграваннем медыя патрэбны доступ да апавяшчэнняў</string>
<string name="widget_config_calendar_missing_calendars_hint">Не можаце знайсці свае календары?</string>
<string name="missing_permission_appshortcuts_search_settings">%1$s павінна быць стандартнай праграмай галоўнага экрана для пошуку ярлыкоў</string>
<string name="missing_permission_file_search_settings">Доступ да файлаў патрэбны для пошуку праз лакальныя файлы</string>
<string name="missing_permission_file_search_settings">Для пошуку праз лакальныя файлы патрэбны доступ да файлаў</string>
<string name="missing_permission_calendar_widget_settings">Гэтаму віджэту патрэбны доступ да календара</string>
<string name="disclaimer">Адмова ад адказнасці</string>
<string name="show_all">Паказаць усе</string>
@@ -217,8 +217,8 @@
<item quantity="many">+%1$d бягучых падзей з тых часоў</item>
<item quantity="other">+%1$d бягучых падзей з тых часоў</item>
</plurals>
<string name="missing_permission_file_search_settings_android10">Доступ да ўсіх файлаў патрэбны для пошуку праз лакальныя файлы</string>
<string name="missing_permission_notification_badges">Доступ да апавяшчэнняў патрэбны для адлюстроўвання бэйджаў апавяшчэнняў на значках</string>
<string name="missing_permission_file_search_settings_android10">Для пошуку праз лакальныя файлы патрэбны доступ да ўсіх файлаў</string>
<string name="missing_permission_notification_badges">Для адлюстравання бэйджаў апавяшчэнняў на значках патрэбны доступ да апавяшчэнняў</string>
<string name="missing_permission_calendar_widget">Дайце дазвол на доступ да календару, каб тут паказваліся будучыя сустрэчы і падзеі.</string>
<string name="missing_permission_calendar_search">Дайце дазвол на доступ да календару, каб выконваць пошук праз ваш каляндар.</string>
<string name="missing_permission_location_search">Дайце дазвол на доступ да месцазнаходжання, каб шукаць праз бліжайшыя месцы.</string>
@@ -279,8 +279,8 @@
<string name="preference_debug_dump_heap">Здымак памяці</string>
<string name="preference_debug_dump_heap_summary">Зрабіце хуткі здымак, каб аналізаваць выкарыстоўванне памяці. Праграма не будзе адказваць, пакуль працэс не завяршыцца.</string>
<string name="preference_debug_dump_heap_in_progress">Здымаецца…</string>
<string name="preference_debug_cleanup_database">Ачысціць базу дадзеных</string>
<string name="preference_debug_cleanup_database_summary">Выдаліць зламаныя і неўжываныя пункты з базы дадзеных праграмы запуску</string>
<string name="preference_debug_cleanup_database">Ачысціць базу даных</string>
<string name="preference_debug_cleanup_database_summary">Выдаліць зламаныя і неўжываныя пункты з базы даных праграмы запуску</string>
<string name="preference_debug_reinstall_iconpacks">Пераўсталяваць пакеты значкоў</string>
<string name="preference_debug_reinstall_iconpacks_summary">Ачысціць і стварыць нанава кэш пакетаў значкоў</string>
<string name="preference_category_icons">Значкі</string>
@@ -388,11 +388,11 @@
<string name="preference_category_license">Ліцэнзія</string>
<string name="preference_clockwidget_music_part_summary">Адлюстроўвае кнопкі для кіравання мультымедыя, калі ёсць актыўны сеанс медыя</string>
<string name="preference_screen_backup">Рэзэрвовае капіраванне і аднаўленне</string>
<string name="preference_screen_backup_summary">Экспарт і імпарт дадзеных праграмы запуску</string>
<string name="preference_screen_backup_summary">Экспарт і імпарт даных праграмы запуску</string>
<string name="preference_clockwidget_battery_part_summary">Адлюстроўвае бягучы зарад батарэі, калі ён нізкі, або калі прылада зараджаецца</string>
<string name="preference_clockwidget_alarm_part_summary">Адлюстроўвае будзільнікі, якія празвоняць меньш чым праз 8 гадзін</string>
<string name="preference_clockwidget_battery_part">Батарэя</string>
<string name="preference_backup_summary">Экспартаваць налады і дадзеныя праграмы запуску</string>
<string name="preference_backup_summary">Экспартаваць налады і даныя праграмы запуску</string>
<string name="preference_restore">Аднаўленне</string>
<string name="preference_restore_summary">Аднавіць з дапамогай раней створанай рэзервовай копіі</string>
<string name="preference_backup">Рэзервовае капіраванне</string>
@@ -467,7 +467,7 @@
<string name="backup_complete">Рэзервовае капіяванне скончана.</string>
<string name="restore_invalid_file">Здаецца, выбраны файл не з\'яўляецца рэзервовай копіяй. Вы ўпэўнены, што выбралі пэўны файл?</string>
<string name="preference_favorites_edit_button_summary">Паказваць кнопку рэдагавання абраных</string>
<string name="restore_different_minor_version">Гэты бэкап быў створаны з іншай версіі %1$s. Некаторыя дадзеныя могуць не аднавіцца пэўна.</string>
<string name="restore_different_minor_version">Гэты бэкап быў створаны з іншай версіі %1$s. Некаторыя даныя могуць не аднавіцца пэўна.</string>
<plurals name="battery_part_remaining_charge_time">
<item quantity="one">Зарадзіцца поўнасцю праз %1$s хвіліну</item>
<item quantity="few">Зарадзіцца поўнасцю праз %1$s хвіліны</item>
@@ -623,9 +623,9 @@
<string name="preference_search_location_custom_tile_server_url">Спасылка на Tileserver</string>
<string name="menu_navigation">Навігацыя</string>
<string name="menu_bugreport">Справаздача аб памылцы</string>
<string name="plugin_type_filesearch">Пошук файлаў</string>
<string name="plugin_type_filesearch">Файлы</string>
<string name="clock_style_digital1">Тоўсты</string>
<string name="clock_style_digital2">Звычайны</string>
<string name="clock_style_digital2">Просты</string>
<string name="clock_style_orbit">Арбітальны</string>
<string name="preference_customize_filter_bar_summary">Дапасуйце элементы ў панэлі фільтраў</string>
<string name="clock_style_custom">Уласны віджэт</string>
@@ -801,4 +801,86 @@
</plurals>
<string name="weather_widget_no_provider">Правайдара надвор\'я не выбрана, альбо выбраны правайдар недаступны</string>
<string name="widget_removed">Віджэт выдалены</string>
<string name="preference_screen_shapes">Фігуры</string>
<string name="preference_shapes_rect">Прамавугольныя</string>
<string name="preference_shapes_extra_round">Вельмі скругленыя</string>
<string name="preference_shapes_base">Стандартная фігура</string>
<string name="weather_condition_thunder">Гром</string>
<string name="weather_condition_haze">Смуга</string>
<string name="preference_location_breezy">Кіраванне месцазнаходжаннем у Breezy Weather</string>
<string name="provider_breezy">Breezy Weather</string>
<string name="action_install">Усталяваць</string>
<string name="preference_colors_high_contrast">Высокі кантраст</string>
<string name="music_widget_interactive_progress_bar">Інтэрактыўная панэль выканання</string>
<string name="missing_permission_tasks_integration">Для інтэграцыі з задачамі патрэбны доступ да заданняў</string>
<string name="preference_shapes_default">Прадвызначаныя</string>
<string name="preference_grid_list_style">Паказваць праграмы спісам</string>
<string name="preference_transparencies_default">Прадвызначана</string>
<string name="preference_transparencies_semi_transparent">Паўпразрыста</string>
<string name="preference_screen_transparencies">Празрыстасць</string>
<string name="no_selection">Не выбрана</string>
<string name="save_as_file">Захаваць у файл</string>
<plurals name="calendar_widget_running_tasks">
<item quantity="one">+%1$d незавершаная задача</item>
<item quantity="few">+%1$d незавершаныя задачы</item>
<item quantity="many">+%1$d незавершаных задач</item>
<item quantity="other">+%1$d незавершаных задач</item>
</plurals>
<string name="missing_permission_tasks_search_settings">Для пошуку праз заданні патрэбны доступ да заданняў</string>
<string name="preference_screen_typography">Тыпаграфія</string>
<string name="preference_grid_list_icons">Паказваць значкі праграм спісам</string>
<string name="preference_grid_list_style_summary">Паказваць праграмы спісам замест сеткі</string>
<string name="preference_grid_list_icons_summary">Паказваць значкі пры праглядзе спісам</string>
<string name="missing_permission_call_contacts_settings">Для выканання выклікаў патрэбны доступ да тэлефону</string>
<string name="departure_time_now">зараз</string>
<string name="import_theme_contents">Змесціва</string>
<string name="preference_launch_breezyweather_app">Адкрыць Breezy Weather</string>
<string name="preference_launch_tasks_app">Адкрыць Tasks</string>
<string name="plugin_type_contacts">Кантакты</string>
<string name="bad_configuration_title">Віншую, вы закрылі самі сябе!</string>
<string name="preference_breezyweather_integration_description">Breezy Weather гэта бясплатная праграма з адкрытым зыходным кодам для паказу надвор\'я. Калі яна ўсталяваная, %1$s можа выкарыстоўваць Breezy Weather як крыніцу даных аб надвор\'і.</string>
<plurals name="departure_time_in">
<item quantity="one">праз %1$d хвіліну</item>
<item quantity="few">праз %1$d хвіліны</item>
<item quantity="many">праз %1$d хвілін</item>
<item quantity="other">праз %1$d хвілін</item>
</plurals>
<string name="preference_search_tasks">Задачы</string>
<string name="preference_tasks_integration_ready">Інтэграцыя з Tasks поўнасцю наладжаная і гатовая да выкарыстання.</string>
<string name="preference_search_tasks_summary">Шукаць задачы ў праграме \"Tasks\"</string>
<string name="clock_variant_analog_ticks">Цікі</string>
<string name="departure_time_departed">адправіўся</string>
<string name="preference_search_local_calendar_summary">Пошук праз календары на гэтай прыладзе</string>
<string name="preference_contacts_call_on_tap_summary">Выклікаць адразу пасля націскання на нумар</string>
<string name="confirmation_delete_transparencies_scheme">Вы сапраўды хочаце выдаліць схему празрыстасці \"%1$s\"?</string>
<string name="preference_breezyweather_integration">Breezy Weather</string>
<string name="new_theme_name">(без назвы)</string>
<string name="theme_export_title">Экспартаваць тэму</string>
<string name="theme_bundle_name">Назва</string>
<string name="theme_bundle_author">Аўтар</string>
<string name="import_theme_exists">Тэма ўжо існуе, таму будзе абноўлена</string>
<string name="theme_import_title">Імпартаваць тэму</string>
<string name="preference_breezyweather_integration_instructions">Каб выкарыстоўваць Breezy Weather як пастаўшчыка даных аб надвор\'і:\n\n1. Перайдзіце ў Breezy Weather &gt; Налады &gt; Віджэты і жывыя шпалеры &gt; Адпраўляць даныя пра надвор’е ў Gadgetbridge &gt; Уключыць %1$s\n\n2. Выберыце Breezy Weather як пастаўшчыка надвор\'я ў %1$s</string>
<string name="preference_tasks_integration">Tasks</string>
<string name="preference_tasks_integration_description">Tasks гэта бясплатная праграма з адкрытым зыходным кодам для вядзення спіса задач і стварэння напамінаў. Калі яна ўсталяваная, %1$s можа паказваць задачы адтуль у пошуку.</string>
<string name="confirmation_delete_shapes_scheme">Вы сапраўды хочаце выдаліць схему фігур \"%1$s\"?</string>
<string name="confirmation_delete_typography_scheme">Вы сапраўды хочаце выдаліць схему тыпаграфіі \"%1$s\"?</string>
<string name="font_name_device_headline">Стандартны шрыфт загалоўкаў</string>
<string name="font_name_device_body">Шрыфт тэксту прылады</string>
<string name="preference_typography_fonts">Шрыфты</string>
<string name="font_category_device_default">Налады прылады</string>
<string name="preference_contacts_call_on_tap">Націсніце, каб выклікнуць</string>
<string name="preference_clock_widget_time_format_system">Сістэмны фармат</string>
<string name="preference_clock_widget_time_format">Фармат часу</string>
<string name="preference_clock_widget_time_format_24h">24 гадзіны</string>
<string name="preference_clock_widget_time_format_12h">12 гадзін</string>
<string name="preference_widgets_on_home_screen">Віджэты на галоўным экране</string>
<string name="preference_widgets_on_home_screen_summary">Паказваць віджэты на галоўным экране, не на дадатковай старонцы</string>
<string name="bad_configuration_summary">Вы знайшлі канфігурацыю, пры якой нельга адкрыць ні пошук, ні налады, тым самым зрабіўшы праграму не прыгоднай для выкарыстання.</string>
<string name="preference_search_bar_style_transparent">Празрысты</string>
<string name="preference_search_bar_style_solid">Суцэльны</string>
<string name="preference_search_bar_style_hidden">Схаваны</string>
<string name="preference_gesture_swipe_up">Чырканне ўверх</string>
<string name="gesture_action_widgets">Віджэты</string>
<string name="font_category_generic">Звычайн</string>
</resources>
@@ -862,4 +862,5 @@
<string name="preference_search_bar_style_solid">Vollflächig</string>
<string name="preference_search_bar_style_hidden">Versteckt</string>
<string name="music_widget_interactive_progress_bar">Interaktiver Fortschrittsbalken</string>
<string name="clock_variant_analog_ticks">Striche</string>
</resources>
+1 -1
View File
@@ -589,7 +589,7 @@
<string name="clock_style_digital2">Semplice</string>
<string name="clock_style_orbit">Orbita</string>
<string name="clock_style_binary">Binario</string>
<string name="clock_style_analog">Mani</string>
<string name="clock_style_analog">Lancette</string>
<string name="clock_style_segment">7 segmenti</string>
<string name="clock_style_empty">Nessun orologio</string>
<string name="clock_variant_outlined">Delineato</string>
@@ -867,4 +867,9 @@
<string name="poi_category_kiosk">קיוסק</string>
<string name="poi_category_museum">מוזיאון</string>
<string name="filter_settings_network_warning">הסינון הנוכחי מאפשר תוצאות מקוונות כברירת מחדל. שאילתות החיפוש שלך עשויות להישלח שלא במתכוון לשירותי אינטרנט חיצוניים. מסיבות של פרטיות, זה לא מומלץ.</string>
<string name="clock_variant_analog_ticks">סימנים</string>
<string name="music_widget_interactive_progress_bar">סרגל התקדמות אינטראקטיבי</string>
<string name="preference_search_bar_style_solid">מוצק</string>
<string name="preference_search_bar_style_hidden">מוסתר</string>
<string name="preference_search_bar_style_transparent">שקוף</string>
</resources>
+1 -1
View File
@@ -442,7 +442,7 @@
<string name="poi_category_books">서점</string>
<string name="poi_category_florist">꽃집</string>
<string name="poi_category_church">교회</string>
<string name="poi_category_restaurant">레스토랑</string>
<string name="poi_category_restaurant">레스토랑|음식점|식당|밥집|분식</string>
<string name="poi_category_fast_food">패스트푸드</string>
<string name="poi_category_hotel">호텔</string>
<string name="poi_category_cafe">카페</string>
@@ -861,4 +861,6 @@
<string name="preference_search_bar_style_transparent">Transparant</string>
<string name="preference_search_bar_style_hidden">Verborgen</string>
<string name="preference_search_bar_style_solid">Massief</string>
<string name="clock_variant_analog_ticks">Streepjes</string>
<string name="music_widget_interactive_progress_bar">Interactieve voortgangsbalk</string>
</resources>
@@ -867,4 +867,9 @@
<string name="font_name_device_headline">Fonte padrão do título</string>
<string name="font_name_device_body">Fonte de texto do dispositivo</string>
<string name="font_category_generic">Genérico</string>
<string name="preference_search_bar_style_transparent">Transparente</string>
<string name="preference_search_bar_style_hidden">Oculto</string>
<string name="preference_search_bar_style_solid">Sólido</string>
<string name="clock_variant_analog_ticks">Tique-taques</string>
<string name="music_widget_interactive_progress_bar">Barra de progresso interativa</string>
</resources>
+60 -1
View File
@@ -554,7 +554,7 @@
<string name="search_action_app">Aplicação</string>
<string name="search_action_websearch_url">Modelo de URL</string>
<string name="preference_plugin_enable">Ativar plugin</string>
<string name="plugin_type_filesearch">Pesquisa de ficheiros</string>
<string name="plugin_type_filesearch">Ficheiros</string>
<string name="experimental_feature">Experimental</string>
<string name="tag_exists_error">Já existe uma etiqueta com este nome.</string>
<string name="tag_exists_message">Já existe uma etiqueta com este nome. Se continuar, as etiquetas serão combinadas.</string>
@@ -799,4 +799,63 @@
<string name="preference_clock_widget_time_format_24h">24 horas</string>
<string name="preference_clock_widget_time_format_12h">12 horas</string>
<string name="preference_clock_widget_time_format_system">Padrão do sistema</string>
<string name="theme_import_title">Importar tema</string>
<string name="import_theme_exists">O tema já existe e será atualizado</string>
<string name="preference_search_bar_style_transparent">Transparente</string>
<string name="preference_search_bar_style_solid">Sólida</string>
<string name="preference_search_bar_style_hidden">Oculta</string>
<string name="import_theme_contents">Conteúdo</string>
<string name="clock_variant_analog_ticks">Ponteiros 2</string>
<string name="theme_export_title">Exportar tema</string>
<string name="preference_colors_high_contrast">Alto contraste</string>
<string name="theme_bundle_name">Nome</string>
<string name="theme_bundle_author">Autor</string>
<string name="preference_shapes_default">Padrão</string>
<string name="preference_shapes_extra_round">Super redonda</string>
<string name="preference_screen_shapes">Formas</string>
<string name="preference_shapes_base">Forma base</string>
<string name="preference_shapes_rect">Retangular</string>
<string name="confirmation_delete_shapes_scheme">Tem a certeza de que deseja eliminar o esquema de formas %1$s?</string>
<string name="bad_configuration_title">Parabéns, acabou de se bloquear!</string>
<string name="preference_launch_breezyweather_app">Abrir Breezy Weather</string>
<string name="preference_screen_transparencies">Transparência</string>
<string name="preference_transparencies_default">Padrão</string>
<string name="preference_transparencies_semi_transparent">Semi-transparente</string>
<string name="provider_breezy">Breezy Weather</string>
<string name="new_theme_name">(sem nome)</string>
<string name="preference_breezyweather_integration_instructions">Para utilizar Breezy Weather como serviço meteorológico:\n\n1. Aceda a Breezy Weather &gt; Definições &gt; Widgets &amp; Papel de parede &gt; Enviara dados Gadgetbridge &gt; Ativar%1$s\n\n2. Em %1$s, selecione Breezy Weather como serviço de meteorologia.</string>
<string name="music_widget_interactive_progress_bar">Barra de progresso interativa</string>
<string name="missing_permission_tasks_integration">A permissão Tasks é necessária para a integração Tasks</string>
<string name="preference_location_breezy">Gerir localização em Breezy Weather</string>
<string name="no_selection">Nada selecionado</string>
<string name="save_as_file">Guardar como ficheiro</string>
<string name="preference_breezyweather_integration">Breezy Weather</string>
<string name="plugin_type_contacts">Contactos</string>
<string name="confirmation_delete_transparencies_scheme">Tem a certeza de que deseja eliminar o esquema de transparência %1$s?</string>
<string name="action_install">Instalar</string>
<string name="missing_permission_tasks_search_settings">A permissão Tasks é necessária para pesquisar tarefas</string>
<string name="preference_search_tasks">Tarefas</string>
<string name="preference_search_tasks_summary">Pesquisar tarefas na aplicação Tasks</string>
<string name="preference_tasks_integration">Tasks</string>
<string name="preference_tasks_integration_description">Tasks é uma aplicação open souce para gerir tarefas, listas e lembretes. Se instalada, %1$s pode mostrar e pesquisar as tarefas na aplicação.</string>
<string name="preference_tasks_integration_ready">Integração com Tasks finalizada e pronta para utilização.</string>
<plurals name="calendar_widget_running_tasks">
<item quantity="one">+%1$d tarefa por realizar</item>
<item quantity="many">+%1$d tarefas por realizar</item>
<item quantity="other">+%1$d tarefas por realizar</item>
</plurals>
<string name="preference_screen_typography">Tipografia</string>
<string name="preference_breezyweather_integration_description">Breezy Weather é uma aplicação open source de meteorologia. Se instalada, %1$s pode utilizar Breezy Weather com a fonte de dados meteorológicos.</string>
<string name="confirmation_delete_typography_scheme">Tem a certeza de que deseja eliminar o esquema tipográfico %1$s?</string>
<string name="preference_typography_fonts">Tipo de letra</string>
<string name="font_category_device_default">Definições do dispositivo</string>
<string name="font_name_device_headline">Tipo de letra padrão</string>
<string name="font_name_device_body">Tipo de letra do dispositivo</string>
<string name="font_category_generic">Genérica</string>
<string name="preference_widgets_on_home_screen">Widgets no ecrã principal</string>
<string name="preference_gesture_swipe_up">Deslize acima</string>
<string name="preference_widgets_on_home_screen_summary">Mostrar widgets no ecrã principal e não em página secundária</string>
<string name="gesture_action_widgets">Widgets</string>
<string name="bad_configuration_summary">Você conseguir encontrar uma combinação de definições em não é possível aceder às pesquisas e às definições. Acabou de se bloquear.</string>
<string name="preference_launch_tasks_app">Abrir Tasks</string>
</resources>
@@ -847,4 +847,9 @@
<string name="font_name_device_body">ชุดแบบอักษรของอุปกรณ์</string>
<string name="font_name_device_headline">ชุดแบบอักษรเริ่มต้นสำหรับหัวเรื่อง</string>
<string name="font_category_generic">ทั่วไป</string>
<string name="preference_search_bar_style_transparent">โปร่งใส</string>
<string name="preference_search_bar_style_solid">สีทึบ</string>
<string name="preference_search_bar_style_hidden">ซ่อน</string>
<string name="clock_variant_analog_ticks">ขีด</string>
<string name="music_widget_interactive_progress_bar">แถบเวลาแบบควบคุมได้</string>
</resources>
@@ -575,7 +575,7 @@
<string name="location_opens_other_day">开启于 %1$s, %2$s</string>
<string name="menu_map">在地图上查看</string>
<string name="menu_website">打开网站</string>
<string name="plugin_state_setup_required">您需要先设置插件</string>
<string name="plugin_state_setup_required">您需要先设置插件</string>
<string name="plugin_state_error">此插件未正常运行</string>
<string name="menu_bugreport">Bug 报告</string>
<string name="length_unit">长度单位</string>
@@ -613,7 +613,7 @@
<string name="preference_search_supportedunits">支持的单位</string>
<string name="note_widget_conflict_description">链接的文件不为空,且其内容与此笔记的上次保存版本不匹配。您想保留哪个版本?</string>
<string name="note_widget_file_read_error">错误读取注释</string>
<string name="note_widget_file_read_error_description">无法读取链接文件可能已被移动或删除。已从启动器的内部存储中恢复副本。如果您编辑笔记,链接文件可能会被覆盖</string>
<string name="note_widget_file_read_error_description">无法读取链接文件可能已被移动或删除。已从启动器的存储中恢复副本。如果您编辑笔记,链接文件可能会被覆盖</string>
<string name="note_widget_file_write_error">保存笔记时出错</string>
<string name="theme_color_scheme_autogenerate">从原色开始</string>
<string name="import_theme_error">无法读取所选文件。请确保您选择的是有效的主题文件 (*.kvtheme),并且该文件未损坏。</string>
@@ -793,4 +793,63 @@
<string name="preference_clock_widget_time_format_12h">12小时</string>
<string name="preference_clock_widget_time_format_system">系统默认</string>
<string name="missing_permission_call_contacts_settings">需要通话权限才能开始通话</string>
<string name="import_theme_exists">已有的样式将会被覆盖</string>
<string name="preference_search_bar_style_transparent">透明</string>
<string name="preference_search_bar_style_hidden">隐藏</string>
<string name="preference_search_bar_style_solid">填充</string>
<string name="import_theme_contents">文件包含</string>
<string name="font_name_device_headline">标题字体</string>
<string name="theme_import_title">导入样式</string>
<string name="clock_variant_analog_ticks">刻度</string>
<string name="theme_export_title">导出样式</string>
<string name="preference_colors_high_contrast">高对比度</string>
<string name="preference_screen_shapes">形状</string>
<string name="preference_shapes_default">默认</string>
<string name="preference_shapes_rect">直角</string>
<string name="preference_shapes_extra_round">大圆角</string>
<string name="preference_shapes_base">基础形状</string>
<string name="preference_gesture_swipe_up">向上滑动</string>
<string name="gesture_action_widgets">组件页</string>
<string name="confirmation_delete_shapes_scheme">删除形状预设“%1$s”吗?</string>
<string name="bad_configuration_summary">你探索出了一套访问不了搜索和组件页的配置,使你无法进入设置 — 让启动器桌面变成了启动弃。</string>
<string name="preference_transparencies_default">默认</string>
<string name="preference_screen_transparencies">透明度</string>
<string name="preference_transparencies_semi_transparent">半透明</string>
<string name="confirmation_delete_transparencies_scheme">删除透明度预设“%1$s”吗?</string>
<string name="weather_condition_thunder">雷雨</string>
<string name="weather_condition_haze">雾霾</string>
<string name="preference_search_tasks">任务</string>
<string name="preference_launch_breezyweather_app">打开 Breezy Weather</string>
<string name="no_selection">未选</string>
<string name="save_as_file">保存为文件</string>
<string name="preference_breezyweather_integration">Breezy Weather</string>
<string name="theme_bundle_name">名称</string>
<string name="theme_bundle_author">作者</string>
<string name="provider_breezy">Breezy Weather</string>
<string name="preference_location_breezy">在 Breezy Weather 中管理位置</string>
<string name="preference_search_tasks_summary">搜索在 Tasks 应用中的任务</string>
<string name="new_theme_name">(新建)</string>
<string name="font_category_device_default">设备默认的</string>
<string name="music_widget_interactive_progress_bar">可交互进度条</string>
<string name="confirmation_delete_typography_scheme">删除字体预设“%1$s”吗?</string>
<string name="bad_configuration_title">恭喜!你给你自个儿锁里头了!</string>
<string name="missing_permission_tasks_integration">需要 Tasks 权限来接入 Tasks 应用</string>
<string name="preference_breezyweather_integration_instructions">要使用 Breezy Weather 作为天气数据源:\n\n1. 启用 Breezy Weather &gt; 设置 &gt; 微件与动态壁纸 &gt; 发送天气数据到 Gadgetbridge 中的 %1$s\n\n2. 在 %1$s 中将 Breezy Weather 设为天气数据源</string>
<string name="action_install">安装</string>
<string name="preference_tasks_integration">Tasks</string>
<string name="preference_tasks_integration_ready">已成功接入 Tasks 并可以开始使用了。</string>
<string name="preference_tasks_integration_description">Tasks 是一个免费的开源 to-do 及提醒事项应用。如果已经安装,%1$s 可以显示并搜索在 Tasks 应用中的任务。</string>
<plurals name="calendar_widget_running_tasks">
<item quantity="other">+%1$d 个未完成任务</item>
</plurals>
<string name="missing_permission_tasks_search_settings">需要 Tasks 应用权限来搜索任务</string>
<string name="preference_screen_typography">文字</string>
<string name="preference_typography_fonts">字体</string>
<string name="preference_breezyweather_integration_description">Breezy Weather 是一款免费的开源天气应用。安装后 %1$s 可将其作为天气数据的数据源。</string>
<string name="font_name_device_body">正文字体</string>
<string name="font_category_generic">常规字体</string>
<string name="preference_widgets_on_home_screen">直达组件</string>
<string name="preference_widgets_on_home_screen_summary">在主屏幕而不是组件页显示组件</string>
<string name="tag_empty_name">标签必须拥有一个名字。如果继续设为空,这个标签会被删除。</string>
<string name="preference_launch_tasks_app">打开 Tasks 应用</string>
</resources>
+10 -10
View File
@@ -4,7 +4,7 @@
<item quantity="other"></item>
</plurals>
<plurals name="unit_kilometer">
<item quantity="other">公里</item>
<item quantity="other">千米</item>
</plurals>
<plurals name="unit_centimeter">
<item quantity="other">厘米</item>
@@ -119,28 +119,28 @@
<item quantity="other">吉位字节</item>
</plurals>
<plurals name="unit_tebibyte">
<item quantity="other">万亿位字节</item>
<item quantity="other">位字节</item>
</plurals>
<plurals name="unit_kilobit">
<item quantity="other">千比特</item>
</plurals>
<string name="unit_kilobit_symbol">kbit</string>
<string name="unit_megabit_symbol">Mbit</string>
<string name="unit_kilobit_symbol">kb</string>
<string name="unit_megabit_symbol">Mb</string>
<plurals name="unit_megabit">
<item quantity="other">兆比特</item>
</plurals>
<string name="unit_gigabit_symbol">Gbit</string>
<string name="unit_gigabit_symbol">Gb</string>
<plurals name="unit_gigabit">
<item quantity="other">吉比特</item>
</plurals>
<string name="unit_terabit_symbol">万亿比特</string>
<string name="unit_terabit_symbol">TB</string>
<plurals name="unit_terabit">
<item quantity="other">terabits</item>
<item quantity="other">太比特</item>
</plurals>
<string name="unit_meter_per_second_symbol">m/s</string>
<string name="unit_kilometer_per_hour_symbol">km/h</string>
<plurals name="unit_kilometer_per_hour">
<item quantity="other">公里每小时</item>
<item quantity="other">千米每小时</item>
</plurals>
<string name="unit_mile_per_hour_symbol">mph</string>
<plurals name="unit_mile_per_hour">
@@ -160,7 +160,7 @@
</plurals>
<string name="unit_metric_ton_symbol">t</string>
<plurals name="unit_metric_ton">
<item quantity="other"></item>
<item quantity="other"></item>
</plurals>
<string name="unit_long_ton_symbol">tn.l.</string>
<plurals name="unit_long_ton">
@@ -192,7 +192,7 @@
<item quantity="other"></item>
</plurals>
<plurals name="unit_terabyte">
<item quantity="other">万亿字节</item>
<item quantity="other">字节</item>
</plurals>
<string name="unit_kibibyte_symbol">kiB</string>
<plurals name="unit_kibibyte">
@@ -66,7 +66,7 @@
<string name="wind_west_north_west">西北偏西</string>
<string name="wind_north_west">西北</string>
<string name="wind_north_north_west">西北偏北</string>
<string name="wikipedia_url">https://en.wikipedia.org</string>
<string name="wikipedia_url">https://zh.wikipedia.org/zh-tw/</string>
<string name="wikipedia_source">來自維基百科</string>
<string name="file_meta_title">標題:%1$s</string>
<string name="file_meta_artist">藝術家:%1$s</string>
@@ -620,7 +620,7 @@
<string name="search_filter_online">線上結果</string>
<string name="search_filter_apps">應用程式</string>
<string name="preference_clock_widget_alignment_center">中間</string>
<string name="preference_clock_widget_show_seconds">顯示秒</string>
<string name="preference_clock_widget_show_seconds">顯示秒</string>
<string name="preference_clock_widget_alignment_bottom">底部</string>
<string name="preference_clockwidget_dynamic_zone">動態區域</string>
<string name="preference_customize_filter_bar">自訂篩選器列</string>
@@ -845,4 +845,9 @@
<string name="preference_gesture_swipe_up">向上滑動</string>
<string name="gesture_action_widgets">小工具</string>
<string name="preference_launch_tasks_app">開啟 Tasks 應用程式</string>
<string name="preference_search_bar_style_solid">不透明</string>
<string name="preference_search_bar_style_hidden">隱藏</string>
<string name="preference_search_bar_style_transparent">透明</string>
<string name="clock_variant_analog_ticks">時鐘標記</string>
<string name="music_widget_interactive_progress_bar">互動式播放器進度條</string>
</resources>
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -11,15 +11,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
@@ -51,7 +51,6 @@ data class LauncherSettingsData internal constructor(
val clockWidgetBatteryPart: Boolean = true,
val clockWidgetMusicPart: Boolean = true,
val clockWidgetDatePart: Boolean = true,
@Deprecated("Use homeScreenWidgets")
val clockWidgetFillHeight: Boolean = true,
val clockWidgetAlignment: ClockWidgetAlignment = ClockWidgetAlignment.Bottom,
@@ -64,7 +64,13 @@ class ClockWidgetSettings internal constructor(
}
val fillHeight
get() = launcherDataStore.data.map { !it.homeScreenWidgets }
get() = launcherDataStore.data.map { it.clockWidgetFillHeight || !it.homeScreenWidgets }
fun setFillHeight(fillHeight: Boolean) {
launcherDataStore.update {
it.copy(clockWidgetFillHeight = fillHeight)
}
}
val dock
get() = launcherDataStore.data.map { it.homeScreenDock }
+11 -5
View File
@@ -11,15 +11,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
create("nightly") {
initWith(getByName("release"))
+11 -5
View File
@@ -14,15 +14,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
// //consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
create("nightly") {
initWith(getByName("release"))
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
@@ -249,7 +249,7 @@ internal class AppRepositoryImpl(
appResults.addAll(apps.mapNotNull {
val score = ResultScore(
query = query,
primaryFields = listOf(it.label),
primaryFields = listOf(it.label,it.componentName.packageName),
)
if (score.score < 0.8f) return@mapNotNull null
it.copy(
+12 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
@@ -48,5 +54,6 @@ dependencies {
implementation(project(":core:ktx"))
implementation(project(":core:crashreporter"))
implementation(project(":core:preferences"))
implementation(project(":core:profiles"))
}
@@ -13,6 +13,7 @@ import de.mm20.launcher2.ktx.normalize
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.search.ShortcutSearchSettings
import de.mm20.launcher2.profiles.ProfileManager
import de.mm20.launcher2.search.AppShortcut
import de.mm20.launcher2.search.ResultScore
import de.mm20.launcher2.search.SearchableRepository
@@ -27,6 +28,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -54,6 +56,7 @@ internal class AppShortcutRepositoryImpl(
private val context: Context,
private val permissionsManager: PermissionsManager,
private val settings: ShortcutSearchSettings,
private val profileManager: ProfileManager,
) : AppShortcutRepository {
private val scope = CoroutineScope(Dispatchers.Default + Job())
@@ -213,9 +216,9 @@ internal class AppShortcutRepositoryImpl(
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
if (!launcherApps.hasShortcutHostPermission()) return emptyList()
val results = mutableListOf<AppShortcutConfigActivity>()
val profiles = launcherApps.profiles
val profiles = profileManager.activeProfiles.first()
for (profile in profiles) {
val activities = launcherApps.getShortcutConfigActivityList(null, profile)
val activities = launcherApps.getShortcutConfigActivityList(null, profile.userHandle)
results.addAll(
activities.map {
AppShortcutConfigActivity(it)
@@ -8,7 +8,7 @@ import org.koin.core.qualifier.named
import org.koin.dsl.module
val appShortcutsModule = module {
factory<AppShortcutRepository> { AppShortcutRepositoryImpl(androidContext(), get(), get()) }
factory<AppShortcutRepository> { AppShortcutRepositoryImpl(androidContext(), get(), get(), get()) }
factory<SearchableRepository<AppShortcut>>(named<AppShortcut>()) { get<AppShortcutRepository>() }
factory<SearchableDeserializer>(named(LauncherShortcut.Domain)) { LauncherShortcutDeserializer(androidContext()) }
factory<SearchableDeserializer>(named(LegacyShortcut.Domain)) { LegacyShortcutDeserializer(androidContext()) }
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -11,15 +11,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
@@ -27,7 +27,7 @@ internal class ContactRepository(
override fun search(query: String, allowNetwork: Boolean): Flow<List<Contact>> {
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Contacts)
if (query.length < 2) {
if (query.length < 1) {
return flow {
emit(persistentListOf())
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -12,7 +12,7 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
javaCompileOptions {
annotationProcessorOptions {
@@ -23,10 +23,16 @@ android {
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -11,15 +11,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+10 -5
View File
@@ -13,16 +13,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
compileOptions {
@@ -0,0 +1,579 @@
package de.mm20.launcher2.locations.providers.googleapi
import android.content.Context
import de.mm20.launcher2.ktx.ifNullOrEmpty
import de.mm20.launcher2.ktx.into
import de.mm20.launcher2.ktx.map
import de.mm20.launcher2.ktx.stripStartOrNull
import de.mm20.launcher2.locations.OsmLocationSerializer
import de.mm20.launcher2.openstreetmaps.R
import de.mm20.launcher2.search.Location
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.UpdatableSearchable
import de.mm20.launcher2.search.UpdateResult
import de.mm20.launcher2.search.location.Address
import de.mm20.launcher2.search.location.Departure
import de.mm20.launcher2.search.location.LocationIcon
import de.mm20.launcher2.search.location.OpeningHours
import de.mm20.launcher2.search.location.OpeningSchedule
import de.mm20.launcher2.search.location.PaymentMethod
import de.westnordost.osm_opening_hours.model.ClockTime
import de.westnordost.osm_opening_hours.model.ExtendedClockTime
import de.westnordost.osm_opening_hours.model.LastNth
import de.westnordost.osm_opening_hours.model.MonthRange
import de.westnordost.osm_opening_hours.model.Nth
import de.westnordost.osm_opening_hours.model.NthRange
import de.westnordost.osm_opening_hours.model.Range
import de.westnordost.osm_opening_hours.model.SingleMonth
import de.westnordost.osm_opening_hours.model.SpecificWeekdays
import de.westnordost.osm_opening_hours.model.StartingAtYear
import de.westnordost.osm_opening_hours.model.TimeSpan
import de.westnordost.osm_opening_hours.model.TimesSelector
import de.westnordost.osm_opening_hours.model.TwentyFourSeven
import de.westnordost.osm_opening_hours.model.Weekday
import de.westnordost.osm_opening_hours.model.WeekdayRange
import de.westnordost.osm_opening_hours.model.WeekdaysSelector
import de.westnordost.osm_opening_hours.model.Year
import de.westnordost.osm_opening_hours.model.YearRange
import de.westnordost.osm_opening_hours.parser.toOpeningHoursOrNull
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import org.woheller69.AndroidAddressFormatter.OsmAddressFormatter
import java.time.DayOfWeek
import java.time.Duration
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.temporal.ChronoUnit
import java.time.temporal.TemporalAdjusters
import java.util.Locale
import kotlin.math.min
internal data class OsmLocation(
internal val id: Long,
override val label: String,
override val icon: LocationIcon?,
override val category: String?,
override val latitude: Double,
override val longitude: Double,
override val address: Address?,
override val openingSchedule: OpeningSchedule?,
override val websiteUrl: String?,
override val phoneNumber: String?,
override val emailAddress: String? = null,
override val labelOverride: String? = null,
override val timestamp: Long,
override var updatedSelf: (suspend (SavableSearchable) -> UpdateResult<Location>)? = null,
override val userRating: Float?,
override val acceptedPaymentMethods: Map<PaymentMethod, Boolean>?
) : Location, UpdatableSearchable<Location> {
override val domain: String
get() = DOMAIN
override val key: String = "$domain://$id"
override val fixMeUrl: String
get() = FIXMEURL
override val userRatingCount: Int? = null
override val departures: List<Departure>? = null
override fun overrideLabel(label: String): OsmLocation {
return this.copy(labelOverride = label)
}
override fun getSerializer(): SearchableSerializer {
return OsmLocationSerializer()
}
companion object {
internal const val DOMAIN = "osm"
internal const val FIXMEURL = "https://www.openstreetmap.org/fixthemap"
internal val addressFormatter =
OsmAddressFormatter(
false,
false,
false
)
fun fromOverpassResponse(
result: OverpassResponse,
context: Context
): List<OsmLocation> = result.elements.mapNotNull {
it.tags ?: return@mapNotNull null
val (category, icon) = it.tags.categorize(context)
icon ?: return@mapNotNull null
OsmLocation(
id = it.id,
label = it.tags["name"] ?: it.tags["brand"] ?: return@mapNotNull null,
icon = icon,
category = category,
latitude = it.lat ?: it.center?.lat ?: return@mapNotNull null,
longitude = it.lon ?: it.center?.lon ?: return@mapNotNull null,
address = it.tags.toAddress(),
openingSchedule = it.tags["opening_hours"]?.let { ot -> parseOpeningSchedule(ot) },
websiteUrl = it.tags["website"] ?: it.tags["contact:website"],
phoneNumber = it.tags["phone"] ?: it.tags["contact:phone"],
emailAddress = it.tags["email"] ?: it.tags["contact:email"],
timestamp = System.currentTimeMillis(),
userRating = it.tags["stars"]?.runCatching { this.toInt() }?.getOrNull()
?.let { min(it, 5) / 5.0f },
acceptedPaymentMethods = with(
it.tags.mapNotNull { (key, value) ->
(key.stripStartOrNull("payment:") ?: return@mapNotNull null) to value
}.toMap()
) {
// best-effort way to take any method payment as it being available,
// otherwise as being unavailable, or undefined
mapOf(
PaymentMethod.Card to listOf("credit_cards", "debit_cards", "cards"),
PaymentMethod.Cash to listOf("cash")
).mapNotNull { (method, values) ->
when {
values.any { this[it] in listOf("yes", "only") } -> method to true
values.any { this[it] == "no" } -> method to false
else -> null
}
}.toMap().takeUnless { it.isEmpty() }
}
)
}
}
}
private fun Map<String, String>.firstOfAlso(vararg strs: String, also: (String) -> Unit): String? {
for (str in strs) {
if (str in this) {
also(str)
return this[str]
}
}
return null
}
private fun Map<String, String>.toAddress(): Address? {
val formatAddrKeys = this.keys.filter { it.contains("addr") }.toMutableSet()
if (formatAddrKeys.isEmpty()) return null
val addr = Address(
city = firstOfAlso("addr:city", "addr:suburb", "addr:hamlet") { formatAddrKeys.remove(it) },
state = firstOfAlso("addr:state", "addr:province") { formatAddrKeys.remove(it) },
postalCode = firstOfAlso("addr:postcode") { formatAddrKeys.remove(it) },
country = firstOfAlso("addr:country") { formatAddrKeys.remove(it) },
)
val formattedRest = buildJsonObject {
formatAddrKeys.mapNotNull {
val (_, subkey) = it.split(':', limit = 2).takeIf { it.size == 2 }
?: return@mapNotNull null
put(subkey, this@toAddress[it])
}
}.takeIf { it.isNotEmpty() }?.toString()?.runCatching {
OsmLocation.addressFormatter.format(
this,
this@toAddress["addr:country"] ?: Locale.getDefault().country
)
}?.getOrNull() ?: return addr
val lines = formattedRest.lines().filter { it.isNotBlank() }
return addr.copy(
address = lines.getOrNull(0),
address2 = lines.getOrNull(1),
address3 = lines.getOrNull(2),
)
}
private class MatchAnyReceiverScope<T, A, B> {
private val pairs = mutableMapOf<T, Pair<A, B>>()
operator fun get(key: T): Pair<A, B>? = pairs[key]
infix fun T.with(pair: Pair<A, B>) = pairs.put(this, pair)
}
private fun <A, B> Map<String, String>.matchAnyTag(
key: String,
block: MatchAnyReceiverScope<String, A, B>.() -> Unit
): Pair<A, B>? {
val scope = MatchAnyReceiverScope<String, A, B>()
scope.block()
return this[key]?.split(' ', ',', '.', ';')?.map { it.trim() }
?.firstNotNullOfOrNull { scope[it] }
}
private fun Map<String, String>.categorize(context: Context): Pair<String?, LocationIcon?> {
val category = this.firstNotNullOfOrNull { (tag, value) ->
val values = value.split(' ', ',', '.', ';').map { it.trim() }.toSet()
when (tag.lowercase()) {
"shop" -> values.firstNotNullOfOrNull {
when (it) {
"florist" -> R.string.poi_category_florist to LocationIcon.Florist
"kiosk" -> R.string.poi_category_kiosk to LocationIcon.Kiosk
"furniture" -> R.string.poi_category_furniture to LocationIcon.FurnitureStore
"cell_phones", "mobile_phone" -> R.string.poi_category_mobile_phone to LocationIcon.CellPhoneStore
"books" -> R.string.poi_category_books to LocationIcon.BookStore
"clothes" -> R.string.poi_category_clothes to LocationIcon.ClothingStore
"convenience" -> R.string.poi_category_convenience to LocationIcon.ConvenienceStore
"discount" -> R.string.poi_category_discount_store to LocationIcon.DiscountStore
"jewelry" -> R.string.poi_category_jewelry to LocationIcon.JewelryStore
"alcohol" -> R.string.poi_category_alcohol to LocationIcon.LiquorStore
"pet", "pet_grooming" -> R.string.poi_category_pet to LocationIcon.PetStore
"mall", "shopping_centre", "department_store" -> R.string.poi_category_mall to LocationIcon.ShoppingMall
"supermarket" -> R.string.poi_category_supermarket to LocationIcon.Supermarket
"bakery" -> R.string.poi_category_bakery to LocationIcon.Bakery
"optician" -> R.string.poi_category_optician to LocationIcon.Optician
"hairdresser" -> R.string.poi_category_hairdresser to LocationIcon.HairSalon
"laundry" -> R.string.poi_category_laundry to LocationIcon.Laundromat
else -> R.string.poi_category_shopping to LocationIcon.Shopping
}
}
"amenity" -> values.firstNotNullOfOrNull {
when (it) {
"place_of_worship" -> matchAnyTag<Int, LocationIcon>("religion") {
"christian" with (R.string.poi_category_church to LocationIcon.Church)
"muslim" with (R.string.poi_category_mosque to LocationIcon.Mosque)
"buddhist" with (R.string.poi_category_buddhist_temple to LocationIcon.BuddhistTemple)
"hindu" with (R.string.poi_category_hindu_temple to LocationIcon.HinduTemple)
"jewish" with (R.string.poi_category_synagogue to LocationIcon.Synagogue)
} ?: (R.string.poi_category_place_of_worship to LocationIcon.PlaceOfWorship)
"fast_food" -> R.string.poi_category_fast_food to LocationIcon.FastFood
"cafe" -> R.string.poi_category_cafe to LocationIcon.Cafe
"ice_cream" -> R.string.poi_category_ice_cream to LocationIcon.IceCream
"bar" -> R.string.poi_category_bar to LocationIcon.Bar
"pub" -> R.string.poi_category_pub to LocationIcon.Pub
"restaurant" -> matchAnyTag<Int, LocationIcon>("cuisine") {
"pizza" with (R.string.poi_category_pizza_restaurant to LocationIcon.Pizza)
"burger" with (R.string.poi_category_burger_restaurant to LocationIcon.Burger)
"chinese" with (R.string.poi_category_chinese_restaurant to LocationIcon.Ramen)
"ramen" with (R.string.poi_category_ramen_restaurant to LocationIcon.Ramen)
"japanese" with (R.string.poi_category_japanese_restaurant to LocationIcon.JapaneseCuisine)
"kebab" with (R.string.poi_category_kebab_restaurant to LocationIcon.Kebab)
"asian" with (R.string.poi_category_asian_restaurant to LocationIcon.AsianCuisine)
"soup" with (R.string.poi_category_soup_restaurant to LocationIcon.Soup)
"coffee_shop" with (R.string.poi_category_cafe to LocationIcon.Cafe)
"brunch" with (R.string.poi_category_brunch_restaurant to LocationIcon.Brunch)
} ?: (R.string.poi_category_restaurant to LocationIcon.Restaurant)
"fuel" -> R.string.poi_category_fuel to LocationIcon.GasStation
"car_rental" -> R.string.poi_category_car_rental to LocationIcon.CarRental
"car_sharing" -> R.string.poi_category_car_sharing to LocationIcon.CarRental
"car_wash" -> R.string.poi_category_car_wash to LocationIcon.CarWash
"charging_station" -> R.string.poi_category_charging_station to LocationIcon.ChargingStation
"parking", "parking_space", "motorcycle_parking" -> R.string.poi_category_parking to LocationIcon.Parking
"motorcycle_rental" -> R.string.poi_category_motorcycle_rental to LocationIcon.Motorcycle
"theatre" -> R.string.poi_category_theater to LocationIcon.Theater
"cinema" -> R.string.poi_category_cinema to LocationIcon.MovieTheater
"nightclub" -> R.string.poi_category_nightclub to LocationIcon.NightClub
"concert_hall" -> R.string.poi_category_concert_hall to LocationIcon.ConcertHall
"casino" -> R.string.poi_category_casino to LocationIcon.Casino
"pharmacy" -> R.string.poi_category_pharmacy to LocationIcon.Pharmacy
"bank" -> R.string.poi_category_bank to LocationIcon.Bank
"atm" -> R.string.poi_category_atm to LocationIcon.Atm
"doctors" -> R.string.poi_category_doctors to LocationIcon.Physician
"dentist" -> R.string.poi_category_dentist to LocationIcon.Dentist
"hospital" -> R.string.poi_category_hospital to LocationIcon.Hospital
"clinic" -> R.string.poi_category_clinic to LocationIcon.Clinic
"police" -> R.string.poi_category_police to LocationIcon.Police
"fire_station" -> R.string.poi_category_fire_station to LocationIcon.FireDepartment
"courthouse" -> R.string.poi_category_courthouse to LocationIcon.Courthouse
"post_office" -> R.string.poi_category_post_office to LocationIcon.PostOffice
"library" -> R.string.poi_category_library to LocationIcon.Library
"school" -> R.string.poi_category_school to LocationIcon.School
"university" -> R.string.poi_category_university to LocationIcon.University
"toilets" -> R.string.poi_category_toilets to LocationIcon.PublicBathroom
"townhall" -> R.string.poi_category_townhall to LocationIcon.GovernmentBuilding
else -> null
}
}
"tourism" -> values.firstNotNullOfOrNull {
when (it) {
"gallery" -> R.string.poi_category_gallery to LocationIcon.ArtGallery
"museum" -> R.string.poi_category_museum to LocationIcon.Museum
"theme_park" -> R.string.poi_category_amusement_park to LocationIcon.AmusementPark
"hotel" -> R.string.poi_category_hotel to LocationIcon.Hotel
else -> null
}
}
"leisure" -> values.firstNotNullOfOrNull {
when (it) {
"stadium" -> R.string.poi_category_stadium to LocationIcon.Stadium
"fitness_centre" -> R.string.poi_category_fitness_center to LocationIcon.FitnessCenter
"swimming_pool" -> R.string.poi_category_swimming to LocationIcon.Swimming
"pitch", "sports_centre" -> matchAnyTag<Int, LocationIcon>("sport") {
"soccer" with (R.string.poi_category_soccer to LocationIcon.Soccer)
"tennis" with (R.string.poi_category_tennis to LocationIcon.Tennis)
"basketball" with (R.string.poi_category_basketball to LocationIcon.Basketball)
"gymnastics" with (R.string.poi_category_gymnastics to LocationIcon.Gymnastics)
"martial_arts" with (R.string.poi_category_martial_arts to LocationIcon.MartialArts)
"ice_hockey" with (R.string.poi_category_ice_hockey to LocationIcon.Hockey)
"baseball" with (R.string.poi_category_baseball to LocationIcon.Baseball)
"american_football" with (R.string.poi_category_american_football to LocationIcon.AmericanFootball)
"handball" with (R.string.poi_category_handball to LocationIcon.Handball)
"volleyball" with (R.string.poi_category_volleyball to LocationIcon.Volleyball)
"skiing" with (R.string.poi_category_skiing to LocationIcon.Skiing)
"cricket" with (R.string.poi_category_cricket to LocationIcon.Cricket)
}
"golf_course" -> R.string.poi_category_golf to LocationIcon.Golf
"park" -> R.string.poi_category_park to LocationIcon.Park
else -> null
}
}
"historic" -> values.firstNotNullOfOrNull {
when (it) {
"monument" -> R.string.poi_category_monument to LocationIcon.Monument
else -> null
}
}
"building" -> values.firstNotNullOfOrNull {
when (it) {
"government" -> R.string.poi_category_government_building to LocationIcon.GovernmentBuilding
else -> null
}
}
else -> null
}
}
val (rid, icon) = category ?: return null to null
return context.resources.getString(rid) to icon
}
internal fun parseOpeningSchedule(
openingHours: String?,
localTime: LocalDateTime = LocalDateTime.now()
): OpeningSchedule? {
val parsed = openingHours?.toOpeningHoursOrNull(lenient = true) ?: return null
if (parsed.rules.singleOrNull()?.selector is TwentyFourSeven) {
return OpeningSchedule.TwentyFourSeven
}
val rangeRules = parsed.rules.mapNotNull { it.selector as? Range }
// Group rules by the weekdays they apply to. Rules can apply to multiple weekdays.
val rulesMap = mutableMapOf<Weekday, MutableList<Range>>(
Weekday.Monday to mutableListOf(),
Weekday.Tuesday to mutableListOf(),
Weekday.Wednesday to mutableListOf(),
Weekday.Thursday to mutableListOf(),
Weekday.Friday to mutableListOf(),
Weekday.Saturday to mutableListOf(),
Weekday.Sunday to mutableListOf()
)
for (rule in rangeRules) {
if (rule.weekdays != null) {
for (selector in rule.weekdays!!) {
when (selector) {
is Weekday -> rulesMap[selector]!!.add(rule)
is WeekdayRange -> {
for (weekday in selector.start.ordinal..selector.end.ordinal) {
rulesMap[Weekday.entries[weekday]]!!.add(rule)
}
}
is SpecificWeekdays -> {
rulesMap[selector.weekday]!!.add(rule)
}
}
}
} 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)
}
}
}
// 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)) }
}
val hours = mutableSetOf<OpeningHours>()
for (range in applicableRules) {
val localTimesWithDuration =
range.times?.mapNotNull { it.toLocalTimeWithDuration() } ?: continue
val daysOfWeek = range.weekdays
.ifNullOrEmpty { Weekday.entries.toList() }
.flatMap { it.toDaysOfWeek() }
hours += daysOfWeek.flatMap { dow ->
localTimesWithDuration.map {
val (start, dur) = it
OpeningHours(dow, start, dur)
}
}
}
return OpeningSchedule.Hours(hours)
}
/**
* Returns only the rules that are valid for the given year.
*/
private fun List<Range>.filterYears(localTime: LocalDateTime): List<Range> {
if (all { it.years.isNullOrEmpty() }) return this
val thisYear = filter {
it.years?.any {
when (it) {
is Year -> it.year == localTime.year
is StartingAtYear -> it.start <= localTime.year
is YearRange -> localTime.year in it.start..it.end step (it.step ?: 1)
}
} == true
}
if (!thisYear.isEmpty()) return thisYear
return filter { it.years.isNullOrEmpty() }
}
/**
* Returns only the rules that are valid for the given month.
*/
private fun List<Range>.filterMonths(localTime: LocalDateTime): List<Range> {
if (all { it.months.isNullOrEmpty() }) return this
val thisMonth = filter {
it.months?.any {
when (it) {
is MonthRange -> (it.year?.let { it == localTime.year } != false) && localTime.month.ordinal in it.start.ordinal..it.end.ordinal
is SingleMonth -> (it.year?.let { it == localTime.year } != false) && localTime.month.ordinal == it.month.ordinal
else -> false
}
} == true
}
if (!thisMonth.isEmpty()) return thisMonth
return filter { it.months.isNullOrEmpty() }
}
/**
* Returns only the rules that are valid for the given week.
* (i.e. if the given week is the 2nd week of the month, it will return only the rules that are
* valid for the 2nd week of the month)
*/
private fun List<Range>.filterNthDays(localTime: LocalDateTime): List<Range> {
if (none { it.weekdays?.any { it is SpecificWeekdays } == true }) return this
val currentWeek = localTime.getNthWeekdaysOfCurrentWeek()
val specific = mapNotNull { range ->
(range.weekdays?.singleOrNull() as? SpecificWeekdays)?.let {
range to it
}
}
val rule = specific.firstOrNull { (_, specific) ->
currentWeek.any { (dow, nthFwd, nthBwd) ->
specific.weekday.ordinal == dow.ordinal && specific.nths.any {
when (it) {
is Nth -> it.nth == nthFwd
is NthRange -> nthFwd in it.start..it.end
is LastNth -> it.nth == nthBwd
}
}
}
}
if (rule != null) return listOf(rule.first)
return listOfNotNull(
singleOrNull { it.weekdays?.singleOrNull() !is SpecificWeekdays }
)
}
private fun WeekdaysSelector.toDaysOfWeek(): List<DayOfWeek> {
return when (this) {
is Weekday -> listOf(
when (this) {
Weekday.Monday -> DayOfWeek.MONDAY
Weekday.Tuesday -> DayOfWeek.TUESDAY
Weekday.Wednesday -> DayOfWeek.WEDNESDAY
Weekday.Thursday -> DayOfWeek.THURSDAY
Weekday.Friday -> DayOfWeek.FRIDAY
Weekday.Saturday -> DayOfWeek.SATURDAY
Weekday.Sunday -> DayOfWeek.SUNDAY
}
)
is WeekdayRange -> (start to end).map { it.toDaysOfWeek().single().value }
.into { start, end -> (start..end).map { DayOfWeek.of(it) } }
is SpecificWeekdays -> weekday.toDaysOfWeek()
}
}
private fun TimesSelector.toLocalTimeWithDuration(): Pair<LocalTime, Duration>? {
if (this !is TimeSpan) return null
val start = start as? ClockTime ?: return null
val end = end as? ExtendedClockTime ?: return null
return LocalTime.of(
start.hour,
start.minutes
) to Duration.ofMinutes(
(Math.floorMod(
end.hour - start.hour,
24
) * 60 + end.minutes - start.minutes).toLong()
)
}
/**
* Calculates the ordinal position of each day of the current week (Monday to Sunday) within the month.
*
* @receiver LocalDateTime The current date and time.
* @return A list of triples, where each triple contains:
* - The `DayOfWeek` (e.g., Monday, Tuesday, etc.).
* - The forward ordinal position of the day in the month (e.g., 1st Monday, 2nd Tuesday, etc.).
* - The backward ordinal position of the day in the month (e.g., last Monday, 2nd last Tuesday, etc.).
*
* Example:
* If today is the 15th of a month (Wednesday), the function will return:
* - For Monday: `(DayOfWeek.MONDAY, 3, 2)` (3rd Monday of the month, 2nd last Monday of the month).
* - For Wednesday: `(DayOfWeek.WEDNESDAY, 3, 2)` (3rd Wednesday of the month, 2nd last Wednesday of the month).
*/
private fun LocalDateTime.getNthWeekdaysOfCurrentWeek(): List<Triple<DayOfWeek, Int, Int>> {
val monday = with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
return (0 until 7).map { i ->
val (nth, nthLast) = monday.plusDays(i.toLong()).let { weekday ->
(
ChronoUnit.WEEKS.between(
with(TemporalAdjusters.firstInMonth(weekday.dayOfWeek)),
weekday
).toInt() + 1
) to (
ChronoUnit.WEEKS.between(
weekday,
with(TemporalAdjusters.lastInMonth(weekday.dayOfWeek))
).toInt() + 1
)
}
Triple(DayOfWeek.entries[i], nth, nthLast)
}
}
@@ -0,0 +1,253 @@
package de.mm20.launcher2.locations.providers.googleapi
import android.content.Context
import android.util.Log
import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.locations.providers.AndroidLocation
import de.mm20.launcher2.locations.providers.LocationProvider
import de.mm20.launcher2.openstreetmaps.R
import de.mm20.launcher2.preferences.search.LocationSearchSettings
import de.mm20.launcher2.search.Location
import de.mm20.launcher2.search.ResultScore
import de.mm20.launcher2.search.UpdateResult
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import retrofit2.HttpException
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.net.UnknownHostException
private val Scope = CoroutineScope(Job() + Dispatchers.IO)
private val HttpClient = OkHttpClient()
internal class OsmLocationProvider(
private val context: Context,
settings: LocationSearchSettings
) : LocationProvider<Long> {
private val overpassApi = settings.overpassUrl.map {
try {
Retrofit.Builder()
.client(HttpClient)
.baseUrl(it?.takeIf { it.isNotBlank() }
?: LocationSearchSettings.DefaultOverpassUrl)
.addConverterFactory(OverpassQueryConverterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(OverpassApi::class.java)
} catch (e: Exception) {
CrashReporter.logException(e)
null
}
}.stateIn(Scope, SharingStarted.Eagerly, null)
suspend fun update(
id: Long
): UpdateResult<Location> = overpassApi.first()?.runCatching {
this.search(
OverpassIdQuery(
id = id
)
).let {
OsmLocation.fromOverpassResponse(it, context)
}.first().apply {
updatedSelf = { update(id) }
}
}?.fold(
onSuccess = { UpdateResult.Success(it) },
onFailure = {
when (it) {
is CancellationException, is UnknownHostException -> {
// network
UpdateResult.TemporarilyUnavailable(it)
}
is HttpException -> when (it.code()) {
in 400..499 -> UpdateResult.PermanentlyUnavailable(it)
else -> UpdateResult.TemporarilyUnavailable(it)
}
is NoSuchElementException -> {
// empty response
UpdateResult.PermanentlyUnavailable(it)
}
else -> {
if (it is Exception) {
CrashReporter.logException(it)
}
UpdateResult.TemporarilyUnavailable(it)
}
}
}
) ?: let {
Log.e("OsmProvider", "overpassApi was not initialized")
UpdateResult.TemporarilyUnavailable()
}
override suspend fun search(
query: String,
userLocation: AndroidLocation,
allowNetwork: Boolean,
searchRadiusMeters: Int,
hideUncategorized: Boolean,
): List<Location> {
if (!allowNetwork || query.length < 2) {
return emptyList()
}
withContext(Dispatchers.IO) {
HttpClient.dispatcher.cancelAll()
}
return overpassApi.first()?.runCatching {
search(
OverpassFuzzyRadiusQuery(
query = query,
tagGroups = delocalizeToQueryableTags(query),
radius = searchRadiusMeters,
latitude = userLocation.latitude,
longitude = userLocation.longitude
)
)
}?.onFailure {
if (it !is HttpException && it !is CancellationException) {
Log.e("OsmLocationProvider", "Failed to search for: $query", it)
}
}?.getOrNull()?.let {
OsmLocation.fromOverpassResponse(it, context)
}?.asSequence()?.filter {
(!hideUncategorized || (it.category != null)) && it.distanceTo(userLocation) < searchRadiusMeters
}?.groupBy {
it.label.lowercase()
}?.flatMap { (_, duplicates) ->
// deduplicate results with same labels, if
// - same category
// - distance is less than 100m
if (duplicates.size < 2) duplicates
else {
val luckyFirst = duplicates.first()
duplicates
.drop(1)
.filter {
it.category != luckyFirst.category ||
it.distanceTo(luckyFirst) > 100.0
} + luckyFirst
}
}?.sortedBy {
it.distanceTo(userLocation)
}?.take(9)?.toImmutableList() ?: emptyList()
}
private val poiCategories = mapOf(
R.string.poi_category_restaurant to "amenity=restaurant",
R.string.poi_category_fast_food to "amenity=fast_food",
R.string.poi_category_bar to "amenity=bar",
R.string.poi_category_cafe to "amenity=cafe",
R.string.poi_category_hotel to "tourism=hotel",
R.string.poi_category_supermarket to "shop=supermarket",
R.string.poi_category_school to "amenity=school",
R.string.poi_category_parking to "amenity=parking",
R.string.poi_category_fuel to "amenity=fuel",
R.string.poi_category_toilets to "amenity=toilets",
R.string.poi_category_pharmacy to "amenity=pharmacy",
R.string.poi_category_hospital to "amenity=hospital",
R.string.poi_category_post_office to "amenity=post_office",
R.string.poi_category_pub to "amenity=pub",
R.string.poi_category_doctors to "amenity=doctors",
R.string.poi_category_police to "amenity=police",
R.string.poi_category_dentist to "amenity=dentist",
R.string.poi_category_library to "amenity=library",
R.string.poi_category_ice_cream to "amenity=ice_cream",
R.string.poi_category_theater to "amenity=theatre",
R.string.poi_category_cinema to "amenity=cinema",
R.string.poi_category_nightclub to "amenity=nightclub",
R.string.poi_category_clinic to "amenity=clinic",
R.string.poi_category_university to "amenity=university",
R.string.poi_category_clothes to "shop=clothes",
R.string.poi_category_convenience to "shop=convenience",
R.string.poi_category_hairdresser to "shop=hairdresser",
R.string.poi_category_books to "shop=books",
R.string.poi_category_bakery to "shop=bakery",
R.string.poi_category_car_rental to "amenity=car_rental",
R.string.poi_category_car_sharing to "amenity=car_sharing",
R.string.poi_category_mobile_phone to "shop=mobile_phone",
R.string.poi_category_furniture to "shop=furniture",
R.string.poi_category_alcohol to "shop=alcohol",
R.string.poi_category_florist to "shop=florist",
R.string.poi_category_mall to "shop=mall",
R.string.poi_category_optician to "shop=optician",
R.string.poi_category_jewelry to "shop=jewelry",
R.string.poi_category_laundry to "amenity=laundry",
R.string.poi_category_bank to "amenity=bank",
R.string.poi_category_soccer to "leisure=pitch,sport=soccer",
R.string.poi_category_basketball to "leisure=pitch,sport=basketball",
R.string.poi_category_tennis to "leisure=pitch,sport=tennis",
R.string.poi_category_atm to "amenity=atm",
R.string.poi_category_kiosk to "shop=kiosk",
R.string.poi_category_museum to "tourism=museum",
R.string.poi_category_fitness_center to "leisure=fitness_centre",
R.string.poi_category_church to "amenity=place_of_worship,religion=christian",
R.string.poi_category_mosque to "amenity=place_of_worship,religion=muslim",
R.string.poi_category_buddhist_temple to "amenity=place_of_worship,religion=buddhist",
R.string.poi_category_hindu_temple to "amenity=place_of_worship,religion=hindu",
R.string.poi_category_synagogue to "amenity=place_of_worship,religion=jewish",
R.string.poi_category_pizza_restaurant to "amenity=restaurant,cuisine=pizza",
R.string.poi_category_burger_restaurant to "amenity=restaurant,cuisine=burger",
R.string.poi_category_place_of_worship to "amenity=place_of_worship",
R.string.poi_category_chinese_restaurant to "amenity=restaurant,cuisine=chinese",
R.string.poi_category_japanese_restaurant to "amenity=restaurant,cuisine=japanese",
R.string.poi_category_kebab_restaurant to "amenity=restaurant,cuisine=kebab",
R.string.poi_category_asian_restaurant to "amenity=restaurant,cuisine=asian",
R.string.poi_category_ramen_restaurant to "amenity=restaurant,cuisine=ramen",
R.string.poi_category_soup_restaurant to "amenity=restaurant,cuisine=soup",
R.string.poi_category_brunch_restaurant to "amenity=restaurant,cuisine=brunch",
R.string.poi_category_car_wash to "amenity=car_wash",
R.string.poi_category_charging_station to "amenity=charging_station",
R.string.poi_category_motorcycle_rental to "amenity=motorcycle_rental",
R.string.poi_category_gallery to "tourism=gallery",
R.string.poi_category_amusement_park to "tourism=theme_park",
R.string.poi_category_concert_hall to "amenity=concert_hall",
R.string.poi_category_stadium to "leisure=stadium",
R.string.poi_category_casino to "amenity=casino",
R.string.poi_category_discount_store to "shop=discount",
R.string.poi_category_pet to "shop=pet",
R.string.poi_category_shopping to "shop=mall",
R.string.poi_category_swimming to "leisure=swimming_pool",
R.string.poi_category_martial_arts to "leisure=sports_centre,sport=martial_arts",
R.string.poi_category_golf to "leisure=golf_course",
R.string.poi_category_gymnastics to "leisure=sports_hall,sport=gymnastics",
R.string.poi_category_ice_hockey to "leisure=sports_centre,sport=ice_hockey",
R.string.poi_category_baseball to "leisure=pitch,sport=baseball",
R.string.poi_category_american_football to "leisure=pitch,sport=american_football",
R.string.poi_category_handball to "leisure=pitch,sport=handball",
R.string.poi_category_volleyball to "leisure=pitch,sport=volleyball",
R.string.poi_category_skiing to "leisure=piste",
R.string.poi_category_cricket to "leisure=pitch,sport=cricket",
R.string.poi_category_park to "leisure=park",
R.string.poi_category_monument to "historic=monument",
R.string.poi_category_government_building to "building=government",
R.string.poi_category_fire_station to "amenity=fire_station",
R.string.poi_category_courthouse to "amenity=courthouse",
R.string.poi_category_townhall to "amenity=townhall"
).mapKeys { context.getString(it.key) }
private fun delocalizeToQueryableTags(localizedQuery: String): List<String> =
poiCategories.mapNotNull { (string, tags) ->
val score = ResultScore(
localizedQuery,
primaryFields = listOf(string)
)
if (score.score > 0.8f) tags else null
}
}
@@ -0,0 +1,126 @@
package de.mm20.launcher2.locations.providers.googleapi
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.http.Body
import retrofit2.http.POST
import java.lang.reflect.Type
import kotlin.math.cos
/**
* Overpass API query builder
* Searches for nodes and ways that at least:
* - match the query string in their name or brand tag
* - match one of the given tag groups
*/
data class OverpassFuzzyRadiusQuery(
/**
* Free text query to search for.
*/
val query: String,
/**
* Tags groups to search for. Each item represents a group of tags, separated by commas.
* The query matches if all tags in a group are present in the element.
* For example:
* ["amenity=restaurant,cuisine=italian", "amenity=cafe"]
* This query will match elements that are either a restaurant with italian cuisine or a cafe.
*/
val tagGroups: List<String>,
val radius: Int,
val latitude: Double,
val longitude: Double,
)
data class OverpassIdQuery(
val id: Long,
)
data class OverpassResponse(
val elements: List<OverpassResponseElement>,
)
data class OverpassResponseElementCenter(
val lat: Double,
val lon: Double,
)
data class OverpassResponseElement(
val type: String,
val id: Long,
val lat: Double?,
val lon: Double?,
val center: OverpassResponseElementCenter?,
val tags: Map<String, String>?,
)
interface OverpassApi {
@POST("api/interpreter")
suspend fun search(@Body data: OverpassFuzzyRadiusQuery): OverpassResponse
@POST("api/interpreter")
suspend fun search(@Body data: OverpassIdQuery): OverpassResponse
}
class OverpassFuzzyRadiusQueryConverter : Converter<OverpassFuzzyRadiusQuery, RequestBody> {
override fun convert(value: OverpassFuzzyRadiusQuery): RequestBody {
val encodedQuery = value.query.split(' ')
.joinToString(
separator = ".*",
prefix = "\"",
postfix = "\""
) { Regex.escapeReplacement(it) }
val overpassQlBuilder = StringBuilder()
val latDegreeChange = value.radius * 0.00001 / 1.11
val lonDegreeChange = latDegreeChange / cos(Math.toRadians(value.latitude))
val boundingBox = arrayOf(
value.latitude - latDegreeChange, value.longitude - lonDegreeChange,
value.latitude + latDegreeChange, value.longitude + lonDegreeChange
)
overpassQlBuilder.append("[out:json][timeout:10][bbox:" + boundingBox.joinToString(",") + "];")
overpassQlBuilder.append("(")
overpassQlBuilder.append("nw[name~$encodedQuery,i];")
overpassQlBuilder.append("nw[brand~$encodedQuery,i];")
for (tag in value.tagGroups) {
val tags = tag.split(',')
if (tags.isEmpty()) continue
overpassQlBuilder.append("nw[${tags.joinToString("][")}];")
}
overpassQlBuilder.append(");")
// center to add the center coordinate of a way to the result, if applicable
overpassQlBuilder.append("out center;")
return overpassQlBuilder.toString().toRequestBody()
}
}
class OverpassIdQueryConverter : Converter<OverpassIdQuery, RequestBody> {
override fun convert(value: OverpassIdQuery): RequestBody = """
[out:json];
nw(${value.id});
out center;
""".trimIndent().toRequestBody()
}
class OverpassQueryConverterFactory : Converter.Factory() {
override fun requestBodyConverter(
type: Type,
parameterAnnotations: Array<out Annotation>,
methodAnnotations: Array<out Annotation>,
retrofit: Retrofit
): Converter<*, RequestBody>? {
if (type == OverpassFuzzyRadiusQuery::class.java)
return OverpassFuzzyRadiusQueryConverter()
if (type == OverpassIdQuery::class.java)
return OverpassIdQueryConverter()
return null
}
}
@@ -102,7 +102,7 @@ internal class OsmLocationProvider(
searchRadiusMeters: Int,
hideUncategorized: Boolean,
): List<Location> {
if (!allowNetwork || query.length < 2) {
if (!allowNetwork || query.length < 1) {
return emptyList()
}
@@ -243,11 +243,20 @@ internal class OsmLocationProvider(
).mapKeys { context.getString(it.key) }
private fun delocalizeToQueryableTags(localizedQuery: String): List<String> =
poiCategories.mapNotNull { (string, tags) ->
val score = ResultScore(
val score = if (string.contains("|")) {
ResultScore(
localizedQuery,
primaryFields = string.split("|")
)
} else {
ResultScore(
localizedQuery,
primaryFields = listOf(string)
)
}
if (score.score > 0.8f) tags else null
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -11,15 +11,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+10 -5
View File
@@ -11,15 +11,19 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
//// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
}
}
@@ -43,6 +47,7 @@ dependencies {
implementation(libs.bundles.retrofit)
implementation(libs.suncalc)
implementation(libs.koin.android)
implementation(libs.logging.interceptor)
implementation(project(":data:database"))
implementation(project(":core:base"))
+11 -3
View File
@@ -1,3 +1,11 @@
-keep class de.mm20.launcher2.weather.** { *; }
-keep class kotlin.coroutines.Continuation
-dontwarn edu.umd.cs.findbugs.annotations.Nullable
#-keep class de.mm20.launcher2.weather.** { *; }
#-keep class kotlin.coroutines.Continuation
#-dontwarn edu.umd.cs.findbugs.annotations.Nullable
#
#-assumenosideeffects class android.util.Log {
## public static *** d(...);
# public static *** v(...);
# public static *** i(...);
# public static *** w(...);
## public static *** e(...);
#}
+26 -19
View File
@@ -1,21 +1,28 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.kts.kts.kts.kts.
## Add project specific ProGuard rules here.
## You can control the set of applied configuration files using the
## proguardFiles setting in build.gradle.kts.kts.kts.kts.kts.
##
## For more details, see
## http://developer.android.com/guide/developing/tools/proguard.html
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
## If your project uses WebView with JS, uncomment the following
## and specify the fully qualified class name to the JavaScript interface
## class:
##-keepclassmembers class fqcn.of.javascript.interface.for.webview {
## public *;
##}
#
## Uncomment this to preserve the line number information for
## debugging stack traces.
##-keepattributes SourceFile,LineNumberTable
#
## If you keep the line number information, uncomment this to
## hide the original source file name.
##-renamesourcefileattribute SourceFile
#-assumenosideeffects class android.util.Log {
## public static *** d(...);
# public static *** v(...);
# public static *** i(...);
# public static *** w(...);
## public static *** e(...);
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -7,9 +7,13 @@ import de.mm20.launcher2.preferences.weather.WeatherLocation
import de.mm20.launcher2.weather.Forecast
import de.mm20.launcher2.weather.R
import de.mm20.launcher2.weather.WeatherProvider
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.Locale
import java.util.concurrent.TimeUnit
import kotlin.getValue
internal class OpenWeatherMapProvider(
private val context: Context,
@@ -17,11 +21,24 @@ internal class OpenWeatherMapProvider(
private val retrofit by lazy {
Retrofit.Builder()
.client(client)
.baseUrl("https://api.openweathermap.org/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
private val client by lazy {
val timeOutMillSeconds = 1500L
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.callTimeout(timeOutMillSeconds,TimeUnit.MILLISECONDS)
.connectTimeout(timeOutMillSeconds,TimeUnit.MILLISECONDS)
.readTimeout(timeOutMillSeconds,TimeUnit.MILLISECONDS)
.writeTimeout(timeOutMillSeconds,TimeUnit.MILLISECONDS)
.build()
}
private val openWeatherMapService by lazy {
retrofit.create(OpenWeatherMapApi::class.java)
}
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--<string name="openweathermap_key" translatable="false">xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</string>-->
<string name="openweathermap_key" translatable="false">87cd0810b7e4b4debd31a6ef98b98154"</string>
<!--<string name="here_key" translatable="false">xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</string>-->
<!--<string name="metno_contact" translatable="false"></string>-->
</resources>
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -11,15 +11,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+1 -1
View File
@@ -23,6 +23,6 @@ hero:
<img src="/img/screenshot-5.png"></img>
<img src="/img/screenshot-6.png"></img>
<div class="credits">Wallpaper by Allec Gomes on <a href="https://unsplash.com/de/fotos/ein-grunes-blatt-das-auf-einem-gewasser-schwimmt-UcWUMqIsld8" target="_blank">Unspash.com</a></div>
<div class="credits">Wallpaper by Allec Gomes on <a href="https://unsplash.com/de/fotos/ein-grunes-blatt-das-auf-einem-gewasser-schwimmt-UcWUMqIsld8" target="_blank">Unsplash.com</a></div>
</div>
<Footer></Footer>
+6 -3
View File
@@ -1,14 +1,15 @@
[versions]
loggingInterceptor = "4.5.0"
mustache = "0.9.14"
jacksonCore = "2.19.0"
jacksonDataformatYaml = "2.19.0"
minSdk = "26"
compileSdk = "36"
targetSdk = "36"
compileSdk = "35"
targetSdk = "34"
pluginSdk = "2.2.0"
gradle = "8.1.2"
gradle = "8.12"
android-gradle-plugin = "8.10.1"
ksp-gradle-plugin = "2.1.21-2.0.1"
@@ -48,6 +49,7 @@ espressoCore = "3.6.1"
osmOpeningHours = "0.2.0"
[libraries]
logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "loggingInterceptor" }
mustache-compiler = { module = "com.github.spullara.mustache.java:compiler", version.ref = "mustache" }
jackson-core = { module = "com.fasterxml.jackson.core:jackson-core", version.ref = "jacksonCore" }
@@ -139,6 +141,7 @@ osmopeninghours = { group = "de.westnordost", name = "osm-opening-hours", versio
kotlin = ["kotlin-stdlib", "kotlinx-coroutines-core", "kotlinx-coroutines-android", "kotlinx-collections-immutable", "kotlinx-serialization-json"]
androidx-lifecycle = ["androidx-lifecycle-viewmodel", "androidx-lifecycle-common", "androidx-lifecycle-runtime", "androidx-lifecycle-viewmodelcompose", "androidx-lifecycle-runtimecompose"]
retrofit = ["retrofit-core", "retrofit-gson"]
tests = ["junit"]
[plugins]
+2 -2
View File
@@ -1,6 +1,6 @@
#Sun Feb 09 12:41:42 CET 2025
#Tue Jul 15 14:35:45 KST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+11 -6
View File
@@ -10,16 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
consumerProguardFiles("proguard-rules.pro")
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -12,15 +12,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
@@ -231,8 +231,12 @@ class NextcloudApiHelper(val context: Context) {
.url("$server/ocs/v2.php/core/apppassword")
.build()
withContext(Dispatchers.IO) {
try {
val response = httpClient.newCall(request).execute()
response
} catch (e: IOException) {
Log.e("NextcloudApiHelper", "Error during Nextcloud logout", e)
}
}
preferences.edit {
putString("server", null)
+11 -5
View File
@@ -12,15 +12,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -14,7 +14,7 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildFeatures {
@@ -23,10 +23,16 @@ android {
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
create("nightly") {
initWith(getByName("release"))
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -11,15 +11,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}
@@ -235,6 +235,7 @@ internal class SearchServiceImpl(
}
if (filters.places) {
launch {
Log.e("SearchService","filters.places >>> $query ${filters.allowNetwork}")
locationRepository.search(query, filters.allowNetwork)
.combine(customAttrResults) { locations, customAttrs ->
if (customAttrs.locations != null) locations + customAttrs.locations
+11 -5
View File
@@ -10,15 +10,21 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
//consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
isMinifyEnabled = false
isShrinkResources = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
}
debug {
isMinifyEnabled = false
isShrinkResources = false
}
}

Some files were not shown because too many files have changed in this diff Show More