mirror of
https://github.com/IllusionMan1212/lyrics-grabbr.git
synced 2026-10-24 02:12:27 +00:00
feat: added setting to configure custom API endpoint
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
<application
|
<application
|
||||||
android:name=".LGApp"
|
android:name=".LGApp"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:localeConfig="@xml/locales_config"
|
android:localeConfig="@xml/locales_config"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.illusionman1212.lyricsgrabbr.data
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import com.illusionman1212.lyricsgrabbr.R
|
import com.illusionman1212.lyricsgrabbr.R
|
||||||
import com.illusionman1212.lyricsgrabbr.data.network.GET
|
import com.illusionman1212.lyricsgrabbr.data.network.GET
|
||||||
@@ -32,11 +33,9 @@ class HomeRepository(private val context: Context) {
|
|||||||
return pkgs.contains(packageName)
|
return pkgs.contains(packageName)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun makeSearchRequest(lContext: Context, song: String, artist: String): Pair<String?, Boolean> {
|
suspend fun makeSearchRequest(lContext: Context, baseUrl: String, song: String, artist: String): Pair<String?, Boolean> {
|
||||||
val builder = Uri.Builder()
|
val url = Uri.parse(baseUrl)
|
||||||
val url = builder.scheme("https")
|
.buildUpon()
|
||||||
.authority("api.illusionman1212.com")
|
|
||||||
.appendPath("lyrics")
|
|
||||||
.appendPath("search")
|
.appendPath("search")
|
||||||
.appendQueryParameter("q", "$song $artist")
|
.appendQueryParameter("q", "$song $artist")
|
||||||
.appendQueryParameter("disableFuzzy", "true")
|
.appendQueryParameter("disableFuzzy", "true")
|
||||||
@@ -76,7 +75,7 @@ class HomeRepository(private val context: Context) {
|
|||||||
return Pair(lContext.resources.getString(R.string.timeout), false)
|
return Pair(lContext.resources.getString(R.string.timeout), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Pair(lContext.resources.getString(R.string.generic_error), false)
|
return Pair(e.message, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+10
@@ -5,6 +5,7 @@ import androidx.datastore.preferences.core.Preferences
|
|||||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
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.stringPreferencesKey
|
||||||
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ data class SettingsPreferences(
|
|||||||
val whitelist: Set<String> = emptySet(),
|
val whitelist: Set<String> = emptySet(),
|
||||||
val keepScreenOn: Boolean = false,
|
val keepScreenOn: Boolean = false,
|
||||||
val whitelistPromptSeen: Boolean = false,
|
val whitelistPromptSeen: Boolean = false,
|
||||||
|
val geniURLbaseURL: String = "https://api.illusionman1212.com/lyrics",
|
||||||
)
|
)
|
||||||
|
|
||||||
class SettingsPreferencesRepository(
|
class SettingsPreferencesRepository(
|
||||||
@@ -29,6 +31,7 @@ class SettingsPreferencesRepository(
|
|||||||
private val WHITELIST = stringSetPreferencesKey("apps_whitelist")
|
private val WHITELIST = stringSetPreferencesKey("apps_whitelist")
|
||||||
private val KEEP_SCREEN_ON = booleanPreferencesKey("keep_screen_on")
|
private val KEEP_SCREEN_ON = booleanPreferencesKey("keep_screen_on")
|
||||||
private val WHITELIST_PROMPT_SEEN = booleanPreferencesKey("whitelist_prompt_seen")
|
private val WHITELIST_PROMPT_SEEN = booleanPreferencesKey("whitelist_prompt_seen")
|
||||||
|
private val GENIURL_BASE_URL = stringPreferencesKey("geniurl_base_url")
|
||||||
}
|
}
|
||||||
|
|
||||||
val preferences = dataStore.data.map { mapSettingsPreferences(it) }
|
val preferences = dataStore.data.map { mapSettingsPreferences(it) }
|
||||||
@@ -63,12 +66,19 @@ class SettingsPreferencesRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun setGeniURLBaseURL(url: String) {
|
||||||
|
dataStore.edit { prefs ->
|
||||||
|
prefs[GENIURL_BASE_URL] = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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[WHITELIST] ?: emptySet(),
|
||||||
prefs[KEEP_SCREEN_ON] ?: false,
|
prefs[KEEP_SCREEN_ON] ?: false,
|
||||||
prefs[WHITELIST_PROMPT_SEEN] ?: false,
|
prefs[WHITELIST_PROMPT_SEEN] ?: false,
|
||||||
|
prefs[GENIURL_BASE_URL] ?: "https://api.illusionman1212.com/lyrics",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.illusionman1212.lyricsgrabbr.ui.screens
|
package com.illusionman1212.lyricsgrabbr.ui.screens
|
||||||
|
|
||||||
|
import android.util.Patterns
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -19,6 +20,7 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
|||||||
import androidx.compose.material.icons.filled.Apps
|
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.filled.ScreenLockPortrait
|
||||||
|
import androidx.compose.material.icons.outlined.Cloud
|
||||||
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
|
||||||
@@ -27,6 +29,7 @@ import androidx.compose.material3.Scaffold
|
|||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -96,6 +99,7 @@ fun SettingsPage(
|
|||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Appearance(viewModel, uiState)
|
Appearance(viewModel, uiState)
|
||||||
|
geniURL(viewModel, uiState)
|
||||||
Listening(navigateToWhitelist)
|
Listening(navigateToWhitelist)
|
||||||
PowerManagement(viewModel, uiState)
|
PowerManagement(viewModel, uiState)
|
||||||
}
|
}
|
||||||
@@ -185,6 +189,63 @@ private fun PowerManagement(viewModel: SettingsViewModel, uiState: SettingsState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun geniURL(viewModel: SettingsViewModel, uiState: SettingsState) {
|
||||||
|
var dialogOpen by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
SettingGroup(title = "geniURL") {
|
||||||
|
DialogSetting(
|
||||||
|
title = stringResource(id = R.string.api_base_url),
|
||||||
|
value = uiState.geniURLbaseURL,
|
||||||
|
icon = Icons.Outlined.Cloud,
|
||||||
|
onClick = { dialogOpen = true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dialogOpen) {
|
||||||
|
BaseURLDialog(
|
||||||
|
uiState = uiState,
|
||||||
|
viewModel = viewModel,
|
||||||
|
onDismiss = { dialogOpen = false },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BaseURLDialog(uiState: SettingsState, viewModel: SettingsViewModel, onDismiss: () -> Unit) {
|
||||||
|
var value by remember { mutableStateOf(uiState.geniURLbaseURL) }
|
||||||
|
var isValidUrl by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
|
LGAlertDialog(
|
||||||
|
onDismiss = onDismiss,
|
||||||
|
title = stringResource(id = R.string.base_url),
|
||||||
|
buttons = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.setGeniURLBaseURL(value)
|
||||||
|
onDismiss()
|
||||||
|
},
|
||||||
|
enabled = isValidUrl
|
||||||
|
) {
|
||||||
|
Text(stringResource(id = R.string.confirm))
|
||||||
|
}
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(stringResource(id = R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
value = value,
|
||||||
|
isError = !isValidUrl,
|
||||||
|
onValueChange = { newValue ->
|
||||||
|
isValidUrl = Patterns.WEB_URL.matcher(newValue.lowercase()).matches() && newValue.startsWith("http", true)
|
||||||
|
value = newValue
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ThemeDialog(
|
fun ThemeDialog(
|
||||||
uiState: SettingsState,
|
uiState: SettingsState,
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import androidx.lifecycle.viewmodel.viewModelFactory
|
|||||||
import com.illusionman1212.lyricsgrabbr.LGApp
|
import com.illusionman1212.lyricsgrabbr.LGApp
|
||||||
import com.illusionman1212.lyricsgrabbr.NotificationEvent
|
import com.illusionman1212.lyricsgrabbr.NotificationEvent
|
||||||
import com.illusionman1212.lyricsgrabbr.data.HomeRepository
|
import com.illusionman1212.lyricsgrabbr.data.HomeRepository
|
||||||
|
import com.illusionman1212.lyricsgrabbr.data.SettingsPreferencesRepository
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -85,7 +87,7 @@ data class HomeState(
|
|||||||
val error: String? = null,
|
val error: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
class HomeViewModel(private val homeRepository: HomeRepository): ViewModel() {
|
class HomeViewModel(private val homeRepository: HomeRepository, private val settingsRepository: SettingsPreferencesRepository): ViewModel() {
|
||||||
private val _uiState = MutableStateFlow(HomeState())
|
private val _uiState = MutableStateFlow(HomeState())
|
||||||
val uiState = _uiState.asStateFlow()
|
val uiState = _uiState.asStateFlow()
|
||||||
private val json = Json {
|
private val json = Json {
|
||||||
@@ -107,7 +109,8 @@ class HomeViewModel(private val homeRepository: HomeRepository): ViewModel() {
|
|||||||
_uiState.update { HomeState(true, uiState.value.results, uiState.value.notification, null) }
|
_uiState.update { HomeState(true, uiState.value.results, uiState.value.notification, null) }
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val (res, isSuccess) = homeRepository.makeSearchRequest(context, song, artist)
|
val baseUrl = settingsRepository.preferences.first().geniURLbaseURL
|
||||||
|
val (res, isSuccess) = homeRepository.makeSearchRequest(context, baseUrl, song, artist)
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
val results = json.decodeFromString(SearchResultsResponse.serializer(), res!!).all.map {
|
val results = json.decodeFromString(SearchResultsResponse.serializer(), res!!).all.map {
|
||||||
SearchResult(
|
SearchResult(
|
||||||
@@ -136,7 +139,7 @@ class HomeViewModel(private val homeRepository: HomeRepository): ViewModel() {
|
|||||||
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)
|
||||||
HomeViewModel(application.homeRepository)
|
HomeViewModel(application.homeRepository, application.settingsPreferencesRepository)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ data class SettingsState(
|
|||||||
val applications: Map<String, AppInfo> = emptyMap(),
|
val applications: Map<String, AppInfo> = emptyMap(),
|
||||||
val whitelistSearchQuery: String = "",
|
val whitelistSearchQuery: String = "",
|
||||||
val keepScreenOn: Boolean = false,
|
val keepScreenOn: Boolean = false,
|
||||||
|
val geniURLbaseURL: String = "",
|
||||||
val whitelistPromptSeen: Boolean = true,
|
val whitelistPromptSeen: Boolean = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -55,6 +56,15 @@ class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesReposi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setGeniURLBaseURL(baseUrl: String) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
settingsPrefsRepo.setGeniURLBaseURL(baseUrl)
|
||||||
|
_uiState.update { state ->
|
||||||
|
state.copy(geniURLbaseURL = baseUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun toggleKeepScreenOn() {
|
fun toggleKeepScreenOn() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
settingsPrefsRepo.toggleKeepScreenOn()
|
settingsPrefsRepo.toggleKeepScreenOn()
|
||||||
@@ -165,6 +175,7 @@ class SettingsViewModel(private val settingsPrefsRepo: SettingsPreferencesReposi
|
|||||||
appTheme = it.appTheme,
|
appTheme = it.appTheme,
|
||||||
keepScreenOn = it.keepScreenOn,
|
keepScreenOn = it.keepScreenOn,
|
||||||
whitelistPromptSeen = it.whitelistPromptSeen,
|
whitelistPromptSeen = it.whitelistPromptSeen,
|
||||||
|
geniURLbaseURL = it.geniURLbaseURL,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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="internal_server_error">تأكد من تشغيل خادم geniURL الخاص بك إذا كنت تستضيف ذاتيًا أو اتصل بمضيفك.</string>
|
<string name="internal_server_error">تأكد من تشغيل خادم geniURL الخاص بك إذا كنت تستضيف ذاتيًا أو اتصل بمضيفك.</string>
|
||||||
@@ -47,6 +48,8 @@
|
|||||||
<string name="keep_device_awake_title">أبق الجهاز مستيقظا</string>
|
<string name="keep_device_awake_title">أبق الجهاز مستيقظا</string>
|
||||||
<string name="keep_device_awake_desc">أبق الجهاز مستيقظا على صفحة كلمات الأغاني (يزيد من استخدام البطارية)</string>
|
<string name="keep_device_awake_desc">أبق الجهاز مستيقظا على صفحة كلمات الأغاني (يزيد من استخدام البطارية)</string>
|
||||||
<string name="screen_will_stay_on">ستبقى الشاشة على قيد التشغيل</string>
|
<string name="screen_will_stay_on">ستبقى الشاشة على قيد التشغيل</string>
|
||||||
|
<string name="api_base_url">عنوان URL الأساسي لواجهة برمجة التطبيقات (API)</string>
|
||||||
|
<string name="base_url">عنوان URL الأساسي</string>
|
||||||
|
|
||||||
<!-- Lyrics -->
|
<!-- Lyrics -->
|
||||||
<string name="instrumental">هذه الأغنية موسيقية</string>
|
<string name="instrumental">هذه الأغنية موسيقية</string>
|
||||||
|
|||||||
@@ -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="internal_server_error">Internal server error. Make sure your geniURL server is running if self-hosting or contact your hoster.</string>
|
<string name="internal_server_error">Internal server error. Make sure your geniURL server is running if self-hosting or contact your hoster.</string>
|
||||||
@@ -46,6 +47,8 @@
|
|||||||
<string name="keep_device_awake_title">Keep device awake</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="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>
|
<string name="screen_will_stay_on">Screen Will Stay On</string>
|
||||||
|
<string name="api_base_url">API Base URL</string>
|
||||||
|
<string name="base_url">Base URL</string>
|
||||||
|
|
||||||
<!-- Lyrics -->
|
<!-- Lyrics -->
|
||||||
<string name="instrumental">This song is an instrumental</string>
|
<string name="instrumental">This song is an instrumental</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user