diff --git a/lawnchair/res/values/strings.xml b/lawnchair/res/values/strings.xml
index abcedb5c285..08dd8f9f0df 100644
--- a/lawnchair/res/values/strings.xml
+++ b/lawnchair/res/values/strings.xml
@@ -94,6 +94,16 @@
Caddy (Beta)
Caddy
+ App drawer folder
+ Drawer folder
+ Add folder
+ Edit folder
+ Add
+ Edit
+ Apply
+ Delete
+ Swipe left to delete, swipe right to edit, or tap to update items.
+
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.
diff --git a/lawnchair/src/app/lawnchair/LawnchairLauncher.kt b/lawnchair/src/app/lawnchair/LawnchairLauncher.kt
index c3321db85fe..9f60aa5c26e 100644
--- a/lawnchair/src/app/lawnchair/LawnchairLauncher.kt
+++ b/lawnchair/src/app/lawnchair/LawnchairLauncher.kt
@@ -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
@@ -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
@@ -239,7 +239,7 @@ class LawnchairLauncher : QuickstepLauncher() {
reloadIconsIfNeeded()
- WallpaperDatabase.INSTANCE.get(this).checkpointSync()
+ AppDatabase.INSTANCE.get(this).checkpointSync()
}
override fun collectStateHandlers(out: MutableList>) {
diff --git a/lawnchair/src/app/lawnchair/allapps/LawnchairAlphabeticalAppsList.kt b/lawnchair/src/app/lawnchair/allapps/LawnchairAlphabeticalAppsList.kt
index 1b50bf17563..75c950f82a3 100644
--- a/lawnchair/src/app/lawnchair/allapps/LawnchairAlphabeticalAppsList.kt
+++ b/lawnchair/src/app/lawnchair/allapps/LawnchairAlphabeticalAppsList.kt
@@ -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
@@ -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(
private val context: T,
- appsStore: AllAppsStore,
+ private val appsStore: AllAppsStore,
workProfileManager: WorkProfileManager?,
privateProfileManager: PrivateProfileManager?,
-) : AlphabeticalAppsList(context, appsStore, workProfileManager, privateProfileManager)
+) : AlphabeticalAppsList(context, appsStore, workProfileManager, privateProfileManager),
+ OnIDPChangeListener
where T : Context, T : ActivityContext {
private var hiddenApps: Set = setOf()
private val prefs2 = PreferenceManager2.getInstance(context)
private val prefs = PreferenceManager.getInstance(context)
+
+ private var viewModel: FolderViewModel
+ private var folderList = mutableListOf()
+
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?) {
- 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()
}
@@ -57,27 +81,40 @@ class LawnchairAlphabeticalAppsList(
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()
+ }
}
diff --git a/lawnchair/src/app/lawnchair/backup/LawnchairBackup.kt b/lawnchair/src/app/lawnchair/backup/LawnchairBackup.kt
index 18f5b3fdb38..f7f8e2113ee 100644
--- a/lawnchair/src/app/lawnchair/backup/LawnchairBackup.kt
+++ b/lawnchair/src/app/lawnchair/backup/LawnchairBackup.kt
@@ -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
@@ -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 {
diff --git a/lawnchair/src/app/lawnchair/data/AppDatabase.kt b/lawnchair/src/app/lawnchair/data/AppDatabase.kt
index 59521d6a81d..15404c41cf5 100644
--- a/lawnchair/src/app/lawnchair/data/AppDatabase.kt
+++ b/lawnchair/src/app/lawnchair/data/AppDatabase.kt
@@ -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 {
@@ -25,7 +41,7 @@ abstract class AppDatabase : RoomDatabase() {
context,
AppDatabase::class.java,
"preferences",
- ).build()
+ ).fallbackToDestructiveMigration().build()
}
}
}
diff --git a/lawnchair/src/app/lawnchair/data/Converters.kt b/lawnchair/src/app/lawnchair/data/Converters.kt
index 5fb6bf29893..50c1fa756c2 100644
--- a/lawnchair/src/app/lawnchair/data/Converters.kt
+++ b/lawnchair/src/app/lawnchair/data/Converters.kt
@@ -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 {
@@ -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()),
+ )
+}
diff --git a/lawnchair/src/app/lawnchair/data/factory/ViewModelFactory.kt b/lawnchair/src/app/lawnchair/data/factory/ViewModelFactory.kt
new file mode 100644
index 00000000000..b9ae465bfad
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/data/factory/ViewModelFactory.kt
@@ -0,0 +1,16 @@
+package app.lawnchair.data.factory
+
+import android.content.Context
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+
+class ViewModelFactory(
+ private val context: Context,
+ private val creator: (Context) -> T,
+) : ViewModelProvider.Factory {
+ override fun create(modelClass: Class): T {
+ @Suppress("UNCHECKED_CAST")
+ return creator(context) as? T
+ ?: throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/data/folder/FolderEntity.kt b/lawnchair/src/app/lawnchair/data/folder/FolderEntity.kt
new file mode 100644
index 00000000000..87034a22902
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/data/folder/FolderEntity.kt
@@ -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(),
+)
diff --git a/lawnchair/src/app/lawnchair/data/folder/model/FolderViewModel.kt b/lawnchair/src/app/lawnchair/data/folder/model/FolderViewModel.kt
new file mode 100644
index 00000000000..bc230ee85d9
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/data/folder/model/FolderViewModel.kt
@@ -0,0 +1,104 @@
+package app.lawnchair.data.folder.model
+
+import android.content.Context
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import app.lawnchair.data.folder.service.FolderService
+import app.lawnchair.ui.preferences.destinations.Action
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.FolderInfo
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+
+class FolderViewModel(context: Context) : ViewModel() {
+
+ private val repository = FolderService.INSTANCE.get(context)
+
+ private val _folders = MutableStateFlow>(emptyList())
+ val folders: StateFlow> = _folders.asStateFlow()
+
+ private val _currentTitle = MutableStateFlow("My Folder")
+ val currentTitle: StateFlow = _currentTitle.asStateFlow()
+
+ private val _action = MutableStateFlow(Action.DEFAULT)
+ val action: StateFlow = _action.asStateFlow()
+
+ private val _foldersMutable = MutableLiveData>()
+ val foldersMutable: LiveData> = _foldersMutable
+
+ private val _folderInfo = MutableStateFlow(null)
+ val folderInfo = _folderInfo.asStateFlow()
+ private var tempTitle: String = ""
+
+ init {
+ viewModelScope.launch {
+ loadFolders()
+ }
+ }
+
+ fun setAction(newAction: Action) {
+ if (_action.value != newAction) {
+ Log.d("FolderPreferences", "Transitioning action from ${action.value} to $newAction")
+ _action.value = newAction
+ }
+ }
+
+ fun refreshFolders() {
+ viewModelScope.launch {
+ loadFolders()
+ }
+ }
+
+ fun updateCurrentTitle(title: String) {
+ if (action.value == Action.EDIT) {
+ tempTitle = title
+ _currentTitle.value = title
+ } else if (action.value != Action.SETTLE) {
+ _currentTitle.value = title
+ }
+ }
+
+ fun setFolderInfo(folderInfoId: Int, hasId: Boolean) {
+ viewModelScope.launch {
+ _folderInfo.value = repository.getFolderInfo(folderInfoId, hasId)
+ }
+ }
+
+ fun updateFolderInfo(folderInfo: FolderInfo, hide: Boolean) {
+ viewModelScope.launch {
+ repository.updateFolderInfo(folderInfo, hide)
+ loadFolders()
+ }
+ }
+
+ fun saveFolder(folderInfo: FolderInfo) {
+ viewModelScope.launch {
+ repository.saveFolderInfo(folderInfo)
+ loadFolders()
+ }
+ }
+
+ fun deleteFolderInfo(id: Int) {
+ viewModelScope.launch {
+ repository.deleteFolderInfo(id)
+ loadFolders()
+ }
+ }
+
+ fun updateFolderWithItems(id: Int, title: String, appInfos: List) {
+ viewModelScope.launch {
+ repository.updateFolderWithItems(id, title, appInfos)
+ }
+ }
+
+ suspend fun loadFolders() {
+ val folders = repository.getAllFolders()
+ _folders.value = folders
+ _foldersMutable.postValue(folders)
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/data/folder/service/FolderDao.kt b/lawnchair/src/app/lawnchair/data/folder/service/FolderDao.kt
new file mode 100644
index 00000000000..968ab1c1838
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/data/folder/service/FolderDao.kt
@@ -0,0 +1,68 @@
+package app.lawnchair.data.folder.service
+
+import androidx.room.Dao
+import androidx.room.Embedded
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.RawQuery
+import androidx.room.Relation
+import androidx.room.Transaction
+import androidx.sqlite.db.SupportSQLiteQuery
+import app.lawnchair.data.folder.FolderInfoEntity
+import app.lawnchair.data.folder.FolderItemEntity
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface FolderDao {
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertFolder(folder: FolderInfoEntity)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertFolderItems(items: List)
+
+ @Query("SELECT * FROM Folders WHERE id = :folderId")
+ @Transaction
+ suspend fun getFolderWithItems(folderId: Int): FolderWithItems?
+
+ @Query("SELECT * FROM Folders")
+ fun getAllFolders(): Flow>
+
+ @Transaction
+ suspend fun insertFolderWithItems(folder: FolderInfoEntity, items: List) {
+ insertFolder(folder)
+ insertFolderItems(items)
+ }
+
+ @Query("DELETE FROM FolderItems WHERE folderId = :folderId")
+ suspend fun deleteFolderItemsByFolderId(folderId: Int)
+
+ @Query(
+ value = """
+ UPDATE Folders
+ SET title = :newTitle, hide = :hide, timestamp = :timestamp
+ WHERE id = :folderId
+ """,
+ )
+ suspend fun updateFolderInfo(
+ folderId: Int,
+ newTitle: String,
+ hide: Boolean,
+ timestamp: Long = System.currentTimeMillis(),
+ )
+
+ @Query("DELETE FROM Folders WHERE id = :folderId")
+ suspend fun deleteFolder(folderId: Int)
+
+ @RawQuery
+ suspend fun checkpoint(supportSQLiteQuery: SupportSQLiteQuery): Int
+}
+
+data class FolderWithItems(
+ @Embedded val folder: FolderInfoEntity,
+ @Relation(
+ parentColumn = "id",
+ entityColumn = "folderId",
+ )
+ val items: List,
+)
diff --git a/lawnchair/src/app/lawnchair/data/folder/service/FolderService.kt b/lawnchair/src/app/lawnchair/data/folder/service/FolderService.kt
new file mode 100644
index 00000000000..849b2242b2c
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/data/folder/service/FolderService.kt
@@ -0,0 +1,99 @@
+package app.lawnchair.data.folder.service
+
+import android.content.Context
+import android.content.pm.LauncherApps
+import android.util.Log
+import app.lawnchair.data.AppDatabase
+import app.lawnchair.data.Converters
+import app.lawnchair.data.folder.FolderInfoEntity
+import app.lawnchair.data.toEntity
+import com.android.launcher3.AppFilter
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.FolderInfo
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.MainThreadInitializedObject
+import com.android.launcher3.util.SafeCloseable
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.withContext
+
+class FolderService(val context: Context) : SafeCloseable {
+
+ val folderDao = AppDatabase.INSTANCE.get(context).folderDao()
+
+ suspend fun updateFolderWithItems(folderInfoId: Int, title: String, appInfos: List) = withContext(Dispatchers.IO) {
+ folderDao.insertFolderWithItems(
+ FolderInfoEntity(id = folderInfoId, title = title),
+ appInfos.map {
+ it.toEntity(folderInfoId)
+ }.toList(),
+ )
+ }
+
+ suspend fun saveFolderInfo(folderInfo: FolderInfo) = withContext(Dispatchers.IO) {
+ folderDao.insertFolder(FolderInfoEntity(title = folderInfo.title.toString()))
+ }
+
+ suspend fun updateFolderInfo(folderInfo: FolderInfo, hide: Boolean = false) = withContext(Dispatchers.IO) {
+ folderDao.updateFolderInfo(folderInfo.id, folderInfo.title.toString(), hide)
+ }
+
+ suspend fun deleteFolderInfo(id: Int) = withContext(Dispatchers.IO) {
+ folderDao.deleteFolder(id)
+ }
+
+ suspend fun getFolderInfo(folderId: Int, hasId: Boolean = false): FolderInfo? = withContext(Dispatchers.IO) {
+ return@withContext try {
+ val folderItems = folderDao.getFolderWithItems(folderId)
+
+ folderItems?.let { items ->
+ val folderInfo = FolderInfo().apply {
+ if (hasId) id = items.folder.id
+ title = items.folder.title
+ }
+
+ items.items.forEach { item ->
+ toItemInfo(item.componentKey)?.let { folderInfo.add(it) }
+ }
+ folderInfo
+ }
+ } catch (e: Exception) {
+ Log.e("FolderService", "Failed to get folder info for folderId: $folderId", e)
+ null
+ }
+ }
+
+ private fun toItemInfo(componentKey: String?): AppInfo? {
+ val launcherApps = context.getSystemService(LauncherApps::class.java)
+ if (launcherApps != null) {
+ return UserCache.INSTANCE.get(context).userProfiles.asSequence()
+ .flatMap { launcherApps.getActivityList(null, it) }
+ .filter { AppFilter(context).shouldShowApp(it.componentName) }
+ .map { AppInfo(context, it, it.user) }
+ .filter { Converters().fromComponentKey(it.componentKey) == componentKey }
+ .firstOrNull()
+ }
+ return null
+ }
+
+ suspend fun getAllFolders(): List = withContext(Dispatchers.IO) {
+ try {
+ val folderEntities = folderDao.getAllFolders().firstOrNull() ?: emptyList()
+ folderEntities.mapNotNull { folderEntity ->
+ getFolderInfo(folderEntity.id, true)
+ }
+ } catch (e: Exception) {
+ Log.e("FolderService", "Failed to get all folders", e)
+ emptyList()
+ }
+ }
+
+ override fun close() {
+ TODO("Not yet implemented")
+ }
+
+ companion object {
+ @JvmField
+ val INSTANCE = MainThreadInitializedObject(::FolderService)
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/wallpaper/service/Wallpaper.kt b/lawnchair/src/app/lawnchair/data/wallpaper/Wallpaper.kt
similarity index 76%
rename from lawnchair/src/app/lawnchair/wallpaper/service/Wallpaper.kt
rename to lawnchair/src/app/lawnchair/data/wallpaper/Wallpaper.kt
index df8aa46b2b9..c1543a683be 100644
--- a/lawnchair/src/app/lawnchair/wallpaper/service/Wallpaper.kt
+++ b/lawnchair/src/app/lawnchair/data/wallpaper/Wallpaper.kt
@@ -1,9 +1,9 @@
-package app.lawnchair.wallpaper.service
+package app.lawnchair.data.wallpaper
import androidx.room.Entity
import androidx.room.PrimaryKey
-@Entity(tableName = "wallpapers")
+@Entity(tableName = "Wallpapers")
data class Wallpaper(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val imagePath: String,
diff --git a/lawnchair/src/app/lawnchair/wallpaper/model/WallpaperViewModel.kt b/lawnchair/src/app/lawnchair/data/wallpaper/model/WallpaperViewModel.kt
similarity index 95%
rename from lawnchair/src/app/lawnchair/wallpaper/model/WallpaperViewModel.kt
rename to lawnchair/src/app/lawnchair/data/wallpaper/model/WallpaperViewModel.kt
index cc0b1053a33..56b7c4a33f8 100644
--- a/lawnchair/src/app/lawnchair/wallpaper/model/WallpaperViewModel.kt
+++ b/lawnchair/src/app/lawnchair/data/wallpaper/model/WallpaperViewModel.kt
@@ -1,4 +1,4 @@
-package app.lawnchair.wallpaper.model
+package app.lawnchair.data.wallpaper.model
import android.app.WallpaperManager
import android.content.Context
@@ -6,8 +6,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import app.lawnchair.data.wallpaper.Wallpaper
import app.lawnchair.wallpaper.WallpaperManagerCompat
-import app.lawnchair.wallpaper.service.Wallpaper
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
diff --git a/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDao.kt b/lawnchair/src/app/lawnchair/data/wallpaper/service/WallpaperDao.kt
similarity index 90%
rename from lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDao.kt
rename to lawnchair/src/app/lawnchair/data/wallpaper/service/WallpaperDao.kt
index 56c3a66df1c..2773b561e1b 100644
--- a/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDao.kt
+++ b/lawnchair/src/app/lawnchair/data/wallpaper/service/WallpaperDao.kt
@@ -1,10 +1,11 @@
-package app.lawnchair.wallpaper.service
+package app.lawnchair.data.wallpaper.service
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.RawQuery
import androidx.sqlite.db.SupportSQLiteQuery
+import app.lawnchair.data.wallpaper.Wallpaper
@Dao
interface WallpaperDao {
diff --git a/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperService.kt b/lawnchair/src/app/lawnchair/data/wallpaper/service/WallpaperService.kt
similarity index 86%
rename from lawnchair/src/app/lawnchair/wallpaper/service/WallpaperService.kt
rename to lawnchair/src/app/lawnchair/data/wallpaper/service/WallpaperService.kt
index 5b0fb77c11c..c9210d77049 100644
--- a/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperService.kt
+++ b/lawnchair/src/app/lawnchair/data/wallpaper/service/WallpaperService.kt
@@ -1,10 +1,12 @@
-package app.lawnchair.wallpaper.service
+package app.lawnchair.data.wallpaper.service
import android.app.WallpaperManager
import android.content.Context
import android.graphics.drawable.BitmapDrawable
import android.util.Log
import androidx.core.graphics.drawable.toBitmap
+import app.lawnchair.data.AppDatabase
+import app.lawnchair.data.wallpaper.Wallpaper
import app.lawnchair.util.bitmapToByteArray
import com.android.launcher3.util.MainThreadInitializedObject
import com.android.launcher3.util.SafeCloseable
@@ -15,7 +17,7 @@ import kotlinx.coroutines.runBlocking
class WallpaperService(val context: Context) : SafeCloseable {
- val dao = WallpaperDatabase.INSTANCE.get(context).wallpaperDao()
+ val dao = AppDatabase.Companion.INSTANCE.get(context).wallpaperDao()
suspend fun saveWallpaper(wallpaperManager: WallpaperManager) {
try {
@@ -49,7 +51,12 @@ class WallpaperService(val context: Context) : SafeCloseable {
}
val imagePath = saveImageToAppStorage(imageData)
if (existingWallpapers.size < 4) {
- val wallpaper = Wallpaper(imagePath = imagePath, rank = existingWallpapers.size, timestamp = timestamp, checksum = checksum)
+ val wallpaper = Wallpaper(
+ imagePath = imagePath,
+ rank = existingWallpapers.size,
+ timestamp = timestamp,
+ checksum = checksum,
+ )
dao.insert(wallpaper)
} else {
val lowestRankedWallpaper = existingWallpapers.minByOrNull { it.timestamp }
@@ -65,7 +72,12 @@ class WallpaperService(val context: Context) : SafeCloseable {
}
}
- val wallpaper = Wallpaper(imagePath = imagePath, rank = 0, timestamp = timestamp, checksum = checksum)
+ val wallpaper = Wallpaper(
+ imagePath = imagePath,
+ rank = 0,
+ timestamp = timestamp,
+ checksum = checksum,
+ )
dao.insert(wallpaper)
}
}
diff --git a/lawnchair/src/app/lawnchair/override/CustomizeDialog.kt b/lawnchair/src/app/lawnchair/override/CustomizeDialog.kt
index bff792629cd..9b4facf167d 100644
--- a/lawnchair/src/app/lawnchair/override/CustomizeDialog.kt
+++ b/lawnchair/src/app/lawnchair/override/CustomizeDialog.kt
@@ -21,7 +21,6 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -30,6 +29,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import app.lawnchair.preferences.getAdapter
import app.lawnchair.preferences.preferenceManager
import app.lawnchair.preferences2.asState
import app.lawnchair.preferences2.preferenceManager2
@@ -115,9 +115,9 @@ fun CustomizeAppDialog(
) {
val prefs = preferenceManager()
val preferenceManager2 = preferenceManager2()
- val coroutineScope = rememberCoroutineScope()
val showComponentNames by preferenceManager2.showComponentNames.asState()
val hiddenApps by preferenceManager2.hiddenApps.asState()
+ val adapter = preferenceManager2.hiddenApps.getAdapter()
val context = LocalContext.current
var title by remember { mutableStateOf("") }
@@ -164,9 +164,7 @@ fun CustomizeAppDialog(
onCheckedChange = { newValue ->
val newSet = hiddenApps.toMutableSet()
if (newValue) newSet.add(stringKey) else newSet.remove(stringKey)
- coroutineScope.launch {
- preferenceManager2.hiddenApps.set(value = newSet)
- }
+ adapter.onChange(newSet)
},
)
}
diff --git a/lawnchair/src/app/lawnchair/preferences2/PreferenceManager2.kt b/lawnchair/src/app/lawnchair/preferences2/PreferenceManager2.kt
index 495b4c5542c..9400f4ce70e 100644
--- a/lawnchair/src/app/lawnchair/preferences2/PreferenceManager2.kt
+++ b/lawnchair/src/app/lawnchair/preferences2/PreferenceManager2.kt
@@ -61,7 +61,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import kotlinx.serialization.encodeToString
class PreferenceManager2 private constructor(private val context: Context) :
PreferenceManager,
diff --git a/lawnchair/src/app/lawnchair/preferences2/ReloadHelper.kt b/lawnchair/src/app/lawnchair/preferences2/ReloadHelper.kt
index 096df8a8a29..b1f4bafc756 100644
--- a/lawnchair/src/app/lawnchair/preferences2/ReloadHelper.kt
+++ b/lawnchair/src/app/lawnchair/preferences2/ReloadHelper.kt
@@ -19,6 +19,7 @@ package app.lawnchair.preferences2
import android.content.Context
import app.lawnchair.LawnchairLauncher
import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherAppState
import com.android.quickstep.TouchInteractionService
import com.android.quickstep.util.TISBindHelper
@@ -43,7 +44,7 @@ class ReloadHelper(private val context: Context) {
}
fun reloadIcons() {
- idp.onPreferencesChanged(context)
+ LauncherAppState.INSTANCE.get(context).reloadIcons()
}
fun reloadTaskbar() {
diff --git a/lawnchair/src/app/lawnchair/ui/AlertBottomSheetContent.kt b/lawnchair/src/app/lawnchair/ui/AlertBottomSheetContent.kt
index 506515926d7..2718e1d7068 100644
--- a/lawnchair/src/app/lawnchair/ui/AlertBottomSheetContent.kt
+++ b/lawnchair/src/app/lawnchair/ui/AlertBottomSheetContent.kt
@@ -13,6 +13,7 @@ import androidx.compose.material3.ProvideTextStyle
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+import app.lawnchair.ui.preferences.components.layout.BottomSpacer
@Composable
fun ModalBottomSheetContent(
@@ -57,6 +58,7 @@ fun ModalBottomSheetContent(
.fillMaxWidth(),
) {
buttons()
+ BottomSpacer()
}
}
}
diff --git a/lawnchair/src/app/lawnchair/ui/popup/WallpaperCarouselView.kt b/lawnchair/src/app/lawnchair/ui/popup/WallpaperCarouselView.kt
index d88960058e9..254d015b28e 100644
--- a/lawnchair/src/app/lawnchair/ui/popup/WallpaperCarouselView.kt
+++ b/lawnchair/src/app/lawnchair/ui/popup/WallpaperCarouselView.kt
@@ -20,10 +20,10 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import app.lawnchair.LawnchairLauncher
+import app.lawnchair.data.factory.ViewModelFactory
+import app.lawnchair.data.wallpaper.Wallpaper
+import app.lawnchair.data.wallpaper.model.WallpaperViewModel
import app.lawnchair.views.component.IconFrame
-import app.lawnchair.wallpaper.model.WallpaperViewModel
-import app.lawnchair.wallpaper.model.WallpaperViewModelFactory
-import app.lawnchair.wallpaper.service.Wallpaper
import com.android.launcher3.R
import com.android.launcher3.util.Themes
import com.android.launcher3.views.ActivityContext
@@ -51,9 +51,10 @@ class WallpaperCarouselView @JvmOverloads constructor(
init {
orientation = HORIZONTAL
addView(loadingView)
+ val factory = ViewModelFactory(context) { WallpaperViewModel(it) }
viewModel = ViewModelProvider(
context as ViewModelStoreOwner,
- WallpaperViewModelFactory(context),
+ factory,
)[WallpaperViewModel::class.java]
observeWallpapers()
diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/AnnouncementPreference.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/AnnouncementPreference.kt
index 8c498f046b9..0eb6548567b 100644
--- a/lawnchair/src/app/lawnchair/ui/preferences/components/AnnouncementPreference.kt
+++ b/lawnchair/src/app/lawnchair/ui/preferences/components/AnnouncementPreference.kt
@@ -178,7 +178,7 @@ private fun AnnouncementItemContent(
}
}
-private fun calculateAlpha(progress: Float): Float {
+fun calculateAlpha(progress: Float): Float {
return when {
progress < 0.5f -> 1f // Fully opaque until halfway
else -> 1f - (progress - 0.5f) * 2 // Fade out linearly from halfway to the end
diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/AppItem.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/AppItem.kt
index 5ff73c85a14..0d94bdaad9c 100644
--- a/lawnchair/src/app/lawnchair/ui/preferences/components/AppItem.kt
+++ b/lawnchair/src/app/lawnchair/ui/preferences/components/AppItem.kt
@@ -34,6 +34,7 @@ import app.lawnchair.ui.placeholder.fade
import app.lawnchair.ui.placeholder.placeholder
import app.lawnchair.ui.preferences.components.layout.PreferenceTemplate
import app.lawnchair.util.App
+import com.android.launcher3.model.data.AppInfo
@Composable
fun AppItem(
@@ -49,6 +50,20 @@ fun AppItem(
)
}
+@Composable
+fun AppItem(
+ appInfo: AppInfo,
+ onClick: (appInfo: AppInfo) -> Unit,
+ widget: (@Composable () -> Unit)? = null,
+) {
+ AppItem(
+ label = appInfo.title.toString(),
+ icon = appInfo.bitmap.icon,
+ onClick = { onClick(appInfo) },
+ widget = widget,
+ )
+}
+
@Composable
fun AppItem(
label: String,
diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/folder/AppListToFolderPreferences.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/folder/AppListToFolderPreferences.kt
new file mode 100644
index 00000000000..e4f5777a796
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/ui/preferences/components/folder/AppListToFolderPreferences.kt
@@ -0,0 +1,137 @@
+@file:Suppress("SYNTHETIC_PROPERTY_WITHOUT_JAVA_ORIGIN")
+
+package app.lawnchair.ui.preferences.components.folder
+
+import android.annotation.SuppressLint
+import androidx.compose.animation.Crossfade
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material3.Checkbox
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import app.lawnchair.LawnchairLauncher
+import app.lawnchair.data.factory.ViewModelFactory
+import app.lawnchair.data.folder.model.FolderViewModel
+import app.lawnchair.launcher
+import app.lawnchair.launcherNullable
+import app.lawnchair.preferences2.ReloadHelper
+import app.lawnchair.ui.preferences.LocalIsExpandedScreen
+import app.lawnchair.ui.preferences.components.AppItem
+import app.lawnchair.ui.preferences.components.AppItemPlaceholder
+import app.lawnchair.ui.preferences.components.layout.PreferenceLazyColumn
+import app.lawnchair.ui.preferences.components.layout.PreferenceScaffold
+import app.lawnchair.ui.preferences.components.layout.preferenceGroupItems
+import app.lawnchair.util.sortedBySelection
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.ItemInfo
+
+@SuppressLint("MutableCollectionMutableState")
+@Composable
+fun AppListToFolderPreferences(
+ folderInfoId: Int?,
+ modifier: Modifier = Modifier,
+) {
+ if (folderInfoId == null) return
+
+ val context = LocalContext.current
+ val launcher = context.launcherNullable ?: LawnchairLauncher.instance?.launcher
+ if (launcher == null) return
+
+ val reloadHelper = ReloadHelper(launcher)
+
+ val viewModel: FolderViewModel = viewModel(factory = ViewModelFactory(launcher) { FolderViewModel(it) })
+
+ val folderInfo by viewModel.folderInfo.collectAsState()
+ val loading = folderInfo == null
+
+ val selectedAppsState = remember { mutableStateOf(setOf()) }
+
+ LaunchedEffect(folderInfoId) {
+ viewModel.setFolderInfo(folderInfoId, false)
+ }
+
+ LaunchedEffect(folderInfo) {
+ val folderContents = folderInfo?.contents?.toMutableSet() ?: mutableSetOf()
+ selectedAppsState.value = folderContents
+ }
+
+ val apps = launcher.mAppsView.appsStore.apps.toList()
+ .sortedBySelection(selectedAppsState.value)
+
+ val state = rememberLazyListState()
+
+ val label = if (loading) {
+ "Loading..."
+ } else {
+ folderInfo?.title.toString() + " (" + selectedAppsState.value.size + ")"
+ }
+ PreferenceScaffold(
+ label = label,
+ modifier = modifier,
+ isExpandedScreen = LocalIsExpandedScreen.current,
+ ) {
+ Crossfade(targetState = loading, label = "") { isLoading ->
+ if (isLoading) {
+ PreferenceLazyColumn(it, enabled = false, state = state) {
+ preferenceGroupItems(
+ count = 20,
+ isFirstChild = true,
+ dividerStartIndent = 40.dp,
+ ) {
+ AppItemPlaceholder {
+ Spacer(Modifier.width(24.dp))
+ }
+ }
+ }
+ } else {
+ PreferenceLazyColumn(it, state = state) {
+ val updateFolderApp = { app: AppInfo ->
+ val updatedSelectedApps = selectedAppsState.value.toMutableSet().apply {
+ val isChecked = any { it is AppInfo && it.targetPackage == app.targetPackage }
+ if (isChecked) {
+ removeIf { it is AppInfo && it.targetPackage == app.targetPackage }
+ } else {
+ add(app)
+ }
+ }
+
+ selectedAppsState.value = updatedSelectedApps
+
+ viewModel.updateFolderWithItems(
+ folderInfoId,
+ folderInfo?.title.toString(),
+ updatedSelectedApps.filterIsInstance().toList(),
+ )
+ reloadHelper.reloadGrid()
+ }
+
+ preferenceGroupItems(apps, isFirstChild = true, dividerStartIndent = 40.dp) { _, app ->
+ key(app.toString()) {
+ AppItem(app, onClick = { updateFolderApp(app) }) {
+ Checkbox(
+ checked = selectedAppsState.value.any {
+ val appInfo = it as? AppInfo
+ appInfo?.targetPackage == app.targetPackage
+ },
+ onCheckedChange = { isChecked ->
+ updateFolderApp(app)
+ },
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/ui/preferences/components/folder/FolderBottomSheet.kt b/lawnchair/src/app/lawnchair/ui/preferences/components/folder/FolderBottomSheet.kt
new file mode 100644
index 00000000000..89899887a7e
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/ui/preferences/components/folder/FolderBottomSheet.kt
@@ -0,0 +1,85 @@
+package app.lawnchair.ui.preferences.components.folder
+
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.OutlinedTextFieldDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import app.lawnchair.ui.ModalBottomSheetContent
+import app.lawnchair.ui.preferences.components.layout.ClickableIcon
+import app.lawnchair.ui.preferences.destinations.Action
+import app.lawnchair.ui.util.BottomSheetHandler
+import com.android.launcher3.R
+
+@Composable
+fun FolderBottomSheet(
+ label: String,
+ title: String,
+ onTitleChange: (String) -> Unit,
+ onAction: (Action) -> Unit,
+ action: Action,
+ defaultTitle: String,
+ bottomSheetHandler: BottomSheetHandler,
+ modifier: Modifier = Modifier,
+) {
+ bottomSheetHandler.show {
+ ModalBottomSheetContent(
+ title = { Text(label) },
+ buttons = {
+ OutlinedButton(onClick = {
+ onAction(Action.CANCEL)
+ }) {
+ Text(text = stringResource(id = android.R.string.cancel))
+ }
+ Spacer(modifier = Modifier.width(10.dp))
+ Button(
+ onClick = {
+ when (action) {
+ Action.ADD -> onAction(Action.SAVE)
+ Action.EDIT -> onAction(Action.UPDATE)
+ else -> {}
+ }
+ },
+ ) {
+ Text(text = stringResource(id = R.string.apply_label))
+ }
+ },
+ ) {
+ OutlinedTextField(
+ value = title,
+ onValueChange = {
+ onTitleChange(it)
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp),
+ trailingIcon = {
+ if (title != defaultTitle) {
+ ClickableIcon(
+ painter = painterResource(id = R.drawable.ic_undo),
+ onClick = { onTitleChange(defaultTitle) },
+ )
+ }
+ },
+ singleLine = true,
+ colors = OutlinedTextFieldDefaults.colors(
+ unfocusedContainerColor = MaterialTheme.colorScheme.surface,
+ focusedTextColor = MaterialTheme.colorScheme.onSurface,
+ ),
+ shape = MaterialTheme.shapes.large,
+ label = { Text(text = stringResource(id = R.string.label)) },
+ isError = title.isEmpty(),
+ )
+ }
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/ui/preferences/destinations/AppDrawerFolderPreferences.kt b/lawnchair/src/app/lawnchair/ui/preferences/destinations/AppDrawerFolderPreferences.kt
new file mode 100644
index 00000000000..b4a8e578c7b
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/ui/preferences/destinations/AppDrawerFolderPreferences.kt
@@ -0,0 +1,387 @@
+@file:Suppress("SYNTHETIC_PROPERTY_WITHOUT_JAVA_ORIGIN")
+
+package app.lawnchair.ui.preferences.destinations
+
+import android.util.Log
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+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.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.SwipeToDismissBox
+import androidx.compose.material3.SwipeToDismissBoxValue
+import androidx.compose.material3.Text
+import androidx.compose.material3.rememberSwipeToDismissBoxState
+import androidx.compose.material3.surfaceColorAtElevation
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import app.lawnchair.LawnchairLauncher
+import app.lawnchair.data.factory.ViewModelFactory
+import app.lawnchair.data.folder.model.FolderViewModel
+import app.lawnchair.launcher
+import app.lawnchair.preferences2.ReloadHelper
+import app.lawnchair.ui.preferences.LocalIsExpandedScreen
+import app.lawnchair.ui.preferences.LocalNavController
+import app.lawnchair.ui.preferences.components.calculateAlpha
+import app.lawnchair.ui.preferences.components.controls.ClickablePreference
+import app.lawnchair.ui.preferences.components.folder.FolderBottomSheet
+import app.lawnchair.ui.preferences.components.layout.LoadingScreen
+import app.lawnchair.ui.preferences.components.layout.PreferenceGroup
+import app.lawnchair.ui.preferences.components.layout.PreferenceLayoutLazyColumn
+import app.lawnchair.ui.preferences.components.layout.preferenceGroupItems
+import app.lawnchair.ui.preferences.navigation.Routes
+import app.lawnchair.ui.util.BottomSheetHandler
+import app.lawnchair.ui.util.bottomSheetHandler
+import com.android.launcher3.R
+import com.android.launcher3.model.data.FolderInfo
+
+@Composable
+fun AppDrawerFolderPreferences(
+ modifier: Modifier = Modifier,
+) {
+ val navController = LocalNavController.current
+
+ val onFolderSettingsClick: () -> Unit = {
+ navController.navigate(route = Routes.APP_DRAWER_FOLDER)
+ }
+
+ PreferenceGroup(
+ heading = stringResource(id = R.string.app_drawer_folder),
+ modifier = modifier,
+ ) {
+ ClickablePreference(
+ label = stringResource(R.string.app_drawer_folder_settings),
+ modifier = Modifier,
+ onClick = onFolderSettingsClick,
+ )
+ }
+}
+
+@Composable
+fun DrawerFolderPreferences(
+ modifier: Modifier = Modifier,
+) {
+ val navController = LocalNavController.current
+ val launcher = LawnchairLauncher.instance?.launcher
+ if (launcher == null) return
+ val viewModel: FolderViewModel = viewModel(factory = ViewModelFactory(launcher) { FolderViewModel(it) })
+
+ val folders by viewModel.folders.collectAsState()
+ val currentTitle by viewModel.currentTitle.collectAsState()
+ val action by viewModel.action.collectAsState()
+
+ val bottomSheetHandler = bottomSheetHandler
+ var folderInfoHolder by remember { mutableStateOf(null) }
+
+ if (action == Action.RESET || action == Action.SETTLE) {
+ folderInfoHolder = null
+ viewModel.setAction(Action.DEFAULT)
+ }
+
+ LaunchedEffect(Unit) {
+ viewModel.loadFolders()
+ }
+
+ LoadingScreen(obj = folders, modifier = modifier.fillMaxWidth()) { items ->
+ PreferenceLayoutLazyColumn(
+ modifier = Modifier.fillMaxSize(),
+ label = stringResource(id = R.string.app_drawer_folder),
+ backArrowVisible = true,
+ isExpandedScreen = LocalIsExpandedScreen.current,
+ ) {
+ item {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.End,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Button(
+ onClick = { viewModel.setAction(Action.ADD) },
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 16.dp, bottom = 16.dp, start = 14.dp, end = 14.dp),
+ ) {
+ Text(text = stringResource(id = R.string.add_label))
+ }
+ }
+ if (items.isNotEmpty()) {
+ Text(
+ text = stringResource(R.string.folder_list_note),
+ modifier = Modifier
+ .width(300.dp)
+ .padding(all = 16.dp),
+ color = MaterialTheme.colorScheme.onTertiaryContainer,
+ fontSize = 12.sp,
+ )
+ }
+ Spacer(modifier = Modifier.height(4.dp))
+ }
+
+ preferenceGroupItems(items, isFirstChild = true, dividerStartIndent = 40.dp) { index, folderInfo ->
+ key(folderInfo.id) {
+ FolderRowItem(
+ folderInfo = folderInfo,
+ action = action,
+ onAction = { item, fi ->
+ folderInfoHolder = fi
+ if (item in listOf(Action.EDIT, Action.DELETE, Action.OPEN)) {
+ viewModel.setAction(item)
+ }
+ },
+ )
+ }
+ }
+ }
+ }
+
+ if (action != Action.SETTLE && (action == Action.EDIT || action == Action.ADD)) {
+ FolderBottomSheet(
+ label = if (action == Action.ADD) stringResource(id = R.string.add_folder) else stringResource(id = R.string.edit_folder),
+ title = currentTitle,
+ onTitleChange = { newTitle -> viewModel.updateCurrentTitle(newTitle) },
+ onAction = { item ->
+ viewModel.setAction(item)
+ },
+ action = action,
+ defaultTitle = currentTitle,
+ bottomSheetHandler = bottomSheetHandler,
+ )
+ }
+
+ HandleActions(
+ launcher = launcher,
+ action = action,
+ folderInfoHolder = folderInfoHolder,
+ currentTitle = currentTitle,
+ navController = navController,
+ viewModel = viewModel,
+ bottomSheetHandler = bottomSheetHandler,
+ )
+}
+
+// TODO
+@Composable
+fun HandleActions(
+ action: Action,
+ folderInfoHolder: FolderInfo?,
+ currentTitle: String,
+ navController: NavController,
+ viewModel: FolderViewModel,
+ bottomSheetHandler: BottomSheetHandler,
+ launcher: LawnchairLauncher,
+) {
+ var loggedAction: String? = null
+
+ val reloadHelper = ReloadHelper(launcher)
+
+ when (action) {
+ Action.OPEN -> {
+ folderInfoHolder?.id?.let {
+ navController.navigate("${Routes.APP_LIST_TO_FOLDER}/$it")
+ viewModel.setAction(Action.DEFAULT)
+ }
+ }
+ Action.SAVE -> {
+ val folderInfo = FolderInfo().apply { title = currentTitle }
+ viewModel.saveFolder(folderInfo)
+ bottomSheetHandler.hide()
+ reloadHelper.reloadGrid()
+ viewModel.setAction(Action.SETTLE)
+ loggedAction = "Saved folder: $currentTitle"
+ }
+ Action.UPDATE -> {
+ folderInfoHolder?.apply {
+ title = currentTitle
+ viewModel.updateFolderInfo(this, false)
+ }
+ bottomSheetHandler.hide()
+ reloadHelper.recreate()
+ viewModel.setAction(Action.SETTLE)
+ loggedAction = "Updated folder: ${folderInfoHolder?.title}"
+ }
+ Action.CANCEL -> {
+ viewModel.refreshFolders()
+ bottomSheetHandler.hide()
+ viewModel.setAction(Action.RESET)
+ loggedAction = "Cancelled action"
+ }
+ Action.DELETE -> {
+ folderInfoHolder?.let {
+ viewModel.deleteFolderInfo(it.id)
+ }
+ reloadHelper.reloadGrid()
+ viewModel.setAction(Action.RESET)
+ loggedAction = "Deleted folder: ${folderInfoHolder?.title}"
+ }
+ Action.RESET -> {
+ viewModel.setAction(Action.DEFAULT)
+ loggedAction = "Reset action"
+ }
+ else -> { /*no action*/ }
+ }
+
+ loggedAction?.let {
+ Log.i("FolderPreferences", it)
+ }
+}
+
+@Composable
+fun FolderRowItem(
+ folderInfo: FolderInfo,
+ action: Action,
+ onAction: (Action, FolderInfo?) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val state = rememberSwipeToDismissBoxState(
+ confirmValueChange = {
+ when (it) {
+ SwipeToDismissBoxValue.StartToEnd -> onAction(Action.DELETE, folderInfo)
+ SwipeToDismissBoxValue.EndToStart -> onAction(Action.EDIT, folderInfo)
+ SwipeToDismissBoxValue.Settled -> { /* no action*/ }
+ }
+ true
+ },
+ )
+
+ LaunchedEffect(action) {
+ if (action == Action.DEFAULT) {
+ Log.d("FolderPreferences", "Action requires state reset ${action.name}")
+ state.reset()
+ }
+ }
+
+ SwipeToDismissBox(
+ modifier = modifier,
+ state = state,
+ backgroundContent = {
+ val backgroundColor = when (state.currentValue) {
+ SwipeToDismissBoxValue.StartToEnd -> MaterialTheme.colorScheme.errorContainer
+ SwipeToDismissBoxValue.EndToStart -> MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)
+ else -> Color.Transparent
+ }
+
+ val alignment = when (state.currentValue) {
+ SwipeToDismissBoxValue.StartToEnd -> Alignment.CenterStart
+ SwipeToDismissBoxValue.EndToStart -> Alignment.CenterEnd
+ else -> Alignment.Center
+ }
+
+ val labelText = when (state.currentValue) {
+ SwipeToDismissBoxValue.StartToEnd -> stringResource(R.string.delete_label)
+ SwipeToDismissBoxValue.EndToStart -> stringResource(R.string.edit_label)
+ else -> ""
+ }
+
+ val textColor = when (state.currentValue) {
+ SwipeToDismissBoxValue.StartToEnd -> MaterialTheme.colorScheme.onErrorContainer
+ SwipeToDismissBoxValue.EndToStart -> MaterialTheme.colorScheme.onSurface
+ else -> Color.Transparent
+ }
+
+ Surface(
+ modifier = Modifier
+ .alpha(
+ if (state.dismissDirection != SwipeToDismissBoxValue.Settled) {
+ 1f
+ } else {
+ calculateAlpha(
+ state.progress,
+ )
+ },
+ )
+ .fillMaxSize(),
+ shape = MaterialTheme.shapes.large,
+ color = backgroundColor,
+ ) {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = alignment,
+ ) {
+ Text(
+ text = labelText,
+ style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold),
+ color = textColor,
+ modifier = Modifier.padding(horizontal = 16.dp),
+ )
+ }
+ }
+ },
+ ) {
+ Surface(
+ modifier = Modifier
+ .fillMaxWidth()
+ .alpha(
+ if (state.dismissDirection != SwipeToDismissBoxValue.StartToEnd) {
+ 1f
+ } else {
+ calculateAlpha(
+ state.progress,
+ )
+ },
+ ),
+ color = Color.Transparent,
+ shape = MaterialTheme.shapes.large,
+ onClick = { onAction(Action.OPEN, folderInfo) },
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Start,
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_apps),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onSurface,
+ )
+ Spacer(Modifier.width(10.dp))
+ Text(
+ text = folderInfo.title.toString() + " (" + folderInfo.contents.size + ")",
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.bodyLarge,
+ modifier = Modifier,
+ )
+ }
+ }
+ }
+}
+
+enum class Action {
+ ADD,
+ EDIT,
+ DELETE,
+ OPEN,
+ CANCEL,
+ SETTLE,
+ SAVE,
+ UPDATE,
+ RESET,
+ DEFAULT,
+}
diff --git a/lawnchair/src/app/lawnchair/ui/preferences/destinations/AppDrawerSettings.kt b/lawnchair/src/app/lawnchair/ui/preferences/destinations/AppDrawerLayoutSettings.kt
similarity index 98%
rename from lawnchair/src/app/lawnchair/ui/preferences/destinations/AppDrawerSettings.kt
rename to lawnchair/src/app/lawnchair/ui/preferences/destinations/AppDrawerLayoutSettings.kt
index 0bb53e3d336..82d4983f4e3 100644
--- a/lawnchair/src/app/lawnchair/ui/preferences/destinations/AppDrawerSettings.kt
+++ b/lawnchair/src/app/lawnchair/ui/preferences/destinations/AppDrawerLayoutSettings.kt
@@ -40,6 +40,7 @@ import com.android.launcher3.R
@Composable
fun AppDrawerLayoutSettings(
modifier: Modifier = Modifier,
+ onOptionSelect: (Boolean) -> Unit = {},
) {
val prefs = preferenceManager()
val context = LocalContext.current
@@ -63,6 +64,7 @@ fun AppDrawerLayoutSettings(
onClick = {
selectedOption = true
prefs.drawerList.set(true)
+ onOptionSelect(true)
},
gridLayout = {
Column(modifier = Modifier, horizontalAlignment = Alignment.CenterHorizontally) {
@@ -94,6 +96,7 @@ fun AppDrawerLayoutSettings(
onClick = {
selectedOption = false
prefs.drawerList.set(false)
+ onOptionSelect(false)
},
gridLayout = {
Column(modifier = Modifier, horizontalAlignment = Alignment.CenterHorizontally) {
diff --git a/lawnchair/src/app/lawnchair/ui/preferences/destinations/AppDrawerPreferences.kt b/lawnchair/src/app/lawnchair/ui/preferences/destinations/AppDrawerPreferences.kt
index ec8970d02cf..b8bcd985172 100644
--- a/lawnchair/src/app/lawnchair/ui/preferences/destinations/AppDrawerPreferences.kt
+++ b/lawnchair/src/app/lawnchair/ui/preferences/destinations/AppDrawerPreferences.kt
@@ -17,6 +17,10 @@
package app.lawnchair.ui.preferences.destinations
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -48,12 +52,19 @@ fun AppDrawerPreferences(
val context = LocalContext.current
val resources = context.resources
+ var selectedOption by remember { mutableStateOf(prefs.drawerList.get()) }
+
PreferenceLayout(
label = stringResource(id = R.string.app_drawer_label),
backArrowVisible = !LocalIsExpandedScreen.current,
modifier = modifier,
) {
- AppDrawerLayoutSettings()
+ AppDrawerLayoutSettings(onOptionSelect = { isSelected -> selectedOption = isSelected })
+ ExpandAndShrink(visible = selectedOption) {
+ DividerColumn {
+ AppDrawerFolderPreferences()
+ }
+ }
PreferenceGroup(heading = stringResource(id = R.string.general_label)) {
ColorPreference(preference = prefs2.appDrawerBackgroundColor)
SliderPreference(
diff --git a/lawnchair/src/app/lawnchair/ui/preferences/navigation/PreferenceNavigation.kt b/lawnchair/src/app/lawnchair/ui/preferences/navigation/PreferenceNavigation.kt
index 7cd4c95768a..77d516e34a8 100644
--- a/lawnchair/src/app/lawnchair/ui/preferences/navigation/PreferenceNavigation.kt
+++ b/lawnchair/src/app/lawnchair/ui/preferences/navigation/PreferenceNavigation.kt
@@ -19,12 +19,14 @@ import app.lawnchair.ui.preferences.about.AboutRoutes
import app.lawnchair.ui.preferences.about.acknowledgements.Acknowledgements
import app.lawnchair.ui.preferences.components.colorpreference.ColorPreferenceModelList
import app.lawnchair.ui.preferences.components.colorpreference.ColorSelection
+import app.lawnchair.ui.preferences.components.folder.AppListToFolderPreferences
import app.lawnchair.ui.preferences.destinations.AppDrawerPreferences
import app.lawnchair.ui.preferences.destinations.AppDrawerRoutes
import app.lawnchair.ui.preferences.destinations.CustomIconShapePreference
import app.lawnchair.ui.preferences.destinations.DebugMenuPreferences
import app.lawnchair.ui.preferences.destinations.DockPreferences
import app.lawnchair.ui.preferences.destinations.DockRoutes
+import app.lawnchair.ui.preferences.destinations.DrawerFolderPreferences
import app.lawnchair.ui.preferences.destinations.DummyPreference
import app.lawnchair.ui.preferences.destinations.ExperimentalFeaturesPreferences
import app.lawnchair.ui.preferences.destinations.FolderPreferences
@@ -119,6 +121,7 @@ fun InnerNavigation(
composable(route = "main") { AppDrawerPreferences() }
composable(route = AppDrawerRoutes.HIDDEN_APPS) { HiddenAppsPreferences() }
}
+ composable(route = Routes.APP_DRAWER_FOLDER) { DrawerFolderPreferences() }
composable(
route = "${Routes.SEARCH}/{selectedId}",
@@ -166,6 +169,16 @@ fun InnerNavigation(
val packageName = args.getString("packageName")!!
IconPickerPreference(packageName)
}
+ composable(
+ route = "${Routes.APP_LIST_TO_FOLDER}/{id}",
+ arguments = listOf(
+ navArgument("id") { type = NavType.IntType },
+ ),
+ ) { backStackEntry ->
+ val args = backStackEntry.arguments!!
+ val folderInfoId = args.getInt("id")
+ AppListToFolderPreferences(folderInfoId)
+ }
composable(route = Routes.EXPERIMENTAL_FEATURES) { ExperimentalFeaturesPreferences() }
composable(
@@ -211,4 +224,6 @@ object Routes {
const val PICK_APP_FOR_GESTURE = "pickAppForGesture"
const val GESTURES = "gestures"
const val SEARCH = "search"
+ const val APP_DRAWER_FOLDER = "appDrawerFolder"
+ const val APP_LIST_TO_FOLDER = "appListToFolder"
}
diff --git a/lawnchair/src/app/lawnchair/util/AppsList.kt b/lawnchair/src/app/lawnchair/util/AppsList.kt
index 991cd1f17a5..24a67beece8 100644
--- a/lawnchair/src/app/lawnchair/util/AppsList.kt
+++ b/lawnchair/src/app/lawnchair/util/AppsList.kt
@@ -35,6 +35,7 @@ import com.android.launcher3.LauncherAppState
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.pm.UserCache
import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
@@ -82,6 +83,16 @@ class App(context: Context, private val info: LauncherActivityInfo) {
val appComparator: Comparator = comparing { it.label.lowercase(Locale.getDefault()) }
val packageInfoCache = mutableMapOf()
+fun List.sortedBySelection(selectedAppsState: Set): List {
+ return sortedWith(
+ compareBy { app ->
+ selectedAppsState.none { it is AppInfo && it.targetPackage == app.targetPackage }
+ }.thenBy { app ->
+ app.title.toString().lowercase(Locale.getDefault())
+ },
+ )
+}
+
fun categorizeApps(context: Context, appList: List?): Map> {
val categories = mutableMapOf>()
val validAppList = appList?.filterNotNull() ?: emptyList()
diff --git a/lawnchair/src/app/lawnchair/wallpaper/WallpaperManagerCompat.kt b/lawnchair/src/app/lawnchair/wallpaper/WallpaperManagerCompat.kt
index 79a84cfaccb..27c32608be7 100644
--- a/lawnchair/src/app/lawnchair/wallpaper/WallpaperManagerCompat.kt
+++ b/lawnchair/src/app/lawnchair/wallpaper/WallpaperManagerCompat.kt
@@ -2,10 +2,10 @@ package app.lawnchair.wallpaper
import android.app.WallpaperManager
import android.content.Context
+import app.lawnchair.data.wallpaper.service.WallpaperService
import app.lawnchair.util.MainThreadInitializedObject
import app.lawnchair.util.requireSystemService
import app.lawnchair.wallpaper.WallpaperColorsCompat.Companion.HINT_SUPPORTS_DARK_THEME
-import app.lawnchair.wallpaper.service.WallpaperService
import com.android.launcher3.Utilities
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
diff --git a/lawnchair/src/app/lawnchair/wallpaper/model/WallpaperViewModelFactory.kt b/lawnchair/src/app/lawnchair/wallpaper/model/WallpaperViewModelFactory.kt
deleted file mode 100644
index 805eae11189..00000000000
--- a/lawnchair/src/app/lawnchair/wallpaper/model/WallpaperViewModelFactory.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package app.lawnchair.wallpaper.model
-
-import android.content.Context
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-
-class WallpaperViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
- override fun create(modelClass: Class): T {
- if (modelClass.isAssignableFrom(WallpaperViewModel::class.java)) {
- @Suppress("UNCHECKED_CAST")
- return WallpaperViewModel(context) as T
- }
- throw IllegalArgumentException("Unknown ViewModel class")
- }
-}
diff --git a/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDatabase.kt b/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDatabase.kt
deleted file mode 100644
index af7bf1cb8bf..00000000000
--- a/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDatabase.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-package app.lawnchair.wallpaper.service
-
-import androidx.room.Database
-import androidx.room.Room
-import androidx.room.RoomDatabase
-import androidx.room.migration.Migration
-import androidx.sqlite.db.SimpleSQLiteQuery
-import androidx.sqlite.db.SupportSQLiteDatabase
-import app.lawnchair.util.MainThreadInitializedObject
-import kotlinx.coroutines.runBlocking
-
-@Database(entities = [Wallpaper::class], version = 2, exportSchema = false)
-abstract class WallpaperDatabase : RoomDatabase() {
-
- abstract fun wallpaperDao(): WallpaperDao
-
- private suspend fun checkpoint() {
- wallpaperDao().checkpoint(SimpleSQLiteQuery("pragma wal_checkpoint(full)"))
- }
-
- fun checkpointSync() {
- runBlocking {
- checkpoint()
- }
- }
-
- companion object {
- private val MIGRATION_1_2 = object : Migration(1, 2) {
- override fun migrate(db: SupportSQLiteDatabase) {
- db.execSQL("ALTER TABLE wallpapers ADD COLUMN checksum TEXT DEFAULT 'undefined'")
- }
- }
- val INSTANCE = MainThreadInitializedObject { context ->
- Room.databaseBuilder(
- context,
- WallpaperDatabase::class.java,
- "wallpaper_database",
- ).addMigrations(MIGRATION_1_2).build()
- }
- }
-}
diff --git a/schemas/app.lawnchair.data.AppDatabase/2.json b/schemas/app.lawnchair.data.AppDatabase/2.json
new file mode 100644
index 00000000000..9f028c8ef6f
--- /dev/null
+++ b/schemas/app.lawnchair.data.AppDatabase/2.json
@@ -0,0 +1,202 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 2,
+ "identityHash": "4a297a47ffad63090079bb7bfc5e09f2",
+ "entities": [
+ {
+ "tableName": "IconOverride",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`target` TEXT NOT NULL, `packPackageName` TEXT NOT NULL, `drawableName` TEXT NOT NULL, `label` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`target`))",
+ "fields": [
+ {
+ "fieldPath": "target",
+ "columnName": "target",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "iconPickerItem.packPackageName",
+ "columnName": "packPackageName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "iconPickerItem.drawableName",
+ "columnName": "drawableName",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "iconPickerItem.label",
+ "columnName": "label",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "iconPickerItem.type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "target"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Wallpapers",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `imagePath` TEXT NOT NULL, `rank` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `checksum` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "imagePath",
+ "columnName": "imagePath",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "rank",
+ "columnName": "rank",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "timestamp",
+ "columnName": "timestamp",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "checksum",
+ "columnName": "checksum",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Folders",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `hide` INTEGER NOT NULL, `rank` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hide",
+ "columnName": "hide",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "rank",
+ "columnName": "rank",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "timestamp",
+ "columnName": "timestamp",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "FolderItems",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `folderId` INTEGER NOT NULL, `rank` INTEGER NOT NULL, `item_info` TEXT, `timestamp` INTEGER NOT NULL, FOREIGN KEY(`folderId`) REFERENCES `Folders`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "folderId",
+ "columnName": "folderId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "rank",
+ "columnName": "rank",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "componentKey",
+ "columnName": "item_info",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "timestamp",
+ "columnName": "timestamp",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": [
+ {
+ "table": "Folders",
+ "onDelete": "CASCADE",
+ "onUpdate": "CASCADE",
+ "columns": [
+ "folderId"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4a297a47ffad63090079bb7bfc5e09f2')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 4f3ec7d8c88..9ab7b6452b2 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -56,7 +56,6 @@
import app.lawnchair.preferences2.PreferenceManager2;
import app.lawnchair.ui.popup.LauncherOptionsPopup;
-import app.lawnchair.wallpaper.service.WallpaperService;
/**
* Popup shown on long pressing an empty space in launcher