Weather widget: add warning if selected providerr is unavailable
This commit is contained in:
parent
e96a382b00
commit
845a11b54b
@ -52,7 +52,7 @@
|
|||||||
android:name=".settings.SettingsActivity"
|
android:name=".settings.SettingsActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/settings"
|
android:label="@string/settings"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTop"
|
||||||
android:parentActivityName=".launcher.SharedLauncherActivity"
|
android:parentActivityName=".launcher.SharedLauncherActivity"
|
||||||
android:taskAffinity="de.mm20.launcher2.settings"
|
android:taskAffinity="de.mm20.launcher2.settings"
|
||||||
android:theme="@style/SettingsTheme.NoActionBar"
|
android:theme="@style/SettingsTheme.NoActionBar"
|
||||||
|
|||||||
@ -23,7 +23,15 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
|||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.CornerSize
|
import androidx.compose.foundation.shape.CornerSize
|
||||||
import androidx.compose.material.icons.Icons
|
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.Divider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -42,21 +50,23 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
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.ktx.tryStartActivity
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.common.WeatherLocationSearchDialog
|
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.MissingPermissionBanner
|
||||||
import de.mm20.launcher2.ui.component.weather.AnimatedWeatherIcon
|
import de.mm20.launcher2.ui.component.weather.AnimatedWeatherIcon
|
||||||
import de.mm20.launcher2.ui.component.weather.WeatherIcon
|
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.ktx.blendIntoViewScale
|
||||||
import de.mm20.launcher2.ui.locals.LocalCardStyle
|
import de.mm20.launcher2.ui.locals.LocalCardStyle
|
||||||
import de.mm20.launcher2.ui.modifier.consumeAllScrolling
|
import de.mm20.launcher2.ui.modifier.consumeAllScrolling
|
||||||
@ -86,16 +96,42 @@ fun WeatherWidget(widget: WeatherWidget) {
|
|||||||
val imperialUnits by viewModel.imperialUnits.collectAsState(false)
|
val imperialUnits by viewModel.imperialUnits.collectAsState(false)
|
||||||
val compactMode = !widget.config.showForecast
|
val compactMode = !widget.config.showForecast
|
||||||
|
|
||||||
|
val isProviderAvailable by viewModel.isProviderAvailable.collectAsStateWithLifecycle(true)
|
||||||
|
|
||||||
var showLocationDialog by remember { mutableStateOf(false) }
|
var showLocationDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
if (showLocationDialog) {
|
if (showLocationDialog) {
|
||||||
WeatherLocationSearchDialog(onDismissRequest = { showLocationDialog = false })
|
WeatherLocationSearchDialog(onDismissRequest = { showLocationDialog = false })
|
||||||
}
|
}
|
||||||
|
|
||||||
val forecast = selectedForecast ?: run {
|
|
||||||
val hasPermission by viewModel.hasLocationPermission.collectAsState()
|
Column {
|
||||||
val autoLocation by viewModel.autoLocation.collectAsState()
|
if (!isProviderAvailable) {
|
||||||
Column {
|
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) {
|
AnimatedVisibility(hasPermission == false && autoLocation == true) {
|
||||||
MissingPermissionBanner(
|
MissingPermissionBanner(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -117,11 +153,10 @@ fun WeatherWidget(widget: WeatherWidget) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
NoData()
|
NoData()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
CurrentWeather(forecast, imperialUnits)
|
CurrentWeather(forecast, imperialUnits)
|
||||||
|
|
||||||
if (!compactMode) {
|
if (!compactMode) {
|
||||||
@ -339,7 +374,9 @@ fun WeatherTimeSelector(
|
|||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
LazyRow(
|
LazyRow(
|
||||||
state = listState,
|
state = listState,
|
||||||
modifier = modifier.fillMaxWidth().consumeAllScrolling(),
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.consumeAllScrolling(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
contentPadding = PaddingValues(start = 16.dp, end = 16.dp),
|
contentPadding = PaddingValues(start = 16.dp, end = 16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
@ -398,7 +435,9 @@ fun WeatherDaySelector(
|
|||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
LazyRow(
|
LazyRow(
|
||||||
state = listState,
|
state = listState,
|
||||||
modifier = modifier.fillMaxWidth().consumeAllScrolling(),
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.consumeAllScrolling(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
contentPadding = PaddingValues(start = 16.dp, end = 16.dp),
|
contentPadding = PaddingValues(start = 16.dp, end = 16.dp),
|
||||||
|
|||||||
@ -1,14 +1,18 @@
|
|||||||
package de.mm20.launcher2.ui.launcher.widgets.weather
|
package de.mm20.launcher2.ui.launcher.widgets.weather
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.*
|
import androidx.lifecycle.*
|
||||||
import de.mm20.launcher2.permissions.PermissionGroup
|
import de.mm20.launcher2.permissions.PermissionGroup
|
||||||
import de.mm20.launcher2.permissions.PermissionsManager
|
import de.mm20.launcher2.permissions.PermissionsManager
|
||||||
import de.mm20.launcher2.preferences.weather.WeatherSettings
|
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.DailyForecast
|
||||||
import de.mm20.launcher2.weather.Forecast
|
import de.mm20.launcher2.weather.Forecast
|
||||||
import de.mm20.launcher2.weather.WeatherRepository
|
import de.mm20.launcher2.weather.WeatherRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@ -132,4 +136,16 @@ class WeatherWidgetVM : ViewModel(), KoinComponent {
|
|||||||
selectDay(dayIndex)
|
selectDay(dayIndex)
|
||||||
selectForecast(forecastIndex)
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package de.mm20.launcher2.ui.settings
|
package de.mm20.launcher2.ui.settings
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.SystemBarStyle
|
import androidx.activity.SystemBarStyle
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
@ -14,7 +15,9 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
@ -72,16 +75,22 @@ import java.util.UUID
|
|||||||
|
|
||||||
class SettingsActivity : BaseActivity() {
|
class SettingsActivity : BaseActivity() {
|
||||||
|
|
||||||
|
private var route by mutableStateOf<String?>(null)
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
|
||||||
|
val newRoute = intent?.getStringExtra(EXTRA_ROUTE)
|
||||||
|
route = newRoute
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
||||||
LaunchedEffect(intent) {
|
LaunchedEffect(route) {
|
||||||
intent.getStringExtra(EXTRA_ROUTE)
|
navController.navigate(route ?: "settings") {
|
||||||
?.let { navController.navigate(it) }
|
popUpTo("settings")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val wallpaperColors by wallpaperColorsAsState()
|
val wallpaperColors by wallpaperColorsAsState()
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
@ -107,7 +116,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = "settings",
|
startDestination = "settings",
|
||||||
exitTransition = {
|
exitTransition = {
|
||||||
slideOutHorizontally {-it / 4 }
|
slideOutHorizontally { -it / 4 }
|
||||||
},
|
},
|
||||||
enterTransition = {
|
enterTransition = {
|
||||||
slideInHorizontally { it / 2 } + scaleIn(initialScale = 0.9f) + fadeIn()
|
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 {
|
companion object {
|
||||||
const val EXTRA_ROUTE = "de.mm20.launcher2.settings.ROUTE"
|
const val EXTRA_ROUTE = "de.mm20.launcher2.settings.ROUTE"
|
||||||
const val ROUTE_WEATHER_INTEGRATION = "settings/integrations/weather"
|
const val ROUTE_WEATHER_INTEGRATION = "settings/integrations/weather"
|
||||||
|
|||||||
@ -968,4 +968,5 @@
|
|||||||
<item quantity="one">%1$s notification</item>
|
<item quantity="one">%1$s notification</item>
|
||||||
<item quantity="other">%1$s notifications</item>
|
<item quantity="other">%1$s notifications</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<string name="weather_widget_no_provider">No weather provider selected or the selected provider is not available</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -27,6 +27,7 @@ import java.util.*
|
|||||||
import kotlin.time.Duration.Companion.minutes
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
interface WeatherRepository {
|
interface WeatherRepository {
|
||||||
|
fun getActiveProvider(): Flow<WeatherProviderInfo?>
|
||||||
fun getProviders(): Flow<List<WeatherProviderInfo>>
|
fun getProviders(): Flow<List<WeatherProviderInfo>>
|
||||||
fun searchLocations(query: String): Flow<List<WeatherLocation>>
|
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>> {
|
override fun getProviders(): Flow<List<WeatherProviderInfo>> {
|
||||||
val providers = mutableListOf<WeatherProviderInfo>()
|
val providers = mutableListOf<WeatherProviderInfo>()
|
||||||
providers.add(
|
providers.add(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user