refactor media integration settings to use lazy items
This commit is contained in:
@@ -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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user