Multiple widget panels (#1805)

* Initial approach

* Add a new setting so users can choose the amount of widgets screens

* Extract indexed string

* Remove unnecessary Migration6

* Make HomeComponent use the Default widget id

* Add defaultParentId to initial widgets for fresh launch

* Fix migration path
This commit is contained in:
Fidel Montesino
2026-02-16 18:46:01 +00:00
committed by GitHub
parent 682ddfb22e
commit adfff23e67
18 changed files with 296 additions and 58 deletions

View File

@@ -271,7 +271,6 @@ abstract class SharedLauncherActivity(
reverse = reverseSearchResults,
openKeyboard = searchBarAutofocus,
)
val widgetComponent by lazy { WidgetsComponent }
fun getScaffoldGesture(
action: GestureAction?,
@@ -294,7 +293,7 @@ abstract class SharedLauncherActivity(
null
} else {
ScaffoldGesture(
component = widgetComponent,
component = WidgetsComponent.forTarget(action.target),
animation = if (gesture.orientation == null) ScaffoldAnimation.ZoomIn else ScaffoldAnimation.Push,
)
}
@@ -491,4 +490,4 @@ abstract class SharedLauncherActivity(
Launcher,
Assistant
}
}
}

View File

@@ -38,6 +38,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import de.mm20.launcher2.preferences.WidgetScreenTarget
import de.mm20.launcher2.preferences.ui.ClockWidgetSettings
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.ktx.toDp
@@ -124,6 +125,7 @@ internal object ClockAndWidgetsHomeComponent : ScaffoldComponent() {
scope.launch { state.lock(hideSearchBar = true) }
editMode = it
},
parentId = WidgetScreenTarget.Default.scopeId.toString(),
)
}
if (editMode) {
@@ -164,4 +166,4 @@ internal object ClockAndWidgetsHomeComponent : ScaffoldComponent() {
super.onDismiss(state)
scrollState.scrollTo(0)
}
}
}

View File

@@ -43,11 +43,32 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import de.mm20.launcher2.preferences.WidgetScreenTarget
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.launcher.widgets.WidgetColumn
import kotlinx.coroutines.launch
internal object WidgetsComponent : ScaffoldComponent() {
internal class WidgetsComponent(
private val target: WidgetScreenTarget
) : ScaffoldComponent() {
companion object {
/**
* Cache for widget component instances.
* Components are created lazily only when needed.
*/
private val componentCache = mutableMapOf<WidgetScreenTarget, WidgetsComponent>()
/**
* Get or create a WidgetsComponent for the given target.
* This ensures we reuse the same instance for each target.
*/
fun forTarget(target: WidgetScreenTarget): WidgetsComponent {
return componentCache.getOrPut(target) {
WidgetsComponent(target)
}
}
}
private val scrollState = ScrollState(0)
@@ -98,6 +119,7 @@ internal object WidgetsComponent : ScaffoldComponent() {
scope.launch { state.lock(hideSearchBar = true) }
editMode = it
},
parentId = target.scopeId.toString(),
)
}
if (editMode) {
@@ -135,4 +157,4 @@ internal object WidgetsComponent : ScaffoldComponent() {
super.onDismiss(state)
scrollState.scrollTo(0)
}
}
}

View File

