List & Main Stores and Models

This commit is contained in:
shabinder 2021-02-01 22:03:30 +05:30
parent a8f5941050
commit 97f9606863
17 changed files with 340 additions and 91 deletions

View File

@ -22,3 +22,12 @@ buildscript {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.21") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.21")
} }
} }
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xallow-jvm-ir-dependencies", "-Xskip-prerelease-check",
"-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xuse-experimental=kotlinx.coroutines.TheAnnotationYouWantToDisable"
)
}
}

View File

@ -75,6 +75,8 @@ object MVIKotlin {
const val rx = "com.arkivanov.mvikotlin:rx:$VERSION" const val rx = "com.arkivanov.mvikotlin:rx:$VERSION"
const val mvikotlin = "com.arkivanov.mvikotlin:mvikotlin:$VERSION" const val mvikotlin = "com.arkivanov.mvikotlin:mvikotlin:$VERSION"
const val mvikotlinMain = "com.arkivanov.mvikotlin:mvikotlin-main:$VERSION" const val mvikotlinMain = "com.arkivanov.mvikotlin:mvikotlin-main:$VERSION"
const val coroutines = "com.arkivanov.mvikotlin:mvikotlin-extensions-coroutines:$VERSION"
const val keepers = "com.arkivanov.mvikotlin:keepers:$VERSION"
const val mvikotlinMainIosX64 = "com.arkivanov.mvikotlin:mvikotlin-main-iosx64:$VERSION" const val mvikotlinMainIosX64 = "com.arkivanov.mvikotlin:mvikotlin-main-iosx64:$VERSION"
const val mvikotlinMainIosArm64 = "com.arkivanov.mvikotlin:mvikotlin-main-iosarm64:$VERSION" const val mvikotlinMainIosArm64 = "com.arkivanov.mvikotlin:mvikotlin-main-iosarm64:$VERSION"
const val mvikotlinLogging = "com.arkivanov.mvikotlin:mvikotlin-logging:$VERSION" const val mvikotlinLogging = "com.arkivanov.mvikotlin:mvikotlin-logging:$VERSION"

View File

@ -10,12 +10,24 @@ kotlin {
implementation(project(":common:dependency-injection")) implementation(project(":common:dependency-injection"))
implementation(project(":common:data-models")) implementation(project(":common:data-models"))
implementation(project(":common:database")) implementation(project(":common:database"))
//implementation(MVIKotlin.rx)
implementation(SqlDelight.coroutineExtensions)
implementation(MVIKotlin.coroutines)
implementation(MVIKotlin.mvikotlin) implementation(MVIKotlin.mvikotlin)
implementation(MVIKotlin.mvikotlinExtensionsReaktive) //implementation(MVIKotlin.mvikotlinExtensionsReaktive)
implementation(Badoo.Reaktive.reaktive) //implementation(Badoo.Reaktive.reaktive)
implementation(Decompose.decompose) implementation(Decompose.decompose)
implementation(Decompose.extensionsCompose) implementation(Decompose.extensionsCompose)
} }
} }
} }
} }
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xallow-jvm-ir-dependencies", "-Xskip-prerelease-check",
"-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xuse-experimental=kotlinx.coroutines.TheAnnotationYouWantToDisable"
)
}
}

View File

@ -0,0 +1,43 @@
package com.shabinder.common.list
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.shabinder.common.PlatformQueryResult
import com.shabinder.common.TrackDetails
import com.shabinder.common.list.integration.SpotiFlyerListImpl
import com.shabinder.database.Database
import kotlinx.coroutines.flow.Flow
interface SpotiFlyerList {
val models: Flow<State>
/*
* For Single Track Download -> list(that track)
* For Download All -> Model.tracks
* */
fun onDownloadClicked(trackList:List<TrackDetails>)
/*
* To Pop and return back to Main Screen
* */
fun onBackPressed()
interface Dependencies {
val storeFactory: StoreFactory
val database: Database
val link: String
fun listOutput(finished: Output.Finished)
}
sealed class Output {
object Finished : Output()
}
data class State(
val result:PlatformQueryResult? = null,
val link:String = ""
)
}
@Suppress("FunctionName") // Factory function
fun SpotiFlyerList(componentContext: ComponentContext, dependencies: SpotiFlyerList.Dependencies): SpotiFlyerList =
SpotiFlyerListImpl(componentContext, dependencies)

