mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-11-24 18:04:33 +01:00
Merge branch 'Shabinder:main' into main
This commit is contained in:
commit
bbb58a3676
6
.github/workflows/build-release-binaries.yml
vendored
6
.github/workflows/build-release-binaries.yml
vendored
@ -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 }}
|
||||||
|
@ -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.
|
||||||
|
14
README.md
14
README.md
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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,10 +195,12 @@ fun AppBar(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(
|
AnimatedVisibility(isSettingsIconVisible) {
|
||||||
onClick = { openPreferenceScreen() }
|
IconButton(
|
||||||
) {
|
onClick = { openPreferenceScreen() }
|
||||||
Icon(Icons.Filled.Settings, Strings.preferences(), tint = Color.Gray)
|
) {
|
||||||
|
Icon(Icons.Filled.Settings, Strings.preferences(), tint = Color.Gray)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
@ -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`
|
||||||
|
@ -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.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()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
3
fastlane/metadata/android/en-US/changelogs/30.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/30.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
- User Configurable Spotify Creds Support.
|
||||||
|
- Crash & Visibility Fix for SettingsIcon.
|
||||||
|
- Changed default Creds, thanks to spotDL.
|
1
fastlane/metadata/android/en-US/changelogs/31.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/31.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
- Fix for Blurry Images.
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user