From 6c167b6f6fecd222ebfb1727b915fb3c73f92994 Mon Sep 17 00:00:00 2001 From: IllusionMan1212 Date: Sun, 14 Jul 2024 06:53:28 +0200 Subject: [PATCH] whitelist search --- app/build.gradle.kts | 2 +- .../lyricsgrabbr/ui/screens/Home.kt | 1 - .../lyricsgrabbr/ui/screens/Whitelist.kt | 138 +++++++++++++----- .../viewmodels/SettingsViewModel.kt | 58 +++++++- app/src/main/res/values-ar/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 6 files changed, 162 insertions(+), 41 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a547982..f615b33 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -118,7 +118,7 @@ dependencies { implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") - implementation("androidx.compose.material3:material3:1.2.1") + implementation("androidx.compose.material3:material3:1.3.0-beta04") implementation("com.google.android.material:material:1.12.0") implementation("androidx.compose.material:material-icons-extended:1.6.8") implementation("androidx.core:core-splashscreen:1.0.1") diff --git a/app/src/main/java/com/illusionman1212/lyricsgrabbr/ui/screens/Home.kt b/app/src/main/java/com/illusionman1212/lyricsgrabbr/ui/screens/Home.kt index fb63f93..9b8fb92 100644 --- a/app/src/main/java/com/illusionman1212/lyricsgrabbr/ui/screens/Home.kt +++ b/app/src/main/java/com/illusionman1212/lyricsgrabbr/ui/screens/Home.kt @@ -433,7 +433,6 @@ fun LongClickBottomSheet( dragHandle = {}, onDismissRequest = onDismiss, sheetState = sheetState, - windowInsets = WindowInsets(top = 0.dp), ) { Column { if (artistUri != Uri.EMPTY) { diff --git a/app/src/main/java/com/illusionman1212/lyricsgrabbr/ui/screens/Whitelist.kt b/app/src/main/java/com/illusionman1212/lyricsgrabbr/ui/screens/Whitelist.kt index 9be68c0..482d820 100644 --- a/app/src/main/java/com/illusionman1212/lyricsgrabbr/ui/screens/Whitelist.kt +++ b/app/src/main/java/com/illusionman1212/lyricsgrabbr/ui/screens/Whitelist.kt @@ -4,18 +4,27 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Clear import androidx.compose.material3.Checkbox import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TopAppBar +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -23,6 +32,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp @@ -30,15 +41,42 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil.compose.AsyncImage import com.illusionman1212.lyricsgrabbr.R import com.illusionman1212.lyricsgrabbr.ui.components.LGIconButton +import com.illusionman1212.lyricsgrabbr.ui.theme.Typography +import com.illusionman1212.lyricsgrabbr.viewmodels.AppInfo import com.illusionman1212.lyricsgrabbr.viewmodels.SettingsViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun WhitelistPage(goBack: () -> Unit, viewModel: SettingsViewModel) { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + Scaffold(topBar = { TopAppBar( title = { - Text(text = stringResource(id = R.string.whitelist)) + TextField( + value = uiState.whitelistSearchQuery, + onValueChange = { + viewModel.filterPackages(it) + }, + modifier = Modifier.fillMaxWidth(), + placeholder = { + Text(text = stringResource(id = R.string.search), style = Typography.titleLarge) + }, + trailingIcon = { + LGIconButton(tooltip = stringResource(id = R.string.clear), onClick = { viewModel.clearWhitelistSearch() }) { + Icon(Icons.Default.Clear, contentDescription = stringResource(id = R.string.clear)) + } + }, + singleLine = true, + colors = TextFieldDefaults.colors( + focusedContainerColor = Color.Transparent, + unfocusedContainerColor = Color.Transparent, + disabledContainerColor = Color.Transparent, + cursorColor = MaterialTheme.colorScheme.primary, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + ), + ) }, navigationIcon = { LGIconButton(tooltip = stringResource(id = R.string.go_back), onClick = goBack) { @@ -49,46 +87,76 @@ fun WhitelistPage(goBack: () -> Unit, viewModel: SettingsViewModel) { } ) }) { padding -> - val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val context = LocalContext.current - Box(Modifier.padding(padding)) { - LazyColumn { - for ((packageName, appInfo) in uiState.applications) { - item { - var isWhitelisted by remember { mutableStateOf(appInfo.isWhitelisted) } + val refreshState = rememberPullToRefreshState() + var isRefreshing by remember { mutableStateOf(false) } - Box( - Modifier - .fillMaxWidth() - .clickable( - role = Role.Checkbox, - onClick = { - if (isWhitelisted) { - viewModel.removeFromWhitelist(packageName) - } else { - viewModel.addToWhitelist(packageName) - } - isWhitelisted = !isWhitelisted - } - )) { - Row( - Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 5.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - AsyncImage(modifier = Modifier.size(32.dp), model = appInfo.icon, contentDescription = null) - Text(appInfo.displayName, Modifier.weight(1f)) - Checkbox( - checked = isWhitelisted, - onCheckedChange = null, - ) - } + if (uiState.applications.isNotEmpty()) { + isRefreshing = false + } + + PullToRefreshBox( + state = refreshState, + isRefreshing = isRefreshing, + onRefresh = { + isRefreshing = true + viewModel.refreshPackages(context) + }, + modifier = Modifier.padding(padding) + ) { + LazyColumn(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) { + if (uiState.applications.any { it.value.isWhitelisted }) { + for ((packageName, appInfo) in uiState.applications.filter { it.value.isWhitelisted }) { + item { + Application(packageName, appInfo, viewModel) } } + item { + HorizontalDivider( + Modifier + .width(84.dp) + .padding(vertical = 12.dp)) + } + } + for ((packageName, appInfo) in uiState.applications.filter { !it.value.isWhitelisted }) { + item { + Application(packageName, appInfo, viewModel) + } } } } } +} + +@Composable +fun Application(packageName: String, app: AppInfo, viewModel: SettingsViewModel) { + Box( + Modifier + .fillMaxWidth() + .clickable( + role = Role.Checkbox, + onClick = { + if (app.isWhitelisted) { + viewModel.removeFromWhitelist(packageName) + } else { + viewModel.addToWhitelist(packageName) + } + } + )) { + Row( + Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 5.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + AsyncImage(modifier = Modifier.size(32.dp), model = app.icon, contentDescription = null) + Text(app.displayName, Modifier.weight(1f)) + Checkbox( + checked = app.isWhitelisted, + onCheckedChange = null, + ) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/illusionman1212/lyricsgrabbr/viewmodels/SettingsViewModel.kt b/app/src/main/java/com/illusionman1212/lyricsgrabbr/viewmodels/SettingsViewModel.kt index 7e647c1..8d9b072 100644 --- a/app/src/main/java/com/illusionman1212/lyricsgrabbr/viewmodels/SettingsViewModel.kt +++ b/app/src/main/java/com/illusionman1212/lyricsgrabbr/viewmodels/SettingsViewModel.kt @@ -28,10 +28,12 @@ data class AppInfo( data class SettingsState( val appTheme: Int = Theme.SYSTEM.ordinal, - val applications: MutableMap = mutableMapOf(), + val applications: Map = emptyMap(), + val whitelistSearchQuery: String = "", ) class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesRepository, context: Context): ViewModel() { + private var immutablePackages: Map = emptyMap() private val _uiState = MutableStateFlow(SettingsState()) val uiState = _uiState.asStateFlow() @@ -50,7 +52,7 @@ class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesReposi } private fun getPackages(context: Context) { - if (uiState.value.applications.isNotEmpty()) return + if (immutablePackages.isNotEmpty()) return viewModelScope.launch(Dispatchers.IO) { val pm = context.packageManager @@ -70,6 +72,7 @@ class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesReposi isWhitelisted = pkg.packageName in whitelistedApps ) } + immutablePackages = apps _uiState.update { SettingsState( appTheme = uiState.value.appTheme, @@ -79,16 +82,63 @@ class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesReposi } } + fun refreshPackages(context: Context) { + _uiState.update { + val apps = uiState.value.applications.toMutableMap() + apps.clear() + it.copy(whitelistSearchQuery = "", applications = apps) + } + val apps = immutablePackages.toMutableMap() + apps.clear() + immutablePackages = apps + getPackages(context) + } + + fun filterPackages(query: String) { + _uiState.update { state -> + state.copy( + applications = immutablePackages.filter { + it.value.displayName.lowercase().contains(query.lowercase()) + }.toMutableMap(), + whitelistSearchQuery = query, + ) + } + } + + fun clearWhitelistSearch() { + _uiState.update { + it.copy(applications = immutablePackages, whitelistSearchQuery = "") + } + } + fun addToWhitelist(packageName: String) { viewModelScope.launch(Dispatchers.IO) { - _uiState.value.applications[packageName]?.let { it.isWhitelisted = true } + _uiState.update { state -> + val apps = immutablePackages.toMutableMap() + apps[packageName] = apps[packageName]!!.copy(isWhitelisted = true) + immutablePackages = apps + state.copy( + applications = immutablePackages.filter { + it.value.displayName.lowercase().contains(uiState.value.whitelistSearchQuery.lowercase()) + }.toMutableMap(), + ) + } settingsPrefsRepo.addToWhitelist(packageName) } } fun removeFromWhitelist(packageName: String) { viewModelScope.launch(Dispatchers.IO) { - _uiState.value.applications[packageName]?.let { it.isWhitelisted = false } + _uiState.update { state -> + val apps = immutablePackages.toMutableMap() + apps[packageName] = apps[packageName]!!.copy(isWhitelisted = false) + immutablePackages = apps + state.copy( + applications = immutablePackages.filter { + it.value.displayName.lowercase().contains(uiState.value.whitelistSearchQuery.lowercase()) + }.toMutableMap(), + ) + } settingsPrefsRepo.removeFromWhitelist(packageName) } } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index c2b4a5c..1011197 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -38,6 +38,8 @@ القائمة البيضاء تطبيقات القائمة البيضاء اختر التطبيقات التي سيستمع التطبيق إلى إشعاراتها + بحث + مسح كلمات الأغنية diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 77b3406..65f4e66 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -37,6 +37,8 @@ Whitelist Whitelist Apps Choose which apps Lyrics Grabbr will listen to + Search + Clear Lyrics