@@ -17,6 +17,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -41,24 +42,29 @@ import de.mm20.launcher2.ui.launcher.sheets.WidgetPickerSheet
import de.mm20.launcher2.ui.locals.LocalSnackbarHostState
import de.mm20.launcher2.widgets.AppWidget
import kotlinx.coroutines.launch
import java.util.UUID
@Composable
fun WidgetColumn(
modifier: Modifier = Modifier,
editMode: Boolean = false,
onEditModeChange: (Boolean) -> Unit,
parentId: String,
) {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val viewModel: WidgetsVM = viewModel()
val viewModel: WidgetsVM = viewModel(
key = "widgets-column-$parentId",
factory = WidgetsVM.Factory(parentId),
)
val snackbarHostState = LocalSnackbarHostState.current
var addNewWidget by rememberSaveable { mutableStateOf(false) }
Column(
modifier = modifier.fillMaxWidth()
modifier = modifier.fillMaxWidth(),
) {
val scope = rememberCoroutineScope()
Column {
@@ -70,7 +76,8 @@ fun WidgetColumn(
for ((i, widget) in widgetsWithIndex) {
key(widget.id) {
var dragOffsetAfterSwap = remember<Float?> { null }
val offsetY = remember(widgets) { mutableStateOf(dragOffsetAfterSwap ?: 0f) }
val offsetY =
remember(widgets) { mutableFloatStateOf(dragOffsetAfterSwap ?: 0f) }
LaunchedEffect(widgets) {
dragOffsetAfterSwap = null
@@ -138,7 +145,7 @@ fun WidgetColumn(
scope.launch {
offsetY.animateTo(0f)
}
}
},
)
}
}
@@ -148,7 +155,7 @@ fun WidgetColumn(
if (editMode || editButton == true) {
val title = stringResource(
if (editMode) R.string.widget_add_widget
else R.string.menu_edit_widgets
else R.string.menu_edit_widgets,
)
Button(
@@ -162,15 +169,16 @@ fun WidgetColumn(
} else {
addNewWidget = true
}
}
},
) {
Icon(
modifier = Modifier
.padding(end = ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize),
painter = painterResource(
if (editMode) R.drawable.add_20px else R.drawable.edit_20px
), contentDescription = null
if (editMode) R.drawable.add_20px else R.drawable.edit_20px,
),
contentDescription = null,
)
Text(title)
}
@@ -184,6 +192,6 @@ fun WidgetColumn(
onWidgetSelected = {
viewModel.addWidget(it)
addNewWidget = false
}
},
)
}
}

View File

