refactor media integration settings to use lazy items

This commit is contained in:
MM20
2026-08-23 18:28:32 +02:00
parent 85a68a7e7e
commit 728795d7bc
2 changed files with 65 additions and 32 deletions

View File

@@ -2,8 +2,11 @@ package de.mm20.launcher2.ui.settings.media
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@@ -11,12 +14,13 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
@@ -29,19 +33,24 @@ import de.mm20.launcher2.ui.component.preferences.CheckboxPreference
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.ktx.toPixels
import kotlinx.serialization.Serializable
@Serializable
data object MediaIntegrationSettingsRoute: NavKey
data object MediaIntegrationSettingsRoute : NavKey
@Composable
fun MediaIntegrationSettingsScreen() {
val context = LocalContext.current
val viewModel: MediaIntegrationSettingsScreenVM = viewModel()
val hasPermission by viewModel.hasPermission.collectAsStateWithLifecycle(null)
val apps by viewModel.appList
val loading by viewModel.loading
val density = LocalDensity.current
val xsShape = MaterialTheme.shapes.extraSmall
val mdShape = MaterialTheme.shapes.medium
val lifecycleOwner = LocalLifecycleOwner.current
@@ -53,7 +62,8 @@ fun MediaIntegrationSettingsScreen() {
PreferenceScreen(
stringResource(R.string.preference_media_integration),
helpUrl = "https://kvaesitso.mm20.de/docs/user-guide/integrations/mediacontrol"
helpUrl = "https://kvaesitso.mm20.de/docs/user-guide/integrations/mediacontrol",
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
if (loading) {
item {
@@ -70,6 +80,7 @@ fun MediaIntegrationSettingsScreen() {
viewModel.requestNotificationPermission(context as AppCompatActivity)
},
modifier = Modifier
.padding(bottom = 10.dp)
.background(
MaterialTheme.colorScheme.surface,
MaterialTheme.shapes.medium
@@ -78,37 +89,55 @@ fun MediaIntegrationSettingsScreen() {
)
}
}
item {
PreferenceCategory(
stringResource(R.string.preference_category_media_apps)
itemsIndexed(apps) { index, app ->
Box(
modifier = Modifier.clip(
when {
apps.size == 1 -> mdShape
index == 0 -> mdShape.copy(
bottomEnd = xsShape.bottomEnd,
bottomStart = xsShape.bottomStart
)
index == apps.size - 1 -> mdShape.copy(
topEnd = xsShape.topEnd,
topStart = xsShape.topStart
)
else -> xsShape
}
)
) {
val apps by viewModel.appList
for (app in apps) {
val icon by app.icon.collectAsState(null)
CheckboxPreference(
icon = {
ShapedLauncherIcon(size = 32.dp, icon = { icon })
},
title = app.label,
value = app.isChecked,
onValueChanged = {
viewModel.onAppChecked(app, it)
}
)
}
CheckboxPreference(
icon = {
val size = 32.dp
val icon by viewModel.getIcon(app.app, size.toPixels().toInt())
.collectAsStateWithLifecycle(null)
ShapedLauncherIcon(size = 32.dp, icon = { icon })
},
title = app.label,
value = app.isChecked,
onValueChanged = {
viewModel.onAppChecked(app, it)
}
)
}
}
if (BuildConfig.DEBUG) {
item {
PreferenceCategory(stringResource(R.string.preference_category_debug)) {
Preference(
title = "Reset widget",
summary = "Clear all music data",
onClick = {
viewModel.resetWidget()
}
)
Box(
modifier = Modifier.padding(top = 10.dp)
) {
PreferenceCategory(stringResource(R.string.preference_category_debug)) {
Preference(
title = "Reset widget",
summary = "Clear all music data",
onClick = {
viewModel.resetWidget()
}
)
}
}
}
}

View File

@@ -12,6 +12,7 @@ import de.mm20.launcher2.music.MusicService
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.media.MediaSettings
import de.mm20.launcher2.search.SavableSearchable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@@ -60,13 +61,12 @@ class MediaIntegrationSettingsScreenVM : ViewModel(), KoinComponent {
appList.value = allApps.map {
AppListItem(
label = it.label,
app = it,
packageName = it.componentName.packageName,
isMusicApp = musicApps.contains(it.componentName.packageName),
isChecked = allowList.contains(it.componentName.packageName) || (!denyList.contains(it.componentName.packageName) && musicApps.contains(
it.componentName.packageName
)),
icon = iconService.getIcon(it, (32 * density).roundToInt())
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(10000))
))
)
}.sorted()
loading.value = false
@@ -96,6 +96,10 @@ class MediaIntegrationSettingsScreenVM : ViewModel(), KoinComponent {
mediaSettings.setLists(allowList.toSet(), denyList.toSet())
}
fun getIcon(app: SavableSearchable, size: Int): Flow<LauncherIcon?> {
return iconService.getIcon(app, size)
}
}
data class AppListItem(
@@ -103,7 +107,7 @@ data class AppListItem(
val packageName: String,
val isMusicApp: Boolean,
val isChecked: Boolean,
val icon: Flow<LauncherIcon?>,
val app: SavableSearchable,
): Comparable<AppListItem> {
override fun compareTo(other: AppListItem): Int {
val label1 = label