Add private space support
This commit is contained in:
parent
99c98e4127
commit
3993f9505c
@ -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"))
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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())
|
||||||
|
|||||||
@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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(),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user