@@ -1,17 +1,22 @@
package de.mm20.launcher2.ui.launcher.widgets
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.widgets.Widget
import de.mm20.launcher2.widgets.WidgetRepository
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.util.UUID
class WidgetsVM : ViewModel(), KoinComponent {
class WidgetsVM(
private val parentId: UUID?,
) : ViewModel(), KoinComponent {
private val widgetRepository: WidgetRepository by inject()
private val uiSettings: UiSettings by inject()
@@ -19,7 +24,7 @@ class WidgetsVM : ViewModel(), KoinComponent {
val editButton = uiSettings.widgetEditButton
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val widgets = widgetRepository.get()
val widgets = widgetRepository.get(parent = parentId)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
fun addWidget(widget: Widget, index: Int? = null) {
@@ -29,7 +34,7 @@ class WidgetsVM : ViewModel(), KoinComponent {
} else {
widgets.add(index.coerceAtMost(widgets.size), widget)
}
widgetRepository.set(widgets)
widgetRepository.set(widgets, parentId)
}
fun removeWidget(widget: Widget) {
@@ -44,13 +49,27 @@ class WidgetsVM : ViewModel(), KoinComponent {
val widgets = widgets.value.toMutableList()
val widget = widgets.removeAt(index)
widgets.add(index - 1, widget)
widgetRepository.set(widgets)
widgetRepository.set(widgets, parentId)
}
fun moveDown(index: Int) {
val widgets = widgets.value.toMutableList()
val widget = widgets.removeAt(index)
widgets.add(index + 1, widget)
widgetRepository.set(widgets)
widgetRepository.set(widgets, parentId)
}
}
companion object : KoinComponent {
fun Factory(parentId: String) = viewModelFactory {
initializer {
val id = try {
UUID.fromString(parentId)
} catch (e: IllegalArgumentException) {
Log.e("WidgetsVM", "Invalid parentId: $parentId", e)
null
}
WidgetsVM(id)
}
}
}
}

View File

@@ -28,6 +28,7 @@ import androidx.navigation3.runtime.NavKey
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.preferences.GestureAction
import de.mm20.launcher2.preferences.WidgetScreenTarget
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.common.SearchablePicker
@@ -48,6 +49,7 @@ fun GestureSettingsScreen() {
val hasPermission by viewModel.hasPermission.collectAsStateWithLifecycle(null)
val allowWidgetGesture by viewModel.allowWidgetGesture.collectAsStateWithLifecycle(null)
val widgetScreenCount by viewModel.widgetScreenCount.collectAsStateWithLifecycle(1)
val options = buildList {
add(stringResource(R.string.gesture_action_none) to GestureAction.NoAction)
@@ -57,7 +59,20 @@ fun GestureSettingsScreen() {
add(stringResource(R.string.gesture_action_recents) to GestureAction.Recents)
add(stringResource(R.string.gesture_action_power_menu) to GestureAction.PowerMenu)
add(stringResource(R.string.gesture_action_open_search) to GestureAction.Search)
if (allowWidgetGesture == true) add(stringResource(R.string.gesture_action_widgets) to GestureAction.Widgets)
if (allowWidgetGesture == true) {
// Dynamically add widget screen targets based on user configuration
WidgetScreenTarget.getAvailableTargets(widgetScreenCount)
.forEachIndexed { index, target ->
val label =
if (widgetScreenCount == 1) {
stringResource(R.string.gesture_action_widgets)
} else {
stringResource(R.string.gesture_action_widgets_indexed, index + 1)
}
add(label to GestureAction.Widgets(target))
}
}
add(stringResource(R.string.gesture_action_launch_app) to GestureAction.Launch(null))
}
@@ -89,7 +104,7 @@ fun GestureSettingsScreen() {
options = options,
app = swipeDownApp,
appIcon = swipeDownAppIcon,
onAppChanged = { viewModel.setSwipeDownApp(it) }
onAppChanged = { viewModel.setSwipeDownApp(it) },
)
}
@@ -111,7 +126,7 @@ fun GestureSettingsScreen() {
options = options,
app = swipeLeftApp,
appIcon = swipeLeftAppIcon,
onAppChanged = { viewModel.setSwipeLeftApp(it) }
onAppChanged = { viewModel.setSwipeLeftApp(it) },
)
}
@@ -133,7 +148,7 @@ fun GestureSettingsScreen() {
options = optionsWithFeed,
app = swipeRightApp,
appIcon = swipeRightAppIcon,
onAppChanged = { viewModel.setSwipeRightApp(it) }
onAppChanged = { viewModel.setSwipeRightApp(it) },
)
}
@@ -155,7 +170,7 @@ fun GestureSettingsScreen() {
options = options,
app = swipeUpApp,
appIcon = swipeUpAppIcon,
onAppChanged = { viewModel.setSwipeUpApp(it) }
onAppChanged = { viewModel.setSwipeUpApp(it) },
)
}
@@ -177,7 +192,7 @@ fun GestureSettingsScreen() {
options = options,
app = doubleTapApp,
appIcon = doubleTapAppIcon,
onAppChanged = { viewModel.setDoubleTapApp(it) }
onAppChanged = { viewModel.setDoubleTapApp(it) },
)
}
@@ -199,7 +214,7 @@ fun GestureSettingsScreen() {
options = options,
app = longPressApp,
appIcon = longPressAppIcon,
onAppChanged = { viewModel.setLongPressApp(it) }
onAppChanged = { viewModel.setLongPressApp(it) },
)
}
val homeButton by viewModel.homeButton.collectAsStateWithLifecycle(null)
@@ -220,7 +235,7 @@ fun GestureSettingsScreen() {
options = options,
app = homeButtonApp,
appIcon = homeButtonAppIcon,
onAppChanged = { viewModel.setHomeButtonApp(it) }
onAppChanged = { viewModel.setHomeButtonApp(it) },
)
}
}
@@ -232,7 +247,8 @@ fun requiresAccessibilityService(action: GestureAction?): Boolean {
return when (action) {
is GestureAction.ScreenLock,
is GestureAction.Recents,
is GestureAction.PowerMenu -> true
is GestureAction.PowerMenu,
-> true
else -> false
}
@@ -251,19 +267,28 @@ fun GesturePreference(
) {
var showAppPicker by remember { mutableStateOf(false) }
Row(
verticalAlignment = (Alignment.CenterVertically)
verticalAlignment = (Alignment.CenterVertically),
) {
Box(
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
) {
ListPreference(
title = title,
icon = icon,
items = options,
value = value,
summary = options.find { value?.javaClass == it.second.javaClass }?.first
?: stringResource(R.string.gesture_action_none),
onValueChanged = { if (it != null) onValueChanged(it) }
summary = options.find { option ->
when {
value is GestureAction.Widgets && option.second is GestureAction.Widgets -> {
val valueTarget = value.target
val optionTarget = (option.second as GestureAction.Widgets).target
valueTarget == optionTarget
}
else -> value?.javaClass == option.second.javaClass
}
}?.first ?: stringResource(R.string.gesture_action_none),
onValueChanged = { if (it != null) onValueChanged(it) },
)
}
@@ -273,12 +298,13 @@ fun GesturePreference(
.height(36.dp)
.width(1.dp)
.alpha(0.38f)
.background(LocalContentColor.current)
.background(LocalContentColor.current),
)
Box(
modifier = Modifier
.clickable { showAppPicker = true }
.padding(12.dp)) {
.padding(12.dp),
) {
ShapedLauncherIcon(size = 32.dp, icon = { appIcon })
}
}
@@ -295,8 +321,8 @@ fun GesturePreference(
onValueChanged = {
showAppPicker = false
onAppChanged(it)
}
},
)
}
}
}

