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) {
|
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(
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user