mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-21 16:54:33 +01:00
- User Configurable Spotify Creds Support.
- Crash & Visibility Fix for SettingsIcon. - Changed default Creds, thanks to spotDL.
This commit is contained in:
parent
76ef76e522
commit
3e865ee622
@ -3,15 +3,19 @@ package com.shabinder.common.uikit.screens
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
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.wrapContentWidth
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@ -21,8 +25,12 @@ import androidx.compose.material.RadioButton
|
||||
import androidx.compose.material.Switch
|
||||
import androidx.compose.material.SwitchDefaults
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.TextField
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Edit
|
||||
import androidx.compose.material.icons.rounded.Insights
|
||||
import androidx.compose.material.icons.rounded.ManageAccounts
|
||||
import androidx.compose.material.icons.rounded.MusicNote
|
||||
import androidx.compose.material.icons.rounded.SnippetFolder
|
||||
import androidx.compose.runtime.Composable
|
||||
@ -35,14 +43,20 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState
|
||||
import com.shabinder.common.models.Actions
|
||||
import com.shabinder.common.models.AudioQuality
|
||||
import com.shabinder.common.models.spotify.SpotifyCredentials
|
||||
import com.shabinder.common.preference.SpotiFlyerPreference
|
||||
import com.shabinder.common.translations.Strings
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerShapes
|
||||
import com.shabinder.common.uikit.configurations.SpotiFlyerTypography
|
||||
import com.shabinder.common.uikit.configurations.colorAccent
|
||||
import com.shabinder.common.uikit.configurations.colorOffWhite
|
||||
import com.shabinder.common.uikit.configurations.colorPrimary
|
||||
|
||||
@Composable
|
||||
fun SpotiFlyerPreferenceContent(component: SpotiFlyerPreference) {
|
||||
@ -110,7 +124,12 @@ fun SpotiFlyerPreferenceContent(component: SpotiFlyerPreference) {
|
||||
onClick = { component.selectNewDownloadDirectory() }
|
||||
)
|
||||
) {
|
||||
Icon(Icons.Rounded.SnippetFolder, Strings.setDownloadDirectory(), Modifier.size(32.dp), tint = Color(0xFFCCCCCC))
|
||||
Icon(
|
||||
Icons.Rounded.SnippetFolder,
|
||||
Strings.setDownloadDirectory(),
|
||||
Modifier.size(32.dp),
|
||||
tint = Color(0xFFCCCCCC)
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column {
|
||||
Text(
|
||||
@ -126,6 +145,92 @@ fun SpotiFlyerPreferenceContent(component: SpotiFlyerPreference) {
|
||||
|
||||
Spacer(Modifier.padding(top = 12.dp))
|
||||
|
||||
SettingsRow(
|
||||
icon = rememberVectorPainter(Icons.Rounded.ManageAccounts),
|
||||
title = Strings.spotifyCreds(),
|
||||
value = if (model.spotifyCredentials == SpotifyCredentials()) Strings.defaultString() else Strings.userSet(),
|
||||
contentEnd = {
|
||||
Spacer(Modifier.weight(1f))
|
||||
Icon(
|
||||
Icons.Rounded.Edit,
|
||||
"Edit",
|
||||
Modifier.padding(end = 8.dp).size(24.dp),
|
||||
tint = Color(0xFFCCCCCC)
|
||||
)
|
||||
}
|
||||
) { save ->
|
||||
Spacer(Modifier.padding(top = 8.dp))
|
||||
|
||||
var clientID by remember { mutableStateOf(model.spotifyCredentials.clientID) }
|
||||
var clientSecret by remember { mutableStateOf(model.spotifyCredentials.clientSecret) }
|
||||
TextField(
|
||||
value = clientID,
|
||||
onValueChange = { clientID = it.trim() },
|
||||
label = { Text(Strings.clientID()) }
|
||||
)
|
||||
Spacer(Modifier.padding(vertical = 4.dp))
|
||||
|
||||
TextField(
|
||||
value = clientSecret,
|
||||
onValueChange = { clientSecret = it.trim() },
|
||||
label = { Text(Strings.clientSecret()) }
|
||||
)
|
||||
|
||||
Spacer(Modifier.padding(vertical = 4.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
TextButton(
|
||||
onClick = {
|
||||
component.updateSpotifyCredentials(
|
||||
SpotifyCredentials(
|
||||
clientID,
|
||||
clientSecret
|
||||
)
|
||||
)
|
||||
Actions.instance.showPopUpMessage(Strings.requestAppRestart())
|
||||
save()
|
||||
},
|
||||
Modifier.padding(bottom = 8.dp, start = 8.dp, end = 8.dp).wrapContentWidth()
|
||||
.background(colorPrimary, shape = SpotiFlyerShapes.medium)
|
||||
.padding(horizontal = 4.dp),
|
||||
shape = SpotiFlyerShapes.small
|
||||
) {
|
||||
Text(
|
||||
Strings.save(),
|
||||
color = Color.Black,
|
||||
fontSize = 16.sp,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
|
||||
TextButton(
|
||||
onClick = {
|
||||
component.updateSpotifyCredentials(
|
||||
SpotifyCredentials()
|
||||
)
|
||||
Actions.instance.showPopUpMessage(Strings.requestAppRestart())
|
||||
save()
|
||||
},
|
||||
Modifier.padding(bottom = 8.dp, start = 8.dp, end = 8.dp).wrapContentWidth()
|
||||
.background(colorPrimary, shape = SpotiFlyerShapes.medium)
|
||||
.padding(horizontal = 4.dp),
|
||||
shape = SpotiFlyerShapes.small
|
||||
) {
|
||||
Text(
|
||||
Strings.reset(),
|
||||
color = Color.Black,
|
||||
fontSize = 16.sp,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.padding(top = 4.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.clickable(
|
||||
@ -134,7 +239,11 @@ fun SpotiFlyerPreferenceContent(component: SpotiFlyerPreference) {
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
@Suppress("DuplicatedCode")
|
||||
Icon(Icons.Rounded.Insights, Strings.analytics() + Strings.status(), Modifier.size(32.dp))
|
||||
Icon(
|
||||
Icons.Rounded.Insights,
|
||||
Strings.analytics() + Strings.status(),
|
||||
Modifier.size(32.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.padding(start = 16.dp))
|
||||
Column(
|
||||
Modifier.weight(1f)
|
||||
@ -166,6 +275,7 @@ fun SettingsRow(
|
||||
icon: Painter,
|
||||
title: String,
|
||||
value: String,
|
||||
contentEnd: @Composable RowScope.() -> Unit = {},
|
||||
editContent: @Composable ColumnScope.(() -> Unit) -> Unit
|
||||
) {
|
||||
|
||||
@ -189,6 +299,7 @@ fun SettingsRow(
|
||||
style = SpotiFlyerTypography.subtitle2
|
||||
)
|
||||
}
|
||||
contentEnd()
|
||||
}
|
||||
AnimatedVisibility(isEditMode) {
|
||||
Column {
|
||||
|
@ -141,6 +141,7 @@ fun MainScreen(
|
||||
onBackPressed = callBacks::popBackToHomeScreen,
|
||||
openPreferenceScreen = callBacks::openPreferenceScreen,
|
||||
isBackButtonVisible = activeComponent.value.activeChild.instance !is Child.Main,
|
||||
isSettingsIconVisible = activeComponent.value.activeChild.instance is Child.Main,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(Modifier.padding(top = topPadding))
|
||||
@ -164,6 +165,7 @@ fun AppBar(
|
||||
onBackPressed: () -> Unit,
|
||||
openPreferenceScreen: () -> Unit,
|
||||
isBackButtonVisible: Boolean,
|
||||
isSettingsIconVisible: Boolean,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
TopAppBar(
|
||||
@ -193,10 +195,12 @@ fun AppBar(
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = { openPreferenceScreen() }
|
||||
) {
|
||||
Icon(Icons.Filled.Settings, Strings.preferences(), tint = Color.Gray)
|
||||
AnimatedVisibility(isSettingsIconVisible) {
|
||||
IconButton(
|
||||
onClick = { openPreferenceScreen() }
|
||||
) {
|
||||
Icon(Icons.Filled.Settings, Strings.preferences(), tint = Color.Gray)
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
|
@ -1,8 +1,13 @@
|
||||
package com.shabinder.common.core_components.preference_manager
|
||||
|
||||
import co.touchlab.stately.annotation.Throws
|
||||
import com.russhwolf.settings.Settings
|
||||
import com.shabinder.common.core_components.analytics.AnalyticsManager
|
||||
import com.shabinder.common.models.AudioQuality
|
||||
import com.shabinder.common.models.spotify.SpotifyCredentials
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlin.native.concurrent.ThreadLocal
|
||||
|
||||
class PreferenceManager(
|
||||
settings: Settings,
|
||||
@ -14,6 +19,14 @@ class PreferenceManager(
|
||||
const val FIRST_LAUNCH = "firstLaunch"
|
||||
const val DONATION_INTERVAL = "donationInterval"
|
||||
const val PREFERRED_AUDIO_QUALITY = "preferredAudioQuality"
|
||||
|
||||
@Suppress("VARIABLE_IN_SINGLETON_WITHOUT_THREAD_LOCAL")
|
||||
lateinit var instance: PreferenceManager
|
||||
private set
|
||||
}
|
||||
|
||||
init {
|
||||
instance = this
|
||||
}
|
||||
|
||||
lateinit var analyticsManager: AnalyticsManager
|
||||
@ -35,6 +48,11 @@ class PreferenceManager(
|
||||
val audioQuality get() = AudioQuality.getQuality(getStringOrNull(PREFERRED_AUDIO_QUALITY) ?: "320")
|
||||
fun setPreferredAudioQuality(quality: AudioQuality) = putString(PREFERRED_AUDIO_QUALITY, quality.kbps)
|
||||
|
||||
val spotifyCredentials: SpotifyCredentials get() = getStringOrNull("spotifyCredentials")?.let {
|
||||
Json.decodeFromString(it)
|
||||
} ?: SpotifyCredentials()
|
||||
fun setSpotifyCredentials(credentials: SpotifyCredentials) = putString("spotifyCredentials", Json.encodeToString(SpotifyCredentials.serializer(), credentials))
|
||||
|
||||
/* OFFSET FOR WHEN TO ASK FOR SUPPORT */
|
||||
val getDonationOffset: Int get() = (getIntOrNull(DONATION_INTERVAL) ?: 3).also {
|
||||
// Min. Donation Asking Interval is `3`
|
||||
|
@ -0,0 +1,9 @@
|
||||
package com.shabinder.common.models.spotify
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SpotifyCredentials(
|
||||
val clientID: String = "5f573c9620494bae87890c0f08a60293",
|
||||
val clientSecret: String = "212476d9b0f3472eaa762d90b19b0ba8",
|
||||
)
|
@ -26,6 +26,7 @@ import com.shabinder.common.core_components.preference_manager.PreferenceManager
|
||||
import com.shabinder.common.models.Actions
|
||||
import com.shabinder.common.models.AudioQuality
|
||||
import com.shabinder.common.models.Consumer
|
||||
import com.shabinder.common.models.spotify.SpotifyCredentials
|
||||
import com.shabinder.common.preference.integration.SpotiFlyerPreferenceImpl
|
||||
|
||||
interface SpotiFlyerPreference {
|
||||
@ -40,6 +41,8 @@ interface SpotiFlyerPreference {
|
||||
|
||||
fun setPreferredQuality(quality: AudioQuality)
|
||||
|
||||
fun updateSpotifyCredentials(credentials: SpotifyCredentials)
|
||||
|
||||
suspend fun loadImage(url: String): Picture
|
||||
|
||||
interface Dependencies {
|
||||
@ -61,7 +64,8 @@ interface SpotiFlyerPreference {
|
||||
data class State(
|
||||
val preferredQuality: AudioQuality = AudioQuality.KBPS320,
|
||||
val downloadPath: String = "",
|
||||
val isAnalyticsEnabled: Boolean = false
|
||||
val isAnalyticsEnabled: Boolean = false,
|
||||
val spotifyCredentials: SpotifyCredentials = SpotifyCredentials()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ import com.shabinder.common.caching.Cache
|
||||
import com.shabinder.common.core_components.picture.Picture
|
||||
import com.shabinder.common.core_components.utils.asValue
|
||||
import com.shabinder.common.models.AudioQuality
|
||||
import com.shabinder.common.models.spotify.SpotifyCredentials
|
||||
import com.shabinder.common.preference.SpotiFlyerPreference
|
||||
import com.shabinder.common.preference.SpotiFlyerPreference.Dependencies
|
||||
import com.shabinder.common.preference.SpotiFlyerPreference.State
|
||||
@ -67,6 +68,10 @@ internal class SpotiFlyerPreferenceImpl(
|
||||
store.accept(Intent.SetPreferredAudioQuality(quality))
|
||||
}
|
||||
|
||||
override fun updateSpotifyCredentials(credentials: SpotifyCredentials) {
|
||||
store.accept(Intent.UpdateSpotifyCredentials(credentials))
|
||||
}
|
||||
|
||||
override suspend fun loadImage(url: String): Picture {
|
||||
return cache.get(url) {
|
||||
fileManager.loadImage(url, 150, 150)
|
||||
|
@ -18,6 +18,7 @@ package com.shabinder.common.preference.store
|
||||
|
||||
import com.arkivanov.mvikotlin.core.store.Store
|
||||
import com.shabinder.common.models.AudioQuality
|
||||
import com.shabinder.common.models.spotify.SpotifyCredentials
|
||||
import com.shabinder.common.preference.SpotiFlyerPreference
|
||||
|
||||
internal interface SpotiFlyerPreferenceStore : Store<SpotiFlyerPreferenceStore.Intent, SpotiFlyerPreference.State, Nothing> {
|
||||
@ -26,6 +27,7 @@ internal interface SpotiFlyerPreferenceStore : Store<SpotiFlyerPreferenceStore.I
|
||||
data class ToggleAnalytics(val enabled: Boolean) : Intent()
|
||||
data class SetDownloadDirectory(val path: String) : Intent()
|
||||
data class SetPreferredAudioQuality(val quality: AudioQuality) : Intent()
|
||||
data class UpdateSpotifyCredentials(val credentials: SpotifyCredentials) : Intent()
|
||||
object GiveDonation : Intent()
|
||||
object ShareApp : Intent()
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import com.arkivanov.mvikotlin.core.store.Store
|
||||
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
|
||||
import com.shabinder.common.models.AudioQuality
|
||||
import com.shabinder.common.models.Actions
|
||||
import com.shabinder.common.models.spotify.SpotifyCredentials
|
||||
import com.shabinder.common.preference.SpotiFlyerPreference
|
||||
import com.shabinder.common.preference.SpotiFlyerPreference.State
|
||||
import com.shabinder.common.preference.store.SpotiFlyerPreferenceStore.Intent
|
||||
@ -45,12 +46,14 @@ internal class SpotiFlyerPreferenceStoreProvider(
|
||||
data class AnalyticsToggled(val isEnabled: Boolean) : Result()
|
||||
data class DownloadPathSet(val path: String) : Result()
|
||||
data class PreferredAudioQualityChanged(val quality: AudioQuality) : Result()
|
||||
data class SpotifyCredentialsUpdated(val spotifyCredentials: SpotifyCredentials) : Result()
|
||||
}
|
||||
|
||||
private inner class ExecutorImpl : SuspendExecutor<Intent, Unit, State, Result, Nothing>() {
|
||||
override suspend fun executeAction(action: Unit, getState: () -> State) {
|
||||
dispatch(Result.AnalyticsToggled(preferenceManager.isAnalyticsEnabled))
|
||||
dispatch(Result.PreferredAudioQualityChanged(preferenceManager.audioQuality))
|
||||
dispatch(Result.SpotifyCredentialsUpdated(preferenceManager.spotifyCredentials))
|
||||
dispatch(Result.DownloadPathSet(fileManager.defaultDir()))
|
||||
}
|
||||
|
||||
@ -71,6 +74,11 @@ internal class SpotiFlyerPreferenceStoreProvider(
|
||||
dispatch(Result.PreferredAudioQualityChanged(intent.quality))
|
||||
preferenceManager.setPreferredAudioQuality(intent.quality)
|
||||
}
|
||||
|
||||
is Intent.UpdateSpotifyCredentials -> {
|
||||
dispatch(Result.SpotifyCredentialsUpdated(intent.credentials))
|
||||
preferenceManager.setSpotifyCredentials(intent.credentials)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -81,6 +89,7 @@ internal class SpotiFlyerPreferenceStoreProvider(
|
||||
is Result.AnalyticsToggled -> copy(isAnalyticsEnabled = result.isEnabled)
|
||||
is Result.DownloadPathSet -> copy(downloadPath = result.path)
|
||||
is Result.PreferredAudioQualityChanged -> copy(preferredQuality = result.quality)
|
||||
is Result.SpotifyCredentialsUpdated -> copy(spotifyCredentials = result.spotifyCredentials)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package com.shabinder.common.providers.spotify.requests
|
||||
|
||||
import com.shabinder.common.core_components.preference_manager.PreferenceManager
|
||||
import com.shabinder.common.models.SpotiFlyerException
|
||||
import com.shabinder.common.models.event.coroutines.SuspendableEvent
|
||||
import com.shabinder.common.models.Actions
|
||||
@ -45,8 +46,7 @@ suspend fun authenticateSpotify(): SuspendableEvent<TokenData, Throwable> = Susp
|
||||
@SharedImmutable
|
||||
private val spotifyAuthClient by lazy {
|
||||
HttpClient {
|
||||
val clientId = "694d8bf4f6ec420fa66ea7fb4c68f89d"
|
||||
val clientSecret = "02ca2d4021a7452dae2328b47a6e8fe8"
|
||||
val (clientId, clientSecret) = PreferenceManager.instance.spotifyCredentials
|
||||
|
||||
install(Auth) {
|
||||
basic {
|
||||
|
@ -40,6 +40,14 @@ checkInternetConnection = Please check your network connection.
|
||||
grantPermissions = Grant permissions
|
||||
requiredPermissions = Required permissions:
|
||||
storagePermission = Storage permissions.
|
||||
spotifyCreds = Spotify credentials.
|
||||
clientID = Client ID
|
||||
clientSecret = Client Secret
|
||||
defaultString = Default
|
||||
userSet = UserSet
|
||||
save = Save
|
||||
reset = Reset
|
||||
requestAppRestart = You need to restart the app for changes to take effect
|
||||
storagePermissionReason = To download your favourite songs to this device.
|
||||
backgroundRunning = Background running.
|
||||
backgroundRunningReason = To download all songs in background without any system interruptions.
|
||||
|
Loading…
Reference in New Issue
Block a user