From 845a11b54b71efab86a419bf2109247fe49eeeaf Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sun, 30 Jun 2024 15:19:09 +0200 Subject: [PATCH] Weather widget: add warning if selected providerr is unavailable --- app/ui/src/main/AndroidManifest.xml | 2 +- .../launcher/widgets/weather/WeatherWidget.kt | 65 +++++++++++++++---- .../widgets/weather/WeatherWidgetVM.kt | 16 +++++ .../launcher2/ui/settings/SettingsActivity.kt | 23 +++++-- core/i18n/src/main/res/values/strings.xml | 1 + .../launcher2/weather/WeatherRepository.kt | 9 +++ 6 files changed, 98 insertions(+), 18 deletions(-) diff --git a/app/ui/src/main/AndroidManifest.xml b/app/ui/src/main/AndroidManifest.xml index 8964123f..019193d9 100644 --- a/app/ui/src/main/AndroidManifest.xml +++ b/app/ui/src/main/AndroidManifest.xml @@ -52,7 +52,7 @@ android:name=".settings.SettingsActivity" android:exported="true" android:label="@string/settings" - android:launchMode="singleTask" + android:launchMode="singleTop" android:parentActivityName=".launcher.SharedLauncherActivity" android:taskAffinity="de.mm20.launcher2.settings" android:theme="@style/SettingsTheme.NoActionBar" diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidget.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidget.kt index 91a27279..ddd9ebc1 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidget.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidget.kt @@ -23,7 +23,15 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CornerSize import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.* +import androidx.compose.material.icons.automirrored.rounded.OpenInNew +import androidx.compose.material.icons.rounded.Air +import androidx.compose.material.icons.rounded.ErrorOutline +import androidx.compose.material.icons.rounded.LightMode +import androidx.compose.material.icons.rounded.LocationCity +import androidx.compose.material.icons.rounded.MyLocation +import androidx.compose.material.icons.rounded.North +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Divider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -42,21 +50,23 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.viewmodel.compose.viewModel +import de.mm20.launcher2.icons.HumidityPercentage +import de.mm20.launcher2.icons.Rain 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.Banner import de.mm20.launcher2.ui.component.MissingPermissionBanner import de.mm20.launcher2.ui.component.weather.AnimatedWeatherIcon import de.mm20.launcher2.ui.component.weather.WeatherIcon -import de.mm20.launcher2.icons.HumidityPercentage -import de.mm20.launcher2.icons.Rain import de.mm20.launcher2.ui.ktx.blendIntoViewScale import de.mm20.launcher2.ui.locals.LocalCardStyle import de.mm20.launcher2.ui.modifier.consumeAllScrolling @@ -86,16 +96,42 @@ fun WeatherWidget(widget: WeatherWidget) { val imperialUnits by viewModel.imperialUnits.collectAsState(false) val compactMode = !widget.config.showForecast + val isProviderAvailable by viewModel.isProviderAvailable.collectAsStateWithLifecycle(true) + var showLocationDialog by remember { mutableStateOf(false) } if (showLocationDialog) { WeatherLocationSearchDialog(onDismissRequest = { showLocationDialog = false }) } - val forecast = selectedForecast ?: run { - val hasPermission by viewModel.hasLocationPermission.collectAsState() - val autoLocation by viewModel.autoLocation.collectAsState() - Column { + + Column { + if (!isProviderAvailable) { + Banner( + modifier = Modifier.fillMaxWidth().padding(16.dp), + text = stringResource(R.string.weather_widget_no_provider), + icon = Icons.Rounded.ErrorOutline, + primaryAction = { + Button( + onClick = { + viewModel.openSettings(context) + }, + contentPadding = ButtonDefaults.ButtonWithIconContentPadding, + ) { + Icon( + Icons.AutoMirrored.Rounded.OpenInNew, + null, + modifier = Modifier.padding(end = ButtonDefaults.IconSpacing).size(ButtonDefaults.IconSize) + ) + Text(stringResource(R.string.settings)) + } + } + ) + } + + val forecast = selectedForecast ?: run { + val hasPermission by viewModel.hasLocationPermission.collectAsState() + val autoLocation by viewModel.autoLocation.collectAsState() AnimatedVisibility(hasPermission == false && autoLocation == true) { MissingPermissionBanner( modifier = Modifier @@ -117,11 +153,10 @@ fun WeatherWidget(widget: WeatherWidget) { ) } NoData() + return } - return - } - Column { + CurrentWeather(forecast, imperialUnits) if (!compactMode) { @@ -339,7 +374,9 @@ fun WeatherTimeSelector( val listState = rememberLazyListState() LazyRow( state = listState, - modifier = modifier.fillMaxWidth().consumeAllScrolling(), + modifier = modifier + .fillMaxWidth() + .consumeAllScrolling(), horizontalArrangement = Arrangement.spacedBy(12.dp), contentPadding = PaddingValues(start = 16.dp, end = 16.dp), verticalAlignment = Alignment.CenterVertically @@ -398,7 +435,9 @@ fun WeatherDaySelector( val listState = rememberLazyListState() LazyRow( state = listState, - modifier = modifier.fillMaxWidth().consumeAllScrolling(), + modifier = modifier + .fillMaxWidth() + .consumeAllScrolling(), horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, contentPadding = PaddingValues(start = 16.dp, end = 16.dp), diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidgetVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidgetVM.kt index ca851c65..5a3e56b7 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidgetVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/weather/WeatherWidgetVM.kt @@ -1,14 +1,18 @@ package de.mm20.launcher2.ui.launcher.widgets.weather +import android.content.Context +import android.content.Intent import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.* import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.preferences.weather.WeatherSettings +import de.mm20.launcher2.ui.settings.SettingsActivity import de.mm20.launcher2.weather.DailyForecast import de.mm20.launcher2.weather.Forecast import de.mm20.launcher2.weather.WeatherRepository +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.map @@ -132,4 +136,16 @@ class WeatherWidgetVM : ViewModel(), KoinComponent { selectDay(dayIndex) selectForecast(forecastIndex) } + + fun openSettings(context: Context) { + context.startActivity( + Intent(context, SettingsActivity::class.java).apply { + putExtra(SettingsActivity.EXTRA_ROUTE, SettingsActivity.ROUTE_WEATHER_INTEGRATION) + } + ) + } + + val isProviderAvailable: Flow = weatherRepository.getActiveProvider().map { + it != null + } } \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt index 33d3acf6..7ed44d3a 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt @@ -1,5 +1,6 @@ package de.mm20.launcher2.ui.settings +import android.content.Intent import android.os.Bundle import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent @@ -14,7 +15,9 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.toArgb import androidx.core.view.WindowCompat import androidx.navigation.compose.NavHost @@ -72,16 +75,22 @@ import java.util.UUID class SettingsActivity : BaseActivity() { + private var route by mutableStateOf(null) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) + val newRoute = intent?.getStringExtra(EXTRA_ROUTE) + route = newRoute + setContent { val navController = rememberNavController() - LaunchedEffect(intent) { - intent.getStringExtra(EXTRA_ROUTE) - ?.let { navController.navigate(it) } + LaunchedEffect(route) { + navController.navigate(route ?: "settings") { + popUpTo("settings") + } } val wallpaperColors by wallpaperColorsAsState() CompositionLocalProvider( @@ -107,7 +116,7 @@ class SettingsActivity : BaseActivity() { navController = navController, startDestination = "settings", exitTransition = { - slideOutHorizontally {-it / 4 } + slideOutHorizontally { -it / 4 } }, enterTransition = { slideInHorizontally { it / 2 } + scaleIn(initialScale = 0.9f) + fadeIn() @@ -267,6 +276,12 @@ class SettingsActivity : BaseActivity() { } } + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + val newRoute = intent.getStringExtra(EXTRA_ROUTE) + route = newRoute + } + companion object { const val EXTRA_ROUTE = "de.mm20.launcher2.settings.ROUTE" const val ROUTE_WEATHER_INTEGRATION = "settings/integrations/weather" diff --git a/core/i18n/src/main/res/values/strings.xml b/core/i18n/src/main/res/values/strings.xml index 8ef090a1..b856878f 100644 --- a/core/i18n/src/main/res/values/strings.xml +++ b/core/i18n/src/main/res/values/strings.xml @@ -968,4 +968,5 @@ %1$s notification %1$s notifications + No weather provider selected or the selected provider is not available \ No newline at end of file diff --git a/data/weather/src/main/java/de/mm20/launcher2/weather/WeatherRepository.kt b/data/weather/src/main/java/de/mm20/launcher2/weather/WeatherRepository.kt index 5252a76b..18403e09 100644 --- a/data/weather/src/main/java/de/mm20/launcher2/weather/WeatherRepository.kt +++ b/data/weather/src/main/java/de/mm20/launcher2/weather/WeatherRepository.kt @@ -27,6 +27,7 @@ import java.util.* import kotlin.time.Duration.Companion.minutes interface WeatherRepository { + fun getActiveProvider(): Flow fun getProviders(): Flow> fun searchLocations(query: String): Flow> @@ -147,6 +148,14 @@ internal class WeatherRepositoryImpl( } } + override fun getActiveProvider(): Flow { + return settings.providerId.flatMapLatest { id -> + getProviders().map { + it.find { it.id == id } + } + } + } + override fun getProviders(): Flow> { val providers = mutableListOf() providers.add(