mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-11 14:54:00 +00:00
feat(deck): app categorization to folders using flowerpot
- Categorize apps using flowerpot and organize into folders - Add blocking loading dialog during categorization process - Prevent user cancellation during workspace setup - Auto-add newly installed apps to appropriate folders - closes : #5846
This commit is contained in:
191
lawnchair/src/app/lawnchair/deck/AddFoldersWithItemsTask.kt
Normal file
191
lawnchair/src/app/lawnchair/deck/AddFoldersWithItemsTask.kt
Normal file
@@ -0,0 +1,191 @@
|
||||
package app.lawnchair.deck
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.UserHandle
|
||||
import android.util.Pair
|
||||
import com.android.launcher3.LauncherAppState
|
||||
import com.android.launcher3.LauncherModel
|
||||
import com.android.launcher3.LauncherSettings
|
||||
import com.android.launcher3.model.AllAppsList
|
||||
import com.android.launcher3.model.BgDataModel
|
||||
import com.android.launcher3.model.ModelTaskController
|
||||
import com.android.launcher3.model.WorkspaceItemSpaceFinder
|
||||
import com.android.launcher3.model.data.CollectionInfo
|
||||
import com.android.launcher3.model.data.FolderInfo
|
||||
import com.android.launcher3.model.data.ItemInfo
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo
|
||||
import com.android.launcher3.util.IntArray
|
||||
import com.android.launcher3.util.PackageManagerHelper
|
||||
|
||||
/**
|
||||
* Custom model task to add folders with their items to the workspace.
|
||||
* This properly handles adding folders and then adding items to those folders.
|
||||
*/
|
||||
class AddFoldersWithItemsTask(
|
||||
private val folders: List<FolderInfo>,
|
||||
private val onComplete: (() -> Unit)? = null,
|
||||
) : LauncherModel.ModelUpdateTask {
|
||||
|
||||
private val itemSpaceFinder = WorkspaceItemSpaceFinder()
|
||||
|
||||
override fun execute(
|
||||
taskController: ModelTaskController,
|
||||
dataModel: BgDataModel,
|
||||
apps: AllAppsList,
|
||||
) {
|
||||
if (folders.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val context = taskController.app.context
|
||||
val addedItemsFinal = ArrayList<ItemInfo>()
|
||||
val addedWorkspaceScreensFinal = IntArray()
|
||||
|
||||
synchronized(dataModel) {
|
||||
val workspaceScreens = dataModel.collectWorkspaceScreens()
|
||||
val modelWriter = taskController.getModelWriter()
|
||||
|
||||
folders.forEach { folderInfo ->
|
||||
// Find space for the folder
|
||||
val coords = itemSpaceFinder.findSpaceForItem(
|
||||
taskController.app,
|
||||
dataModel,
|
||||
workspaceScreens,
|
||||
addedWorkspaceScreensFinal,
|
||||
folderInfo.spanX,
|
||||
folderInfo.spanY,
|
||||
)
|
||||
val screenId = coords[0]
|
||||
val cellX = coords[1]
|
||||
val cellY = coords[2]
|
||||
|
||||
// Add folder to database
|
||||
modelWriter.addItemToDatabase(
|
||||
folderInfo,
|
||||
LauncherSettings.Favorites.CONTAINER_DESKTOP,
|
||||
screenId,
|
||||
cellX,
|
||||
cellY,
|
||||
)
|
||||
|
||||
// Now add items to the folder
|
||||
// Items need to be added with proper rank/position
|
||||
folderInfo.getContents().forEachIndexed { index, item ->
|
||||
if (item is WorkspaceItemInfo) {
|
||||
// Check if item already exists on workspace
|
||||
if (shortcutExists(dataModel, item.intent, item.user)) {
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
// Add item to folder using folder's ID as container
|
||||
// Use rank as position - folder will arrange items
|
||||
modelWriter.addOrMoveItemInDatabase(
|
||||
item,
|
||||
folderInfo.id,
|
||||
0, // screenId is 0 for items in folders
|
||||
index % 4, // cellX - approximate grid position
|
||||
index / 4, // cellY - approximate grid position
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
addedItemsFinal.add(folderInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule callback to bind items
|
||||
if (addedItemsFinal.isNotEmpty()) {
|
||||
taskController.scheduleCallbackTask { callbacks ->
|
||||
val addAnimated = ArrayList<ItemInfo>()
|
||||
val addNotAnimated = ArrayList<ItemInfo>()
|
||||
|
||||
if (addedItemsFinal.isNotEmpty()) {
|
||||
val lastScreenId = addedItemsFinal.last().screenId
|
||||
addedItemsFinal.forEach { item ->
|
||||
if (item.screenId == lastScreenId) {
|
||||
addAnimated.add(item)
|
||||
} else {
|
||||
addNotAnimated.add(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callbacks.bindAppsAdded(
|
||||
addedWorkspaceScreensFinal,
|
||||
ArrayList(addNotAnimated),
|
||||
ArrayList(addAnimated),
|
||||
)
|
||||
|
||||
// Notify completion after items are bound
|
||||
onComplete?.invoke()
|
||||
}
|
||||
} else {
|
||||
// No items to add, notify completion immediately
|
||||
onComplete?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the shortcut already exists on the workspace.
|
||||
* Based on AddWorkspaceItemsTask.shortcutExists
|
||||
*/
|
||||
private fun shortcutExists(
|
||||
dataModel: BgDataModel,
|
||||
intent: Intent?,
|
||||
user: UserHandle,
|
||||
): Boolean {
|
||||
if (intent == null) {
|
||||
return true
|
||||
}
|
||||
|
||||
val compPkgName: String?
|
||||
val intentWithPkg: String
|
||||
val intentWithoutPkg: String
|
||||
|
||||
if (intent.component != null) {
|
||||
compPkgName = intent.component!!.packageName
|
||||
if (intent.`package` != null) {
|
||||
intentWithPkg = intent.toUri(0)
|
||||
intentWithoutPkg = Intent(intent).apply { `package` = null }.toUri(0)
|
||||
} else {
|
||||
intentWithPkg = Intent(intent).apply { `package` = compPkgName }.toUri(0)
|
||||
intentWithoutPkg = intent.toUri(0)
|
||||
}
|
||||
} else {
|
||||
compPkgName = null
|
||||
intentWithPkg = intent.toUri(0)
|
||||
intentWithoutPkg = intent.toUri(0)
|
||||
}
|
||||
|
||||
val isLauncherAppTarget = PackageManagerHelper.isLauncherAppTarget(intent)
|
||||
|
||||
synchronized(dataModel) {
|
||||
dataModel.itemsIdMap.forEach { existingItem ->
|
||||
if (existingItem is WorkspaceItemInfo) {
|
||||
val existingIntent = existingItem.intent
|
||||
if (existingIntent != null && existingItem.user == user) {
|
||||
val copyIntent = Intent(existingIntent)
|
||||
copyIntent.sourceBounds = intent.sourceBounds
|
||||
val s = copyIntent.toUri(0)
|
||||
if (intentWithPkg == s || intentWithoutPkg == s) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check for existing promise icon with same package name
|
||||
if (isLauncherAppTarget &&
|
||||
existingItem.isPromise() &&
|
||||
existingItem.hasStatusFlag(WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON) &&
|
||||
existingItem.targetComponent != null &&
|
||||
compPkgName != null &&
|
||||
compPkgName == existingItem.targetComponent!!.packageName
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,23 @@ package app.lawnchair.deck
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import app.lawnchair.LawnchairLauncher
|
||||
import app.lawnchair.flowerpot.Flowerpot
|
||||
import app.lawnchair.launcher
|
||||
import app.lawnchair.launcherNullable
|
||||
import app.lawnchair.util.restartLauncher
|
||||
import com.android.launcher3.InvariantDeviceProfile
|
||||
import com.android.launcher3.LauncherAppState
|
||||
import com.android.launcher3.LauncherSettings
|
||||
import com.android.launcher3.model.ItemInstallQueue
|
||||
import com.android.launcher3.model.ModelDbController
|
||||
import com.android.launcher3.model.data.AppInfo
|
||||
import com.android.launcher3.model.data.FolderInfo
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo
|
||||
import com.android.launcher3.provider.RestoreDbTask
|
||||
import com.android.launcher3.util.ComponentKey
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -21,13 +29,24 @@ class LawndeckManager(private val context: Context) {
|
||||
|
||||
private val launcher = context.launcherNullable ?: LawnchairLauncher.instance?.launcher
|
||||
|
||||
suspend fun enableLawndeck() = withContext(Dispatchers.IO) {
|
||||
suspend fun enableLawndeck(
|
||||
onProgress: ((String) -> Unit)? = null,
|
||||
) = withContext(Dispatchers.IO) {
|
||||
val completionDeferred = CompletableDeferred<Unit>()
|
||||
|
||||
if (!backupExists("bk")) createBackup("bk")
|
||||
if (backupExists("lawndeck")) {
|
||||
onProgress?.invoke("Restoring previous layout...")
|
||||
restoreBackup("lawndeck")
|
||||
completionDeferred.complete(Unit)
|
||||
} else {
|
||||
addAllAppsToWorkspace()
|
||||
onProgress?.invoke("Categorizing apps...")
|
||||
addAllAppsToWorkspace(onProgress) {
|
||||
completionDeferred.complete(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
completionDeferred.await()
|
||||
}
|
||||
|
||||
suspend fun disableLawndeck() = withContext(Dispatchers.IO) {
|
||||
@@ -70,12 +89,171 @@ class LawndeckManager(private val context: Context) {
|
||||
restartLauncher(context)
|
||||
}
|
||||
|
||||
private fun addAllAppsToWorkspace() {
|
||||
launcher?.mAppsView?.appsStore?.apps
|
||||
?.sortedBy { it.title?.toString()?.lowercase(Locale.getDefault()) }
|
||||
?.forEach { app ->
|
||||
private fun addAllAppsToWorkspace(
|
||||
onProgress: ((String) -> Unit)?,
|
||||
onComplete: (() -> Unit)?,
|
||||
) {
|
||||
val apps = launcher?.mAppsView?.appsStore?.apps ?: return
|
||||
if (apps.isEmpty()) {
|
||||
onComplete?.invoke()
|
||||
return
|
||||
}
|
||||
|
||||
onProgress?.invoke("Categorizing apps...")
|
||||
|
||||
// Use flowerpot to categorize apps
|
||||
val potsManager = Flowerpot.Manager.getInstance(context)
|
||||
val categorizedApps = potsManager.categorizeApps(apps.map { it as? AppInfo })
|
||||
|
||||
onProgress?.invoke("Adding apps to workspace...")
|
||||
|
||||
val launcher = this.launcher ?: return
|
||||
val model = launcher.model
|
||||
|
||||
// Collect folders to add and count single apps
|
||||
val foldersToAdd = mutableListOf<FolderInfo>()
|
||||
var singleAppCount = 0
|
||||
|
||||
// Process each category
|
||||
categorizedApps.forEach { (category, categoryApps) ->
|
||||
if (categoryApps.isEmpty()) return@forEach
|
||||
|
||||
if (categoryApps.size == 1) {
|
||||
// Single app - add directly to workspace
|
||||
val app = categoryApps.first()
|
||||
ItemInstallQueue.INSTANCE.get(context).queueItem(app.targetPackage, app.user)
|
||||
singleAppCount++
|
||||
} else {
|
||||
// Multiple apps - create folder
|
||||
onProgress?.invoke("Creating folder: $category...")
|
||||
val folderInfo = createFolderInfo(category, categoryApps)
|
||||
if (folderInfo != null) {
|
||||
foldersToAdd.add(folderInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add all folders with their items to workspace using custom task
|
||||
if (foldersToAdd.isNotEmpty()) {
|
||||
// Wait for folder task to complete
|
||||
model.enqueueModelUpdateTask(
|
||||
AddFoldersWithItemsTask(foldersToAdd) {
|
||||
// Callback runs on UI thread from model task
|
||||
// Also wait for ItemInstallQueue to finish for single apps
|
||||
// ItemInstallQueue processes asynchronously, so we need to wait a bit
|
||||
if (singleAppCount > 0) {
|
||||
// Post to handler to give ItemInstallQueue time to process
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
||||
onComplete?.invoke()
|
||||
}, 800) // Wait for queue to process
|
||||
} else {
|
||||
onComplete?.invoke()
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
// No folders, but may have single apps
|
||||
if (singleAppCount > 0) {
|
||||
// Give ItemInstallQueue time to process
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
||||
onComplete?.invoke()
|
||||
}, 800) // Wait for queue to process
|
||||
} else {
|
||||
onComplete?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a newly installed app to the workspace with proper categorization.
|
||||
* This is called when a new app is installed and deck layout is enabled.
|
||||
*
|
||||
* @param packageName The package name of the newly installed app
|
||||
* @param user The user handle for the app
|
||||
* @param modelWriter The ModelWriter to use for database operations (must be called from model thread)
|
||||
* @param dataModel The BgDataModel to search for existing folders
|
||||
*/
|
||||
fun addNewlyInstalledApp(
|
||||
packageName: String,
|
||||
user: android.os.UserHandle,
|
||||
modelWriter: com.android.launcher3.model.ModelWriter,
|
||||
dataModel: com.android.launcher3.model.BgDataModel,
|
||||
) {
|
||||
// Get app info from LauncherApps directly (app might not be in all apps list yet)
|
||||
val launcherApps = context.getSystemService(android.content.pm.LauncherApps::class.java)
|
||||
?: return
|
||||
val activities = launcherApps.getActivityList(packageName, user)
|
||||
if (activities.isEmpty()) return
|
||||
|
||||
val activityInfo = activities[0]
|
||||
val appInfo = AppInfo(context, activityInfo, user)
|
||||
|
||||
// Use flowerpot to categorize the app
|
||||
val potsManager = Flowerpot.Manager.getInstance(context)
|
||||
val categorizedApps = potsManager.categorizeApps(listOf(appInfo))
|
||||
|
||||
if (categorizedApps.isEmpty()) {
|
||||
// No category found, add directly to workspace
|
||||
ItemInstallQueue.INSTANCE.get(context).queueItem(packageName, user)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the category for this app (categorizedApps is a Map<String, List<AppInfo>>)
|
||||
val categoryEntry = categorizedApps.entries.firstOrNull() ?: run {
|
||||
ItemInstallQueue.INSTANCE.get(context).queueItem(packageName, user)
|
||||
return
|
||||
}
|
||||
val category = categoryEntry.key
|
||||
|
||||
// Check if there's already a folder for this category on workspace
|
||||
val existingFolder = findFolderByCategory(category, dataModel)
|
||||
|
||||
if (existingFolder != null) {
|
||||
// Add app to existing folder
|
||||
val workspaceItem = appInfo.makeWorkspaceItem(context) ?: return
|
||||
existingFolder.add(workspaceItem)
|
||||
// Update folder in database
|
||||
modelWriter.addOrMoveItemInDatabase(
|
||||
workspaceItem,
|
||||
existingFolder.id,
|
||||
0,
|
||||
existingFolder.getContents().size % 4,
|
||||
existingFolder.getContents().size / 4,
|
||||
)
|
||||
} else {
|
||||
// Single app in category, add directly to workspace
|
||||
// The app will be categorized properly when added
|
||||
ItemInstallQueue.INSTANCE.get(context).queueItem(packageName, user)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findFolderByCategory(category: String, dataModel: com.android.launcher3.model.BgDataModel): FolderInfo? {
|
||||
// Search through workspace items to find folder with matching category name
|
||||
synchronized(dataModel) {
|
||||
dataModel.itemsIdMap.forEach { item ->
|
||||
if (item is FolderInfo && item.title?.toString() == category) {
|
||||
return item
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun createFolderInfo(categoryName: String, apps: List<AppInfo>): FolderInfo? {
|
||||
if (apps.isEmpty()) return null
|
||||
|
||||
val folderInfo = FolderInfo().apply {
|
||||
title = categoryName
|
||||
}
|
||||
|
||||
// Create workspace items for each app and add to folder
|
||||
apps.forEach { app ->
|
||||
val workspaceItem = app.makeWorkspaceItem(context) ?: return@forEach
|
||||
folderInfo.add(workspaceItem)
|
||||
}
|
||||
|
||||
// Only return folder if it has items
|
||||
return if (folderInfo.getContents().isNotEmpty()) folderInfo else null
|
||||
}
|
||||
|
||||
private data class DatabaseFiles(
|
||||
|
||||
@@ -9,12 +9,15 @@ 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.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.movableContentOf
|
||||
@@ -26,6 +29,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.lawnchair.deck.LawndeckManager
|
||||
@@ -34,6 +38,7 @@ import app.lawnchair.preferences.getAdapter
|
||||
import app.lawnchair.preferences.preferenceManager
|
||||
import app.lawnchair.preferences2.preferenceManager2
|
||||
import app.lawnchair.ui.preferences.components.controls.SwitchPreferenceWithPreview
|
||||
import app.lawnchair.util.BackHandler
|
||||
import com.android.launcher3.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -55,21 +60,76 @@ fun HomeLayoutSettings(
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
var isLoading by remember { mutableStateOf(false) }
|
||||
var loadingMessage by remember { mutableStateOf("") }
|
||||
|
||||
// Block back button when loading
|
||||
if (isLoading) {
|
||||
BackHandler {
|
||||
// Prevent back button during loading - do nothing
|
||||
}
|
||||
}
|
||||
|
||||
// Show blocking loading dialog
|
||||
if (isLoading) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
// Prevent dismissal during loading
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.home_lawn_deck_label_beta),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
)
|
||||
if (loadingMessage.isNotEmpty()) {
|
||||
Text(
|
||||
text = loadingMessage,
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = "Please wait...",
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {},
|
||||
dismissButton = {},
|
||||
)
|
||||
}
|
||||
|
||||
SwitchPreferenceWithPreview(
|
||||
label = stringResource(R.string.layout),
|
||||
checked = deskLayout.state.value,
|
||||
onCheckedChange = { newValue ->
|
||||
isLoading = true
|
||||
loadingMessage = ""
|
||||
deskLayout.onChange(newValue)
|
||||
if (newValue) {
|
||||
coroutineScope.launch {
|
||||
swipeUpGesture.onChange(GestureHandlerConfig.NoOp)
|
||||
addNewAppToHome.onChange(true)
|
||||
withContext(Dispatchers.IO) {
|
||||
deckManager.enableLawndeck()
|
||||
isLoading = false
|
||||
deckManager.enableLawndeck { message ->
|
||||
// Update on main thread using coroutine scope
|
||||
coroutineScope.launch(Dispatchers.Main) {
|
||||
loadingMessage = message
|
||||
}
|
||||
}
|
||||
}
|
||||
isLoading = false
|
||||
loadingMessage = ""
|
||||
}
|
||||
} else {
|
||||
coroutineScope.launch {
|
||||
@@ -77,6 +137,7 @@ fun HomeLayoutSettings(
|
||||
withContext(Dispatchers.IO) {
|
||||
deckManager.disableLawndeck()
|
||||
isLoading = false
|
||||
loadingMessage = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ fun HomeScreenPreferences(
|
||||
val isDeckLayoutAdapter = prefs2.deckLayout.getAdapter()
|
||||
ExpandAndShrink(visible = !isDeckLayoutAdapter.state.value) {
|
||||
SwitchPreference(
|
||||
checked = !lockHomeScreenAdapter.state.value && addIconToHomeAdapter.state.value,
|
||||
checked = (!lockHomeScreenAdapter.state.value && addIconToHomeAdapter.state.value) || isDeckLayoutAdapter.state.value,
|
||||
onCheckedChange = addIconToHomeAdapter::onChange,
|
||||
label = stringResource(id = R.string.auto_add_shortcuts_label),
|
||||
description = if (lockHomeScreenAdapter.state.value) stringResource(id = R.string.home_screen_locked) else null,
|
||||
|
||||
@@ -68,7 +68,10 @@ import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import app.lawnchair.deck.LawndeckManager;
|
||||
import app.lawnchair.preferences.PreferenceManager;
|
||||
import app.lawnchair.preferences2.PreferenceManager2;
|
||||
import com.patrykmichalik.opto.core.PreferenceExtensionsKt;
|
||||
|
||||
/**
|
||||
* Handles updates due to changes in package manager (app installed/updated/removed)
|
||||
@@ -457,6 +460,16 @@ public class PackageUpdatedTask implements ModelUpdateTask {
|
||||
dataModel.widgetsModel.update(app, new PackageUserKey(packages[i], mUser));
|
||||
}
|
||||
taskController.bindUpdatedWidgets(dataModel);
|
||||
|
||||
// If deck layout is enabled, add newly installed apps to workspace with categorization
|
||||
PreferenceManager2 pref2 = PreferenceManager2.INSTANCE.get(context);
|
||||
if (PreferenceExtensionsKt.firstBlocking(pref2.getDeckLayout())) {
|
||||
LawndeckManager deckManager = new LawndeckManager(context);
|
||||
ModelWriter modelWriter = taskController.getModelWriter();
|
||||
for (int i = 0; i < N; i++) {
|
||||
deckManager.addNewlyInstalledApp(packages[i], mUser, modelWriter, dataModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user