View File

@@ -37,6 +37,9 @@ class GestureSettingsScreenVM : ViewModel(), KoinComponent {
val allowWidgetGesture = uiSettings.homeScreenWidgets.map { it == false }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val widgetScreenCount = uiSettings.widgetScreenCount
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)
val swipeDown = gestureSettings.swipeDown
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val swipeLeft = gestureSettings.swipeLeft
@@ -187,4 +190,4 @@ class GestureSettingsScreenVM : ViewModel(), KoinComponent {
if (searchable == null) return emptyFlow()
return iconService.getIcon(searchable, size)
}
}
}

View File

@@ -135,6 +135,28 @@ fun HomescreenSettingsScreen() {
onValueChanged = {
viewModel.setWidgetsOnHomeScreen(it)
})
AnimatedVisibility(widgetsOnHomeScreen == false) {
val widgetScreenCount by viewModel.widgetScreenCount.collectAsStateWithLifecycle(1)
Column {
SliderPreference(
title = stringResource(R.string.preference_widget_screen_count),
value = widgetScreenCount,
min = 1,
max = 4,
onValueChanged = {
viewModel.setWidgetScreenCount(it)
}
)
Text(
text = stringResource(R.string.preference_widget_screen_count_info),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
)
}
}
SwitchPreference(
title = stringResource(id = R.string.preference_edit_button),
summary = stringResource(id = R.string.preference_widgets_edit_button_summary),
@@ -442,4 +464,4 @@ fun SearchBarStylePreference(
}
}
}
}
}

View File

@@ -166,6 +166,13 @@ class HomescreenSettingsScreenVM(
uiSettings.setHomeScreenWidgets(widgetsOnHomeScreen)
}
val widgetScreenCount = uiSettings.widgetScreenCount
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)
fun setWidgetScreenCount(count: Int) {
uiSettings.setWidgetScreenCount(count)
}
companion object : KoinComponent {
val Factory = viewModelFactory {
initializer {
@@ -177,4 +184,4 @@ class HomescreenSettingsScreenVM(
}
}
}
}
}

View File

@@ -686,6 +686,8 @@
<string name="preference_widgets_edit_button_summary">Show a button to add, remove and rearrange widgets</string>
<string name="preference_widgets_on_home_screen">Widgets on home screen</string>
<string name="preference_widgets_on_home_screen_summary">Show widgets on the home screen instead of a separate page</string>
<string name="preference_widget_screen_count">Widget screen count</string>
<string name="preference_widget_screen_count_info">Note: Reducing the count will automatically reset gestures assigned to disabled screens.</string>
<string name="preference_screen_homescreen">Home screen</string>
<string name="preference_screen_homescreen_summary">Clock, search bar, wallpaper, system bars</string>
<string name="preference_screen_icons">Grid and icons</string>
@@ -809,6 +811,7 @@
<string name="gesture_action_none">Do nothing</string>
<string name="gesture_action_open_search">Open search</string>
<string name="gesture_action_widgets">Widgets</string>
<string name="gesture_action_widgets_indexed">Widgets %1$d</string>
<string name="gesture_action_launch_app">Launch app</string>
<string name="gesture_action_notifications">Open notification drawer</string>
<string name="gesture_action_lock_screen">Turn off screen</string>

View File

@@ -29,4 +29,4 @@ internal class LauncherDataStore(
fun update(block: (LauncherSettingsData) -> LauncherSettingsData) {
updateData(block)
}
}
}

View File

