diff --git a/i18n/src/main/res/values-de/strings.xml b/i18n/src/main/res/values-de/strings.xml
index e6cfa307..ff18592e 100644
--- a/i18n/src/main/res/values-de/strings.xml
+++ b/i18n/src/main/res/values-de/strings.xml
@@ -423,4 +423,5 @@
Gewähren
Standortzugriff wird benötigt, um den Standort automatisch zu ermitteln
+ Standort festlegen
\ No newline at end of file
diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml
index b36cd965..3621d044 100644
--- a/i18n/src/main/res/values/strings.xml
+++ b/i18n/src/main/res/values/strings.xml
@@ -461,4 +461,5 @@
Grant
Location access is required to determine the location automatically
+ Set location
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/common/WeatherLocationSearchDialog.kt b/ui/src/main/java/de/mm20/launcher2/ui/common/WeatherLocationSearchDialog.kt
new file mode 100644
index 00000000..e7a14b6a
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/common/WeatherLocationSearchDialog.kt
@@ -0,0 +1,116 @@
+package de.mm20.launcher2.ui.common
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.CircularProgressIndicator
+import androidx.compose.material.OutlinedTextField
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.*
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.lifecycle.viewmodel.compose.viewModel
+import de.mm20.launcher2.ui.R
+import kotlinx.coroutines.launch
+
+@Composable
+fun WeatherLocationSearchDialog(
+ onDismissRequest: () -> Unit
+) {
+ val scope = rememberCoroutineScope()
+ val viewModel : WeatherLocationSearchDialogVM = viewModel()
+ val isSearching by viewModel.isSearchingLocation.observeAsState(initial = false)
+ val locations by viewModel.locationResults.observeAsState(emptyList())
+ Dialog(onDismissRequest = onDismissRequest) {
+ Surface(
+ tonalElevation = 16.dp,
+ shadowElevation = 16.dp,
+ shape = RoundedCornerShape(16.dp),
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(vertical = 16.dp),
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text(
+ text = stringResource(R.string.preference_location),
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ start = 24.dp, end = 24.dp, top = 16.dp, bottom = 8.dp
+ )
+ )
+ var query by remember { mutableStateOf("") }
+ OutlinedTextField(
+ singleLine = true,
+ value = query,
+ onValueChange = {
+ query = it
+ scope.launch {
+ viewModel.searchLocation(it)
+ }
+ }, modifier = Modifier
+ .fillMaxWidth()
+ .padding(24.dp)
+ )
+ if (isSearching) {
+ CircularProgressIndicator(
+ modifier = Modifier
+ .align(Alignment.CenterHorizontally)
+ .weight(1f)
+ .padding(vertical = 16.dp)
+ )
+ } else {
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f)
+ .padding(bottom = 16.dp)
+ ) {
+ items(locations) {
+ Text(
+ text = it.name,
+ modifier = Modifier
+ .clickable {
+ viewModel.setLocation(it)
+ onDismissRequest()
+ }
+ .padding(
+ horizontal = 24.dp,
+ vertical = 16.dp
+ )
+ )
+ }
+ }
+
+ }
+ TextButton(
+ onClick = onDismissRequest,
+ modifier = Modifier
+ .align(Alignment.End)
+ .padding(24.dp)
+ ) {
+ Text(
+ text = stringResource(R.string.close),
+ style = MaterialTheme.typography.labelLarge
+ )
+ }
+ }
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/common/WeatherLocationSearchDialogVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/common/WeatherLocationSearchDialogVM.kt
new file mode 100644
index 00000000..9a3bb5a3
--- /dev/null
+++ b/ui/src/main/java/de/mm20/launcher2/ui/common/WeatherLocationSearchDialogVM.kt
@@ -0,0 +1,41 @@
+package de.mm20.launcher2.ui.common
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import de.mm20.launcher2.weather.WeatherLocation
+import de.mm20.launcher2.weather.WeatherRepository
+import kotlinx.coroutines.*
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
+import kotlin.coroutines.coroutineContext
+
+class WeatherLocationSearchDialogVM: ViewModel(), KoinComponent {
+ private val repository: WeatherRepository by inject()
+
+ val isSearchingLocation = MutableLiveData(false)
+ val locationResults = MutableLiveData>(emptyList())
+
+ private var debounceSearchJob: Job? = null
+ suspend fun searchLocation(query: String) {
+ debounceSearchJob?.cancelAndJoin()
+ if (query.isBlank()) {
+ locationResults.value = emptyList()
+ isSearchingLocation.value = false
+ return
+ }
+ withContext(coroutineContext) {
+ debounceSearchJob = launch {
+ delay(1000)
+ isSearchingLocation.value = true
+ locationResults.value = repository.lookupLocation(query)
+ isSearchingLocation.value = false
+ }
+ }
+ }
+
+ fun setLocation(location: WeatherLocation) {
+ locationResults.postValue(emptyList())
+ repository.setAutoLocation(false)
+ repository.setLocation(location)
+ }
+}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidget.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidget.kt
index a998364c..8d7daa94 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidget.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidget.kt
@@ -29,6 +29,7 @@ import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.ui.R
+import de.mm20.launcher2.ui.common.WeatherLocationSearchDialog
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.weather.AnimatedWeatherIcon
import de.mm20.launcher2.ui.weather.WeatherIcon
@@ -50,16 +51,32 @@ fun WeatherWidget() {
val imperialUnits by viewModel.imperialUnits.observeAsState(false)
+ var showLocationDialog by remember { mutableStateOf(false) }
+
+ if (showLocationDialog) {
+ WeatherLocationSearchDialog(onDismissRequest = { showLocationDialog = false })
+ }
+
val forecast = selectedForecast ?: run {
val hasPermission by viewModel.hasLocationPermission.observeAsState()
val autoLocation by viewModel.autoLocation.observeAsState()
AnimatedVisibility(hasPermission == false && autoLocation == true) {
MissingPermissionBanner(
- modifier = Modifier.padding(horizontal = 16.dp).padding(top = 16.dp),
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .padding(top = 16.dp),
text = stringResource(id = R.string.missing_permission_auto_location),
onClick = {
viewModel.requestLocationPermission(context as AppCompatActivity)
- })
+ },
+ secondaryAction = {
+ TextButton(onClick = {
+ showLocationDialog = true
+ }) {
+ Text(stringResource(R.string.weather_widget_set_location), style = MaterialTheme.typography.labelLarge)
+ }
+ }
+ )
}
NoData()
return
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/weather/WeatherScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/weather/WeatherScreen.kt
index 72144dfe..dcfc54b3 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/settings/weather/WeatherScreen.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/weather/WeatherScreen.kt
@@ -2,36 +2,21 @@ package de.mm20.launcher2.ui.settings.weather
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.CircularProgressIndicator
-import androidx.compose.material.OutlinedTextField
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.Dialog
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.preferences.Settings.WeatherSettings.WeatherProvider
import de.mm20.launcher2.ui.BuildConfig
import de.mm20.launcher2.ui.R
+import de.mm20.launcher2.ui.common.WeatherLocationSearchDialog
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.preferences.*
import de.mm20.launcher2.weather.WeatherLocation
-import kotlinx.coroutines.launch
@Composable
fun WeatherScreen() {
@@ -87,25 +72,11 @@ fun WeatherScreen() {
viewModel.setAutoLocation(it)
}
)
- val scope = rememberCoroutineScope()
-
val location by viewModel.location.observeAsState()
- val isSearching by viewModel.isSearchingLocation.observeAsState(initial = false)
- val locations by viewModel.locationResults.observeAsState(emptyList())
LocationPreference(
title = stringResource(R.string.preference_location),
value = location,
- locations = locations,
- onValueChanged = {
- viewModel.setLocation(it)
- },
- onLocationSearch = {
- scope.launch {
- viewModel.searchLocation(it)
- }
- },
enabled = !autoLocation,
- isSearching = isSearching,
)
}
}
@@ -117,7 +88,7 @@ fun WeatherScreen() {
summary = "Remove weather data from database",
onClick = {
viewModel.clearWeatherData()
- })
+ })
}
}
}
@@ -128,10 +99,6 @@ fun WeatherScreen() {
fun LocationPreference(
title: String,
value: WeatherLocation?,
- onValueChanged: (WeatherLocation) -> Unit,
- onLocationSearch: (String) -> Unit,
- isSearching: Boolean,
- locations: List,
enabled: Boolean = true
) {
var showDialog by remember { mutableStateOf(false) }
@@ -144,83 +111,6 @@ fun LocationPreference(
}
)
if (showDialog) {
- Dialog(onDismissRequest = { showDialog = false }) {
- Surface(
- tonalElevation = 16.dp,
- shadowElevation = 16.dp,
- shape = RoundedCornerShape(16.dp),
- modifier = Modifier
- .fillMaxSize()
- .padding(vertical = 16.dp),
- ) {
- Column(
- modifier = Modifier.fillMaxWidth()
- ) {
- Text(
- text = title,
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier
- .fillMaxWidth()
- .padding(
- start = 24.dp, end = 24.dp, top = 16.dp, bottom = 8.dp
- )
- )
- var query by remember { mutableStateOf("") }
- OutlinedTextField(
- singleLine = true,
- value = query,
- onValueChange = {
- query = it
- onLocationSearch(it)
- }, modifier = Modifier
- .fillMaxWidth()
- .padding(24.dp)
- )
- if (isSearching) {
- CircularProgressIndicator(
- modifier = Modifier
- .align(Alignment.CenterHorizontally)
- .weight(1f)
- .padding(vertical = 16.dp)
- )
- } else {
- LazyColumn(
- modifier = Modifier
- .fillMaxWidth()
- .weight(1f)
- .padding(bottom = 16.dp)
- ) {
- items(locations) {
- Text(
- text = it.name,
- modifier = Modifier
- .clickable {
- onValueChanged(it)
- showDialog = false
- }
- .padding(
- horizontal = 24.dp,
- vertical = 16.dp
- )
- )
- }
- }
-
- }
- TextButton(
- onClick = { showDialog = false },
- modifier = Modifier
- .align(Alignment.End)
- .padding(24.dp)
- ) {
- Text(
- text = stringResource(R.string.close),
- style = MaterialTheme.typography.labelLarge
- )
- }
- }
-
- }
- }
+ WeatherLocationSearchDialog(onDismissRequest = { showDialog = false })
}
}
\ No newline at end of file
diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/weather/WeatherScreenVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/weather/WeatherScreenVM.kt
index 88109f3f..f7695c4a 100644
--- a/ui/src/main/java/de/mm20/launcher2/ui/settings/weather/WeatherScreenVM.kt
+++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/weather/WeatherScreenVM.kt
@@ -46,28 +46,6 @@ class WeatherScreenVM : ViewModel(), KoinComponent {
}
val location = MutableLiveData(null)
- fun setLocation(location: WeatherLocation) {
- locationResults.postValue(emptyList())
- repository.setLocation(location)
- }
-
- private var debounceSearchJob: Job? = null
- suspend fun searchLocation(query: String) {
- debounceSearchJob?.cancelAndJoin()
- if (query.isBlank()) {
- locationResults.value = emptyList()
- isSearchingLocation.value = false
- return
- }
- withContext(coroutineContext) {
- debounceSearchJob = launch {
- delay(1000)
- isSearchingLocation.value = true
- locationResults.value = repository.lookupLocation(query)
- isSearchingLocation.value = false
- }
- }
- }
val hasLocationPermission = permissionsManager.hasPermission(PermissionGroup.Location).asLiveData()
@@ -75,8 +53,6 @@ class WeatherScreenVM : ViewModel(), KoinComponent {
permissionsManager.requestPermission(activity, PermissionGroup.Location)
}
- val isSearchingLocation = MutableLiveData(false)
- val locationResults = MutableLiveData>(emptyList())
init {
viewModelScope.launch {