parent
7a73b72314
commit
b068e4d6fd
@ -43,6 +43,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation(libs.commons.text)
|
implementation(libs.commons.text)
|
||||||
|
|
||||||
|
implementation(project(":applications"))
|
||||||
implementation(project(":search"))
|
implementation(project(":search"))
|
||||||
implementation(project(":permissions"))
|
implementation(project(":permissions"))
|
||||||
implementation(project(":base"))
|
implementation(project(":base"))
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import de.mm20.launcher2.permissions.PermissionGroup
|
|||||||
import de.mm20.launcher2.permissions.PermissionsManager
|
import de.mm20.launcher2.permissions.PermissionsManager
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
import de.mm20.launcher2.search.data.AppShortcut
|
import de.mm20.launcher2.search.data.AppShortcut
|
||||||
|
import de.mm20.launcher2.search.data.LauncherApp
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -31,6 +32,8 @@ interface AppShortcutRepository {
|
|||||||
count: Int = 5
|
count: Int = 5
|
||||||
): List<AppShortcut>
|
): List<AppShortcut>
|
||||||
|
|
||||||
|
suspend fun getShortcutsConfigActivities(): List<LauncherApp>
|
||||||
|
|
||||||
fun search(query: String): Flow<List<AppShortcut>>
|
fun search(query: String): Flow<List<AppShortcut>>
|
||||||
|
|
||||||
fun removePinnedShortcut(shortcut: AppShortcut)
|
fun removePinnedShortcut(shortcut: AppShortcut)
|
||||||
@ -204,6 +207,24 @@ internal class AppShortcutRepositoryImpl(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getShortcutsConfigActivities(): List<LauncherApp> {
|
||||||
|
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
|
||||||
|
if (!launcherApps.hasShortcutHostPermission()) return emptyList()
|
||||||
|
val results = mutableListOf<LauncherApp>()
|
||||||
|
val profiles = launcherApps.profiles
|
||||||
|
for (profile in profiles) {
|
||||||
|
val activities = launcherApps.getShortcutConfigActivityList(null, profile)
|
||||||
|
results.addAll(
|
||||||
|
activities.map {
|
||||||
|
LauncherApp(
|
||||||
|
context, it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return results.sorted()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun matches(label: String, query: String): Boolean {
|
private fun matches(label: String, query: String): Boolean {
|
||||||
val labelLatin = label.normalize()
|
val labelLatin = label.normalize()
|
||||||
|
|||||||
@ -389,6 +389,8 @@
|
|||||||
<string name="missing_permission_contact_search">Grant contact permission to search your contact.</string>
|
<string name="missing_permission_contact_search">Grant contact permission to search your contact.</string>
|
||||||
<!-- Missing permission app shortcuts permission, used in app shortcut search results. %1$s: app name -->
|
<!-- Missing permission app shortcuts permission, used in app shortcut search results. %1$s: app name -->
|
||||||
<string name="missing_permission_appshortcuts_search">Set %1$s as default home app to search app shortcuts.</string>
|
<string name="missing_permission_appshortcuts_search">Set %1$s as default home app to search app shortcuts.</string>
|
||||||
|
<!-- Missing permission app shortcuts permission, used when creating a shortcut in the edit favorites sheet -->
|
||||||
|
<string name="missing_permission_appshortcuts_create">Set %1$s as default home app to create shortcuts.</string>
|
||||||
<!-- Grant a permission, shown in permission banners -->
|
<!-- Grant a permission, shown in permission banners -->
|
||||||
<string name="grant_permission">Grant</string>
|
<string name="grant_permission">Grant</string>
|
||||||
<!-- Appearance preference title -->
|
<!-- Appearance preference title -->
|
||||||
@ -648,4 +650,5 @@
|
|||||||
<string name="apps_profile_work">Work</string>
|
<string name="apps_profile_work">Work</string>
|
||||||
|
|
||||||
<string name="favorites">Favorites</string>
|
<string name="favorites">Favorites</string>
|
||||||
|
<string name="create_app_shortcut">Create shortcut</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -1,13 +1,20 @@
|
|||||||
package de.mm20.launcher2.ui.launcher.modals
|
package de.mm20.launcher2.ui.launcher.modals
|
||||||
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
import android.app.Activity
|
||||||
import androidx.compose.foundation.border
|
import android.content.Context
|
||||||
|
import android.content.pm.LauncherApps
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.IntentSenderRequest
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material.icons.rounded.Add
|
||||||
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@ -16,6 +23,8 @@ import androidx.compose.ui.draw.drawBehind
|
|||||||
import androidx.compose.ui.graphics.PathEffect
|
import androidx.compose.ui.graphics.PathEffect
|
||||||
import androidx.compose.ui.graphics.drawOutline
|
import androidx.compose.ui.graphics.drawOutline
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
@ -27,6 +36,7 @@ import de.mm20.launcher2.icons.LauncherIcon
|
|||||||
import de.mm20.launcher2.search.data.Searchable
|
import de.mm20.launcher2.search.data.Searchable
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||||
|
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||||
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
import de.mm20.launcher2.ui.component.ShapedLauncherIcon
|
||||||
import de.mm20.launcher2.ui.ktx.toPixels
|
import de.mm20.launcher2.ui.ktx.toPixels
|
||||||
import de.mm20.launcher2.ui.launcher.helper.DraggableItem
|
import de.mm20.launcher2.ui.launcher.helper.DraggableItem
|
||||||
@ -45,9 +55,61 @@ fun EditFavoritesSheet(
|
|||||||
viewModel.reload()
|
viewModel.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
val items by viewModel.gridItems.observeAsState(emptyList())
|
|
||||||
val loading by viewModel.loading.observeAsState(true)
|
val loading by viewModel.loading.observeAsState(true)
|
||||||
|
val createShortcutTarget by viewModel.createShortcutTarget.observeAsState(null)
|
||||||
|
|
||||||
|
BottomSheetDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
if (createShortcutTarget == null) {
|
||||||
|
stringResource(id = R.string.menu_item_edit_favs)
|
||||||
|
} else {
|
||||||
|
stringResource(id = R.string.create_app_shortcut)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
swipeToDismiss = {
|
||||||
|
createShortcutTarget == null
|
||||||
|
},
|
||||||
|
dismissOnBackPress = {
|
||||||
|
createShortcutTarget == null
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
if (createShortcutTarget != null) {
|
||||||
|
OutlinedButton(onClick = { viewModel.cancelPickShortcut() }) {
|
||||||
|
Text(stringResource(id = android.R.string.cancel))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
OutlinedButton(onClick = onDismiss) {
|
||||||
|
Text(stringResource(id = R.string.close))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
if (loading) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(1f)
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (createShortcutTarget != null) {
|
||||||
|
ShortcutPicker(viewModel)
|
||||||
|
} else {
|
||||||
|
ReorderFavoritesGrid(viewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReorderFavoritesGrid(viewModel: EditFavoritesSheetVM) {
|
||||||
|
val items by viewModel.gridItems.observeAsState(emptyList())
|
||||||
val columns = LocalGridColumns.current
|
val columns = LocalGridColumns.current
|
||||||
|
|
||||||
val state = rememberLazyDragAndDropGridState(
|
val state = rememberLazyDragAndDropGridState(
|
||||||
@ -60,125 +122,144 @@ fun EditFavoritesSheet(
|
|||||||
|
|
||||||
val iconSize = 48.dp.toPixels()
|
val iconSize = 48.dp.toPixels()
|
||||||
|
|
||||||
BottomSheetDialog(onDismissRequest = onDismiss, title = {
|
LazyVerticalDragAndDropGrid(
|
||||||
Text(stringResource(id = R.string.menu_item_edit_favs))
|
state = state,
|
||||||
}) {
|
columns = GridCells.Fixed(columns),
|
||||||
if (loading) {
|
|
||||||
Box(modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.aspectRatio(1f)) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(48.dp)
|
|
||||||
.align(Alignment.Center)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LazyVerticalDragAndDropGrid(
|
|
||||||
state = state,
|
|
||||||
columns = GridCells.Fixed(columns),
|
|
||||||
|
|
||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items.size,
|
items.size,
|
||||||
key = { i ->
|
key = { i ->
|
||||||
val it = items[i]
|
val it = items[i]
|
||||||
if (it is FavoritesSheetGridItem.Favorite) it.item.key else i
|
if (it is FavoritesSheetGridItem.Favorite) it.item.key else i
|
||||||
},
|
},
|
||||||
span = { i ->
|
span = { i ->
|
||||||
val it = items[i]
|
val it = items[i]
|
||||||
when (it) {
|
when (it) {
|
||||||
is FavoritesSheetGridItem.Favorite -> GridItemSpan(1)
|
is FavoritesSheetGridItem.Favorite -> GridItemSpan(1)
|
||||||
is FavoritesSheetGridItem.Divider -> GridItemSpan(columns)
|
is FavoritesSheetGridItem.Divider -> GridItemSpan(columns)
|
||||||
is FavoritesSheetGridItem.EmptySection -> GridItemSpan(columns)
|
is FavoritesSheetGridItem.EmptySection -> GridItemSpan(columns)
|
||||||
is FavoritesSheetGridItem.Spacer -> GridItemSpan(it.span)
|
is FavoritesSheetGridItem.Spacer -> GridItemSpan(it.span)
|
||||||
is FavoritesSheetGridItem.Tags -> GridItemSpan(columns)
|
is FavoritesSheetGridItem.Tags -> GridItemSpan(columns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { i ->
|
||||||
|
when (val it = items[i]) {
|
||||||
|
is FavoritesSheetGridItem.Favorite -> {
|
||||||
|
val icon by remember(it.item.key) {
|
||||||
|
viewModel.getIcon(
|
||||||
|
it.item,
|
||||||
|
iconSize.roundToInt()
|
||||||
|
)
|
||||||
|
}.collectAsState(null)
|
||||||
|
val badge by remember(it.item.key) {
|
||||||
|
viewModel.getBadge(
|
||||||
|
it.item,
|
||||||
|
)
|
||||||
|
}.collectAsState(null)
|
||||||
|
DraggableItem(state = state, key = it.item.key) { dragged ->
|
||||||
|
GridItem(
|
||||||
|
label = it.item.labelOverride ?: it.item.label,
|
||||||
|
icon = icon,
|
||||||
|
badge = badge
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { i ->
|
is FavoritesSheetGridItem.Divider -> {
|
||||||
when (val it = items[i]) {
|
val title = when (it.section) {
|
||||||
is FavoritesSheetGridItem.Favorite -> {
|
FavoritesSheetSection.ManuallySorted -> R.string.edit_favorites_dialog_pinned_sorted
|
||||||
val icon by remember(it.item.key) {
|
FavoritesSheetSection.AutomaticallySorted -> R.string.edit_favorites_dialog_pinned_unsorted
|
||||||
viewModel.getIcon(
|
FavoritesSheetSection.FrequentlyUsed -> R.string.edit_favorites_dialog_unpinned
|
||||||
it.item,
|
|
||||||
iconSize.roundToInt()
|
|
||||||
)
|
|
||||||
}.collectAsState(null)
|
|
||||||
val badge by remember(it.item.key) {
|
|
||||||
viewModel.getBadge(
|
|
||||||
it.item,
|
|
||||||
)
|
|
||||||
}.collectAsState(null)
|
|
||||||
DraggableItem(state = state, key = it.item.key) { dragged ->
|
|
||||||
GridItem(
|
|
||||||
label = it.item.labelOverride ?: it.item.label,
|
|
||||||
icon = icon,
|
|
||||||
badge = badge
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is FavoritesSheetGridItem.Divider -> {
|
Row(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(top = 16.dp, bottom = 8.dp),
|
modifier = Modifier
|
||||||
text = stringResource(id = it.titleRes),
|
.weight(1f)
|
||||||
|
.padding(end = 16.dp),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
text = stringResource(id = title),
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
color = MaterialTheme.colorScheme.secondary
|
color = MaterialTheme.colorScheme.secondary
|
||||||
)
|
)
|
||||||
|
if (it.section == FavoritesSheetSection.FrequentlyUsed) {
|
||||||
|
/*FilledTonalIconToggleButton(
|
||||||
|
modifier = Modifier.offset(x = 4.dp),
|
||||||
|
checked = false,
|
||||||
|
onCheckedChange = {}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Settings,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}*/
|
||||||
|
} else {
|
||||||
|
FilledTonalIconButton(
|
||||||
|
modifier = Modifier.offset(x = 4.dp),
|
||||||
|
onClick = {
|
||||||
|
viewModel.pickShortcut(it.section)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Add,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is FavoritesSheetGridItem.EmptySection -> {
|
}
|
||||||
val shape = MaterialTheme.shapes.medium
|
is FavoritesSheetGridItem.EmptySection -> {
|
||||||
val color = MaterialTheme.colorScheme.outline
|
val shape = MaterialTheme.shapes.medium
|
||||||
Box(
|
val color = MaterialTheme.colorScheme.outline
|
||||||
modifier = Modifier
|
Box(
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.padding(vertical = 8.dp)
|
.fillMaxWidth()
|
||||||
.drawBehind {
|
.padding(vertical = 8.dp)
|
||||||
drawOutline(
|
.drawBehind {
|
||||||
outline = shape.createOutline(
|
drawOutline(
|
||||||
size,
|
outline = shape.createOutline(
|
||||||
layoutDirection,
|
size,
|
||||||
Density(density, fontScale)
|
layoutDirection,
|
||||||
),
|
Density(density, fontScale)
|
||||||
color = color,
|
),
|
||||||
style = Stroke(
|
color = color,
|
||||||
2.dp.toPx(),
|
style = Stroke(
|
||||||
pathEffect = PathEffect.dashPathEffect(
|
2.dp.toPx(),
|
||||||
intervals = floatArrayOf(
|
pathEffect = PathEffect.dashPathEffect(
|
||||||
4.dp.toPx(),
|
intervals = floatArrayOf(
|
||||||
4.dp.toPx(),
|
4.dp.toPx(),
|
||||||
)
|
4.dp.toPx(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
) {
|
}
|
||||||
Text(
|
) {
|
||||||
modifier = Modifier
|
Text(
|
||||||
.align(Alignment.Center)
|
|
||||||
.padding(
|
|
||||||
horizontal = 16.dp,
|
|
||||||
vertical = 24.dp,
|
|
||||||
),
|
|
||||||
text = stringResource(R.string.edit_favorites_dialog_empty_section),
|
|
||||||
style = MaterialTheme.typography.labelSmall,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
color = MaterialTheme.colorScheme.outline
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is FavoritesSheetGridItem.Spacer -> {
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.align(Alignment.Center)
|
||||||
.height(48.dp)
|
.padding(
|
||||||
|
horizontal = 16.dp,
|
||||||
|
vertical = 24.dp,
|
||||||
|
),
|
||||||
|
text = stringResource(R.string.edit_favorites_dialog_empty_section),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colorScheme.outline
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is FavoritesSheetGridItem.Tags -> {}
|
|
||||||
}
|
}
|
||||||
|
is FavoritesSheetGridItem.Spacer -> {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(48.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is FavoritesSheetGridItem.Tags -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,10 +290,81 @@ fun GridItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ShortcutPicker(viewModel: EditFavoritesSheetVM) {
|
||||||
|
|
||||||
|
val hasShortcutPermission by remember { viewModel.hasShortcutPermission }.collectAsState(null)
|
||||||
|
|
||||||
|
val shortcutActivities by remember(hasShortcutPermission) { viewModel.getShortcutActivities() }.collectAsState(
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
val activityLauncher =
|
||||||
|
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartIntentSenderForResult()) {
|
||||||
|
if (it.resultCode != Activity.RESULT_OK) {
|
||||||
|
viewModel.cancelPickShortcut()
|
||||||
|
}
|
||||||
|
viewModel.createShortcut(context, it.data)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val iconSize = 48.dp.toPixels().roundToInt()
|
||||||
|
val activity = LocalLifecycleOwner.current as AppCompatActivity
|
||||||
|
LazyColumn {
|
||||||
|
if (hasShortcutPermission == false) {
|
||||||
|
item {
|
||||||
|
MissingPermissionBanner(
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp),
|
||||||
|
text = stringResource(
|
||||||
|
R.string.missing_permission_appshortcuts_create,
|
||||||
|
stringResource(R.string.app_name)
|
||||||
|
),
|
||||||
|
onClick = { viewModel.requestShortcutPermission(activity) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items(shortcutActivities) {
|
||||||
|
val icon by remember(it.key) { viewModel.getIcon(it, iconSize) }.collectAsState(null)
|
||||||
|
val badge by remember(it.key) { viewModel.getBadge(it) }.collectAsState(null)
|
||||||
|
OutlinedCard(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 4.dp),
|
||||||
|
onClick = {
|
||||||
|
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
|
||||||
|
val sender = launcherApps.getShortcutConfigActivityIntent(it.launcherActivityInfo) ?: return@OutlinedCard
|
||||||
|
activityLauncher.launch(IntentSenderRequest.Builder(sender).build(), null)
|
||||||
|
}) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
ShapedLauncherIcon(
|
||||||
|
size = 48.dp,
|
||||||
|
icon = { icon },
|
||||||
|
badge = { badge },
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = it.labelOverride ?: it.label,
|
||||||
|
modifier = Modifier.padding(start = 16.dp),
|
||||||
|
style = MaterialTheme.typography.titleSmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sealed interface FavoritesSheetGridItem {
|
sealed interface FavoritesSheetGridItem {
|
||||||
class Favorite(val item: Searchable) : FavoritesSheetGridItem
|
class Favorite(val item: Searchable) : FavoritesSheetGridItem
|
||||||
class Divider(val titleRes: Int) : FavoritesSheetGridItem
|
class Divider(val section: FavoritesSheetSection) : FavoritesSheetGridItem
|
||||||
class Spacer(val span: Int = 1) : FavoritesSheetGridItem
|
class Spacer(val span: Int = 1) : FavoritesSheetGridItem
|
||||||
class EmptySection() : FavoritesSheetGridItem
|
object EmptySection : FavoritesSheetGridItem
|
||||||
class Tags() : FavoritesSheetGridItem
|
class Tags() : FavoritesSheetGridItem
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class FavoritesSheetSection {
|
||||||
|
ManuallySorted,
|
||||||
|
AutomaticallySorted,
|
||||||
|
FrequentlyUsed
|
||||||
}
|
}
|
||||||
@ -1,18 +1,29 @@
|
|||||||
package de.mm20.launcher2.ui.launcher.modals
|
package de.mm20.launcher2.ui.launcher.modals
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.LauncherApps
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
|
import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import de.mm20.launcher2.appshortcuts.AppShortcutRepository
|
||||||
import de.mm20.launcher2.badges.Badge
|
import de.mm20.launcher2.badges.Badge
|
||||||
import de.mm20.launcher2.badges.BadgeRepository
|
import de.mm20.launcher2.badges.BadgeRepository
|
||||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||||
import de.mm20.launcher2.icons.IconRepository
|
import de.mm20.launcher2.icons.IconRepository
|
||||||
import de.mm20.launcher2.icons.LauncherIcon
|
import de.mm20.launcher2.icons.LauncherIcon
|
||||||
|
import de.mm20.launcher2.permissions.PermissionGroup
|
||||||
|
import de.mm20.launcher2.permissions.PermissionsManager
|
||||||
|
import de.mm20.launcher2.search.data.AppShortcut
|
||||||
|
import de.mm20.launcher2.search.data.LauncherApp
|
||||||
import de.mm20.launcher2.search.data.Searchable
|
import de.mm20.launcher2.search.data.Searchable
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
@ -20,13 +31,17 @@ import org.koin.core.component.inject
|
|||||||
class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
||||||
|
|
||||||
private val repository: FavoritesRepository by inject()
|
private val repository: FavoritesRepository by inject()
|
||||||
|
private val shortcutRepository: AppShortcutRepository by inject()
|
||||||
private val iconRepository: IconRepository by inject()
|
private val iconRepository: IconRepository by inject()
|
||||||
private val badgeRepository: BadgeRepository by inject()
|
private val badgeRepository: BadgeRepository by inject()
|
||||||
|
private val permissionsManager: PermissionsManager by inject()
|
||||||
|
|
||||||
val gridItems = MutableLiveData<List<FavoritesSheetGridItem>>(emptyList())
|
val gridItems = MutableLiveData<List<FavoritesSheetGridItem>>(emptyList())
|
||||||
|
|
||||||
val loading = MutableLiveData(false)
|
val loading = MutableLiveData(false)
|
||||||
|
|
||||||
|
val createShortcutTarget = MutableLiveData<FavoritesSheetSection?>(null)
|
||||||
|
|
||||||
private var manuallySorted: MutableList<Searchable> = mutableListOf()
|
private var manuallySorted: MutableList<Searchable> = mutableListOf()
|
||||||
private var automaticallySorted: MutableList<Searchable> = mutableListOf()
|
private var automaticallySorted: MutableList<Searchable> = mutableListOf()
|
||||||
private var frequentlyUsed: MutableList<Searchable> = mutableListOf()
|
private var frequentlyUsed: MutableList<Searchable> = mutableListOf()
|
||||||
@ -52,25 +67,25 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
|||||||
|
|
||||||
items.add(FavoritesSheetGridItem.Tags())
|
items.add(FavoritesSheetGridItem.Tags())
|
||||||
|
|
||||||
items.add(FavoritesSheetGridItem.Divider(R.string.edit_favorites_dialog_pinned_sorted))
|
items.add(FavoritesSheetGridItem.Divider(FavoritesSheetSection.ManuallySorted))
|
||||||
if (manuallySorted.isEmpty()) {
|
if (manuallySorted.isEmpty()) {
|
||||||
items.add(FavoritesSheetGridItem.EmptySection())
|
items.add(FavoritesSheetGridItem.EmptySection)
|
||||||
} else {
|
} else {
|
||||||
items.addAll(manuallySorted.map { FavoritesSheetGridItem.Favorite(it) })
|
items.addAll(manuallySorted.map { FavoritesSheetGridItem.Favorite(it) })
|
||||||
items.add(FavoritesSheetGridItem.Spacer())
|
items.add(FavoritesSheetGridItem.Spacer())
|
||||||
}
|
}
|
||||||
|
|
||||||
items.add(FavoritesSheetGridItem.Divider(R.string.edit_favorites_dialog_pinned_unsorted))
|
items.add(FavoritesSheetGridItem.Divider(FavoritesSheetSection.AutomaticallySorted))
|
||||||
if (automaticallySorted.isEmpty()) {
|
if (automaticallySorted.isEmpty()) {
|
||||||
items.add(FavoritesSheetGridItem.EmptySection())
|
items.add(FavoritesSheetGridItem.EmptySection)
|
||||||
} else {
|
} else {
|
||||||
items.addAll(automaticallySorted.map { FavoritesSheetGridItem.Favorite(it) })
|
items.addAll(automaticallySorted.map { FavoritesSheetGridItem.Favorite(it) })
|
||||||
items.add(FavoritesSheetGridItem.Spacer())
|
items.add(FavoritesSheetGridItem.Spacer())
|
||||||
}
|
}
|
||||||
|
|
||||||
items.add(FavoritesSheetGridItem.Divider(R.string.edit_favorites_dialog_unpinned))
|
items.add(FavoritesSheetGridItem.Divider(FavoritesSheetSection.FrequentlyUsed))
|
||||||
if (frequentlyUsed.isEmpty()) {
|
if (frequentlyUsed.isEmpty()) {
|
||||||
items.add(FavoritesSheetGridItem.EmptySection())
|
items.add(FavoritesSheetGridItem.EmptySection)
|
||||||
} else {
|
} else {
|
||||||
items.addAll(frequentlyUsed.map { FavoritesSheetGridItem.Favorite(it) })
|
items.addAll(frequentlyUsed.map { FavoritesSheetGridItem.Favorite(it) })
|
||||||
items.add(FavoritesSheetGridItem.Spacer())
|
items.add(FavoritesSheetGridItem.Spacer())
|
||||||
@ -123,6 +138,11 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
save()
|
||||||
|
buildItemList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun save() {
|
||||||
repository.updateFavorites(
|
repository.updateFavorites(
|
||||||
buildList {
|
buildList {
|
||||||
addAll(manuallySorted)
|
addAll(manuallySorted)
|
||||||
@ -131,7 +151,6 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
|||||||
addAll(automaticallySorted)
|
addAll(automaticallySorted)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
buildItemList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getIcon(searchable: Searchable, size: Int): Flow<LauncherIcon?> {
|
fun getIcon(searchable: Searchable, size: Int): Flow<LauncherIcon?> {
|
||||||
@ -142,4 +161,43 @@ class EditFavoritesSheetVM : ViewModel(), KoinComponent {
|
|||||||
return badgeRepository.getBadge(searchable)
|
return badgeRepository.getBadge(searchable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun pickShortcut(section: FavoritesSheetSection) {
|
||||||
|
createShortcutTarget.value = section
|
||||||
|
}
|
||||||
|
fun cancelPickShortcut() {
|
||||||
|
createShortcutTarget.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getShortcutActivities() = flow {
|
||||||
|
emit(shortcutRepository.getShortcutsConfigActivities())
|
||||||
|
}
|
||||||
|
|
||||||
|
val hasShortcutPermission = permissionsManager.hasPermission(PermissionGroup.AppShortcuts)
|
||||||
|
|
||||||
|
fun requestShortcutPermission(context: AppCompatActivity) {
|
||||||
|
permissionsManager.requestPermission(context, PermissionGroup.AppShortcuts)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createShortcut(context: Context, data: Intent?) {
|
||||||
|
data ?: return cancelPickShortcut()
|
||||||
|
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
|
||||||
|
val pinRequest = launcherApps.getPinItemRequest(data) ?: return cancelPickShortcut()
|
||||||
|
val shortcutInfo = pinRequest.shortcutInfo ?: return cancelPickShortcut()
|
||||||
|
pinRequest.accept()
|
||||||
|
val shortcut = AppShortcut(
|
||||||
|
context,
|
||||||
|
shortcutInfo,
|
||||||
|
context.packageManager.getApplicationInfo(shortcutInfo.`package`, 0)
|
||||||
|
.loadLabel(context.packageManager).toString()
|
||||||
|
)
|
||||||
|
if (createShortcutTarget.value == FavoritesSheetSection.ManuallySorted) {
|
||||||
|
manuallySorted.add(shortcut)
|
||||||
|
} else {
|
||||||
|
automaticallySorted.add(shortcut)
|
||||||
|
}
|
||||||
|
save()
|
||||||
|
buildItemList()
|
||||||
|
createShortcutTarget.value = null
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user