Skip to content

Commit

Permalink
feat : initial implementation of LawnDeck (#5255)
Browse files Browse the repository at this point in the history
Introducing the home screen layout feature! Now, users have the ability to personalize their experience by disabling the app drawer and arranging apps directly on their home screen.

- closes : #4323
  • Loading branch information
MrSluffy authored Feb 14, 2025
1 parent 4c05942 commit dd3725c
Show file tree
Hide file tree
Showing 13 changed files with 498 additions and 112 deletions.
4 changes: 4 additions & 0 deletions lawnchair/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,10 @@
<string name="wallpaper_background_blur">Blur intensity</string>
<string name="wallpaper_background_blur_factor">Factor threshold</string>

<string name="home_lawn_deck_label">Deck</string>
<string name="home_lawn_deck_label_beta">Deck (Beta)</string>
<string name="home_lawn_deck_description">Effortlessly access your applications with Deck, the ultimate drawerless experience designed for simplicity and seamless organization</string>

<string name="auto_add_shortcuts_label">Add new apps to home screen</string>

<string name="minus_one_enable">Show feed</string>
Expand Down
87 changes: 87 additions & 0 deletions lawnchair/src/app/lawnchair/deck/LawndeckManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package app.lawnchair.deck

import android.content.Context
import android.util.Log
import app.lawnchair.LawnchairLauncher
import app.lawnchair.launcher
import app.lawnchair.launcherNullable
import app.lawnchair.util.restartLauncher
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.model.ItemInstallQueue
import com.android.launcher3.model.ModelDbController
import com.android.launcher3.provider.RestoreDbTask
import java.io.File
import java.util.Locale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class LawndeckManager(private val context: Context) {

// TODO

private val launcher = context.launcherNullable ?: LawnchairLauncher.instance?.launcher

suspend fun enableLawndeck() = withContext(Dispatchers.IO) {
if (!backupExists("bk")) createBackup("bk")
if (backupExists("lawndeck")) {
restoreBackup("lawndeck")
} else {
addAllAppsToWorkspace()
}
}

suspend fun disableLawndeck() = withContext(Dispatchers.IO) {
if (backupExists("bk")) {
createBackup("lawndeck")
restoreBackup("bk")
}
}

private fun createBackup(suffix: String) = runCatching {
getDatabaseFiles(suffix).apply {
db.copyTo(backupDb, overwrite = true)
if (journal.exists()) journal.copyTo(backupJournal, overwrite = true)
}
}.onFailure { Log.e("LawndeckManager", "Failed to create backup: $suffix", it) }

private fun restoreBackup(suffix: String) = runCatching {
getDatabaseFiles(suffix).apply {
backupDb.copyTo(db, overwrite = true)
if (backupJournal.exists()) backupJournal.copyTo(journal, overwrite = true)
}
postRestoreActions()
}.onFailure { Log.e("LawndeckManager", "Failed to restore backup: $suffix", it) }

private fun getDatabaseFiles(suffix: String): DatabaseFiles {
val idp = InvariantDeviceProfile.INSTANCE.get(context)
val dbFile = context.getDatabasePath(idp.dbFile)
return DatabaseFiles(
db = dbFile,
backupDb = File(dbFile.parent, "${suffix}_${idp.dbFile}"),
journal = File(dbFile.parent, "${idp.dbFile}-journal"),
backupJournal = File(dbFile.parent, "${suffix}_${idp.dbFile}-journal"),
)
}

private fun backupExists(suffix: String): Boolean = getDatabaseFiles(suffix).backupDb.exists()

private fun postRestoreActions() {
ModelDbController(context).let { RestoreDbTask.performRestore(context, it) }
restartLauncher(context)
}

private fun addAllAppsToWorkspace() {
launcher?.mAppsView?.appsStore?.apps
?.sortedBy { it.title?.toString()?.lowercase(Locale.getDefault()) }
?.forEach { app ->
ItemInstallQueue.INSTANCE.get(context).queueItem(app.targetPackage, app.user)
}
}

private data class DatabaseFiles(
val db: File,
val backupDb: File,
val journal: File,
val backupJournal: File,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,12 @@ class PreferenceManager2 private constructor(private val context: Context) :
onSet = { reloadHelper.recreate() },
)

val deckLayout = preference(
key = booleanPreferencesKey(name = "enable_lawn_deck"),
defaultValue = false,
onSet = { reloadHelper.reloadIcons() },
)

val doubleTapGestureHandler = serializablePreference<GestureHandlerConfig>(
key = stringPreferencesKey("double_tap_gesture_handler"),
defaultValue = GestureHandlerConfig.Sleep,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import androidx.compose.ui.unit.dp
import app.lawnchair.gestures.config.GestureHandlerConfig
import app.lawnchair.gestures.config.GestureHandlerOption
import app.lawnchair.preferences.PreferenceAdapter
import app.lawnchair.preferences2.preferenceManager2
import app.lawnchair.ui.ModalBottomSheetContent
import app.lawnchair.ui.preferences.components.layout.PreferenceDivider
import app.lawnchair.ui.preferences.components.layout.PreferenceTemplate
import app.lawnchair.ui.util.LocalBottomSheetHandler
import com.patrykmichalik.opto.core.firstBlocking
import kotlinx.coroutines.launch

val options = listOf(
Expand All @@ -44,6 +46,7 @@ fun GestureHandlerPreference(
val context = LocalContext.current
val scope = rememberCoroutineScope()
val bottomSheetHandler = LocalBottomSheetHandler.current
val pref2 = preferenceManager2()

val currentConfig = adapter.state.value

Expand All @@ -54,6 +57,14 @@ fun GestureHandlerPreference(
}
}

val newOptions = options.filterNot { option ->
option in listOf(
GestureHandlerOption.OpenAppDrawer,
GestureHandlerOption.OpenAppSearch,
) &&
pref2.deckLayout.firstBlocking()
}

PreferenceTemplate(
title = { Text(text = label) },
description = { Text(text = currentConfig.getLabel(context)) },
Expand All @@ -68,7 +79,7 @@ fun GestureHandlerPreference(
},
) {
LazyColumn {
itemsIndexed(options) { index, option ->
itemsIndexed(newOptions) { index, option ->
if (index > 0) {
PreferenceDivider(startIndent = 40.dp)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package app.lawnchair.ui.preferences.components.layout

import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

@Composable
fun ButtonSection(
label: String,
isSelected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
gridLayout: @Composable () -> Unit,
) {
val backgroundColor by animateColorAsState(
targetValue = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceContainer,
animationSpec = tween(durationMillis = 300),
)

val textColor by animateColorAsState(
targetValue = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary,
animationSpec = tween(durationMillis = 300),
)
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(
modifier = Modifier
.size(160.dp, 120.dp)
.clip(MaterialTheme.shapes.large)
.background(backgroundColor)
.clickable { onClick() }
.padding(12.dp),
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally,
) {
gridLayout()
}
}

Spacer(modifier = Modifier.height(8.dp))
Text(
text = label,
color = textColor,
fontSize = 14.sp,
)
}
}
Loading

0 comments on commit dd3725c

Please sign in to comment.