From a44f4cc06176196cf0a6cf5a35be0fbe9f739e63 Mon Sep 17 00:00:00 2001 From: shabinder Date: Sun, 11 Jul 2021 01:44:27 +0530 Subject: [PATCH] Preference Screen Added --- .../com/shabinder/spotiflyer/MainActivity.kt | 5 +- common/compose/build.gradle.kts | 1 + .../uikit/screens/SpotiFlyerPreferenceUi.kt | 201 ++++++++++++++++++ .../common/uikit/screens/SpotiFlyerRootUi.kt | 9 +- .../com/shabinder/common/models/Actions.kt | 4 +- .../shabinder/common/models/AudioQuality.kt | 2 - .../common/di/preference/PreferenceManager.kt | 29 +-- .../main/integration/SpotiFlyerMainImpl.kt | 4 + .../main/store/SpotiFlyerMainStoreProvider.kt | 8 +- .../common/preference/SpotiFlyerPreference.kt | 7 +- .../integration/SpotiFlyerPreferenceImpl.kt | 15 +- .../store/SpotiFlyerPreferenceStore.kt | 3 + .../SpotiFlyerPreferenceStoreProvider.kt | 29 ++- common/root/build.gradle.kts | 3 + .../shabinder/common/root/SpotiFlyerRoot.kt | 2 + .../root/callbacks/SpotiFlyerRootCallBacks.kt | 2 +- .../root/integration/SpotiFlyerRootImpl.kt | 48 +++-- desktop/src/jvmMain/kotlin/Main.kt | 8 +- .../metadata/android/en-US/changelogs/21.txt | 7 + .../android/en-US/short_description.txt | 2 +- web-app/src/main/kotlin/App.kt | 2 +- 21 files changed, 337 insertions(+), 54 deletions(-) create mode 100644 common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/screens/SpotiFlyerPreferenceUi.kt create mode 100644 fastlane/metadata/android/en-US/changelogs/21.txt diff --git a/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt b/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt index a3ea43a3..604d2d17 100644 --- a/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt +++ b/android/src/main/java/com/shabinder/spotiflyer/MainActivity.kt @@ -300,7 +300,7 @@ class MainActivity : ComponentActivity() { override fun showPopUpMessage(string: String, long: Boolean) = this@MainActivity.showPopUpMessage(string,long) - override fun setDownloadDirectoryAction() = setUpOnPrefClickListener() + override fun setDownloadDirectoryAction(callBack: (String) -> Unit) = setUpOnPrefClickListener(callBack) override fun queryActiveTracks() = this@MainActivity.queryActiveTracks() @@ -400,7 +400,7 @@ class MainActivity : ComponentActivity() { } @Suppress("DEPRECATION") - private fun setUpOnPrefClickListener() { + private fun setUpOnPrefClickListener(callBack : (String) -> Unit) { // Initialize Builder val chooser = StorageChooser.Builder() .withActivity(this) @@ -421,6 +421,7 @@ class MainActivity : ComponentActivity() { if (f.canWrite()) { // hell yeah :) preferenceManager.setDownloadDirectory(path) + callBack(dir.defaultDir()) showPopUpMessage(Strings.downloadDirectorySetTo("\n${dir.defaultDir()}")) }else{ showPopUpMessage(Strings.noWriteAccess("\n$path ")) diff --git a/common/compose/build.gradle.kts b/common/compose/build.gradle.kts index ac27f094..3b8e6760 100644 --- a/common/compose/build.gradle.kts +++ b/common/compose/build.gradle.kts @@ -33,6 +33,7 @@ kotlin { implementation(project(":common:root")) implementation(project(":common:main")) implementation(project(":common:list")) + implementation(project(":common:preference")) implementation(project(":common:database")) implementation(project(":common:data-models")) implementation(project(":common:dependency-injection")) diff --git a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/screens/SpotiFlyerPreferenceUi.kt b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/screens/SpotiFlyerPreferenceUi.kt new file mode 100644 index 00000000..9eeefaff --- /dev/null +++ b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/screens/SpotiFlyerPreferenceUi.kt @@ -0,0 +1,201 @@ +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.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +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.rememberScrollState +import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Card +import androidx.compose.material.Icon +import androidx.compose.material.RadioButton +import androidx.compose.material.Switch +import androidx.compose.material.SwitchDefaults +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Insights +import androidx.compose.material.icons.rounded.MusicNote +import androidx.compose.material.icons.rounded.SnippetFolder +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.unit.dp +import com.arkivanov.decompose.extensions.compose.jetbrains.subscribeAsState +import com.shabinder.common.models.AudioQuality +import com.shabinder.common.preference.SpotiFlyerPreference +import com.shabinder.common.translations.Strings +import com.shabinder.common.uikit.configurations.SpotiFlyerTypography +import com.shabinder.common.uikit.configurations.colorAccent +import com.shabinder.common.uikit.configurations.colorOffWhite + +@Composable +fun SpotiFlyerPreferenceContent(component: SpotiFlyerPreference) { + val model by component.model.subscribeAsState() + + val stateVertical = rememberScrollState(0) + + Column(Modifier.fillMaxSize().padding(8.dp).verticalScroll(stateVertical)) { + Spacer(Modifier.padding(top = 16.dp)) + Card( + modifier = Modifier.fillMaxWidth(), + border = BorderStroke(1.dp, Color.Gray) + ) { + Column(Modifier.padding(12.dp)) { + Text( + text = Strings.preferences(), + style = SpotiFlyerTypography.body1, + color = colorAccent + ) + Spacer(modifier = Modifier.padding(top = 12.dp)) + + SettingsRow( + icon = rememberVectorPainter(Icons.Rounded.MusicNote), + title = "Preferred Audio Quality", + value = model.preferredQuality.kbps + "KBPS" + ) { save -> + val audioQualities = AudioQuality.values() + + audioQualities.forEach { quality -> + Row( + Modifier + .fillMaxWidth() + .selectable( + selected = (quality == model.preferredQuality), + onClick = { + component.setPreferredQuality(quality) + save() + } + ) + .padding(horizontal = 16.dp,vertical = 2.dp) + ) { + RadioButton( + selected = (quality == model.preferredQuality), + onClick = { + component.setPreferredQuality(quality) + save() + } + ) + Text( + text = quality.kbps + " KBPS", + style = SpotiFlyerTypography.h6, + modifier = Modifier.padding(start = 16.dp) + ) + } + } + + } + + Spacer(Modifier.padding(top = 12.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().clickable( + onClick = { component.selectNewDownloadDirectory() } + ) + ) { + Icon(Icons.Rounded.SnippetFolder, Strings.setDownloadDirectory(), Modifier.size(32.dp), tint = Color(0xFFCCCCCC)) + Spacer(modifier = Modifier.padding(start = 16.dp)) + Column { + Text( + text = Strings.setDownloadDirectory(), + style = SpotiFlyerTypography.h6 + ) + Text( + text = model.downloadPath, + style = SpotiFlyerTypography.subtitle2 + ) + } + } + + Spacer(Modifier.padding(top = 12.dp)) + + Row( + modifier = Modifier.fillMaxWidth() + .clickable( + onClick = { component.toggleAnalytics(!model.isAnalyticsEnabled) } + ), + verticalAlignment = Alignment.CenterVertically + ) { + @Suppress("DuplicatedCode") + Icon(Icons.Rounded.Insights, Strings.analytics() + Strings.status(), Modifier.size(32.dp)) + Spacer(modifier = Modifier.padding(start = 16.dp)) + Column( + Modifier.weight(1f) + ) { + Text( + text = Strings.analytics(), + style = SpotiFlyerTypography.h6 + ) + Text( + text = Strings.analyticsDescription(), + style = SpotiFlyerTypography.subtitle2 + ) + } + Switch( + checked = model.isAnalyticsEnabled, + onCheckedChange = null, + colors = SwitchDefaults.colors(uncheckedThumbColor = colorOffWhite) + ) + } + } + } + Spacer(modifier = Modifier.padding(top = 8.dp)) + } + +} + +@OptIn(ExperimentalAnimationApi::class) +@Composable +fun SettingsRow( + icon: Painter, + title: String, + value:String, + editContent: @Composable ColumnScope.(() -> Unit) -> Unit +) { + + var isEditMode by remember { mutableStateOf(false) } + Column { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().clickable( + onClick = { isEditMode = !isEditMode } + ).padding(vertical = 6.dp) + ) { + Icon(icon, title, Modifier.size(32.dp), tint = Color(0xFFCCCCCC)) + Spacer(modifier = Modifier.padding(start = 16.dp)) + Column { + Text( + text = title, + style = SpotiFlyerTypography.h6 + ) + Text( + text = value, + style = SpotiFlyerTypography.subtitle2 + ) + } + } + AnimatedVisibility(isEditMode) { + Column { + editContent { + isEditMode = false + } + } + } + } +} \ No newline at end of file diff --git a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/screens/SpotiFlyerRootUi.kt b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/screens/SpotiFlyerRootUi.kt index 6b842b48..2255668f 100644 --- a/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/screens/SpotiFlyerRootUi.kt +++ b/common/compose/src/commonMain/kotlin/com/shabinder/common/uikit/screens/SpotiFlyerRootUi.kt @@ -136,8 +136,8 @@ fun MainScreen(modifier: Modifier = Modifier, alpha: Float, topPadding: Dp = 0.d AppBar( backgroundColor = appBarColor, onBackPressed = callBacks::popBackToHomeScreen, - setDownloadDirectory = callBacks::setDownloadDirectory, - isBackButtonVisible = activeComponent.value.activeChild.instance is Child.List, + openPreferenceScreen = callBacks::openPreferenceScreen, + isBackButtonVisible = activeComponent.value.activeChild.instance !is Child.Main, modifier = Modifier.fillMaxWidth() ) Spacer(Modifier.padding(top = topPadding)) @@ -148,6 +148,7 @@ fun MainScreen(modifier: Modifier = Modifier, alpha: Float, topPadding: Dp = 0.d when (val child = it.instance) { is Child.Main -> SpotiFlyerMainContent(component = child.component) is Child.List -> SpotiFlyerListContent(component = child.component) + is Child.Preference -> SpotiFlyerPreferenceContent(component = child.component) } } } @@ -158,7 +159,7 @@ fun MainScreen(modifier: Modifier = Modifier, alpha: Float, topPadding: Dp = 0.d fun AppBar( backgroundColor: Color, onBackPressed: () -> Unit, - setDownloadDirectory: () -> Unit, + openPreferenceScreen: () -> Unit, isBackButtonVisible: Boolean, modifier: Modifier = Modifier ) { @@ -189,7 +190,7 @@ fun AppBar( }, actions = { IconButton( - onClick = { setDownloadDirectory() } + onClick = { openPreferenceScreen() } ) { Icon(Icons.Filled.Settings, Strings.preferences(), tint = Color.Gray) } diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/Actions.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/Actions.kt index 6a9d8988..5fc8c4f4 100644 --- a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/Actions.kt +++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/Actions.kt @@ -22,7 +22,7 @@ interface Actions { fun showPopUpMessage(string: String, long: Boolean = false) // Change Download Directory - fun setDownloadDirectoryAction() + fun setDownloadDirectoryAction(callBack: (String) -> Unit) /* * Query Downloading Tracks @@ -47,7 +47,7 @@ interface Actions { private fun stubActions(): Actions = object : Actions { override val platformActions = StubPlatformActions override fun showPopUpMessage(string: String, long: Boolean) {} - override fun setDownloadDirectoryAction() {} + override fun setDownloadDirectoryAction(callBack: (String) -> Unit) {} override fun queryActiveTracks() {} override fun giveDonation() {} override fun shareApp() {} diff --git a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/AudioQuality.kt b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/AudioQuality.kt index 1d5a7b3b..3de643f4 100644 --- a/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/AudioQuality.kt +++ b/common/data-models/src/commonMain/kotlin/com/shabinder/common/models/AudioQuality.kt @@ -4,7 +4,6 @@ enum class AudioQuality(val kbps: String) { KBPS128("128"), KBPS160("160"), KBPS192("192"), - KBPS224("224"), KBPS256("256"), KBPS320("320"); @@ -14,7 +13,6 @@ enum class AudioQuality(val kbps: String) { "128" -> KBPS128 "160" -> KBPS160 "192" -> KBPS192 - "224" -> KBPS224 "256" -> KBPS256 "320" -> KBPS320 else -> KBPS160 // Use 160 as baseline diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/preference/PreferenceManager.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/preference/PreferenceManager.kt index 3e44ba91..fb173f28 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/preference/PreferenceManager.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/preference/PreferenceManager.kt @@ -1,35 +1,40 @@ package com.shabinder.common.di.preference import com.russhwolf.settings.Settings +import com.shabinder.common.models.AudioQuality class PreferenceManager(settings: Settings): Settings by settings { companion object { - const val DirKey = "downloadDir" - const val AnalyticsKey = "analytics" - const val FirstLaunch = "firstLaunch" - const val DonationInterval = "donationInterval" + const val DIR_KEY = "downloadDir" + const val ANALYTICS_KEY = "analytics" + const val FIRST_LAUNCH = "firstLaunch" + const val DONATION_INTERVAL = "donationInterval" + const val PREFERRED_AUDIO_QUALITY = "preferredAudioQuality" } /* ANALYTICS */ - val isAnalyticsEnabled get() = getBooleanOrNull(AnalyticsKey) ?: false - fun toggleAnalytics(enabled: Boolean) = putBoolean(AnalyticsKey, enabled) + val isAnalyticsEnabled get() = getBooleanOrNull(ANALYTICS_KEY) ?: false + fun toggleAnalytics(enabled: Boolean) = putBoolean(ANALYTICS_KEY, enabled) /* DOWNLOAD DIRECTORY */ - val downloadDir get() = getStringOrNull(DirKey) - fun setDownloadDirectory(newBasePath: String) = putString(DirKey, newBasePath) + val downloadDir get() = getStringOrNull(DIR_KEY) + fun setDownloadDirectory(newBasePath: String) = putString(DIR_KEY, newBasePath) + /* Preferred Audio Quality */ + val audioQuality get() = AudioQuality.getQuality(getStringOrNull(PREFERRED_AUDIO_QUALITY) ?: "320") + fun setPreferredAudioQuality(quality: AudioQuality) = putString(PREFERRED_AUDIO_QUALITY,quality.kbps) /* OFFSET FOR WHEN TO ASK FOR SUPPORT */ - val getDonationOffset: Int get() = (getIntOrNull(DonationInterval) ?: 3).also { + val getDonationOffset: Int get() = (getIntOrNull(DONATION_INTERVAL) ?: 3).also { // Min. Donation Asking Interval is `3` if (it < 3) setDonationOffset(3) else setDonationOffset(it - 1) } - fun setDonationOffset(offset: Int = 5) = putInt(DonationInterval, offset) + fun setDonationOffset(offset: Int = 5) = putInt(DONATION_INTERVAL, offset) /* TO CHECK IF THIS IS APP's FIRST LAUNCH */ - val isFirstLaunch get() = getBooleanOrNull(FirstLaunch) ?: true - fun firstLaunchDone() = putBoolean(FirstLaunch, false) + val isFirstLaunch get() = getBooleanOrNull(FIRST_LAUNCH) ?: true + fun firstLaunchDone() = putBoolean(FIRST_LAUNCH, false) } \ No newline at end of file diff --git a/common/main/src/commonMain/kotlin/com/shabinder/common/main/integration/SpotiFlyerMainImpl.kt b/common/main/src/commonMain/kotlin/com/shabinder/common/main/integration/SpotiFlyerMainImpl.kt index fc5ae0b0..adc094d8 100644 --- a/common/main/src/commonMain/kotlin/com/shabinder/common/main/integration/SpotiFlyerMainImpl.kt +++ b/common/main/src/commonMain/kotlin/com/shabinder/common/main/integration/SpotiFlyerMainImpl.kt @@ -18,6 +18,7 @@ package com.shabinder.common.main.integration import co.touchlab.stately.ensureNeverFrozen import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.lifecycle.doOnResume import com.arkivanov.decompose.value.Value import com.shabinder.common.caching.Cache import com.shabinder.common.di.Picture @@ -39,6 +40,9 @@ internal class SpotiFlyerMainImpl( init { instanceKeeper.ensureNeverFrozen() + lifecycle.doOnResume { + store.accept(Intent.ToggleAnalytics(preferenceManager.isAnalyticsEnabled)) + } } private val store = diff --git a/common/main/src/commonMain/kotlin/com/shabinder/common/main/store/SpotiFlyerMainStoreProvider.kt b/common/main/src/commonMain/kotlin/com/shabinder/common/main/store/SpotiFlyerMainStoreProvider.kt index 4ee7c951..5cdfd129 100644 --- a/common/main/src/commonMain/kotlin/com/shabinder/common/main/store/SpotiFlyerMainStoreProvider.kt +++ b/common/main/src/commonMain/kotlin/com/shabinder/common/main/store/SpotiFlyerMainStoreProvider.kt @@ -71,12 +71,12 @@ internal class SpotiFlyerMainStoreProvider( data class ItemsLoaded(val items: List) : Result() data class CategoryChanged(val category: SpotiFlyerMain.HomeCategory) : Result() data class LinkChanged(val link: String) : Result() - data class ToggleAnalytics(val isEnabled: Boolean) : Result() + data class AnalyticsToggled(val isEnabled: Boolean) : Result() } private inner class ExecutorImpl : SuspendExecutor() { override suspend fun executeAction(action: Unit, getState: () -> State) { - dispatch(Result.ToggleAnalytics(preferenceManager.isAnalyticsEnabled)) + dispatch(Result.AnalyticsToggled(preferenceManager.isAnalyticsEnabled)) updates?.collect { dispatch(Result.ItemsLoaded(it)) } @@ -90,7 +90,7 @@ internal class SpotiFlyerMainStoreProvider( is Intent.SetLink -> dispatch(Result.LinkChanged(link = intent.link)) is Intent.SelectCategory -> dispatch(Result.CategoryChanged(intent.category)) is Intent.ToggleAnalytics -> { - dispatch(Result.ToggleAnalytics(intent.enabled)) + dispatch(Result.AnalyticsToggled(intent.enabled)) preferenceManager.toggleAnalytics(intent.enabled) } } @@ -103,7 +103,7 @@ internal class SpotiFlyerMainStoreProvider( is Result.ItemsLoaded -> copy(records = result.items) is Result.LinkChanged -> copy(link = result.link) is Result.CategoryChanged -> copy(selectedCategory = result.category) - is Result.ToggleAnalytics -> copy(isAnalyticsEnabled = result.isEnabled) + is Result.AnalyticsToggled -> copy(isAnalyticsEnabled = result.isEnabled) } } } diff --git a/common/preference/src/commonMain/kotlin/com/shabinder/common/preference/SpotiFlyerPreference.kt b/common/preference/src/commonMain/kotlin/com/shabinder/common/preference/SpotiFlyerPreference.kt index 006e204d..0b5c5605 100644 --- a/common/preference/src/commonMain/kotlin/com/shabinder/common/preference/SpotiFlyerPreference.kt +++ b/common/preference/src/commonMain/kotlin/com/shabinder/common/preference/SpotiFlyerPreference.kt @@ -22,6 +22,7 @@ import com.arkivanov.mvikotlin.core.store.StoreFactory import com.shabinder.common.di.Dir import com.shabinder.common.di.Picture import com.shabinder.common.di.preference.PreferenceManager +import com.shabinder.common.models.Actions import com.shabinder.common.models.AudioQuality import com.shabinder.common.models.Consumer import com.shabinder.common.preference.integration.SpotiFlyerPreferenceImpl @@ -34,7 +35,9 @@ interface SpotiFlyerPreference { fun toggleAnalytics(enabled: Boolean) - fun setDownloadDirectory(newBasePath: String) + fun selectNewDownloadDirectory() + + fun setPreferredQuality(quality: AudioQuality) suspend fun loadImage(url: String): Picture @@ -43,6 +46,7 @@ interface SpotiFlyerPreference { val storeFactory: StoreFactory val dir: Dir val preferenceManager: PreferenceManager + val actions: Actions val preferenceAnalytics: Analytics } @@ -54,6 +58,7 @@ interface SpotiFlyerPreference { data class State( val preferredQuality: AudioQuality = AudioQuality.KBPS320, + val downloadPath: String = "", val isAnalyticsEnabled: Boolean = false ) } diff --git a/common/preference/src/commonMain/kotlin/com/shabinder/common/preference/integration/SpotiFlyerPreferenceImpl.kt b/common/preference/src/commonMain/kotlin/com/shabinder/common/preference/integration/SpotiFlyerPreferenceImpl.kt index 18b457d8..10401e18 100644 --- a/common/preference/src/commonMain/kotlin/com/shabinder/common/preference/integration/SpotiFlyerPreferenceImpl.kt +++ b/common/preference/src/commonMain/kotlin/com/shabinder/common/preference/integration/SpotiFlyerPreferenceImpl.kt @@ -22,6 +22,7 @@ import com.arkivanov.decompose.value.Value import com.shabinder.common.caching.Cache import com.shabinder.common.di.Picture import com.shabinder.common.di.utils.asValue +import com.shabinder.common.models.AudioQuality import com.shabinder.common.preference.SpotiFlyerPreference import com.shabinder.common.preference.SpotiFlyerPreference.Dependencies import com.shabinder.common.preference.SpotiFlyerPreference.State @@ -42,7 +43,9 @@ internal class SpotiFlyerPreferenceImpl( instanceKeeper.getStore { SpotiFlyerPreferenceStoreProvider( storeFactory = storeFactory, - preferenceManager = preferenceManager + preferenceManager = preferenceManager, + dir = dir, + actions = actions ).provide() } @@ -59,8 +62,14 @@ internal class SpotiFlyerPreferenceImpl( store.accept(Intent.ToggleAnalytics(enabled)) } - override fun setDownloadDirectory(newBasePath: String) { - preferenceManager.setDownloadDirectory(newBasePath) + override fun selectNewDownloadDirectory() { + actions.setDownloadDirectoryAction { + store.accept(Intent.SetDownloadDirectory(dir.defaultDir())) + } + } + + override fun setPreferredQuality(quality: AudioQuality) { + store.accept(Intent.SetPreferredAudioQuality(quality)) } override suspend fun loadImage(url: String): Picture { diff --git a/common/preference/src/commonMain/kotlin/com/shabinder/common/preference/store/SpotiFlyerPreferenceStore.kt b/common/preference/src/commonMain/kotlin/com/shabinder/common/preference/store/SpotiFlyerPreferenceStore.kt index 054fff9d..b12056dc 100644 --- a/common/preference/src/commonMain/kotlin/com/shabinder/common/preference/store/SpotiFlyerPreferenceStore.kt +++ b/common/preference/src/commonMain/kotlin/com/shabinder/common/preference/store/SpotiFlyerPreferenceStore.kt @@ -17,12 +17,15 @@ package com.shabinder.common.preference.store import com.arkivanov.mvikotlin.core.store.Store +import com.shabinder.common.models.AudioQuality import com.shabinder.common.preference.SpotiFlyerPreference internal interface SpotiFlyerPreferenceStore : Store { sealed class Intent { data class OpenPlatform(val platformID: String, val platformLink: String) : Intent() data class ToggleAnalytics(val enabled: Boolean) : Intent() + data class SetDownloadDirectory(val path: String) : Intent() + data class SetPreferredAudioQuality(val quality: AudioQuality) : Intent() object GiveDonation : Intent() object ShareApp : Intent() } diff --git a/common/preference/src/commonMain/kotlin/com/shabinder/common/preference/store/SpotiFlyerPreferenceStoreProvider.kt b/common/preference/src/commonMain/kotlin/com/shabinder/common/preference/store/SpotiFlyerPreferenceStoreProvider.kt index ba273448..73a90890 100644 --- a/common/preference/src/commonMain/kotlin/com/shabinder/common/preference/store/SpotiFlyerPreferenceStoreProvider.kt +++ b/common/preference/src/commonMain/kotlin/com/shabinder/common/preference/store/SpotiFlyerPreferenceStoreProvider.kt @@ -21,14 +21,19 @@ import com.arkivanov.mvikotlin.core.store.SimpleBootstrapper import com.arkivanov.mvikotlin.core.store.Store import com.arkivanov.mvikotlin.core.store.StoreFactory import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor +import com.shabinder.common.di.Dir import com.shabinder.common.di.preference.PreferenceManager +import com.shabinder.common.models.Actions +import com.shabinder.common.models.AudioQuality import com.shabinder.common.models.methods import com.shabinder.common.preference.SpotiFlyerPreference.State import com.shabinder.common.preference.store.SpotiFlyerPreferenceStore.Intent internal class SpotiFlyerPreferenceStoreProvider( private val storeFactory: StoreFactory, - private val preferenceManager: PreferenceManager + private val preferenceManager: PreferenceManager, + private val dir: Dir, + private val actions: Actions ) { fun provide(): SpotiFlyerPreferenceStore = @@ -43,12 +48,16 @@ internal class SpotiFlyerPreferenceStoreProvider( ) {} private sealed class Result { - data class ToggleAnalytics(val isEnabled: Boolean) : Result() + data class AnalyticsToggled(val isEnabled: Boolean) : Result() + data class DownloadPathSet(val path: String) : Result() + data class PreferredAudioQualityChanged(val quality: AudioQuality) : Result() } private inner class ExecutorImpl : SuspendExecutor() { override suspend fun executeAction(action: Unit, getState: () -> State) { - dispatch(Result.ToggleAnalytics(preferenceManager.isAnalyticsEnabled)) + dispatch(Result.AnalyticsToggled(preferenceManager.isAnalyticsEnabled)) + dispatch(Result.PreferredAudioQualityChanged(preferenceManager.audioQuality)) + dispatch(Result.DownloadPathSet(dir.defaultDir())) } override suspend fun executeIntent(intent: Intent, getState: () -> State) { @@ -57,9 +66,17 @@ internal class SpotiFlyerPreferenceStoreProvider( is Intent.GiveDonation -> methods.value.giveDonation() is Intent.ShareApp -> methods.value.shareApp() is Intent.ToggleAnalytics -> { - dispatch(Result.ToggleAnalytics(intent.enabled)) + dispatch(Result.AnalyticsToggled(intent.enabled)) preferenceManager.toggleAnalytics(intent.enabled) } + is Intent.SetDownloadDirectory -> { + dispatch(Result.DownloadPathSet(intent.path)) + preferenceManager.setDownloadDirectory(intent.path) + } + is Intent.SetPreferredAudioQuality -> { + dispatch(Result.PreferredAudioQualityChanged(intent.quality)) + preferenceManager.setPreferredAudioQuality(intent.quality) + } } } } @@ -67,7 +84,9 @@ internal class SpotiFlyerPreferenceStoreProvider( private object ReducerImpl : Reducer { override fun State.reduce(result: Result): State = when (result) { - is Result.ToggleAnalytics -> copy(isAnalyticsEnabled = result.isEnabled) + is Result.AnalyticsToggled -> copy(isAnalyticsEnabled = result.isEnabled) + is Result.DownloadPathSet -> copy(downloadPath = result.path) + is Result.PreferredAudioQualityChanged -> copy(preferredQuality = result.quality) } } } diff --git a/common/root/build.gradle.kts b/common/root/build.gradle.kts index 12365d43..7afb8a79 100644 --- a/common/root/build.gradle.kts +++ b/common/root/build.gradle.kts @@ -30,6 +30,7 @@ fun org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer.generateFramewor export(project(":common:database")) export(project(":common:main")) export(project(":common:list")) + export(project(":common:preference")) export(Decompose.decompose) export(MVIKotlin.mvikotlinMain) export(MVIKotlin.mvikotlinLogging) @@ -65,6 +66,7 @@ kotlin { implementation(project(":common:database")) implementation(project(":common:list")) implementation(project(":common:main")) + implementation(project(":common:preference")) implementation(SqlDelight.coroutineExtensions) } } @@ -79,6 +81,7 @@ kotlin { api(project(":common:database")) api(project(":common:list")) api(project(":common:main")) + api(project(":common:preference")) api(Decompose.decompose) api(MVIKotlin.mvikotlinMain) api(MVIKotlin.mvikotlinLogging) diff --git a/common/root/src/commonMain/kotlin/com/shabinder/common/root/SpotiFlyerRoot.kt b/common/root/src/commonMain/kotlin/com/shabinder/common/root/SpotiFlyerRoot.kt index 9c260c97..1c8a8ff9 100644 --- a/common/root/src/commonMain/kotlin/com/shabinder/common/root/SpotiFlyerRoot.kt +++ b/common/root/src/commonMain/kotlin/com/shabinder/common/root/SpotiFlyerRoot.kt @@ -27,6 +27,7 @@ import com.shabinder.common.list.SpotiFlyerList import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.models.Actions import com.shabinder.common.models.DownloadStatus +import com.shabinder.common.preference.SpotiFlyerPreference import com.shabinder.common.root.SpotiFlyerRoot.Dependencies import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks import com.shabinder.common.root.integration.SpotiFlyerRootImpl @@ -45,6 +46,7 @@ interface SpotiFlyerRoot { sealed class Child { data class Main(val component: SpotiFlyerMain) : Child() data class List(val component: SpotiFlyerList) : Child() + data class Preference(val component: SpotiFlyerPreference) : Child() } interface Dependencies { diff --git a/common/root/src/commonMain/kotlin/com/shabinder/common/root/callbacks/SpotiFlyerRootCallBacks.kt b/common/root/src/commonMain/kotlin/com/shabinder/common/root/callbacks/SpotiFlyerRootCallBacks.kt index 039bfd9c..f9fbf200 100644 --- a/common/root/src/commonMain/kotlin/com/shabinder/common/root/callbacks/SpotiFlyerRootCallBacks.kt +++ b/common/root/src/commonMain/kotlin/com/shabinder/common/root/callbacks/SpotiFlyerRootCallBacks.kt @@ -20,5 +20,5 @@ interface SpotiFlyerRootCallBacks { fun searchLink(link: String) fun showToast(text: String) fun popBackToHomeScreen() - fun setDownloadDirectory() + fun openPreferenceScreen() } diff --git a/common/root/src/commonMain/kotlin/com/shabinder/common/root/integration/SpotiFlyerRootImpl.kt b/common/root/src/commonMain/kotlin/com/shabinder/common/root/integration/SpotiFlyerRootImpl.kt index bc062ff7..8b802293 100644 --- a/common/root/src/commonMain/kotlin/com/shabinder/common/root/integration/SpotiFlyerRootImpl.kt +++ b/common/root/src/commonMain/kotlin/com/shabinder/common/root/integration/SpotiFlyerRootImpl.kt @@ -33,6 +33,7 @@ import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.models.Actions import com.shabinder.common.models.Consumer import com.shabinder.common.models.methods +import com.shabinder.common.preference.SpotiFlyerPreference import com.shabinder.common.root.SpotiFlyerRoot import com.shabinder.common.root.SpotiFlyerRoot.Analytics import com.shabinder.common.root.SpotiFlyerRoot.Child @@ -47,6 +48,7 @@ internal class SpotiFlyerRootImpl( componentContext: ComponentContext, private val main: (ComponentContext, output: Consumer) -> SpotiFlyerMain, private val list: (ComponentContext, link: String, output: Consumer) -> SpotiFlyerList, + private val preference: (ComponentContext, output: Consumer) -> SpotiFlyerPreference, private val actions: Actions, private val analytics: Analytics ) : SpotiFlyerRoot, ComponentContext by componentContext { @@ -57,19 +59,13 @@ internal class SpotiFlyerRootImpl( ) : this( componentContext = componentContext, main = { childContext, output -> - spotiFlyerMain( - childContext, - output, - dependencies - ) + spotiFlyerMain(childContext, output, dependencies) }, list = { childContext, link, output -> - spotiFlyerList( - childContext, - link, - output, - dependencies - ) + spotiFlyerList(childContext, link, output, dependencies) + }, + preference = { childContext, output -> + spotiFlyerPreference(childContext, output, dependencies) }, actions = dependencies.actions.freeze(), analytics = dependencies.analytics @@ -95,20 +91,25 @@ internal class SpotiFlyerRootImpl( override val callBacks = object : SpotiFlyerRootCallBacks { override fun searchLink(link: String) = onMainOutput(SpotiFlyerMain.Output.Search(link)) override fun popBackToHomeScreen() { - if (router.state.value.activeChild.instance is Child.List && router.state.value.backStack.isNotEmpty()) { + if (router.state.value.activeChild.instance !is Child.Main && router.state.value.backStack.isNotEmpty()) { router.popWhile { it !is Configuration.Main } } } + + override fun openPreferenceScreen() { + router.push(Configuration.Preference) + } + override fun showToast(text: String) { toastState.value = text } - override fun setDownloadDirectory() { actions.setDownloadDirectoryAction() } } private fun createChild(configuration: Configuration, componentContext: ComponentContext): Child = when (configuration) { is Configuration.Main -> Child.Main(main(componentContext, Consumer(::onMainOutput))) is Configuration.List -> Child.List(list(componentContext, configuration.link, Consumer(::onListOutput))) + is Configuration.Preference -> Child.Preference(preference(componentContext, Consumer(::onPreferenceOutput)),) } private fun onMainOutput(output: SpotiFlyerMain.Output) = @@ -128,6 +129,15 @@ internal class SpotiFlyerRootImpl( analytics.homeScreenVisit() } } + private fun onPreferenceOutput(output: SpotiFlyerPreference.Output): Unit = + when (output) { + is SpotiFlyerPreference.Output.Finished -> { + if (router.state.value.activeChild.instance is Child.Preference && router.state.value.backStack.isNotEmpty()) { + router.pop() + } + Unit + } + } @OptIn(DelicateCoroutinesApi::class) private fun initAppLaunchAndAuthenticateSpotify(authenticator: suspend () -> Unit) { @@ -142,6 +152,9 @@ internal class SpotiFlyerRootImpl( @Parcelize object Main : Configuration() + @Parcelize + object Preference : Configuration() + @Parcelize data class List(val link: String) : Configuration() } @@ -165,3 +178,12 @@ private fun spotiFlyerList(componentContext: ComponentContext, link: String, out override val listAnalytics = object : SpotiFlyerList.Analytics, Analytics by analytics {} } ) + +private fun spotiFlyerPreference(componentContext: ComponentContext, output: Consumer, dependencies: Dependencies): SpotiFlyerPreference = + SpotiFlyerPreference( + componentContext = componentContext, + dependencies = object : SpotiFlyerPreference.Dependencies, Dependencies by dependencies { + override val prefOutput: Consumer = output + override val preferenceAnalytics = object : SpotiFlyerPreference.Analytics, Analytics by analytics {} + } + ) diff --git a/desktop/src/jvmMain/kotlin/Main.kt b/desktop/src/jvmMain/kotlin/Main.kt index 227a996f..f302f09d 100644 --- a/desktop/src/jvmMain/kotlin/Main.kt +++ b/desktop/src/jvmMain/kotlin/Main.kt @@ -37,6 +37,7 @@ import com.shabinder.common.models.Actions import com.shabinder.common.models.PlatformActions import com.shabinder.common.models.TrackDetails import com.shabinder.common.root.SpotiFlyerRoot +import com.shabinder.common.translations.Strings import com.shabinder.common.uikit.configurations.SpotiFlyerColors import com.shabinder.common.uikit.configurations.SpotiFlyerShapes import com.shabinder.common.uikit.configurations.SpotiFlyerTypography @@ -106,7 +107,7 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot = } } - override fun setDownloadDirectoryAction() { + override fun setDownloadDirectoryAction(callBack: (String) -> Unit) { val fileChooser = JFileChooser().apply { fileSelectionMode = JFileChooser.DIRECTORIES_ONLY } @@ -115,9 +116,10 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot = val directory = fileChooser.selectedFile if(directory.canWrite()){ preferenceManager.setDownloadDirectory(directory.absolutePath) - showPopUpMessage("Set New Download Directory:\n${directory.absolutePath}") + callBack(dir.defaultDir()) + showPopUpMessage("${Strings.setDownloadDirectory()} \n${dir.defaultDir()}") } else { - showPopUpMessage("Cant Write to Selected Directory!") + showPopUpMessage(Strings.noWriteAccess("\n${directory.absolutePath} ")) } } else -> { diff --git a/fastlane/metadata/android/en-US/changelogs/21.txt b/fastlane/metadata/android/en-US/changelogs/21.txt new file mode 100644 index 00000000..b5e4dea0 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/21.txt @@ -0,0 +1,7 @@ + - YT Quality 128 -> 192 KBPS + - Bug Fixes + - Better Error Handling, and Bubbling upto the caller + - Error Dialog with error info added + - YT extraction fixed + - Retry on Error Logo Added + - Translations added for languages: Locales - de, en, es, fr, id, pt, ru, uk \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt index 02dbcb24..2fb159ef 100644 --- a/fastlane/metadata/android/en-US/short_description.txt +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -1 +1 @@ -Download All your songs from Spotify, Gaana, Youtube Music. \ No newline at end of file +Download All your songs from Spotify, Gaana, Jio Saavn, Youtube Music. \ No newline at end of file diff --git a/web-app/src/main/kotlin/App.kt b/web-app/src/main/kotlin/App.kt index 42c46f28..fefd0023 100644 --- a/web-app/src/main/kotlin/App.kt +++ b/web-app/src/main/kotlin/App.kt @@ -73,7 +73,7 @@ class App(props: AppProps): RComponent(props) { override fun copyToClipboard(text: String) {} - override fun setDownloadDirectoryAction() {} + override fun setDownloadDirectoryAction(callBack: (String) -> Unit) {} override fun queryActiveTracks() {}