Skip to content

Commit

Permalink
Merge pull request #58 from stslex/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
stslex authored Oct 15, 2023
2 parents 9b97fa9 + c4f5ee8 commit 3bcac81
Show file tree
Hide file tree
Showing 18 changed files with 166 additions and 103 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ The app client with Image library using Unsplash api
### Tools

- JetpackCompose
- MVVM
- Coin (DI)
- MVVM + MVI
- Dagger (DI)
- Ktor
- Coroutines / Flow
- Jetpack paging library
- Jetpack compose navigation
- Coil (in process for migration from Glide)
- Coil
- Material design 3 (Material YOU)

### Installation
Expand Down
4 changes: 2 additions & 2 deletions build-logic/dependencies/src/main/kotlin/AppVersions.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
object AppVersions {
const val versionName = "1.62"
const val versionCode = 8
const val versionName = "1.63"
const val versionCode = 9
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ internal fun Project.configureKotlinAndroid(

val daggerCompiler = libs.findLibrary("dagger-compiler").get()
add("ksp", daggerCompiler)

val coroutines = libs.findLibrary("coroutines").get()
add("implementation", coroutines)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
package st.slex.csplashscreen.core.core

import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine as combinePrimary
import kotlinx.coroutines.flow.map as mapPrimary

object CoroutineExt {
}

fun <T, R> StateFlow<T>.mapState(
transform: (a: T) -> R,
): StateFlow<R> = TransformableStateFlow(
getValue = { transform(this.value) },
flow = mapPrimary(transform = transform),
)

fun <T1, T2, R> StateFlow<T1>.combineState(
flow: StateFlow<T2>,
transform: (a: T1, b: T2) -> R,
): StateFlow<R> = TransformableStateFlow(
getValue = { transform(this.value, flow.value) },
flow = combinePrimary(
flow = flow,
transform = transform,
),
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package st.slex.csplashscreen.core.core

import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.stateIn

class TransformableStateFlow<out T>(
val getValue: () -> T,
val flow: Flow<T>,
) : StateFlow<T> {

override val replayCache: List<T>
get() = listOf(value)

override val value: T
get() = getValue()

override suspend fun collect(collector: FlowCollector<T>): Nothing {
coroutineScope {
flow.distinctUntilChanged()
.stateIn(this)
.collect(collector)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ open class BaseViewModel<out S : State, out E : Event, in A : Action>(
fun sendAction(action: A) {
store.processAction(action)
}

override fun onCleared() {
super.onCleared()
store.destroy()
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
package st.slex.csplashscreen.core.ui.mvi

import st.slex.csplashscreen.core.ui.mvi.Store.Action
import st.slex.csplashscreen.core.ui.mvi.Store.Event
import st.slex.csplashscreen.core.ui.mvi.Store.State
import androidx.paging.PagingData
import androidx.paging.cachedIn
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import st.slex.csplashscreen.core.ui.mvi.Store.Action
import st.slex.csplashscreen.core.ui.mvi.Store.Event
import st.slex.csplashscreen.core.ui.mvi.Store.State

abstract class BaseStoreImpl<S : State, E : Event, A : Action> :
Store<S, E, A>,
Expand All @@ -34,4 +43,18 @@ abstract class BaseStoreImpl<S : State, E : Event, A : Action> :
override fun init(scope: CoroutineScope) {
_scope = scope
}

override fun destroy() {
_scope?.cancel()
_scope = null
}

fun <T : Any> Flow<PagingData<T>>.state(): StateFlow<PagingData<T>> = this
.flowOn(Dispatchers.IO)
.cachedIn(scope)
.stateIn(
initialValue = PagingData.empty(),
scope = scope,
started = SharingStarted.Lazily
)
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package st.slex.csplashscreen.core.ui.mvi

import st.slex.csplashscreen.core.ui.mvi.Store.Action
import st.slex.csplashscreen.core.ui.mvi.Store.Event
import st.slex.csplashscreen.core.ui.mvi.Store.State
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import st.slex.csplashscreen.core.ui.mvi.Store.Action
import st.slex.csplashscreen.core.ui.mvi.Store.Event
import st.slex.csplashscreen.core.ui.mvi.Store.State

interface Store<out S : State, out E : Event, in A : Action> {

Expand All @@ -16,8 +16,9 @@ interface Store<out S : State, out E : Event, in A : Action> {

fun init(scope: CoroutineScope)

fun destroy()

interface State
interface Event
interface Action
}

Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ class SingleCollectionInteractorImpl @Inject constructor(
uuid: String,
page: Int,
pageSize: Int
): List<ImageModel> = repository.getPhotos(
uuid = uuid,
page = page,
pageSize = pageSize
).toDomain()
): List<ImageModel> = repository
.getPhotos(
uuid = uuid,
page = page,
pageSize = pageSize
)
.toDomain()
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ interface SingleCollectionStore : Store<State, Event, Action> {

@Stable
data class State(
val photos: () -> StateFlow<PagingData<PhotoModel>>
val photos: () -> StateFlow<PagingData<PhotoModel>>,
val collectionId: String,
) : Store.State

@Stable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package st.slex.csplashscreen.feature.collection.ui.store
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.map
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import st.slex.csplashscreen.core.core.CoroutineExt.mapState
import st.slex.csplashscreen.core.photos.ui.model.PhotoModel
import st.slex.csplashscreen.core.photos.ui.model.toPresentation
import st.slex.csplashscreen.core.ui.mvi.BaseStoreImpl
import st.slex.csplashscreen.core.ui.paging.PagingSource
Expand All @@ -24,10 +25,10 @@ class SingleCollectionStoreImpl @Inject constructor(
private val interactor: SingleCollectionInteractor
) : SingleCollectionStore, BaseStoreImpl<State, Event, Action>() {


override val initialState: State
get() = State(
photos = { MutableStateFlow(PagingData.empty()) }
photos = ::allPhotos,
collectionId = ""
)

override val state: MutableStateFlow<State> = MutableStateFlow(initialState)
Expand All @@ -43,31 +44,31 @@ class SingleCollectionStoreImpl @Inject constructor(
private fun actionInit(action: Action.Init) {
updateState { currentState ->
currentState.copy(
photos = {
getPhotos(action.collectionId)
}
collectionId = action.collectionId
)
}
}

private fun getPhotos(collectionId: String) = Pager(pagingConfig) {
PagingSource { page, pageSize ->
interactor.getPhotos(
uuid = collectionId,
page = page,
pageSize = pageSize
)
}
}
.flow
.map { pagingData -> pagingData.map { it.toPresentation() } }
.flowOn(Dispatchers.IO)
.cachedIn(scope)
.stateIn(
initialValue = PagingData.empty(),
scope = scope,
started = SharingStarted.Lazily
)
@OptIn(ExperimentalCoroutinesApi::class)
private val allPhotos: StateFlow<PagingData<PhotoModel>>
get() = state
.mapState { currentState ->
currentState.collectionId
}
.filter { it.isNotBlank() }
.flatMapLatest { collectionId ->
Pager(pagingConfig) {
PagingSource { page, pageSize ->
interactor.getPhotos(
uuid = collectionId,
page = page,
pageSize = pageSize
)
}
}.flow
}
.map { pagingData -> pagingData.map { it.toPresentation() } }
.state()

private fun actionProfileClick(action: Action.OnProfileClick) {
sendEvent(Event.Navigation.Profile(action.username))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
package st.slex.csplashscreen.feature.favourite.ui.store

import androidx.paging.PagingData
import androidx.paging.cachedIn
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import st.slex.csplashscreen.core.photos.ui.model.PhotoModel
import st.slex.csplashscreen.core.ui.mvi.BaseStoreImpl
import st.slex.csplashscreen.feature.favourite.domain.FavouriteInteractor
Expand All @@ -32,13 +27,7 @@ class FavouriteStoreImpl @Inject constructor(
.map { pagingData ->
pagingData
}
.flowOn(Dispatchers.IO)
.cachedIn(scope)
.stateIn(
scope = scope,
started = SharingStarted.Lazily,
initialValue = PagingData.empty()
)
.state()

override fun processAction(action: Action) {
when (action) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,7 @@ class HomeStoreImpl @Inject constructor(
}
.flow
.map { pagingData -> pagingData.map { it.toPresentation() } }
.flowOn(Dispatchers.IO)
.cachedIn(scope)
.stateIn(
scope = scope,
started = SharingStarted.Lazily,
initialValue = PagingData.empty()
)
.state()

private val photos: StateFlow<PagingData<PhotoModel>>
get() = Pager(config = config) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,14 @@ package st.slex.csplashscreen.feature.search.ui.store
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.map
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import st.slex.csplashscreen.core.core.Logger
import st.slex.csplashscreen.core.network.model.ui.ImageModel
Expand Down Expand Up @@ -42,10 +38,7 @@ class SearchStoreImpl @Inject constructor(
override val state = MutableStateFlow(initialState)

private val searchHistory: StateFlow<PagingData<SearchItem>>
get() = interactor.searchHistory
.flowOn(Dispatchers.IO)
.cachedIn(scope)
.stateIn(scope, SharingStarted.Lazily, PagingData.empty())
get() = interactor.searchHistory.state()

@OptIn(ExperimentalCoroutinesApi::class)
private val photosSearch: StateFlow<PagingData<PhotoModel>>
Expand All @@ -54,9 +47,7 @@ class SearchStoreImpl @Inject constructor(
.map(::newPagerPhotosSearch)
.flatMapLatest { it.flow }
.map { pagingData -> pagingData.map { it.toPresentation() } }
.flowOn(Dispatchers.IO)
.cachedIn(scope)
.stateIn(scope, SharingStarted.Lazily, PagingData.empty())
.state()

private fun newPagerPhotosSearch(
query: String
Expand Down
Loading

0 comments on commit 3bcac81

Please sign in to comment.