Merge branch 'Shabinder:main' into main

This commit is contained in:
j-romchain 2022-11-25 12:02:51 -05:00 committed by GitHub
commit bbb58a3676
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 207 additions and 31 deletions

View File

@ -33,7 +33,7 @@ jobs:
with: with:
draft: true draft: true
allowUpdates: true allowUpdates: true
tag: "v3.6.1" tag: "v3.6.3"
artifacts: "desktop/build/compose/jars/*.jar,desktop/build/compose/binaries/main/*/*" artifacts: "desktop/build/compose/jars/*.jar,desktop/build/compose/binaries/main/*/*"
token: ${{ secrets.GH_TOKEN }} token: ${{ secrets.GH_TOKEN }}
@ -83,7 +83,7 @@ jobs:
with: with:
draft: true draft: true
allowUpdates: true allowUpdates: true
tag: "v3.6.1" tag: "v3.6.3"
artifacts: "desktop/build/compose/jars/*.jar,desktop/build/compose/binaries/main/*/*,android/build/outputs/apk/release/*signed.apk" artifacts: "desktop/build/compose/jars/*.jar,desktop/build/compose/binaries/main/*/*,android/build/outputs/apk/release/*signed.apk"
token: ${{ secrets.GH_TOKEN }} token: ${{ secrets.GH_TOKEN }}
@ -117,6 +117,6 @@ jobs:
with: with:
draft: true draft: true
allowUpdates: true allowUpdates: true
tag: "v3.6.1" tag: "v3.6.3"
artifacts: "desktop/build/compose/jars/*.jar,desktop/build/compose/binaries/main/*/*" artifacts: "desktop/build/compose/jars/*.jar,desktop/build/compose/binaries/main/*/*"
token: ${{ secrets.GH_TOKEN }} token: ${{ secrets.GH_TOKEN }}

View File

