parent
9bee1158d6
commit
0b01019be8
@ -1,5 +1,6 @@
|
|||||||
package de.mm20.launcher2.ui.launcher.search
|
package de.mm20.launcher2.ui.launcher.search
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
@ -76,6 +77,8 @@ fun SearchColumn(
|
|||||||
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 privateApps by viewModel.privateSpaceAppResults
|
||||||
|
val profiles by viewModel.profiles.collectAsState(emptyList())
|
||||||
|
val profileStates by viewModel.profileStates.collectAsState(emptyList())
|
||||||
val workProfile by viewModel.workProfile.collectAsState(null)
|
val workProfile by viewModel.workProfile.collectAsState(null)
|
||||||
val workProfileState by viewModel.workProfileState.collectAsState(null)
|
val workProfileState by viewModel.workProfileState.collectAsState(null)
|
||||||
val privateProfile by viewModel.privateProfile.collectAsState(null)
|
val privateProfile by viewModel.privateProfile.collectAsState(null)
|
||||||
@ -110,6 +113,7 @@ fun SearchColumn(
|
|||||||
|
|
||||||
val expandedCategory: SearchCategory? by viewModel.expandedCategory
|
val expandedCategory: SearchCategory? by viewModel.expandedCategory
|
||||||
|
|
||||||
|
var selectedAppProfileIndex: Int by remember(isSearchEmpty) { mutableIntStateOf(0) }
|
||||||
var selectedContactIndex: Int by remember(contacts) { mutableIntStateOf(-1) }
|
var selectedContactIndex: Int by remember(contacts) { mutableIntStateOf(-1) }
|
||||||
var selectedFileIndex: Int by remember(files) { mutableIntStateOf(-1) }
|
var selectedFileIndex: Int by remember(files) { mutableIntStateOf(-1) }
|
||||||
var selectedCalendarIndex: Int by remember(events) { mutableIntStateOf(-1) }
|
var selectedCalendarIndex: Int by remember(events) { mutableIntStateOf(-1) }
|
||||||
@ -171,39 +175,34 @@ fun SearchColumn(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
AppResults(
|
if (isSearchEmpty && profiles.size > 1) {
|
||||||
key = "apps",
|
|
||||||
apps = apps,
|
|
||||||
highlightedItem = bestMatch as? Application,
|
|
||||||
columns = columns,
|
|
||||||
reverse = reverse
|
|
||||||
)
|
|
||||||
|
|
||||||
if (privateProfile != null && isSearchEmpty) {
|
|
||||||
AppResults(
|
AppResults(
|
||||||
key = "apps-priv",
|
apps = when(selectedAppProfileIndex) {
|
||||||
apps = privateApps,
|
1 -> privateApps
|
||||||
profile = privateProfile,
|
2 -> workApps
|
||||||
isProfileLocked = privateProfileState?.locked == true,
|
else -> apps
|
||||||
onProfileLockChange = if (hasProfilesPermission) {
|
},
|
||||||
{ viewModel.setProfileLock(privateProfile, it) }
|
|
||||||
} else null,
|
|
||||||
highlightedItem = bestMatch as? Application,
|
highlightedItem = bestMatch as? Application,
|
||||||
|
profiles = profiles,
|
||||||
|
selectedProfileIndex = selectedAppProfileIndex,
|
||||||
|
onProfileSelected = {
|
||||||
|
selectedAppProfileIndex = it
|
||||||
|
},
|
||||||
|
isProfileLocked = profileStates.getOrNull(selectedAppProfileIndex)?.locked == true,
|
||||||
|
onProfileLockChange = { p, l ->
|
||||||
|
viewModel.setProfileLock(p, l)
|
||||||
|
},
|
||||||
columns = columns,
|
columns = columns,
|
||||||
reverse = reverse
|
reverse = reverse,
|
||||||
|
showProfileLockControls = hasProfilesPermission,
|
||||||
)
|
)
|
||||||
}
|
} else {
|
||||||
|
|
||||||
if (workProfile != null && isSearchEmpty) {
|
|
||||||
AppResults(
|
AppResults(
|
||||||
key = "apps-work",
|
apps = apps,
|
||||||
apps = workApps,
|
|
||||||
profile = workProfile,
|
|
||||||
isProfileLocked = workProfileState?.locked == true,
|
|
||||||
onProfileLockChange = if (hasProfilesPermission) {
|
|
||||||
{ viewModel.setProfileLock(workProfile, it) }
|
|
||||||
} else null,
|
|
||||||
highlightedItem = bestMatch as? Application,
|
highlightedItem = bestMatch as? Application,
|
||||||
|
onProfileSelected = {
|
||||||
|
selectedAppProfileIndex = it
|
||||||
|
},
|
||||||
columns = columns,
|
columns = columns,
|
||||||
reverse = reverse
|
reverse = reverse
|
||||||
)
|
)
|
||||||
|
|||||||
@ -86,6 +86,11 @@ class SearchVM : ViewModel(), KoinComponent {
|
|||||||
SharingStarted.WhileSubscribed(),
|
SharingStarted.WhileSubscribed(),
|
||||||
replay = 1
|
replay = 1
|
||||||
)
|
)
|
||||||
|
val profileStates = profiles.flatMapLatest {
|
||||||
|
combine(it.map { profileManager.getProfileState(it) }) {
|
||||||
|
it.toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
val workProfile = profiles.map {
|
val workProfile = profiles.map {
|
||||||
it.find { it.type == Profile.Type.Work }
|
it.find { it.type == Profile.Type.Work }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,33 @@
|
|||||||
package de.mm20.launcher2.ui.launcher.search.apps
|
package de.mm20.launcher2.ui.launcher.search.apps
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.heightIn
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Lock
|
import androidx.compose.material.icons.rounded.Lock
|
||||||
import androidx.compose.material.icons.rounded.LockOpen
|
import androidx.compose.material.icons.rounded.LockOpen
|
||||||
import androidx.compose.material.icons.rounded.Person
|
import androidx.compose.material.icons.rounded.Person
|
||||||
import androidx.compose.material.icons.rounded.Work
|
import androidx.compose.material.icons.rounded.Work
|
||||||
import androidx.compose.material3.FilledIconToggleButton
|
import androidx.compose.material.icons.rounded.WorkOff
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.FilledTonalButton
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LeadingIconTab
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
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 androidx.compose.ui.unit.dp
|
||||||
import de.mm20.launcher2.icons.PrivateSpace
|
import de.mm20.launcher2.icons.PrivateSpace
|
||||||
@ -23,13 +36,16 @@ import de.mm20.launcher2.search.Application
|
|||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
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.LocalGridSettings
|
import de.mm20.launcher2.ui.locals.LocalGridSettings
|
||||||
|
|
||||||
fun LazyListScope.AppResults(
|
fun LazyListScope.AppResults(
|
||||||
key: String,
|
onProfileSelected: (Int) -> Unit,
|
||||||
profile: Profile? = null,
|
profiles: List<Profile> = emptyList(),
|
||||||
|
selectedProfileIndex: Int = -1,
|
||||||
|
showProfileLockControls: Boolean = false,
|
||||||
isProfileLocked: Boolean = false,
|
isProfileLocked: Boolean = false,
|
||||||
onProfileLockChange: ((Boolean) -> Unit)? = null,
|
onProfileLockChange: ((Profile, Boolean) -> Unit)? = null,
|
||||||
apps: List<Application>,
|
apps: List<Application>,
|
||||||
highlightedItem: Application? = null,
|
highlightedItem: Application? = null,
|
||||||
columns: Int,
|
columns: Int,
|
||||||
@ -37,47 +53,140 @@ fun LazyListScope.AppResults(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
GridResults(
|
GridResults(
|
||||||
key = key,
|
key = "apps",
|
||||||
items = apps,
|
items = apps,
|
||||||
before = if (profile != null) {
|
before = if (profiles.size > 1) {
|
||||||
{
|
{
|
||||||
Row(
|
Column(
|
||||||
modifier = Modifier
|
verticalArrangement = if (reverse) Arrangement.BottomReversed else Arrangement.Top,
|
||||||
.padding(top = 4.dp, start = 16.dp, end = 4.dp, bottom = 4.dp)
|
|
||||||
.heightIn(min = 40.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
PrimaryScrollableTabRow(
|
||||||
when (profile.type) {
|
selectedTabIndex = selectedProfileIndex,
|
||||||
Profile.Type.Work -> Icons.Rounded.Work
|
containerColor = Color.Transparent,
|
||||||
Profile.Type.Private -> Icons.Rounded.PrivateSpace
|
edgePadding = 16.dp,
|
||||||
else -> Icons.Rounded.Person
|
divider = {}
|
||||||
},
|
) {
|
||||||
null,
|
for ((i, profile) in profiles.withIndex()) {
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
LeadingIconTab(
|
||||||
)
|
selected = selectedProfileIndex == profiles.indexOf(profile),
|
||||||
Text(
|
text = {
|
||||||
text = when (profile.type) {
|
Text(
|
||||||
Profile.Type.Personal -> stringResource(R.string.apps_profile_main)
|
when (profile.type) {
|
||||||
Profile.Type.Work -> stringResource(R.string.apps_profile_work)
|
Profile.Type.Personal -> stringResource(R.string.apps_profile_main)
|
||||||
Profile.Type.Private -> stringResource(R.string.apps_profile_private)
|
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)
|
icon = {
|
||||||
.padding(horizontal = 12.dp)
|
when (profile.type) {
|
||||||
)
|
Profile.Type.Personal -> Icon(
|
||||||
if (onProfileLockChange != null) {
|
Icons.Rounded.Person,
|
||||||
FilledIconToggleButton(checked = isProfileLocked, onCheckedChange = {
|
contentDescription = null
|
||||||
onProfileLockChange(it)
|
)
|
||||||
}) {
|
|
||||||
Icon(
|
Profile.Type.Work -> Icon(
|
||||||
if (isProfileLocked) Icons.Rounded.Lock else Icons.Rounded.LockOpen,
|
Icons.Rounded.Work,
|
||||||
null
|
contentDescription = null
|
||||||
|
)
|
||||||
|
|
||||||
|
Profile.Type.Private -> Icon(
|
||||||
|
Icons.Rounded.PrivateSpace,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
onProfileSelected(i)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
HorizontalDivider()
|
||||||
|
|
||||||
|
val profileType = profiles[selectedProfileIndex].type
|
||||||
|
if (showProfileLockControls && profileType != Profile.Type.Personal) {
|
||||||
|
if (isProfileLocked) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(12.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, MaterialTheme.shapes.small)
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceContainer, MaterialTheme.shapes.small)
|
||||||
|
.padding(vertical = 64.dp),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
if (profileType == Profile.Type.Work) Icons.Rounded.WorkOff else Icons.Rounded.Lock,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(48.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.secondary,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
if (profileType == Profile.Type.Work) R.string.profile_work_profile_state_locked
|
||||||
|
else R.string.profile_private_profile_state_locked
|
||||||
|
),
|
||||||
|
modifier = Modifier.padding(top = 8.dp, bottom = 32.dp),
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
modifier = Modifier,
|
||||||
|
onClick = {
|
||||||
|
onProfileLockChange?.invoke(
|
||||||
|
profiles[selectedProfileIndex],
|
||||||
|
false
|
||||||
|
)
|
||||||
|
},
|
||||||
|
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
if (profileType == Profile.Type.Work) Icons.Rounded.Work else Icons.Rounded.LockOpen,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = ButtonDefaults.IconSpacing)
|
||||||
|
.size(ButtonDefaults.IconSize)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
if (profileType == Profile.Type.Work) R.string.profile_work_profile_action_unlock
|
||||||
|
else R.string.profile_private_profile_action_unlock
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FilledTonalButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(12.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
onClick = {
|
||||||
|
onProfileLockChange?.invoke(
|
||||||
|
profiles[selectedProfileIndex],
|
||||||
|
true
|
||||||
|
)
|
||||||
|
},
|
||||||
|
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
if (profileType == Profile.Type.Work) Icons.Rounded.WorkOff else Icons.Rounded.Lock,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = ButtonDefaults.IconSpacing)
|
||||||
|
.size(ButtonDefaults.IconSize)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
if (profileType == Profile.Type.Work) R.string.profile_work_profile_action_lock
|
||||||
|
else R.string.profile_private_profile_action_lock
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else null,
|
} else null,
|
||||||
|
|||||||
@ -985,4 +985,10 @@
|
|||||||
<string name="calendar_widget_next_day">Next day</string>
|
<string name="calendar_widget_next_day">Next day</string>
|
||||||
<string name="calendar_widget_create_event">Create new event</string>
|
<string name="calendar_widget_create_event">Create new event</string>
|
||||||
<string name="calendar_widget_open_calendar">Open calendar app</string>
|
<string name="calendar_widget_open_calendar">Open calendar app</string>
|
||||||
|
<string name="profile_work_profile_state_locked">Work apps are paused.</string>
|
||||||
|
<string name="profile_work_profile_action_unlock">Unpause</string>
|
||||||
|
<string name="profile_work_profile_action_lock">Pause work apps</string>
|
||||||
|
<string name="profile_private_profile_state_locked">Private space is locked.</string>
|
||||||
|
<string name="profile_private_profile_action_unlock">Unlock</string>
|
||||||
|
<string name="profile_private_profile_action_lock">Lock private space</string>
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
x
Reference in New Issue
Block a user