Add private space support

This commit is contained in:
MM20 2024-07-19 17:13:06 +02:00
parent 99c98e4127
commit 3993f9505c
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
11 changed files with 206 additions and 101 deletions

View File

@ -120,6 +120,7 @@ dependencies {
implementation(project(":core:i18n")) implementation(project(":core:i18n"))
implementation(project(":core:compat")) implementation(project(":core:compat"))
implementation(project(":core:ktx")) implementation(project(":core:ktx"))
implementation(project(":core:profiles"))
implementation(project(":services:icons")) implementation(project(":services:icons"))
implementation(project(":services:music")) implementation(project(":services:music"))
implementation(project(":services:tags")) implementation(project(":services:tags"))

View File

@ -17,10 +17,8 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -74,8 +72,15 @@ fun SearchColumn(
val hideFavs by viewModel.hideFavorites val hideFavs by viewModel.hideFavorites
val favoritesEnabled by viewModel.favoritesEnabled.collectAsState(false) val favoritesEnabled by viewModel.favoritesEnabled.collectAsState(false)
val apps by viewModel.appResults val apps by viewModel.appResults
val workApps by viewModel.workAppResults val workApps by viewModel.workAppResults
val privateApps by viewModel.privateSpaceAppResults
val workProfile by viewModel.workProfile.collectAsState(null)
val workProfileState by viewModel.workProfileState.collectAsState(null)
val privateProfile by viewModel.privateProfile.collectAsState(null)
val privateProfileState by viewModel.privateProfileState.collectAsState(null)
val appShortcuts by viewModel.appShortcutResults val appShortcuts by viewModel.appShortcutResults
val contacts by viewModel.contactResults val contacts by viewModel.contactResults
val files by viewModel.fileResults val files by viewModel.fileResults
@ -96,26 +101,13 @@ fun SearchColumn(
val missingContactsPermission by viewModel.missingContactsPermission.collectAsState(false) val missingContactsPermission by viewModel.missingContactsPermission.collectAsState(false)
val missingLocationPermission by viewModel.missingLocationPermission.collectAsState(false) val missingLocationPermission by viewModel.missingLocationPermission.collectAsState(false)
val missingFilesPermission by viewModel.missingFilesPermission.collectAsState(false) val missingFilesPermission by viewModel.missingFilesPermission.collectAsState(false)
val hasProfilesPermission by viewModel.hasProfilesPermission.collectAsState(false)
val pinnedTags by favoritesVM.pinnedTags.collectAsState(emptyList()) val pinnedTags by favoritesVM.pinnedTags.collectAsState(emptyList())
val selectedTag by favoritesVM.selectedTag.collectAsState(null) val selectedTag by favoritesVM.selectedTag.collectAsState(null)
val favoritesEditButton by favoritesVM.showEditButton.collectAsState(false) val favoritesEditButton by favoritesVM.showEditButton.collectAsState(false)
val favoritesTagsExpanded by favoritesVM.tagsExpanded.collectAsState(false) val favoritesTagsExpanded by favoritesVM.tagsExpanded.collectAsState(false)
var showWorkProfileApps by remember { mutableStateOf(false) }
val separateWorkProfile by viewModel.separateWorkProfile.collectAsState(true)
val visibleApps by remember {
derivedStateOf {
when {
!separateWorkProfile -> (apps + workApps).sorted()
workApps.isEmpty() -> apps
apps.isEmpty() -> workApps
showWorkProfileApps -> workApps
else -> apps
}
}
}
val expandedCategory: SearchCategory? by viewModel.expandedCategory val expandedCategory: SearchCategory? by viewModel.expandedCategory
var selectedContactIndex: Int by remember(contacts) { mutableIntStateOf(-1) } var selectedContactIndex: Int by remember(contacts) { mutableIntStateOf(-1) }
@ -180,15 +172,43 @@ fun SearchColumn(
} }
AppResults( AppResults(
apps = visibleApps, key = "apps",
showTabs = separateWorkProfile && apps.isNotEmpty() && workApps.isNotEmpty(), apps = apps,
highlightedItem = bestMatch as? Application, highlightedItem = bestMatch as? Application,
selectedTab = if (showWorkProfileApps) 1 else 0,
onSelectedTabChange = { showWorkProfileApps = it == 1 },
columns = columns, columns = columns,
reverse = reverse reverse = reverse
) )
if (privateProfile != null && isSearchEmpty) {
AppResults(
key = "apps-priv",
apps = privateApps,
profile = privateProfile,
isProfileLocked = privateProfileState?.locked == true,
onProfileLockChange = if (hasProfilesPermission) {
{ viewModel.setProfileLock(privateProfile, it) }
} else null,
highlightedItem = bestMatch as? Application,
columns = columns,
reverse = reverse
)
}
if (workProfile != null && isSearchEmpty) {
AppResults(
key = "apps-work",
apps = workApps,
profile = workProfile,
isProfileLocked = workProfileState?.locked == true,
onProfileLockChange = if (hasProfilesPermission) {
{ viewModel.setProfileLock(workProfile, it) }
} else null,
highlightedItem = bestMatch as? Application,
columns = columns,
reverse = reverse
)
}
if (!isSearchEmpty) { if (!isSearchEmpty) {
ShortcutResults( ShortcutResults(

View File

@ -6,6 +6,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.devicepose.DevicePoseProvider import de.mm20.launcher2.devicepose.DevicePoseProvider
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.SearchResultOrder import de.mm20.launcher2.preferences.SearchResultOrder
@ -16,6 +17,8 @@ import de.mm20.launcher2.preferences.search.LocationSearchSettings
import de.mm20.launcher2.preferences.search.SearchFilterSettings import de.mm20.launcher2.preferences.search.SearchFilterSettings
import de.mm20.launcher2.preferences.search.ShortcutSearchSettings import de.mm20.launcher2.preferences.search.ShortcutSearchSettings
import de.mm20.launcher2.preferences.ui.SearchUiSettings import de.mm20.launcher2.preferences.ui.SearchUiSettings
import de.mm20.launcher2.profiles.Profile
import de.mm20.launcher2.profiles.ProfileManager
import de.mm20.launcher2.search.AppShortcut import de.mm20.launcher2.search.AppShortcut
import de.mm20.launcher2.search.Application import de.mm20.launcher2.search.Application
import de.mm20.launcher2.search.Article import de.mm20.launcher2.search.Article
@ -41,6 +44,9 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
@ -51,6 +57,7 @@ class SearchVM : ViewModel(), KoinComponent {
private val favoritesService: FavoritesService by inject() private val favoritesService: FavoritesService by inject()
private val searchableRepository: SavableSearchableRepository by inject() private val searchableRepository: SavableSearchableRepository by inject()
private val permissionsManager: PermissionsManager by inject() private val permissionsManager: PermissionsManager by inject()
private val profileManager: ProfileManager by inject()
private val fileSearchSettings: FileSearchSettings by inject() private val fileSearchSettings: FileSearchSettings by inject()
private val contactSearchSettings: ContactSearchSettings by inject() private val contactSearchSettings: ContactSearchSettings by inject()
@ -72,9 +79,37 @@ class SearchVM : ViewModel(), KoinComponent {
val expandedCategory = mutableStateOf<SearchCategory?>(null) val expandedCategory = mutableStateOf<SearchCategory?>(null)
val locationResults = mutableStateOf<List<Location>>(emptyList()) val locationResults = mutableStateOf<List<Location>>(emptyList())
val profiles = profileManager.profiles.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), replay = 1)
val workProfile = profiles.map {
it.find { it.type == Profile.Type.Work }
}
val privateProfile = profiles.map {
it.find { it.type == Profile.Type.Private }
}
val workProfileState = workProfile.flatMapLatest {
profileManager.getProfileState(it)
}
val privateProfileState = privateProfile.flatMapLatest {
profileManager.getProfileState(it)
}
val hasProfilesPermission = permissionsManager.hasPermission(PermissionGroup.ManageProfiles)
fun setProfileLock(profile: Profile?, locked: Boolean) {
if (isAtLeastApiLevel(28) && profile != null) {
if (locked) {
profileManager.lockProfile(profile)
} else {
profileManager.unlockProfile(profile)
}
}
}
val appResults = mutableStateOf<List<Application>>(emptyList()) val appResults = mutableStateOf<List<Application>>(emptyList())
val workAppResults = mutableStateOf<List<Application>>(emptyList()) val workAppResults = mutableStateOf<List<Application>>(emptyList())
val privateSpaceAppResults = mutableStateOf<List<Application>>(emptyList()) val privateSpaceAppResults = mutableStateOf<List<Application>>(emptyList())
val appShortcutResults = mutableStateOf<List<AppShortcut>>(emptyList()) val appShortcutResults = mutableStateOf<List<AppShortcut>>(emptyList())
val fileResults = mutableStateOf<List<File>>(emptyList()) val fileResults = mutableStateOf<List<File>>(emptyList())
val contactResults = mutableStateOf<List<Contact>>(emptyList()) val contactResults = mutableStateOf<List<Contact>>(emptyList())

View File

@ -1,42 +1,86 @@
package de.mm20.launcher2.ui.launcher.search.apps package de.mm20.launcher2.ui.launcher.search.apps
import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.HorizontalDivider import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Lock
import androidx.compose.material.icons.rounded.LockOpen
import androidx.compose.material.icons.rounded.Person
import androidx.compose.material.icons.rounded.Work
import androidx.compose.material3.FilledIconToggleButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryTabRow
import androidx.compose.material3.Tab
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.icons.PrivateSpace
import de.mm20.launcher2.profiles.Profile
import de.mm20.launcher2.search.Application import de.mm20.launcher2.search.Application
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.ktx.animateCorners
import de.mm20.launcher2.ui.ktx.withCorners
import de.mm20.launcher2.ui.launcher.search.common.grid.GridItem import de.mm20.launcher2.ui.launcher.search.common.grid.GridItem
import de.mm20.launcher2.ui.launcher.search.common.grid.GridResults import de.mm20.launcher2.ui.launcher.search.common.grid.GridResults
import de.mm20.launcher2.ui.layout.BottomReversed
import de.mm20.launcher2.ui.locals.LocalCardStyle
import de.mm20.launcher2.ui.locals.LocalGridSettings import de.mm20.launcher2.ui.locals.LocalGridSettings
fun LazyListScope.AppResults( fun LazyListScope.AppResults(
key: String,
profile: Profile? = null,
isProfileLocked: Boolean = false,
onProfileLockChange: ((Boolean) -> Unit)? = null,
apps: List<Application>, apps: List<Application>,
showTabs: Boolean,
selectedTab: Int,
onSelectedTabChange: (Int) -> Unit,
highlightedItem: Application? = null, highlightedItem: Application? = null,
columns: Int, columns: Int,
reverse: Boolean, reverse: Boolean,
) { ) {
GridResults( GridResults(
key = "app", key = key,
items = apps, items = apps,
before = if (profile != null) {
{
Row(
modifier = Modifier
.padding(top = 4.dp, start = 16.dp, end = 4.dp, bottom = 4.dp)
.heightIn(min = 40.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
when (profile.type) {
Profile.Type.Work -> Icons.Rounded.Work
Profile.Type.Private -> Icons.Rounded.PrivateSpace
else -> Icons.Rounded.Person
},
null,
tint = MaterialTheme.colorScheme.primary,
)
Text(
text = when (profile.type) {
Profile.Type.Personal -> stringResource(R.string.apps_profile_main)
Profile.Type.Work -> stringResource(R.string.apps_profile_work)
Profile.Type.Private -> stringResource(R.string.apps_profile_private)
},
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier
.weight(1f)
.padding(horizontal = 12.dp)
)
if (onProfileLockChange != null) {
FilledIconToggleButton(checked = isProfileLocked, onCheckedChange = {
onProfileLockChange(it)
}) {
Icon(
if (isProfileLocked) Icons.Rounded.Lock else Icons.Rounded.LockOpen,
null
)
}
}
}
}
} else null,
itemContent = { itemContent = {
GridItem( GridItem(
item = it, item = it,
@ -44,43 +88,6 @@ fun LazyListScope.AppResults(
highlight = it.key == highlightedItem?.key highlight = it.key == highlightedItem?.key
) )
}, },
before = if (showTabs) {
{
Column(
verticalArrangement = if (reverse) Arrangement.BottomReversed else Arrangement.Top,
) {
PrimaryTabRow(
selectedTabIndex = selectedTab,
modifier = Modifier
.fillMaxWidth()
.clip(
MaterialTheme.shapes.medium.withCorners(
topStart = !reverse,
topEnd = !reverse,
bottomEnd = reverse,
bottomStart = reverse,
)
),
divider = {},
containerColor = Color.Transparent
) {
Tab(
selected = selectedTab == 0,
onClick = { onSelectedTabChange(0) },
text = { Text(stringResource(R.string.apps_profile_main)) },
unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
)
Tab(
selected = selectedTab == 1,
onClick = { onSelectedTabChange(1) },
text = { Text(stringResource(R.string.apps_profile_work)) },
unselectedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
HorizontalDivider()
}
}
} else null,
reverse = reverse, reverse = reverse,
columns = columns, columns = columns,
) )

View File

@ -34,6 +34,10 @@ fun <T : SavableSearchable> LazyListScope.GridResults(
val isBottom = reverse || items.isEmpty() && after == null val isBottom = reverse || items.isEmpty() && after == null
Box( Box(
modifier = Modifier modifier = Modifier
.padding(
top = if (reverse && isTop) 8.dp else 0.dp,
bottom = if (!reverse && isBottom) 8.dp else 0.dp,
)
.background( .background(
MaterialTheme.colorScheme.surface.copy(alpha = LocalCardStyle.current.opacity), MaterialTheme.colorScheme.surface.copy(alpha = LocalCardStyle.current.opacity),
MaterialTheme.shapes.medium.withCorners( MaterialTheme.shapes.medium.withCorners(

View File

@ -1647,4 +1647,38 @@ private val _Google = materialIcon("Icons.Rounded.Google") {
} }
val Icons.Rounded.Google val Icons.Rounded.Google
get() = _Google get() = _Google
private val _PrivateSpace = materialIcon("Icons.Rounded.PrivateSpace") {
materialPath {
moveTo(11.999784f, 1.9998779f)
lineTo(3.9997559f, 5.0002116f)
verticalLineToRelative(6.0895504f)
curveToRelative(0f, 5.049995f, 3.410033f, 9.760447f, 8.0000281f, 10.910446f)
curveToRelative(4.589996f, -1.149999f, 8.000029f, -5.860451f, 8.000029f, -10.910446f)
verticalLineTo(5.0002116f)
close()
moveToRelative(0f, 4.0002727f)
curveToRelative(1.929998f, 0f, 3.500045f, 1.5700466f, 3.500045f, 3.5000447f)
curveToRelative(0f, 1.5799987f, -1.05959f, 2.9098487f, -2.499589f, 3.3398477f)
verticalLineToRelative(2.160075f)
horizontalLineToRelative(1.999878f)
verticalLineToRelative(1.999878f)
horizontalLineTo(13.00024f)
verticalLineToRelative(0.999939f)
horizontalLineTo(10.999845f)
verticalLineTo(12.840043f)
curveTo(9.5598468f, 12.410044f, 8.5002563f, 11.090194f, 8.5002563f, 9.5001953f)
curveToRelative(0f, -1.9299981f, 1.5695297f, -3.5000447f, 3.4995277f, -3.5000447f)
close()
moveToRelative(0f, 1.9998779f)
curveToRelative(-0.827999f, 0f, -1.49965f, 0.6721676f, -1.49965f, 1.5001668f)
curveToRelative(0f, 0.8279987f, 0.671651f, 1.4996497f, 1.49965f, 1.4996497f)
curveToRelative(0.828f, 0f, 1.500167f, -0.671651f, 1.500167f, -1.4996497f)
curveToRelative(0f, -0.8279992f, -0.672167f, -1.5001668f, -1.500167f, -1.5001668f)
close()
}
}
val Icons.Rounded.PrivateSpace
get() = _PrivateSpace

View File

@ -682,6 +682,7 @@
<string name="icon_picker_filter_all_packs">All icon packs</string> <string name="icon_picker_filter_all_packs">All icon packs</string>
<string name="apps_profile_main">Personal</string> <string name="apps_profile_main">Personal</string>
<string name="apps_profile_work">Work</string> <string name="apps_profile_work">Work</string>
<string name="apps_profile_private">Private space</string>
<string name="favorites">Favorites</string> <string name="favorites">Favorites</string>
<string name="favorites_empty">Pinned and frequently used items will appear here</string> <string name="favorites_empty">Pinned and frequently used items will appear here</string>
<string name="favorites_empty_tag">There are no items with this tag</string> <string name="favorites_empty_tag">There are no items with this tag</string>

View File

@ -18,7 +18,6 @@ import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.ktx.checkPermission import de.mm20.launcher2.ktx.checkPermission
import de.mm20.launcher2.ktx.isAtLeastApiLevel import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.ktx.tryStartActivity import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.plugin.contracts.PluginContract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -65,7 +64,7 @@ enum class PermissionGroup {
Notifications, Notifications,
AppShortcuts, AppShortcuts,
Accessibility, Accessibility,
HiddenProfiles, ManageProfiles,
} }
internal class PermissionsManagerImpl( internal class PermissionsManagerImpl(
@ -91,8 +90,8 @@ internal class PermissionsManagerImpl(
private val appShortcutsPermissionState = MutableStateFlow( private val appShortcutsPermissionState = MutableStateFlow(
checkPermissionOnce(PermissionGroup.AppShortcuts) checkPermissionOnce(PermissionGroup.AppShortcuts)
) )
private val hiddenProfilesPermissionState = MutableStateFlow( private val mManageProfilesPermissionState = MutableStateFlow(
checkPermissionOnce(PermissionGroup.HiddenProfiles) checkPermissionOnce(PermissionGroup.ManageProfiles)
) )
override fun requestPermission(context: AppCompatActivity, permissionGroup: PermissionGroup) { override fun requestPermission(context: AppCompatActivity, permissionGroup: PermissionGroup) {
@ -146,7 +145,7 @@ internal class PermissionsManagerImpl(
} }
} }
PermissionGroup.HiddenProfiles, PermissionGroup.ManageProfiles,
PermissionGroup.AppShortcuts -> { PermissionGroup.AppShortcuts -> {
if (isAtLeastApiLevel(29)) { if (isAtLeastApiLevel(29)) {
val roleManager = context.getSystemService<RoleManager>() val roleManager = context.getSystemService<RoleManager>()
@ -201,7 +200,7 @@ internal class PermissionsManagerImpl(
context.getSystemService<LauncherApps>()?.hasShortcutHostPermission() == true context.getSystemService<LauncherApps>()?.hasShortcutHostPermission() == true
} }
PermissionGroup.HiddenProfiles -> { PermissionGroup.ManageProfiles -> {
if (isAtLeastApiLevel(29)) { if (isAtLeastApiLevel(29)) {
context.getSystemService<RoleManager>()?.isRoleHeld(RoleManager.ROLE_HOME) == true context.getSystemService<RoleManager>()?.isRoleHeld(RoleManager.ROLE_HOME) == true
} else false } else false
@ -222,7 +221,7 @@ internal class PermissionsManagerImpl(
PermissionGroup.Notifications -> notificationsPermissionState PermissionGroup.Notifications -> notificationsPermissionState
PermissionGroup.AppShortcuts -> appShortcutsPermissionState PermissionGroup.AppShortcuts -> appShortcutsPermissionState
PermissionGroup.Accessibility -> accessibilityPermissionState PermissionGroup.Accessibility -> accessibilityPermissionState
PermissionGroup.HiddenProfiles -> hiddenProfilesPermissionState PermissionGroup.ManageProfiles -> mManageProfilesPermissionState
} }
} }
@ -241,14 +240,14 @@ internal class PermissionsManagerImpl(
PermissionGroup.Notifications -> notificationsPermissionState.value = granted PermissionGroup.Notifications -> notificationsPermissionState.value = granted
PermissionGroup.AppShortcuts -> appShortcutsPermissionState.value = granted PermissionGroup.AppShortcuts -> appShortcutsPermissionState.value = granted
PermissionGroup.Accessibility -> accessibilityPermissionState.value = granted PermissionGroup.Accessibility -> accessibilityPermissionState.value = granted
PermissionGroup.HiddenProfiles -> hiddenProfilesPermissionState.value = granted PermissionGroup.ManageProfiles -> mManageProfilesPermissionState.value = granted
} }
} }
override fun onResume() { override fun onResume() {
externalStoragePermissionState.value = checkPermissionOnce(PermissionGroup.ExternalStorage) externalStoragePermissionState.value = checkPermissionOnce(PermissionGroup.ExternalStorage)
appShortcutsPermissionState.value = checkPermissionOnce(PermissionGroup.AppShortcuts) appShortcutsPermissionState.value = checkPermissionOnce(PermissionGroup.AppShortcuts)
hiddenProfilesPermissionState.value = checkPermissionOnce(PermissionGroup.HiddenProfiles) mManageProfilesPermissionState.value = checkPermissionOnce(PermissionGroup.ManageProfiles)
} }
override fun reportNotificationListenerState(running: Boolean) { override fun reportNotificationListenerState(running: Boolean) {

View File

@ -8,13 +8,11 @@ import android.content.pm.LauncherApps
import android.os.Process import android.os.Process
import android.os.UserHandle import android.os.UserHandle
import android.os.UserManager import android.os.UserManager
import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import de.mm20.launcher2.ktx.isAtLeastApiLevel import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.plugin.data.get
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -54,6 +52,10 @@ class ProfileManager(
} }
}.shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1) }.shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
val profiles: Flow<List<Profile>> = profileStates.map {
it.map { it.profile }
}.shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
init { init {
val receiver = object : BroadcastReceiver() { val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
@ -82,7 +84,7 @@ class ProfileManager(
) )
scope.launch { scope.launch {
if (isAtLeastApiLevel(35)) { if (isAtLeastApiLevel(35)) {
permissionsManager.hasPermission(PermissionGroup.HiddenProfiles).collectLatest { permissionsManager.hasPermission(PermissionGroup.ManageProfiles).collectLatest {
refreshProfiles() refreshProfiles()
} }
} else { } else {
@ -108,7 +110,6 @@ class ProfileManager(
) )
) )
} }
Log.d("MM20", "Profiles: $profiles")
profileStates.value = profiles profileStates.value = profiles
} }
} }
@ -119,19 +120,16 @@ class ProfileManager(
} }
} }
fun getProfileState(profile: Profile): Flow<Profile.State?> { fun getProfileState(profile: Profile?): Flow<Profile.State?> {
return profileStates.map { profiles -> return profileStates.map { profiles ->
profiles.find { it.profile == profile }?.state profiles.find { it.profile == profile }?.state
} }
} }
/**
* This only works when the launcher is installed in the primary profile.
*/
private fun getProfileType(userHandle: UserHandle): Profile.Type { private fun getProfileType(userHandle: UserHandle): Profile.Type {
if (isAtLeastApiLevel(35)) { if (isAtLeastApiLevel(35)) {
val launcherUserInfo = launcherApps.getLauncherUserInfo(userHandle) val launcherUserInfo = launcherApps.getLauncherUserInfo(userHandle)
return when(launcherUserInfo?.userType) { return when (launcherUserInfo?.userType) {
UserManager.USER_TYPE_PROFILE_PRIVATE -> Profile.Type.Private UserManager.USER_TYPE_PROFILE_PRIVATE -> Profile.Type.Private
UserManager.USER_TYPE_PROFILE_MANAGED -> Profile.Type.Work UserManager.USER_TYPE_PROFILE_MANAGED -> Profile.Type.Work
else -> Profile.Type.Personal else -> Profile.Type.Personal

View File

@ -5,6 +5,7 @@ import androidx.compose.material.icons.rounded.Lock
import androidx.compose.material.icons.rounded.Work import androidx.compose.material.icons.rounded.Work
import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.badges.BadgeIcon import de.mm20.launcher2.badges.BadgeIcon
import de.mm20.launcher2.icons.PrivateSpace
import de.mm20.launcher2.profiles.Profile import de.mm20.launcher2.profiles.Profile
import de.mm20.launcher2.profiles.ProfileManager import de.mm20.launcher2.profiles.ProfileManager
import de.mm20.launcher2.search.AppShortcut import de.mm20.launcher2.search.AppShortcut
@ -47,7 +48,7 @@ class ProfileBadgeProvider : BadgeProvider, KoinComponent {
) )
private val PrivateProfile = Badge( private val PrivateProfile = Badge(
icon = BadgeIcon(Icons.Rounded.Lock) icon = BadgeIcon(Icons.Rounded.PrivateSpace)
) )
} }
} }

View File

@ -259,13 +259,18 @@ internal class SearchServiceImpl(
val privateSpace = profiles.find { it.type == Profile.Type.Private } val privateSpace = profiles.find { it.type == Profile.Type.Private }
appRepository.search("", false) appRepository.search("", false)
.withCustomLabels(customAttributesRepository) .withCustomLabels(customAttributesRepository)
.map { .map { apps ->
val grouped = it.groupBy { it.user } val standardProfileApps = mutableListOf<Application>()
val standardProfileApps = val workProfileApps = mutableListOf<Application>()
standardProfile?.let { grouped[it.userHandle] } ?: emptyList() val privateSpaceApps = mutableListOf<Application>()
val workProfileApps = workProfile?.let { grouped[it.userHandle] } ?: emptyList() for (app in apps) {
val privateSpaceApps = when {
privateSpace?.let { grouped[it.userHandle] } ?: emptyList() standardProfile != null && app.user == standardProfile.userHandle -> standardProfileApps.add(app)
workProfile != null && app.user == workProfile.userHandle -> workProfileApps.add(app)
privateSpace != null && app.user == privateSpace.userHandle -> privateSpaceApps.add(app)
else -> standardProfileApps.add(app)
}
}
AllAppsResults( AllAppsResults(
standardProfileApps = standardProfileApps.sorted(), standardProfileApps = standardProfileApps.sorted(),