@@ -11,7 +11,7 @@ import java.util.UUID
@Serializable
@ConsistentCopyVisibility
data class LauncherSettingsData internal constructor(
val schemaVersion: Int = 5,
val schemaVersion: Int = 6,
val uiColorScheme: ColorScheme = ColorScheme.System,
@Serializable(with = UUIDSerializer::class)
@@ -143,11 +143,12 @@ data class LauncherSettingsData internal constructor(
val surfacesShape: SurfaceShape = SurfaceShape.Rounded,
val widgetsEditButton: Boolean = true,
val widgetScreenCount: Int = 1,
val gesturesSwipeDown: GestureAction = GestureAction.Search,
val gesturesSwipeLeft: GestureAction = GestureAction.NoAction,
val gesturesSwipeRight: GestureAction = GestureAction.NoAction,
val gesturesSwipeUp: GestureAction = GestureAction.Widgets,
val gesturesSwipeUp: GestureAction = GestureAction.Widgets(),
val gesturesDoubleTap: GestureAction = GestureAction.ScreenLock,
val gesturesLongPress: GestureAction = GestureAction.NoAction,
val gesturesHomeButton: GestureAction = GestureAction.NoAction,
@@ -376,7 +377,7 @@ sealed interface GestureAction {
@Serializable
@SerialName("widgets")
data object Widgets : GestureAction
data class Widgets(val target: WidgetScreenTarget = WidgetScreenTarget.Default) : GestureAction
@Serializable
@SerialName("power_menu")
@@ -445,4 +446,4 @@ enum class MeasurementSystem {
@SerialName("metric") Metric,
@SerialName("uk") UnitedKingdom,
@SerialName("us") UnitedStates,
}
}

View File

@@ -0,0 +1,45 @@
package de.mm20.launcher2.preferences
import kotlinx.serialization.Serializable
import java.util.UUID
/**
* Represents different widget screen targets.
* Each target corresponds to a separate widget area with its own scope.
*/
@Serializable
enum class WidgetScreenTarget {
Widgets1,
Widgets2,
Widgets3,
Widgets4;
/**
* Returns the UUID used as parent identifier for widget repository operations.
* These are deterministic UUIDs generated using UUID v5 (name-based).
*/
val scopeId: UUID
get() = when (this) {
Widgets1 -> UUID.fromString("00000000-0000-0000-0000-000000000001")
Widgets2 -> UUID.fromString("00000000-0000-0000-0000-000000000002")
Widgets3 -> UUID.fromString("00000000-0000-0000-0000-000000000003")
Widgets4 -> UUID.fromString("00000000-0000-0000-0000-000000000004")
}
companion object {
/**
* The default widget screen target
*/
val Default = Widgets1
/**
* Get list of available widget screen targets based on configured count.
* @param count Number of widget screens to make available (1-4)
* @return List of WidgetScreenTarget enum values
*/
fun getAvailableTargets(count: Int): List<WidgetScreenTarget> {
val all = listOf(Widgets1, Widgets2, Widgets3, Widgets4)
return all.take(count.coerceIn(1, 4))
}
}
}

View File

@@ -15,7 +15,7 @@ class Migration5 : DataMigration<LauncherSettingsData> {
gesturesSwipeDown = if (currentData.uiBaseLayout == BaseLayout.PullDown) GestureAction.Search else currentData.gesturesSwipeDown,
gesturesSwipeLeft = if (currentData.uiBaseLayout == BaseLayout.Pager) GestureAction.Search else currentData.gesturesSwipeLeft,
gesturesSwipeRight = if (currentData.uiBaseLayout == BaseLayout.PagerReversed) GestureAction.Search else currentData.gesturesSwipeRight,
gesturesSwipeUp = GestureAction.Widgets,
gesturesSwipeUp = GestureAction.Widgets(),
homeScreenWidgets = !currentData.clockWidgetFillHeight,
)
}
@@ -23,4 +23,4 @@ class Migration5 : DataMigration<LauncherSettingsData> {
override suspend fun shouldMigrate(currentData: LauncherSettingsData): Boolean {
return currentData.schemaVersion < 5
}
}
}

View File

