Allow resizing of custom clock widgets

This commit is contained in:
MM20 2024-04-19 22:01:40 +02:00
parent 64002c9f38
commit 38c048ec03
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
6 changed files with 481 additions and 204 deletions

View File

@ -384,7 +384,7 @@ fun ColumnScope.ConfigureAppWidget(
val minHeight = if (widgetInfo.minResizeHeight in 1..widgetInfo.minHeight) { val minHeight = if (widgetInfo.minResizeHeight in 1..widgetInfo.minHeight) {
widgetInfo.minResizeHeight.toDp() widgetInfo.minResizeHeight.toDp()
} else { } else {
widgetInfo.minHeight.toDp() widgetInfo.minHeight.toDp()
} }
DragResizeHandle( DragResizeHandle(

View File

@ -163,7 +163,7 @@ fun ClockWidget(
Box( Box(
modifier = Modifier modifier = Modifier
.then(if (fillScreenHeight) Modifier.weight(1f) else Modifier) .then(if (fillScreenHeight) Modifier.weight(1f) else Modifier)
.fillMaxWidth(), .fillMaxWidth().padding(horizontal = 24.dp),
contentAlignment = when (alignment) { contentAlignment = when (alignment) {
ClockWidgetAlignment.Center -> Alignment.Center ClockWidgetAlignment.Center -> Alignment.Center
ClockWidgetAlignment.Top -> Alignment.TopCenter ClockWidgetAlignment.Top -> Alignment.TopCenter
@ -408,6 +408,7 @@ fun ConfigureClockWidgetSheet(
styles = availableStyles, styles = availableStyles,
compact = compact!!, compact = compact!!,
colors = color!!, colors = color!!,
themeColors = useAccentColor,
selected = style, selected = style,
onSelect = { onSelect = {
viewModel.setClockStyle(it) viewModel.setClockStyle(it)

View File

@ -5,34 +5,49 @@ import android.app.ActivityOptions
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.util.Log import androidx.compose.animation.AnimatedContent
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
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowDropDown import androidx.compose.material.icons.rounded.ArrowDropDown
import androidx.compose.material.icons.rounded.Check import androidx.compose.material.icons.rounded.CheckCircle
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.CropFree
import androidx.compose.material.icons.rounded.Done
import androidx.compose.material.icons.rounded.PhotoSizeSelectSmall
import androidx.compose.material.icons.rounded.RadioButtonUnchecked
import androidx.compose.material.icons.rounded.RestartAlt
import androidx.compose.material.icons.rounded.Settings import androidx.compose.material.icons.rounded.Settings
import androidx.compose.material.icons.rounded.SwapHoriz
import androidx.compose.material.icons.rounded.Tune import androidx.compose.material.icons.rounded.Tune
import androidx.compose.material.icons.rounded.Widgets 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
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
@ -48,16 +63,26 @@ 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
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.coerceAtMost
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.isUnspecified
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import de.mm20.launcher2.ktx.isAtLeastApiLevel
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.BuildConfig
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.base.LocalAppWidgetHost import de.mm20.launcher2.ui.base.LocalAppWidgetHost
import de.mm20.launcher2.ui.component.DragResizeHandle
import de.mm20.launcher2.ui.ktx.toDp
import de.mm20.launcher2.ui.launcher.sheets.WidgetPickerSheet import de.mm20.launcher2.ui.launcher.sheets.WidgetPickerSheet
import de.mm20.launcher2.ui.launcher.widgets.external.AppWidgetHost
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 de.mm20.launcher2.widgets.AppWidget
@ -68,235 +93,315 @@ fun WatchFaceSelector(
styles: List<ClockWidgetStyle>, styles: List<ClockWidgetStyle>,
compact: Boolean, compact: Boolean,
colors: ClockWidgetColors, colors: ClockWidgetColors,
themeColors: Boolean,
selected: ClockWidgetStyle?, selected: ClockWidgetStyle?,
onSelect: (ClockWidgetStyle) -> Unit, onSelect: (ClockWidgetStyle) -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
var showWidgetPicker by rememberSaveable { mutableStateOf(false) } var showWidgetPicker by rememberSaveable { mutableStateOf(false) }
var resizeCustomWidget by remember { mutableStateOf(false) }
val lightBackground =
colors == ClockWidgetColors.Dark || colors == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current
Surface( Surface(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 16.dp, horizontal = 32.dp), .padding(vertical = 16.dp, horizontal = 0.dp),
color = if (colors == ClockWidgetColors.Dark || colors == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current) { color = if (lightBackground) {
if (LocalDarkTheme.current) MaterialTheme.colorScheme.inverseSurface else MaterialTheme.colorScheme.surfaceContainer if (LocalDarkTheme.current) MaterialTheme.colorScheme.inverseSurface else MaterialTheme.colorScheme.surfaceContainer
} else { } else {
if (LocalDarkTheme.current) MaterialTheme.colorScheme.surfaceContainer else MaterialTheme.colorScheme.inverseSurface if (LocalDarkTheme.current) MaterialTheme.colorScheme.surfaceContainer else MaterialTheme.colorScheme.inverseSurface
}, },
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
) { ) {
Column( AnimatedContent(resizeCustomWidget) { resize ->
modifier = Modifier, if (resize && selected is ClockWidgetStyle.Custom) {
) { ResizeCustomWidget(
val pagerState = rememberPagerState( style = selected,
initialPage = styles.indexOfFirst { it.javaClass == selected?.javaClass } compact = compact,
.coerceAtLeast(0), themeColors = themeColors,
lightBackground = lightBackground,
onChange = { onSelect(it) },
onExit = { resizeCustomWidget = false }
)
return@AnimatedContent
}
Column(
modifier = Modifier,
) { ) {
styles.size val pagerState = rememberPagerState(
} initialPage = styles.indexOfFirst { it.javaClass == selected?.javaClass }
.coerceAtLeast(0),
LaunchedEffect(pagerState.currentPage) {
val newStyle = styles[pagerState.currentPage]
if (newStyle.javaClass == selected?.javaClass) return@LaunchedEffect
onSelect(newStyle)
}
val scope = rememberCoroutineScope()
Box {
androidx.compose.animation.AnimatedVisibility(
selected is ClockWidgetStyle.Digital1 || selected is ClockWidgetStyle.Custom,
modifier = Modifier
.align(Alignment.TopEnd)
.zIndex(1f),
enter = scaleIn(),
exit = scaleOut(),
) { ) {
var showStyleSettings by remember { mutableStateOf(false) } styles.size
IconButton( }
onClick = { showStyleSettings = true },
LaunchedEffect(pagerState.currentPage) {
val newStyle = styles[pagerState.currentPage]
if (newStyle.javaClass == selected?.javaClass) return@LaunchedEffect
onSelect(newStyle)
}
val scope = rememberCoroutineScope()
Box {
androidx.compose.animation.AnimatedVisibility(
selected is ClockWidgetStyle.Digital1 || (selected is ClockWidgetStyle.Custom && selected.widgetId != null),
modifier = Modifier modifier = Modifier
.padding(4.dp) .align(Alignment.TopEnd)
.zIndex(1f),
enter = scaleIn(),
exit = scaleOut(),
) { ) {
Icon(Icons.Rounded.Tune, null) var showStyleSettings by remember { mutableStateOf(false) }
DropdownMenu( IconButton(
expanded = showStyleSettings, onClick = { showStyleSettings = true },
onDismissRequest = { showStyleSettings = false }) { modifier = Modifier
if (selected is ClockWidgetStyle.Digital1) { .padding(4.dp)
DropdownMenuItem( ) {
text = { Text(stringResource(R.string.clock_variant_outlined)) }, Icon(Icons.Rounded.Tune, null)
leadingIcon = { DropdownMenu(
if (selected.outlined) { expanded = showStyleSettings,
Icon(Icons.Rounded.Check, null) onDismissRequest = { showStyleSettings = false }) {
} if (selected is ClockWidgetStyle.Digital1) {
},
onClick = {
onSelect(selected.copy(outlined = !selected.outlined))
}
)
}
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( DropdownMenuItem(
text = { Text(stringResource(R.string.widget_config_appwidget_configure)) }, text = { Text(stringResource(R.string.clock_variant_outlined)) },
leadingIcon = { leadingIcon = {
Icon(Icons.Rounded.Settings, null) Icon(
if (selected.outlined) Icons.Rounded.CheckCircle
else Icons.Rounded.RadioButtonUnchecked,
null
)
}, },
onClick = { onClick = {
appWidgetHost.startAppWidgetConfigureActivityForResult( onSelect(selected.copy(outlined = !selected.outlined))
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()
}
)
} }
) )
} }
} if (selected is ClockWidgetStyle.Custom) {
} DropdownMenuItem(
} text = { Text(stringResource(R.string.widget_pick_widget)) },
} leadingIcon = {
Icon(Icons.Rounded.SwapHoriz, null)
val darkColors = colors == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current || colors == ClockWidgetColors.Dark },
onClick = {
CompositionLocalProvider( showWidgetPicker = true
LocalContentColor provides if (darkColors) { showStyleSettings = false
Color(0, 0, 0, 180) }
} else { )
Color.White DropdownMenuItem(
}, leadingIcon = {
) { Icon(Icons.Rounded.PhotoSizeSelectSmall, null)
},
HorizontalPager( text = { Text(stringResource(R.string.widget_config_appwidget_resize)) },
modifier = Modifier.animateContentSize(), onClick = { resizeCustomWidget = true }
state = pagerState, )
verticalAlignment = Alignment.Top, val widget = remember(selected.widgetId) {
) { pageIndex -> val id = selected.widgetId ?: return@remember null
Box( AppWidgetManager.getInstance(context)
modifier = Modifier .getAppWidgetInfo(id)
.fillMaxWidth() }
.padding(top = 24.dp, bottom = 8.dp), val appWidgetHost = LocalAppWidgetHost.current
contentAlignment = Alignment.TopCenter, if (widget?.configure != null) {
) { DropdownMenuItem(
val currentPageStyle = styles[pageIndex] text = { Text(stringResource(R.string.widget_config_appwidget_configure)) },
if (currentPageStyle.javaClass == selected?.javaClass) { leadingIcon = {
Clock(selected, compact, darkColors) Icon(Icons.Rounded.Settings, null)
} else { },
Clock(currentPageStyle, compact, darkColors) onClick = {
} appWidgetHost.startAppWidgetConfigureActivityForResult(
} context as Activity,
} selected.widgetId ?: return@DropdownMenuItem,
0,
} 0,
} if (Build.VERSION.SDK_INT < 34) {
null
Row( } else {
verticalAlignment = Alignment.CenterVertically ActivityOptions.makeBasic()
) { .setPendingIntentBackgroundActivityStartMode(
IconButton( ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
enabled = pagerState.currentPage > 0, )
onClick = { .setPendingIntentCreatorBackgroundActivityStartMode(
scope.launch { ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
pagerState.animateScrollToPage( )
pagerState.currentPage - 1, .toBundle()
) }
} )
}) { }
Icon(Icons.Rounded.ChevronLeft, null) )
} }
var showStyleDropdown by remember { mutableStateOf(false) } if (BuildConfig.DEBUG) {
TextButton( DropdownMenuItem(
modifier = Modifier leadingIcon = {
.weight(1f) Icon(Icons.Rounded.RestartAlt, null)
.padding(horizontal = 16.dp), },
onClick = { showStyleDropdown = true }, text = { Text("Reset") },
contentPadding = PaddingValues( onClick = {
top = 8.dp, val widgetId = selected.widgetId
bottom = 8.dp, if (widgetId != null) {
start = 16.dp, appWidgetHost.deleteAppWidgetId(widgetId)
end = 12.dp, }
), onSelect(
colors = ButtonDefaults.textButtonColors( ClockWidgetStyle.Custom()
contentColor = LocalContentColor.current, )
), }
) {
Text(
text = getClockStyleName(
context,
styles[pagerState.currentPage]
),
textAlign = TextAlign.Center,
)
Icon(
Icons.Rounded.ArrowDropDown,
null,
modifier = Modifier
.padding(ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
)
DropdownMenu(
expanded = showStyleDropdown,
onDismissRequest = { showStyleDropdown = false }
) {
for (style in styles.withIndex()) {
DropdownMenuItem(
onClick = {
scope.launch {
pagerState.animateScrollToPage(
style.index,
) )
} }
showStyleDropdown = false
},
text = {
Text(
text = getClockStyleName(context, style.value),
)
} }
) }
} }
} }
val darkColors =
colors == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current || colors == ClockWidgetColors.Dark
CompositionLocalProvider(
LocalContentColor provides if (darkColors) {
Color(0, 0, 0, 180)
} else {
Color.White
},
) {
HorizontalPager(
modifier = Modifier.animateContentSize(),
state = pagerState,
verticalAlignment = Alignment.Top,
) { pageIndex ->
val currentPageStyle = styles[pageIndex]
if (currentPageStyle is ClockWidgetStyle.Custom && currentPageStyle.widgetId == null) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp, bottom = 8.dp),
contentAlignment = Alignment.TopCenter,
) {
OutlinedButton(
onClick = {
showWidgetPicker = true
},
contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
modifier = Modifier
.padding(16.dp)
) {
Icon(
modifier = Modifier
.padding(end = ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize),
imageVector = Icons.Rounded.Widgets,
contentDescription = null,
)
Text(stringResource(R.string.widget_pick_widget))
}
}
} else {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp, bottom = 8.dp)
.pointerInput(Unit) {
awaitEachGesture {
val event =
awaitFirstDown(pass = PointerEventPass.Initial)
event.consume()
}
},
contentAlignment = Alignment.TopCenter,
) {
if (currentPageStyle.javaClass == selected?.javaClass) {
Clock(selected, compact, darkColors)
} else {
Clock(currentPageStyle, compact, darkColors)
}
}
}
}
}
} }
IconButton( Row(
enabled = pagerState.currentPage < pagerState.pageCount - 1, verticalAlignment = Alignment.CenterVertically
onClick = { ) {
scope.launch { IconButton(
pagerState.animateScrollToPage( enabled = pagerState.currentPage > 0,
pagerState.currentPage + 1, onClick = {
) scope.launch {
pagerState.animateScrollToPage(
pagerState.currentPage - 1,
)
}
}) {
Icon(Icons.Rounded.ChevronLeft, null)
}
var showStyleDropdown by remember { mutableStateOf(false) }
TextButton(
modifier = Modifier
.weight(1f)
.padding(horizontal = 16.dp),
onClick = { showStyleDropdown = true },
contentPadding = PaddingValues(
top = 8.dp,
bottom = 8.dp,
start = 16.dp,
end = 12.dp,
),
colors = ButtonDefaults.textButtonColors(
contentColor = LocalContentColor.current,
),
) {
Text(
text = getClockStyleName(
context,
styles[pagerState.currentPage]
),
textAlign = TextAlign.Center,
)
Icon(
Icons.Rounded.ArrowDropDown,
null,
modifier = Modifier
.padding(ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
)
DropdownMenu(
expanded = showStyleDropdown,
onDismissRequest = { showStyleDropdown = false }
) {
for (style in styles.withIndex()) {
DropdownMenuItem(
onClick = {
scope.launch {
pagerState.animateScrollToPage(
style.index,
)
}
showStyleDropdown = false
},
text = {
Text(
text = getClockStyleName(context, style.value),
)
}
)
}
} }
}) { }
Icon(Icons.Rounded.ChevronRight, null)
IconButton(
enabled = pagerState.currentPage < pagerState.pageCount - 1,
onClick = {
scope.launch {
pagerState.animateScrollToPage(
pagerState.currentPage + 1,
)
}
}) {
Icon(Icons.Rounded.ChevronRight, null)
}
} }
} }
} }
@ -311,7 +416,14 @@ fun WatchFaceSelector(
if (previousWidgetId != null) { if (previousWidgetId != null) {
appWidgetHost.deleteAppWidgetId(previousWidgetId) appWidgetHost.deleteAppWidgetId(previousWidgetId)
} }
onSelect(selected.copy(widgetId = (it as AppWidget).config.widgetId)) it as AppWidget
onSelect(
selected.copy(
widgetId = it.config.widgetId,
width = it.config.width,
height = it.config.height,
)
)
}, },
onDismiss = { onDismiss = {
showWidgetPicker = false showWidgetPicker = false
@ -333,3 +445,145 @@ fun getClockStyleName(context: Context, style: ClockWidgetStyle): String {
else -> "" else -> ""
} }
} }
@Composable
private fun ResizeCustomWidget(
style: ClockWidgetStyle.Custom,
compact: Boolean,
themeColors: Boolean,
lightBackground: Boolean,
onChange: (ClockWidgetStyle.Custom) -> Unit,
onExit: () -> Unit = {},
) {
val context = LocalContext.current
val widgetId = style.widgetId
val widgetInfo = remember(widgetId) {
widgetId?.let {
AppWidgetManager.getInstance(context)
.getAppWidgetInfo(it)
}
}
if (widgetId != null && widgetInfo != null) {
val minWidth = when {
compact -> 64.dp
widgetInfo.minResizeWidth in 1..widgetInfo.minWidth -> {
widgetInfo.minResizeWidth.toDp()
}
else -> {
widgetInfo.minWidth.toDp()
}
}
val minHeight = when {
compact -> 16.dp
widgetInfo.minResizeHeight in 1..widgetInfo.minHeight -> {
widgetInfo.minResizeHeight.toDp()
}
else -> {
widgetInfo.minHeight.toDp()
}
}
val maxWidth = when {
compact -> 200.dp
isAtLeastApiLevel(31) && widgetInfo.maxResizeWidth > 0 -> {
widgetInfo.maxResizeWidth.toDp()
}
else -> Dp.Unspecified
}
val maxHeight = when {
compact -> 64.dp
isAtLeastApiLevel(31) && widgetInfo.maxResizeHeight > 0 -> {
widgetInfo.maxResizeHeight.toDp()
}
else -> Dp.Unspecified
}
var resizeWidth by remember(style.widgetId) {
mutableStateOf(
style.width?.dp ?: Dp.Unspecified
)
}
var resizeHeight by remember(style.widgetId) { mutableStateOf(style.height.dp) }
Box(
modifier = Modifier
.fillMaxSize()
.padding(top = 16.dp, bottom = 64.dp),
contentAlignment = Alignment.TopCenter,
) {
AppWidgetHost(
widgetInfo = widgetInfo,
widgetId = widgetId,
modifier = Modifier
.then(
when {
compact && resizeWidth.isUnspecified -> Modifier.widthIn(max = 200.dp)
compact && !resizeWidth.isUnspecified -> Modifier.width(
resizeWidth.coerceAtMost(
200.dp
)
)
!compact && !resizeWidth.isUnspecified -> Modifier.width(resizeWidth)
else -> Modifier.fillMaxWidth()
}
)
.then(
when {
compact -> Modifier.height(resizeHeight.coerceAtMost(64.dp))
else -> Modifier.height(resizeHeight)
}
)
.pointerInput(Unit) {
awaitEachGesture {
val event = awaitFirstDown(pass = PointerEventPass.Initial)
event.consume()
}
},
borderless = compact,
useThemeColors = themeColors,
onLightBackground = lightBackground,
)
DragResizeHandle(
alignment = Alignment.TopCenter,
width = resizeWidth.coerceAtMost(maxWidth),
height = resizeHeight.coerceAtMost(maxHeight),
minWidth = minWidth,
minHeight = minHeight,
maxWidth = maxWidth,
maxHeight = if (compact) 64.dp else Dp.Unspecified,
onResize = { w, h ->
resizeWidth = w
resizeHeight = h
},
onResizeStopped = {
onChange(
style.copy(
width = resizeWidth.value.toInt(),
height = resizeHeight.value.toInt()
)
)
}
)
FilledIconButton(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(8.dp)
.offset(y = 64.dp),
onClick = onExit
) {
Icon(Icons.Rounded.Done, null)
}
}
}
}

View File

@ -1,7 +1,10 @@
package de.mm20.launcher2.ui.launcher.widgets.clock.clocks package de.mm20.launcher2.ui.launcher.widgets.clock.clocks
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -21,22 +24,36 @@ fun CustomClock(
) { ) {
val widgetId = style.widgetId val widgetId = style.widgetId
if (widgetId == null) { if (widgetId != null) {
Text("Hmmm…")
} else {
val context = LocalContext.current val context = LocalContext.current
val widgetInfo = remember(widgetId) { val widgetInfo = remember(widgetId) {
AppWidgetManager.getInstance(context) AppWidgetManager.getInstance(context)
.getAppWidgetInfo(widgetId) .getAppWidgetInfo(widgetId)
} }
if (widgetInfo != null) { if (widgetInfo != null) {
val width = style.width
val height = style.height
AppWidgetHost( AppWidgetHost(
widgetInfo = widgetInfo, widgetInfo = widgetInfo,
widgetId = widgetId, widgetId = widgetId,
useThemeColors = useThemeColor, useThemeColors = useThemeColor,
onLightBackground = darkColors, onLightBackground = darkColors,
borderless = compact, borderless = compact,
modifier = Modifier.widthIn(max = 250.dp).height(if (compact) 64.dp else 200.dp) modifier = Modifier
.then(
when {
compact && width == null -> Modifier.widthIn(max = 200.dp)
compact && width != null -> Modifier.width(width.coerceAtMost(200).dp)
!compact && width != null -> Modifier.width(width.dp)
else -> Modifier.fillMaxWidth()
}
)
.then(
when {
compact -> Modifier.height(height.coerceAtMost(64).dp)
else -> Modifier.height(height.dp)
}
)
) )
} }
} }

View File

@ -843,6 +843,7 @@
<string name="widget_config_appwidget_background">Background card</string> <string name="widget_config_appwidget_background">Background card</string>
<string name="widget_config_appwidget_configure">Configure widget</string> <string name="widget_config_appwidget_configure">Configure widget</string>
<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_appwidget_resize">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="widget_pick_widget">Pick widget</string>

View File

@ -239,7 +239,11 @@ sealed interface ClockWidgetStyle {
@Serializable @Serializable
@SerialName("custom") @SerialName("custom")
data class Custom(val widgetId: Int? = null) : ClockWidgetStyle data class Custom(
val widgetId: Int? = null,
val width: Int? = null,
val height: Int = 200,
) : ClockWidgetStyle
} }
@Serializable @Serializable