mirror of
https://github.com/IllusionMan1212/lyrics-grabbr.git
synced 2026-10-24 18:12:27 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37b5ca8917
|
||
|
|
6b64ead713
|
@@ -1,3 +1,4 @@
|
|||||||
|
*.iml
|
||||||
.gradle
|
.gradle
|
||||||
/local.properties
|
/local.properties
|
||||||
/.idea/caches
|
/.idea/caches
|
||||||
@@ -6,6 +7,7 @@
|
|||||||
/.idea/workspace.xml
|
/.idea/workspace.xml
|
||||||
/.idea/navEditor.xml
|
/.idea/navEditor.xml
|
||||||
/.idea/assetWizardSettings.xml
|
/.idea/assetWizardSettings.xml
|
||||||
|
.DS_Store
|
||||||
/build
|
/build
|
||||||
/captures
|
/captures
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ android {
|
|||||||
applicationId = "com.illusionman1212.lyricsgrabbr"
|
applicationId = "com.illusionman1212.lyricsgrabbr"
|
||||||
minSdk = 23
|
minSdk = 23
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 7
|
versionCode = 6
|
||||||
versionName = "1.1.0"
|
versionName = "1.0.0"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
@@ -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.3.0-beta04")
|
implementation("androidx.compose.material3:material3:1.2.1")
|
||||||
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,8 +6,6 @@
|
|||||||
<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"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@@ -34,7 +35,6 @@ 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
|
||||||
@@ -126,22 +126,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
},
|
},
|
||||||
lyricsViewModel = lyricsViewModel,
|
lyricsViewModel = lyricsViewModel,
|
||||||
settingsViewModel = settingsViewModel
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
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,29 +20,15 @@ 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 {
|
||||||
@@ -67,12 +53,8 @@ class NotificationListener: NotificationListenerService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onNotificationPosted(sbn: StatusBarNotification) {
|
override fun onNotificationPosted(sbn: StatusBarNotification) {
|
||||||
serviceScope.launch {
|
|
||||||
val whitelist = settingsPrefsRepo.preferences.first().whitelist
|
|
||||||
if (sbn.packageName !in whitelist) return@launch
|
|
||||||
sendNotificationBroadcast(sbn, applicationContext)
|
sendNotificationBroadcast(sbn, applicationContext)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun getStopServiceNotificationAction(): PendingIntent {
|
private fun getStopServiceNotificationAction(): PendingIntent {
|
||||||
val intent = Intent(android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
val intent = Intent(android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
@@ -109,15 +91,10 @@ class NotificationListener: NotificationListenerService() {
|
|||||||
|
|
||||||
private fun getCurrentNotifications(context: Context) {
|
private fun getCurrentNotifications(context: Context) {
|
||||||
val sbns = activeNotifications
|
val sbns = activeNotifications
|
||||||
serviceScope.launch {
|
|
||||||
val whitelist = settingsPrefsRepo.preferences.first().whitelist
|
|
||||||
|
|
||||||
for (sbn in sbns) {
|
for (sbn in sbns) {
|
||||||
if (sbn.packageName !in whitelist) continue
|
|
||||||
sendNotificationBroadcast(sbn, context)
|
sendNotificationBroadcast(sbn, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun getInitialNotification(): Notification {
|
private fun getInitialNotification(): Notification {
|
||||||
val notification: Notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
|
val notification: Notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package com.illusionman1212.lyricsgrabbr
|
package com.illusionman1212.lyricsgrabbr
|
||||||
|
|
||||||
sealed class Routes(val route: String) {
|
sealed class Routes(val route: String) {
|
||||||
data object Home : Routes("home")
|
object Home : Routes("home")
|
||||||
data object Lyrics : Routes("lyrics")
|
object Lyrics : Routes("lyrics")
|
||||||
data object Settings : Routes("settings")
|
object Settings : Routes("settings")
|
||||||
data object Whitelist : Routes("settings/whitelist")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-26
@@ -2,10 +2,8 @@ package com.illusionman1212.lyricsgrabbr.data
|
|||||||
|
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
|
||||||
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 {
|
||||||
@@ -16,8 +14,6 @@ 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(),
|
|
||||||
val keepScreenOn: Boolean = false,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class SettingsPreferencesRepository(
|
class SettingsPreferencesRepository(
|
||||||
@@ -25,8 +21,6 @@ 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")
|
|
||||||
private val KEEP_SCREEN_ON = booleanPreferencesKey("keep_screen_on")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val preferences = dataStore.data.map { mapSettingsPreferences(it) }
|
val preferences = dataStore.data.map { mapSettingsPreferences(it) }
|
||||||
@@ -37,29 +31,9 @@ class SettingsPreferencesRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun addToWhitelist(packageName: String) {
|
|
||||||
dataStore.edit { prefs ->
|
|
||||||
prefs[WHITELIST] = (prefs[WHITELIST]?.toMutableSet() ?: emptySet()) + packageName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun toggleKeepScreenOn() {
|
|
||||||
dataStore.edit { prefs ->
|
|
||||||
prefs[KEEP_SCREEN_ON] = !(prefs[KEEP_SCREEN_ON] ?: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(),
|
|
||||||
prefs[KEEP_SCREEN_ON] ?: false,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.wrapContentHeight
|
|||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.AlertDialogDefaults
|
import androidx.compose.material3.AlertDialogDefaults
|
||||||
import androidx.compose.material3.BasicAlertDialog
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
@@ -30,9 +29,9 @@ fun LGAlertDialog(
|
|||||||
buttons: @Composable () -> Unit,
|
buttons: @Composable () -> Unit,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
BasicAlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
properties = properties
|
properties = properties,
|
||||||
) {
|
) {
|
||||||
Surface (
|
Surface (
|
||||||
modifier = Modifier.wrapContentHeight().wrapContentWidth(),
|
modifier = Modifier.wrapContentHeight().wrapContentWidth(),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@@ -57,6 +58,7 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
@@ -72,7 +74,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.compose.currentStateAsState
|
import androidx.lifecycle.compose.currentStateAsState
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
@@ -432,6 +433,7 @@ 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) {
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
package com.illusionman1212.lyricsgrabbr.ui.screens
|
package com.illusionman1212.lyricsgrabbr.ui.screens
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.view.WindowManager.LayoutParams
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
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.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
@@ -16,7 +10,7 @@ import androidx.compose.foundation.text.KeyboardOptions
|
|||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
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.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.MusicNote
|
import androidx.compose.material.icons.filled.MusicNote
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
@@ -29,12 +23,10 @@ import androidx.compose.material3.TextFieldDefaults
|
|||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
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.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
@@ -44,13 +36,12 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
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.utils.mirror
|
||||||
import com.illusionman1212.lyricsgrabbr.viewmodels.LyricsViewModel
|
import com.illusionman1212.lyricsgrabbr.viewmodels.LyricsViewModel
|
||||||
import com.illusionman1212.lyricsgrabbr.viewmodels.SettingsViewModel
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -59,27 +50,13 @@ fun LyricsPage(
|
|||||||
lyricsViewModel: LyricsViewModel = viewModel(
|
lyricsViewModel: LyricsViewModel = viewModel(
|
||||||
factory = LyricsViewModel.Factory
|
factory = LyricsViewModel.Factory
|
||||||
),
|
),
|
||||||
settingsViewModel: SettingsViewModel,
|
|
||||||
) {
|
) {
|
||||||
val uiState = lyricsViewModel.uiState.collectAsStateWithLifecycle().value
|
val uiState = lyricsViewModel.uiState.collectAsStateWithLifecycle().value
|
||||||
val settingsState = settingsViewModel.uiState.collectAsStateWithLifecycle().value
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
LaunchedEffect(uiState.song, uiState.artist, uiState.url) {
|
LaunchedEffect(uiState.song, uiState.artist, uiState.url) {
|
||||||
lyricsViewModel.fetchLyricsFromGenius()
|
lyricsViewModel.fetchLyricsFromGenius()
|
||||||
}
|
}
|
||||||
|
|
||||||
DisposableEffect(Unit) {
|
|
||||||
val window = (context as Activity).window
|
|
||||||
if (settingsState.keepScreenOn) {
|
|
||||||
window.addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
||||||
}
|
|
||||||
|
|
||||||
onDispose {
|
|
||||||
window.clearFlags(LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
@@ -105,7 +82,8 @@ fun LyricsPage(
|
|||||||
onClick = goBack
|
onClick = goBack
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
modifier = Modifier.mirror(),
|
||||||
|
imageVector = Icons.Filled.ArrowBack,
|
||||||
contentDescription = stringResource(
|
contentDescription = stringResource(
|
||||||
id = R.string.go_back
|
id = R.string.go_back
|
||||||
)
|
)
|
||||||
@@ -115,19 +93,9 @@ fun LyricsPage(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { padding ->
|
) { padding ->
|
||||||
Column(Modifier.padding(padding)) {
|
|
||||||
if (settingsState.keepScreenOn) {
|
|
||||||
Row(
|
|
||||||
Modifier
|
|
||||||
.background(MaterialTheme.colorScheme.secondaryContainer)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.Center,
|
|
||||||
) {
|
|
||||||
Text(stringResource(id = R.string.screen_will_stay_on).uppercase(), fontSize = 12.sp, color = MaterialTheme.colorScheme.onSecondaryContainer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
|
.padding(padding)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.then(
|
.then(
|
||||||
if (!uiState.isLoading && uiState.lyrics.isNotEmpty()) Modifier.verticalScroll(
|
if (!uiState.isLoading && uiState.lyrics.isNotEmpty()) Modifier.verticalScroll(
|
||||||
@@ -149,6 +117,7 @@ fun LyricsPage(
|
|||||||
} else if (uiState.lyrics.isEmpty()) {
|
} else if (uiState.lyrics.isEmpty()) {
|
||||||
Instrumental()
|
Instrumental()
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||||
Column(
|
Column(
|
||||||
Modifier.padding(
|
Modifier.padding(
|
||||||
@@ -179,7 +148,6 @@ fun LyricsPage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
|
|||||||
@@ -16,9 +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.filled.ScreenLockPortrait
|
|
||||||
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
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -35,6 +33,7 @@ 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.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@@ -62,7 +61,6 @@ 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()
|
||||||
@@ -96,14 +94,12 @@ fun SettingsPage(
|
|||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Appearance(viewModel, uiState)
|
Appearance(viewModel, uiState)
|
||||||
Listening(navigateToWhitelist)
|
|
||||||
PowerManagement(viewModel, uiState)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Appearance(viewModel: SettingsViewModel, uiState: SettingsState) {
|
fun Appearance(viewModel: SettingsViewModel, uiState: SettingsState) {
|
||||||
var themeOpenDialog by remember { mutableStateOf(false) }
|
var themeOpenDialog by remember { mutableStateOf(false) }
|
||||||
var languageOpenDialog by remember { mutableStateOf(false) }
|
var languageOpenDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@@ -142,49 +138,6 @@ private 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
|
|
||||||
private fun PowerManagement(viewModel: SettingsViewModel, uiState: SettingsState) {
|
|
||||||
SettingGroup(title = stringResource(id = R.string.power_management)) {
|
|
||||||
ToggleSetting(
|
|
||||||
title = stringResource(id = R.string.keep_device_awake_title),
|
|
||||||
description = stringResource(id = R.string.keep_device_awake_desc),
|
|
||||||
icon = Icons.Default.ScreenLockPortrait,
|
|
||||||
checked = uiState.keepScreenOn
|
|
||||||
) {
|
|
||||||
viewModel.toggleKeepScreenOn()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ThemeDialog(
|
fun ThemeDialog(
|
||||||
uiState: SettingsState,
|
uiState: SettingsState,
|
||||||
@@ -326,8 +279,7 @@ fun DialogSetting(
|
|||||||
@Composable
|
@Composable
|
||||||
fun ToggleSetting(
|
fun ToggleSetting(
|
||||||
title: String,
|
title: String,
|
||||||
description: String? = null,
|
icon: Painter? = null,
|
||||||
icon: ImageVector? = null,
|
|
||||||
checked: Boolean,
|
checked: Boolean,
|
||||||
onClick: (Boolean) -> Unit)
|
onClick: (Boolean) -> Unit)
|
||||||
{
|
{
|
||||||
@@ -342,20 +294,11 @@ fun ToggleSetting(
|
|||||||
modifier = Modifier.padding(top = 8.dp, end = 24.dp, bottom = 8.dp),
|
modifier = Modifier.padding(top = 8.dp, end = 24.dp, bottom = 8.dp),
|
||||||
) {
|
) {
|
||||||
if (icon != null) {
|
if (icon != null) {
|
||||||
Icon(imageVector = icon, contentDescription = null, Modifier.width(56.dp))
|
Icon(painter = icon, contentDescription = null, Modifier.width(56.dp))
|
||||||
} else {
|
} else {
|
||||||
Box(modifier = Modifier.width(56.dp))
|
Box(modifier = Modifier.width(56.dp))
|
||||||
}
|
}
|
||||||
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
Text(text = title, color = MaterialTheme.colorScheme.onBackground, modifier = Modifier.weight(1.0f))
|
||||||
Text(text = title, color = MaterialTheme.colorScheme.onBackground)
|
|
||||||
if (description != null) {
|
|
||||||
Text(
|
|
||||||
text = description,
|
|
||||||
color = ColorSecondaryLight,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Switch(
|
Switch(
|
||||||
checked = checked,
|
checked = checked,
|
||||||
onCheckedChange = null,
|
onCheckedChange = null,
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.illusionman1212.lyricsgrabbr.utils
|
||||||
|
|
||||||
|
import android.util.LayoutDirection
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.scale
|
||||||
|
import androidx.core.text.layoutDirection
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
fun Modifier.mirror(): Modifier {
|
||||||
|
return if (Locale.getDefault().layoutDirection == LayoutDirection.RTL)
|
||||||
|
this.scale(scaleX = -1f, scaleY = 1f)
|
||||||
|
else
|
||||||
|
this
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ fun annotatedStringResource(
|
|||||||
val spannedString = SpannedString(resources.getText(id))
|
val spannedString = SpannedString(resources.getText(id))
|
||||||
val resultBuilder = AnnotatedString.Builder()
|
val resultBuilder = AnnotatedString.Builder()
|
||||||
resultBuilder.append(spannedString.toString())
|
resultBuilder.append(spannedString.toString())
|
||||||
spannedString.getSpans(0, spannedString.length, Annotation::class.java).forEach { annotation ->
|
spannedString.getSpans<Annotation>(0, spannedString.length, Annotation::class.java).forEach { annotation ->
|
||||||
val spanStart = spannedString.getSpanStart(annotation)
|
val spanStart = spannedString.getSpanStart(annotation)
|
||||||
val spanEnd = spannedString.getSpanEnd(annotation)
|
val spanEnd = spannedString.getSpanEnd(annotation)
|
||||||
resultBuilder.addStringAnnotation(
|
resultBuilder.addStringAnnotation(
|
||||||
|
|||||||
@@ -96,9 +96,7 @@ class HomeViewModel(private val homeRepository: HomeRepository): ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setSong(song: NotificationEvent) {
|
fun setSong(song: NotificationEvent) {
|
||||||
_uiState.update { state ->
|
_uiState.value = _uiState.value.copy(isLoading = true, notification = song, error = null)
|
||||||
state.copy(isLoading = true, notification = song, error = null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun makeRequestToGenius(song: String, artist: String) {
|
fun makeRequestToGenius(song: String, artist: String) {
|
||||||
|
|||||||
@@ -41,16 +41,12 @@ class LyricsViewModel(private val lyricsRepository: LyricsRepository): ViewModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun fetchLyricsFromGenius() {
|
fun fetchLyricsFromGenius() {
|
||||||
_uiState.update { state ->
|
_uiState.value = _uiState.value.copy(isLoading = true, lyrics = "")
|
||||||
state.copy(isLoading = true, lyrics = "")
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val lyrics = lyricsRepository.fetchLyricsFromGenius(uiState.value.url)
|
val lyrics = lyricsRepository.fetchLyricsFromGenius(uiState.value.url)
|
||||||
_uiState.update { state ->
|
_uiState.value = _uiState.value.copy(isLoading = false, lyrics = lyrics)
|
||||||
state.copy(isLoading = false, lyrics = lyrics)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-122
@@ -1,8 +1,5 @@
|
|||||||
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
|
||||||
@@ -17,24 +14,13 @@ 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 = "",
|
|
||||||
val keepScreenOn: Boolean = false,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesRepository, context: Context): ViewModel() {
|
class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesRepository): 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()
|
||||||
|
|
||||||
@@ -48,126 +34,21 @@ class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesReposi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleKeepScreenOn() {
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
settingsPrefsRepo.toggleKeepScreenOn()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 { state ->
|
|
||||||
state.copy(
|
|
||||||
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.update { state ->
|
_uiState.value = _uiState.value.copy(
|
||||||
state.copy(
|
|
||||||
appTheme = it.appTheme,
|
appTheme = it.appTheme,
|
||||||
keepScreenOn = it.keepScreenOn,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
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, application.applicationContext)
|
SettingsViewModel(application.settingsPreferencesRepository)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10s10,-4.5 10,-10S17.5,2 12,2zM16.057,16.406c-0.188,0 -0.282,-0.094 -0.47,-0.188c-1.411,-0.847 -3.198,-1.317 -5.079,-1.317c-1.035,0 -2.163,0.188 -3.104,0.376c-0.188,0 -0.376,0.094 -0.47,0.094c-0.376,0 -0.658,-0.282 -0.658,-0.658c0,-0.376 0.188,-0.658 0.564,-0.658c1.223,-0.282 2.446,-0.47 3.763,-0.47c2.163,0 4.139,0.564 5.832,1.505c0.188,0.188 0.376,0.282 0.376,0.658C16.621,16.218 16.339,16.406 16.057,16.406zM17.091,13.773c-0.188,0 -0.376,-0.094 -0.564,-0.188c-1.599,-1.035 -3.857,-1.599 -6.302,-1.599c-1.223,0 -2.352,0.188 -3.198,0.376c-0.188,0.094 -0.282,0.094 -0.47,0.094c-0.47,0 -0.753,-0.376 -0.753,-0.753c0,-0.47 0.188,-0.658 0.658,-0.847c1.129,-0.282 2.258,-0.564 3.857,-0.564c2.634,0 5.079,0.658 7.149,1.881c0.282,0.188 0.47,0.47 0.47,0.753C17.844,13.396 17.562,13.773 17.091,13.773zM18.314,10.762c-0.188,0 -0.282,-0.094 -0.564,-0.188c-1.881,-1.035 -4.609,-1.693 -7.243,-1.693c-1.317,0 -2.728,0.188 -3.951,0.47c-0.188,0 -0.282,0.094 -0.564,0.094C5.428,9.54 5.051,9.069 5.051,8.505c0,-0.564 0.282,-0.847 0.658,-0.941C7.215,7.188 8.814,7 10.507,7c2.916,0 5.926,0.658 8.278,1.881c0.282,0.188 0.564,0.47 0.564,0.941C19.255,10.386 18.878,10.762 18.314,10.762z"/>
|
||||||
|
</vector>
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
<!-- General -->
|
<!-- General -->
|
||||||
<string name="go_back">رجوع</string>
|
<string name="go_back">رجوع</string>
|
||||||
<string name="cancel">إلغاء</string>
|
<string name="cancel">إلغاء</string>
|
||||||
|
<string name="confirm">تأكيد</string>
|
||||||
<string name="timeout">لقد انتهت مهلة الطلب، حاول مرة أخرى لاحقًا</string>
|
<string name="timeout">لقد انتهت مهلة الطلب، حاول مرة أخرى لاحقًا</string>
|
||||||
<string name="generic_error">لقد حدث خطأ، حاول مرة أخرى لاحقاً</string>
|
<string name="generic_error">لقد حدث خطأ، حاول مرة أخرى لاحقاً</string>
|
||||||
<string name="retry">أعد المحاولة</string>
|
<string name="retry">أعد المحاولة</string>
|
||||||
@@ -32,18 +33,10 @@
|
|||||||
<string name="theme_dark">ليلي</string>
|
<string name="theme_dark">ليلي</string>
|
||||||
<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_apps">تطبيقات القائمة البيضاء</string>
|
|
||||||
<string name="whitelist_desc">اختر التطبيقات التي سيستمع التطبيق إلى إشعاراتها</string>
|
|
||||||
<string name="search">بحث</string>
|
|
||||||
<string name="clear">مسح</string>
|
|
||||||
<string name="power_management">إدارة إستخدام الطافة</string>
|
|
||||||
<string name="keep_device_awake_title">أبق الجهاز مستيقظا</string>
|
|
||||||
<string name="keep_device_awake_desc">أبق الجهاز مستيقظا على صفحة كلمات الأغاني (يزيد من استخدام البطارية)</string>
|
|
||||||
<string name="screen_will_stay_on">ستبقى الشاشة على قيد التشغيل</string>
|
|
||||||
|
|
||||||
<!-- Lyrics -->
|
<!-- Lyrics -->
|
||||||
|
<string name="lyrics">كلمات الأغنية</string>
|
||||||
<string name="instrumental">هذه الأغنية موسيقية</string>
|
<string name="instrumental">هذه الأغنية موسيقية</string>
|
||||||
|
|
||||||
<!-- Notification -->
|
<!-- Notification -->
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<!-- General -->
|
<!-- General -->
|
||||||
<string name="go_back">Go Back</string>
|
<string name="go_back">Go Back</string>
|
||||||
<string name="cancel">Cancel</string>
|
<string name="cancel">Cancel</string>
|
||||||
|
<string name="confirm">Confirm</string>
|
||||||
<string name="timeout">Request has timed out, try again later</string>
|
<string name="timeout">Request has timed out, try again later</string>
|
||||||
<string name="generic_error">An error has occurred, try again later</string>
|
<string name="generic_error">An error has occurred, try again later</string>
|
||||||
<string name="retry">Retry</string>
|
<string name="retry">Retry</string>
|
||||||
@@ -31,18 +32,10 @@
|
|||||||
<string name="theme_dark">Dark</string>
|
<string name="theme_dark">Dark</string>
|
||||||
<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">Use System Language</string>
|
<string name="_default">Default</string>
|
||||||
<string name="listening">Listening</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>
|
|
||||||
<string name="power_management">Power Management</string>
|
|
||||||
<string name="keep_device_awake_title">Keep device awake</string>
|
|
||||||
<string name="keep_device_awake_desc">Keep device awake on lyrics page (Increases battery usage)</string>
|
|
||||||
<string name="screen_will_stay_on">Screen Will Stay On</string>
|
|
||||||
|
|
||||||
<!-- Lyrics -->
|
<!-- Lyrics -->
|
||||||
|
<string name="lyrics">Lyrics</string>
|
||||||
<string name="instrumental">This song is an instrumental</string>
|
<string name="instrumental">This song is an instrumental</string>
|
||||||
|
|
||||||
<!-- Notification -->
|
<!-- Notification -->
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
- A whitelist of apps is now required in order for the app to pick up on notifications from them
|
|
||||||
- Completely remove Spotify due to non-FOSS SDK
|
|
||||||
- Language settings (help with translations is much appreciated)
|
|
||||||
- Added a setting to keep the device awake when reading lyrics
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 294 KiB After Width: | Height: | Size: 145 KiB |
Reference in New Issue
Block a user