From 7056c5963a475d0fc06e8f693a313583407b9121 Mon Sep 17 00:00:00 2001
From: MM20 <15646950+MM2-0@users.noreply.github.com>
Date: Sat, 13 Apr 2024 18:26:59 +0200
Subject: [PATCH] Add option to use theme colors in app widgets and use light
background variant when appropriate
---
.../launcher/sheets/ConfigureWidgetSheet.kt | 25 +-
.../ui/launcher/widgets/WidgetItem.kt | 62 +----
.../ui/launcher/widgets/clock/ClockWidget.kt | 2 +-
.../widgets/clock/clocks/CustomClock.kt | 10 +-
.../ui/launcher/widgets/external/AppWidget.kt | 228 +++++++-----------
.../widgets/external/AppWidgetHost.kt | 167 +++++++++++++
core/i18n/src/main/res/values-ca/strings.xml | 2 +-
core/i18n/src/main/res/values-cs/strings.xml | 2 +-
core/i18n/src/main/res/values-da/strings.xml | 2 +-
core/i18n/src/main/res/values-de/strings.xml | 2 +-
core/i18n/src/main/res/values-es/strings.xml | 2 +-
core/i18n/src/main/res/values-hu/strings.xml | 2 +-
core/i18n/src/main/res/values-it/strings.xml | 2 +-
core/i18n/src/main/res/values/strings.xml | 2 +-
.../de/mm20/launcher2/widgets/AppWidget.kt | 1 +
15 files changed, 288 insertions(+), 223 deletions(-)
create mode 100644 app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/external/AppWidgetHost.kt
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 49702f5b..5dd31be9 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
@@ -2,7 +2,6 @@ package de.mm20.launcher2.ui.launcher.sheets
import android.app.Activity
import android.app.ActivityOptions
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.Intent
@@ -41,6 +40,7 @@ import androidx.compose.material.icons.rounded.OpenInNew
import androidx.compose.material.icons.rounded.UnfoldMore
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Divider
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
@@ -87,7 +87,9 @@ import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.preferences.CheckboxPreference
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
-import de.mm20.launcher2.ui.launcher.widgets.external.ExternalWidget
+import de.mm20.launcher2.ui.launcher.widgets.external.AppWidgetHost
+import de.mm20.launcher2.ui.locals.LocalDarkTheme
+import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper
import de.mm20.launcher2.ui.settings.SettingsActivity
import de.mm20.launcher2.widgets.AppWidget
import de.mm20.launcher2.widgets.CalendarWidget
@@ -344,12 +346,14 @@ fun ColumnScope.ConfigureAppWidget(
.clip(MaterialTheme.shapes.medium)
.background(MaterialTheme.colorScheme.surfaceVariant)
) {
- ExternalWidget(
+ AppWidgetHost(
widgetInfo = widgetInfo,
widgetId = widget.config.widgetId,
modifier = Modifier.fillMaxWidth(),
height = widget.config.height + dragDelta,
borderless = widget.config.borderless,
+ useThemeColors = widget.config.themeColors,
+ onLightBackground = (!LocalDarkTheme.current && widget.config.background) || LocalPreferDarkContentOverWallpaper.current
)
}
val density = LocalDensity.current
@@ -422,7 +426,7 @@ fun ColumnScope.ConfigureAppWidget(
onValueChange = {
val intValue = it.toIntOrNull()
if (intValue == null) textFieldValue = ""
- else if (intValue in 1..500) {
+ else if (intValue in 1..1000) {
onWidgetUpdated(
widget.copy(
config = widget.config.copy(
@@ -458,7 +462,7 @@ fun ColumnScope.ConfigureAppWidget(
onWidgetUpdated(widget.copy(config = widget.config.copy(borderless = it)))
}
)
- Divider()
+ HorizontalDivider()
SwitchPreference(
title = stringResource(R.string.widget_config_appwidget_background),
iconPadding = false,
@@ -467,6 +471,17 @@ fun ColumnScope.ConfigureAppWidget(
onWidgetUpdated(widget.copy(config = widget.config.copy(background = it)))
}
)
+ if (isAtLeastApiLevel(31)) {
+ HorizontalDivider()
+ SwitchPreference(
+ title = stringResource(R.string.widget_use_theme_colors),
+ iconPadding = false,
+ value = widget.config.themeColors,
+ onValueChanged = {
+ onWidgetUpdated(widget.copy(config = widget.config.copy(themeColors = it)))
+ }
+ )
+ }
}
}
if (isAtLeastApiLevel(28) && widgetInfo.widgetFeatures and AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE != 0) {
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 64563529..c3d09f63 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
@@ -1,6 +1,5 @@
package de.mm20.launcher2.ui.launcher.widgets
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
@@ -43,7 +42,7 @@ import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.launcher.sheets.ConfigureWidgetSheet
import de.mm20.launcher2.ui.launcher.sheets.WidgetPickerSheet
import de.mm20.launcher2.ui.launcher.widgets.calendar.CalendarWidget
-import de.mm20.launcher2.ui.launcher.widgets.external.ExternalWidget
+import de.mm20.launcher2.ui.launcher.widgets.external.AppWidget
import de.mm20.launcher2.ui.launcher.widgets.favorites.FavoritesWidget
import de.mm20.launcher2.ui.launcher.widgets.music.MusicWidget
import de.mm20.launcher2.ui.launcher.widgets.notes.NotesWidget
@@ -174,60 +173,11 @@ fun WidgetItem(
}
is AppWidget -> {
- val widgetInfo = remember(widget.config.widgetId) {
- AppWidgetManager.getInstance(context)
- .getAppWidgetInfo(widget.config.widgetId)
- }
- if (widgetInfo == null) {
- var replaceWidget by rememberSaveable {
- mutableStateOf(false)
- }
- Banner(
- modifier = Modifier.padding(16.dp),
- text = stringResource(R.string.app_widget_loading_failed),
- icon = Icons.Rounded.Warning,
- secondaryAction = {
- OutlinedButton(onClick = onWidgetRemove) {
- Text(stringResource(R.string.widget_action_remove))
- }
- },
- primaryAction = {
- Button(onClick = { replaceWidget = true }) {
- Text(stringResource(R.string.widget_action_replace))
- }
- }
- )
- if (replaceWidget) {
- WidgetPickerSheet(
- onDismiss = { replaceWidget = false },
- onWidgetSelected = {
- val updatedWidget = when (it) {
- is AppWidget -> widget.copy(
- config = widget.config.copy(
- widgetId = it.config.widgetId
- )
- )
-
- is WeatherWidget -> it.copy(id = widget.id)
- is MusicWidget -> it.copy(id = widget.id)
- is CalendarWidget -> it.copy(id = widget.id)
- is FavoritesWidget -> it.copy(id = widget.id)
- is NotesWidget -> it.copy(id = widget.id)
- }
- onWidgetUpdate(updatedWidget)
- replaceWidget = false
- }
- )
- }
- } else {
- ExternalWidget(
- widgetId = widget.config.widgetId,
- widgetInfo = widgetInfo,
- modifier = Modifier.fillMaxWidth(),
- height = widget.config.height,
- borderless = widget.config.borderless,
- )
- }
+ AppWidget(
+ widget,
+ onWidgetUpdate = onWidgetUpdate,
+ onWidgetRemove = onWidgetRemove,
+ )
}
}
}
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 a8629208..8849f5ed 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
@@ -464,7 +464,7 @@ fun ConfigureClockWidgetSheet(
modifier = Modifier.fillMaxWidth()
) {
SwitchPreference(
- title = stringResource(R.string.preference_clock_widget_use_theme_color),
+ title = stringResource(R.string.widget_use_theme_colors),
icon = Icons.Rounded.ColorLens,
value = useAccentColor,
onValueChanged = {
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
index 3bd47108..5b22d662 100644
--- 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
@@ -2,20 +2,14 @@ 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
+import de.mm20.launcher2.ui.launcher.widgets.external.AppWidgetHost
@Composable
fun CustomClock(
@@ -35,7 +29,7 @@ fun CustomClock(
.getAppWidgetInfo(widgetId)
}
if (widgetInfo != null) {
- ExternalWidget(
+ AppWidgetHost(
widgetInfo = widgetInfo,
widgetId = widgetId,
height = if (compact) 64 else 200,
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/external/AppWidget.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/external/AppWidget.kt
index c22d8af6..96c3e609 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/external/AppWidget.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/external/AppWidget.kt
@@ -1,165 +1,103 @@
package de.mm20.launcher2.ui.launcher.widgets.external
import android.appwidget.AppWidgetHost
-import android.appwidget.AppWidgetProviderInfo
-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.annotation.RequiresApi
-import androidx.compose.foundation.layout.BoxWithConstraints
+import android.appwidget.AppWidgetManager
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.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.Warning
+import androidx.compose.material3.Button
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.key
+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.graphics.toArgb
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.viewinterop.AndroidView
-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.R
+import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.ktx.toPixels
-import palettes.TonalPalette
+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 de.mm20.launcher2.widgets.CalendarWidget
+import de.mm20.launcher2.widgets.FavoritesWidget
+import de.mm20.launcher2.widgets.MusicWidget
+import de.mm20.launcher2.widgets.NotesWidget
+import de.mm20.launcher2.widgets.WeatherWidget
+import de.mm20.launcher2.widgets.Widget
import kotlin.math.roundToInt
@Composable
-fun ExternalWidget(
- widgetInfo: AppWidgetProviderInfo,
- widgetId: Int,
- height: Int,
- modifier: Modifier = Modifier,
- borderless: Boolean = false,
- useThemeColors: Boolean = false,
- onLightBackground: Boolean = false,
+fun AppWidget(
+ widget: AppWidget,
+ onWidgetUpdate: (Widget) -> Unit,
+ onWidgetRemove: () -> Unit,
) {
- val padding = if (borderless) 0 else 8.dp.toPixels().roundToInt()
+ val context = LocalContext.current
- val colorScheme = MaterialTheme.colorScheme
- val appWidgetHost = LocalAppWidgetHost.current
+ val lightBackground = (!LocalDarkTheme.current && widget.config.background) || LocalPreferDarkContentOverWallpaper.current
- BoxWithConstraints {
- val maxWidth = maxWidth
- key(widgetId) {
- AndroidView(
- modifier = modifier
- .height(height.dp),
- factory = {
- val view = appWidgetHost.createView(it.applicationContext, widgetId, widgetInfo)
- enableNestedScroll(view)
- return@AndroidView view
- },
- update = {
- if (isAtLeastApiLevel(31)) {
- if (useThemeColors) {
- val colorMapping = getColorMapping(colorScheme)
- it.setColorResources(colorMapping)
- } else {
- it.resetColorResources()
- }
+ val widgetInfo = remember(widget.config.widgetId) {
+ AppWidgetManager.getInstance(context)
+ .getAppWidgetInfo(widget.config.widgetId)
+ }
+ if (widgetInfo == null) {
+ var replaceWidget by rememberSaveable {
+ mutableStateOf(false)
+ }
+ Banner(
+ modifier = Modifier.padding(16.dp),
+ text = stringResource(R.string.app_widget_loading_failed),
+ icon = Icons.Rounded.Warning,
+ secondaryAction = {
+ OutlinedButton(onClick = onWidgetRemove) {
+ Text(stringResource(R.string.widget_action_remove))
+ }
+ },
+ primaryAction = {
+ Button(onClick = { replaceWidget = true }) {
+ Text(stringResource(R.string.widget_action_replace))
+ }
+ }
+ )
+ if (replaceWidget) {
+ WidgetPickerSheet(
+ onDismiss = { replaceWidget = false },
+ onWidgetSelected = {
+ val updatedWidget = when (it) {
+ is AppWidget -> widget.copy(
+ config = widget.config.copy(
+ widgetId = it.config.widgetId
+ )
+ )
+
+ is WeatherWidget -> it.copy(id = widget.id)
+ is MusicWidget -> it.copy(id = widget.id)
+ is CalendarWidget -> it.copy(id = widget.id)
+ is FavoritesWidget -> it.copy(id = widget.id)
+ is NotesWidget -> it.copy(id = widget.id)
}
-
- if (isAtLeastApiLevel(29)) {
- it.setOnLightBackground(onLightBackground)
- }
-
- it.updateAppWidgetSize(
- null,
- maxWidth.value.roundToInt(),
- height,
- maxWidth.value.roundToInt(),
- height,
- )
- it.setPadding(padding)
+ onWidgetUpdate(updatedWidget)
+ replaceWidget = false
}
)
}
+ } else {
+ AppWidgetHost(
+ widgetId = widget.config.widgetId,
+ widgetInfo = widgetInfo,
+ modifier = Modifier.fillMaxWidth(),
+ height = widget.config.height,
+ borderless = widget.config.borderless,
+ useThemeColors = widget.config.themeColors,
+ onLightBackground = lightBackground,
+ )
}
-}
-
-private fun enableNestedScroll(view: View) {
- if (view is ViewGroup) {
- for (child in view.iterator()) {
- enableNestedScroll(child)
- }
- }
- 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/launcher/widgets/external/AppWidgetHost.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/external/AppWidgetHost.kt
new file mode 100644
index 00000000..e84a9e79
--- /dev/null
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/external/AppWidgetHost.kt
@@ -0,0 +1,167 @@
+package de.mm20.launcher2.ui.launcher.widgets.external
+
+import android.appwidget.AppWidgetHost
+import android.appwidget.AppWidgetProviderInfo
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import android.util.SparseIntArray
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ListView
+import android.widget.ScrollView
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.layout.BoxWithConstraints
+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.ui.Modifier
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+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 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
+ .height(height.dp),
+ factory = {
+ val view = appWidgetHost.createView(it.applicationContext, widgetId, widgetInfo)
+ enableNestedScroll(view)
+ return@AndroidView view
+ },
+ update = {
+ if (isAtLeastApiLevel(29)) {
+ it.setOnLightBackground(onLightBackground)
+ }
+ if (isAtLeastApiLevel(31)) {
+ if (useThemeColors) {
+ val colorMapping = getColorMapping(colorScheme)
+ it.setColorResources(colorMapping)
+ } else {
+ it.resetColorResources()
+ }
+ }
+
+ it.updateAppWidgetSize(
+ null,
+ maxWidth.value.roundToInt(),
+ height,
+ maxWidth.value.roundToInt(),
+ height,
+ )
+ it.setPadding(padding)
+ // Workaround to force update of the widget view
+ it.updateAppWidgetOptions(Bundle())
+ }
+ )
+ }
+ }
+}
+
+private fun enableNestedScroll(view: View) {
+ if (view is ViewGroup) {
+ for (child in view.iterator()) {
+ enableNestedScroll(child)
+ }
+ }
+ 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/core/i18n/src/main/res/values-ca/strings.xml b/core/i18n/src/main/res/values-ca/strings.xml
index e661ecbc..79291357 100644
--- a/core/i18n/src/main/res/values-ca/strings.xml
+++ b/core/i18n/src/main/res/values-ca/strings.xml
@@ -710,7 +710,7 @@
Mostra les aplicacions del perfil de treball en una pestanya independent
Aplicacions del perfil de treball separades
Mostra els segons
- Utilitzeu el color del tema
+ Utilitzeu el color del tema
Negreta
Simple
Orbit
diff --git a/core/i18n/src/main/res/values-cs/strings.xml b/core/i18n/src/main/res/values-cs/strings.xml
index 528bccbf..b037be8c 100644
--- a/core/i18n/src/main/res/values-cs/strings.xml
+++ b/core/i18n/src/main/res/values-cs/strings.xml
@@ -715,7 +715,7 @@
Oddělit aplikace pracovního profilu
Zobrazit aplikace pracovního profilu v oddělené kartě
Zobrazit sekundy
- Použít barvu motivu
+ Použít barvu motivu
Tučné
Jednoduché
Kruh
diff --git a/core/i18n/src/main/res/values-da/strings.xml b/core/i18n/src/main/res/values-da/strings.xml
index f3fe69ea..ffcbc240 100644
--- a/core/i18n/src/main/res/values-da/strings.xml
+++ b/core/i18n/src/main/res/values-da/strings.xml
@@ -687,7 +687,7 @@
Åbn hjemmeside
Giv tilladelse til placering for at søge i nærheden.
Vis sekunder
- Brug temafarve
+ Brug temafarve
Steder
Søgeradius
Søg i det lokale område efter butikker og andre steder
diff --git a/core/i18n/src/main/res/values-de/strings.xml b/core/i18n/src/main/res/values-de/strings.xml
index a6bc7b7f..d8979d59 100644
--- a/core/i18n/src/main/res/values-de/strings.xml
+++ b/core/i18n/src/main/res/values-de/strings.xml
@@ -710,7 +710,7 @@
Apps im Arbeitsprofil in einem separaten Tab anzeigen
Dieses Element existiert nicht mehr.
Sekunden anzeigen
- Design-Farbe verwenden
+ Design-Farbe verwenden
Fett
Einfach
Orbit
diff --git a/core/i18n/src/main/res/values-es/strings.xml b/core/i18n/src/main/res/values-es/strings.xml
index dd2de2cb..fbf01ac4 100644
--- a/core/i18n/src/main/res/values-es/strings.xml
+++ b/core/i18n/src/main/res/values-es/strings.xml
@@ -693,7 +693,7 @@
Separar apps del perfil de trabajo
Mostrar apps del perfil de trabajo en una pestaña separada
Mostrar segundos
- Usar color del tema
+ Usar color del tema
Radio de búsqueda
Mostrar tu ubicación en el mapa
Negrita
diff --git a/core/i18n/src/main/res/values-hu/strings.xml b/core/i18n/src/main/res/values-hu/strings.xml
index 988e1715..32bc0ec4 100644
--- a/core/i18n/src/main/res/values-hu/strings.xml
+++ b/core/i18n/src/main/res/values-hu/strings.xml
@@ -709,7 +709,7 @@
Ez az elem már nem létezik.
7-szegmenses
Általános
- Színséma használata
+ Színséma használata
Lap elkülönítés az alkalmazásokhoz
Külön lapon mutatja az alkalmazásokat
Másodperc megjelenítése
diff --git a/core/i18n/src/main/res/values-it/strings.xml b/core/i18n/src/main/res/values-it/strings.xml
index a2cd3ec8..45466057 100644
--- a/core/i18n/src/main/res/values-it/strings.xml
+++ b/core/i18n/src/main/res/values-it/strings.xml
@@ -690,7 +690,7 @@
Apertura %1$s
Nascondi luoghi senza categoria
Mostra secondi
- Usa colore del tema
+ Usa colore del tema
Luoghi
Raggio di ricerca
Cerca nella zona negozi e altri luoghi
diff --git a/core/i18n/src/main/res/values/strings.xml b/core/i18n/src/main/res/values/strings.xml
index 61036e9d..5ae5a124 100644
--- a/core/i18n/src/main/res/values/strings.xml
+++ b/core/i18n/src/main/res/values/strings.xml
@@ -601,7 +601,7 @@
Default
Compact
Show seconds
- Use theme color
+ Use theme color
Fill screen height
Insert extra space above the clock to fill the entire screen height
Color
diff --git a/data/widgets/src/main/java/de/mm20/launcher2/widgets/AppWidget.kt b/data/widgets/src/main/java/de/mm20/launcher2/widgets/AppWidget.kt
index 98cf5259..3dd22bc0 100644
--- a/data/widgets/src/main/java/de/mm20/launcher2/widgets/AppWidget.kt
+++ b/data/widgets/src/main/java/de/mm20/launcher2/widgets/AppWidget.kt
@@ -17,6 +17,7 @@ data class AppWidgetConfig(
val height: Int,
val borderless: Boolean = false,
val background: Boolean = true,
+ val themeColors: Boolean = true,
)
data class AppWidget(