View File

@ -0,0 +1,37 @@
package com.shabinder.common.list.integration
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.mvikotlin.extensions.coroutines.states
import com.shabinder.common.TrackDetails
import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.list.SpotiFlyerList.Dependencies
import com.shabinder.common.list.SpotiFlyerList.State
import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
import com.shabinder.common.list.store.SpotiFlyerListStoreProvider
import com.shabinder.common.utils.getStore
import kotlinx.coroutines.flow.Flow
internal class SpotiFlyerListImpl(
componentContext: ComponentContext,
dependencies: Dependencies
): SpotiFlyerList,ComponentContext by componentContext, Dependencies by dependencies {
private val store =
instanceKeeper.getStore {
SpotiFlyerListStoreProvider(
storeFactory = storeFactory,
database = database,
link = link
).provide()
}
override val models: Flow<State> = store.states
override fun onDownloadClicked(trackList: List<TrackDetails>) {
store.accept(Intent.StartDownload(trackList))
}
override fun onBackPressed(){
listOutput(SpotiFlyerList.Output.Finished)
}
}

View File

@ -0,0 +1,15 @@
package com.shabinder.common.list.store
import com.arkivanov.mvikotlin.core.store.Store
import com.shabinder.common.PlatformQueryResult
import com.shabinder.common.TrackDetails
import com.shabinder.common.list.SpotiFlyerList
import com.shabinder.common.list.SpotiFlyerList.State
import com.shabinder.common.list.store.SpotiFlyerListStore.*
internal interface SpotiFlyerListStore: Store<Intent, State, Nothing> {
sealed class Intent {
data class StartDownload(val trackList: List<TrackDetails>): Intent()
data class SearchLink(val link: String): Intent()
}
}

View File

@ -0,0 +1,57 @@
package com.shabinder.common.list.store
import com.arkivanov.mvikotlin.core.store.*
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
import com.shabinder.common.FetchPlatformQueryResult
import com.shabinder.common.PlatformQueryResult
import com.shabinder.common.list.SpotiFlyerList.State
import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
import com.shabinder.database.Database
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
internal class SpotiFlyerListStoreProvider(
private val storeFactory: StoreFactory,
private val database: Database,
private val link: String
) {
fun provide(): SpotiFlyerListStore =
object : SpotiFlyerListStore, Store<Intent, State, Nothing> by storeFactory.create(
name = "SpotiFlyerListStore",
initialState = State(),
bootstrapper = SimpleBootstrapper(Unit),
executorFactory = ::ExecutorImpl,
reducer = ReducerImpl
) {}
private sealed class Result {
data class ResultFetched(val result: PlatformQueryResult) : Result()
data class SearchLink(val link: String) : Result()
}
private inner class ExecutorImpl : SuspendExecutor<Intent, Unit, State, Result, Nothing>() {
override suspend fun executeAction(action: Unit, getState: () -> State) {
FetchPlatformQueryResult().query(link)?.let{
dispatch(Result.ResultFetched(it))
}
}
override suspend fun executeIntent(intent: Intent, getState: () -> State) {
when (intent) {//TODO: Add Dispatchers where needed
is Intent.StartDownload -> {}//TODO()
is Intent.SearchLink -> FetchPlatformQueryResult().query(link)?.let{
dispatch((Result.ResultFetched(it)))
}
}
}
}
private object ReducerImpl : Reducer<State, Result> {
override fun State.reduce(result: Result): State =
when (result) {
is Result.ResultFetched -> copy(result = result.result)
is Result.SearchLink -> copy(link = result.link)
}
}
}

