Weather widget: add warning if selected providerr is unavailable

This commit is contained in:
MM20 2024-06-30 15:19:09 +02:00
parent e96a382b00
commit 845a11b54b
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
6 changed files with 98 additions and 18 deletions

View File

@ -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"

View File

@ -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),

View File

@ -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<Boolean> = weatherRepository.getActiveProvider().map {
it != null
}
}

View File

@ -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<String?>(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"

View File

@ -968,4 +968,5 @@
<item quantity="one">%1$s notification</item>
<item quantity="other">%1$s notifications</item>
</plurals>
<string name="weather_widget_no_provider">No weather provider selected or the selected provider is not available</string>
</resources>

View File

@ -27,6 +27,7 @@ import java.util.*
import kotlin.time.Duration.Companion.minutes
interface WeatherRepository {
fun getActiveProvider(): Flow<WeatherProviderInfo?>
fun getProviders(): Flow<List<WeatherProviderInfo>>
fun searchLocations(query: String): Flow<List<WeatherLocation>>
@ -147,6 +148,14 @@ internal class WeatherRepositoryImpl(
}
}
override fun getActiveProvider(): Flow<WeatherProviderInfo?> {
return settings.providerId.flatMapLatest { id ->
getProviders().map {
it.find { it.id == id }
}
}
}
override fun getProviders(): Flow<List<WeatherProviderInfo>> {
val providers = mutableListOf<WeatherProviderInfo>()
providers.add(