@@ -1,12 +1,14 @@
package de.mm20.launcher2.preferences.ui
import de.mm20.launcher2.preferences.ColorScheme
import de.mm20.launcher2.preferences.GestureAction
import de.mm20.launcher2.preferences.IconShape
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.ScreenOrientation
import de.mm20.launcher2.preferences.SearchBarColors
import de.mm20.launcher2.preferences.SearchBarStyle
import de.mm20.launcher2.preferences.SystemBarColors
import de.mm20.launcher2.preferences.WidgetScreenTarget
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import java.util.UUID
@@ -377,4 +379,44 @@ class UiSettings internal constructor(
it.copy(widgetsEditButton = editButton)
}
}
val widgetScreenCount
get() = launcherDataStore.data.map {
it.widgetScreenCount.coerceIn(1, 4)
}.distinctUntilChanged()
fun setWidgetScreenCount(count: Int) {
val validCount = count.coerceIn(1, 4)
launcherDataStore.update { data ->
var updatedData = data.copy(widgetScreenCount = validCount)
// Auto-reset gestures that point to invalid widget screens
val resetGesture: (GestureAction) -> GestureAction = { action ->
if (action is GestureAction.Widgets) {
val targetIndex = when (action.target) {
WidgetScreenTarget.Widgets1 -> 1
WidgetScreenTarget.Widgets2 -> 2
WidgetScreenTarget.Widgets3 -> 3
WidgetScreenTarget.Widgets4 -> 4
else -> 0
}
if (targetIndex > validCount) GestureAction.NoAction else action
} else {
action
}
}
updatedData = updatedData.copy(
gesturesSwipeUp = resetGesture(updatedData.gesturesSwipeUp),
gesturesSwipeDown = resetGesture(updatedData.gesturesSwipeDown),
gesturesSwipeLeft = resetGesture(updatedData.gesturesSwipeLeft),
gesturesSwipeRight = resetGesture(updatedData.gesturesSwipeRight),
gesturesDoubleTap = resetGesture(updatedData.gesturesDoubleTap),
gesturesLongPress = resetGesture(updatedData.gesturesLongPress),
gesturesHomeButton = resetGesture(updatedData.gesturesHomeButton),
)
updatedData
}
}
}

View File

@@ -39,14 +39,17 @@ import de.mm20.launcher2.database.migrations.Migration_22_23
import de.mm20.launcher2.database.migrations.Migration_23_24
import de.mm20.launcher2.database.migrations.Migration_24_25
import de.mm20.launcher2.database.migrations.Migration_25_26
import de.mm20.launcher2.database.migrations.Migration_26_27
import de.mm20.launcher2.database.migrations.Migration_27_28
import de.mm20.launcher2.database.migrations.Migration_28_29
import de.mm20.launcher2.database.migrations.Migration_29_30
import de.mm20.launcher2.database.migrations.Migration_30_31
import de.mm20.launcher2.database.migrations.Migration_6_7
import de.mm20.launcher2.database.migrations.Migration_7_8
import de.mm20.launcher2.database.migrations.Migration_8_9
import de.mm20.launcher2.database.migrations.Migration_9_10
import de.mm20.launcher2.ktx.toBytes
import de.mm20.launcher2.preferences.WidgetScreenTarget
import java.util.UUID
@Database(
@@ -64,7 +67,7 @@ import java.util.UUID
ShapesEntity::class,
TransparenciesEntity::class,
TypographyEntity::class,
], version = 30, exportSchema = true
], version = 31, exportSchema = true
)
@TypeConverters(ComponentNameConverter::class)
abstract class AppDatabase : RoomDatabase() {
@@ -129,15 +132,19 @@ abstract class AppDatabase : RoomDatabase() {
)
)
val defaultParentId = WidgetScreenTarget.Default.scopeId
db.execSQL(
"INSERT INTO Widget (`type`, `position`, `id`) VALUES " +
"('weather', 0, ?)," +
"('music', 1, ?)," +
"('calendar', 2, ?);",
"INSERT INTO Widget (`type`, `position`, `id`, `parentId`) VALUES " +
"('weather', 0, ?, ?)," +
"('music', 1, ?, ?)," +
"('calendar', 2, ?, ?);",
arrayOf(
UUID.randomUUID().toBytes(),
defaultParentId.toBytes(),
UUID.randomUUID().toBytes(),
UUID.randomUUID().toBytes()
defaultParentId.toBytes(),
UUID.randomUUID().toBytes(),
defaultParentId.toBytes()
)
)
}
@@ -163,13 +170,14 @@ abstract class AppDatabase : RoomDatabase() {
Migration_23_24(),
Migration_24_25(),
Migration_25_26(),
Migration_26_27(),
Migration_27_28(),
Migration_28_29(),
Migration_29_30(),
Migration_30_31(),
).build()
if (_instance == null) _instance = instance
return instance
}
}
}

View File

@@ -0,0 +1,11 @@
package de.mm20.launcher2.database.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration_26_27: Migration(26, 27) {
override fun migrate(db: SupportSQLiteDatabase) {
// Nothing to do
}
}

View File

@@ -0,0 +1,20 @@
package de.mm20.launcher2.database.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import de.mm20.launcher2.ktx.toBytes
import de.mm20.launcher2.preferences.WidgetScreenTarget
class Migration_30_31 : Migration(30, 31) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"""
UPDATE Widget
SET parentId = ?
WHERE parentId IS NULL
""".trimIndent(),
arrayOf(WidgetScreenTarget.Default.scopeId.toBytes()),
)
}
}