[WIP] Custom widgets as watch face
This commit is contained in:
parent
efc50eb0a8
commit
3288d933c5
@ -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<AppWidgetHost> { 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)
|
||||||
|
}
|
||||||
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -38,8 +38,7 @@ import de.mm20.launcher2.preferences.BaseLayout
|
|||||||
import de.mm20.launcher2.preferences.SystemBarColors
|
import de.mm20.launcher2.preferences.SystemBarColors
|
||||||
import de.mm20.launcher2.ui.assistant.AssistantScaffold
|
import de.mm20.launcher2.ui.assistant.AssistantScaffold
|
||||||
import de.mm20.launcher2.ui.base.BaseActivity
|
import de.mm20.launcher2.ui.base.BaseActivity
|
||||||
import de.mm20.launcher2.ui.base.ProvideCurrentTime
|
import de.mm20.launcher2.ui.base.ProvideCompositionLocals
|
||||||
import de.mm20.launcher2.ui.base.ProvideSettings
|
|
||||||
import de.mm20.launcher2.ui.component.NavBarEffects
|
import de.mm20.launcher2.ui.component.NavBarEffects
|
||||||
import de.mm20.launcher2.ui.gestures.GestureDetector
|
import de.mm20.launcher2.ui.gestures.GestureDetector
|
||||||
import de.mm20.launcher2.ui.gestures.LocalGestureDetector
|
import de.mm20.launcher2.ui.gestures.LocalGestureDetector
|
||||||
@ -101,7 +100,7 @@ abstract class SharedLauncherActivity(
|
|||||||
LocalGestureDetector provides gestureDetector,
|
LocalGestureDetector provides gestureDetector,
|
||||||
) {
|
) {
|
||||||
LauncherTheme {
|
LauncherTheme {
|
||||||
ProvideSettings {
|
ProvideCompositionLocals {
|
||||||
val statusBarColor by viewModel.statusBarColor.collectAsState()
|
val statusBarColor by viewModel.statusBarColor.collectAsState()
|
||||||
val navBarColor by viewModel.navBarColor.collectAsState()
|
val navBarColor by viewModel.navBarColor.collectAsState()
|
||||||
|
|
||||||
@ -160,111 +159,109 @@ abstract class SharedLauncherActivity(
|
|||||||
systemUiController.isNavigationBarVisible = !hideNav
|
systemUiController.isNavigationBarVisible = !hideNav
|
||||||
}
|
}
|
||||||
|
|
||||||
ProvideCurrentTime {
|
OverlayHost(
|
||||||
OverlayHost(
|
modifier = Modifier
|
||||||
modifier = Modifier
|
.fillMaxSize()
|
||||||
.fillMaxSize()
|
.background(if (dimBackground) Color.Black.copy(alpha = 0.30f) else Color.Transparent),
|
||||||
.background(if (dimBackground) Color.Black.copy(alpha = 0.30f) else Color.Transparent),
|
contentAlignment = Alignment.BottomCenter
|
||||||
contentAlignment = Alignment.BottomCenter
|
) {
|
||||||
) {
|
if (chargingAnimation == true) {
|
||||||
if (chargingAnimation == true) {
|
NavBarEffects(modifier = Modifier.fillMaxSize())
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,6 +80,7 @@ import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
|||||||
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.ui.R
|
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.BottomSheetDialog
|
||||||
import de.mm20.launcher2.ui.component.LargeMessage
|
import de.mm20.launcher2.ui.component.LargeMessage
|
||||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||||
@ -103,7 +104,6 @@ import kotlin.math.roundToInt
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ConfigureWidgetSheet(
|
fun ConfigureWidgetSheet(
|
||||||
appWidgetHost: AppWidgetHost,
|
|
||||||
widget: Widget,
|
widget: Widget,
|
||||||
onWidgetUpdated: (Widget) -> Unit,
|
onWidgetUpdated: (Widget) -> Unit,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
@ -128,7 +128,7 @@ fun ConfigureWidgetSheet(
|
|||||||
) {
|
) {
|
||||||
when (widget) {
|
when (widget) {
|
||||||
is WeatherWidget -> ConfigureWeatherWidget(widget, onWidgetUpdated)
|
is WeatherWidget -> ConfigureWeatherWidget(widget, onWidgetUpdated)
|
||||||
is AppWidget -> ConfigureAppWidget(appWidgetHost, widget, onWidgetUpdated)
|
is AppWidget -> ConfigureAppWidget(widget, onWidgetUpdated)
|
||||||
is CalendarWidget -> ConfigureCalendarWidget(widget, onWidgetUpdated)
|
is CalendarWidget -> ConfigureCalendarWidget(widget, onWidgetUpdated)
|
||||||
is FavoritesWidget -> ConfigureFavoritesWidget(widget, onWidgetUpdated)
|
is FavoritesWidget -> ConfigureFavoritesWidget(widget, onWidgetUpdated)
|
||||||
is MusicWidget -> ConfigureMusicWidget()
|
is MusicWidget -> ConfigureMusicWidget()
|
||||||
@ -272,7 +272,6 @@ fun ColumnScope.ConfigureMusicWidget(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ColumnScope.ConfigureAppWidget(
|
fun ColumnScope.ConfigureAppWidget(
|
||||||
appWidgetHost: AppWidgetHost,
|
|
||||||
widget: AppWidget,
|
widget: AppWidget,
|
||||||
onWidgetUpdated: (Widget) -> Unit,
|
onWidgetUpdated: (Widget) -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -346,7 +345,6 @@ fun ColumnScope.ConfigureAppWidget(
|
|||||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||||
) {
|
) {
|
||||||
ExternalWidget(
|
ExternalWidget(
|
||||||
appWidgetHost = appWidgetHost,
|
|
||||||
widgetInfo = widgetInfo,
|
widgetInfo = widgetInfo,
|
||||||
widgetId = widget.config.widgetId,
|
widgetId = widget.config.widgetId,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@ -472,6 +470,7 @@ fun ColumnScope.ConfigureAppWidget(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isAtLeastApiLevel(28) && widgetInfo.widgetFeatures and AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE != 0) {
|
if (isAtLeastApiLevel(28) && widgetInfo.widgetFeatures and AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE != 0) {
|
||||||
|
val appWidgetHost = LocalAppWidgetHost.current
|
||||||
TextButton(
|
TextButton(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(top = 8.dp)
|
.padding(top = 8.dp)
|
||||||
|
|||||||
@ -251,6 +251,8 @@ private class BindAndConfigureAppWidgetContract(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WidgetPickerSheet(
|
fun WidgetPickerSheet(
|
||||||
|
includeBuiltinWidgets: Boolean = true,
|
||||||
|
title: String = stringResource(R.string.widget_pick_widget),
|
||||||
onWidgetSelected: (Widget) -> Unit,
|
onWidgetSelected: (Widget) -> Unit,
|
||||||
onDismiss: () -> Unit
|
onDismiss: () -> Unit
|
||||||
) {
|
) {
|
||||||
@ -277,7 +279,7 @@ fun WidgetPickerSheet(
|
|||||||
BottomSheetDialog(
|
BottomSheetDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = {
|
title = {
|
||||||
Text(stringResource(R.string.widget_add_widget))
|
Text(title)
|
||||||
}) {
|
}) {
|
||||||
val builtIn by viewModel.builtInWidgets.collectAsState(emptyList())
|
val builtIn by viewModel.builtInWidgets.collectAsState(emptyList())
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
@ -318,44 +320,46 @@ fun WidgetPickerSheet(
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items(builtIn) {
|
if (includeBuiltinWidgets) {
|
||||||
OutlinedCard(
|
items(builtIn) {
|
||||||
modifier = Modifier
|
OutlinedCard(
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.padding(bottom = 16.dp, start = 16.dp, end = 16.dp),
|
.fillMaxWidth()
|
||||||
onClick = {
|
.padding(bottom = 16.dp, start = 16.dp, end = 16.dp),
|
||||||
val id = UUID.randomUUID()
|
onClick = {
|
||||||
val widget = when(it.type) {
|
val id = UUID.randomUUID()
|
||||||
WeatherWidget.Type -> WeatherWidget(id)
|
val widget = when (it.type) {
|
||||||
CalendarWidget.Type -> CalendarWidget(id)
|
WeatherWidget.Type -> WeatherWidget(id)
|
||||||
MusicWidget.Type -> MusicWidget(id)
|
CalendarWidget.Type -> CalendarWidget(id)
|
||||||
FavoritesWidget.Type -> FavoritesWidget(id)
|
MusicWidget.Type -> MusicWidget(id)
|
||||||
NotesWidget.Type -> NotesWidget(id)
|
FavoritesWidget.Type -> FavoritesWidget(id)
|
||||||
else -> return@OutlinedCard
|
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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,7 @@ import androidx.lifecycle.repeatOnLifecycle
|
|||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
import de.mm20.launcher2.ui.R
|
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.ktx.animateTo
|
||||||
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
|
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
|
||||||
import de.mm20.launcher2.ui.launcher.sheets.WidgetPickerSheet
|
import de.mm20.launcher2.ui.launcher.sheets.WidgetPickerSheet
|
||||||
@ -51,27 +52,11 @@ fun WidgetColumn(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
val viewModel: WidgetsVM = viewModel()
|
val viewModel: WidgetsVM = viewModel()
|
||||||
val context = LocalContext.current
|
|
||||||
val bottomSheetManager = LocalBottomSheetManager.current
|
val bottomSheetManager = LocalBottomSheetManager.current
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
|
||||||
val widgetHost = remember { AppWidgetHost(context.applicationContext, 44203) }
|
|
||||||
|
|
||||||
var addNewWidget by rememberSaveable { mutableStateOf(false) }
|
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(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@ -92,9 +77,10 @@ fun WidgetColumn(
|
|||||||
dragOffsetAfterSwap = null
|
dragOffsetAfterSwap = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val widgetHost = LocalAppWidgetHost.current
|
||||||
|
|
||||||
WidgetItem(
|
WidgetItem(
|
||||||
widget = widget,
|
widget = widget,
|
||||||
appWidgetHost = widgetHost,
|
|
||||||
editMode = editMode,
|
editMode = editMode,
|
||||||
onWidgetAdd = { widget, offset ->
|
onWidgetAdd = { widget, offset ->
|
||||||
viewModel.addWidget(widget, i + offset)
|
viewModel.addWidget(widget, i + offset)
|
||||||
|
|||||||
@ -60,7 +60,6 @@ import de.mm20.launcher2.widgets.Widget
|
|||||||
@Composable
|
@Composable
|
||||||
fun WidgetItem(
|
fun WidgetItem(
|
||||||
widget: Widget,
|
widget: Widget,
|
||||||
appWidgetHost: AppWidgetHost,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
editMode: Boolean = false,
|
editMode: Boolean = false,
|
||||||
onWidgetAdd: (widget: Widget, offset: Int) -> Unit = { _, _ -> },
|
onWidgetAdd: (widget: Widget, offset: Int) -> Unit = { _, _ -> },
|
||||||
@ -222,7 +221,6 @@ fun WidgetItem(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ExternalWidget(
|
ExternalWidget(
|
||||||
appWidgetHost = appWidgetHost,
|
|
||||||
widgetId = widget.config.widgetId,
|
widgetId = widget.config.widgetId,
|
||||||
widgetInfo = widgetInfo,
|
widgetInfo = widgetInfo,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@ -237,7 +235,6 @@ fun WidgetItem(
|
|||||||
}
|
}
|
||||||
if (configure) {
|
if (configure) {
|
||||||
ConfigureWidgetSheet(
|
ConfigureWidgetSheet(
|
||||||
appWidgetHost = appWidgetHost,
|
|
||||||
widget = widget,
|
widget = widget,
|
||||||
onWidgetUpdated = onWidgetUpdate,
|
onWidgetUpdated = onWidgetUpdate,
|
||||||
onDismiss = { configure = false },
|
onDismiss = { configure = false },
|
||||||
|
|||||||
@ -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.component.preferences.SwitchPreference
|
||||||
import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.AnalogClock
|
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.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.DigitalClock1
|
||||||
import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.DigitalClock2
|
import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.DigitalClock2
|
||||||
import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.OrbitClock
|
import de.mm20.launcher2.ui.launcher.widgets.clock.clocks.OrbitClock
|
||||||
@ -97,8 +98,11 @@ fun ClockWidget(
|
|||||||
val alignment by viewModel.alignment.collectAsState()
|
val alignment by viewModel.alignment.collectAsState()
|
||||||
val time = LocalTime.current
|
val time = LocalTime.current
|
||||||
|
|
||||||
|
val darkColors =
|
||||||
|
color == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current || color == ClockWidgetColors.Dark
|
||||||
|
|
||||||
val contentColor =
|
val contentColor =
|
||||||
if (color == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current || color == ClockWidgetColors.Dark) {
|
if (darkColors) {
|
||||||
Color(0, 0, 0, 180)
|
Color(0, 0, 0, 180)
|
||||||
} else {
|
} else {
|
||||||
Color.White
|
Color.White
|
||||||
@ -182,7 +186,7 @@ fun ClockWidget(
|
|||||||
viewModel.launchClockApp(context)
|
viewModel.launchClockApp(context)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Clock(clockStyle, false)
|
Clock(clockStyle, false, darkColors)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (partProvider != null) {
|
if (partProvider != null) {
|
||||||
@ -227,7 +231,7 @@ fun ClockWidget(
|
|||||||
viewModel.launchClockApp(context)
|
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
|
@Composable
|
||||||
fun Clock(
|
fun Clock(
|
||||||
style: ClockWidgetStyle?,
|
style: ClockWidgetStyle?,
|
||||||
compact: Boolean,
|
compact: Boolean,
|
||||||
|
darkColors: Boolean = false
|
||||||
) {
|
) {
|
||||||
val time = LocalTime.current
|
val time = LocalTime.current
|
||||||
val clockSettings: ClockWidgetSettings by inject()
|
val clockSettings: ClockWidgetSettings by inject()
|
||||||
@ -264,14 +272,51 @@ fun Clock(
|
|||||||
style,
|
style,
|
||||||
compact,
|
compact,
|
||||||
showSeconds,
|
showSeconds,
|
||||||
useThemeColor
|
useThemeColor,
|
||||||
|
darkColors
|
||||||
)
|
)
|
||||||
|
|
||||||
is ClockWidgetStyle.Digital2 -> DigitalClock2(time, compact, showSeconds, useThemeColor)
|
is ClockWidgetStyle.Digital2 -> DigitalClock2(
|
||||||
is ClockWidgetStyle.Binary -> BinaryClock(time, compact, showSeconds, useThemeColor)
|
time,
|
||||||
is ClockWidgetStyle.Analog -> AnalogClock(time, compact, showSeconds, useThemeColor)
|
compact,
|
||||||
is ClockWidgetStyle.Orbit -> OrbitClock(time, compact, showSeconds, useThemeColor)
|
showSeconds,
|
||||||
is ClockWidgetStyle.Segment -> SegmentClock(time, compact, showSeconds, useThemeColor)
|
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 -> {}
|
is ClockWidgetStyle.Empty -> {}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
@ -426,7 +471,7 @@ fun ConfigureClockWidgetSheet(
|
|||||||
viewModel.setUseThemeColor(it)
|
viewModel.setUseThemeColor(it)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
AnimatedVisibility(compact == false) {
|
AnimatedVisibility(compact == false && style !is ClockWidgetStyle.Custom) {
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
title = stringResource(R.string.preference_clock_widget_show_seconds),
|
title = stringResource(R.string.preference_clock_widget_show_seconds),
|
||||||
icon = Icons.Rounded.AccessTime,
|
icon = Icons.Rounded.AccessTime,
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
package de.mm20.launcher2.ui.launcher.widgets.clock
|
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.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.animation.scaleIn
|
import androidx.compose.animation.scaleIn
|
||||||
import androidx.compose.animation.scaleOut
|
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.ChevronLeft
|
||||||
import androidx.compose.material.icons.rounded.ChevronRight
|
import androidx.compose.material.icons.rounded.ChevronRight
|
||||||
import androidx.compose.material.icons.rounded.Settings
|
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.Tune
|
||||||
|
import androidx.compose.material.icons.rounded.Widgets
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
@ -38,6 +43,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.ClockWidgetColors
|
||||||
import de.mm20.launcher2.preferences.ClockWidgetStyle
|
import de.mm20.launcher2.preferences.ClockWidgetStyle
|
||||||
import de.mm20.launcher2.ui.R
|
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.LocalDarkTheme
|
||||||
import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper
|
import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper
|
||||||
|
import de.mm20.launcher2.widgets.AppWidget
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -63,6 +72,9 @@ fun WatchFaceSelector(
|
|||||||
onSelect: (ClockWidgetStyle) -> Unit,
|
onSelect: (ClockWidgetStyle) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
var showWidgetPicker by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@ -78,7 +90,8 @@ fun WatchFaceSelector(
|
|||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val pagerState = rememberPagerState(
|
val pagerState = rememberPagerState(
|
||||||
initialPage = styles.indexOfFirst { it.javaClass == selected?.javaClass }.coerceAtLeast(0),
|
initialPage = styles.indexOfFirst { it.javaClass == selected?.javaClass }
|
||||||
|
.coerceAtLeast(0),
|
||||||
) {
|
) {
|
||||||
styles.size
|
styles.size
|
||||||
}
|
}
|
||||||
@ -93,7 +106,7 @@ fun WatchFaceSelector(
|
|||||||
|
|
||||||
Box {
|
Box {
|
||||||
androidx.compose.animation.AnimatedVisibility(
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
selected is ClockWidgetStyle.Digital1,
|
selected is ClockWidgetStyle.Digital1 || selected is ClockWidgetStyle.Custom,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.TopEnd)
|
.align(Alignment.TopEnd)
|
||||||
.zIndex(1f),
|
.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(
|
CompositionLocalProvider(
|
||||||
LocalContentColor provides if (colors == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current || colors == ClockWidgetColors.Dark) {
|
LocalContentColor provides if (darkColors) {
|
||||||
Color(0, 0, 0, 180)
|
Color(0, 0, 0, 180)
|
||||||
} else {
|
} else {
|
||||||
Color.White
|
Color.White
|
||||||
@ -148,9 +209,9 @@ fun WatchFaceSelector(
|
|||||||
) {
|
) {
|
||||||
val currentPageStyle = styles[pageIndex]
|
val currentPageStyle = styles[pageIndex]
|
||||||
if (currentPageStyle.javaClass == selected?.javaClass) {
|
if (currentPageStyle.javaClass == selected?.javaClass) {
|
||||||
Clock(selected, compact)
|
Clock(selected, compact, darkColors)
|
||||||
} else {
|
} 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 {
|
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.Analog -> context.getString(R.string.clock_style_analog)
|
||||||
is ClockWidgetStyle.Segment -> context.getString(R.string.clock_style_segment)
|
is ClockWidgetStyle.Segment -> context.getString(R.string.clock_style_segment)
|
||||||
is ClockWidgetStyle.Empty -> context.getString(R.string.clock_style_empty)
|
is ClockWidgetStyle.Empty -> context.getString(R.string.clock_style_empty)
|
||||||
|
is ClockWidgetStyle.Custom -> context.getString(R.string.clock_style_custom)
|
||||||
else -> ""
|
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
|
|
||||||
}
|
}
|
||||||
@ -22,6 +22,7 @@ fun AnalogClock(
|
|||||||
compact: Boolean,
|
compact: Boolean,
|
||||||
showSeconds: Boolean,
|
showSeconds: Boolean,
|
||||||
useThemeColor: Boolean,
|
useThemeColor: Boolean,
|
||||||
|
darkColors: Boolean,
|
||||||
) {
|
) {
|
||||||
val verticalLayout = !compact
|
val verticalLayout = !compact
|
||||||
val date = Calendar.getInstance()
|
val date = Calendar.getInstance()
|
||||||
@ -34,7 +35,7 @@ fun AnalogClock(
|
|||||||
val strokeWidth = if (verticalLayout) 4.dp else 2.dp
|
val strokeWidth = if (verticalLayout) 4.dp else 2.dp
|
||||||
|
|
||||||
val color = if (useThemeColor) {
|
val color = if (useThemeColor) {
|
||||||
if (LocalContentColor.current == Color.White) {
|
if (!darkColors) {
|
||||||
if (LocalDarkTheme.current) MaterialTheme.colorScheme.onPrimaryContainer
|
if (LocalDarkTheme.current) MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
else MaterialTheme.colorScheme.primaryContainer
|
else MaterialTheme.colorScheme.primaryContainer
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -23,6 +23,7 @@ fun BinaryClock(
|
|||||||
compact: Boolean,
|
compact: Boolean,
|
||||||
showSeconds: Boolean,
|
showSeconds: Boolean,
|
||||||
useThemeColor: Boolean,
|
useThemeColor: Boolean,
|
||||||
|
darkColors: Boolean,
|
||||||
) {
|
) {
|
||||||
val verticalLayout = !compact
|
val verticalLayout = !compact
|
||||||
val date = Calendar.getInstance()
|
val date = Calendar.getInstance()
|
||||||
@ -33,7 +34,7 @@ fun BinaryClock(
|
|||||||
if (hour == 0) hour = 12
|
if (hour == 0) hour = 12
|
||||||
|
|
||||||
val color = if (useThemeColor) {
|
val color = if (useThemeColor) {
|
||||||
if (LocalContentColor.current == Color.White) {
|
if (!darkColors) {
|
||||||
if (LocalDarkTheme.current) MaterialTheme.colorScheme.onPrimaryContainer
|
if (LocalDarkTheme.current) MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
else MaterialTheme.colorScheme.primaryContainer
|
else MaterialTheme.colorScheme.primaryContainer
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -35,6 +35,7 @@ fun DigitalClock1(
|
|||||||
compact: Boolean,
|
compact: Boolean,
|
||||||
showSeconds: Boolean,
|
showSeconds: Boolean,
|
||||||
useThemeColor: Boolean,
|
useThemeColor: Boolean,
|
||||||
|
darkColors: Boolean,
|
||||||
) {
|
) {
|
||||||
val verticalLayout = !compact
|
val verticalLayout = !compact
|
||||||
val format = SimpleDateFormat(
|
val format = SimpleDateFormat(
|
||||||
@ -56,7 +57,7 @@ fun DigitalClock1(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val color = if (useThemeColor) {
|
val color = if (useThemeColor) {
|
||||||
if (LocalContentColor.current == Color.White) {
|
if (!darkColors) {
|
||||||
if (LocalDarkTheme.current) MaterialTheme.colorScheme.onPrimaryContainer
|
if (LocalDarkTheme.current) MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
else MaterialTheme.colorScheme.primaryContainer
|
else MaterialTheme.colorScheme.primaryContainer
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -22,11 +22,12 @@ fun DigitalClock2(
|
|||||||
compact: Boolean,
|
compact: Boolean,
|
||||||
showSeconds: Boolean,
|
showSeconds: Boolean,
|
||||||
useThemeColor: Boolean,
|
useThemeColor: Boolean,
|
||||||
|
darkColors: Boolean,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val verticalLayout = !compact
|
val verticalLayout = !compact
|
||||||
val color = if (useThemeColor) {
|
val color = if (useThemeColor) {
|
||||||
if (LocalContentColor.current == Color.White) {
|
if (!darkColors) {
|
||||||
if (LocalDarkTheme.current) MaterialTheme.colorScheme.onPrimaryContainer
|
if (LocalDarkTheme.current) MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
else MaterialTheme.colorScheme.primaryContainer
|
else MaterialTheme.colorScheme.primaryContainer
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -43,15 +43,13 @@ import kotlin.math.sin
|
|||||||
|
|
||||||
private const val PHI_F = 1.618033988749895.toFloat()
|
private const val PHI_F = 1.618033988749895.toFloat()
|
||||||
|
|
||||||
private val currentTime
|
|
||||||
get() = Instant.ofEpochMilli(System.currentTimeMillis()).atZone(ZoneId.systemDefault())
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun OrbitClock(
|
fun OrbitClock(
|
||||||
time: Long,
|
time: Long,
|
||||||
compact: Boolean,
|
compact: Boolean,
|
||||||
showSeconds: Boolean,
|
showSeconds: Boolean,
|
||||||
useThemeColor: Boolean
|
useThemeColor: Boolean,
|
||||||
|
darkColors: Boolean,
|
||||||
) {
|
) {
|
||||||
val verticalLayout = !compact
|
val verticalLayout = !compact
|
||||||
|
|
||||||
@ -107,8 +105,8 @@ fun OrbitClock(
|
|||||||
label = "hoursAnimation"
|
label = "hoursAnimation"
|
||||||
)
|
)
|
||||||
|
|
||||||
val fgTone = if (LocalContentColor.current == Color.White) 10 else 90
|
val fgTone = if (!darkColors) 10 else 90
|
||||||
val bgTone = if (LocalContentColor.current == Color.White) 90 else 30
|
val bgTone = if (!darkColors) 90 else 30
|
||||||
|
|
||||||
val background = if (useThemeColor) {
|
val background = if (useThemeColor) {
|
||||||
Color(TonalPalette.fromInt(MaterialTheme.colorScheme.primaryContainer.toArgb()).tone(bgTone))
|
Color(TonalPalette.fromInt(MaterialTheme.colorScheme.primaryContainer.toArgb()).tone(bgTone))
|
||||||
|
|||||||
@ -50,7 +50,8 @@ fun SegmentClock(
|
|||||||
time: Long,
|
time: Long,
|
||||||
compact: Boolean,
|
compact: Boolean,
|
||||||
showSeconds: Boolean,
|
showSeconds: Boolean,
|
||||||
useThemeColor: Boolean
|
useThemeColor: Boolean,
|
||||||
|
darkColors: Boolean,
|
||||||
) {
|
) {
|
||||||
val parsed = Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault())
|
val parsed = Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault())
|
||||||
val hour = parsed.hour
|
val hour = parsed.hour
|
||||||
@ -66,7 +67,7 @@ fun SegmentClock(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val enabled = if (useThemeColor) {
|
val enabled = if (useThemeColor) {
|
||||||
if (LocalContentColor.current == Color.White) {
|
if (!darkColors) {
|
||||||
if (LocalDarkTheme.current) MaterialTheme.colorScheme.onPrimaryContainer
|
if (LocalDarkTheme.current) MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
else MaterialTheme.colorScheme.primaryContainer
|
else MaterialTheme.colorScheme.primaryContainer
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,48 +1,53 @@
|
|||||||
package de.mm20.launcher2.ui.launcher.widgets.external
|
package de.mm20.launcher2.ui.launcher.widgets.external
|
||||||
|
|
||||||
import android.appwidget.AppWidgetHost
|
import android.appwidget.AppWidgetHost
|
||||||
import android.appwidget.AppWidgetManager
|
|
||||||
import android.appwidget.AppWidgetProviderInfo
|
import android.appwidget.AppWidgetProviderInfo
|
||||||
import android.os.Bundle
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.SparseIntArray
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ListView
|
import android.widget.ListView
|
||||||
import android.widget.ScrollView
|
import android.widget.ScrollView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
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.Composable
|
||||||
import androidx.compose.runtime.key
|
import androidx.compose.runtime.key
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.view.doOnNextLayout
|
|
||||||
import androidx.core.view.iterator
|
import androidx.core.view.iterator
|
||||||
import androidx.core.view.setPadding
|
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 de.mm20.launcher2.ui.ktx.toPixels
|
||||||
|
import palettes.TonalPalette
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExternalWidget(
|
fun ExternalWidget(
|
||||||
appWidgetHost: AppWidgetHost,
|
|
||||||
widgetInfo: AppWidgetProviderInfo,
|
widgetInfo: AppWidgetProviderInfo,
|
||||||
widgetId: Int,
|
widgetId: Int,
|
||||||
height: Int,
|
height: Int,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
borderless: Boolean = false,
|
borderless: Boolean = false,
|
||||||
|
useThemeColors: Boolean = false,
|
||||||
|
onLightBackground: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val padding = if (borderless) 0 else 8.dp.toPixels().roundToInt()
|
val padding = if (borderless) 0 else 8.dp.toPixels().roundToInt()
|
||||||
|
|
||||||
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
|
val appWidgetHost = LocalAppWidgetHost.current
|
||||||
|
|
||||||
BoxWithConstraints {
|
BoxWithConstraints {
|
||||||
val maxWidth = maxWidth
|
val maxWidth = maxWidth
|
||||||
key(widgetId) {
|
key(widgetId) {
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
|
||||||
.height(height.dp),
|
.height(height.dp),
|
||||||
factory = {
|
factory = {
|
||||||
val view = appWidgetHost.createView(it.applicationContext, widgetId, widgetInfo)
|
val view = appWidgetHost.createView(it.applicationContext, widgetId, widgetInfo)
|
||||||
@ -50,6 +55,19 @@ fun ExternalWidget(
|
|||||||
return@AndroidView view
|
return@AndroidView view
|
||||||
},
|
},
|
||||||
update = {
|
update = {
|
||||||
|
if (isAtLeastApiLevel(31)) {
|
||||||
|
if (useThemeColors) {
|
||||||
|
val colorMapping = getColorMapping(colorScheme)
|
||||||
|
it.setColorResources(colorMapping)
|
||||||
|
} else {
|
||||||
|
it.resetColorResources()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAtLeastApiLevel(29)) {
|
||||||
|
it.setOnLightBackground(onLightBackground)
|
||||||
|
}
|
||||||
|
|
||||||
it.updateAppWidgetSize(
|
it.updateAppWidgetSize(
|
||||||
null,
|
null,
|
||||||
maxWidth.value.roundToInt(),
|
maxWidth.value.roundToInt(),
|
||||||
@ -71,4 +89,77 @@ private fun enableNestedScroll(view: View) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (view is ListView || view is ScrollView) view.isNestedScrollingEnabled = true
|
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
|
||||||
}
|
}
|
||||||
@ -11,8 +11,6 @@ import de.mm20.launcher2.ui.theme.WallpaperColors
|
|||||||
|
|
||||||
val LocalWindowSize = compositionLocalOf { Size(0f, 0f) }
|
val LocalWindowSize = compositionLocalOf { Size(0f, 0f) }
|
||||||
|
|
||||||
val LocalAppWidgetHost = compositionLocalOf<AppWidgetHost?>(defaultFactory = { null })
|
|
||||||
|
|
||||||
val LocalNavController = compositionLocalOf<NavController?> { null }
|
val LocalNavController = compositionLocalOf<NavController?> { null }
|
||||||
|
|
||||||
val LocalCardStyle = compositionLocalOf { CardStyle() }
|
val LocalCardStyle = compositionLocalOf { CardStyle() }
|
||||||
|
|||||||
@ -24,8 +24,7 @@ import androidx.navigation.navArgument
|
|||||||
import de.mm20.launcher2.licenses.AppLicense
|
import de.mm20.launcher2.licenses.AppLicense
|
||||||
import de.mm20.launcher2.licenses.OpenSourceLicenses
|
import de.mm20.launcher2.licenses.OpenSourceLicenses
|
||||||
import de.mm20.launcher2.ui.base.BaseActivity
|
import de.mm20.launcher2.ui.base.BaseActivity
|
||||||
import de.mm20.launcher2.ui.base.ProvideCurrentTime
|
import de.mm20.launcher2.ui.base.ProvideCompositionLocals
|
||||||
import de.mm20.launcher2.ui.base.ProvideSettings
|
|
||||||
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
||||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||||
import de.mm20.launcher2.ui.locals.LocalWallpaperColors
|
import de.mm20.launcher2.ui.locals.LocalWallpaperColors
|
||||||
@ -84,158 +83,161 @@ class SettingsActivity : BaseActivity() {
|
|||||||
LocalNavController provides navController,
|
LocalNavController provides navController,
|
||||||
LocalWallpaperColors provides wallpaperColors,
|
LocalWallpaperColors provides wallpaperColors,
|
||||||
) {
|
) {
|
||||||
ProvideSettings {
|
ProvideCompositionLocals {
|
||||||
ProvideCurrentTime {
|
LauncherTheme {
|
||||||
LauncherTheme {
|
val systemBarColor = MaterialTheme.colorScheme.surfaceDim
|
||||||
val systemBarColor = MaterialTheme.colorScheme.surfaceDim
|
val systemBarColorAlt = MaterialTheme.colorScheme.onSurface
|
||||||
val systemBarColorAlt = MaterialTheme.colorScheme.onSurface
|
val isDarkTheme = LocalDarkTheme.current
|
||||||
val isDarkTheme = LocalDarkTheme.current
|
LaunchedEffect(isDarkTheme, systemBarColor, systemBarColorAlt) {
|
||||||
LaunchedEffect(isDarkTheme, systemBarColor, systemBarColorAlt) {
|
enableEdgeToEdge(
|
||||||
enableEdgeToEdge(
|
if (isDarkTheme) SystemBarStyle.dark(systemBarColor.toArgb())
|
||||||
if (isDarkTheme) SystemBarStyle.dark(systemBarColor.toArgb())
|
else SystemBarStyle.light(
|
||||||
else SystemBarStyle.light(systemBarColor.toArgb(), systemBarColorAlt.toArgb())
|
systemBarColor.toArgb(),
|
||||||
|
systemBarColorAlt.toArgb()
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
OverlayHost {
|
}
|
||||||
NavHost(
|
OverlayHost {
|
||||||
navController = navController,
|
NavHost(
|
||||||
startDestination = "settings",
|
navController = navController,
|
||||||
exitTransition = {
|
startDestination = "settings",
|
||||||
fadeOut() + scaleOut(targetScale = 0.5f)
|
exitTransition = {
|
||||||
},
|
fadeOut() + scaleOut(targetScale = 0.5f)
|
||||||
enterTransition = {
|
},
|
||||||
slideInHorizontally { it }
|
enterTransition = {
|
||||||
},
|
slideInHorizontally { it }
|
||||||
popEnterTransition = {
|
},
|
||||||
fadeIn() + scaleIn(initialScale = 0.5f)
|
popEnterTransition = {
|
||||||
},
|
fadeIn() + scaleIn(initialScale = 0.5f)
|
||||||
popExitTransition = {
|
},
|
||||||
slideOutHorizontally { it }
|
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") {
|
val id = it.arguments?.getString("id")?.let {
|
||||||
MainSettingsScreen()
|
UUID.fromString(it)
|
||||||
}
|
} ?: return@composable
|
||||||
composable("settings/appearance") {
|
ThemeSettingsScreen(id)
|
||||||
AppearanceSettingsScreen()
|
}
|
||||||
}
|
composable("settings/appearance/cards") {
|
||||||
composable("settings/homescreen") {
|
CardsSettingsScreen()
|
||||||
HomescreenSettingsScreen()
|
}
|
||||||
}
|
composable("settings/search") {
|
||||||
composable("settings/icons") {
|
SearchSettingsScreen()
|
||||||
IconsSettingsScreen()
|
}
|
||||||
}
|
composable("settings/gestures") {
|
||||||
composable("settings/appearance/themes") {
|
GestureSettingsScreen()
|
||||||
ThemesSettingsScreen()
|
}
|
||||||
}
|
composable("settings/search/unitconverter") {
|
||||||
composable(
|
UnitConverterSettingsScreen()
|
||||||
"settings/appearance/themes/{id}",
|
}
|
||||||
arguments = listOf(navArgument("id") {
|
composable("settings/search/wikipedia") {
|
||||||
nullable = false
|
WikipediaSettingsScreen()
|
||||||
})
|
}
|
||||||
) {
|
composable("settings/search/locations") {
|
||||||
val id = it.arguments?.getString("id")?.let {
|
LocationsSettingsScreen()
|
||||||
UUID.fromString(it)
|
}
|
||||||
} ?: return@composable
|
composable("settings/search/files") {
|
||||||
ThemeSettingsScreen(id)
|
FileSearchSettingsScreen()
|
||||||
}
|
}
|
||||||
composable("settings/appearance/cards") {
|
composable("settings/search/searchactions") {
|
||||||
CardsSettingsScreen()
|
SearchActionsSettingsScreen()
|
||||||
}
|
}
|
||||||
composable("settings/search") {
|
composable("settings/search/hiddenitems") {
|
||||||
SearchSettingsScreen()
|
HiddenItemsSettingsScreen()
|
||||||
}
|
}
|
||||||
composable("settings/gestures") {
|
composable("settings/search/tags") {
|
||||||
GestureSettingsScreen()
|
TagsSettingsScreen()
|
||||||
}
|
}
|
||||||
composable("settings/search/unitconverter") {
|
composable(ROUTE_WEATHER_INTEGRATION) {
|
||||||
UnitConverterSettingsScreen()
|
WeatherIntegrationSettingsScreen()
|
||||||
}
|
}
|
||||||
composable("settings/search/wikipedia") {
|
composable(ROUTE_MEDIA_INTEGRATION) {
|
||||||
WikipediaSettingsScreen()
|
MediaIntegrationSettingsScreen()
|
||||||
}
|
}
|
||||||
composable("settings/search/locations") {
|
composable("settings/favorites") {
|
||||||
LocationsSettingsScreen()
|
FavoritesSettingsScreen()
|
||||||
}
|
}
|
||||||
composable("settings/search/files") {
|
composable("settings/integrations") {
|
||||||
FileSearchSettingsScreen()
|
IntegrationsSettingsScreen()
|
||||||
}
|
}
|
||||||
composable("settings/search/searchactions") {
|
composable("settings/plugins") {
|
||||||
SearchActionsSettingsScreen()
|
PluginsSettingsScreen()
|
||||||
}
|
}
|
||||||
composable("settings/search/hiddenitems") {
|
composable("settings/plugins/{id}") {
|
||||||
HiddenItemsSettingsScreen()
|
PluginSettingsScreen(
|
||||||
}
|
it.arguments?.getString("id") ?: return@composable
|
||||||
composable("settings/search/tags") {
|
)
|
||||||
TagsSettingsScreen()
|
}
|
||||||
}
|
composable("settings/about") {
|
||||||
composable(ROUTE_WEATHER_INTEGRATION) {
|
AboutSettingsScreen()
|
||||||
WeatherIntegrationSettingsScreen()
|
}
|
||||||
}
|
composable("settings/about/buildinfo") {
|
||||||
composable(ROUTE_MEDIA_INTEGRATION) {
|
BuildInfoSettingsScreen()
|
||||||
MediaIntegrationSettingsScreen()
|
}
|
||||||
}
|
composable("settings/about/easteregg") {
|
||||||
composable("settings/favorites") {
|
EasterEggSettingsScreen()
|
||||||
FavoritesSettingsScreen()
|
}
|
||||||
}
|
composable("settings/debug") {
|
||||||
composable("settings/integrations") {
|
DebugSettingsScreen()
|
||||||
IntegrationsSettingsScreen()
|
}
|
||||||
}
|
composable("settings/backup") {
|
||||||
composable("settings/plugins") {
|
BackupSettingsScreen()
|
||||||
PluginsSettingsScreen()
|
}
|
||||||
}
|
composable("settings/debug/crashreporter") {
|
||||||
composable("settings/plugins/{id}") {
|
CrashReporterScreen()
|
||||||
PluginSettingsScreen(it.arguments?.getString("id") ?: return@composable)
|
}
|
||||||
}
|
composable("settings/debug/logs") {
|
||||||
composable("settings/about") {
|
LogScreen()
|
||||||
AboutSettingsScreen()
|
}
|
||||||
}
|
composable(
|
||||||
composable("settings/about/buildinfo") {
|
"settings/debug/crashreporter/report?fileName={fileName}",
|
||||||
BuildInfoSettingsScreen()
|
arguments = listOf(navArgument("fileName") {
|
||||||
}
|
nullable = false
|
||||||
composable("settings/about/easteregg") {
|
})
|
||||||
EasterEggSettingsScreen()
|
) {
|
||||||
}
|
val fileName = it.arguments?.getString("fileName")
|
||||||
composable("settings/debug") {
|
?.let {
|
||||||
DebugSettingsScreen()
|
URLDecoder.decode(it, "utf8")
|
||||||
}
|
}
|
||||||
composable("settings/backup") {
|
CrashReportScreen(fileName!!)
|
||||||
BackupSettingsScreen()
|
}
|
||||||
}
|
composable(
|
||||||
composable("settings/debug/crashreporter") {
|
"settings/license?library={libraryName}",
|
||||||
CrashReporterScreen()
|
arguments = listOf(navArgument("libraryName") {
|
||||||
}
|
nullable = true
|
||||||
composable("settings/debug/logs") {
|
})
|
||||||
LogScreen()
|
) {
|
||||||
}
|
val libraryName = it.arguments?.getString("libraryName")
|
||||||
composable(
|
val library = remember(libraryName) {
|
||||||
"settings/debug/crashreporter/report?fileName={fileName}",
|
if (libraryName == null) {
|
||||||
arguments = listOf(navArgument("fileName") {
|
AppLicense.get(this@SettingsActivity)
|
||||||
nullable = false
|
} else {
|
||||||
})
|
OpenSourceLicenses.first { it.name == libraryName }
|
||||||
) {
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import de.mm20.launcher2.preferences.ClockWidgetColors
|
|||||||
import de.mm20.launcher2.preferences.ClockWidgetStyle
|
import de.mm20.launcher2.preferences.ClockWidgetStyle
|
||||||
import de.mm20.launcher2.preferences.ui.ClockWidgetSettings
|
import de.mm20.launcher2.preferences.ui.ClockWidgetSettings
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
@ -20,7 +21,7 @@ class ClockWidgetSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
settings.setCompact(compact)
|
settings.setCompact(compact)
|
||||||
}
|
}
|
||||||
|
|
||||||
val availableClockStyles = settings.digital1.map {digital1 ->
|
val availableClockStyles = combine(settings.digital1, settings.custom) {digital1, custom ->
|
||||||
listOf(
|
listOf(
|
||||||
digital1,
|
digital1,
|
||||||
ClockWidgetStyle.Digital2,
|
ClockWidgetStyle.Digital2,
|
||||||
@ -28,6 +29,7 @@ class ClockWidgetSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
ClockWidgetStyle.Orbit,
|
ClockWidgetStyle.Orbit,
|
||||||
ClockWidgetStyle.Segment,
|
ClockWidgetStyle.Segment,
|
||||||
ClockWidgetStyle.Binary,
|
ClockWidgetStyle.Binary,
|
||||||
|
custom,
|
||||||
ClockWidgetStyle.Empty,
|
ClockWidgetStyle.Empty,
|
||||||
)
|
)
|
||||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
||||||
|
|||||||
@ -845,6 +845,7 @@
|
|||||||
<string name="widget_config_appwidget_resize_hint">Drag to resize</string>
|
<string name="widget_config_appwidget_resize_hint">Drag to resize</string>
|
||||||
<string name="widget_config_weather_integration_settings">Weather integration settings</string>
|
<string name="widget_config_weather_integration_settings">Weather integration settings</string>
|
||||||
<string name="widget_config_calendar_no_calendars">No calendars found</string>
|
<string name="widget_config_calendar_no_calendars">No calendars found</string>
|
||||||
|
<string name="widget_pick_widget">Pick widget</string>
|
||||||
<string name="app_widget_loading_failed">App widget failed to load.</string>
|
<string name="app_widget_loading_failed">App widget failed to load.</string>
|
||||||
<string name="widget_config_music_integration_settings">Media control integration settings</string>
|
<string name="widget_config_music_integration_settings">Media control integration settings</string>
|
||||||
<string name="note_widget_link_file">Link to file</string>
|
<string name="note_widget_link_file">Link to file</string>
|
||||||
@ -909,6 +910,7 @@
|
|||||||
<string name="clock_style_analog">Hands</string>
|
<string name="clock_style_analog">Hands</string>
|
||||||
<string name="clock_style_segment">7-segment</string>
|
<string name="clock_style_segment">7-segment</string>
|
||||||
<string name="clock_style_empty">No clock</string>
|
<string name="clock_style_empty">No clock</string>
|
||||||
|
<string name="clock_style_custom">Custom widget</string>
|
||||||
<string name="clock_variant_standard">Standard</string>
|
<string name="clock_variant_standard">Standard</string>
|
||||||
<string name="clock_variant_outlined">Outlined</string>
|
<string name="clock_variant_outlined">Outlined</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -30,6 +30,7 @@ data class LauncherSettingsData internal constructor(
|
|||||||
@SerialName("clockWidgetStyle2")
|
@SerialName("clockWidgetStyle2")
|
||||||
internal val clockWidgetStyle: ClockWidgetStyleEnum = ClockWidgetStyleEnum.Digital1,
|
internal val clockWidgetStyle: ClockWidgetStyleEnum = ClockWidgetStyleEnum.Digital1,
|
||||||
val clockWidgetDigital1: ClockWidgetStyle.Digital1 = ClockWidgetStyle.Digital1(),
|
val clockWidgetDigital1: ClockWidgetStyle.Digital1 = ClockWidgetStyle.Digital1(),
|
||||||
|
val clockWidgetCustom: ClockWidgetStyle.Custom = ClockWidgetStyle.Custom(),
|
||||||
val clockWidgetColors: ClockWidgetColors = ClockWidgetColors.Auto,
|
val clockWidgetColors: ClockWidgetColors = ClockWidgetColors.Auto,
|
||||||
val clockWidgetShowSeconds: Boolean = false,
|
val clockWidgetShowSeconds: Boolean = false,
|
||||||
val clockWidgetUseThemeColor: Boolean = false,
|
val clockWidgetUseThemeColor: Boolean = false,
|
||||||
@ -192,6 +193,7 @@ internal enum class ClockWidgetStyleEnum {
|
|||||||
Binary,
|
Binary,
|
||||||
Segment,
|
Segment,
|
||||||
Empty,
|
Empty,
|
||||||
|
Custom,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -234,6 +236,10 @@ sealed interface ClockWidgetStyle {
|
|||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("empty")
|
@SerialName("empty")
|
||||||
data object Empty : ClockWidgetStyle
|
data object Empty : ClockWidgetStyle
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("custom")
|
||||||
|
data class Custom(val widgetId: Int? = null) : ClockWidgetStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|||||||
@ -99,17 +99,22 @@ class ClockWidgetSettings internal constructor(
|
|||||||
ClockWidgetStyleEnum.Binary -> ClockWidgetStyle.Binary
|
ClockWidgetStyleEnum.Binary -> ClockWidgetStyle.Binary
|
||||||
ClockWidgetStyleEnum.Segment -> ClockWidgetStyle.Segment
|
ClockWidgetStyleEnum.Segment -> ClockWidgetStyle.Segment
|
||||||
ClockWidgetStyleEnum.Empty -> ClockWidgetStyle.Empty
|
ClockWidgetStyleEnum.Empty -> ClockWidgetStyle.Empty
|
||||||
|
ClockWidgetStyleEnum.Custom -> it.clockWidgetCustom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val digital1: Flow<ClockWidgetStyle.Digital1>
|
val digital1: Flow<ClockWidgetStyle.Digital1>
|
||||||
get() = launcherDataStore.data.map { it.clockWidgetDigital1 }
|
get() = launcherDataStore.data.map { it.clockWidgetDigital1 }
|
||||||
|
|
||||||
|
val custom: Flow<ClockWidgetStyle.Custom>
|
||||||
|
get() = launcherDataStore.data.map { it.clockWidgetCustom }
|
||||||
|
|
||||||
fun setClockStyle(clockStyle: ClockWidgetStyle) {
|
fun setClockStyle(clockStyle: ClockWidgetStyle) {
|
||||||
launcherDataStore.update {
|
launcherDataStore.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
clockWidgetStyle = clockStyle.enumValue,
|
clockWidgetStyle = clockStyle.enumValue,
|
||||||
clockWidgetDigital1 = if (clockStyle is ClockWidgetStyle.Digital1) clockStyle else it.clockWidgetDigital1,
|
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.Binary -> ClockWidgetStyleEnum.Binary
|
||||||
is ClockWidgetStyle.Segment -> ClockWidgetStyleEnum.Segment
|
is ClockWidgetStyle.Segment -> ClockWidgetStyleEnum.Segment
|
||||||
is ClockWidgetStyle.Empty -> ClockWidgetStyleEnum.Empty
|
is ClockWidgetStyle.Empty -> ClockWidgetStyleEnum.Empty
|
||||||
|
is ClockWidgetStyle.Custom -> ClockWidgetStyleEnum.Custom
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user