Feat: App list view (#1170)
* Feat: grid icon visibility settings and migration support * fix: remove unnecessary padding * Feat: Create List View Settings * fix FavoritesPartProvider * small fix * fix favorites * Remove useless datastore migration * Change app list default value * Revert migration 3 * Hide list icon preference if list is not enabled * Use ListResults for app list --------- Co-authored-by: MM20 <15646950+MM2-0@users.noreply.github.com>
This commit is contained in:
parent
b2cf7f5e5e
commit
2c2c88b93c
@ -13,7 +13,6 @@ import de.mm20.launcher2.widgets.FavoritesWidget
|
|||||||
import de.mm20.launcher2.widgets.WidgetRepository
|
import de.mm20.launcher2.widgets.WidgetRepository
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import org.koin.androidx.compose.inject
|
import org.koin.androidx.compose.inject
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@ -64,6 +64,7 @@ fun SearchColumn(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
val columns = LocalGridSettings.current.columnCount
|
val columns = LocalGridSettings.current.columnCount
|
||||||
|
val showList = LocalGridSettings.current.showList
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val viewModel: SearchVM = viewModel()
|
val viewModel: SearchVM = viewModel()
|
||||||
@ -111,6 +112,7 @@ fun SearchColumn(
|
|||||||
val expandedCategory: SearchCategory? by viewModel.expandedCategory
|
val expandedCategory: SearchCategory? by viewModel.expandedCategory
|
||||||
|
|
||||||
var selectedAppProfileIndex: Int by remember(isSearchEmpty) { mutableIntStateOf(0) }
|
var selectedAppProfileIndex: Int by remember(isSearchEmpty) { mutableIntStateOf(0) }
|
||||||
|
var selectedAppIndex: Int by remember(website) { mutableIntStateOf(-1) }
|
||||||
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) }
|
||||||
@ -193,6 +195,9 @@ fun SearchColumn(
|
|||||||
columns = columns,
|
columns = columns,
|
||||||
reverse = reverse,
|
reverse = reverse,
|
||||||
showProfileLockControls = hasProfilesPermission,
|
showProfileLockControls = hasProfilesPermission,
|
||||||
|
showList = showList,
|
||||||
|
selectedIndex = selectedAppIndex,
|
||||||
|
onSelect = { selectedAppIndex = it },
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
AppResults(
|
AppResults(
|
||||||
@ -202,7 +207,10 @@ fun SearchColumn(
|
|||||||
selectedAppProfileIndex = it
|
selectedAppProfileIndex = it
|
||||||
},
|
},
|
||||||
columns = columns,
|
columns = columns,
|
||||||
reverse = reverse
|
reverse = reverse,
|
||||||
|
showList = showList,
|
||||||
|
selectedIndex = selectedAppIndex,
|
||||||
|
onSelect = { selectedAppIndex = it },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import android.app.PendingIntent
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.SharedTransitionLayout
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.animation.core.MutableTransitionState
|
import androidx.compose.animation.core.MutableTransitionState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
@ -61,7 +62,6 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.IntRect
|
import androidx.compose.ui.unit.IntRect
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.lerp
|
import androidx.compose.ui.unit.lerp
|
||||||
import androidx.compose.ui.unit.roundToIntRect
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
@ -85,11 +85,15 @@ import kotlinx.coroutines.launch
|
|||||||
fun AppItem(
|
fun AppItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
app: Application,
|
app: Application,
|
||||||
|
showDetails: Boolean,
|
||||||
onBack: () -> Unit
|
onBack: () -> Unit
|
||||||
) {
|
) {
|
||||||
val viewModel: SearchableItemVM = listItemViewModel(key = "search-${app.key}")
|
val viewModel: SearchableItemVM = listItemViewModel(key = "search-${app.key}")
|
||||||
val iconSize = LocalGridSettings.current.iconSize.dp.toPixels()
|
val iconSize = LocalGridSettings.current.iconSize.dp.toPixels()
|
||||||
|
|
||||||
|
val badge by viewModel.badge.collectAsStateWithLifecycle(null)
|
||||||
|
val icon by viewModel.icon.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
LaunchedEffect(app) {
|
LaunchedEffect(app) {
|
||||||
viewModel.init(app, iconSize.toInt())
|
viewModel.init(app, iconSize.toInt())
|
||||||
}
|
}
|
||||||
@ -97,386 +101,440 @@ fun AppItem(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
Column(
|
SharedTransitionLayout(modifier = modifier) {
|
||||||
modifier = modifier.verticalScroll(rememberScrollState())
|
AnimatedContent(showDetails) { showDetails ->
|
||||||
) {
|
if (showDetails) {
|
||||||
Row {
|
Column(
|
||||||
Column(
|
modifier = Modifier.verticalScroll(rememberScrollState())
|
||||||
modifier = Modifier
|
) {
|
||||||
.weight(1f)
|
Row {
|
||||||
.padding(16.dp)
|
Column(
|
||||||
) {
|
modifier = Modifier
|
||||||
Text(
|
.weight(1f)
|
||||||
text = app.labelOverride ?: app.label,
|
.padding(16.dp)
|
||||||
style = MaterialTheme.typography.titleMedium
|
) {
|
||||||
)
|
Text(
|
||||||
|
text = app.labelOverride ?: app.label,
|
||||||
if (!app.isPrivate) {
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
|
||||||
val tags by viewModel.tags.collectAsState(emptyList())
|
|
||||||
if (tags.isNotEmpty()) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(top = 1.dp, bottom = 4.dp),
|
|
||||||
text = tags.joinToString(separator = " #", prefix = "#"),
|
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
|
||||||
style = MaterialTheme.typography.labelSmall
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
app.versionName?.let {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.app_info_version, it),
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
modifier = Modifier.padding(top = 4.dp),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = app.componentName.packageName,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
modifier = Modifier.padding(top = 1.dp),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.profile_private_profile_state_locked),
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
modifier = Modifier.padding(top = 8.dp),
|
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
val badge by viewModel.badge.collectAsStateWithLifecycle(null)
|
|
||||||
val icon by viewModel.icon.collectAsStateWithLifecycle()
|
|
||||||
ShapedLauncherIcon(
|
|
||||||
size = 48.dp,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp),
|
|
||||||
badge = { badge },
|
|
||||||
icon = { icon },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val notifications by viewModel.notifications.collectAsState(emptyList())
|
|
||||||
|
|
||||||
AnimatedVisibility(notifications.isNotEmpty()) {
|
|
||||||
var showAllNotifications by remember { mutableStateOf(false) }
|
|
||||||
AnimatedContent(
|
|
||||||
showAllNotifications || notifications.size == 1,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
.padding(bottom = 12.dp)
|
|
||||||
.border(
|
|
||||||
1.dp,
|
|
||||||
MaterialTheme.colorScheme.outlineVariant,
|
|
||||||
MaterialTheme.shapes.small
|
|
||||||
)
|
|
||||||
.clip(MaterialTheme.shapes.small)
|
|
||||||
) { showAll ->
|
|
||||||
if (showAll) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.animateContentSize()
|
|
||||||
) {
|
|
||||||
for ((i, not) in notifications.withIndex()) {
|
|
||||||
val icon =
|
|
||||||
remember(not.smallIcon) { not.smallIcon?.loadDrawable(context) }
|
|
||||||
|
|
||||||
if (not.title == null && not.text == null) continue
|
|
||||||
|
|
||||||
if (i > 0) {
|
|
||||||
HorizontalDivider()
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable {
|
.sharedBounds(
|
||||||
try {
|
rememberSharedContentState("label"),
|
||||||
not.contentIntent?.sendWithBackgroundPermission(context)
|
this@AnimatedContent,
|
||||||
} catch (e: PendingIntent.CanceledException) {
|
),
|
||||||
CrashReporter.logException(e)
|
)
|
||||||
}
|
|
||||||
}
|
if (!app.isPrivate) {
|
||||||
.padding(vertical = 4.dp)
|
|
||||||
) {
|
val tags by viewModel.tags.collectAsState(emptyList())
|
||||||
Box(
|
if (tags.isNotEmpty()) {
|
||||||
modifier = Modifier
|
Text(
|
||||||
.padding(horizontal = 12.dp)
|
modifier = Modifier.padding(top = 1.dp, bottom = 4.dp),
|
||||||
.clip(CircleShape)
|
text = tags.joinToString(separator = " #", prefix = "#"),
|
||||||
.background(Color(not.color))
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
.size(32.dp)
|
style = MaterialTheme.typography.labelSmall
|
||||||
.padding(8.dp),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
AsyncImage(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
model = icon,
|
|
||||||
contentDescription = null
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Column(
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
if (not.title != null) {
|
|
||||||
Text(
|
|
||||||
not.title!!,
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (not.text != null) {
|
|
||||||
Text(
|
app.versionName?.let {
|
||||||
not.text!!,
|
Text(
|
||||||
modifier = Modifier.padding(top = 2.dp),
|
text = stringResource(R.string.app_info_version, it),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
maxLines = 1,
|
modifier = Modifier.padding(top = 4.dp),
|
||||||
overflow = TextOverflow.Ellipsis
|
maxLines = 1,
|
||||||
)
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = app.componentName.packageName,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
modifier = Modifier.padding(top = 1.dp),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.profile_private_profile_state_locked),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
ShapedLauncherIcon(
|
||||||
|
size = 48.dp,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp),
|
||||||
|
badge = { badge },
|
||||||
|
icon = { icon },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val notifications by viewModel.notifications.collectAsState(emptyList())
|
||||||
|
|
||||||
|
AnimatedVisibility(notifications.isNotEmpty()) {
|
||||||
|
var showAllNotifications by remember { mutableStateOf(false) }
|
||||||
|
AnimatedContent(
|
||||||
|
showAllNotifications || notifications.size == 1,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.padding(bottom = 12.dp)
|
||||||
|
.border(
|
||||||
|
1.dp,
|
||||||
|
MaterialTheme.colorScheme.outlineVariant,
|
||||||
|
MaterialTheme.shapes.small
|
||||||
|
)
|
||||||
|
.clip(MaterialTheme.shapes.small)
|
||||||
|
) { showAll ->
|
||||||
|
if (showAll) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.animateContentSize()
|
||||||
|
) {
|
||||||
|
for ((i, not) in notifications.withIndex()) {
|
||||||
|
val icon =
|
||||||
|
remember(not.smallIcon) {
|
||||||
|
not.smallIcon?.loadDrawable(
|
||||||
|
context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (not.title == null && not.text == null) continue
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
HorizontalDivider()
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
try {
|
||||||
|
not.contentIntent?.sendWithBackgroundPermission(
|
||||||
|
context
|
||||||
|
)
|
||||||
|
} catch (e: PendingIntent.CanceledException) {
|
||||||
|
CrashReporter.logException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 12.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(Color(not.color))
|
||||||
|
.size(32.dp)
|
||||||
|
.padding(8.dp),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
AsyncImage(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
model = icon,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
if (not.title != null) {
|
||||||
|
Text(
|
||||||
|
not.title!!,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (not.text != null) {
|
||||||
|
Text(
|
||||||
|
not.text!!,
|
||||||
|
modifier = Modifier.padding(top = 2.dp),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (not.isClearable) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.clearNotification(not)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(Icons.Rounded.Clear, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (not.isClearable) {
|
} else {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
showAllNotifications = true
|
||||||
|
}
|
||||||
|
.padding(vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.Notifications,
|
||||||
|
null,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
pluralStringResource(
|
||||||
|
R.plurals.app_info_notifications,
|
||||||
|
notifications.size,
|
||||||
|
notifications.size
|
||||||
|
),
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
Icons.AutoMirrored.Rounded.NavigateNext,
|
||||||
|
null,
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val shortcuts by viewModel.children.collectAsState(emptyList())
|
||||||
|
if (shortcuts.isNotEmpty()) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.padding(bottom = 12.dp)
|
||||||
|
.border(
|
||||||
|
1.dp,
|
||||||
|
MaterialTheme.colorScheme.outlineVariant,
|
||||||
|
MaterialTheme.shapes.small
|
||||||
|
)
|
||||||
|
.clip(MaterialTheme.shapes.small)
|
||||||
|
) {
|
||||||
|
for ((i, shortcut) in shortcuts.withIndex()) {
|
||||||
|
val isPinned by remember(shortcut) {
|
||||||
|
viewModel.isChildPinned(
|
||||||
|
shortcut
|
||||||
|
)
|
||||||
|
}.collectAsState(
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
val iconSizePx = 32.dp.toPixels()
|
||||||
|
|
||||||
|
val icon by
|
||||||
|
remember {
|
||||||
|
viewModel.getChildIcon(
|
||||||
|
shortcut,
|
||||||
|
iconSizePx.toInt()
|
||||||
|
)
|
||||||
|
}.collectAsState(null)
|
||||||
|
if (i > 0) {
|
||||||
|
HorizontalDivider()
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
viewModel.launchChild(context, shortcut)
|
||||||
|
}
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
ShapedLauncherIcon(
|
||||||
|
size = 32.dp,
|
||||||
|
icon = { icon },
|
||||||
|
shape = CircleShape,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 12.dp)
|
||||||
|
.size(32.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
shortcut.labelOverride ?: shortcut.label,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.clearNotification(not)
|
if (isPinned) {
|
||||||
|
viewModel.unpinChild(shortcut)
|
||||||
|
} else {
|
||||||
|
viewModel.pinChild(shortcut)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Rounded.Clear, null)
|
Icon(
|
||||||
|
if (isPinned) Icons.Rounded.Star else Icons.Rounded.StarOutline,
|
||||||
|
stringResource(if (isPinned) R.string.menu_favorites_unpin else R.string.menu_favorites_pin),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable {
|
|
||||||
showAllNotifications = true
|
|
||||||
}
|
|
||||||
.padding(vertical = 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Rounded.Notifications,
|
|
||||||
null,
|
|
||||||
modifier = Modifier.padding(horizontal = 16.dp)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
pluralStringResource(
|
|
||||||
R.plurals.app_info_notifications,
|
|
||||||
notifications.size,
|
|
||||||
notifications.size
|
|
||||||
),
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
)
|
|
||||||
Icon(
|
|
||||||
Icons.AutoMirrored.Rounded.NavigateNext,
|
|
||||||
null,
|
|
||||||
modifier = Modifier.padding(horizontal = 12.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val shortcuts by viewModel.children.collectAsState(emptyList())
|
val toolbarActions = mutableListOf<ToolbarAction>()
|
||||||
if (shortcuts.isNotEmpty()) {
|
|
||||||
Column(
|
if (LocalFavoritesEnabled.current) {
|
||||||
modifier = Modifier
|
val isPinned by viewModel.isPinned.collectAsState(false)
|
||||||
.fillMaxWidth()
|
val favAction = if (isPinned) {
|
||||||
.padding(horizontal = 16.dp)
|
DefaultToolbarAction(
|
||||||
.padding(bottom = 12.dp)
|
label = stringResource(R.string.menu_favorites_unpin),
|
||||||
.border(
|
icon = Icons.Rounded.Star,
|
||||||
1.dp,
|
action = {
|
||||||
MaterialTheme.colorScheme.outlineVariant,
|
viewModel.unpin()
|
||||||
MaterialTheme.shapes.small
|
}
|
||||||
)
|
)
|
||||||
.clip(MaterialTheme.shapes.small)
|
} else {
|
||||||
) {
|
DefaultToolbarAction(
|
||||||
for ((i, shortcut) in shortcuts.withIndex()) {
|
label = stringResource(R.string.menu_favorites_pin),
|
||||||
val isPinned by remember(shortcut) { viewModel.isChildPinned(shortcut) }.collectAsState(
|
icon = Icons.Rounded.StarOutline,
|
||||||
false
|
action = {
|
||||||
|
viewModel.pin()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
toolbarActions.add(favAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!app.isPrivate) {
|
||||||
|
toolbarActions.add(
|
||||||
|
DefaultToolbarAction(
|
||||||
|
label = stringResource(R.string.menu_app_info),
|
||||||
|
icon = Icons.Rounded.Info
|
||||||
|
) {
|
||||||
|
app.openAppDetails(context)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
toolbarActions.add(
|
||||||
|
DefaultToolbarAction(
|
||||||
|
label = stringResource(R.string.menu_launch),
|
||||||
|
icon = Icons.AutoMirrored.Rounded.OpenInNew,
|
||||||
|
action = {
|
||||||
|
viewModel.launch(context)
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val iconSizePx = 32.dp.toPixels()
|
val sheetManager = LocalBottomSheetManager.current
|
||||||
|
if (!app.isPrivate) {
|
||||||
val icon by
|
toolbarActions.add(
|
||||||
remember {
|
DefaultToolbarAction(
|
||||||
viewModel.getChildIcon(
|
label = stringResource(R.string.menu_customize),
|
||||||
shortcut,
|
icon = Icons.Rounded.Tune,
|
||||||
iconSizePx.toInt()
|
action = { sheetManager.showCustomizeSearchableModal(app) }
|
||||||
)
|
))
|
||||||
}.collectAsState(null)
|
|
||||||
if (i > 0) {
|
|
||||||
HorizontalDivider()
|
|
||||||
}
|
}
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable {
|
|
||||||
viewModel.launchChild(context, shortcut)
|
|
||||||
}
|
|
||||||
.padding(vertical = 4.dp)
|
|
||||||
) {
|
|
||||||
ShapedLauncherIcon(
|
|
||||||
size = 32.dp,
|
|
||||||
icon = { icon },
|
|
||||||
shape = CircleShape,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 12.dp)
|
|
||||||
.size(32.dp),
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
if (!app.isPrivate) {
|
||||||
shortcut.labelOverride ?: shortcut.label,
|
val storeDetails = remember(app) { app.getStoreDetails(context) }
|
||||||
modifier = Modifier.weight(1f),
|
val shareAction = if (storeDetails == null) {
|
||||||
style = MaterialTheme.typography.titleSmall,
|
DefaultToolbarAction(
|
||||||
maxLines = 1,
|
label = stringResource(R.string.menu_share),
|
||||||
overflow = TextOverflow.Ellipsis
|
icon = Icons.Rounded.Share
|
||||||
)
|
) {
|
||||||
IconButton(
|
scope.launch {
|
||||||
onClick = {
|
app.shareApkFile(context)
|
||||||
if (isPinned) {
|
|
||||||
viewModel.unpinChild(shortcut)
|
|
||||||
} else {
|
|
||||||
viewModel.pinChild(shortcut)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
} else {
|
||||||
Icon(
|
SubmenuToolbarAction(
|
||||||
if (isPinned) Icons.Rounded.Star else Icons.Rounded.StarOutline,
|
label = stringResource(R.string.menu_share),
|
||||||
stringResource(if (isPinned) R.string.menu_favorites_unpin else R.string.menu_favorites_pin),
|
icon = Icons.Rounded.Share,
|
||||||
|
children = listOf(
|
||||||
|
DefaultToolbarAction(
|
||||||
|
label = stringResource(
|
||||||
|
R.string.menu_share_store_link,
|
||||||
|
storeDetails.label
|
||||||
|
),
|
||||||
|
icon = Icons.Rounded.Link,
|
||||||
|
action = {
|
||||||
|
val shareIntent = Intent(Intent.ACTION_SEND)
|
||||||
|
shareIntent.putExtra(
|
||||||
|
Intent.EXTRA_TEXT,
|
||||||
|
storeDetails.url
|
||||||
|
)
|
||||||
|
shareIntent.type = "text/plain"
|
||||||
|
context.startActivity(
|
||||||
|
Intent.createChooser(
|
||||||
|
shareIntent,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
DefaultToolbarAction(
|
||||||
|
label = stringResource(R.string.menu_share_apk_file),
|
||||||
|
icon = Icons.Rounded.Android
|
||||||
|
) {
|
||||||
|
scope.launch {
|
||||||
|
app.shareApkFile(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
toolbarActions.add(shareAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
if (app.canUninstall) {
|
||||||
}
|
toolbarActions.add(
|
||||||
}
|
DefaultToolbarAction(
|
||||||
|
label = stringResource(R.string.menu_uninstall),
|
||||||
val toolbarActions = mutableListOf<ToolbarAction>()
|
icon = Icons.Rounded.Delete,
|
||||||
|
) {
|
||||||
if (LocalFavoritesEnabled.current) {
|
app.uninstall(context)
|
||||||
val isPinned by viewModel.isPinned.collectAsState(false)
|
onBack()
|
||||||
val favAction = if (isPinned) {
|
}
|
||||||
DefaultToolbarAction(
|
)
|
||||||
label = stringResource(R.string.menu_favorites_unpin),
|
|
||||||
icon = Icons.Rounded.Star,
|
|
||||||
action = {
|
|
||||||
viewModel.unpin()
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
} else {
|
|
||||||
DefaultToolbarAction(
|
|
||||||
label = stringResource(R.string.menu_favorites_pin),
|
|
||||||
icon = Icons.Rounded.StarOutline,
|
|
||||||
action = {
|
|
||||||
viewModel.pin()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
toolbarActions.add(favAction)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!app.isPrivate) {
|
Toolbar(
|
||||||
toolbarActions.add(
|
leftActions = listOf(
|
||||||
DefaultToolbarAction(
|
DefaultToolbarAction(
|
||||||
label = stringResource(R.string.menu_app_info),
|
label = stringResource(id = R.string.menu_back),
|
||||||
icon = Icons.Rounded.Info
|
icon = Icons.AutoMirrored.Rounded.ArrowBack
|
||||||
) {
|
) {
|
||||||
app.openAppDetails(context)
|
onBack()
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
toolbarActions.add(
|
|
||||||
DefaultToolbarAction(
|
|
||||||
label = stringResource(R.string.menu_launch),
|
|
||||||
icon = Icons.AutoMirrored.Rounded.OpenInNew,
|
|
||||||
action = {
|
|
||||||
viewModel.launch(context)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val sheetManager = LocalBottomSheetManager.current
|
|
||||||
if (!app.isPrivate) {
|
|
||||||
toolbarActions.add(DefaultToolbarAction(
|
|
||||||
label = stringResource(R.string.menu_customize),
|
|
||||||
icon = Icons.Rounded.Tune,
|
|
||||||
action = { sheetManager.showCustomizeSearchableModal(app) }
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!app.isPrivate) {
|
|
||||||
val storeDetails = remember(app) { app.getStoreDetails(context) }
|
|
||||||
val shareAction = if (storeDetails == null) {
|
|
||||||
DefaultToolbarAction(
|
|
||||||
label = stringResource(R.string.menu_share),
|
|
||||||
icon = Icons.Rounded.Share
|
|
||||||
) {
|
|
||||||
scope.launch {
|
|
||||||
app.shareApkFile(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SubmenuToolbarAction(
|
|
||||||
label = stringResource(R.string.menu_share),
|
|
||||||
icon = Icons.Rounded.Share,
|
|
||||||
children = listOf(
|
|
||||||
DefaultToolbarAction(
|
|
||||||
label = stringResource(
|
|
||||||
R.string.menu_share_store_link,
|
|
||||||
storeDetails.label
|
|
||||||
),
|
|
||||||
icon = Icons.Rounded.Link,
|
|
||||||
action = {
|
|
||||||
val shareIntent = Intent(Intent.ACTION_SEND)
|
|
||||||
shareIntent.putExtra(Intent.EXTRA_TEXT, storeDetails.url)
|
|
||||||
shareIntent.type = "text/plain"
|
|
||||||
context.startActivity(Intent.createChooser(shareIntent, null))
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
DefaultToolbarAction(
|
rightActions = toolbarActions
|
||||||
label = stringResource(R.string.menu_share_apk_file),
|
|
||||||
icon = Icons.Rounded.Android
|
|
||||||
) {
|
|
||||||
scope.launch {
|
|
||||||
app.shareApkFile(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
|
} else {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
if (LocalGridSettings.current.showListIcons) {
|
||||||
|
ShapedLauncherIcon(
|
||||||
|
size = LocalGridSettings.current.iconSize.dp,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 16.dp),
|
||||||
|
badge = { badge },
|
||||||
|
icon = { icon },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
text = app.labelOverride ?: app.label,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
modifier = Modifier
|
||||||
|
.sharedBounds(
|
||||||
|
rememberSharedContentState("label"),
|
||||||
|
this@AnimatedContent,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
toolbarActions.add(shareAction)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app.canUninstall) {
|
|
||||||
toolbarActions.add(
|
|
||||||
DefaultToolbarAction(
|
|
||||||
label = stringResource(R.string.menu_uninstall),
|
|
||||||
icon = Icons.Rounded.Delete,
|
|
||||||
) {
|
|
||||||
app.uninstall(context)
|
|
||||||
onBack()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Toolbar(
|
|
||||||
leftActions = listOf(
|
|
||||||
DefaultToolbarAction(
|
|
||||||
label = stringResource(id = R.string.menu_back),
|
|
||||||
icon = Icons.AutoMirrored.Rounded.ArrowBack
|
|
||||||
) {
|
|
||||||
onBack()
|
|
||||||
}
|
|
||||||
),
|
|
||||||
rightActions = toolbarActions
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -507,6 +565,7 @@ fun AppItemGridPopup(
|
|||||||
y = lerp(-16.dp, 0.dp, animationProgress)
|
y = lerp(-16.dp, 0.dp, animationProgress)
|
||||||
),
|
),
|
||||||
app = app,
|
app = app,
|
||||||
|
showDetails = true,
|
||||||
onBack = onDismiss
|
onBack = onDismiss
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
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.layout.size
|
||||||
@ -22,9 +22,9 @@ import androidx.compose.material3.HorizontalDivider
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LeadingIconTab
|
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.PrimaryScrollableTabRow
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
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.graphics.Color
|
||||||
@ -36,6 +36,8 @@ 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.launcher.search.common.list.ListItem
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.common.list.ListResults
|
||||||
import de.mm20.launcher2.ui.layout.BottomReversed
|
import de.mm20.launcher2.ui.layout.BottomReversed
|
||||||
import de.mm20.launcher2.ui.locals.LocalGridSettings
|
import de.mm20.launcher2.ui.locals.LocalGridSettings
|
||||||
|
|
||||||
@ -47,158 +49,192 @@ fun LazyListScope.AppResults(
|
|||||||
isProfileLocked: Boolean = false,
|
isProfileLocked: Boolean = false,
|
||||||
onProfileLockChange: ((Profile, Boolean) -> Unit)? = null,
|
onProfileLockChange: ((Profile, Boolean) -> Unit)? = null,
|
||||||
apps: List<Application>,
|
apps: List<Application>,
|
||||||
|
selectedIndex: Int,
|
||||||
|
onSelect: (Int) -> Unit,
|
||||||
highlightedItem: Application? = null,
|
highlightedItem: Application? = null,
|
||||||
columns: Int,
|
columns: Int,
|
||||||
reverse: Boolean,
|
reverse: Boolean,
|
||||||
|
showList: Boolean,
|
||||||
) {
|
) {
|
||||||
|
val before = if (profiles.size > 1) {
|
||||||
GridResults(
|
@Composable {
|
||||||
key = "apps",
|
Column(
|
||||||
items = apps,
|
verticalArrangement = if (reverse) Arrangement.BottomReversed else Arrangement.Top,
|
||||||
before = if (profiles.size > 1) {
|
) {
|
||||||
{
|
PrimaryScrollableTabRow(
|
||||||
Column(
|
selectedTabIndex = selectedProfileIndex,
|
||||||
verticalArrangement = if (reverse) Arrangement.BottomReversed else Arrangement.Top,
|
containerColor = Color.Transparent,
|
||||||
|
edgePadding = 16.dp,
|
||||||
|
divider = {}
|
||||||
) {
|
) {
|
||||||
PrimaryScrollableTabRow(
|
for ((i, profile) in profiles.withIndex()) {
|
||||||
selectedTabIndex = selectedProfileIndex,
|
LeadingIconTab(
|
||||||
containerColor = Color.Transparent,
|
selected = selectedProfileIndex == profiles.indexOf(profile),
|
||||||
edgePadding = 16.dp,
|
text = {
|
||||||
divider = {}
|
Text(
|
||||||
) {
|
|
||||||
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) {
|
when (profile.type) {
|
||||||
Profile.Type.Personal -> Icon(
|
Profile.Type.Personal -> stringResource(R.string.apps_profile_main)
|
||||||
Icons.Rounded.Person,
|
Profile.Type.Work -> stringResource(R.string.apps_profile_work)
|
||||||
contentDescription = null
|
Profile.Type.Private -> stringResource(R.string.apps_profile_private)
|
||||||
)
|
|
||||||
|
|
||||||
Profile.Type.Work -> Icon(
|
|
||||||
Icons.Rounded.Work,
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
|
|
||||||
Profile.Type.Private -> Icon(
|
|
||||||
Icons.Rounded.PrivateSpace,
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
onClick = {
|
},
|
||||||
onProfileSelected(i)
|
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 (!showList || isProfileLocked) {
|
||||||
if (profileType != Profile.Type.Personal) {
|
HorizontalDivider()
|
||||||
if (isProfileLocked) {
|
}
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
val profileType = profiles[selectedProfileIndex].type
|
||||||
.padding(12.dp)
|
if (profileType != Profile.Type.Personal) {
|
||||||
.fillMaxWidth()
|
if (isProfileLocked) {
|
||||||
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, MaterialTheme.shapes.small)
|
Column(
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainer, MaterialTheme.shapes.small)
|
modifier = Modifier
|
||||||
.padding(vertical = 64.dp),
|
.padding(12.dp)
|
||||||
verticalArrangement = Arrangement.Center,
|
.fillMaxWidth()
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
.border(
|
||||||
) {
|
1.dp,
|
||||||
Icon(
|
MaterialTheme.colorScheme.outlineVariant,
|
||||||
if (profileType == Profile.Type.Work) Icons.Rounded.WorkOff else Icons.Rounded.Lock,
|
MaterialTheme.shapes.small
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(48.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.secondary,
|
|
||||||
)
|
)
|
||||||
Text(
|
.background(
|
||||||
stringResource(
|
MaterialTheme.colorScheme.surfaceContainer,
|
||||||
if (profileType == Profile.Type.Work) R.string.profile_work_profile_state_locked
|
MaterialTheme.shapes.small
|
||||||
else R.string.profile_private_profile_state_locked
|
|
||||||
),
|
|
||||||
modifier = Modifier.padding(top = 8.dp),
|
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
)
|
)
|
||||||
if (showProfileLockControls) {
|
.padding(vertical = 64.dp),
|
||||||
Button(
|
verticalArrangement = Arrangement.Center,
|
||||||
modifier = Modifier.padding(top = 32.dp),
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
onClick = {
|
) {
|
||||||
onProfileLockChange?.invoke(
|
Icon(
|
||||||
profiles[selectedProfileIndex],
|
if (profileType == Profile.Type.Work) Icons.Rounded.WorkOff else Icons.Rounded.Lock,
|
||||||
false
|
contentDescription = null,
|
||||||
)
|
modifier = Modifier.size(48.dp),
|
||||||
},
|
tint = MaterialTheme.colorScheme.secondary,
|
||||||
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
|
)
|
||||||
) {
|
Text(
|
||||||
Icon(
|
stringResource(
|
||||||
if (profileType == Profile.Type.Work) Icons.Rounded.Work else Icons.Rounded.LockOpen,
|
if (profileType == Profile.Type.Work) R.string.profile_work_profile_state_locked
|
||||||
contentDescription = null,
|
else R.string.profile_private_profile_state_locked
|
||||||
modifier = Modifier
|
),
|
||||||
.padding(end = ButtonDefaults.IconSpacing)
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
.size(ButtonDefaults.IconSize)
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
)
|
||||||
|
if (showProfileLockControls) {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.padding(top = 32.dp),
|
||||||
|
onClick = {
|
||||||
|
onProfileLockChange?.invoke(
|
||||||
|
profiles[selectedProfileIndex],
|
||||||
|
false
|
||||||
)
|
)
|
||||||
Text(
|
},
|
||||||
stringResource(
|
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
|
||||||
if (profileType == Profile.Type.Work) R.string.profile_work_profile_action_unlock
|
) {
|
||||||
else R.string.profile_private_profile_action_unlock
|
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 if (showProfileLockControls) {
|
}
|
||||||
FilledTonalButton(
|
} else if (showProfileLockControls) {
|
||||||
|
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
|
modifier = Modifier
|
||||||
.padding(12.dp)
|
.padding(end = ButtonDefaults.IconSpacing)
|
||||||
.fillMaxWidth(),
|
.size(ButtonDefaults.IconSize)
|
||||||
onClick = {
|
)
|
||||||
onProfileLockChange?.invoke(
|
Text(
|
||||||
profiles[selectedProfileIndex],
|
stringResource(
|
||||||
true
|
if (profileType == Profile.Type.Work) R.string.profile_work_profile_action_lock
|
||||||
)
|
else R.string.profile_private_profile_action_lock
|
||||||
},
|
|
||||||
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,
|
}
|
||||||
itemContent = {
|
} else null
|
||||||
GridItem(
|
if (showList) {
|
||||||
item = it,
|
ListResults(
|
||||||
showLabels = LocalGridSettings.current.showLabels,
|
key = "apps",
|
||||||
highlight = it.key == highlightedItem?.key
|
items = apps,
|
||||||
)
|
before = before?.let { { it() } },
|
||||||
},
|
selectedIndex = selectedIndex,
|
||||||
reverse = reverse,
|
itemContent = { app, showDetails, index ->
|
||||||
columns = columns,
|
ListItem(
|
||||||
)
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
item = app,
|
||||||
|
showDetails = showDetails,
|
||||||
|
onShowDetails = { onSelect(if(it) index else -1) },
|
||||||
|
highlight = highlightedItem?.key == app.key
|
||||||
|
)
|
||||||
|
},
|
||||||
|
reverse = reverse,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
GridResults(
|
||||||
|
key = "apps",
|
||||||
|
items = apps,
|
||||||
|
before = before,
|
||||||
|
itemContent = {
|
||||||
|
GridItem(
|
||||||
|
item = it,
|
||||||
|
showLabels = LocalGridSettings.current.showLabels,
|
||||||
|
highlight = it.key == highlightedItem?.key
|
||||||
|
)
|
||||||
|
},
|
||||||
|
reverse = reverse,
|
||||||
|
columns = columns,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
package de.mm20.launcher2.ui.launcher.search.common.grid
|
package de.mm20.launcher2.ui.launcher.search.common.grid
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.core.Animatable
|
import androidx.compose.animation.core.Animatable
|
||||||
import androidx.compose.animation.core.MutableTransitionState
|
import androidx.compose.animation.core.MutableTransitionState
|
||||||
@ -13,6 +12,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@ -110,6 +110,7 @@ fun GridItem(
|
|||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
.padding(4.dp)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (!launchOnPress || !viewModel.launch(context, bounds)) {
|
if (!launchOnPress || !viewModel.launch(context, bounds)) {
|
||||||
@ -170,7 +171,9 @@ fun GridItem(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(4.dp)
|
.padding(4.dp)
|
||||||
.onGloballyPositioned {
|
.onGloballyPositioned {
|
||||||
bounds = it.boundsInWindow().roundToIntRect()
|
bounds = it
|
||||||
|
.boundsInWindow()
|
||||||
|
.roundToIntRect()
|
||||||
} then
|
} then
|
||||||
if (highlight) Modifier.background(
|
if (highlight) Modifier.background(
|
||||||
MaterialTheme.colorScheme.surface,
|
MaterialTheme.colorScheme.surface,
|
||||||
@ -195,10 +198,10 @@ fun GridItem(
|
|||||||
color = MaterialTheme.colorScheme.onBackground,
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (showPopup) {
|
if (showPopup) {
|
||||||
ItemPopup(origin = bounds, searchable = item, onDismissRequest = { showPopup = false })
|
ItemPopup(origin = bounds, searchable = item, onDismissRequest = { showPopup = false })
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,7 +401,7 @@ private fun Modifier.placeOverlay(
|
|||||||
constraints.maxHeight - placeable.height,
|
constraints.maxHeight - placeable.height,
|
||||||
),
|
),
|
||||||
animationProgress.pow(2)
|
animationProgress.pow(2)
|
||||||
).toInt()
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,8 +83,8 @@ fun <T : SavableSearchable> LazyListScope.GridResults(
|
|||||||
.padding(
|
.padding(
|
||||||
top = if (it == 0) 8.dp else 0.dp,
|
top = if (it == 0) 8.dp else 0.dp,
|
||||||
bottom = if (it == rows - 1) 8.dp else 0.dp,
|
bottom = if (it == rows - 1) 8.dp else 0.dp,
|
||||||
start = 4.dp,
|
start = if (columns == 1) 0.dp else 4.dp,
|
||||||
end = 4.dp,
|
end = if (columns == 1) 0.dp else 4.dp,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Row {
|
Row {
|
||||||
@ -94,7 +94,6 @@ fun <T : SavableSearchable> LazyListScope.GridResults(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(4.dp)
|
|
||||||
) {
|
) {
|
||||||
itemContent(item)
|
itemContent(item)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -22,6 +23,7 @@ import androidx.compose.ui.unit.IntRect
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.roundToIntRect
|
import androidx.compose.ui.unit.roundToIntRect
|
||||||
import de.mm20.launcher2.search.AppShortcut
|
import de.mm20.launcher2.search.AppShortcut
|
||||||
|
import de.mm20.launcher2.search.Application
|
||||||
import de.mm20.launcher2.search.Article
|
import de.mm20.launcher2.search.Article
|
||||||
import de.mm20.launcher2.search.CalendarEvent
|
import de.mm20.launcher2.search.CalendarEvent
|
||||||
import de.mm20.launcher2.search.Contact
|
import de.mm20.launcher2.search.Contact
|
||||||
@ -30,6 +32,7 @@ import de.mm20.launcher2.search.Location
|
|||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.search.Website
|
import de.mm20.launcher2.search.Website
|
||||||
import de.mm20.launcher2.ui.ktx.toPixels
|
import de.mm20.launcher2.ui.ktx.toPixels
|
||||||
|
import de.mm20.launcher2.ui.launcher.search.apps.AppItem
|
||||||
import de.mm20.launcher2.ui.launcher.search.calendar.CalendarItem
|
import de.mm20.launcher2.ui.launcher.search.calendar.CalendarItem
|
||||||
import de.mm20.launcher2.ui.launcher.search.common.SearchableItemVM
|
import de.mm20.launcher2.ui.launcher.search.common.SearchableItemVM
|
||||||
import de.mm20.launcher2.ui.launcher.search.contacts.ContactItem
|
import de.mm20.launcher2.ui.launcher.search.contacts.ContactItem
|
||||||
@ -80,6 +83,25 @@ fun ListItem(
|
|||||||
LocalContentColor provides MaterialTheme.colorScheme.onSurface
|
LocalContentColor provides MaterialTheme.colorScheme.onSurface
|
||||||
) {
|
) {
|
||||||
when (item) {
|
when (item) {
|
||||||
|
is Application -> {
|
||||||
|
AppItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(max = 9999.dp) // we have infinite space, but there is an inner scroll that needs a constraint
|
||||||
|
.combinedClickable(
|
||||||
|
enabled = !showDetails,
|
||||||
|
onClick = {
|
||||||
|
if (!viewModel.launch(context, bounds)) {
|
||||||
|
onShowDetails(true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClick = { onShowDetails(true) }
|
||||||
|
),
|
||||||
|
app = item,
|
||||||
|
showDetails = showDetails,
|
||||||
|
onBack = { onShowDetails(false) }
|
||||||
|
)
|
||||||
|
}
|
||||||
is Contact -> {
|
is Contact -> {
|
||||||
ContactItem(
|
ContactItem(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@ -110,6 +110,26 @@ fun IconsSettingsScreen() {
|
|||||||
viewModel.setShowLabels(it)
|
viewModel.setShowLabels(it)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
SwitchPreference(
|
||||||
|
title = stringResource(R.string.preference_grid_list_style),
|
||||||
|
summary = stringResource(R.string.preference_grid_list_style_summary),
|
||||||
|
value = grid.showList,
|
||||||
|
onValueChanged = {
|
||||||
|
viewModel.setShowList(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
AnimatedVisibility(
|
||||||
|
grid.showList
|
||||||
|
) {
|
||||||
|
SwitchPreference(
|
||||||
|
title = stringResource(R.string.preference_grid_list_icons),
|
||||||
|
summary = stringResource(R.string.preference_grid_list_icons_summary),
|
||||||
|
value = grid.showListIcons,
|
||||||
|
onValueChanged = {
|
||||||
|
viewModel.setShowListIcons(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
SliderPreference(
|
SliderPreference(
|
||||||
title = stringResource(R.string.preference_grid_column_count),
|
title = stringResource(R.string.preference_grid_column_count),
|
||||||
value = grid.columnCount,
|
value = grid.columnCount,
|
||||||
|
|||||||
@ -53,6 +53,14 @@ class IconsSettingsScreenVM(
|
|||||||
uiSettings.setGridShowLabels(showLabels)
|
uiSettings.setGridShowLabels(showLabels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setShowList(showList: Boolean) {
|
||||||
|
uiSettings.setGridShowList(showList)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setShowListIcons(showIcons: Boolean) {
|
||||||
|
uiSettings.setGridShowListIcons(showIcons)
|
||||||
|
}
|
||||||
|
|
||||||
val iconShape = uiSettings.iconShape
|
val iconShape = uiSettings.iconShape
|
||||||
fun setIconShape(iconShape: IconShape) {
|
fun setIconShape(iconShape: IconShape) {
|
||||||
uiSettings.setIconShape(iconShape)
|
uiSettings.setIconShape(iconShape)
|
||||||
|
|||||||
@ -557,8 +557,12 @@
|
|||||||
<string name="preference_category_grid">Grid</string>
|
<string name="preference_category_grid">Grid</string>
|
||||||
<string name="preference_grid_icon_size">Icon size</string>
|
<string name="preference_grid_icon_size">Icon size</string>
|
||||||
<string name="preference_grid_column_count">Number of columns</string>
|
<string name="preference_grid_column_count">Number of columns</string>
|
||||||
|
<string name="preference_grid_list_style">Show app results in a list</string>
|
||||||
|
<string name="preference_grid_list_icons">Show app icons in list</string>
|
||||||
<string name="preference_grid_labels">Show labels</string>
|
<string name="preference_grid_labels">Show labels</string>
|
||||||
<string name="preference_grid_labels_summary">Show the app name below the icon</string>
|
<string name="preference_grid_labels_summary">Show the app name below the icon</string>
|
||||||
|
<string name="preference_grid_list_style_summary">Show applications in a list view instead of grid</string>
|
||||||
|
<string name="preference_grid_list_icons_summary">Show icons in the list view</string>
|
||||||
<string name="preference_screen_debug">Debug</string>
|
<string name="preference_screen_debug">Debug</string>
|
||||||
<string name="preference_screen_debug_summary">Troubleshooting tools</string>
|
<string name="preference_screen_debug_summary">Troubleshooting tools</string>
|
||||||
<string name="preference_category_widgets">Widgets</string>
|
<string name="preference_category_widgets">Widgets</string>
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
package de.mm20.launcher2.preferences
|
package de.mm20.launcher2.preferences
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import de.mm20.launcher2.preferences.search.LocationSearchSettings
|
|
||||||
import de.mm20.launcher2.search.SearchFilters
|
import de.mm20.launcher2.search.SearchFilters
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class LauncherSettingsData internal constructor(
|
data class LauncherSettingsData internal constructor(
|
||||||
val schemaVersion: Int = 2,
|
val schemaVersion: Int = 3,
|
||||||
|
|
||||||
val uiColorScheme: ColorScheme = ColorScheme.System,
|
val uiColorScheme: ColorScheme = ColorScheme.System,
|
||||||
val uiTheme: ThemeDescriptor = ThemeDescriptor.Default,
|
val uiTheme: ThemeDescriptor = ThemeDescriptor.Default,
|
||||||
@ -83,6 +82,8 @@ data class LauncherSettingsData internal constructor(
|
|||||||
val gridColumnCount: Int = 5,
|
val gridColumnCount: Int = 5,
|
||||||
val gridIconSize: Int = 48,
|
val gridIconSize: Int = 48,
|
||||||
val gridLabels: Boolean = true,
|
val gridLabels: Boolean = true,
|
||||||
|
val gridList: Boolean = false,
|
||||||
|
val gridListIcons: Boolean = true,
|
||||||
|
|
||||||
val searchBarStyle: SearchBarStyle = SearchBarStyle.Transparent,
|
val searchBarStyle: SearchBarStyle = SearchBarStyle.Transparent,
|
||||||
val searchBarColors: SearchBarColors = SearchBarColors.Auto,
|
val searchBarColors: SearchBarColors = SearchBarColors.Auto,
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package de.mm20.launcher2.preferences
|
|||||||
|
|
||||||
import androidx.datastore.core.CorruptionException
|
import androidx.datastore.core.CorruptionException
|
||||||
import androidx.datastore.core.Serializer
|
import androidx.datastore.core.Serializer
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
import kotlinx.serialization.json.decodeFromStream
|
||||||
@ -21,6 +22,7 @@ internal object LauncherSettingsDataSerializer : Serializer<LauncherSettingsData
|
|||||||
override val defaultValue: LauncherSettingsData
|
override val defaultValue: LauncherSettingsData
|
||||||
get() = LauncherSettingsData(schemaVersion = 0)
|
get() = LauncherSettingsData(schemaVersion = 0)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
override suspend fun readFrom(input: InputStream): LauncherSettingsData {
|
override suspend fun readFrom(input: InputStream): LauncherSettingsData {
|
||||||
try {
|
try {
|
||||||
return json.decodeFromStream(input)
|
return json.decodeFromStream(input)
|
||||||
|
|||||||
@ -25,6 +25,8 @@ data class GridSettings(
|
|||||||
val columnCount: Int = 5,
|
val columnCount: Int = 5,
|
||||||
val iconSize: Int = 48,
|
val iconSize: Int = 48,
|
||||||
val showLabels: Boolean = true,
|
val showLabels: Boolean = true,
|
||||||
|
val showList: Boolean = false,
|
||||||
|
val showListIcons: Boolean = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
class UiSettings internal constructor(
|
class UiSettings internal constructor(
|
||||||
@ -48,6 +50,8 @@ class UiSettings internal constructor(
|
|||||||
get() = launcherDataStore.data.map {
|
get() = launcherDataStore.data.map {
|
||||||
GridSettings(
|
GridSettings(
|
||||||
showLabels = it.gridLabels,
|
showLabels = it.gridLabels,
|
||||||
|
showList = it.gridList,
|
||||||
|
showListIcons = it.gridListIcons,
|
||||||
iconSize = it.gridIconSize,
|
iconSize = it.gridIconSize,
|
||||||
columnCount = it.gridColumnCount,
|
columnCount = it.gridColumnCount,
|
||||||
)
|
)
|
||||||
@ -71,6 +75,17 @@ class UiSettings internal constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setGridShowList(showList: Boolean) {
|
||||||
|
launcherDataStore.update {
|
||||||
|
it.copy(gridList = showList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setGridShowListIcons(showIcons: Boolean) {
|
||||||
|
launcherDataStore.update {
|
||||||
|
it.copy(gridListIcons = showIcons)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val cardStyle
|
val cardStyle
|
||||||
get() = launcherDataStore.data.map {
|
get() = launcherDataStore.data.map {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user