Allow resizing of custom clock widgets
This commit is contained in:
parent
64002c9f38
commit
38c048ec03
@ -384,7 +384,7 @@ fun ColumnScope.ConfigureAppWidget(
|
||||
val minHeight = if (widgetInfo.minResizeHeight in 1..widgetInfo.minHeight) {
|
||||
widgetInfo.minResizeHeight.toDp()
|
||||
} else {
|
||||
widgetInfo.minHeight.toDp()
|
||||
widgetInfo.minHeight.toDp()
|
||||
}
|
||||
|
||||
DragResizeHandle(
|
||||
|
||||
@ -163,7 +163,7 @@ fun ClockWidget(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.then(if (fillScreenHeight) Modifier.weight(1f) else Modifier)
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth().padding(horizontal = 24.dp),
|
||||
contentAlignment = when (alignment) {
|
||||
ClockWidgetAlignment.Center -> Alignment.Center
|
||||
ClockWidgetAlignment.Top -> Alignment.TopCenter
|
||||
@ -408,6 +408,7 @@ fun ConfigureClockWidgetSheet(
|
||||
styles = availableStyles,
|
||||
compact = compact!!,
|
||||
colors = color!!,
|
||||
themeColors = useAccentColor,
|
||||
selected = style,
|
||||
onSelect = {
|
||||
viewModel.setClockStyle(it)
|
||||
|
||||
@ -5,34 +5,49 @@ import android.app.ActivityOptions
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.scaleIn
|
||||
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.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
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.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material.icons.Icons
|
||||
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.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.SwapHoriz
|
||||
import androidx.compose.material.icons.rounded.Tune
|
||||
import androidx.compose.material.icons.rounded.Widgets
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.FilledIconButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
@ -48,16 +63,26 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.res.stringResource
|
||||
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.isUnspecified
|
||||
import androidx.compose.ui.zIndex
|
||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||
import de.mm20.launcher2.preferences.ClockWidgetColors
|
||||
import de.mm20.launcher2.preferences.ClockWidgetStyle
|
||||
import de.mm20.launcher2.ui.BuildConfig
|
||||
import de.mm20.launcher2.ui.R
|
||||
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.widgets.external.AppWidgetHost
|
||||
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
||||
import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper
|
||||
import de.mm20.launcher2.widgets.AppWidget
|
||||
@ -68,235 +93,315 @@ fun WatchFaceSelector(
|
||||
styles: List<ClockWidgetStyle>,
|
||||
compact: Boolean,
|
||||
colors: ClockWidgetColors,
|
||||
themeColors: Boolean,
|
||||
selected: ClockWidgetStyle?,
|
||||
onSelect: (ClockWidgetStyle) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
var showWidgetPicker by rememberSaveable { mutableStateOf(false) }
|
||||
var resizeCustomWidget by remember { mutableStateOf(false) }
|
||||
|
||||
val lightBackground =
|
||||
colors == ClockWidgetColors.Dark || colors == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp, horizontal = 32.dp),
|
||||
color = if (colors == ClockWidgetColors.Dark || colors == ClockWidgetColors.Auto && LocalPreferDarkContentOverWallpaper.current) {
|
||||
.padding(vertical = 16.dp, horizontal = 0.dp),
|
||||
color = if (lightBackground) {
|
||||
if (LocalDarkTheme.current) MaterialTheme.colorScheme.inverseSurface else MaterialTheme.colorScheme.surfaceContainer
|
||||
} else {
|
||||
if (LocalDarkTheme.current) MaterialTheme.colorScheme.surfaceContainer else MaterialTheme.colorScheme.inverseSurface
|
||||
},
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier,
|
||||
) {
|
||||
val pagerState = rememberPagerState(
|
||||
initialPage = styles.indexOfFirst { it.javaClass == selected?.javaClass }
|
||||
.coerceAtLeast(0),
|
||||
AnimatedContent(resizeCustomWidget) { resize ->
|
||||
if (resize && selected is ClockWidgetStyle.Custom) {
|
||||
ResizeCustomWidget(
|
||||
style = selected,
|
||||
compact = compact,
|
||||
themeColors = themeColors,
|
||||
lightBackground = lightBackground,
|
||||
onChange = { onSelect(it) },
|
||||
onExit = { resizeCustomWidget = false }
|
||||
)
|
||||
return@AnimatedContent
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier,
|
||||
) {
|
||||
styles.size
|
||||
}
|
||||
|
||||
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(),
|
||||
val pagerState = rememberPagerState(
|
||||
initialPage = styles.indexOfFirst { it.javaClass == selected?.javaClass }
|
||||
.coerceAtLeast(0),
|
||||
) {
|
||||
var showStyleSettings by remember { mutableStateOf(false) }
|
||||
IconButton(
|
||||
onClick = { showStyleSettings = true },
|
||||
styles.size
|
||||
}
|
||||
|
||||
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
|
||||
.padding(4.dp)
|
||||
.align(Alignment.TopEnd)
|
||||
.zIndex(1f),
|
||||
enter = scaleIn(),
|
||||
exit = scaleOut(),
|
||||
) {
|
||||
Icon(Icons.Rounded.Tune, null)
|
||||
DropdownMenu(
|
||||
expanded = showStyleSettings,
|
||||
onDismissRequest = { showStyleSettings = false }) {
|
||||
if (selected is ClockWidgetStyle.Digital1) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.clock_variant_outlined)) },
|
||||
leadingIcon = {
|
||||
if (selected.outlined) {
|
||||
Icon(Icons.Rounded.Check, null)
|
||||
}
|
||||
},
|
||||
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) {
|
||||
var showStyleSettings by remember { mutableStateOf(false) }
|
||||
IconButton(
|
||||
onClick = { showStyleSettings = true },
|
||||
modifier = Modifier
|
||||
.padding(4.dp)
|
||||
) {
|
||||
Icon(Icons.Rounded.Tune, null)
|
||||
DropdownMenu(
|
||||
expanded = showStyleSettings,
|
||||
onDismissRequest = { showStyleSettings = false }) {
|
||||
if (selected is ClockWidgetStyle.Digital1) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.widget_config_appwidget_configure)) },
|
||||
text = { Text(stringResource(R.string.clock_variant_outlined)) },
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.Settings, null)
|
||||
Icon(
|
||||
if (selected.outlined) Icons.Rounded.CheckCircle
|
||||
else Icons.Rounded.RadioButtonUnchecked,
|
||||
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()
|
||||
}
|
||||
)
|
||||
onSelect(selected.copy(outlined = !selected.outlined))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 24.dp, bottom = 8.dp),
|
||||
contentAlignment = Alignment.TopCenter,
|
||||
) {
|
||||
val currentPageStyle = styles[pageIndex]
|
||||
if (currentPageStyle.javaClass == selected?.javaClass) {
|
||||
Clock(selected, compact, darkColors)
|
||||
} else {
|
||||
Clock(currentPageStyle, compact, darkColors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
IconButton(
|
||||
enabled = pagerState.currentPage > 0,
|
||||
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,
|
||||
if (selected is ClockWidgetStyle.Custom) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.widget_pick_widget)) },
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.SwapHoriz, null)
|
||||
},
|
||||
onClick = {
|
||||
showWidgetPicker = true
|
||||
showStyleSettings = false
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.PhotoSizeSelectSmall, null)
|
||||
},
|
||||
text = { Text(stringResource(R.string.widget_config_appwidget_resize)) },
|
||||
onClick = { resizeCustomWidget = true }
|
||||
)
|
||||
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()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
if (BuildConfig.DEBUG) {
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(Icons.Rounded.RestartAlt, null)
|
||||
},
|
||||
text = { Text("Reset") },
|
||||
onClick = {
|
||||
val widgetId = selected.widgetId
|
||||
if (widgetId != null) {
|
||||
appWidgetHost.deleteAppWidgetId(widgetId)
|
||||
}
|
||||
onSelect(
|
||||
ClockWidgetStyle.Custom()
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
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(
|
||||
enabled = pagerState.currentPage < pagerState.pageCount - 1,
|
||||
onClick = {
|
||||
scope.launch {
|
||||
pagerState.animateScrollToPage(
|
||||
pagerState.currentPage + 1,
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
IconButton(
|
||||
enabled = pagerState.currentPage > 0,
|
||||
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) {
|
||||
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 = {
|
||||
showWidgetPicker = false
|
||||
@ -333,3 +445,145 @@ fun getClockStyleName(context: Context, style: ClockWidgetStyle): String {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,10 @@
|
||||
package de.mm20.launcher2.ui.launcher.widgets.clock.clocks
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
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.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@ -21,22 +24,36 @@ fun CustomClock(
|
||||
) {
|
||||
val widgetId = style.widgetId
|
||||
|
||||
if (widgetId == null) {
|
||||
Text("Hmmm…")
|
||||
} else {
|
||||
if (widgetId != null) {
|
||||
val context = LocalContext.current
|
||||
val widgetInfo = remember(widgetId) {
|
||||
AppWidgetManager.getInstance(context)
|
||||
.getAppWidgetInfo(widgetId)
|
||||
}
|
||||
if (widgetInfo != null) {
|
||||
val width = style.width
|
||||
val height = style.height
|
||||
AppWidgetHost(
|
||||
widgetInfo = widgetInfo,
|
||||
widgetId = widgetId,
|
||||
useThemeColors = useThemeColor,
|
||||
onLightBackground = darkColors,
|
||||
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)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -843,6 +843,7 @@
|
||||
<string name="widget_config_appwidget_background">Background card</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">Resize</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_pick_widget">Pick widget</string>
|
||||
|
||||
@ -239,7 +239,11 @@ sealed interface ClockWidgetStyle {
|
||||
|
||||
@Serializable
|
||||
@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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user