diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/base/AppWidgetHost.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/base/AppWidgetHost.kt new file mode 100644 index 00000000..f2648693 --- /dev/null +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/base/AppWidgetHost.kt @@ -0,0 +1,41 @@ +package de.mm20.launcher2.ui.base + +import android.appwidget.AppWidgetHost +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +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.repeatOnLifecycle +import de.mm20.launcher2.crashreporter.CrashReporter +import kotlinx.coroutines.awaitCancellation + +val LocalAppWidgetHost = + staticCompositionLocalOf { throw IllegalStateException("AppWidgetHost is not provided") } + +@Composable +fun ProvideAppWidgetHost( + content: @Composable () -> Unit, +) { + val lifecycleOwner = LocalLifecycleOwner.current + val context = LocalContext.current + val widgetHost = remember { AppWidgetHost(context.applicationContext, 44203) } + LaunchedEffect(null) { + lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + widgetHost.startListening() + try { + awaitCancellation() + } finally { + try { + widgetHost.stopListening() + } catch (e: Exception) { + CrashReporter.logException(e) + } + } + } + } + CompositionLocalProvider(LocalAppWidgetHost provides widgetHost, content = content) +} \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/base/ProvideLocals.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/base/ProvideLocals.kt new file mode 100644 index 00000000..28bb7fcb --- /dev/null +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/base/ProvideLocals.kt @@ -0,0 +1,14 @@ +package de.mm20.launcher2.ui.base + +import androidx.compose.runtime.Composable + +@Composable +fun ProvideCompositionLocals(content: @Composable () -> Unit) { + ProvideCurrentTime { + ProvideSettings { + ProvideAppWidgetHost { + content() + } + } + } +} \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt index 10d6cb6b..c6c83e3a 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt @@ -38,8 +38,7 @@ import de.mm20.launcher2.preferences.BaseLayout import de.mm20.launcher2.preferences.SystemBarColors import de.mm20.launcher2.ui.assistant.AssistantScaffold import de.mm20.launcher2.ui.base.BaseActivity -import de.mm20.launcher2.ui.base.ProvideCurrentTime -import de.mm20.launcher2.ui.base.ProvideSettings +import de.mm20.launcher2.ui.base.ProvideCompositionLocals import de.mm20.launcher2.ui.component.NavBarEffects import de.mm20.launcher2.ui.gestures.GestureDetector import de.mm20.launcher2.ui.gestures.LocalGestureDetector @@ -101,7 +100,7 @@ abstract class SharedLauncherActivity( LocalGestureDetector provides gestureDetector, ) { LauncherTheme { - ProvideSettings { + ProvideCompositionLocals { val statusBarColor by viewModel.statusBarColor.collectAsState() val navBarColor by viewModel.navBarColor.collectAsState() @@ -160,111 +159,109 @@ abstract class SharedLauncherActivity( systemUiController.isNavigationBarVisible = !hideNav } - ProvideCurrentTime { - OverlayHost( - modifier = Modifier - .fillMaxSize() - .background(if (dimBackground) Color.Black.copy(alpha = 0.30f) else Color.Transparent), - contentAlignment = Alignment.BottomCenter - ) { - if (chargingAnimation == true) { - NavBarEffects(modifier = Modifier.fillMaxSize()) - } - if (mode == LauncherActivityMode.Assistant) { - key(bottomSearchBar, reverseSearchResults) { - AssistantScaffold( - modifier = Modifier - .fillMaxSize(), - darkStatusBarIcons = lightStatus, - darkNavBarIcons = lightNav, - bottomSearchBar = bottomSearchBar, - reverseSearchResults = reverseSearchResults, - fixedSearchBar = fixedSearchBar, - ) - } - } else { - when (layout) { - BaseLayout.PullDown -> { - key(bottomSearchBar, reverseSearchResults) { - PullDownScaffold( - modifier = Modifier - .fillMaxSize() - .graphicsLayer { - scaleX = - 0.5f + enterTransitionProgress.value * 0.5f - scaleY = - 0.5f + enterTransitionProgress.value * 0.5f - alpha = enterTransitionProgress.value - }, - darkStatusBarIcons = lightStatus, - darkNavBarIcons = lightNav, - bottomSearchBar = bottomSearchBar, - reverseSearchResults = reverseSearchResults, - fixedSearchBar = fixedSearchBar, - ) - } - } - - BaseLayout.Pager, - BaseLayout.PagerReversed -> { - key(bottomSearchBar, reverseSearchResults) { - PagerScaffold( - modifier = Modifier - .fillMaxSize() - .graphicsLayer { - scaleX = - 0.5f + enterTransitionProgress.value * 0.5f - scaleY = - 0.5f + enterTransitionProgress.value * 0.5f - alpha = enterTransitionProgress.value - }, - darkStatusBarIcons = lightStatus, - darkNavBarIcons = lightNav, - reverse = layout == BaseLayout.PagerReversed, - bottomSearchBar = bottomSearchBar, - reverseSearchResults = reverseSearchResults, - fixedSearchBar = fixedSearchBar, - ) - } - } - - else -> {} - } - } - SnackbarHost( - snackbarHostState, - modifier = Modifier - .navigationBarsPadding() - .imePadding() - ) - enterTransition?.let { - if (it.startBounds == null || it.targetBounds == null) return@let - val dX = it.startBounds.center.x - it.targetBounds.center.x - val dY = it.startBounds.center.y - it.targetBounds.center.y - val s = - (it.startBounds.minDimension / it.targetBounds.minDimension - 1f) * 0.5f - Box( - modifier = Modifier - .align(Alignment.TopStart) - .graphicsLayer { - val p = (enterTransitionProgress.value).pow(2f) - transformOrigin = TransformOrigin.Center - translationX = it.targetBounds.left + dX * (1 - p) - translationY = it.targetBounds.top + dY * (1 - p) - alpha = enterTransitionProgress.value - scaleX = 1f + s * (1 - p) - scaleY = 1f + s * (1 - p) - }) { - it.icon?.invoke( - Offset( - dX, - dY - ) - ) { enterTransitionProgress.value } - } - } - LauncherBottomSheets() + OverlayHost( + modifier = Modifier + .fillMaxSize() + .background(if (dimBackground) Color.Black.copy(alpha = 0.30f) else Color.Transparent), + contentAlignment = Alignment.BottomCenter + ) { + if (chargingAnimation == true) { + NavBarEffects(modifier = Modifier.fillMaxSize()) } + if (mode == LauncherActivityMode.Assistant) { + key(bottomSearchBar, reverseSearchResults) { + AssistantScaffold( + modifier = Modifier + .fillMaxSize(), + darkStatusBarIcons = lightStatus, + darkNavBarIcons = lightNav, + bottomSearchBar = bottomSearchBar, + reverseSearchResults = reverseSearchResults, + fixedSearchBar = fixedSearchBar, + ) + } + } else { + when (layout) { + BaseLayout.PullDown -> { + key(bottomSearchBar, reverseSearchResults) { + PullDownScaffold( + modifier = Modifier + .fillMaxSize() + .graphicsLayer { + scaleX = + 0.5f + enterTransitionProgress.value * 0.5f + scaleY = + 0.5f + enterTransitionProgress.value * 0.5f + alpha = enterTransitionProgress.value + }, + darkStatusBarIcons = lightStatus, + darkNavBarIcons = lightNav, + bottomSearchBar = bottomSearchBar, + reverseSearchResults = reverseSearchResults, + fixedSearchBar = fixedSearchBar, + ) + } + } + + BaseLayout.Pager, + BaseLayout.PagerReversed -> { + key(bottomSearchBar, reverseSearchResults) { + PagerScaffold( + modifier = Modifier + .fillMaxSize() + .graphicsLayer { + scaleX = + 0.5f + enterTransitionProgress.value * 0.5f + scaleY = + 0.5f + enterTransitionProgress.value * 0.5f + alpha = enterTransitionProgress.value + }, + darkStatusBarIcons = lightStatus, + darkNavBarIcons = lightNav, + reverse = layout == BaseLayout.PagerReversed, + bottomSearchBar = bottomSearchBar, + reverseSearchResults = reverseSearchResults, + fixedSearchBar = fixedSearchBar, + ) + } + } + + else -> {} + } + } + SnackbarHost( + snackbarHostState, + modifier = Modifier + .navigationBarsPadding() + .imePadding() + ) + enterTransition?.let { + if (it.startBounds == null || it.targetBounds == null) return@let + val dX = it.startBounds.center.x - it.targetBounds.center.x + val dY = it.startBounds.center.y - it.targetBounds.center.y + val s = + (it.startBounds.minDimension / it.targetBounds.minDimension - 1f) * 0.5f + Box( + modifier = Modifier + .align(Alignment.TopStart) + .graphicsLayer { + val p = (enterTransitionProgress.value).pow(2f) + transformOrigin = TransformOrigin.Center + translationX = it.targetBounds.left + dX * (1 - p) + translationY = it.targetBounds.top + dY * (1 - p) + alpha = enterTransitionProgress.value + scaleX = 1f + s * (1 - p) + scaleY = 1f + s * (1 - p) + }) { + it.icon?.invoke( + Offset( + dX, + dY + ) + ) { enterTransitionProgress.value } + } + } + LauncherBottomSheets() } } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/ConfigureWidgetSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/ConfigureWidgetSheet.kt index 11a00e32..49702f5b 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/ConfigureWidgetSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/ConfigureWidgetSheet.kt @@ -80,6 +80,7 @@ import de.mm20.launcher2.ktx.isAtLeastApiLevel import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.ui.R +import de.mm20.launcher2.ui.base.LocalAppWidgetHost import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.LargeMessage import de.mm20.launcher2.ui.component.MissingPermissionBanner @@ -103,7 +104,6 @@ import kotlin.math.roundToInt @Composable fun ConfigureWidgetSheet( - appWidgetHost: AppWidgetHost, widget: Widget, onWidgetUpdated: (Widget) -> Unit, onDismiss: () -> Unit, @@ -128,7 +128,7 @@ fun ConfigureWidgetSheet( ) { when (widget) { is WeatherWidget -> ConfigureWeatherWidget(widget, onWidgetUpdated) - is AppWidget -> ConfigureAppWidget(appWidgetHost, widget, onWidgetUpdated) + is AppWidget -> ConfigureAppWidget(widget, onWidgetUpdated) is CalendarWidget -> ConfigureCalendarWidget(widget, onWidgetUpdated) is FavoritesWidget -> ConfigureFavoritesWidget(widget, onWidgetUpdated) is MusicWidget -> ConfigureMusicWidget() @@ -272,7 +272,6 @@ fun ColumnScope.ConfigureMusicWidget( @Composable fun ColumnScope.ConfigureAppWidget( - appWidgetHost: AppWidgetHost, widget: AppWidget, onWidgetUpdated: (Widget) -> Unit, ) { @@ -346,7 +345,6 @@ fun ColumnScope.ConfigureAppWidget( .background(MaterialTheme.colorScheme.surfaceVariant) ) { ExternalWidget( - appWidgetHost = appWidgetHost, widgetInfo = widgetInfo, widgetId = widget.config.widgetId, modifier = Modifier.fillMaxWidth(), @@ -472,6 +470,7 @@ fun ColumnScope.ConfigureAppWidget( } } if (isAtLeastApiLevel(28) && widgetInfo.widgetFeatures and AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE != 0) { + val appWidgetHost = LocalAppWidgetHost.current TextButton( modifier = Modifier .padding(top = 8.dp) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/WidgetPickerSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/WidgetPickerSheet.kt index 547f170b..2f46d43a 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/WidgetPickerSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/sheets/WidgetPickerSheet.kt @@ -251,6 +251,8 @@ private class BindAndConfigureAppWidgetContract( @Composable fun WidgetPickerSheet( + includeBuiltinWidgets: Boolean = true, + title: String = stringResource(R.string.widget_pick_widget), onWidgetSelected: (Widget) -> Unit, onDismiss: () -> Unit ) { @@ -277,7 +279,7 @@ fun WidgetPickerSheet( BottomSheetDialog( onDismissRequest = onDismiss, title = { - Text(stringResource(R.string.widget_add_widget)) + Text(title) }) { val builtIn by viewModel.builtInWidgets.collectAsState(emptyList()) LazyColumn( @@ -318,44 +320,46 @@ fun WidgetPickerSheet( ) { } } - items(builtIn) { - OutlinedCard( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 16.dp, start = 16.dp, end = 16.dp), - onClick = { - val id = UUID.randomUUID() - val widget = when(it.type) { - WeatherWidget.Type -> WeatherWidget(id) - CalendarWidget.Type -> CalendarWidget(id) - MusicWidget.Type -> MusicWidget(id) - FavoritesWidget.Type -> FavoritesWidget(id) - NotesWidget.Type -> NotesWidget(id) - else -> return@OutlinedCard + if (includeBuiltinWidgets) { + items(builtIn) { + OutlinedCard( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp, start = 16.dp, end = 16.dp), + onClick = { + val id = UUID.randomUUID() + val widget = when (it.type) { + WeatherWidget.Type -> WeatherWidget(id) + CalendarWidget.Type -> CalendarWidget(id) + MusicWidget.Type -> MusicWidget(id) + FavoritesWidget.Type -> FavoritesWidget(id) + NotesWidget.Type -> NotesWidget(id) + else -> return@OutlinedCard + } + onWidgetSelected(widget) + onDismiss() + }) { + Row( + modifier = Modifier.padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = when (it.type) { + WeatherWidget.Type -> Icons.Rounded.LightMode + CalendarWidget.Type -> Icons.Rounded.Today + MusicWidget.Type -> Icons.Rounded.MusicNote + FavoritesWidget.Type -> Icons.Rounded.Star + NotesWidget.Type -> Icons.Rounded.StickyNote2 + else -> Icons.Rounded.Widgets + }, + contentDescription = null, + modifier = Modifier.padding(end = 16.dp) + ) + Text( + text = it.label, + style = MaterialTheme.typography.titleSmall + ) } - onWidgetSelected(widget) - onDismiss() - }) { - Row( - modifier = Modifier.padding(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = when (it.type) { - WeatherWidget.Type -> Icons.Rounded.LightMode - CalendarWidget.Type -> Icons.Rounded.Today - MusicWidget.Type -> Icons.Rounded.MusicNote - FavoritesWidget.Type -> Icons.Rounded.Star - NotesWidget.Type -> Icons.Rounded.StickyNote2 - else -> Icons.Rounded.Widgets - }, - contentDescription = null, - modifier = Modifier.padding(end = 16.dp) - ) - Text( - text = it.label, - style = MaterialTheme.typography.titleSmall - ) } } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/WidgetColumn.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/WidgetColumn.kt index 1edf46c1..635fdf27 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/WidgetColumn.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/WidgetColumn.kt @@ -36,6 +36,7 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.ui.R +import de.mm20.launcher2.ui.base.LocalAppWidgetHost import de.mm20.launcher2.ui.ktx.animateTo import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager import de.mm20.launcher2.ui.launcher.sheets.WidgetPickerSheet @@ -51,27 +52,11 @@ fun WidgetColumn( ) { val viewModel: WidgetsVM = viewModel() - val context = LocalContext.current val bottomSheetManager = LocalBottomSheetManager.current - val lifecycleOwner = LocalLifecycleOwner.current - val widgetHost = remember { AppWidgetHost(context.applicationContext, 44203) } var addNewWidget by rememberSaveable { mutableStateOf(false) } - LaunchedEffect(null) { - lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - widgetHost.startListening() - try { - awaitCancellation() - } finally { - try { - widgetHost.stopListening() - } catch (e: Exception) { - CrashReporter.logException(e) - } - } - } - } + Column( modifier = modifier @@ -92,9 +77,10 @@ fun WidgetColumn( dragOffsetAfterSwap = null } + val widgetHost = LocalAppWidgetHost.current + WidgetItem( widget = widget, - appWidgetHost = widgetHost, editMode = editMode, onWidgetAdd = { widget, offset -> viewModel.addWidget(widget, i + offset) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/WidgetItem.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/WidgetItem.kt index 47f7b7c1..64563529 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/WidgetItem.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/WidgetItem.kt @@ -60,7 +60,6 @@ import de.mm20.launcher2.widgets.Widget @Composable fun WidgetItem( widget: Widget, - appWidgetHost: AppWidgetHost, modifier: Modifier = Modifier, editMode: Boolean = false, onWidgetAdd: (widget: Widget, offset: Int) -> Unit = { _, _ -> }, @@ -222,7 +221,6 @@ fun WidgetItem( } } else { ExternalWidget( - appWidgetHost = appWidgetHost, widgetId = widget.config.widgetId, widgetInfo = widgetInfo, modifier = Modifier.fillMaxWidth(), @@ -237,7 +235,6 @@ fun WidgetItem( } if (configure) { ConfigureWidgetSheet( - appWidgetHost = appWidgetHost, widget = widget, onWidgetUpdated = onWidgetUpdate, onDismiss = { configure = false }, diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt index b0bdeb99..a8629208 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt @@ -74,6 +74,7 @@ import de.mm20.launcher2.ui.component.preferences.Preference import de.mm20.launcher2.ui.component.preferences.SwitchPreference import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.AnalogClock import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.BinaryClock +import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.CustomClock import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.DigitalClock1 import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.DigitalClock2 import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.OrbitClock @@ -97,8 +98,11 @@ fun ClockWidget( val alignment by viewModel.alignment.collectAsState() val time = LocalTime.current + val darkColors = + color == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current || color == ClockWidgetColors.Dark + val contentColor = - if (color == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current || color == ClockWidgetColors.Dark) { + if (darkColors) { Color(0, 0, 0, 180) } else { Color.White @@ -182,7 +186,7 @@ fun ClockWidget( viewModel.launchClockApp(context) } ) { - Clock(clockStyle, false) + Clock(clockStyle, false, darkColors) } if (partProvider != null) { @@ -227,7 +231,7 @@ fun ClockWidget( viewModel.launchClockApp(context) } ) { - Clock(clockStyle, true) + Clock(clockStyle, true, darkColors) } } } @@ -248,10 +252,14 @@ fun ClockWidget( } } +/** + * @param darkColors: use dark content color / suited for light backgrounds + */ @Composable fun Clock( style: ClockWidgetStyle?, compact: Boolean, + darkColors: Boolean = false ) { val time = LocalTime.current val clockSettings: ClockWidgetSettings by inject() @@ -264,14 +272,51 @@ fun Clock( style, compact, showSeconds, - useThemeColor + useThemeColor, + darkColors ) - is ClockWidgetStyle.Digital2 -> DigitalClock2(time, compact, showSeconds, useThemeColor) - is ClockWidgetStyle.Binary -> BinaryClock(time, compact, showSeconds, useThemeColor) - is ClockWidgetStyle.Analog -> AnalogClock(time, compact, showSeconds, useThemeColor) - is ClockWidgetStyle.Orbit -> OrbitClock(time, compact, showSeconds, useThemeColor) - is ClockWidgetStyle.Segment -> SegmentClock(time, compact, showSeconds, useThemeColor) + is ClockWidgetStyle.Digital2 -> DigitalClock2( + time, + compact, + showSeconds, + useThemeColor, + darkColors + ) + + is ClockWidgetStyle.Binary -> BinaryClock( + time, + compact, + showSeconds, + useThemeColor, + darkColors + ) + + is ClockWidgetStyle.Analog -> AnalogClock( + time, + compact, + showSeconds, + useThemeColor, + darkColors + ) + + is ClockWidgetStyle.Orbit -> OrbitClock( + time, + compact, + showSeconds, + useThemeColor, + darkColors + ) + + is ClockWidgetStyle.Segment -> SegmentClock( + time, + compact, + showSeconds, + useThemeColor, + darkColors + ) + + is ClockWidgetStyle.Custom -> CustomClock(style, compact, useThemeColor, darkColors) is ClockWidgetStyle.Empty -> {} else -> {} } @@ -426,7 +471,7 @@ fun ConfigureClockWidgetSheet( viewModel.setUseThemeColor(it) } ) - AnimatedVisibility(compact == false) { + AnimatedVisibility(compact == false && style !is ClockWidgetStyle.Custom) { SwitchPreference( title = stringResource(R.string.preference_clock_widget_show_seconds), icon = Icons.Rounded.AccessTime, diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/WatchFaceSelector.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/WatchFaceSelector.kt index 67563d48..a0b57be8 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/WatchFaceSelector.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/WatchFaceSelector.kt @@ -1,6 +1,11 @@ package de.mm20.launcher2.ui.launcher.widgets.clock +import android.app.Activity +import android.app.ActivityOptions +import android.appwidget.AppWidgetManager import android.content.Context +import android.os.Build +import android.util.Log import androidx.compose.animation.animateContentSize import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut @@ -19,8 +24,8 @@ import androidx.compose.material.icons.rounded.Check import androidx.compose.material.icons.rounded.ChevronLeft import androidx.compose.material.icons.rounded.ChevronRight import androidx.compose.material.icons.rounded.Settings -import androidx.compose.material.icons.rounded.Style import androidx.compose.material.icons.rounded.Tune +import androidx.compose.material.icons.rounded.Widgets import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem @@ -38,6 +43,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -50,8 +56,11 @@ import androidx.compose.ui.zIndex import de.mm20.launcher2.preferences.ClockWidgetColors import de.mm20.launcher2.preferences.ClockWidgetStyle import de.mm20.launcher2.ui.R +import de.mm20.launcher2.ui.base.LocalAppWidgetHost +import de.mm20.launcher2.ui.launcher.sheets.WidgetPickerSheet import de.mm20.launcher2.ui.locals.LocalDarkTheme import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper +import de.mm20.launcher2.widgets.AppWidget import kotlinx.coroutines.launch @Composable @@ -63,6 +72,9 @@ fun WatchFaceSelector( onSelect: (ClockWidgetStyle) -> Unit, ) { val context = LocalContext.current + + var showWidgetPicker by rememberSaveable { mutableStateOf(false) } + Surface( modifier = Modifier .fillMaxWidth() @@ -78,7 +90,8 @@ fun WatchFaceSelector( modifier = Modifier, ) { val pagerState = rememberPagerState( - initialPage = styles.indexOfFirst { it.javaClass == selected?.javaClass }.coerceAtLeast(0), + initialPage = styles.indexOfFirst { it.javaClass == selected?.javaClass } + .coerceAtLeast(0), ) { styles.size } @@ -93,7 +106,7 @@ fun WatchFaceSelector( Box { androidx.compose.animation.AnimatedVisibility( - selected is ClockWidgetStyle.Digital1, + selected is ClockWidgetStyle.Digital1 || selected is ClockWidgetStyle.Custom, modifier = Modifier .align(Alignment.TopEnd) .zIndex(1f), @@ -123,12 +136,60 @@ fun WatchFaceSelector( } ) } + if (selected is ClockWidgetStyle.Custom) { + DropdownMenuItem( + text = { Text(stringResource(R.string.widget_pick_widget)) }, + leadingIcon = { + Icon(Icons.Rounded.Widgets, null) + }, + onClick = { + showWidgetPicker = true + showStyleSettings = false + } + ) + val widget = remember(selected.widgetId) { + val id = selected.widgetId ?: return@remember null + AppWidgetManager.getInstance(context) + .getAppWidgetInfo(id) + } + val appWidgetHost = LocalAppWidgetHost.current + if (widget?.configure != null) { + DropdownMenuItem( + text = { Text(stringResource(R.string.widget_config_appwidget_configure)) }, + leadingIcon = { + Icon(Icons.Rounded.Settings, null) + }, + onClick = { + appWidgetHost.startAppWidgetConfigureActivityForResult( + context as Activity, + selected.widgetId ?: return@DropdownMenuItem, + 0, + 0, + if (Build.VERSION.SDK_INT < 34) { + null + } else { + ActivityOptions.makeBasic() + .setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + ) + .setPendingIntentCreatorBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + ) + .toBundle() + } + ) + } + ) + } + } } } } + val darkColors = colors == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current || colors == ClockWidgetColors.Dark + CompositionLocalProvider( - LocalContentColor provides if (colors == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current || colors == ClockWidgetColors.Dark) { + LocalContentColor provides if (darkColors) { Color(0, 0, 0, 180) } else { Color.White @@ -148,9 +209,9 @@ fun WatchFaceSelector( ) { val currentPageStyle = styles[pageIndex] if (currentPageStyle.javaClass == selected?.javaClass) { - Clock(selected, compact) + Clock(selected, compact, darkColors) } else { - Clock(currentPageStyle, compact) + Clock(currentPageStyle, compact, darkColors) } } } @@ -240,6 +301,23 @@ fun WatchFaceSelector( } } } + + if (showWidgetPicker && selected is ClockWidgetStyle.Custom) { + val previousWidgetId = selected.widgetId + val appWidgetHost = LocalAppWidgetHost.current + WidgetPickerSheet( + includeBuiltinWidgets = false, + onWidgetSelected = { + if (previousWidgetId != null) { + appWidgetHost.deleteAppWidgetId(previousWidgetId) + } + onSelect(selected.copy(widgetId = (it as AppWidget).config.widgetId)) + }, + onDismiss = { + showWidgetPicker = false + } + ) + } } fun getClockStyleName(context: Context, style: ClockWidgetStyle): String { @@ -251,18 +329,7 @@ fun getClockStyleName(context: Context, style: ClockWidgetStyle): String { is ClockWidgetStyle.Analog -> context.getString(R.string.clock_style_analog) is ClockWidgetStyle.Segment -> context.getString(R.string.clock_style_segment) is ClockWidgetStyle.Empty -> context.getString(R.string.clock_style_empty) + is ClockWidgetStyle.Custom -> context.getString(R.string.clock_style_custom) else -> "" } -} - -// Compat for old enum names, TODO refactor this screen -object ClockStyle { - val DigitalClock1 = ClockWidgetStyle.Digital1() - val DigitalClock1_Outlined = ClockWidgetStyle.Digital1(outlined = true) - val DigitalClock2 = ClockWidgetStyle.Digital2 - val OrbitClock = ClockWidgetStyle.Orbit - val AnalogClock = ClockWidgetStyle.Analog - val BinaryClock = ClockWidgetStyle.Binary - val SegmentClock = ClockWidgetStyle.Segment - val EmptyClock = ClockWidgetStyle.Empty } \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/AnalogClock.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/AnalogClock.kt index 905477fd..9326cb74 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/AnalogClock.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/AnalogClock.kt @@ -22,6 +22,7 @@ fun AnalogClock( compact: Boolean, showSeconds: Boolean, useThemeColor: Boolean, + darkColors: Boolean, ) { val verticalLayout = !compact val date = Calendar.getInstance() @@ -34,7 +35,7 @@ fun AnalogClock( val strokeWidth = if (verticalLayout) 4.dp else 2.dp val color = if (useThemeColor) { - if (LocalContentColor.current == Color.White) { + if (!darkColors) { if (LocalDarkTheme.current) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.primaryContainer } else { diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/BinaryClock.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/BinaryClock.kt index 2c39b68a..7eeb70f3 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/BinaryClock.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/BinaryClock.kt @@ -23,6 +23,7 @@ fun BinaryClock( compact: Boolean, showSeconds: Boolean, useThemeColor: Boolean, + darkColors: Boolean, ) { val verticalLayout = !compact val date = Calendar.getInstance() @@ -33,7 +34,7 @@ fun BinaryClock( if (hour == 0) hour = 12 val color = if (useThemeColor) { - if (LocalContentColor.current == Color.White) { + if (!darkColors) { if (LocalDarkTheme.current) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.primaryContainer } else { diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/CustomClock.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/CustomClock.kt new file mode 100644 index 00000000..3bd47108 --- /dev/null +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/CustomClock.kt @@ -0,0 +1,49 @@ +package de.mm20.launcher2.ui.launcher.widgets.clock.clocks + +import android.appwidget.AppWidgetManager +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material3.ElevatedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import de.mm20.launcher2.preferences.ClockWidgetStyle +import de.mm20.launcher2.ui.launcher.sheets.WidgetPickerSheet +import de.mm20.launcher2.ui.launcher.widgets.external.ExternalWidget + +@Composable +fun CustomClock( + style: ClockWidgetStyle.Custom, + compact: Boolean, + useThemeColor: Boolean, + darkColors: Boolean, +) { + val widgetId = style.widgetId + + if (widgetId == null) { + Text("Hmmm…") + } else { + val context = LocalContext.current + val widgetInfo = remember(widgetId) { + AppWidgetManager.getInstance(context) + .getAppWidgetInfo(widgetId) + } + if (widgetInfo != null) { + ExternalWidget( + widgetInfo = widgetInfo, + widgetId = widgetId, + height = if (compact) 64 else 200, + useThemeColors = useThemeColor, + onLightBackground = darkColors, + borderless = compact, + modifier = Modifier.widthIn(max = 250.dp) + ) + } + } +} \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock1.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock1.kt index 178d38ce..469835a3 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock1.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock1.kt @@ -35,6 +35,7 @@ fun DigitalClock1( compact: Boolean, showSeconds: Boolean, useThemeColor: Boolean, + darkColors: Boolean, ) { val verticalLayout = !compact val format = SimpleDateFormat( @@ -56,7 +57,7 @@ fun DigitalClock1( ) val color = if (useThemeColor) { - if (LocalContentColor.current == Color.White) { + if (!darkColors) { if (LocalDarkTheme.current) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.primaryContainer } else { diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock2.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock2.kt index 6ee4d5fb..63746452 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock2.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/DigitalClock2.kt @@ -22,11 +22,12 @@ fun DigitalClock2( compact: Boolean, showSeconds: Boolean, useThemeColor: Boolean, + darkColors: Boolean, ) { val verticalLayout = !compact val color = if (useThemeColor) { - if (LocalContentColor.current == Color.White) { + if (!darkColors) { if (LocalDarkTheme.current) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.primaryContainer } else { diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/OrbitClock.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/OrbitClock.kt index 2a4fffdd..cea5ddde 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/OrbitClock.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/OrbitClock.kt @@ -43,15 +43,13 @@ import kotlin.math.sin private const val PHI_F = 1.618033988749895.toFloat() -private val currentTime - get() = Instant.ofEpochMilli(System.currentTimeMillis()).atZone(ZoneId.systemDefault()) - @Composable fun OrbitClock( time: Long, compact: Boolean, showSeconds: Boolean, - useThemeColor: Boolean + useThemeColor: Boolean, + darkColors: Boolean, ) { val verticalLayout = !compact @@ -107,8 +105,8 @@ fun OrbitClock( label = "hoursAnimation" ) - val fgTone = if (LocalContentColor.current == Color.White) 10 else 90 - val bgTone = if (LocalContentColor.current == Color.White) 90 else 30 + val fgTone = if (!darkColors) 10 else 90 + val bgTone = if (!darkColors) 90 else 30 val background = if (useThemeColor) { Color(TonalPalette.fromInt(MaterialTheme.colorScheme.primaryContainer.toArgb()).tone(bgTone)) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/SegmentClock.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/SegmentClock.kt index 5d500ffd..47084820 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/SegmentClock.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/SegmentClock.kt @@ -50,7 +50,8 @@ fun SegmentClock( time: Long, compact: Boolean, showSeconds: Boolean, - useThemeColor: Boolean + useThemeColor: Boolean, + darkColors: Boolean, ) { val parsed = Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault()) val hour = parsed.hour @@ -66,7 +67,7 @@ fun SegmentClock( } val enabled = if (useThemeColor) { - if (LocalContentColor.current == Color.White) { + if (!darkColors) { if (LocalDarkTheme.current) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.primaryContainer } else { diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/external/ExternalWidget.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/external/ExternalWidget.kt index a3a8fdac..c22d8af6 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/external/ExternalWidget.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/external/ExternalWidget.kt @@ -1,48 +1,53 @@ package de.mm20.launcher2.ui.launcher.widgets.external import android.appwidget.AppWidgetHost -import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProviderInfo -import android.os.Bundle -import android.util.Log +import android.os.Build +import android.util.SparseIntArray import android.view.View import android.view.ViewGroup import android.widget.ListView import android.widget.ScrollView -import androidx.appcompat.app.AppCompatActivity +import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.key -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.view.doOnNextLayout import androidx.core.view.iterator import androidx.core.view.setPadding +import de.mm20.launcher2.ktx.isAtLeastApiLevel +import de.mm20.launcher2.ui.base.LocalAppWidgetHost import de.mm20.launcher2.ui.ktx.toPixels +import palettes.TonalPalette import kotlin.math.roundToInt @Composable fun ExternalWidget( - appWidgetHost: AppWidgetHost, widgetInfo: AppWidgetProviderInfo, widgetId: Int, height: Int, modifier: Modifier = Modifier, borderless: Boolean = false, + useThemeColors: Boolean = false, + onLightBackground: Boolean = false, ) { val padding = if (borderless) 0 else 8.dp.toPixels().roundToInt() + + val colorScheme = MaterialTheme.colorScheme + val appWidgetHost = LocalAppWidgetHost.current + BoxWithConstraints { val maxWidth = maxWidth key(widgetId) { AndroidView( modifier = modifier - .fillMaxWidth() .height(height.dp), factory = { val view = appWidgetHost.createView(it.applicationContext, widgetId, widgetInfo) @@ -50,6 +55,19 @@ fun ExternalWidget( return@AndroidView view }, update = { + if (isAtLeastApiLevel(31)) { + if (useThemeColors) { + val colorMapping = getColorMapping(colorScheme) + it.setColorResources(colorMapping) + } else { + it.resetColorResources() + } + } + + if (isAtLeastApiLevel(29)) { + it.setOnLightBackground(onLightBackground) + } + it.updateAppWidgetSize( null, maxWidth.value.roundToInt(), @@ -71,4 +89,77 @@ private fun enableNestedScroll(view: View) { } } if (view is ListView || view is ScrollView) view.isNestedScrollingEnabled = true +} + +@RequiresApi(Build.VERSION_CODES.S) +private fun getColorMapping(colorScheme: ColorScheme): SparseIntArray { + val p = TonalPalette.fromInt(colorScheme.primary.toArgb()) + val s = TonalPalette.fromInt(colorScheme.secondary.toArgb()) + val t = TonalPalette.fromInt(colorScheme.tertiary.toArgb()) + val n = TonalPalette.fromInt(colorScheme.outline.toArgb()) + val nv = TonalPalette.fromInt(colorScheme.outlineVariant.toArgb()) + + val colorResources = SparseIntArray() + colorResources.append(android.R.color.system_accent1_0, p.tone(100)) + colorResources.append(android.R.color.system_accent1_10, p.tone(99)) + colorResources.append(android.R.color.system_accent1_100, p.tone(90)) + colorResources.append(android.R.color.system_accent1_200, p.tone(80)) + colorResources.append(android.R.color.system_accent1_300, p.tone(70)) + colorResources.append(android.R.color.system_accent1_400, p.tone(60)) + colorResources.append(android.R.color.system_accent1_500, p.tone(50)) + colorResources.append(android.R.color.system_accent1_600, p.tone(40)) + colorResources.append(android.R.color.system_accent1_700, p.tone(30)) + colorResources.append(android.R.color.system_accent1_800, p.tone(20)) + colorResources.append(android.R.color.system_accent1_900, p.tone(10)) + colorResources.append(android.R.color.system_accent1_1000, s.tone(0)) + colorResources.append(android.R.color.system_accent2_0, s.tone(100)) + colorResources.append(android.R.color.system_accent2_10, s.tone(99)) + colorResources.append(android.R.color.system_accent2_100, s.tone(90)) + colorResources.append(android.R.color.system_accent2_200, s.tone(80)) + colorResources.append(android.R.color.system_accent2_300, s.tone(70)) + colorResources.append(android.R.color.system_accent2_400, s.tone(60)) + colorResources.append(android.R.color.system_accent2_500, s.tone(50)) + colorResources.append(android.R.color.system_accent2_600, s.tone(40)) + colorResources.append(android.R.color.system_accent2_700, s.tone(30)) + colorResources.append(android.R.color.system_accent2_800, s.tone(20)) + colorResources.append(android.R.color.system_accent2_900, s.tone(10)) + colorResources.append(android.R.color.system_accent2_1000, t.tone(0)) + colorResources.append(android.R.color.system_accent3_0, t.tone(100)) + colorResources.append(android.R.color.system_accent3_10, t.tone(99)) + colorResources.append(android.R.color.system_accent3_100, t.tone(90)) + colorResources.append(android.R.color.system_accent3_200, t.tone(80)) + colorResources.append(android.R.color.system_accent3_300, t.tone(70)) + colorResources.append(android.R.color.system_accent3_400, t.tone(60)) + colorResources.append(android.R.color.system_accent3_500, t.tone(50)) + colorResources.append(android.R.color.system_accent3_600, t.tone(40)) + colorResources.append(android.R.color.system_accent3_700, t.tone(30)) + colorResources.append(android.R.color.system_accent3_800, t.tone(20)) + colorResources.append(android.R.color.system_accent3_900, t.tone(10)) + colorResources.append(android.R.color.system_accent3_1000, t.tone(0)) + colorResources.append(android.R.color.system_neutral1_0, n.tone(100)) + colorResources.append(android.R.color.system_neutral1_10, n.tone(99)) + colorResources.append(android.R.color.system_neutral1_100, n.tone(90)) + colorResources.append(android.R.color.system_neutral1_200, n.tone(80)) + colorResources.append(android.R.color.system_neutral1_300, n.tone(70)) + colorResources.append(android.R.color.system_neutral1_400, n.tone(60)) + colorResources.append(android.R.color.system_neutral1_500, n.tone(50)) + colorResources.append(android.R.color.system_neutral1_600, n.tone(40)) + colorResources.append(android.R.color.system_neutral1_700, n.tone(30)) + colorResources.append(android.R.color.system_neutral1_800, n.tone(20)) + colorResources.append(android.R.color.system_neutral1_900, n.tone(10)) + colorResources.append(android.R.color.system_neutral1_1000, nv.tone(0)) + colorResources.append(android.R.color.system_neutral2_0, nv.tone(100)) + colorResources.append(android.R.color.system_neutral2_10, nv.tone(99)) + colorResources.append(android.R.color.system_neutral2_100, nv.tone(90)) + colorResources.append(android.R.color.system_neutral2_200, nv.tone(80)) + colorResources.append(android.R.color.system_neutral2_300, nv.tone(70)) + colorResources.append(android.R.color.system_neutral2_400, nv.tone(60)) + colorResources.append(android.R.color.system_neutral2_500, nv.tone(50)) + colorResources.append(android.R.color.system_neutral2_600, nv.tone(40)) + colorResources.append(android.R.color.system_neutral2_700, nv.tone(30)) + colorResources.append(android.R.color.system_neutral2_800, nv.tone(20)) + colorResources.append(android.R.color.system_neutral2_900, nv.tone(10)) + colorResources.append(android.R.color.system_neutral2_1000, nv.tone(0)) + + return colorResources } \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/locals/CompositionLocals.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/locals/CompositionLocals.kt index 77caaafa..d82780eb 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/locals/CompositionLocals.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/locals/CompositionLocals.kt @@ -11,8 +11,6 @@ import de.mm20.launcher2.ui.theme.WallpaperColors val LocalWindowSize = compositionLocalOf { Size(0f, 0f) } -val LocalAppWidgetHost = compositionLocalOf(defaultFactory = { null }) - val LocalNavController = compositionLocalOf { null } val LocalCardStyle = compositionLocalOf { CardStyle() } 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 1ede778f..6d75653c 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 @@ -24,8 +24,7 @@ import androidx.navigation.navArgument import de.mm20.launcher2.licenses.AppLicense import de.mm20.launcher2.licenses.OpenSourceLicenses import de.mm20.launcher2.ui.base.BaseActivity -import de.mm20.launcher2.ui.base.ProvideCurrentTime -import de.mm20.launcher2.ui.base.ProvideSettings +import de.mm20.launcher2.ui.base.ProvideCompositionLocals import de.mm20.launcher2.ui.locals.LocalDarkTheme import de.mm20.launcher2.ui.locals.LocalNavController import de.mm20.launcher2.ui.locals.LocalWallpaperColors @@ -84,158 +83,161 @@ class SettingsActivity : BaseActivity() { LocalNavController provides navController, LocalWallpaperColors provides wallpaperColors, ) { - ProvideSettings { - ProvideCurrentTime { - LauncherTheme { - val systemBarColor = MaterialTheme.colorScheme.surfaceDim - val systemBarColorAlt = MaterialTheme.colorScheme.onSurface - val isDarkTheme = LocalDarkTheme.current - LaunchedEffect(isDarkTheme, systemBarColor, systemBarColorAlt) { - enableEdgeToEdge( - if (isDarkTheme) SystemBarStyle.dark(systemBarColor.toArgb()) - else SystemBarStyle.light(systemBarColor.toArgb(), systemBarColorAlt.toArgb()) + ProvideCompositionLocals { + LauncherTheme { + val systemBarColor = MaterialTheme.colorScheme.surfaceDim + val systemBarColorAlt = MaterialTheme.colorScheme.onSurface + val isDarkTheme = LocalDarkTheme.current + LaunchedEffect(isDarkTheme, systemBarColor, systemBarColorAlt) { + enableEdgeToEdge( + if (isDarkTheme) SystemBarStyle.dark(systemBarColor.toArgb()) + else SystemBarStyle.light( + systemBarColor.toArgb(), + systemBarColorAlt.toArgb() ) - } - OverlayHost { - NavHost( - navController = navController, - startDestination = "settings", - exitTransition = { - fadeOut() + scaleOut(targetScale = 0.5f) - }, - enterTransition = { - slideInHorizontally { it } - }, - popEnterTransition = { - fadeIn() + scaleIn(initialScale = 0.5f) - }, - popExitTransition = { - slideOutHorizontally { it } - }, + ) + } + OverlayHost { + NavHost( + navController = navController, + startDestination = "settings", + exitTransition = { + fadeOut() + scaleOut(targetScale = 0.5f) + }, + enterTransition = { + slideInHorizontally { it } + }, + popEnterTransition = { + fadeIn() + scaleIn(initialScale = 0.5f) + }, + popExitTransition = { + slideOutHorizontally { it } + }, + ) { + composable("settings") { + MainSettingsScreen() + } + composable("settings/appearance") { + AppearanceSettingsScreen() + } + composable("settings/homescreen") { + HomescreenSettingsScreen() + } + composable("settings/icons") { + IconsSettingsScreen() + } + composable("settings/appearance/themes") { + ThemesSettingsScreen() + } + composable( + "settings/appearance/themes/{id}", + arguments = listOf(navArgument("id") { + nullable = false + }) ) { - composable("settings") { - MainSettingsScreen() - } - composable("settings/appearance") { - AppearanceSettingsScreen() - } - composable("settings/homescreen") { - HomescreenSettingsScreen() - } - composable("settings/icons") { - IconsSettingsScreen() - } - composable("settings/appearance/themes") { - ThemesSettingsScreen() - } - composable( - "settings/appearance/themes/{id}", - arguments = listOf(navArgument("id") { - nullable = false - }) - ) { - val id = it.arguments?.getString("id")?.let { - UUID.fromString(it) - } ?: return@composable - ThemeSettingsScreen(id) - } - composable("settings/appearance/cards") { - CardsSettingsScreen() - } - composable("settings/search") { - SearchSettingsScreen() - } - composable("settings/gestures") { - GestureSettingsScreen() - } - composable("settings/search/unitconverter") { - UnitConverterSettingsScreen() - } - composable("settings/search/wikipedia") { - WikipediaSettingsScreen() - } - composable("settings/search/locations") { - LocationsSettingsScreen() - } - composable("settings/search/files") { - FileSearchSettingsScreen() - } - composable("settings/search/searchactions") { - SearchActionsSettingsScreen() - } - composable("settings/search/hiddenitems") { - HiddenItemsSettingsScreen() - } - composable("settings/search/tags") { - TagsSettingsScreen() - } - composable(ROUTE_WEATHER_INTEGRATION) { - WeatherIntegrationSettingsScreen() - } - composable(ROUTE_MEDIA_INTEGRATION) { - MediaIntegrationSettingsScreen() - } - composable("settings/favorites") { - FavoritesSettingsScreen() - } - composable("settings/integrations") { - IntegrationsSettingsScreen() - } - composable("settings/plugins") { - PluginsSettingsScreen() - } - composable("settings/plugins/{id}") { - PluginSettingsScreen(it.arguments?.getString("id") ?: return@composable) - } - composable("settings/about") { - AboutSettingsScreen() - } - composable("settings/about/buildinfo") { - BuildInfoSettingsScreen() - } - composable("settings/about/easteregg") { - EasterEggSettingsScreen() - } - composable("settings/debug") { - DebugSettingsScreen() - } - composable("settings/backup") { - BackupSettingsScreen() - } - composable("settings/debug/crashreporter") { - CrashReporterScreen() - } - composable("settings/debug/logs") { - LogScreen() - } - composable( - "settings/debug/crashreporter/report?fileName={fileName}", - arguments = listOf(navArgument("fileName") { - nullable = false - }) - ) { - val fileName = it.arguments?.getString("fileName") - ?.let { - URLDecoder.decode(it, "utf8") - } - CrashReportScreen(fileName!!) - } - composable( - "settings/license?library={libraryName}", - arguments = listOf(navArgument("libraryName") { - nullable = true - }) - ) { - val libraryName = it.arguments?.getString("libraryName") - val library = remember(libraryName) { - if (libraryName == null) { - AppLicense.get(this@SettingsActivity) - } else { - OpenSourceLicenses.first { it.name == libraryName } - } + val id = it.arguments?.getString("id")?.let { + UUID.fromString(it) + } ?: return@composable + ThemeSettingsScreen(id) + } + composable("settings/appearance/cards") { + CardsSettingsScreen() + } + composable("settings/search") { + SearchSettingsScreen() + } + composable("settings/gestures") { + GestureSettingsScreen() + } + composable("settings/search/unitconverter") { + UnitConverterSettingsScreen() + } + composable("settings/search/wikipedia") { + WikipediaSettingsScreen() + } + composable("settings/search/locations") { + LocationsSettingsScreen() + } + composable("settings/search/files") { + FileSearchSettingsScreen() + } + composable("settings/search/searchactions") { + SearchActionsSettingsScreen() + } + composable("settings/search/hiddenitems") { + HiddenItemsSettingsScreen() + } + composable("settings/search/tags") { + TagsSettingsScreen() + } + composable(ROUTE_WEATHER_INTEGRATION) { + WeatherIntegrationSettingsScreen() + } + composable(ROUTE_MEDIA_INTEGRATION) { + MediaIntegrationSettingsScreen() + } + composable("settings/favorites") { + FavoritesSettingsScreen() + } + composable("settings/integrations") { + IntegrationsSettingsScreen() + } + composable("settings/plugins") { + PluginsSettingsScreen() + } + composable("settings/plugins/{id}") { + PluginSettingsScreen( + it.arguments?.getString("id") ?: return@composable + ) + } + composable("settings/about") { + AboutSettingsScreen() + } + composable("settings/about/buildinfo") { + BuildInfoSettingsScreen() + } + composable("settings/about/easteregg") { + EasterEggSettingsScreen() + } + composable("settings/debug") { + DebugSettingsScreen() + } + composable("settings/backup") { + BackupSettingsScreen() + } + composable("settings/debug/crashreporter") { + CrashReporterScreen() + } + composable("settings/debug/logs") { + LogScreen() + } + composable( + "settings/debug/crashreporter/report?fileName={fileName}", + arguments = listOf(navArgument("fileName") { + nullable = false + }) + ) { + val fileName = it.arguments?.getString("fileName") + ?.let { + URLDecoder.decode(it, "utf8") + } + CrashReportScreen(fileName!!) + } + composable( + "settings/license?library={libraryName}", + arguments = listOf(navArgument("libraryName") { + nullable = true + }) + ) { + val libraryName = it.arguments?.getString("libraryName") + val library = remember(libraryName) { + if (libraryName == null) { + AppLicense.get(this@SettingsActivity) + } else { + OpenSourceLicenses.first { it.name == libraryName } } - LicenseScreen(library) } + LicenseScreen(library) } } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreenVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreenVM.kt index 3ffbf3c0..6781420e 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreenVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/clockwidget/ClockWidgetSettingsScreenVM.kt @@ -7,6 +7,7 @@ import de.mm20.launcher2.preferences.ClockWidgetColors import de.mm20.launcher2.preferences.ClockWidgetStyle import de.mm20.launcher2.preferences.ui.ClockWidgetSettings import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import org.koin.core.component.KoinComponent @@ -20,7 +21,7 @@ class ClockWidgetSettingsScreenVM : ViewModel(), KoinComponent { settings.setCompact(compact) } - val availableClockStyles = settings.digital1.map {digital1 -> + val availableClockStyles = combine(settings.digital1, settings.custom) {digital1, custom -> listOf( digital1, ClockWidgetStyle.Digital2, @@ -28,6 +29,7 @@ class ClockWidgetSettingsScreenVM : ViewModel(), KoinComponent { ClockWidgetStyle.Orbit, ClockWidgetStyle.Segment, ClockWidgetStyle.Binary, + custom, ClockWidgetStyle.Empty, ) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList()) diff --git a/core/i18n/src/main/res/values/strings.xml b/core/i18n/src/main/res/values/strings.xml index a65f9c9e..61036e9d 100644 --- a/core/i18n/src/main/res/values/strings.xml +++ b/core/i18n/src/main/res/values/strings.xml @@ -845,6 +845,7 @@ Drag to resize Weather integration settings No calendars found + Pick widget App widget failed to load. Media control integration settings Link to file @@ -909,6 +910,7 @@ Hands 7-segment No clock + Custom widget Standard Outlined \ No newline at end of file diff --git a/core/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherSettingsData.kt b/core/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherSettingsData.kt index e124b1d3..ddd4d04f 100644 --- a/core/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherSettingsData.kt +++ b/core/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherSettingsData.kt @@ -30,6 +30,7 @@ data class LauncherSettingsData internal constructor( @SerialName("clockWidgetStyle2") internal val clockWidgetStyle: ClockWidgetStyleEnum = ClockWidgetStyleEnum.Digital1, val clockWidgetDigital1: ClockWidgetStyle.Digital1 = ClockWidgetStyle.Digital1(), + val clockWidgetCustom: ClockWidgetStyle.Custom = ClockWidgetStyle.Custom(), val clockWidgetColors: ClockWidgetColors = ClockWidgetColors.Auto, val clockWidgetShowSeconds: Boolean = false, val clockWidgetUseThemeColor: Boolean = false, @@ -192,6 +193,7 @@ internal enum class ClockWidgetStyleEnum { Binary, Segment, Empty, + Custom, } @Serializable @@ -234,6 +236,10 @@ sealed interface ClockWidgetStyle { @Serializable @SerialName("empty") data object Empty : ClockWidgetStyle + + @Serializable + @SerialName("custom") + data class Custom(val widgetId: Int? = null) : ClockWidgetStyle } @Serializable diff --git a/core/preferences/src/main/java/de/mm20/launcher2/preferences/ui/ClockWidgetSettings.kt b/core/preferences/src/main/java/de/mm20/launcher2/preferences/ui/ClockWidgetSettings.kt index 09686fc2..3d473c93 100644 --- a/core/preferences/src/main/java/de/mm20/launcher2/preferences/ui/ClockWidgetSettings.kt +++ b/core/preferences/src/main/java/de/mm20/launcher2/preferences/ui/ClockWidgetSettings.kt @@ -99,17 +99,22 @@ class ClockWidgetSettings internal constructor( ClockWidgetStyleEnum.Binary -> ClockWidgetStyle.Binary ClockWidgetStyleEnum.Segment -> ClockWidgetStyle.Segment ClockWidgetStyleEnum.Empty -> ClockWidgetStyle.Empty + ClockWidgetStyleEnum.Custom -> it.clockWidgetCustom } } val digital1: Flow get() = launcherDataStore.data.map { it.clockWidgetDigital1 } + val custom: Flow + get() = launcherDataStore.data.map { it.clockWidgetCustom } + fun setClockStyle(clockStyle: ClockWidgetStyle) { launcherDataStore.update { it.copy( clockWidgetStyle = clockStyle.enumValue, clockWidgetDigital1 = if (clockStyle is ClockWidgetStyle.Digital1) clockStyle else it.clockWidgetDigital1, + clockWidgetCustom = if (clockStyle is ClockWidgetStyle.Custom) clockStyle else it.clockWidgetCustom, ) } } @@ -151,4 +156,5 @@ internal val ClockWidgetStyle.enumValue is ClockWidgetStyle.Binary -> ClockWidgetStyleEnum.Binary is ClockWidgetStyle.Segment -> ClockWidgetStyleEnum.Segment is ClockWidgetStyle.Empty -> ClockWidgetStyleEnum.Empty + is ClockWidgetStyle.Custom -> ClockWidgetStyleEnum.Custom } \ No newline at end of file