View File

@ -1,30 +1,43 @@
package com.shabinder.common.main package com.shabinder.common.main
import com.arkivanov.decompose.value.Value import com.arkivanov.decompose.ComponentContext
import com.arkivanov.mvikotlin.core.store.StoreFactory import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.badoo.reaktive.base.Consumer
import com.shabinder.common.DownloadRecord import com.shabinder.common.DownloadRecord
import com.shabinder.common.main.integration.SpotiFlyerMainImpl
import com.shabinder.common.utils.Consumer
import com.shabinder.database.Database import com.shabinder.database.Database
import kotlinx.coroutines.flow.Flow
interface SpotiFlyerMain { interface SpotiFlyerMain {
val models: Value<Model> val models: Flow<State>
fun onDownloadRecordClicked(link: String) /*
* We Intend to Move to List Screen
* Note: Implementation in Root
* */
fun onLinkSearch(link: String)
/*
* Update TextBox's Text
* */
fun onInputLinkChanged(link: String) fun onInputLinkChanged(link: String)
interface Dependencies { interface Dependencies {
fun mainOutput(searched: Output): Consumer<Output>
val storeFactory: StoreFactory val storeFactory: StoreFactory
val database: Database val database: Database
val mainOutput: Consumer<Output> }
sealed class Output {
data class Search(val link: String) : Output()
} }
data class Model( data class State(
val record: List<DownloadRecord>, val records: List<DownloadRecord> = emptyList(),
val link: String val link: String = ""
) )
sealed class Output { }
data class Searched(val link: String) : Output()
} @Suppress("FunctionName") // Factory function
} fun SpotiFlyerMain(componentContext: ComponentContext, dependencies: SpotiFlyerMain.Dependencies): SpotiFlyerMain =
SpotiFlyerMainImpl(componentContext, dependencies)

View File

@ -1,23 +1,34 @@
package com.shabinder.common.main.integration package com.shabinder.common.main.integration
import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.value.Value import com.arkivanov.mvikotlin.extensions.coroutines.states
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.*
import com.shabinder.common.main.store.SpotiFlyerMainStore.Intent
import com.shabinder.common.main.store.SpotiFlyerMainStoreProvider
import com.shabinder.common.utils.getStore
import kotlinx.coroutines.flow.Flow
internal class SpotiFlyerMainImpl( internal class SpotiFlyerMainImpl(
componentContext: ComponentContext, componentContext: ComponentContext,
dependencies: Dependencies dependencies: Dependencies
): SpotiFlyerMain,ComponentContext by componentContext, Dependencies by dependencies { ): SpotiFlyerMain,ComponentContext by componentContext, Dependencies by dependencies {
override val models: Value<SpotiFlyerMain.Model>
get() = TODO("Not yet implemented")
override fun onDownloadRecordClicked(link: String) { private val store =
TODO("Not yet implemented") instanceKeeper.getStore {
SpotiFlyerMainStoreProvider(
storeFactory = storeFactory,
database = database
).provide()
}
override val models: Flow<State> = store.states
override fun onLinkSearch(link: String) {
mainOutput(Output.Search(link = link))
} }
override fun onInputLinkChanged(link: String) { override fun onInputLinkChanged(link: String) {
TODO("Not yet implemented") store.accept(Intent.SetLink(link))
} }
} }

View File

@ -2,17 +2,14 @@ package com.shabinder.common.main.store
import com.arkivanov.mvikotlin.core.store.Store import com.arkivanov.mvikotlin.core.store.Store
import com.shabinder.common.DownloadRecord import com.shabinder.common.DownloadRecord
import com.shabinder.common.main.SpotiFlyerMain
import com.shabinder.common.main.store.SpotiFlyerMainStore.* import com.shabinder.common.main.store.SpotiFlyerMainStore.*
internal interface SpotiFlyerMainStore: Store<Intent, State, Nothing> { internal interface SpotiFlyerMainStore: Store<Intent, SpotiFlyerMain.State, Nothing> {
sealed class Intent { sealed class Intent {
data class OpenPlatform(val platformID:String,val platformLink:String):Intent() data class OpenPlatform(val platformID:String,val platformLink:String):Intent()
data class SetLink(val link:String):Intent()
object GiveDonation : Intent() object GiveDonation : Intent()
object ShareApp: Intent() object ShareApp: Intent()
} }
}
data class State(
val records: List<DownloadRecord> = emptyList(),
val link: String = ""
)
}

