mirror of
https://github.com/IllusionMan1212/lyrics-grabbr.git
synced 2026-10-24 02:12:27 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c167b6f6f
|
||
|
|
1a105f8413
|
||
|
|
edd5b24d92 | ||
|
|
7d07433e95 |
@@ -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")
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||||
|
tools:ignore="QueryAllPackagesPermission" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".LGApp"
|
android:name=".LGApp"
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import com.illusionman1212.lyricsgrabbr.ui.screens.HomePage
|
|||||||
import com.illusionman1212.lyricsgrabbr.ui.screens.LyricsPage
|
import com.illusionman1212.lyricsgrabbr.ui.screens.LyricsPage
|
||||||
import com.illusionman1212.lyricsgrabbr.ui.theme.LyricsGrabbrTheme
|
import com.illusionman1212.lyricsgrabbr.ui.theme.LyricsGrabbrTheme
|
||||||
import com.illusionman1212.lyricsgrabbr.ui.screens.SettingsPage
|
import com.illusionman1212.lyricsgrabbr.ui.screens.SettingsPage
|
||||||
|
import com.illusionman1212.lyricsgrabbr.ui.screens.WhitelistPage
|
||||||
import com.illusionman1212.lyricsgrabbr.viewmodels.LyricsViewModel
|
import com.illusionman1212.lyricsgrabbr.viewmodels.LyricsViewModel
|
||||||
import com.illusionman1212.lyricsgrabbr.viewmodels.SettingsViewModel
|
import com.illusionman1212.lyricsgrabbr.viewmodels.SettingsViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -130,6 +131,17 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
composable(Routes.Settings.route) {
|
composable(Routes.Settings.route) {
|
||||||
SettingsPage(
|
SettingsPage(
|
||||||
|
goBack = {
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
navigateToWhitelist = {
|
||||||
|
navController.navigate(Routes.Whitelist.route)
|
||||||
|
},
|
||||||
|
viewModel = settingsViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable(Routes.Whitelist.route) {
|
||||||
|
WhitelistPage(
|
||||||
goBack = {
|
goBack = {
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,15 +20,29 @@ import androidx.core.app.NotificationManagerCompat
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.os.BundleCompat
|
import androidx.core.os.BundleCompat
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
|
import com.illusionman1212.lyricsgrabbr.data.SettingsPreferencesRepository
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class NotificationListener: NotificationListenerService() {
|
class NotificationListener: NotificationListenerService() {
|
||||||
private var notificationManager : NotificationManagerCompat? = null
|
private var notificationManager : NotificationManagerCompat? = null
|
||||||
|
private lateinit var settingsPrefsRepo: SettingsPreferencesRepository
|
||||||
|
private val serviceScope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
initNotificationChannels(this)
|
initNotificationChannels(this)
|
||||||
notificationManager = NotificationManagerCompat.from(applicationContext)
|
notificationManager = NotificationManagerCompat.from(applicationContext)
|
||||||
|
settingsPrefsRepo = (application as LGApp).settingsPreferencesRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
serviceScope.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
@@ -53,7 +67,11 @@ class NotificationListener: NotificationListenerService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onNotificationPosted(sbn: StatusBarNotification) {
|
override fun onNotificationPosted(sbn: StatusBarNotification) {
|
||||||
sendNotificationBroadcast(sbn, applicationContext)
|
serviceScope.launch {
|
||||||
|
val whitelist = settingsPrefsRepo.preferences.first().whitelist
|
||||||
|
if (sbn.packageName !in whitelist) return@launch
|
||||||
|
sendNotificationBroadcast(sbn, applicationContext)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getStopServiceNotificationAction(): PendingIntent {
|
private fun getStopServiceNotificationAction(): PendingIntent {
|
||||||
@@ -91,8 +109,13 @@ class NotificationListener: NotificationListenerService() {
|
|||||||
|
|
||||||
private fun getCurrentNotifications(context: Context) {
|
private fun getCurrentNotifications(context: Context) {
|
||||||
val sbns = activeNotifications
|
val sbns = activeNotifications
|
||||||
for (sbn in sbns) {
|
serviceScope.launch {
|
||||||
sendNotificationBroadcast(sbn, context)
|
val whitelist = settingsPrefsRepo.preferences.first().whitelist
|
||||||
|
|
||||||
|
for (sbn in sbns) {
|
||||||
|
if (sbn.packageName !in whitelist) continue
|
||||||
|
sendNotificationBroadcast(sbn, context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package com.illusionman1212.lyricsgrabbr
|
package com.illusionman1212.lyricsgrabbr
|
||||||
|
|
||||||
sealed class Routes(val route: String) {
|
sealed class Routes(val route: String) {
|
||||||
object Home : Routes("home")
|
data object Home : Routes("home")
|
||||||
object Lyrics : Routes("lyrics")
|
data object Lyrics : Routes("lyrics")
|
||||||
object Settings : Routes("settings")
|
data object Settings : Routes("settings")
|
||||||
|
data object Whitelist : Routes("settings/whitelist")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
@@ -4,6 +4,7 @@ import androidx.datastore.core.DataStore
|
|||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import androidx.datastore.preferences.core.edit
|
import androidx.datastore.preferences.core.edit
|
||||||
import androidx.datastore.preferences.core.intPreferencesKey
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
|
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
enum class Theme {
|
enum class Theme {
|
||||||
@@ -14,6 +15,7 @@ enum class Theme {
|
|||||||
|
|
||||||
data class SettingsPreferences(
|
data class SettingsPreferences(
|
||||||
val appTheme: Int = Theme.SYSTEM.ordinal,
|
val appTheme: Int = Theme.SYSTEM.ordinal,
|
||||||
|
val whitelist: Set<String> = emptySet(),
|
||||||
)
|
)
|
||||||
|
|
||||||
class SettingsPreferencesRepository(
|
class SettingsPreferencesRepository(
|
||||||
@@ -21,6 +23,7 @@ class SettingsPreferencesRepository(
|
|||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private val APP_THEME = intPreferencesKey("app_theme")
|
private val APP_THEME = intPreferencesKey("app_theme")
|
||||||
|
private val WHITELIST = stringSetPreferencesKey("apps_whitelist")
|
||||||
}
|
}
|
||||||
|
|
||||||
val preferences = dataStore.data.map { mapSettingsPreferences(it) }
|
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 {
|
private fun mapSettingsPreferences(prefs: Preferences): SettingsPreferences {
|
||||||
return SettingsPreferences(
|
return SettingsPreferences(
|
||||||
prefs[APP_THEME] ?: Theme.SYSTEM.ordinal,
|
prefs[APP_THEME] ?: Theme.SYSTEM.ordinal,
|
||||||
|
prefs[WHITELIST] ?: emptySet(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ 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.Apps
|
||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.filled.Check
|
||||||
import androidx.compose.material.icons.outlined.Palette
|
import androidx.compose.material.icons.outlined.Palette
|
||||||
import androidx.compose.material.icons.outlined.Translate
|
import androidx.compose.material.icons.outlined.Translate
|
||||||
@@ -61,6 +62,7 @@ private val Themes = hashMapOf(
|
|||||||
@Composable
|
@Composable
|
||||||
fun SettingsPage(
|
fun SettingsPage(
|
||||||
goBack: () -> Unit,
|
goBack: () -> Unit,
|
||||||
|
navigateToWhitelist: () -> Unit,
|
||||||
viewModel: SettingsViewModel,
|
viewModel: SettingsViewModel,
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
@@ -94,6 +96,7 @@ fun SettingsPage(
|
|||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Appearance(viewModel, uiState)
|
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
|
@Composable
|
||||||
fun ThemeDialog(
|
fun ThemeDialog(
|
||||||
uiState: SettingsState,
|
uiState: SettingsState,
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
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.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
|
||||||
|
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
|
||||||
|
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 = {
|
||||||
|
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) {
|
||||||
|
Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(
|
||||||
|
id = R.string.go_back
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}) { padding ->
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val refreshState = rememberPullToRefreshState()
|
||||||
|
var isRefreshing by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+112
-2
@@ -1,5 +1,8 @@
|
|||||||
package com.illusionman1212.lyricsgrabbr.viewmodels
|
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.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
|
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
|
||||||
@@ -14,13 +17,23 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
data class AppInfo(
|
||||||
|
val displayName: String,
|
||||||
|
val icon: Drawable,
|
||||||
|
var isWhitelisted: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
data class SettingsState(
|
data class SettingsState(
|
||||||
val appTheme: Int = Theme.SYSTEM.ordinal,
|
val appTheme: Int = Theme.SYSTEM.ordinal,
|
||||||
|
val applications: Map<String, AppInfo> = emptyMap(),
|
||||||
|
val whitelistSearchQuery: String = "",
|
||||||
)
|
)
|
||||||
|
|
||||||
class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesRepository): 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()
|
||||||
|
|
||||||
@@ -34,7 +47,104 @@ class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesReposi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun getWhitelistedApps(): Set<String> {
|
||||||
|
return settingsPrefsRepo.preferences.first().whitelist
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPackages(context: Context) {
|
||||||
|
if (immutablePackages.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<String, AppInfo> = 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
immutablePackages = apps
|
||||||
|
_uiState.update {
|
||||||
|
SettingsState(
|
||||||
|
appTheme = uiState.value.appTheme,
|
||||||
|
applications = apps,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
getPackages(context)
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
settingsPrefsRepo.preferences.collectLatest {
|
settingsPrefsRepo.preferences.collectLatest {
|
||||||
_uiState.value = _uiState.value.copy(
|
_uiState.value = _uiState.value.copy(
|
||||||
@@ -48,7 +158,7 @@ class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesReposi
|
|||||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||||
initializer {
|
initializer {
|
||||||
val application = (this[APPLICATION_KEY] as LGApp)
|
val application = (this[APPLICATION_KEY] as LGApp)
|
||||||
SettingsViewModel(application.settingsPreferencesRepository)
|
SettingsViewModel(application.settingsPreferencesRepository, application.applicationContext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,12 @@
|
|||||||
<string name="theme_system">استخدم مظهر النظام</string>
|
<string name="theme_system">استخدم مظهر النظام</string>
|
||||||
<string name="language">اللغة</string>
|
<string name="language">اللغة</string>
|
||||||
<string name="_default">الإفتراضي</string>
|
<string name="_default">الإفتراضي</string>
|
||||||
|
<string name="listening">الإستماع</string>
|
||||||
|
<string name="whitelist">القائمة البيضاء</string>
|
||||||
|
<string name="whitelist_apps">تطبيقات القائمة البيضاء</string>
|
||||||
|
<string name="whitelist_desc">اختر التطبيقات التي سيستمع التطبيق إلى إشعاراتها</string>
|
||||||
|
<string name="search">بحث</string>
|
||||||
|
<string name="clear">مسح</string>
|
||||||
|
|
||||||
<!-- Lyrics -->
|
<!-- Lyrics -->
|
||||||
<string name="lyrics">كلمات الأغنية</string>
|
<string name="lyrics">كلمات الأغنية</string>
|
||||||
|
|||||||
@@ -33,6 +33,12 @@
|
|||||||
<string name="theme_system">Use System Theme</string>
|
<string name="theme_system">Use System Theme</string>
|
||||||
<string name="language">Language</string>
|
<string name="language">Language</string>
|
||||||
<string name="_default">Default</string>
|
<string name="_default">Default</string>
|
||||||
|
<string name="listening">Listening</string>
|
||||||
|
<string name="whitelist">Whitelist</string>
|
||||||
|
<string name="whitelist_apps">Whitelist Apps</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