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,8 +101,11 @@ fun AppItem(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
SharedTransitionLayout(modifier = modifier) {
|
||||||
|
AnimatedContent(showDetails) { showDetails ->
|
||||||
|
if (showDetails) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.verticalScroll(rememberScrollState())
|
modifier = Modifier.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
Row {
|
Row {
|
||||||
Column(
|
Column(
|
||||||
@ -108,7 +115,12 @@ fun AppItem(
|
|||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = app.labelOverride ?: app.label,
|
text = app.labelOverride ?: app.label,
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
modifier = Modifier
|
||||||
|
.sharedBounds(
|
||||||
|
rememberSharedContentState("label"),
|
||||||
|
this@AnimatedContent,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!app.isPrivate) {
|
if (!app.isPrivate) {
|
||||||
@ -150,8 +162,6 @@ fun AppItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
val badge by viewModel.badge.collectAsStateWithLifecycle(null)
|
|
||||||
val icon by viewModel.icon.collectAsStateWithLifecycle()
|
|
||||||
ShapedLauncherIcon(
|
ShapedLauncherIcon(
|
||||||
size = 48.dp,
|
size = 48.dp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -183,7 +193,11 @@ fun AppItem(
|
|||||||
) {
|
) {
|
||||||
for ((i, not) in notifications.withIndex()) {
|
for ((i, not) in notifications.withIndex()) {
|
||||||
val icon =
|
val icon =
|
||||||
remember(not.smallIcon) { not.smallIcon?.loadDrawable(context) }
|
remember(not.smallIcon) {
|
||||||
|
not.smallIcon?.loadDrawable(
|
||||||
|
context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (not.title == null && not.text == null) continue
|
if (not.title == null && not.text == null) continue
|
||||||
|
|
||||||
@ -195,7 +209,9 @@ fun AppItem(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable {
|
.clickable {
|
||||||
try {
|
try {
|
||||||
not.contentIntent?.sendWithBackgroundPermission(context)
|
not.contentIntent?.sendWithBackgroundPermission(
|
||||||
|
context
|
||||||
|
)
|
||||||
} catch (e: PendingIntent.CanceledException) {
|
} catch (e: PendingIntent.CanceledException) {
|
||||||
CrashReporter.logException(e)
|
CrashReporter.logException(e)
|
||||||
}
|
}
|
||||||
@ -299,7 +315,11 @@ fun AppItem(
|
|||||||
.clip(MaterialTheme.shapes.small)
|
.clip(MaterialTheme.shapes.small)
|
||||||
) {
|
) {
|
||||||
for ((i, shortcut) in shortcuts.withIndex()) {
|
for ((i, shortcut) in shortcuts.withIndex()) {
|
||||||
val isPinned by remember(shortcut) { viewModel.isChildPinned(shortcut) }.collectAsState(
|
val isPinned by remember(shortcut) {
|
||||||
|
viewModel.isChildPinned(
|
||||||
|
shortcut
|
||||||
|
)
|
||||||
|
}.collectAsState(
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -404,7 +424,8 @@ fun AppItem(
|
|||||||
|
|
||||||
val sheetManager = LocalBottomSheetManager.current
|
val sheetManager = LocalBottomSheetManager.current
|
||||||
if (!app.isPrivate) {
|
if (!app.isPrivate) {
|
||||||
toolbarActions.add(DefaultToolbarAction(
|
toolbarActions.add(
|
||||||
|
DefaultToolbarAction(
|
||||||
label = stringResource(R.string.menu_customize),
|
label = stringResource(R.string.menu_customize),
|
||||||
icon = Icons.Rounded.Tune,
|
icon = Icons.Rounded.Tune,
|
||||||
action = { sheetManager.showCustomizeSearchableModal(app) }
|
action = { sheetManager.showCustomizeSearchableModal(app) }
|
||||||
@ -435,9 +456,17 @@ fun AppItem(
|
|||||||
icon = Icons.Rounded.Link,
|
icon = Icons.Rounded.Link,
|
||||||
action = {
|
action = {
|
||||||
val shareIntent = Intent(Intent.ACTION_SEND)
|
val shareIntent = Intent(Intent.ACTION_SEND)
|
||||||
shareIntent.putExtra(Intent.EXTRA_TEXT, storeDetails.url)
|
shareIntent.putExtra(
|
||||||
|
Intent.EXTRA_TEXT,
|
||||||
|
storeDetails.url
|
||||||
|
)
|
||||||
shareIntent.type = "text/plain"
|
shareIntent.type = "text/plain"
|
||||||
context.startActivity(Intent.createChooser(shareIntent, null))
|
context.startActivity(
|
||||||
|
Intent.createChooser(
|
||||||
|
shareIntent,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
DefaultToolbarAction(
|
DefaultToolbarAction(
|
||||||
@ -478,6 +507,35 @@ fun AppItem(
|
|||||||
rightActions = toolbarActions
|
rightActions = toolbarActions
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} 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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -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,16 +49,15 @@ 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",
|
|
||||||
items = apps,
|
|
||||||
before = if (profiles.size > 1) {
|
|
||||||
{
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = if (reverse) Arrangement.BottomReversed else Arrangement.Top,
|
verticalArrangement = if (reverse) Arrangement.BottomReversed else Arrangement.Top,
|
||||||
) {
|
) {
|
||||||
@ -102,7 +103,10 @@ fun LazyListScope.AppResults(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!showList || isProfileLocked) {
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
|
}
|
||||||
|
|
||||||
val profileType = profiles[selectedProfileIndex].type
|
val profileType = profiles[selectedProfileIndex].type
|
||||||
if (profileType != Profile.Type.Personal) {
|
if (profileType != Profile.Type.Personal) {
|
||||||
@ -111,8 +115,15 @@ fun LazyListScope.AppResults(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(12.dp)
|
.padding(12.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, MaterialTheme.shapes.small)
|
.border(
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainer, MaterialTheme.shapes.small)
|
1.dp,
|
||||||
|
MaterialTheme.colorScheme.outlineVariant,
|
||||||
|
MaterialTheme.shapes.small
|
||||||
|
)
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surfaceContainer,
|
||||||
|
MaterialTheme.shapes.small
|
||||||
|
)
|
||||||
.padding(vertical = 64.dp),
|
.padding(vertical = 64.dp),
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
@ -190,7 +201,30 @@ fun LazyListScope.AppResults(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else null,
|
} else null
|
||||||
|
if (showList) {
|
||||||
|
ListResults(
|
||||||
|
key = "apps",
|
||||||
|
items = apps,
|
||||||
|
before = before?.let { { it() } },
|
||||||
|
selectedIndex = selectedIndex,
|
||||||
|
itemContent = { app, showDetails, index ->
|
||||||
|
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 = {
|
itemContent = {
|
||||||
GridItem(
|
GridItem(
|
||||||
item = it,
|
item = it,
|
||||||
@ -202,3 +236,5 @@ fun LazyListScope.AppResults(
|
|||||||
columns = columns,
|
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,12 +198,12 @@ 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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ItemPopup(origin: IntRect, searchable: Searchable, onDismissRequest: () -> Unit) {
|
fun ItemPopup(origin: IntRect, searchable: Searchable, onDismissRequest: () -> Unit) {
|
||||||
@ -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