@ -1,17 +1,17 @@
### Contibuting Translations: ### Contributing Translations:
- **Fork** this repo (Button at top Right Corner) - **Fork** this repo (Button at top Right Corner)
- Create **Strings_{language-code}.properties.xml** in **/translations/** - Create **`Strings_{language-code}.properties.xml`** in **`/translations/`**
- Refer **Default File** with English **(en)** = [Strings_en.properties](https://github.com/Shabinder/SpotiFlyer/blob/main/translations/Strings_en.properties) - Refer to the **Default file** - *English **(en)*** => [`Strings_en.properties`](https://github.com/Shabinder/SpotiFlyer/blob/main/translations/Strings_en.properties)
- Like if you want to translate app to **French** , create **Strings_fr.properties** - For example, if you want to translate the app to **French** , create **`Strings_fr.properties`**
- **Copy** all Key-Value pairs from **Strings_en.properties -> Strings_fr.properties and Translate all Values.** - **Copy** all Key-Value pairs from **`Strings_en.properties`** to your newly created **`Strings_fr.properties`** and Translate all Values.
- **Commit** your Changes to your Fork and then **Create a Pull Request** to this Repo. - **Commit** your Changes to your Fork and then **Create a Pull Request** to this Repo.
- **Sit Back and Relax** and **kudos** for your contibution 👍 , many people will appreciate your effort. 😄 - **Sit Back and Relax**... and **kudos** for your contibution! Your effort and contribution will benefit lots of people. 🙏😊
If Need any Help with Anything create an issue asking about for same. If You Need any Help with Anything, just create an issue asking about the same.

View File

@ -13,7 +13,7 @@ Supports- Playlist, Albums, Artists(currently only on spotify), Tracks. _(If You
**Currently running on:** **Currently running on:**
- [Android (Jetpack Compose)](https://github.com/Shabinder/SpotiFlyer#-install) - [Android (Jetpack Compose)](https://github.com/Shabinder/SpotiFlyer#-install)
- [Desktop (Compose for Desktop) <sup>β</sup>](https://github.com/Shabinder/SpotiFlyer#-install) - [Desktop (Compose for Desktop) <sup>β</sup>](https://github.com/Shabinder/SpotiFlyer#-install)
- [Web (Kotlin/JS + React Wrapper) <sup>β</sup>](https://spotiflyer.ml/web/) - [Web (Kotlin/JS + React Wrapper) <sup>β</sup>](https://shabinder.github.io/SpotiFlyer/)
- [_(WIP)_ IOS SOON (SWIFTUI)](https://github.com/Shabinder/spotiflyer-ios) - [_(WIP)_ IOS SOON (SWIFTUI)](https://github.com/Shabinder/spotiflyer-ios)
<!--[![Build Status](https://github.com/Shabinder/SpotiFlyer/blob/master/app/build_passing.svg)](https://github.com/Shabinder/SpotiFlyer/releases) <!--[![Build Status](https://github.com/Shabinder/SpotiFlyer/blob/master/app/build_passing.svg)](https://github.com/Shabinder/SpotiFlyer/releases)
@ -50,12 +50,12 @@ SpotiFlyer is an **App**(Written in **Kotlin**), which **aims** to work as:
| Platform | Download | Status | | Platform | Download | Status |
|----------|----------|--------| |----------|----------|--------|
| Android |[![Download Button](https://img.shields.io/github/v/release/Shabinder/SpotiFlyer?color=7885FF&label=Android-Apk&logo=android&style=for-the-badge)](https://github.com/Shabinder/SpotiFlyer/releases/download/v3.6.1/SpotiFlyer-3.6.1.apk)| ✅ Stable | | Android |[![Download Button](https://img.shields.io/github/v/release/Shabinder/SpotiFlyer?color=7885FF&label=Android-Apk&logo=android&style=for-the-badge)](https://github.com/Shabinder/SpotiFlyer/releases/download/v3.6.3/SpotiFlyer-3.6.3.apk)| ✅ Stable |
| Windows |[![Download Button](https://img.shields.io/github/v/release/Shabinder/SpotiFlyer?color=00A8E8&label=Windows-msi&logo=windows&style=for-the-badge)](https://github.com/Shabinder/SpotiFlyer/releases/download/v3.6.1/SpotiFlyer-3.6.1.msi)| ✅ Stable | | Windows |[![Download Button](https://img.shields.io/github/v/release/Shabinder/SpotiFlyer?color=00A8E8&label=Windows-msi&logo=windows&style=for-the-badge)](https://github.com/Shabinder/SpotiFlyer/releases/download/v3.6.3/SpotiFlyer-3.6.3.msi)| ✅ Stable |
| Windows-JAR |[![Download Button](https://img.shields.io/github/v/release/Shabinder/SpotiFlyer?color=00719c&label=Windows-jar&logo=windows&style=for-the-badge)](https://github.com/Shabinder/SpotiFlyer/releases/download/v3.6.1/SpotiFlyer-windows-x64-3.6.1.jar)| ✅ Stable | | Windows-JAR |[![Download Button](https://img.shields.io/github/v/release/Shabinder/SpotiFlyer?color=00719c&label=Windows-jar&logo=windows&style=for-the-badge)](https://github.com/Shabinder/SpotiFlyer/releases/download/v3.6.3/SpotiFlyer-windows-x64-3.6.3.jar)| ✅ Stable |
| MacOS-JAR |[![Download Button](https://img.shields.io/github/v/release/Shabinder/SpotiFlyer?color=5F85CE&label=MacOS-jar&logo=apple&style=for-the-badge)](https://github.com/Shabinder/SpotiFlyer/releases/download/v3.6.1/SpotiFlyer-macos-x64-3.6.1.jar) | ✅ Stable | | MacOS-JAR |[![Download Button](https://img.shields.io/github/v/release/Shabinder/SpotiFlyer?color=5F85CE&label=MacOS-jar&logo=apple&style=for-the-badge)](https://github.com/Shabinder/SpotiFlyer/releases/download/v3.6.3/SpotiFlyer-macos-x64-3.6.3.jar) | ✅ Stable |
| Linux-DEB |[![Download Button](https://img.shields.io/github/v/release/Shabinder/SpotiFlyer?color=D0074E&label=Linux-deb&logo=debian&style=for-the-badge)](https://github.com/Shabinder/SpotiFlyer/releases/download/v3.6.1/spotiflyer_3.6.1-1_amd64.deb)| ✅ Stable | | Linux-DEB |[![Download Button](https://img.shields.io/github/v/release/Shabinder/SpotiFlyer?color=D0074E&label=Linux-deb&logo=debian&style=for-the-badge)](https://github.com/Shabinder/SpotiFlyer/releases/download/v3.6.3/spotiflyer_3.6.3-1_amd64.deb)| ✅ Stable |
| Linux-JAR |[![Download Button](https://img.shields.io/github/v/release/Shabinder/SpotiFlyer?color=EBA201&label=Linux-jar&logo=linux&style=for-the-badge)](https://github.com/Shabinder/SpotiFlyer/releases/download/v3.6.1/SpotiFlyer-linux-x64-3.6.1.jar)| ✅ Stable | | Linux-JAR |[![Download Button](https://img.shields.io/github/v/release/Shabinder/SpotiFlyer?color=EBA201&label=Linux-jar&logo=linux&style=for-the-badge)](https://github.com/Shabinder/SpotiFlyer/releases/download/v3.6.3/SpotiFlyer-linux-x64-3.6.3.jar)| ✅ Stable |
| Web |[![Download Button](https://img.shields.io/github/v/release/Shabinder/SpotiFlyer?color=FF7139&label=SpotiFlyer&logo=firefox&style=for-the-badge)](https://shabinder.github.io/SpotiFlyer/) | ⚠️ Beta | | Web |[![Download Button](https://img.shields.io/github/v/release/Shabinder/SpotiFlyer?color=FF7139&label=SpotiFlyer&logo=firefox&style=for-the-badge)](https://shabinder.github.io/SpotiFlyer/) | ⚠️ Beta |
- To run the `jar` version, you need JAVA installed. - To run the `jar` version, you need JAVA installed.

View File

@ -26,9 +26,9 @@ import org.gradle.kotlin.dsl.getByType
object Versions { object Versions {
// App's Version (To be bumped at each update) // App's Version (To be bumped at each update)
const val versionName = "3.6.1" const val versionName = "3.6.3"
const val versionCode = 29 const val versionCode = 31
// Android // Android
const val minSdkVersion = 21 const val minSdkVersion = 21

View File

@ -3,15 +3,19 @@ package com.shabinder.common.uikit.screens
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
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.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
@ -21,8 +25,12 @@ import androidx.compose.material.RadioButton
import androidx.compose.material.Switch import androidx.compose.material.Switch
import androidx.compose.material.SwitchDefaults import androidx.compose.material.SwitchDefaults
import androidx.compose.material.Text 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.Icons
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.Insights 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.MusicNote
import androidx.compose.material.icons.rounded.SnippetFolder import androidx.compose.material.icons.rounded.SnippetFolder
import androidx.compose.runtime.Composable 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.Color
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.rememberVectorPainter 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.dp
import androidx.compose.ui.unit.sp
import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState 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.AudioQuality
import com.shabinder.common.models.spotify.SpotifyCredentials
import com.shabinder.common.preference.SpotiFlyerPreference import com.shabinder.common.preference.SpotiFlyerPreference
import com.shabinder.common.translations.Strings 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.SpotiFlyerTypography
import com.shabinder.common.uikit.configurations.colorAccent import com.shabinder.common.uikit.configurations.colorAccent
import com.shabinder.common.uikit.configurations.colorOffWhite import com.shabinder.common.uikit.configurations.colorOffWhite
import com.shabinder.common.uikit.configurations.colorPrimary
@Composable @Composable
fun SpotiFlyerPreferenceContent(component: SpotiFlyerPreference) { fun SpotiFlyerPreferenceContent(component: SpotiFlyerPreference) {
@ -110,7 +124,12 @@ fun SpotiFlyerPreferenceContent(component: SpotiFlyerPreference) {
onClick = { component.selectNewDownloadDirectory() } 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)) Spacer(modifier = Modifier.padding(start = 16.dp))
Column { Column {
Text( Text(
@ -126,6 +145,94 @@ fun SpotiFlyerPreferenceContent(component: SpotiFlyerPreference) {
Spacer(Modifier.padding(top = 12.dp)) 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()) },
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.padding(vertical = 4.dp))
TextField(
value = clientSecret,
onValueChange = { clientSecret = it.trim() },
label = { Text(Strings.clientSecret()) },
modifier = Modifier.fillMaxWidth()
)
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( Row(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
.clickable( .clickable(
@ -134,7 +241,11 @@ fun SpotiFlyerPreferenceContent(component: SpotiFlyerPreference) {
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@Suppress("DuplicatedCode") @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)) Spacer(modifier = Modifier.padding(start = 16.dp))
Column( Column(
Modifier.weight(1f) Modifier.weight(1f)
@ -166,6 +277,7 @@ fun SettingsRow(
icon: Painter, icon: Painter,
title: String, title: String,
value: String, value: String,
contentEnd: @Composable RowScope.() -> Unit = {},
editContent: @Composable ColumnScope.(() -> Unit) -> Unit editContent: @Composable ColumnScope.(() -> Unit) -> Unit
) { ) {
@ -189,6 +301,7 @@ fun SettingsRow(
style = SpotiFlyerTypography.subtitle2 style = SpotiFlyerTypography.subtitle2
) )
} }
contentEnd()
} }
AnimatedVisibility(isEditMode) { AnimatedVisibility(isEditMode) {
Column { Column {

View File

@ -141,6 +141,7 @@ fun MainScreen(
onBackPressed = callBacks::popBackToHomeScreen, onBackPressed = callBacks::popBackToHomeScreen,
openPreferenceScreen = callBacks::openPreferenceScreen, openPreferenceScreen = callBacks::openPreferenceScreen,
isBackButtonVisible = activeComponent.value.activeChild.instance !is Child.Main, isBackButtonVisible = activeComponent.value.activeChild.instance !is Child.Main,
isSettingsIconVisible = activeComponent.value.activeChild.instance is Child.Main,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
Spacer(Modifier.padding(top = topPadding)) Spacer(Modifier.padding(top = topPadding))
@ -164,6 +165,7 @@ fun AppBar(
onBackPressed: () -> Unit, onBackPressed: () -> Unit,
openPreferenceScreen: () -> Unit, openPreferenceScreen: () -> Unit,
isBackButtonVisible: Boolean, isBackButtonVisible: Boolean,
isSettingsIconVisible: Boolean,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
TopAppBar( TopAppBar(
@ -193,11 +195,13 @@ fun AppBar(
} }
}, },
actions = { actions = {
AnimatedVisibility(isSettingsIconVisible) {
IconButton( IconButton(
onClick = { openPreferenceScreen() } onClick = { openPreferenceScreen() }
) { ) {
Icon(Icons.Filled.Settings, Strings.preferences(), tint = Color.Gray) Icon(Icons.Filled.Settings, Strings.preferences(), tint = Color.Gray)
} }
}
}, },
modifier = modifier, modifier = modifier,
elevation = 0.dp elevation = 0.dp

View File

@ -1,8 +1,13 @@
package com.shabinder.common.core_components.preference_manager package com.shabinder.common.core_components.preference_manager
import co.touchlab.stately.annotation.Throws
import com.russhwolf.settings.Settings import com.russhwolf.settings.Settings
import com.shabinder.common.core_components.analytics.AnalyticsManager import com.shabinder.common.core_components.analytics.AnalyticsManager
import com.shabinder.common.models.AudioQuality 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( class PreferenceManager(
settings: Settings, settings: Settings,
@ -14,6 +19,14 @@ class PreferenceManager(
const val FIRST_LAUNCH = "firstLaunch" const val FIRST_LAUNCH = "firstLaunch"
const val DONATION_INTERVAL = "donationInterval" const val DONATION_INTERVAL = "donationInterval"
const val PREFERRED_AUDIO_QUALITY = "preferredAudioQuality" 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 lateinit var analyticsManager: AnalyticsManager
@ -35,6 +48,11 @@ class PreferenceManager(
val audioQuality get() = AudioQuality.getQuality(getStringOrNull(PREFERRED_AUDIO_QUALITY) ?: "320") val audioQuality get() = AudioQuality.getQuality(getStringOrNull(PREFERRED_AUDIO_QUALITY) ?: "320")
fun setPreferredAudioQuality(quality: AudioQuality) = putString(PREFERRED_AUDIO_QUALITY, quality.kbps) 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 */ /* OFFSET FOR WHEN TO ASK FOR SUPPORT */
val getDonationOffset: Int get() = (getIntOrNull(DONATION_INTERVAL) ?: 3).also { val getDonationOffset: Int get() = (getIntOrNull(DONATION_INTERVAL) ?: 3).also {
// Min. Donation Asking Interval is `3` // Min. Donation Asking Interval is `3`

View File

@ -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",
)

View File

@ -26,6 +26,7 @@ import com.shabinder.common.core_components.preference_manager.PreferenceManager
import com.shabinder.common.models.Actions import com.shabinder.common.models.Actions
import com.shabinder.common.models.AudioQuality import com.shabinder.common.models.AudioQuality
import com.shabinder.common.models.Consumer import com.shabinder.common.models.Consumer
import com.shabinder.common.models.spotify.SpotifyCredentials
import com.shabinder.common.preference.integration.SpotiFlyerPreferenceImpl import com.shabinder.common.preference.integration.SpotiFlyerPreferenceImpl
interface SpotiFlyerPreference { interface SpotiFlyerPreference {
@ -40,6 +41,8 @@ interface SpotiFlyerPreference {
fun setPreferredQuality(quality: AudioQuality) fun setPreferredQuality(quality: AudioQuality)
fun updateSpotifyCredentials(credentials: SpotifyCredentials)
suspend fun loadImage(url: String): Picture suspend fun loadImage(url: String): Picture
interface Dependencies { interface Dependencies {
@ -61,7 +64,8 @@ interface SpotiFlyerPreference {
data class State( data class State(
val preferredQuality: AudioQuality = AudioQuality.KBPS320, val preferredQuality: AudioQuality = AudioQuality.KBPS320,
val downloadPath: String = "", val downloadPath: String = "",
val isAnalyticsEnabled: Boolean = false val isAnalyticsEnabled: Boolean = false,
val spotifyCredentials: SpotifyCredentials = SpotifyCredentials()
) )
} }

View File

@ -23,6 +23,7 @@ import com.shabinder.common.caching.Cache
import com.shabinder.common.core_components.picture.Picture import com.shabinder.common.core_components.picture.Picture
import com.shabinder.common.core_components.utils.asValue import com.shabinder.common.core_components.utils.asValue
import com.shabinder.common.models.AudioQuality 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
import com.shabinder.common.preference.SpotiFlyerPreference.Dependencies import com.shabinder.common.preference.SpotiFlyerPreference.Dependencies
import com.shabinder.common.preference.SpotiFlyerPreference.State import com.shabinder.common.preference.SpotiFlyerPreference.State
@ -67,6 +68,10 @@ internal class SpotiFlyerPreferenceImpl(
store.accept(Intent.SetPreferredAudioQuality(quality)) store.accept(Intent.SetPreferredAudioQuality(quality))
} }
override fun updateSpotifyCredentials(credentials: SpotifyCredentials) {
store.accept(Intent.UpdateSpotifyCredentials(credentials))
}
override suspend fun loadImage(url: String): Picture { override suspend fun loadImage(url: String): Picture {
return cache.get(url) { return cache.get(url) {
fileManager.loadImage(url, 150, 150) fileManager.loadImage(url, 150, 150)

View File

@ -18,6 +18,7 @@ package com.shabinder.common.preference.store
import com.arkivanov.mvikotlin.core.store.Store import com.arkivanov.mvikotlin.core.store.Store
import com.shabinder.common.models.AudioQuality 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
internal interface SpotiFlyerPreferenceStore : Store<SpotiFlyerPreferenceStore.Intent, SpotiFlyerPreference.State, Nothing> { 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 ToggleAnalytics(val enabled: Boolean) : Intent()
data class SetDownloadDirectory(val path: String) : Intent() data class SetDownloadDirectory(val path: String) : Intent()
data class SetPreferredAudioQuality(val quality: AudioQuality) : Intent() data class SetPreferredAudioQuality(val quality: AudioQuality) : Intent()
data class UpdateSpotifyCredentials(val credentials: SpotifyCredentials) : Intent()
object GiveDonation : Intent() object GiveDonation : Intent()
object ShareApp : Intent() object ShareApp : Intent()
} }

View File

@ -22,6 +22,7 @@ import com.arkivanov.mvikotlin.core.store.Store
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
import com.shabinder.common.models.AudioQuality import com.shabinder.common.models.AudioQuality
import com.shabinder.common.models.Actions 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
import com.shabinder.common.preference.SpotiFlyerPreference.State import com.shabinder.common.preference.SpotiFlyerPreference.State
import com.shabinder.common.preference.store.SpotiFlyerPreferenceStore.Intent import com.shabinder.common.preference.store.SpotiFlyerPreferenceStore.Intent
@ -45,12 +46,14 @@ internal class SpotiFlyerPreferenceStoreProvider(
data class AnalyticsToggled(val isEnabled: Boolean) : Result() data class AnalyticsToggled(val isEnabled: Boolean) : Result()
data class DownloadPathSet(val path: String) : Result() data class DownloadPathSet(val path: String) : Result()
data class PreferredAudioQualityChanged(val quality: AudioQuality) : 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>() { private inner class ExecutorImpl : SuspendExecutor<Intent, Unit, State, Result, Nothing>() {
override suspend fun executeAction(action: Unit, getState: () -> State) { override suspend fun executeAction(action: Unit, getState: () -> State) {
dispatch(Result.AnalyticsToggled(preferenceManager.isAnalyticsEnabled)) dispatch(Result.AnalyticsToggled(preferenceManager.isAnalyticsEnabled))
dispatch(Result.PreferredAudioQualityChanged(preferenceManager.audioQuality)) dispatch(Result.PreferredAudioQualityChanged(preferenceManager.audioQuality))
dispatch(Result.SpotifyCredentialsUpdated(preferenceManager.spotifyCredentials))
dispatch(Result.DownloadPathSet(fileManager.defaultDir())) dispatch(Result.DownloadPathSet(fileManager.defaultDir()))
} }
@ -71,6 +74,11 @@ internal class SpotiFlyerPreferenceStoreProvider(
dispatch(Result.PreferredAudioQualityChanged(intent.quality)) dispatch(Result.PreferredAudioQualityChanged(intent.quality))
preferenceManager.setPreferredAudioQuality(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.AnalyticsToggled -> copy(isAnalyticsEnabled = result.isEnabled)
is Result.DownloadPathSet -> copy(downloadPath = result.path) is Result.DownloadPathSet -> copy(downloadPath = result.path)
is Result.PreferredAudioQualityChanged -> copy(preferredQuality = result.quality) is Result.PreferredAudioQualityChanged -> copy(preferredQuality = result.quality)
is Result.SpotifyCredentialsUpdated -> copy(spotifyCredentials = result.spotifyCredentials)
} }
} }
} }

View File

@ -261,14 +261,14 @@ class SpotifyProvider(
artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(), artists = it.artists?.map { artist -> artist?.name.toString() } ?: listOf(),
albumArtists = it.album?.artists?.mapNotNull { artist -> artist?.name } ?: emptyList(), albumArtists = it.album?.artists?.mapNotNull { artist -> artist?.name } ?: emptyList(),
durationSec = (it.duration_ms / 1000).toInt(), durationSec = (it.duration_ms / 1000).toInt(),
albumArtPath = fileManager.getImageCachePath(it.album?.images?.firstOrNull()?.url ?: ""), albumArtPath = fileManager.getImageCachePath(it.album?.images?.maxByOrNull { img -> img?.width ?: 0 }?.url ?: ""),
albumName = it.album?.name, albumName = it.album?.name,
year = it.album?.release_date, year = it.album?.release_date,
comment = "Genres:${it.album?.genres?.joinToString()}", comment = "Genres:${it.album?.genres?.joinToString()}",
trackUrl = it.href, trackUrl = it.href,
downloaded = it.updateStatusIfPresent(type, subFolder), downloaded = it.updateStatusIfPresent(type, subFolder),
source = Source.Spotify, source = Source.Spotify,
albumArtURL = it.album?.images?.firstOrNull()?.url.toString(), albumArtURL = it.album?.images?.maxByOrNull { img -> img?.width ?: 0 }?.url.toString(),
outputFilePath = fileManager.finalOutputDir( outputFilePath = fileManager.finalOutputDir(
it.name.toString(), it.name.toString(),
type, type,

View File

@ -16,6 +16,7 @@
package com.shabinder.common.providers.spotify.requests 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.SpotiFlyerException
import com.shabinder.common.models.event.coroutines.SuspendableEvent import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.models.Actions import com.shabinder.common.models.Actions
@ -45,8 +46,7 @@ suspend fun authenticateSpotify(): SuspendableEvent<TokenData, Throwable> = Susp
@SharedImmutable @SharedImmutable
private val spotifyAuthClient by lazy { private val spotifyAuthClient by lazy {
HttpClient { HttpClient {
val clientId = "694d8bf4f6ec420fa66ea7fb4c68f89d" val (clientId, clientSecret) = PreferenceManager.instance.spotifyCredentials
val clientSecret = "02ca2d4021a7452dae2328b47a6e8fe8"
install(Auth) { install(Auth) {
basic { basic {

View File

@ -88,7 +88,7 @@ compose.desktop {
iconFile.set(iconsRoot.resolve("spotiflyer.ico")) iconFile.set(iconsRoot.resolve("spotiflyer.ico"))
// Wondering what the heck is this? See : https://wixtoolset.org/documentation/manual/v3/howtos/general/generate_guids.html // Wondering what the heck is this? See : https://wixtoolset.org/documentation/manual/v3/howtos/general/generate_guids.html
// https://www.guidgen.com/ // https://www.guidgen.com/
upgradeUuid = "0d0b3288-5f87-4e7a-8929-540d79f89bc2" upgradeUuid = "50dac393-a24f-48a6-89c6-9218b24a5291"
menuGroup = packageName menuGroup = packageName
} }
linux { linux {

View File

@ -0,0 +1,3 @@
- User Configurable Spotify Creds Support.
- Crash & Visibility Fix for SettingsIcon.
- Changed default Creds, thanks to spotDL.

View File

@ -0,0 +1 @@
- Fix for Blurry Images.

View File

@ -40,6 +40,14 @@ checkInternetConnection = Please check your network connection.
grantPermissions = Grant permissions grantPermissions = Grant permissions
requiredPermissions = Required permissions: requiredPermissions = Required permissions:
storagePermission = Storage 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. storagePermissionReason = To download your favourite songs to this device.
backgroundRunning = Background running. backgroundRunning = Background running.
backgroundRunningReason = To download all songs in background without any system interruptions. backgroundRunningReason = To download all songs in background without any system interruptions.