mirror of
https://github.com/IllusionMan1212/lyrics-grabbr.git
synced 2026-10-24 02:12:27 +00:00
whitelist search
This commit is contained in:
@@ -118,7 +118,7 @@ dependencies {
|
|||||||
implementation("androidx.compose.ui:ui")
|
implementation("androidx.compose.ui:ui")
|
||||||
implementation("androidx.compose.ui:ui-graphics")
|
implementation("androidx.compose.ui:ui-graphics")
|
||||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
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("com.google.android.material:material:1.12.0")
|
||||||
implementation("androidx.compose.material:material-icons-extended:1.6.8")
|
implementation("androidx.compose.material:material-icons-extended:1.6.8")
|
||||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||||
|
|||||||
@@ -433,7 +433,6 @@ fun LongClickBottomSheet(
|
|||||||
dragHandle = {},
|
dragHandle = {},
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
sheetState = sheetState,
|
sheetState = sheetState,
|
||||||
windowInsets = WindowInsets(top = 0.dp),
|
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
if (artistUri != Uri.EMPTY) {
|
if (artistUri != Uri.EMPTY) {
|
||||||
|
|||||||
@@ -4,18 +4,27 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Clear
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
import androidx.compose.material3.TopAppBar
|
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.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -23,6 +32,8 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.res.stringResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -30,15 +41,42 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.illusionman1212.lyricsgrabbr.R
|
import com.illusionman1212.lyricsgrabbr.R
|
||||||
import com.illusionman1212.lyricsgrabbr.ui.components.LGIconButton
|
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
|
import com.illusionman1212.lyricsgrabbr.viewmodels.SettingsViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun WhitelistPage(goBack: () -> Unit, viewModel: SettingsViewModel) {
|
fun WhitelistPage(goBack: () -> Unit, viewModel: SettingsViewModel) {
|
||||||
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
Scaffold(topBar = {
|
Scaffold(topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
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 = {
|
navigationIcon = {
|
||||||
LGIconButton(tooltip = stringResource(id = R.string.go_back), onClick = goBack) {
|
LGIconButton(tooltip = stringResource(id = R.string.go_back), onClick = goBack) {
|
||||||
@@ -49,46 +87,76 @@ fun WhitelistPage(goBack: () -> Unit, viewModel: SettingsViewModel) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}) { padding ->
|
}) { padding ->
|
||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val context = LocalContext.current
|
||||||
|
|
||||||
Box(Modifier.padding(padding)) {
|
val refreshState = rememberPullToRefreshState()
|
||||||
LazyColumn {
|
var isRefreshing by remember { mutableStateOf(false) }
|
||||||
for ((packageName, appInfo) in uiState.applications) {
|
|
||||||
item {
|
|
||||||
var isWhitelisted by remember { mutableStateOf(appInfo.isWhitelisted) }
|
|
||||||
|
|
||||||
Box(
|
if (uiState.applications.isNotEmpty()) {
|
||||||
Modifier
|
isRefreshing = false
|
||||||
.fillMaxWidth()
|
}
|
||||||
.clickable(
|
|
||||||
role = Role.Checkbox,
|
PullToRefreshBox(
|
||||||
onClick = {
|
state = refreshState,
|
||||||
if (isWhitelisted) {
|
isRefreshing = isRefreshing,
|
||||||
viewModel.removeFromWhitelist(packageName)
|
onRefresh = {
|
||||||
} else {
|
isRefreshing = true
|
||||||
viewModel.addToWhitelist(packageName)
|
viewModel.refreshPackages(context)
|
||||||
}
|
},
|
||||||
isWhitelisted = !isWhitelisted
|
modifier = Modifier.padding(padding)
|
||||||
}
|
) {
|
||||||
)) {
|
LazyColumn(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Row(
|
if (uiState.applications.any { it.value.isWhitelisted }) {
|
||||||
Modifier
|
for ((packageName, appInfo) in uiState.applications.filter { it.value.isWhitelisted }) {
|
||||||
.fillMaxWidth()
|
item {
|
||||||
.padding(horizontal = 24.dp, vertical = 5.dp),
|
Application(packageName, appInfo, viewModel)
|
||||||
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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,10 +28,12 @@ data class AppInfo(
|
|||||||
|
|
||||||
data class SettingsState(
|
data class SettingsState(
|
||||||
val appTheme: Int = Theme.SYSTEM.ordinal,
|
val appTheme: Int = Theme.SYSTEM.ordinal,
|
||||||
val applications: MutableMap<String, AppInfo> = mutableMapOf(),
|
val applications: Map<String, AppInfo> = emptyMap(),
|
||||||
|
val whitelistSearchQuery: String = "",
|
||||||
)
|
)
|
||||||
|
|
||||||
class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesRepository, context: Context): ViewModel() {
|
class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesRepository, context: Context): ViewModel() {
|
||||||
|
private var immutablePackages: Map<String, AppInfo> = emptyMap()
|
||||||
private val _uiState = MutableStateFlow(SettingsState())
|
private val _uiState = MutableStateFlow(SettingsState())
|
||||||
val uiState = _uiState.asStateFlow()
|
val uiState = _uiState.asStateFlow()
|
||||||
|
|
||||||
@@ -50,7 +52,7 @@ class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesReposi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getPackages(context: Context) {
|
private fun getPackages(context: Context) {
|
||||||
if (uiState.value.applications.isNotEmpty()) return
|
if (immutablePackages.isNotEmpty()) return
|
||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val pm = context.packageManager
|
val pm = context.packageManager
|
||||||
@@ -70,6 +72,7 @@ class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesReposi
|
|||||||
isWhitelisted = pkg.packageName in whitelistedApps
|
isWhitelisted = pkg.packageName in whitelistedApps
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
immutablePackages = apps
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
SettingsState(
|
SettingsState(
|
||||||
appTheme = uiState.value.appTheme,
|
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) {
|
fun addToWhitelist(packageName: String) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
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)
|
settingsPrefsRepo.addToWhitelist(packageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeFromWhitelist(packageName: String) {
|
fun removeFromWhitelist(packageName: String) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
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)
|
settingsPrefsRepo.removeFromWhitelist(packageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
<string name="whitelist">القائمة البيضاء</string>
|
<string name="whitelist">القائمة البيضاء</string>
|
||||||
<string name="whitelist_apps">تطبيقات القائمة البيضاء</string>
|
<string name="whitelist_apps">تطبيقات القائمة البيضاء</string>
|
||||||
<string name="whitelist_desc">اختر التطبيقات التي سيستمع التطبيق إلى إشعاراتها</string>
|
<string name="whitelist_desc">اختر التطبيقات التي سيستمع التطبيق إلى إشعاراتها</string>
|
||||||
|
<string name="search">بحث</string>
|
||||||
|
<string name="clear">مسح</string>
|
||||||
|
|
||||||
<!-- Lyrics -->
|
<!-- Lyrics -->
|
||||||
<string name="lyrics">كلمات الأغنية</string>
|
<string name="lyrics">كلمات الأغنية</string>
|
||||||
|
|||||||
@@ -37,6 +37,8 @@
|
|||||||
<string name="whitelist">Whitelist</string>
|
<string name="whitelist">Whitelist</string>
|
||||||
<string name="whitelist_apps">Whitelist Apps</string>
|
<string name="whitelist_apps">Whitelist Apps</string>
|
||||||
<string name="whitelist_desc">Choose which apps Lyrics Grabbr will listen to</string>
|
<string name="whitelist_desc">Choose which apps Lyrics Grabbr will listen to</string>
|
||||||
|
<string name="search">Search</string>
|
||||||
|
<string name="clear">Clear</string>
|
||||||
|
|
||||||
<!-- Lyrics -->
|
<!-- Lyrics -->
|
||||||
<string name="lyrics">Lyrics</string>
|
<string name="lyrics">Lyrics</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user