View File

@ -1,52 +1,79 @@
package com.shabinder.common.main.store package com.shabinder.common.main.store
import com.arkivanov.mvikotlin.core.store.Reducer
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.core.store.StoreFactory
import com.arkivanov.mvikotlin.extensions.reaktive.ReaktiveExecutor import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
import com.badoo.reaktive.observable.Observable
import com.badoo.reaktive.observable.map
import com.badoo.reaktive.observable.mapIterable
import com.badoo.reaktive.observable.observeOn
import com.badoo.reaktive.scheduler.mainScheduler
import com.shabinder.common.DownloadRecord import com.shabinder.common.DownloadRecord
import com.shabinder.common.database.asObservable import com.shabinder.common.giveDonation
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.SpotiFlyerMainStore.State import com.shabinder.common.openPlatform
import com.shabinder.common.shareApp
import com.shabinder.database.Database import com.shabinder.database.Database
import com.squareup.sqldelight.Query import com.squareup.sqldelight.runtime.coroutines.asFlow
import com.squareup.sqldelight.runtime.coroutines.mapToList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
internal class SpotiFlyerMainStoreProvider( internal class SpotiFlyerMainStoreProvider(
private val storeFactory: StoreFactory, private val storeFactory: StoreFactory,
private val database: Database database: Database
) { ) {
fun provide(): SpotiFlyerMainStore =
object : SpotiFlyerMainStore, Store<Intent, State, Nothing> by storeFactory.create(
name = "SpotiFlyerHomeStore",
initialState = State(),
bootstrapper = SimpleBootstrapper(Unit),
executorFactory = ::ExecutorImpl,
reducer = ReducerImpl
) {}
val updates: Flow<List<DownloadRecord>> =
database.downloadRecordDatabaseQueries
.selectAll()
.asFlow()
.mapToList(Dispatchers.Default)
.map {
it.map { record ->
record.run{
DownloadRecord(id, type, name, link, coverUrl, totalFiles)
}
}
}
private sealed class Result { private sealed class Result {
data class ItemsLoaded(val items: List<DownloadRecord>) : Result() data class ItemsLoaded(val items: List<DownloadRecord>) : Result()
data class TextChanged(val text: String) : Result() data class LinkChanged(val link: String) : Result()
} }
private inner class ExecutorImpl : ReaktiveExecutor<Intent, Unit, State, Result, Nothing>() { private inner class ExecutorImpl : SuspendExecutor<Intent, Unit, State, Result, Nothing>() {
override fun executeAction(action: Unit, getState: () -> State) { override suspend fun executeAction(action: Unit, getState: () -> State) {
val updates: Observable<List<DownloadRecord>> = updates.collect {
database.downloadRecordDatabaseQueries dispatch(Result.ItemsLoaded(it))
.selectAll() }
.asObservable(Query<com.shabinder.common.database.DownloadRecord>::executeAsList)
.mapIterable { it.run {
DownloadRecord(
id, type, name, link, coverUrl, totalFiles
)
} }
updates
.observeOn(mainScheduler)
.map(Result::ItemsLoaded)
.subscribeScoped(onNext = ::dispatch)
} }
override fun executeIntent(intent: Intent, getState: () -> State) {
when (intent) {//TODO override suspend fun executeIntent(intent: Intent, getState: () -> State) {
is Intent.OpenPlatform -> {} when (intent) {//TODO: Add Dispatchers where needed
is Intent.GiveDonation -> {} is Intent.OpenPlatform -> openPlatform(intent.platformID, intent.platformLink)
is Intent.ShareApp -> {} is Intent.GiveDonation -> giveDonation()
is Intent.ShareApp -> shareApp()
is Intent.SetLink -> dispatch(Result.LinkChanged(link = intent.link))
} }
} }
} }
private object ReducerImpl : Reducer<State, Result> {
override fun State.reduce(result: Result): State =
when (result) {
is Result.ItemsLoaded -> copy(records = result.items)
is Result.LinkChanged -> copy(link = result.link)
}
}
} }

