Skip to content

Commit

Permalink
feat: initial implementation of custom app drawer folder (#5189)
Browse files Browse the repository at this point in the history
- Implemented basic CRUD operations for app drawer folders.
- Moved wallpaper database to preference database for better organization.

closes :
- #4674
- #4710
- #4475
- #3481
  • Loading branch information
MrSluffy authored Jan 21, 2025
1 parent 8eee1af commit 95b87da
Show file tree
Hide file tree
Showing 34 changed files with 1,322 additions and 107 deletions.
10 changes: 10 additions & 0 deletions lawnchair/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@
<string name="caddy_beta">Caddy (Beta)</string>
<string name="caddy">Caddy</string>

<string name="app_drawer_folder">App drawer folder</string>
<string name="app_drawer_folder_settings">Drawer folder</string>
<string name="add_folder">Add folder</string>
<string name="edit_folder">Edit folder</string>
<string name="add_label">Add</string>
<string name="edit_label">Edit</string>
<string name="apply_label">Apply</string>
<string name="delete_label">Delete</string>
<string name="folder_list_note">Swipe left to delete, swipe right to edit, or tap to update items.</string>

<!-- A11y description -->
<string name="accessibility_service_description">To lock your phone when performing a gesture, and to open Recents via gesture, Lawnchair requires accessibility access.\n\nLawnchair doesn\'t watch any user action, though the privilege to do so is required for all accessibility services. Lawnchair discards any event sent by the system.\n\nIn order to lock your phone, or to open Recents, Lawnchair uses the performGlobalAction Accessibility service.</string>

Expand Down
6 changes: 3 additions & 3 deletions lawnchair/src/app/lawnchair/LawnchairLauncher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.lifecycleScope
import app.lawnchair.LawnchairApp.Companion.showQuickstepWarningIfNecessary
import app.lawnchair.compat.LawnchairQuickstepCompat
import app.lawnchair.data.AppDatabase
import app.lawnchair.data.wallpaper.service.WallpaperService
import app.lawnchair.factory.LawnchairWidgetHolder
import app.lawnchair.gestures.GestureController
import app.lawnchair.gestures.VerticalSwipeTouchController
Expand All @@ -49,8 +51,6 @@ import app.lawnchair.ui.popup.LauncherOptionsPopup
import app.lawnchair.ui.popup.LawnchairShortcut
import app.lawnchair.util.getThemedIconPacksInstalled
import app.lawnchair.util.unsafeLazy
import app.lawnchair.wallpaper.service.WallpaperDatabase
import app.lawnchair.wallpaper.service.WallpaperService
import com.android.launcher3.AbstractFloatingView
import com.android.launcher3.BaseActivity
import com.android.launcher3.BubbleTextView
Expand Down Expand Up @@ -239,7 +239,7 @@ class LawnchairLauncher : QuickstepLauncher() {

reloadIconsIfNeeded()

WallpaperDatabase.INSTANCE.get(this).checkpointSync()
AppDatabase.INSTANCE.get(this).checkpointSync()
}

override fun collectStateHandlers(out: MutableList<StateHandler<LauncherState>>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ package app.lawnchair.allapps

import android.content.Context
import android.util.Log
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.lifecycleScope
import app.lawnchair.data.factory.ViewModelFactory
import app.lawnchair.data.folder.model.FolderViewModel
import app.lawnchair.launcher
import app.lawnchair.preferences.PreferenceManager
import app.lawnchair.preferences2.PreferenceManager2
import app.lawnchair.util.categorizeApps
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener
import com.android.launcher3.allapps.AllAppsStore
import com.android.launcher3.allapps.AlphabeticalAppsList
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem
Expand All @@ -19,33 +25,51 @@ import com.android.launcher3.views.ActivityContext
import com.patrykmichalik.opto.core.onEach
import java.util.function.Predicate

@Suppress("SYNTHETIC_PROPERTY_WITHOUT_JAVA_ORIGIN")
class LawnchairAlphabeticalAppsList<T>(
private val context: T,
appsStore: AllAppsStore<T>,
private val appsStore: AllAppsStore<T>,
workProfileManager: WorkProfileManager?,
privateProfileManager: PrivateProfileManager?,
) : AlphabeticalAppsList<T>(context, appsStore, workProfileManager, privateProfileManager)
) : AlphabeticalAppsList<T>(context, appsStore, workProfileManager, privateProfileManager),
OnIDPChangeListener
where T : Context, T : ActivityContext {

private var hiddenApps: Set<String> = setOf()
private val prefs2 = PreferenceManager2.getInstance(context)
private val prefs = PreferenceManager.getInstance(context)

private var viewModel: FolderViewModel
private var folderList = mutableListOf<FolderInfo>()

init {
context.launcher.deviceProfile.inv.addOnChangeListener(this)
try {
prefs2.hiddenApps.onEach(launchIn = context.launcher.lifecycleScope) {
hiddenApps = it
onAppsUpdated()
}
} catch (t: Throwable) {
Log.w(TAG, "Failed initialize ignore: ", t)
Log.w(TAG, "Failed to initialize hidden apps", t)
}

val factory = ViewModelFactory(context) { FolderViewModel(it) }
viewModel = ViewModelProvider(context as ViewModelStoreOwner, factory)[FolderViewModel::class.java]
observeFolders()
}

private fun observeFolders() {
viewModel.foldersMutable.observe(context as LifecycleOwner) { folders ->
folderList = folders.toMutableList()
updateAdapterItems()
}
}

override fun updateItemFilter(itemFilter: Predicate<ItemInfo>?) {
this.mItemFilter = Predicate { info ->
mItemFilter = Predicate { info ->
require(info is AppInfo) { "`info` must be an instance of `AppInfo`." }
val componentKey = info.toComponentKey().toString()
itemFilter?.test(info) != false && !hiddenApps.contains(componentKey)
(itemFilter?.test(info) != false) && !hiddenApps.contains(componentKey)
}
onAppsUpdated()
}
Expand All @@ -57,27 +81,40 @@ class LawnchairAlphabeticalAppsList<T>(

if (!drawerListDefault) {
val categorizedApps = categorizeApps(context, appList)

if (categorizedApps.isNotEmpty()) {
for ((category, apps) in categorizedApps) {
if (apps.size <= 1) {
val app = apps[0]
mAdapterItems.add(AdapterItem.asApp(app))
} else {
val folderInfo = FolderInfo()
folderInfo.title = category
for (app in apps) {
folderInfo.add(app)
}
mAdapterItems.add(AdapterItem.asFolder(folderInfo))
categorizedApps.forEach { (category, apps) ->
if (apps.size == 1) {
mAdapterItems.add(AdapterItem.asApp(apps.first()))
} else {
val folderInfo = FolderInfo().apply {
title = category
apps.forEach { add(it) }
}
position++
mAdapterItems.add(AdapterItem.asFolder(folderInfo))
}
position++
}
} else {
position = super.addAppsWithSections(appList, startPosition)
folderList.forEach { folder ->
if (folder.contents.size > 1) {
val folderInfo = FolderInfo()
folderInfo.title = folder.title
mAdapterItems.add(AdapterItem.asFolder(folderInfo))
folder.contents.forEach { app ->
(appsStore.getApp(app.componentKey) as? AppInfo)?.let {
folderInfo.add(it)
}
}
}
position++
}
position = super.addAppsWithSections(appList, position)
}

return position
}

override fun onIdpChanged(modelPropertiesChanged: Boolean) {
onAppsUpdated()
viewModel.refreshFolders()
}
}
2 changes: 0 additions & 2 deletions lawnchair/src/app/lawnchair/backup/LawnchairBackup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import android.graphics.BitmapFactory
import android.net.Uri
import androidx.core.graphics.drawable.toBitmap
import app.lawnchair.LawnchairProto.BackupInfo
import app.lawnchair.data.AppDatabase
import app.lawnchair.util.hasFlag
import app.lawnchair.util.scaleDownTo
import app.lawnchair.util.scaleDownToDisplaySize
Expand Down Expand Up @@ -159,7 +158,6 @@ class LawnchairBackup(
.setPreviewDarkText(wallpaperSupportsDarkText)
.build()

AppDatabase.INSTANCE.get(context).checkpoint()
val pfd = context.contentResolver.openFileDescriptor(fileUri, "w")!!
withContext(Dispatchers.IO) {
pfd.use {
Expand Down
20 changes: 18 additions & 2 deletions lawnchair/src/app/lawnchair/data/AppDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,34 @@ import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.sqlite.db.SimpleSQLiteQuery
import app.lawnchair.data.folder.FolderInfoEntity
import app.lawnchair.data.folder.FolderItemEntity
import app.lawnchair.data.folder.service.FolderDao
import app.lawnchair.data.iconoverride.IconOverride
import app.lawnchair.data.iconoverride.IconOverrideDao
import app.lawnchair.data.wallpaper.Wallpaper
import app.lawnchair.data.wallpaper.service.WallpaperDao
import app.lawnchair.util.MainThreadInitializedObject
import kotlinx.coroutines.runBlocking

@Database(entities = [IconOverride::class], version = 1)
@Database(entities = [IconOverride::class, Wallpaper::class, FolderInfoEntity::class, FolderItemEntity::class], version = 2)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {

abstract fun iconOverrideDao(): IconOverrideDao
abstract fun wallpaperDao(): WallpaperDao
abstract fun folderDao(): FolderDao

suspend fun checkpoint() {
iconOverrideDao().checkpoint(SimpleSQLiteQuery("pragma wal_checkpoint(full)"))
wallpaperDao().checkpoint(SimpleSQLiteQuery("pragma wal_checkpoint(full)"))
folderDao().checkpoint(SimpleSQLiteQuery("pragma wal_checkpoint(full)"))
}

fun checkpointSync() {
runBlocking {
checkpoint()
}
}

companion object {
Expand All @@ -25,7 +41,7 @@ abstract class AppDatabase : RoomDatabase() {
context,
AppDatabase::class.java,
"preferences",
).build()
).fallbackToDestructiveMigration().build()
}
}
}
9 changes: 9 additions & 0 deletions lawnchair/src/app/lawnchair/data/Converters.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package app.lawnchair.data

import androidx.room.TypeConverter
import app.lawnchair.data.folder.FolderItemEntity
import com.android.launcher3.model.data.AppInfo
import com.android.launcher3.util.ComponentKey

class Converters {
Expand All @@ -11,3 +13,10 @@ class Converters {
@TypeConverter
fun toComponentKey(value: String?) = value?.let { ComponentKey.fromString(it) }
}

fun AppInfo.toEntity(folderId: Int): FolderItemEntity {
return FolderItemEntity(
folderId = folderId,
componentKey = Converters().fromComponentKey(this.toComponentKey()),
)
}
16 changes: 16 additions & 0 deletions lawnchair/src/app/lawnchair/data/factory/ViewModelFactory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package app.lawnchair.data.factory

import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider

class ViewModelFactory<T : ViewModel>(
private val context: Context,
private val creator: (Context) -> T,
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return creator(context) as? T
?: throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
}
35 changes: 35 additions & 0 deletions lawnchair/src/app/lawnchair/data/folder/FolderEntity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package app.lawnchair.data.folder

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey

@Entity(tableName = "Folders")
data class FolderInfoEntity(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val title: String,
val hide: Boolean = false,
val rank: Int = 0,
val timestamp: Long = System.currentTimeMillis(),
)

@Entity(
tableName = "FolderItems",
foreignKeys = [
ForeignKey(
entity = FolderInfoEntity::class,
parentColumns = ["id"],
childColumns = ["folderId"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE,
),
],
)
data class FolderItemEntity(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val folderId: Int,
val rank: Int = 0,
@ColumnInfo(name = "item_info") val componentKey: String?,
val timestamp: Long = System.currentTimeMillis(),
)
Loading

0 comments on commit 95b87da

Please sign in to comment.