diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/media/MediaIntegrationSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/media/MediaIntegrationSettingsScreen.kt index 8461c996e..8cbee5428 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/media/MediaIntegrationSettingsScreen.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/media/MediaIntegrationSettingsScreen.kt @@ -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() + } + ) + } } } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/media/MediaIntegrationSettingsScreenVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/media/MediaIntegrationSettingsScreenVM.kt index cc82a22ba..76a3c1154 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/media/MediaIntegrationSettingsScreenVM.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/media/MediaIntegrationSettingsScreenVM.kt @@ -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 { + 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, + val app: SavableSearchable, ): Comparable { override fun compareTo(other: AppListItem): Int { val label1 = label