Preference Screen Added

This commit is contained in:
shabinder 2021-07-11 01:44:27 +05:30
parent 34fcfe4d88
commit a44f4cc061
21 changed files with 337 additions and 54 deletions

View File

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

View File

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

View File

@ -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
}
}
}
}
}

View File

@ -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)
}

View File

@ -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() {}

View File

@ -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

View File

@ -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)
}

View File

@ -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 =

View File

@ -71,12 +71,12 @@ internal class SpotiFlyerMainStoreProvider(
data class ItemsLoaded(val items: List<DownloadRecord>) : 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<Intent, Unit, State, Result, Nothing>() {
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)
}
}
}

View File

@ -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
)
}

View File

@ -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 {

View File

@ -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<SpotiFlyerPreferenceStore.Intent, SpotiFlyerPreference.State, Nothing> {
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()
}

View File

@ -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<Intent, Unit, State, Result, Nothing>() {
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<State, Result> {
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)
}
}
}

View File

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

View File

@ -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 {

View File

@ -20,5 +20,5 @@ interface SpotiFlyerRootCallBacks {
fun searchLink(link: String)
fun showToast(text: String)
fun popBackToHomeScreen()
fun setDownloadDirectory()
fun openPreferenceScreen()
}

View File

@ -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.Output>) -> SpotiFlyerMain,
private val list: (ComponentContext, link: String, output: Consumer<SpotiFlyerList.Output>) -> SpotiFlyerList,
private val preference: (ComponentContext, output: Consumer<SpotiFlyerPreference.Output>) -> 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<SpotiFlyerPreference.Output>, dependencies: Dependencies): SpotiFlyerPreference =
SpotiFlyerPreference(
componentContext = componentContext,
dependencies = object : SpotiFlyerPreference.Dependencies, Dependencies by dependencies {
override val prefOutput: Consumer<SpotiFlyerPreference.Output> = output
override val preferenceAnalytics = object : SpotiFlyerPreference.Analytics, Analytics by analytics {}
}
)

View File

@ -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 -> {

View File

@ -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

View File

@ -1 +1 @@
Download All your songs from Spotify, Gaana, Youtube Music.
Download All your songs from Spotify, Gaana, Jio Saavn, Youtube Music.

View File

@ -73,7 +73,7 @@ class App(props: AppProps): RComponent<AppProps, RState>(props) {
override fun copyToClipboard(text: String) {}
override fun setDownloadDirectoryAction() {}
override fun setDownloadDirectoryAction(callBack: (String) -> Unit) {}
override fun queryActiveTracks() {}