Restore tab based profile layout

Close #989
This commit is contained in:
MM20 2024-08-11 21:50:57 +02:00
parent 9bee1158d6
commit 0b01019be8
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
4 changed files with 187 additions and 68 deletions

View File

@ -1,5 +1,6 @@
package de.mm20.launcher2.ui.launcher.search
import android.util.Log
import androidx.activity.compose.BackHandler
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedContent
@ -76,6 +77,8 @@ fun SearchColumn(
val apps by viewModel.appResults
val workApps by viewModel.workAppResults
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 workProfileState by viewModel.workProfileState.collectAsState(null)
val privateProfile by viewModel.privateProfile.collectAsState(null)
@ -110,6 +113,7 @@ fun SearchColumn(
val expandedCategory: SearchCategory? by viewModel.expandedCategory
var selectedAppProfileIndex: Int by remember(isSearchEmpty) { mutableIntStateOf(0) }
var selectedContactIndex: Int by remember(contacts) { mutableIntStateOf(-1) }
var selectedFileIndex: Int by remember(files) { mutableIntStateOf(-1) }
var selectedCalendarIndex: Int by remember(events) { mutableIntStateOf(-1) }
@ -171,39 +175,34 @@ fun SearchColumn(
)
}
AppResults(
key = "apps",
apps = apps,
highlightedItem = bestMatch as? Application,
columns = columns,
reverse = reverse
)
if (privateProfile != null && isSearchEmpty) {
if (isSearchEmpty && profiles.size > 1) {
AppResults(
key = "apps-priv",
apps = privateApps,
profile = privateProfile,
isProfileLocked = privateProfileState?.locked == true,
onProfileLockChange = if (hasProfilesPermission) {
{ viewModel.setProfileLock(privateProfile, it) }
} else null,
apps = when(selectedAppProfileIndex) {
1 -> privateApps
2 -> workApps
else -> apps
},
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,
reverse = reverse
reverse = reverse,
showProfileLockControls = hasProfilesPermission,
)
}
if (workProfile != null && isSearchEmpty) {
} else {
AppResults(
key = "apps-work",
apps = workApps,
profile = workProfile,
isProfileLocked = workProfileState?.locked == true,
onProfileLockChange = if (hasProfilesPermission) {
{ viewModel.setProfileLock(workProfile, it) }
} else null,
apps = apps,
highlightedItem = bestMatch as? Application,
onProfileSelected = {
selectedAppProfileIndex = it
},
columns = columns,
reverse = reverse
)

View File

@ -86,6 +86,11 @@ class SearchVM : ViewModel(), KoinComponent {
SharingStarted.WhileSubscribed(),
replay = 1
)
val profileStates = profiles.flatMapLatest {
combine(it.map { profileManager.getProfileState(it) }) {
it.toList()
}
}
val workProfile = profiles.map {
it.find { it.type == Profile.Type.Work }
}

View File

@ -1,20 +1,33 @@
package de.mm20.launcher2.ui.launcher.search.apps
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.background
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.size
import androidx.compose.foundation.lazy.LazyListScope
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.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.LeadingIconTab
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.PrimaryScrollableTabRow
import androidx.compose.material3.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
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.launcher.search.common.grid.GridItem
import de.mm20.launcher2.ui.launcher.search.common.grid.GridResults
import de.mm20.launcher2.ui.layout.BottomReversed
import de.mm20.launcher2.ui.locals.LocalGridSettings
fun LazyListScope.AppResults(
key: String,
profile: Profile? = null,
onProfileSelected: (Int) -> Unit,
profiles: List<Profile> = emptyList(),
selectedProfileIndex: Int = -1,
showProfileLockControls: Boolean = false,
isProfileLocked: Boolean = false,
onProfileLockChange: ((Boolean) -> Unit)? = null,
onProfileLockChange: ((Profile, Boolean) -> Unit)? = null,
apps: List<Application>,
highlightedItem: Application? = null,
columns: Int,
@ -37,47 +53,140 @@ fun LazyListScope.AppResults(
) {
GridResults(
key = key,
key = "apps",
items = apps,
before = if (profile != null) {
before = if (profiles.size > 1) {
{
Row(
modifier = Modifier
.padding(top = 4.dp, start = 16.dp, end = 4.dp, bottom = 4.dp)
.heightIn(min = 40.dp),
verticalAlignment = Alignment.CenterVertically,
Column(
verticalArrangement = if (reverse) Arrangement.BottomReversed else Arrangement.Top,
) {
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
PrimaryScrollableTabRow(
selectedTabIndex = selectedProfileIndex,
containerColor = Color.Transparent,
edgePadding = 16.dp,
divider = {}
) {
for ((i, profile) in profiles.withIndex()) {
LeadingIconTab(
selected = selectedProfileIndex == profiles.indexOf(profile),
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)
}
)
},
icon = {
when (profile.type) {
Profile.Type.Personal -> Icon(
Icons.Rounded.Person,
contentDescription = null
)
Profile.Type.Work -> Icon(
Icons.Rounded.Work,
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,

View File

@ -985,4 +985,10 @@
<string name="calendar_widget_next_day">Next day</string>
<string name="calendar_widget_create_event">Create new event</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>