diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c071f10..623e1a3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,6 +6,8 @@
+
= emptySet(),
)
class SettingsPreferencesRepository(
@@ -21,6 +23,7 @@ class SettingsPreferencesRepository(
) {
companion object {
private val APP_THEME = intPreferencesKey("app_theme")
+ private val WHITELIST = stringSetPreferencesKey("apps_whitelist")
}
val preferences = dataStore.data.map { mapSettingsPreferences(it) }
@@ -31,9 +34,22 @@ class SettingsPreferencesRepository(
}
}
+ suspend fun addToWhitelist(packageName: String) {
+ dataStore.edit { prefs ->
+ prefs[WHITELIST] = (prefs[WHITELIST]?.toMutableSet() ?: emptySet()) + packageName
+ }
+ }
+
+ suspend fun removeFromWhitelist(packageName: String) {
+ dataStore.edit { prefs ->
+ prefs[WHITELIST] = (prefs[WHITELIST]?.toMutableSet() ?: emptySet()) - packageName
+ }
+ }
+
private fun mapSettingsPreferences(prefs: Preferences): SettingsPreferences {
return SettingsPreferences(
prefs[APP_THEME] ?: Theme.SYSTEM.ordinal,
+ prefs[WHITELIST] ?: emptySet(),
)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/illusionman1212/lyricsgrabbr/ui/screens/Settings.kt b/app/src/main/java/com/illusionman1212/lyricsgrabbr/ui/screens/Settings.kt
index c3f8cdc..817ef1a 100644
--- a/app/src/main/java/com/illusionman1212/lyricsgrabbr/ui/screens/Settings.kt
+++ b/app/src/main/java/com/illusionman1212/lyricsgrabbr/ui/screens/Settings.kt
@@ -16,6 +16,7 @@ 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.Apps
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.outlined.Palette
import androidx.compose.material.icons.outlined.Translate
@@ -61,6 +62,7 @@ private val Themes = hashMapOf(
@Composable
fun SettingsPage(
goBack: () -> Unit,
+ navigateToWhitelist: () -> Unit,
viewModel: SettingsViewModel,
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
@@ -94,6 +96,7 @@ fun SettingsPage(
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Appearance(viewModel, uiState)
+ Listening(navigateToWhitelist)
}
}
}
@@ -138,6 +141,35 @@ fun Appearance(viewModel: SettingsViewModel, uiState: SettingsState) {
}
}
+@Composable
+private fun Listening(navigateToWhitelist: () -> Unit) {
+ SettingGroup(title = stringResource(id = R.string.listening)) {
+ Box(
+ Modifier
+ .fillMaxWidth()
+ .clickable { navigateToWhitelist() }
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.padding(vertical = 8.dp)
+ ) {
+ Icon(imageVector = Icons.Default.Apps, contentDescription = null, modifier = Modifier.width(56.dp))
+ Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
+ Text(text = stringResource(id = R.string.whitelist_apps), color = MaterialTheme.colorScheme.onBackground)
+ Text(
+ text = stringResource(id = R.string.whitelist_desc),
+ color = ColorSecondaryLight,
+ fontSize = 14.sp,
+ maxLines = 2,
+ overflow = TextOverflow.Ellipsis,
+ modifier = Modifier.padding(end = 24.dp)
+ )
+ }
+ }
+ }
+ }
+}
+
@Composable
fun ThemeDialog(
uiState: SettingsState,
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
new file mode 100644
index 0000000..9be68c0
--- /dev/null
+++ b/app/src/main/java/com/illusionman1212/lyricsgrabbr/ui/screens/Whitelist.kt
@@ -0,0 +1,94 @@
+package com.illusionman1212.lyricsgrabbr.ui.screens
+
+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.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.unit.dp
+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.viewmodels.SettingsViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun WhitelistPage(goBack: () -> Unit, viewModel: SettingsViewModel) {
+ Scaffold(topBar = {
+ TopAppBar(
+ title = {
+ Text(text = stringResource(id = R.string.whitelist))
+ },
+ navigationIcon = {
+ LGIconButton(tooltip = stringResource(id = R.string.go_back), onClick = goBack) {
+ Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(
+ id = R.string.go_back
+ ))
+ }
+ }
+ )
+ }) { padding ->
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+ Box(Modifier.padding(padding)) {
+ LazyColumn {
+ for ((packageName, appInfo) in uiState.applications) {
+ item {
+ var isWhitelisted by remember { mutableStateOf(appInfo.isWhitelisted) }
+
+ 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,
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ 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 8473710..7e647c1 100644
--- a/app/src/main/java/com/illusionman1212/lyricsgrabbr/viewmodels/SettingsViewModel.kt
+++ b/app/src/main/java/com/illusionman1212/lyricsgrabbr/viewmodels/SettingsViewModel.kt
@@ -1,5 +1,8 @@
package com.illusionman1212.lyricsgrabbr.viewmodels
+import android.content.Context
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
@@ -14,13 +17,21 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
+data class AppInfo(
+ val displayName: String,
+ val icon: Drawable,
+ var isWhitelisted: Boolean,
+)
+
data class SettingsState(
val appTheme: Int = Theme.SYSTEM.ordinal,
+ val applications: MutableMap = mutableMapOf(),
)
-class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesRepository): ViewModel() {
+class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesRepository, context: Context): ViewModel() {
private val _uiState = MutableStateFlow(SettingsState())
val uiState = _uiState.asStateFlow()
@@ -34,7 +45,56 @@ class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesReposi
}
}
+ private suspend fun getWhitelistedApps(): Set {
+ return settingsPrefsRepo.preferences.first().whitelist
+ }
+
+ private fun getPackages(context: Context) {
+ if (uiState.value.applications.isNotEmpty()) return
+
+ viewModelScope.launch(Dispatchers.IO) {
+ val pm = context.packageManager
+ val packages = pm.getInstalledApplications(PackageManager.GET_META_DATA)
+ .filter { it.packageName != context.packageName && pm.getLaunchIntentForPackage(it.packageName) != null }
+ .sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.loadLabel(pm).toString() })
+
+ val apps: MutableMap = mutableMapOf()
+ val whitelistedApps = getWhitelistedApps()
+
+ packages.forEach { pkg ->
+ val label = pkg.loadLabel(pm)
+ val icon = pkg.loadIcon(pm)
+ apps[pkg.packageName] = AppInfo(
+ displayName = label.toString(),
+ icon = icon,
+ isWhitelisted = pkg.packageName in whitelistedApps
+ )
+ }
+ _uiState.update {
+ SettingsState(
+ appTheme = uiState.value.appTheme,
+ applications = apps,
+ )
+ }
+ }
+ }
+
+ fun addToWhitelist(packageName: String) {
+ viewModelScope.launch(Dispatchers.IO) {
+ _uiState.value.applications[packageName]?.let { it.isWhitelisted = true }
+ settingsPrefsRepo.addToWhitelist(packageName)
+ }
+ }
+
+ fun removeFromWhitelist(packageName: String) {
+ viewModelScope.launch(Dispatchers.IO) {
+ _uiState.value.applications[packageName]?.let { it.isWhitelisted = false }
+ settingsPrefsRepo.removeFromWhitelist(packageName)
+ }
+ }
+
init {
+ getPackages(context)
viewModelScope.launch {
settingsPrefsRepo.preferences.collectLatest {
_uiState.value = _uiState.value.copy(
@@ -48,7 +108,7 @@ class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesReposi
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val application = (this[APPLICATION_KEY] as LGApp)
- SettingsViewModel(application.settingsPreferencesRepository)
+ SettingsViewModel(application.settingsPreferencesRepository, application.applicationContext)
}
}
}
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 337341c..c2b4a5c 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -34,6 +34,10 @@
استخدم مظهر النظام
اللغة
الإفتراضي
+ الإستماع
+ القائمة البيضاء
+ تطبيقات القائمة البيضاء
+ اختر التطبيقات التي سيستمع التطبيق إلى إشعاراتها
كلمات الأغنية
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a47faa0..77b3406 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -33,6 +33,10 @@
Use System Theme
Language
Default
+ Listening
+ Whitelist
+ Whitelist Apps
+ Choose which apps Lyrics Grabbr will listen to
Lyrics