mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-12-22 12:47:54 +01:00
Preference Screen Added
This commit is contained in:
parent
34fcfe4d88
commit
a44f4cc061
@ -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 "))
|
||||
|
@ -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"))
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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() {}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
@ -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 =
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -20,5 +20,5 @@ interface SpotiFlyerRootCallBacks {
|
||||
fun searchLink(link: String)
|
||||
fun showToast(text: String)
|
||||
fun popBackToHomeScreen()
|
||||
fun setDownloadDirectory()
|
||||
fun openPreferenceScreen()
|
||||
}
|
||||
|
@ -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 {}
|
||||
}
|
||||
)
|
||||
|
@ -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 -> {
|
||||
|
7
fastlane/metadata/android/en-US/changelogs/21.txt
Normal file
7
fastlane/metadata/android/en-US/changelogs/21.txt
Normal 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
|
@ -1 +1 @@
|
||||
Download All your songs from Spotify, Gaana, Youtube Music.
|
||||
Download All your songs from Spotify, Gaana, Jio Saavn, Youtube Music.
|
@ -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() {}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user