View File

@ -0,0 +1,16 @@
package com.shabinder.common.utils
/*
* Callback Utility
* */
interface Consumer<in T> {
fun onCall(value: T)
}
@Suppress("FunctionName") // Factory function
inline fun <T> Consumer(crossinline block: (T) -> Unit): Consumer<T> =
object : Consumer<T> {
override fun onCall(value: T) {
block(value)
}
}

View File

@ -0,0 +1,20 @@
package com.shabinder.common.utils
import com.arkivanov.decompose.instancekeeper.InstanceKeeper
import com.arkivanov.decompose.instancekeeper.getOrCreate
import com.arkivanov.mvikotlin.core.store.Store
fun <T : Store<*, *, *>> InstanceKeeper.getStore(key: Any, factory: () -> T): T =
getOrCreate(key) { StoreHolder(factory()) }
.store
inline fun <reified T : Store<*, *, *>> InstanceKeeper.getStore(noinline factory: () -> T): T =
getStore(T::class, factory)
private class StoreHolder<T : Store<*, *, *>>(
val store: T
) : InstanceKeeper.Instance {
override fun onDestroy() {
store.dispose()
}
}

View File

@ -1,28 +0,0 @@
package com.shabinder.common.database
import com.badoo.reaktive.base.setCancellable
import com.badoo.reaktive.observable.Observable
import com.badoo.reaktive.observable.map
import com.badoo.reaktive.observable.observable
import com.badoo.reaktive.observable.observeOn
import com.badoo.reaktive.scheduler.ioScheduler
import com.squareup.sqldelight.Query
fun <T : Any, R> Query<T>.asObservable(execute: (Query<T>) -> R): Observable<R> =
asObservable()
.observeOn(ioScheduler)
.map(execute)
fun <T : Any> Query<T>.asObservable(): Observable<Query<T>> =
observable { emitter ->
val listener =
object : Query.Listener {
override fun queryResultsChanged() {
emitter.onNext(this@asObservable)
}
}
emitter.onNext(this@asObservable)
addListener(listener)
emitter.setCancellable { removeListener(listener) }
}

View File

@ -2,10 +2,11 @@ package com.shabinder.common
import android.content.Context import android.content.Context
import android.os.Environment import android.os.Environment
import co.touchlab.kermit.Kermit
import com.shabinder.common.database.appContext import com.shabinder.common.database.appContext
import java.io.File import java.io.File
actual open class Dir { actual open class Dir actual constructor(logger: Kermit) {
private val context:Context private val context:Context
get() = appContext get() = appContext
@ -21,4 +22,6 @@ actual open class Dir {
"SpotiFlyer"+ File.separator "SpotiFlyer"+ File.separator
actual fun isPresent(path: String): Boolean = File(path).exists() actual fun isPresent(path: String): Boolean = File(path).exists()
actual fun createDirectory(dirPath: String) {
}
} }

View File

@ -3,6 +3,13 @@ package com.shabinder.common
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.utils.removeIllegalChars import com.shabinder.common.utils.removeIllegalChars
expect fun openPlatform(platformID:String ,platformLink:String)
expect fun shareApp()
expect fun giveDonation()
expect open class Dir( expect open class Dir(
logger: Kermit logger: Kermit
) { ) {

View File

@ -0,0 +1,8 @@
package com.shabinder.common
//TODO
class FetchPlatformQueryResult {
suspend fun query(link:String): PlatformQueryResult?{
return null
}
}