Preference Screen & Preference Manager (WIP)

This commit is contained in:
shabinder 2021-06-23 16:43:26 +05:30
parent bb3776af56
commit c010916953
46 changed files with 249 additions and 185 deletions

View File

@ -52,6 +52,7 @@ import com.google.accompanist.insets.navigationBarsPadding
import com.google.accompanist.insets.statusBarsHeight import com.google.accompanist.insets.statusBarsHeight
import com.google.accompanist.insets.statusBarsPadding import com.google.accompanist.insets.statusBarsPadding
import com.shabinder.common.di.* import com.shabinder.common.di.*
import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.models.Actions import com.shabinder.common.models.Actions
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformActions import com.shabinder.common.models.PlatformActions
@ -78,6 +79,7 @@ class MainActivity : ComponentActivity() {
private val fetcher: FetchPlatformQueryResult by inject() private val fetcher: FetchPlatformQueryResult by inject()
private val dir: Dir by inject() private val dir: Dir by inject()
private val preferenceManager: PreferenceManager by inject()
private lateinit var root: SpotiFlyerRoot private lateinit var root: SpotiFlyerRoot
private val callBacks: SpotiFlyerRootCallBacks get() = root.callBacks private val callBacks: SpotiFlyerRootCallBacks get() = root.callBacks
private val trackStatusFlow = MutableSharedFlow<HashMap<String, DownloadStatus>>(1) private val trackStatusFlow = MutableSharedFlow<HashMap<String, DownloadStatus>>(1)
@ -129,18 +131,18 @@ class MainActivity : ComponentActivity() {
AnalyticsDialog( AnalyticsDialog(
askForAnalyticsPermission, askForAnalyticsPermission,
enableAnalytics = { enableAnalytics = {
dir.toggleAnalytics(true) preferenceManager.toggleAnalytics(true)
dir.firstLaunchDone() preferenceManager.firstLaunchDone()
}, },
dismissDialog = { dismissDialog = {
askForAnalyticsPermission = false askForAnalyticsPermission = false
dir.firstLaunchDone() preferenceManager.firstLaunchDone()
} }
) )
LaunchedEffect(view) { LaunchedEffect(view) {
permissionGranted.value = checkPermissions() permissionGranted.value = checkPermissions()
if(dir.isFirstLaunch) { if(preferenceManager.isFirstLaunch) {
delay(2500) delay(2500)
// Ask For Analytics Permission on first Dialog // Ask For Analytics Permission on first Dialog
askForAnalyticsPermission = true askForAnalyticsPermission = true
@ -161,7 +163,7 @@ class MainActivity : ComponentActivity() {
* for `Github Downloads` we will track Downloads using : https://tooomm.github.io/github-release-stats/?username=Shabinder&repository=SpotiFlyer * for `Github Downloads` we will track Downloads using : https://tooomm.github.io/github-release-stats/?username=Shabinder&repository=SpotiFlyer
* */ * */
if(isGithubRelease) { checkIfLatestVersion() } if(isGithubRelease) { checkIfLatestVersion() }
if(dir.isAnalyticsEnabled && !isGithubRelease) { if(preferenceManager.isAnalyticsEnabled && !isGithubRelease) {
// Download/App Install Event for F-Droid builds // Download/App Install Event for F-Droid builds
TrackHelper.track().download().with(tracker) TrackHelper.track().download().with(tracker)
} }
@ -246,9 +248,10 @@ class MainActivity : ComponentActivity() {
dependencies = object : SpotiFlyerRoot.Dependencies{ dependencies = object : SpotiFlyerRoot.Dependencies{
override val storeFactory = LoggingStoreFactory(DefaultStoreFactory) override val storeFactory = LoggingStoreFactory(DefaultStoreFactory)
override val database = this@MainActivity.dir.db override val database = this@MainActivity.dir.db
override val fetchPlatformQueryResult = this@MainActivity.fetcher override val fetchQuery = this@MainActivity.fetcher
override val directories: Dir = this@MainActivity.dir override val dir: Dir = this@MainActivity.dir
override val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>> = trackStatusFlow override val preferenceManager = this@MainActivity.preferenceManager
override val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> = trackStatusFlow
override val actions = object: Actions { override val actions = object: Actions {
override val platformActions = object : PlatformActions { override val platformActions = object : PlatformActions {
@ -316,7 +319,7 @@ class MainActivity : ComponentActivity() {
* */ * */
override val analytics = object: Analytics { override val analytics = object: Analytics {
override fun appLaunchEvent() { override fun appLaunchEvent() {
if(dir.isAnalyticsEnabled){ if(preferenceManager.isAnalyticsEnabled){
TrackHelper.track() TrackHelper.track()
.event("events","App_Launch") .event("events","App_Launch")
.name("App Launch").with(tracker) .name("App Launch").with(tracker)
@ -324,7 +327,7 @@ class MainActivity : ComponentActivity() {
} }
override fun homeScreenVisit() { override fun homeScreenVisit() {
if(dir.isAnalyticsEnabled){ if(preferenceManager.isAnalyticsEnabled){
// HomeScreen Visit Event // HomeScreen Visit Event
TrackHelper.track().screen("/main_activity/home_screen") TrackHelper.track().screen("/main_activity/home_screen")
.title("HomeScreen").with(tracker) .title("HomeScreen").with(tracker)
@ -332,7 +335,7 @@ class MainActivity : ComponentActivity() {
} }
override fun listScreenVisit() { override fun listScreenVisit() {
if(dir.isAnalyticsEnabled){ if(preferenceManager.isAnalyticsEnabled){
// ListScreen Visit Event // ListScreen Visit Event
TrackHelper.track().screen("/main_activity/list_screen") TrackHelper.track().screen("/main_activity/list_screen")
.title("ListScreen").with(tracker) .title("ListScreen").with(tracker)
@ -340,7 +343,7 @@ class MainActivity : ComponentActivity() {
} }
override fun donationDialogVisit() { override fun donationDialogVisit() {
if (dir.isAnalyticsEnabled) { if (preferenceManager.isAnalyticsEnabled) {
// Donation Dialog Open Event // Donation Dialog Open Event
TrackHelper.track().screen("/main_activity/donation_dialog") TrackHelper.track().screen("/main_activity/donation_dialog")
.title("DonationDialog").with(tracker) .title("DonationDialog").with(tracker)
@ -384,7 +387,7 @@ class MainActivity : ComponentActivity() {
val f = File(path) val f = File(path)
if (f.canWrite()) { if (f.canWrite()) {
// hell yeah :) // hell yeah :)
dir.setDownloadDirectory(path) preferenceManager.setDownloadDirectory(path)
showPopUpMessage( showPopUpMessage(
"Download Directory Set to:\n${dir.defaultDir()} " "Download Directory Set to:\n${dir.defaultDir()} "
) )

View File

@ -33,12 +33,17 @@ allprojects {
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach { tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"
useIR = true freeCompilerArgs = freeCompilerArgs + "-Xopt-in=kotlin.RequiresOptIn"
} }
} }
afterEvaluate { afterEvaluate {
project.extensions.findByType<org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension>()?.let { kmpExt -> project.extensions.findByType<org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension>()?.let { kmpExt ->
kmpExt.sourceSets.removeAll { it.name == "androidAndroidTestRelease" } kmpExt.sourceSets.run {
all {
languageSettings.useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi")
}
removeAll { it.name == "androidAndroidTestRelease" }
}
} }
} }
} }

View File

@ -60,6 +60,10 @@ object HostOS {
val isLinux = hostOs.startsWith("Linux",true) val isLinux = hostOs.startsWith("Linux",true)
} }
object MultiPlatformSettings {
const val dep = "com.russhwolf:multiplatform-settings-no-arg:0.7.7"
}
object Koin { object Koin {
val core = "io.insert-koin:koin-core:${Versions.koin}" val core = "io.insert-koin:koin-core:${Versions.koin}"
val test = "io.insert-koin:koin-test:${Versions.koin}" val test = "io.insert-koin:koin-test:${Versions.koin}"

View File

@ -10,6 +10,11 @@ sealed class SpotiFlyerException(override val message: String): Exception(messag
override val message: String = "MP3 Converter unreachable, probably BUSY ! \nCAUSE:$extraInfo" override val message: String = "MP3 Converter unreachable, probably BUSY ! \nCAUSE:$extraInfo"
): SpotiFlyerException(message) ): SpotiFlyerException(message)
data class UnknownReason(
val exception: Throwable? = null,
override val message: String = "Unknown Error"
): SpotiFlyerException(message)
data class NoMatchFound( data class NoMatchFound(
val trackName: String? = null, val trackName: String? = null,
override val message: String = "$trackName : NO Match Found!" override val message: String = "$trackName : NO Match Found!"

View File

@ -32,7 +32,7 @@ kotlin {
implementation(project(":common:database")) implementation(project(":common:database"))
implementation("org.jetbrains.kotlinx:atomicfu:0.16.1") implementation("org.jetbrains.kotlinx:atomicfu:0.16.1")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.2.1") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.2.1")
implementation("com.russhwolf:multiplatform-settings-no-arg:0.7.7") api(MultiPlatformSettings.dep)
implementation(Extras.youtubeDownloader) implementation(Extras.youtubeDownloader)
implementation(Extras.fuzzyWuzzy) implementation(Extras.fuzzyWuzzy)
implementation(MVIKotlin.rx) implementation(MVIKotlin.rx)

View File

@ -22,8 +22,8 @@ import android.os.Environment
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.mpatric.mp3agic.Mp3File import com.mpatric.mp3agic.Mp3File
import com.russhwolf.settings.Settings
import com.shabinder.common.database.SpotiFlyerDatabase import com.shabinder.common.database.SpotiFlyerDatabase
import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.di.utils.ParallelExecutor import com.shabinder.common.di.utils.ParallelExecutor
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods import com.shabinder.common.models.methods
@ -43,7 +43,7 @@ import java.net.URL
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
actual class Dir actual constructor( actual class Dir actual constructor(
private val logger: Kermit, private val logger: Kermit,
settingsPref: Settings, private val preferenceManager: PreferenceManager,
spotiFlyerDatabase: SpotiFlyerDatabase, spotiFlyerDatabase: SpotiFlyerDatabase,
) { ) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@ -54,7 +54,7 @@ actual class Dir actual constructor(
actual fun imageCacheDir(): String = methods.value.platformActions.imageCacheDir actual fun imageCacheDir(): String = methods.value.platformActions.imageCacheDir
// fun call in order to always access Updated Value // fun call in order to always access Updated Value
actual fun defaultDir(): String = (settings.getStringOrNull(DirKey) ?: defaultBaseDir) + actual fun defaultDir(): String = (preferenceManager.downloadDir ?: defaultBaseDir) +
File.separator + "SpotiFlyer" + File.separator File.separator + "SpotiFlyer" + File.separator
actual fun isPresent(path: String): Boolean = File(path).exists() actual fun isPresent(path: String): Boolean = File(path).exists()
@ -202,5 +202,4 @@ actual class Dir actual constructor(
private val parallelExecutor = ParallelExecutor(Dispatchers.IO) private val parallelExecutor = ParallelExecutor(Dispatchers.IO)
actual val db: Database? = spotiFlyerDatabase.instance actual val db: Database? = spotiFlyerDatabase.instance
actual val settings: Settings = settingsPref
} }

View File

@ -1,8 +1,7 @@
package com.shabinder.common.di.saavn package com.shabinder.common.di.providers.requests.saavn
import android.annotation.SuppressLint import android.annotation.SuppressLint
import io.ktor.util.InternalAPI import io.ktor.util.*
import io.ktor.util.decodeBase64Bytes
import java.security.SecureRandom import java.security.SecureRandom
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.SecretKey import javax.crypto.SecretKey

View File

@ -20,13 +20,8 @@ import co.touchlab.kermit.Kermit
import com.russhwolf.settings.Settings import com.russhwolf.settings.Settings
import com.shabinder.common.database.databaseModule import com.shabinder.common.database.databaseModule
import com.shabinder.common.database.getLogger import com.shabinder.common.database.getLogger
import com.shabinder.common.di.audioToMp3.AudioToMp3 import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.di.providers.GaanaProvider import com.shabinder.common.di.providers.providersModule
import com.shabinder.common.di.providers.SaavnProvider
import com.shabinder.common.di.providers.SpotifyProvider
import com.shabinder.common.di.providers.YoutubeMp3
import com.shabinder.common.di.providers.YoutubeMusic
import com.shabinder.common.di.providers.YoutubeProvider
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.features.* import io.ktor.client.features.*
import io.ktor.client.features.json.* import io.ktor.client.features.json.*
@ -42,7 +37,11 @@ import kotlin.native.concurrent.ThreadLocal
fun initKoin(enableNetworkLogs: Boolean = false, appDeclaration: KoinAppDeclaration = {}) = fun initKoin(enableNetworkLogs: Boolean = false, appDeclaration: KoinAppDeclaration = {}) =
startKoin { startKoin {
appDeclaration() appDeclaration()
modules(commonModule(enableNetworkLogs = enableNetworkLogs), databaseModule()) modules(
commonModule(enableNetworkLogs = enableNetworkLogs),
providersModule(),
databaseModule()
)
} }
// Called by IOS // Called by IOS
@ -52,16 +51,9 @@ fun commonModule(enableNetworkLogs: Boolean) = module {
single { createHttpClient(enableNetworkLogs = enableNetworkLogs) } single { createHttpClient(enableNetworkLogs = enableNetworkLogs) }
single { Dir(get(), get(), get()) } single { Dir(get(), get(), get()) }
single { Settings() } single { Settings() }
single { PreferenceManager(get()) }
single { Kermit(getLogger()) } single { Kermit(getLogger()) }
single { TokenStore(get(), get()) } single { TokenStore(get(), get()) }
single { AudioToMp3(get(), get()) }
single { SpotifyProvider(get(), get(), get()) }
single { GaanaProvider(get(), get(), get()) }
single { SaavnProvider(get(), get(), get(), get()) }
single { YoutubeProvider(get(), get(), get()) }
single { YoutubeMp3(get(), get()) }
single { YoutubeMusic(get(), get(), get(), get(), get()) }
single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get(), get(), get(), get()) }
} }
@ThreadLocal @ThreadLocal

View File

@ -17,8 +17,8 @@
package com.shabinder.common.di package com.shabinder.common.di
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.russhwolf.settings.Settings
import com.shabinder.common.database.SpotiFlyerDatabase import com.shabinder.common.database.SpotiFlyerDatabase
import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.di.utils.removeIllegalChars import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
@ -30,18 +30,12 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlin.math.roundToInt import kotlin.math.roundToInt
const val DirKey = "downloadDir"
const val AnalyticsKey = "analytics"
const val FirstLaunch = "firstLaunch"
const val DonationInterval = "donationInterval"
expect class Dir( expect class Dir(
logger: Kermit, logger: Kermit,
settingsPref: Settings, preferenceManager: PreferenceManager,
spotiFlyerDatabase: SpotiFlyerDatabase, spotiFlyerDatabase: SpotiFlyerDatabase,
) { ) {
val db: Database? val db: Database?
val settings: Settings
fun isPresent(path: String): Boolean fun isPresent(path: String): Boolean
fun fileSeparator(): String fun fileSeparator(): String
fun defaultDir(): String fun defaultDir(): String
@ -54,22 +48,6 @@ expect class Dir(
fun addToLibrary(path: String) fun addToLibrary(path: String)
} }
val Dir.isAnalyticsEnabled get() = settings.getBooleanOrNull(AnalyticsKey) ?: false
fun Dir.toggleAnalytics(enabled: Boolean) = settings.putBoolean(AnalyticsKey, enabled)
fun Dir.setDownloadDirectory(newBasePath: String) = settings.putString(DirKey, newBasePath)
val Dir.getDonationOffset: Int get() = (settings.getIntOrNull(DonationInterval) ?: 3).also {
// Min. Donation Asking Interval is `3`
if (it < 3) setDonationOffset(3) else setDonationOffset(it - 1)
}
fun Dir.setDonationOffset(offset: Int = 5) = settings.putInt(DonationInterval, offset)
val Dir.isFirstLaunch get() = settings.getBooleanOrNull(FirstLaunch) ?: true
fun Dir.firstLaunchDone() {
settings.putBoolean(FirstLaunch, false)
}
/* /*
* Call this function at startup! * Call this function at startup!
* */ * */

View File

@ -18,7 +18,6 @@ package com.shabinder.common.di
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.database.DownloadRecordDatabaseQueries import com.shabinder.common.database.DownloadRecordDatabaseQueries
import com.shabinder.common.di.audioToMp3.AudioToMp3
import com.shabinder.common.di.providers.GaanaProvider import com.shabinder.common.di.providers.GaanaProvider
import com.shabinder.common.di.providers.SaavnProvider import com.shabinder.common.di.providers.SaavnProvider
import com.shabinder.common.di.providers.SpotifyProvider import com.shabinder.common.di.providers.SpotifyProvider
@ -26,6 +25,7 @@ import com.shabinder.common.di.providers.YoutubeMp3
import com.shabinder.common.di.providers.YoutubeMusic import com.shabinder.common.di.providers.YoutubeMusic
import com.shabinder.common.di.providers.YoutubeProvider import com.shabinder.common.di.providers.YoutubeProvider
import com.shabinder.common.di.providers.get import com.shabinder.common.di.providers.get
import com.shabinder.common.di.providers.requests.audioToMp3.AudioToMp3
import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.SpotiFlyerException import com.shabinder.common.models.SpotiFlyerException
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
@ -35,6 +35,7 @@ import com.shabinder.common.models.event.coroutines.flatMapError
import com.shabinder.common.models.event.coroutines.success import com.shabinder.common.models.event.coroutines.success
import com.shabinder.common.models.spotify.Source import com.shabinder.common.models.spotify.Source
import com.shabinder.common.requireNotNull import com.shabinder.common.requireNotNull
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -137,6 +138,7 @@ class FetchPlatformQueryResult(
} }
} }
@OptIn(DelicateCoroutinesApi::class)
private fun addToDatabaseAsync(link: String, result: PlatformQueryResult) { private fun addToDatabaseAsync(link: String, result: PlatformQueryResult) {
GlobalScope.launch(dispatcherIO) { GlobalScope.launch(dispatcherIO) {
db?.add( db?.add(

View File

@ -18,7 +18,7 @@ package com.shabinder.common.di
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.database.TokenDBQueries import com.shabinder.common.database.TokenDBQueries
import com.shabinder.common.di.spotify.authenticateSpotify import com.shabinder.common.di.providers.requests.spotify.authenticateSpotify
import com.shabinder.common.models.spotify.TokenData import com.shabinder.common.models.spotify.TokenData
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View File

@ -0,0 +1,35 @@
package com.shabinder.common.di.preference
import com.russhwolf.settings.Settings
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"
}
/* ANALYTICS */
val isAnalyticsEnabled get() = getBooleanOrNull(AnalyticsKey) ?: false
fun toggleAnalytics(enabled: Boolean) = putBoolean(AnalyticsKey, enabled)
/* DOWNLOAD DIRECTORY */
val downloadDir get() = getStringOrNull(DirKey)
fun setDownloadDirectory(newBasePath: String) = putString(DirKey, newBasePath)
/* OFFSET FOR WHEN TO ASK FOR SUPPORT */
val getDonationOffset: Int get() = (getIntOrNull(DonationInterval) ?: 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)
/* TO CHECK IF THIS IS APP's FIRST LAUNCH */
val isFirstLaunch get() = getBooleanOrNull(FirstLaunch) ?: true
fun firstLaunchDone() = putBoolean(FirstLaunch, false)
}

View File

@ -19,7 +19,7 @@ package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.di.finalOutputDir import com.shabinder.common.di.finalOutputDir
import com.shabinder.common.di.gaana.GaanaRequests import com.shabinder.common.di.providers.requests.gaana.GaanaRequests
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.PlatformQueryResult
import com.shabinder.common.models.SpotiFlyerException import com.shabinder.common.models.SpotiFlyerException

View File

@ -0,0 +1,16 @@
package com.shabinder.common.di.providers
import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.providers.requests.audioToMp3.AudioToMp3
import org.koin.dsl.module
fun providersModule() = module {
single { AudioToMp3(get(), get()) }
single { SpotifyProvider(get(), get(), get()) }
single { GaanaProvider(get(), get(), get()) }
single { SaavnProvider(get(), get(), get(), get()) }
single { YoutubeProvider(get(), get(), get()) }
single { YoutubeMp3(get(), get()) }
single { YoutubeMusic(get(), get(), get(), get(), get()) }
single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get(), get(), get(), get()) }
}

View File

@ -2,9 +2,9 @@ package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.di.audioToMp3.AudioToMp3
import com.shabinder.common.di.finalOutputDir import com.shabinder.common.di.finalOutputDir
import com.shabinder.common.di.saavn.JioSaavnRequests import com.shabinder.common.di.providers.requests.audioToMp3.AudioToMp3
import com.shabinder.common.di.providers.requests.saavn.JioSaavnRequests
import com.shabinder.common.di.utils.removeIllegalChars import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.PlatformQueryResult

View File

@ -22,8 +22,8 @@ import com.shabinder.common.di.TokenStore
import com.shabinder.common.di.createHttpClient import com.shabinder.common.di.createHttpClient
import com.shabinder.common.di.finalOutputDir import com.shabinder.common.di.finalOutputDir
import com.shabinder.common.di.globalJson import com.shabinder.common.di.globalJson
import com.shabinder.common.di.spotify.SpotifyRequests import com.shabinder.common.di.providers.requests.spotify.SpotifyRequests
import com.shabinder.common.di.spotify.authenticateSpotify import com.shabinder.common.di.providers.requests.spotify.authenticateSpotify
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.NativeAtomicReference import com.shabinder.common.models.NativeAtomicReference
import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.PlatformQueryResult

View File

@ -17,7 +17,7 @@
package com.shabinder.common.di.providers package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.di.youtubeMp3.Yt1sMp3 import com.shabinder.common.di.providers.requests.youtubeMp3.Yt1sMp3
import com.shabinder.common.models.corsApi import com.shabinder.common.models.corsApi
import com.shabinder.common.models.event.coroutines.SuspendableEvent import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.models.event.coroutines.map import com.shabinder.common.models.event.coroutines.map

View File

@ -17,7 +17,7 @@
package com.shabinder.common.di.providers package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.di.audioToMp3.AudioToMp3 import com.shabinder.common.di.providers.requests.audioToMp3.AudioToMp3
import com.shabinder.common.models.SpotiFlyerException import com.shabinder.common.models.SpotiFlyerException
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.YoutubeTrack import com.shabinder.common.models.YoutubeTrack

View File

@ -1,4 +1,4 @@
package com.shabinder.common.di.audioToMp3 package com.shabinder.common.di.providers.requests.audioToMp3
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.models.AudioQuality import com.shabinder.common.models.AudioQuality

View File

@ -14,7 +14,7 @@
* * along with this program. If not, see <https://www.gnu.org/licenses/>. * * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.shabinder.common.di.gaana package com.shabinder.common.di.providers.requests.gaana
import com.shabinder.common.models.corsApi import com.shabinder.common.models.corsApi
import com.shabinder.common.models.gaana.GaanaAlbum import com.shabinder.common.models.gaana.GaanaAlbum

View File

@ -1,8 +1,8 @@
package com.shabinder.common.di.saavn package com.shabinder.common.di.providers.requests.saavn
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.di.audioToMp3.AudioToMp3
import com.shabinder.common.di.globalJson import com.shabinder.common.di.globalJson
import com.shabinder.common.di.providers.requests.audioToMp3.AudioToMp3
import com.shabinder.common.models.corsApi import com.shabinder.common.models.corsApi
import com.shabinder.common.models.event.coroutines.SuspendableEvent import com.shabinder.common.models.event.coroutines.SuspendableEvent
import com.shabinder.common.models.event.coroutines.map import com.shabinder.common.models.event.coroutines.map

View File

@ -1,4 +1,6 @@
package com.shabinder.common.di.saavn package com.shabinder.common.di.providers.requests.saavn
import com.shabinder.common.di.utils.unescape
expect suspend fun decryptURL(url: String): String expect suspend fun decryptURL(url: String): String

View File

@ -14,7 +14,7 @@
* * along with this program. If not, see <https://www.gnu.org/licenses/>. * * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.shabinder.common.di.spotify package com.shabinder.common.di.providers.requests.spotify
import com.shabinder.common.di.globalJson import com.shabinder.common.di.globalJson
import com.shabinder.common.models.SpotiFlyerException import com.shabinder.common.models.SpotiFlyerException

View File

@ -14,7 +14,7 @@
* * along with this program. If not, see <https://www.gnu.org/licenses/>. * * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.shabinder.common.di.spotify package com.shabinder.common.di.providers.requests.spotify
import com.shabinder.common.models.NativeAtomicReference import com.shabinder.common.models.NativeAtomicReference
import com.shabinder.common.models.corsApi import com.shabinder.common.models.corsApi

View File

@ -14,7 +14,7 @@
* * along with this program. If not, see <https://www.gnu.org/licenses/>. * * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.shabinder.common.di.youtubeMp3 package com.shabinder.common.di.providers.requests.youtubeMp3
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.models.corsApi import com.shabinder.common.models.corsApi

View File

@ -1,4 +1,4 @@
package com.shabinder.common.di.saavn package com.shabinder.common.di.utils
/* /*
* JSON UTILS * JSON UTILS

View File

@ -19,6 +19,7 @@ package com.shabinder.common.di
import com.shabinder.common.di.utils.ParallelExecutor import com.shabinder.common.di.utils.ParallelExecutor
import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.SpotiFlyerException
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -40,15 +41,15 @@ actual suspend fun downloadTracks(
) { ) {
list.forEach { trackDetails -> list.forEach { trackDetails ->
DownloadScope.execute { // Send Download to Pool. DownloadScope.execute { // Send Download to Pool.
val url = fetcher.findMp3DownloadLink(trackDetails) fetcher.findMp3DownloadLink(trackDetails).fold(
if (!url.isNullOrBlank()) { // Successfully Grabbed Mp3 URL success = { url ->
downloadFile(url).collect { downloadFile(url).collect {
when (it) { when (it) {
is DownloadResult.Error -> { is DownloadResult.Error -> {
DownloadProgressFlow.emit( DownloadProgressFlow.emit(
DownloadProgressFlow.replayCache.getOrElse( DownloadProgressFlow.replayCache.getOrElse(
0 0
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Failed) } ) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Failed(it.cause ?: SpotiFlyerException.UnknownReason(it.cause))) }
) )
} }
is DownloadResult.Progress -> { is DownloadResult.Progress -> {
@ -68,13 +69,15 @@ actual suspend fun downloadTracks(
} }
} }
} }
} else { },
failure = { error ->
DownloadProgressFlow.emit( DownloadProgressFlow.emit(
DownloadProgressFlow.replayCache.getOrElse( DownloadProgressFlow.replayCache.getOrElse(
0 0
) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Failed) } ) { hashMapOf() }.apply { set(trackDetails.title, DownloadStatus.Failed(error)) }
)
}
) )
} }
} }
}
} }

View File

@ -20,8 +20,8 @@ import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.mpatric.mp3agic.Mp3File import com.mpatric.mp3agic.Mp3File
import com.russhwolf.settings.Settings
import com.shabinder.common.database.SpotiFlyerDatabase import com.shabinder.common.database.SpotiFlyerDatabase
import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.database.Database import com.shabinder.database.Database
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -40,7 +40,7 @@ import javax.imageio.ImageIO
actual class Dir actual constructor( actual class Dir actual constructor(
private val logger: Kermit, private val logger: Kermit,
settingsPref: Settings, private val preferenceManager: PreferenceManager,
spotiFlyerDatabase: SpotiFlyerDatabase, spotiFlyerDatabase: SpotiFlyerDatabase,
) { ) {
@ -55,7 +55,7 @@ actual class Dir actual constructor(
private val defaultBaseDir = System.getProperty("user.home") private val defaultBaseDir = System.getProperty("user.home")
actual fun defaultDir(): String = (settings.getStringOrNull(DirKey) ?: defaultBaseDir) + fileSeparator() + actual fun defaultDir(): String = (preferenceManager.downloadDir ?: defaultBaseDir) + fileSeparator() +
"SpotiFlyer" + fileSeparator() "SpotiFlyer" + fileSeparator()
actual fun isPresent(path: String): Boolean = File(path).exists() actual fun isPresent(path: String): Boolean = File(path).exists()
@ -199,7 +199,6 @@ actual class Dir actual constructor(
} }
actual val db: Database? = spotiFlyerDatabase.instance actual val db: Database? = spotiFlyerDatabase.instance
actual val settings: Settings = settingsPref
} }
fun BufferedImage.toImageBitmap() = Image.makeFromEncoded( fun BufferedImage.toImageBitmap() = Image.makeFromEncoded(

View File

@ -1,7 +1,6 @@
package com.shabinder.common.di.saavn package com.shabinder.common.di.providers.requests.saavn
import io.ktor.util.InternalAPI import io.ktor.util.*
import io.ktor.util.decodeBase64Bytes
import java.security.SecureRandom import java.security.SecureRandom
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.SecretKey import javax.crypto.SecretKey

View File

@ -1,8 +1,8 @@
package com.shabinder.common.di package com.shabinder.common.di
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.russhwolf.settings.Settings
import com.shabinder.common.database.SpotiFlyerDatabase import com.shabinder.common.database.SpotiFlyerDatabase
import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.database.Database import com.shabinder.database.Database
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -24,7 +24,7 @@ import platform.UIKit.UIImageJPEGRepresentation
actual class Dir actual constructor( actual class Dir actual constructor(
val logger: Kermit, val logger: Kermit,
settingsPref: Settings, private val preferenceManager: PreferenceManager,
spotiFlyerDatabase: SpotiFlyerDatabase, spotiFlyerDatabase: SpotiFlyerDatabase,
) { ) {
@ -35,7 +35,7 @@ actual class Dir actual constructor(
private val defaultBaseDir = NSFileManager.defaultManager.URLForDirectory(NSMusicDirectory, NSUserDomainMask, null, true, null)!!.path!! private val defaultBaseDir = NSFileManager.defaultManager.URLForDirectory(NSMusicDirectory, NSUserDomainMask, null, true, null)!!.path!!
// TODO Error Handling // TODO Error Handling
actual fun defaultDir(): String = (settings.getStringOrNull(DirKey) ?: defaultBaseDir) + actual fun defaultDir(): String = (preferenceManager.downloadDir ?: defaultBaseDir) +
fileSeparator() + "SpotiFlyer" + fileSeparator() fileSeparator() + "SpotiFlyer" + fileSeparator()
private val defaultDirURL: NSURL by lazy { private val defaultDirURL: NSURL by lazy {
@ -176,6 +176,5 @@ actual class Dir actual constructor(
// TODO // TODO
} }
actual val settings: Settings = settingsPref
actual val db: Database? = spotiFlyerDatabase.instance actual val db: Database? = spotiFlyerDatabase.instance
} }

View File

@ -18,6 +18,7 @@ package com.shabinder.common.di
import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.SpotiFlyerException
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -41,8 +42,8 @@ actual suspend fun downloadTracks(
list.forEach { track -> list.forEach { track ->
withContext(dispatcherIO) { withContext(dispatcherIO) {
allTracksStatus[track.title] = DownloadStatus.Queued allTracksStatus[track.title] = DownloadStatus.Queued
val url = fetcher.findMp3DownloadLink(track) fetcher.findMp3DownloadLink(track).fold(
if (!url.isNullOrBlank()) { // Successfully Grabbed Mp3 URL success = { url ->
downloadFile(url).collect { downloadFile(url).collect {
when (it) { when (it) {
is DownloadResult.Success -> { is DownloadResult.Success -> {
@ -50,7 +51,7 @@ actual suspend fun downloadTracks(
dir.saveFileWithMetadata(it.byteArray, track) {} dir.saveFileWithMetadata(it.byteArray, track) {}
} }
is DownloadResult.Error -> { is DownloadResult.Error -> {
allTracksStatus[track.title] = DownloadStatus.Failed allTracksStatus[track.title] = DownloadStatus.Failed(it.cause ?: SpotiFlyerException.UnknownReason(it.cause))
println("Download Error: ${track.title}") println("Download Error: ${track.title}")
} }
is DownloadResult.Progress -> { is DownloadResult.Progress -> {
@ -60,10 +61,12 @@ actual suspend fun downloadTracks(
} }
DownloadProgressFlow.emit(allTracksStatus) DownloadProgressFlow.emit(allTracksStatus)
} }
} else { },
allTracksStatus[track.title] = DownloadStatus.Failed failure = { error ->
allTracksStatus[track.title] = DownloadStatus.Failed(error)
DownloadProgressFlow.emit(allTracksStatus) DownloadProgressFlow.emit(allTracksStatus)
} }
)
} }
} }
} }

View File

@ -17,13 +17,13 @@
package com.shabinder.common.di package com.shabinder.common.di
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.russhwolf.settings.Settings
import com.shabinder.common.database.SpotiFlyerDatabase import com.shabinder.common.database.SpotiFlyerDatabase
import com.shabinder.common.di.gaana.corsApi import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.di.utils.removeIllegalChars import com.shabinder.common.di.utils.removeIllegalChars
import com.shabinder.common.models.DownloadResult import com.shabinder.common.models.DownloadResult
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.corsApi
import com.shabinder.database.Database import com.shabinder.database.Database
import kotlinext.js.Object import kotlinext.js.Object
import kotlinext.js.js import kotlinext.js.js
@ -34,7 +34,7 @@ import org.w3c.dom.ImageBitmap
actual class Dir actual constructor( actual class Dir actual constructor(
private val logger: Kermit, private val logger: Kermit,
settingsPref: Settings, private val preferenceManager: PreferenceManager,
spotiFlyerDatabase: SpotiFlyerDatabase, spotiFlyerDatabase: SpotiFlyerDatabase,
) { ) {
/*init { /*init {
@ -116,7 +116,6 @@ actual class Dir actual constructor(
private suspend fun freshImage(url: String): ImageBitmap? = null private suspend fun freshImage(url: String): ImageBitmap? = null
actual val db: Database? = spotiFlyerDatabase.instance actual val db: Database? = spotiFlyerDatabase.instance
actual val settings: Settings = settingsPref
} }
fun ByteArray.toArrayBuffer(): ArrayBuffer { fun ByteArray.toArrayBuffer(): ArrayBuffer {

View File

@ -1,4 +1,4 @@
package com.shabinder.common.di.saavn package com.shabinder.common.di.providers.requests.saavn
actual suspend fun decryptURL(url: String): String { actual suspend fun decryptURL(url: String): String {
TODO("Not yet implemented") TODO("Not yet implemented")

View File

@ -22,6 +22,7 @@ import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.di.FetchPlatformQueryResult import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture
import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.list.integration.SpotiFlyerListImpl import com.shabinder.common.list.integration.SpotiFlyerListImpl
import com.shabinder.common.models.Consumer import com.shabinder.common.models.Consumer
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
@ -67,6 +68,7 @@ interface SpotiFlyerList {
val storeFactory: StoreFactory val storeFactory: StoreFactory
val fetchQuery: FetchPlatformQueryResult val fetchQuery: FetchPlatformQueryResult
val dir: Dir val dir: Dir
val preferenceManager: PreferenceManager
val link: String val link: String
val listOutput: Consumer<Output> val listOutput: Consumer<Output>
val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>> val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>

View File

@ -22,7 +22,6 @@ import com.arkivanov.decompose.lifecycle.doOnResume
import com.arkivanov.decompose.value.Value import com.arkivanov.decompose.value.Value
import com.shabinder.common.caching.Cache import com.shabinder.common.caching.Cache
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture
import com.shabinder.common.di.setDonationOffset
import com.shabinder.common.di.utils.asValue import com.shabinder.common.di.utils.asValue
import com.shabinder.common.list.SpotiFlyerList import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.list.SpotiFlyerList.Dependencies import com.shabinder.common.list.SpotiFlyerList.Dependencies
@ -48,6 +47,7 @@ internal class SpotiFlyerListImpl(
instanceKeeper.getStore { instanceKeeper.getStore {
SpotiFlyerListStoreProvider( SpotiFlyerListStoreProvider(
dir = this.dir, dir = this.dir,
preferenceManager = preferenceManager,
storeFactory = storeFactory, storeFactory = storeFactory,
fetchQuery = fetchQuery, fetchQuery = fetchQuery,
downloadProgressFlow = downloadProgressFlow, downloadProgressFlow = downloadProgressFlow,
@ -79,7 +79,7 @@ internal class SpotiFlyerListImpl(
} }
override fun snoozeDonationDialog() { override fun snoozeDonationDialog() {
dir.setDonationOffset(offset = 10) preferenceManager.setDonationOffset(offset = 10)
} }
override suspend fun loadImage(url: String, isCover: Boolean): Picture { override suspend fun loadImage(url: String, isCover: Boolean): Picture {

View File

@ -24,7 +24,7 @@ import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.di.FetchPlatformQueryResult import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.downloadTracks import com.shabinder.common.di.downloadTracks
import com.shabinder.common.di.getDonationOffset import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.list.SpotiFlyerList.State import com.shabinder.common.list.SpotiFlyerList.State
import com.shabinder.common.list.store.SpotiFlyerListStore.Intent import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
import com.shabinder.common.models.DownloadStatus import com.shabinder.common.models.DownloadStatus
@ -36,6 +36,7 @@ import kotlinx.coroutines.flow.collect
internal class SpotiFlyerListStoreProvider( internal class SpotiFlyerListStoreProvider(
private val dir: Dir, private val dir: Dir,
private val preferenceManager: PreferenceManager,
private val storeFactory: StoreFactory, private val storeFactory: StoreFactory,
private val fetchQuery: FetchPlatformQueryResult, private val fetchQuery: FetchPlatformQueryResult,
private val link: String, private val link: String,
@ -68,7 +69,7 @@ internal class SpotiFlyerListStoreProvider(
dir.db?.downloadRecordDatabaseQueries?.getLastInsertId()?.executeAsOneOrNull()?.also { dir.db?.downloadRecordDatabaseQueries?.getLastInsertId()?.executeAsOneOrNull()?.also {
// See if It's Time we can request for support for maintaining this project or not // See if It's Time we can request for support for maintaining this project or not
fetchQuery.logger.d(message = { "Database List Last ID: $it" }, tag = "Database Last ID") fetchQuery.logger.d(message = { "Database List Last ID: $it" }, tag = "Database Last ID")
val offset = dir.getDonationOffset val offset = preferenceManager.getDonationOffset
dispatch( dispatch(
Result.AskForSupport( Result.AskForSupport(
// Every 3rd Interval or After some offset // Every 3rd Interval or After some offset

View File

@ -21,6 +21,7 @@ import com.arkivanov.decompose.value.Value
import com.arkivanov.mvikotlin.core.store.StoreFactory import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture
import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.main.integration.SpotiFlyerMainImpl import com.shabinder.common.main.integration.SpotiFlyerMainImpl
import com.shabinder.common.models.Consumer import com.shabinder.common.models.Consumer
import com.shabinder.common.models.DownloadRecord import com.shabinder.common.models.DownloadRecord
@ -63,6 +64,7 @@ interface SpotiFlyerMain {
val storeFactory: StoreFactory val storeFactory: StoreFactory
val database: Database? val database: Database?
val dir: Dir val dir: Dir
val preferenceManager: PreferenceManager
val mainAnalytics: Analytics val mainAnalytics: Analytics
} }

View File

@ -23,7 +23,10 @@ import com.shabinder.common.caching.Cache
import com.shabinder.common.di.Picture import com.shabinder.common.di.Picture
import com.shabinder.common.di.utils.asValue import com.shabinder.common.di.utils.asValue
import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.* import com.shabinder.common.main.SpotiFlyerMain.Dependencies
import com.shabinder.common.main.SpotiFlyerMain.HomeCategory
import com.shabinder.common.main.SpotiFlyerMain.Output
import com.shabinder.common.main.SpotiFlyerMain.State
import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent
import com.shabinder.common.main.store.SpotiFlyerMainStoreProvider import com.shabinder.common.main.store.SpotiFlyerMainStoreProvider
import com.shabinder.common.main.store.getStore import com.shabinder.common.main.store.getStore
@ -41,6 +44,7 @@ internal class SpotiFlyerMainImpl(
private val store = private val store =
instanceKeeper.getStore { instanceKeeper.getStore {
SpotiFlyerMainStoreProvider( SpotiFlyerMainStoreProvider(
preferenceManager = preferenceManager,
storeFactory = storeFactory, storeFactory = storeFactory,
database = database, database = database,
dir = dir dir = dir

View File

@ -22,8 +22,7 @@ import com.arkivanov.mvikotlin.core.store.Store
import com.arkivanov.mvikotlin.core.store.StoreFactory import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.di.isAnalyticsEnabled import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.di.toggleAnalytics
import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.SpotiFlyerMain.State import com.shabinder.common.main.SpotiFlyerMain.State
import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent
@ -39,6 +38,7 @@ import kotlinx.coroutines.flow.map
internal class SpotiFlyerMainStoreProvider( internal class SpotiFlyerMainStoreProvider(
private val storeFactory: StoreFactory, private val storeFactory: StoreFactory,
private val preferenceManager: PreferenceManager,
private val dir: Dir, private val dir: Dir,
database: Database? database: Database?
) { ) {
@ -76,7 +76,7 @@ internal class SpotiFlyerMainStoreProvider(
private inner class ExecutorImpl : SuspendExecutor<Intent, Unit, State, Result, Nothing>() { private inner class ExecutorImpl : SuspendExecutor<Intent, Unit, State, Result, Nothing>() {
override suspend fun executeAction(action: Unit, getState: () -> State) { override suspend fun executeAction(action: Unit, getState: () -> State) {
dispatch(Result.ToggleAnalytics(dir.isAnalyticsEnabled)) dispatch(Result.ToggleAnalytics(preferenceManager.isAnalyticsEnabled))
updates?.collect { updates?.collect {
dispatch(Result.ItemsLoaded(it)) dispatch(Result.ItemsLoaded(it))
} }
@ -91,7 +91,7 @@ internal class SpotiFlyerMainStoreProvider(
is Intent.SelectCategory -> dispatch(Result.CategoryChanged(intent.category)) is Intent.SelectCategory -> dispatch(Result.CategoryChanged(intent.category))
is Intent.ToggleAnalytics -> { is Intent.ToggleAnalytics -> {
dispatch(Result.ToggleAnalytics(intent.enabled)) dispatch(Result.ToggleAnalytics(intent.enabled))
dir.toggleAnalytics(intent.enabled) preferenceManager.toggleAnalytics(intent.enabled)
} }
} }
} }

View File

@ -22,6 +22,7 @@ import com.arkivanov.decompose.value.Value
import com.arkivanov.mvikotlin.core.store.StoreFactory import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.di.FetchPlatformQueryResult import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.list.SpotiFlyerList import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.models.Actions import com.shabinder.common.models.Actions
@ -49,9 +50,10 @@ interface SpotiFlyerRoot {
interface Dependencies { interface Dependencies {
val storeFactory: StoreFactory val storeFactory: StoreFactory
val database: Database? val database: Database?
val fetchPlatformQueryResult: FetchPlatformQueryResult val fetchQuery: FetchPlatformQueryResult
val directories: Dir val dir: Dir
val downloadProgressReport: MutableSharedFlow<HashMap<String, DownloadStatus>> val preferenceManager: PreferenceManager
val downloadProgressFlow: MutableSharedFlow<HashMap<String, DownloadStatus>>
val actions: Actions val actions: Actions
val analytics: Analytics val analytics: Analytics
} }

View File

@ -27,7 +27,6 @@ import com.arkivanov.decompose.router
import com.arkivanov.decompose.statekeeper.Parcelable import com.arkivanov.decompose.statekeeper.Parcelable
import com.arkivanov.decompose.statekeeper.Parcelize import com.arkivanov.decompose.statekeeper.Parcelize
import com.arkivanov.decompose.value.Value import com.arkivanov.decompose.value.Value
import com.shabinder.common.di.Dir
import com.shabinder.common.di.dispatcherIO import com.shabinder.common.di.dispatcherIO
import com.shabinder.common.list.SpotiFlyerList import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.main.SpotiFlyerMain import com.shabinder.common.main.SpotiFlyerMain
@ -39,6 +38,7 @@ import com.shabinder.common.root.SpotiFlyerRoot.Analytics
import com.shabinder.common.root.SpotiFlyerRoot.Child import com.shabinder.common.root.SpotiFlyerRoot.Child
import com.shabinder.common.root.SpotiFlyerRoot.Dependencies import com.shabinder.common.root.SpotiFlyerRoot.Dependencies
import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks import com.shabinder.common.root.callbacks.SpotiFlyerRootCallBacks
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -77,7 +77,7 @@ internal class SpotiFlyerRootImpl(
instanceKeeper.ensureNeverFrozen() instanceKeeper.ensureNeverFrozen()
methods.value = dependencies.actions.freeze() methods.value = dependencies.actions.freeze()
/*Init App Launch & Authenticate Spotify Client*/ /*Init App Launch & Authenticate Spotify Client*/
initAppLaunchAndAuthenticateSpotify(dependencies.fetchPlatformQueryResult::authenticateSpotifyClient) initAppLaunchAndAuthenticateSpotify(dependencies.fetchQuery::authenticateSpotifyClient)
} }
private val router = private val router =
@ -128,6 +128,7 @@ internal class SpotiFlyerRootImpl(
} }
} }
@OptIn(DelicateCoroutinesApi::class)
private fun initAppLaunchAndAuthenticateSpotify(authenticator: suspend () -> Unit) { private fun initAppLaunchAndAuthenticateSpotify(authenticator: suspend () -> Unit) {
GlobalScope.launch(dispatcherIO) { GlobalScope.launch(dispatcherIO) {
analytics.appLaunchEvent() analytics.appLaunchEvent()
@ -150,10 +151,7 @@ private fun spotiFlyerMain(componentContext: ComponentContext, output: Consumer<
componentContext = componentContext, componentContext = componentContext,
dependencies = object : SpotiFlyerMain.Dependencies, Dependencies by dependencies { dependencies = object : SpotiFlyerMain.Dependencies, Dependencies by dependencies {
override val mainOutput: Consumer<SpotiFlyerMain.Output> = output override val mainOutput: Consumer<SpotiFlyerMain.Output> = output
override val dir: Dir = directories override val mainAnalytics = object : SpotiFlyerMain.Analytics , Analytics by analytics {}
override val mainAnalytics = object : SpotiFlyerMain.Analytics {
override fun donationDialogVisit() = analytics.donationDialogVisit()
}
} }
) )
@ -161,11 +159,8 @@ private fun spotiFlyerList(componentContext: ComponentContext, link: String, out
SpotiFlyerList( SpotiFlyerList(
componentContext = componentContext, componentContext = componentContext,
dependencies = object : SpotiFlyerList.Dependencies, Dependencies by dependencies { dependencies = object : SpotiFlyerList.Dependencies, Dependencies by dependencies {
override val fetchQuery = fetchPlatformQueryResult
override val dir: Dir = directories
override val link: String = link override val link: String = link
override val listOutput: Consumer<SpotiFlyerList.Output> = output override val listOutput: Consumer<SpotiFlyerList.Output> = output
override val downloadProgressFlow = downloadProgressReport override val listAnalytics = object : SpotiFlyerList.Analytics, Analytics by analytics {}
override val listAnalytics = object : SpotiFlyerList.Analytics {}
} }
) )

View File

@ -27,12 +27,21 @@ import com.arkivanov.decompose.extensions.compose.jetbrains.rememberRootComponen
import com.arkivanov.mvikotlin.core.lifecycle.LifecycleRegistry import com.arkivanov.mvikotlin.core.lifecycle.LifecycleRegistry
import com.arkivanov.mvikotlin.core.lifecycle.resume import com.arkivanov.mvikotlin.core.lifecycle.resume
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
import com.shabinder.common.di.* import com.shabinder.common.di.Dir
import com.shabinder.common.di.DownloadProgressFlow
import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.initKoin
import com.shabinder.common.di.isInternetAccessible
import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.models.Actions import com.shabinder.common.models.Actions
import com.shabinder.common.models.PlatformActions import com.shabinder.common.models.PlatformActions
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.root.SpotiFlyerRoot import com.shabinder.common.root.SpotiFlyerRoot
import com.shabinder.common.uikit.* import com.shabinder.common.uikit.SpotiFlyerColors
import com.shabinder.common.uikit.SpotiFlyerRootContent
import com.shabinder.common.uikit.SpotiFlyerShapes
import com.shabinder.common.uikit.SpotiFlyerTypography
import com.shabinder.common.uikit.colorOffWhite
import com.shabinder.database.Database import com.shabinder.database.Database
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.piwik.java.tracking.PiwikTracker import org.piwik.java.tracking.PiwikTracker
@ -79,10 +88,11 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
componentContext = componentContext, componentContext = componentContext,
dependencies = object : SpotiFlyerRoot.Dependencies { dependencies = object : SpotiFlyerRoot.Dependencies {
override val storeFactory = DefaultStoreFactory override val storeFactory = DefaultStoreFactory
override val fetchPlatformQueryResult: FetchPlatformQueryResult = koin.get() override val fetchQuery: FetchPlatformQueryResult = koin.get()
override val directories: Dir = koin.get() override val dir: Dir = koin.get()
override val database: Database? = directories.db override val database: Database? = dir.db
override val downloadProgressReport = DownloadProgressFlow override val preferenceManager: PreferenceManager = koin.get()
override val downloadProgressFlow = DownloadProgressFlow
override val actions: Actions = object: Actions { override val actions: Actions = object: Actions {
override val platformActions = object : PlatformActions {} override val platformActions = object : PlatformActions {}
@ -100,7 +110,7 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
APPROVE_OPTION -> { APPROVE_OPTION -> {
val directory = fileChooser.selectedFile val directory = fileChooser.selectedFile
if(directory.canWrite()){ if(directory.canWrite()){
directories.setDownloadDirectory(directory.absolutePath) preferenceManager.setDownloadDirectory(directory.absolutePath)
showPopUpMessage("Set New Download Directory:\n${directory.absolutePath}") showPopUpMessage("Set New Download Directory:\n${directory.absolutePath}")
} else { } else {
showPopUpMessage("Cant Write to Selected Directory!") showPopUpMessage("Cant Write to Selected Directory!")
@ -137,10 +147,10 @@ private fun spotiFlyerRoot(componentContext: ComponentContext): SpotiFlyerRoot =
} }
override val analytics = object: SpotiFlyerRoot.Analytics { override val analytics = object: SpotiFlyerRoot.Analytics {
override fun appLaunchEvent() { override fun appLaunchEvent() {
if(directories.isFirstLaunch) { if(preferenceManager.isFirstLaunch) {
// Enable Analytics on First Launch // Enable Analytics on First Launch
directories.toggleAnalytics(true) preferenceManager.toggleAnalytics(true)
directories.firstLaunchDone() preferenceManager.firstLaunchDone()
} }
tracker.trackAsync { tracker.trackAsync {
eventName = "App Launch" eventName = "App Launch"

View File

@ -22,6 +22,7 @@ include(
":common:root", ":common:root",
":common:main", ":common:main",
":common:list", ":common:list",
":common:preference",
":common:data-models", ":common:data-models",
":common:dependency-injection", ":common:dependency-injection",
":android", ":android",

View File

@ -22,6 +22,7 @@ import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.logging.store.LoggingStoreFactory import com.arkivanov.mvikotlin.logging.store.LoggingStoreFactory
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
import com.shabinder.common.di.DownloadProgressFlow import com.shabinder.common.di.DownloadProgressFlow
import com.shabinder.common.di.preference.PreferenceManager
import com.shabinder.common.models.Actions import com.shabinder.common.models.Actions
import com.shabinder.common.models.PlatformActions import com.shabinder.common.models.PlatformActions
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
@ -58,10 +59,11 @@ class App(props: AppProps): RComponent<AppProps, RState>(props) {
private val root = SpotiFlyerRoot(ctx, private val root = SpotiFlyerRoot(ctx,
object : SpotiFlyerRoot.Dependencies { object : SpotiFlyerRoot.Dependencies {
override val storeFactory: StoreFactory = LoggingStoreFactory(DefaultStoreFactory) override val storeFactory: StoreFactory = LoggingStoreFactory(DefaultStoreFactory)
override val fetchPlatformQueryResult = dependencies.fetchPlatformQueryResult override val fetchQuery = dependencies.fetchPlatformQueryResult
override val directories = dependencies.directories override val dir = dependencies.directories
override val database: Database? = directories.db override val preferenceManager: PreferenceManager = dependencies.preferenceManager
override val downloadProgressReport = DownloadProgressFlow override val database: Database? = dir.db
override val downloadProgressFlow = DownloadProgressFlow
override val actions = object : Actions { override val actions = object : Actions {
override val platformActions = object : PlatformActions {} override val platformActions = object : PlatformActions {}

View File

@ -18,11 +18,12 @@ import co.touchlab.kermit.Kermit
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.di.FetchPlatformQueryResult import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.initKoin import com.shabinder.common.di.initKoin
import react.dom.render import com.shabinder.common.di.preference.PreferenceManager
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.browser.window import kotlinx.browser.window
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.get import org.koin.core.component.get
import react.dom.render
fun main() { fun main() {
window.onload = { window.onload = {
@ -38,10 +39,12 @@ object AppDependencies : KoinComponent {
val logger: Kermit val logger: Kermit
val directories: Dir val directories: Dir
val fetchPlatformQueryResult: FetchPlatformQueryResult val fetchPlatformQueryResult: FetchPlatformQueryResult
val preferenceManager: PreferenceManager
init { init {
initKoin() initKoin()
directories = get() directories = get()
logger = get() logger = get()
fetchPlatformQueryResult = get() fetchPlatformQueryResult = get()
preferenceManager = get()
} }
} }