diff --git a/.gitignore b/.gitignore
index 58490dda..eb8080a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -239,3 +239,9 @@ fabric.properties
app/.config/secrets.properties
app/schema.graphql
+
+.idea/deploymentTargetSelector.xml
+
+.idea/appInsightsSettings.xml
+
+app/lint-baseline.xml
diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml
deleted file mode 100644
index e7629756..00000000
--- a/.idea/appInsightsSettings.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
deleted file mode 100644
index b268ef36..00000000
--- a/.idea/deploymentTargetSelector.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index fe63bb67..148fdd24 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index d18963a7..00000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 57d6d2dd..4615a58d 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -16,6 +16,9 @@ android {
buildFeatures {
buildConfig = true
}
+ lint {
+ baseline = file("lint-baseline.xml")
+ }
}
dependencies {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d7f84553..d84bea7b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -7,6 +7,7 @@
+
-
-
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/SampleApp.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/SampleApp.kt
index 0e9c00ca..0bbd829f 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/SampleApp.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/SampleApp.kt
@@ -6,11 +6,6 @@ import io.wax911.emojify.EmojiManager
abstract class SampleApp : Application() {
- /**
- * Emoji manager instance
- */
- internal abstract val emojiManager: EmojiManager
-
/**
* Uncaught exception handler
*/
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/koin/CoreModules.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/koin/CoreModules.kt
index 3fb57272..58cd9cec 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/koin/CoreModules.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/koin/CoreModules.kt
@@ -1,6 +1,7 @@
package co.anitrend.retrofit.graphql.core.koin
-import co.anitrend.arch.extension.dispatchers.SupportDispatchers
+import co.anitrend.arch.extension.dispatchers.SupportDispatcher
+import co.anitrend.arch.extension.dispatchers.contract.ISupportDispatcher
import co.anitrend.retrofit.graphql.core.settings.Settings
import coil.ImageLoader
import coil.ImageLoaderFactory
@@ -20,8 +21,8 @@ private val coreModule = module {
androidContext()
)
} binds(Settings.BINDINGS)
- single {
- SupportDispatchers()
+ single {
+ SupportDispatcher()
}
}
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/model/FragmentItem.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/model/FragmentItem.kt
index 2bc37bab..9a966c39 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/model/FragmentItem.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/model/FragmentItem.kt
@@ -10,5 +10,5 @@ data class FragmentItem(
val parameter: Bundle? = null,
val fragment: Class
) {
- fun tag() = fragment.simpleName
+ fun tag(): String = fragment.simpleName
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/settings/Settings.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/settings/Settings.kt
index 20e001e4..c88b1d9f 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/settings/Settings.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/settings/Settings.kt
@@ -1,36 +1,39 @@
package co.anitrend.retrofit.graphql.core.settings
import android.content.Context
-import co.anitrend.arch.extension.preference.BooleanPreference
-import co.anitrend.arch.extension.preference.IntPreference
-import co.anitrend.arch.extension.preference.StringPreference
-import co.anitrend.arch.extension.preference.SupportSettings
+import co.anitrend.arch.extension.preference.SupportPreference
+import co.anitrend.arch.extension.settings.BooleanSetting
+import co.anitrend.arch.extension.settings.IntSetting
+import co.anitrend.arch.extension.settings.StringSetting
import co.anitrend.retrofit.graphql.data.authentication.settings.IAuthenticationSettings
import co.anitrend.retrofit.graphql.sample.R
-class Settings(context: Context) : SupportSettings(context), IAuthenticationSettings {
+class Settings(context: Context) : SupportPreference(context), IAuthenticationSettings {
- override var authenticatedUserId by StringPreference(
- R.string.setting_authenticated_user_id,
- IAuthenticationSettings.INVALID_USER_ID,
- context.resources
+ override val authenticatedUserId = StringSetting(
+ key = R.string.setting_authenticated_user_id,
+ default = IAuthenticationSettings.INVALID_USER_ID,
+ resources = context.resources,
+ preference = this,
)
- override var isNewInstallation by BooleanPreference(
- R.string.setting_is_new_installation,
- true,
- context.resources
+ override val isNewInstallation = BooleanSetting(
+ key = R.string.setting_is_new_installation,
+ default = true,
+ resources = context.resources,
+ preference = this,
)
- override var versionCode by IntPreference(
- R.string.setting_version_code,
- 1,
- context.resources
+ override val versionCode = IntSetting(
+ key = R.string.setting_version_code,
+ default = 1,
+ resources = context.resources,
+ preference = this,
)
companion object {
val BINDINGS = arrayOf(
- Settings::class, SupportSettings::class, IAuthenticationSettings::class
+ Settings::class, SupportPreference::class, IAuthenticationSettings::class
)
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/view/SampleActivity.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/view/SampleActivity.kt
index ba2f5e47..40d2a119 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/view/SampleActivity.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/view/SampleActivity.kt
@@ -3,28 +3,39 @@ package co.anitrend.retrofit.graphql.core.view
import android.os.Build
import android.view.View
import androidx.appcompat.app.AppCompatDelegate
+import co.anitrend.arch.core.model.ISupportViewModelState
import co.anitrend.arch.extension.ext.getCompatColor
-import co.anitrend.arch.ui.R
import co.anitrend.arch.ui.activity.SupportActivity
+import org.koin.android.scope.AndroidScopeComponent
import org.koin.androidx.fragment.android.setupKoinFragmentFactory
-import org.koin.androidx.scope.lifecycleScope as koinLifecycleScope
+import org.koin.androidx.scope.activityRetainedScope
+import org.koin.core.component.KoinScopeComponent
+import timber.log.Timber
-abstract class SampleActivity : SupportActivity() {
+abstract class SampleActivity : SupportActivity(), AndroidScopeComponent, KoinScopeComponent {
+
+ override val scope by activityRetainedScope()
/**
* Can be used to configure custom theme styling as desired
*/
override fun configureActivity() {
- setupKoinFragmentFactory()
+ runCatching {
+ Timber.v("Setting up fragment factory using scope: $scope")
+ setupKoinFragmentFactory(scope)
+ }.onFailure {
+ setupKoinFragmentFactory()
+ Timber.v(it, "Reverting to scope-less based fragment factory")
+ }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val systemUiOptions = window.decorView.systemUiVisibility
when (AppCompatDelegate.getDefaultNightMode()) {
AppCompatDelegate.MODE_NIGHT_NO -> {
- window.navigationBarColor = getCompatColor(R.color.colorPrimary)
+ window.navigationBarColor = getCompatColor(co.anitrend.arch.theme.R.color.colorPrimary)
window.decorView.systemUiVisibility = systemUiOptions or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
}
AppCompatDelegate.MODE_NIGHT_YES -> {
- window.navigationBarColor = getCompatColor(R.color.colorPrimary)
+ window.navigationBarColor = getCompatColor(co.anitrend.arch.theme.R.color.colorPrimary)
window.decorView.systemUiVisibility = systemUiOptions and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
window.decorView.systemUiVisibility = systemUiOptions and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
@@ -34,4 +45,9 @@ abstract class SampleActivity : SupportActivity() {
}
}
}
+
+ /**
+ * Proxy for a view model state if one exists
+ */
+ override fun viewModelState(): ISupportViewModelState<*>? = null
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/view/SampleListFragment.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/view/SampleListFragment.kt
index bdaf28ce..a326585e 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/view/SampleListFragment.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/core/view/SampleListFragment.kt
@@ -1,11 +1,18 @@
package co.anitrend.retrofit.graphql.core.view
-import androidx.lifecycle.Observer
-import co.anitrend.arch.domain.entities.NetworkState
+import co.anitrend.arch.core.model.ISupportViewModelState
import co.anitrend.arch.ui.fragment.list.SupportFragmentList
+import org.koin.android.scope.AndroidScopeComponent
+import org.koin.androidx.scope.fragmentScope
+import org.koin.core.component.KoinScopeComponent
-abstract class SampleListFragment : SupportFragmentList() {
- override val onRefreshObserver = Observer {
- // workaround for support-arch:ui on refresh overrides network state
- }
+abstract class SampleListFragment : SupportFragmentList(),
+ AndroidScopeComponent, KoinScopeComponent {
+
+ override val scope by fragmentScope()
+
+ /**
+ * Proxy for a view model state if one exists
+ */
+ override fun viewModelState(): ISupportViewModelState<*>? = null
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/api/provider/RetrofitProvider.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/api/provider/RetrofitProvider.kt
index accec74d..751f9c34 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/api/provider/RetrofitProvider.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/api/provider/RetrofitProvider.kt
@@ -18,7 +18,6 @@ import timber.log.Timber
*/
internal object RetrofitProvider {
- private val moduleTag = javaClass.simpleName
private val retrofitCache = LruCache(3)
private fun provideOkHttpClient(endpointType: EndpointType, scope: Scope) : OkHttpClient {
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/common/SampleMapper.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/common/SampleMapper.kt
index da147568..29c01c62 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/common/SampleMapper.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/common/SampleMapper.kt
@@ -1,6 +1,6 @@
package co.anitrend.retrofit.graphql.data.arch.common
-import co.anitrend.arch.data.mapper.contract.ISupportMapperHelper
+import co.anitrend.arch.data.mapper.SupportResponseMapper
import co.anitrend.retrofit.graphql.domain.common.EntityId
/**
@@ -8,6 +8,6 @@ import co.anitrend.retrofit.graphql.domain.common.EntityId
* @param M model
*/
internal abstract class SampleMapper {
- protected abstract fun from(): ISupportMapperHelper
- protected abstract fun to(): ISupportMapperHelper
+ protected abstract fun from(): SupportResponseMapper
+ protected abstract fun to(): SupportResponseMapper
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/common/SamplePagedSource.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/common/SamplePagedSource.kt
deleted file mode 100644
index ed868716..00000000
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/common/SamplePagedSource.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-package co.anitrend.retrofit.graphql.data.arch.common
-
-import androidx.paging.PagedList
-import androidx.paging.PagingRequestHelper
-import co.anitrend.arch.data.source.paging.SupportPagingDataSource
-import co.anitrend.arch.extension.dispatchers.SupportDispatchers
-import kotlinx.coroutines.launch
-
-internal abstract class SamplePagedSource(
- supportDispatchers: SupportDispatchers
-) : SupportPagingDataSource(supportDispatchers) {
-
- /**
- * Invoked when a request to the network needs to happen
- */
- abstract suspend operator fun invoke(
- callback: PagingRequestHelper.Request.Callback,
- requestType: PagingRequestHelper.RequestType,
- model: T?
- )
-
- /**
- * Called when the item at the end of the PagedList has been loaded, and access has
- * occurred within [PagedList.Config.prefetchDistance] of it.
- *
- * No more data will be appended to the [PagedList] after this item.
- *
- * @param itemAtEnd The first item of [PagedList]
- */
- override fun onItemAtEndLoaded(itemAtEnd: T) {
- val requestType = PagingRequestHelper.RequestType.AFTER
- pagingRequestHelper.runIfNotRunning(
- requestType
- ) { pagingRequestCallback ->
- launch {
- invoke(
- pagingRequestCallback,
- requestType,
- itemAtEnd
- )
- }
- }
- }
-
- /**
- * Called when the item at the front of the PagedList has been loaded, and access has
- * occurred within [PagedList.Config.prefetchDistance] of it.
- *
- * No more data will be prepended to the PagedList before this item.
- *
- * @param itemAtFront The first item of PagedList
- */
- override fun onItemAtFrontLoaded(itemAtFront: T) {
- val requestType = PagingRequestHelper.RequestType.BEFORE
- pagingRequestHelper.runIfNotRunning(
- requestType
- ) { pagingRequestCallback ->
- launch {
- invoke(
- pagingRequestCallback,
- requestType,
- itemAtFront
- )
- }
- }
- }
-
- /**
- * Called when zero items are returned from an initial load of the PagedList's data source.
- */
- override fun onZeroItemsLoaded() {
- val requestType = PagingRequestHelper.RequestType.INITIAL
- pagingRequestHelper.runIfNotRunning(
- requestType
- ) { pagingRequestCallback ->
- launch {
- invoke(
- pagingRequestCallback,
- PagingRequestHelper.RequestType.INITIAL,
- null
- )
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/SampleController.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/SampleController.kt
index de2ede15..da615cfb 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/SampleController.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/SampleController.kt
@@ -1,26 +1,22 @@
package co.anitrend.retrofit.graphql.data.arch.controller
-import androidx.lifecycle.MutableLiveData
-import androidx.paging.PagingRequestHelper
-import co.anitrend.arch.data.common.ISupportPagingResponse
import co.anitrend.arch.data.common.ISupportResponse
import co.anitrend.arch.data.mapper.SupportResponseMapper
-import co.anitrend.arch.domain.entities.NetworkState
-import co.anitrend.arch.extension.dispatchers.SupportDispatchers
+import co.anitrend.arch.request.callback.RequestCallback
import co.anitrend.retrofit.graphql.data.arch.controller.strategy.ControllerStrategy
import co.anitrend.retrofit.graphql.data.arch.extensions.fetchBodyWithRetry
import io.github.wax911.library.model.attribute.GraphError
import io.github.wax911.library.model.body.GraphContainer
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.withContext
import retrofit2.Response
-internal class SampleController private constructor(
+internal class SampleController private constructor(
private val responseMapper: SupportResponseMapper,
private val strategy: ControllerStrategy,
- private val dispatchers: SupportDispatchers
-) : ISupportResponse>>, D>,
- ISupportPagingResponse>>> {
+ private val dispatcher: CoroutineDispatcher
+) : ISupportResponse>>, D> {
@Throws
private fun handleErrorsIfExist(errors: List) {
@@ -31,69 +27,48 @@ internal class SampleController private constructor(
)
}
- /**
- * Response handler for coroutine contexts which need to observe [NetworkState]
- *
- * @param resource awaiting execution
- * @param networkState for the deferred result
- *
- * @return resource fetched if present
- */
- override suspend fun invoke(
+ suspend operator fun invoke(
resource: Deferred>>,
- networkState: MutableLiveData
- ): D? {
- return strategy({
- val responseBody = resource.fetchBodyWithRetry(dispatchers.io)
- val mapped = withContext(dispatchers.computation) {
- handleErrorsIfExist(responseBody.errors.orEmpty())
- responseBody.data?.let { data ->
- responseMapper.onResponseMapFrom(data)
- }
+ requestCallback: RequestCallback,
+ interceptor: (S) -> S
+ ) = strategy(requestCallback) {
+ val responseBody = resource.fetchBodyWithRetry(dispatcher)
+ val mapped = withContext(dispatcher) {
+ handleErrorsIfExist(responseBody.errors.orEmpty())
+ responseBody.data?.let {
+ val data = interceptor(it)
+ responseMapper.onResponseMapFrom(data)
}
- withContext(dispatchers.io) {
- if (mapped != null)
- responseMapper.onResponseDatabaseInsert(mapped)
- }
- mapped
- }, networkState)
+ }
+ withContext(dispatcher) {
+ if (mapped != null)
+ responseMapper.onResponseDatabaseInsert(mapped)
+ }
+ mapped
}
/**
- * Response handler for coroutine contexts, mainly for paging
+ * Response handler for coroutine contexts which need to observe [LoadState]
*
* @param resource awaiting execution
- * @param pagingRequestHelper optional paging request callback
+ * @param requestCallback for the deferred result
+ *
+ * @return resource fetched if present
*/
override suspend fun invoke(
resource: Deferred>>,
- pagingRequestHelper: PagingRequestHelper.Request.Callback
- ) {
- strategy({
- val responseBody = resource.fetchBodyWithRetry(dispatchers.io)
- val mapped = withContext(dispatchers.computation) {
- handleErrorsIfExist(responseBody.errors.orEmpty())
- responseBody.data?.let { data ->
- responseMapper.onResponseMapFrom(data)
- }
- }
- withContext(dispatchers.io) {
- if (mapped != null)
- responseMapper.onResponseDatabaseInsert(mapped)
- }
- }, pagingRequestHelper)
- }
-
+ requestCallback: RequestCallback
+ ) = invoke(resource, requestCallback) { it }
companion object {
fun newInstance(
responseMapper: SupportResponseMapper,
strategy: ControllerStrategy,
- supportDispatchers: SupportDispatchers
+ dispatcher: CoroutineDispatcher
) = SampleController(
responseMapper,
strategy,
- supportDispatchers
+ dispatcher
)
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/policy/OfflineControllerPolicy.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/policy/OfflineControllerPolicy.kt
index c0d76c31..fdede46d 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/policy/OfflineControllerPolicy.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/policy/OfflineControllerPolicy.kt
@@ -16,9 +16,8 @@
package co.anitrend.retrofit.graphql.data.arch.controller.policy
-import androidx.lifecycle.MutableLiveData
-import androidx.paging.PagingRequestHelper
-import co.anitrend.arch.domain.entities.NetworkState
+import co.anitrend.arch.domain.entities.RequestError
+import co.anitrend.arch.request.callback.RequestCallback
import co.anitrend.retrofit.graphql.data.arch.controller.strategy.ControllerStrategy
import timber.log.Timber
@@ -30,49 +29,23 @@ import timber.log.Timber
internal class OfflineControllerPolicy private constructor() : ControllerStrategy() {
/**
- * Execute a paging task under an implementation strategy
+ * Execute a task under an implementation strategy
*
+ * @param callback event emitter
* @param block what will be executed
- * @param pagingRequestHelper paging event emitter
*/
- override suspend fun invoke(
- block: suspend () -> Unit,
- pagingRequestHelper: PagingRequestHelper.Request.Callback
- ) {
+ override suspend fun invoke(callback: RequestCallback, block: suspend () -> D?): D? {
runCatching {
block()
- pagingRequestHelper.recordSuccess()
+ callback.recordSuccess()
}.exceptionOrNull()?.also { e ->
Timber.e(e)
- pagingRequestHelper.recordFailure(e)
- }
- }
-
- /**
- * Execute a task under an implementation strategy
- *
- * @param block what will be executed
- * @param networkState network state event emitter
- */
- override suspend fun invoke(
- block: suspend () -> D?,
- networkState: MutableLiveData
- ): D? {
- return runCatching{
- networkState.postValue(NetworkState.Loading)
- val result = block()
- networkState.postValue(NetworkState.Success)
- result
- }.getOrElse {
- Timber.e(it)
- networkState.postValue(
- NetworkState.Error(
- heading = it.cause?.message ?: "Unexpected error encountered",
- message = it.message
- )
- )
- null
+ when (e) {
+ is RequestError -> callback.recordFailure(e)
+ else -> callback.recordFailure(RequestError(e))
+ }
}
+ return null
}
companion object {
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/policy/OnlineControllerPolicy.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/policy/OnlineControllerPolicy.kt
index a9bb2e73..f089664c 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/policy/OnlineControllerPolicy.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/policy/OnlineControllerPolicy.kt
@@ -1,9 +1,8 @@
package co.anitrend.retrofit.graphql.data.arch.controller.policy
-import androidx.lifecycle.MutableLiveData
-import androidx.paging.PagingRequestHelper
-import co.anitrend.arch.domain.entities.NetworkState
+import co.anitrend.arch.domain.entities.RequestError
import co.anitrend.arch.extension.network.SupportConnectivity
+import co.anitrend.arch.request.callback.RequestCallback
import co.anitrend.retrofit.graphql.data.arch.controller.strategy.ControllerStrategy
import timber.log.Timber
@@ -14,67 +13,36 @@ internal class OnlineControllerPolicy private constructor(
private val connectivity: SupportConnectivity
) : ControllerStrategy() {
- /**
- * Execute a paging task under an implementation strategy
- *
- * @param block what will be executed
- * @param pagingRequestHelper paging event emitter
- */
- override suspend fun invoke(
- block: suspend () -> Unit,
- pagingRequestHelper: PagingRequestHelper.Request.Callback
- ) {
- if (connectivity.isConnected) {
- runCatching {
- block()
- pagingRequestHelper.recordSuccess()
- }.exceptionOrNull()?.also { e ->
- e.printStackTrace()
- Timber.e(e)
- pagingRequestHelper.recordFailure(e)
- }
- }
- else {
- pagingRequestHelper.recordFailure(
- Throwable("Please check your internet connection")
- )
- }
- }
/**
* Execute a task under an implementation strategy
*
+ * @param callback event emitter
* @param block what will be executed
- * @param networkState network state event emitter
*/
override suspend fun invoke(
- block: suspend () -> D?,
- networkState: MutableLiveData
+ callback: RequestCallback,
+ block: suspend () -> D?
): D? {
- if (connectivity.isConnected) {
- return runCatching{
- networkState.postValue(NetworkState.Loading)
- val result = block()
- networkState.postValue(NetworkState.Success)
- result
- }.getOrElse {
- Timber.e(it)
- networkState.postValue(
- NetworkState.Error(
- heading = it.cause?.message ?: "Unexpected error encountered \uD83E\uDD2D",
- message = it.message
- )
+ runCatching {
+ if (connectivity.isConnected)
+ block()
+ else
+ throw RequestError(
+ "No internet connection",
+ "Make sure you have an active internet connection"
)
- null
+ }.onSuccess { result ->
+ callback.recordSuccess()
+ return result
+ }.onFailure { exception ->
+ Timber.e(exception)
+ when (exception) {
+ is RequestError -> callback.recordFailure(exception)
+ else -> callback.recordFailure(RequestError(exception))
}
- } else {
- networkState.postValue(
- NetworkState.Error(
- heading = "No internet connection detected \uD83E\uDD2D",
- message = "Please check your internet connection"
- )
- )
}
+
return null
}
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/strategy/ControllerStrategy.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/strategy/ControllerStrategy.kt
index 80dbac4d..2f2af225 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/strategy/ControllerStrategy.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/controller/strategy/ControllerStrategy.kt
@@ -1,32 +1,17 @@
package co.anitrend.retrofit.graphql.data.arch.controller.strategy
-import androidx.lifecycle.MutableLiveData
-import androidx.paging.PagingRequestHelper
-import co.anitrend.arch.domain.entities.NetworkState
+import co.anitrend.arch.request.callback.RequestCallback
internal abstract class ControllerStrategy {
- protected val moduleTag = javaClass.simpleName
-
- /**
- * Execute a paging task under an implementation strategy
- *
- * @param block what will be executed
- * @param pagingRequestHelper paging event emitter
- */
- internal abstract suspend operator fun invoke(
- block: suspend () -> Unit,
- pagingRequestHelper: PagingRequestHelper.Request.Callback
- )
-
/**
* Execute a task under an implementation strategy
*
+ * @param callback event emitter
* @param block what will be executed
- * @param networkState network state event emitter
*/
internal abstract suspend operator fun invoke(
- block: suspend () -> D?,
- networkState: MutableLiveData
+ callback: RequestCallback,
+ block: suspend () -> D?
): D?
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/extensions/SampleExtensions.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/extensions/SampleExtensions.kt
index 10a23c9e..0a98317e 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/extensions/SampleExtensions.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/extensions/SampleExtensions.kt
@@ -1,7 +1,7 @@
package co.anitrend.retrofit.graphql.data.arch.extensions
import co.anitrend.arch.data.mapper.SupportResponseMapper
-import co.anitrend.arch.extension.dispatchers.SupportDispatchers
+import co.anitrend.arch.extension.dispatchers.contract.ISupportDispatcher
import co.anitrend.retrofit.graphql.data.arch.controller.SampleController
import co.anitrend.retrofit.graphql.data.arch.controller.policy.OfflineControllerPolicy
import co.anitrend.retrofit.graphql.data.arch.controller.policy.OnlineControllerPolicy
@@ -42,7 +42,7 @@ private suspend inline fun Deferred>.executeWithRetry(
// If we have a HttpException, check whether we have a Retry-After
// header to decide how long to delay
val retryAfterHeader = e.response()?.headers()?.get("Retry-After")
- if (retryAfterHeader != null && retryAfterHeader.isNotEmpty()) {
+ if (!retryAfterHeader.isNullOrEmpty()) {
// Got a Retry-After value, try and parse it to an long
try {
nextDelay = (retryAfterHeader.toLong() + 10).coerceAtLeast(defaultDelay)
@@ -80,11 +80,11 @@ private fun defaultShouldRetry(exception: Exception) = when (exception) {
*/
internal fun SupportResponseMapper.controller(
strategy: ControllerStrategy,
- supportDispatchers: SupportDispatchers
+ supportDispatchers: ISupportDispatcher
) = SampleController.newInstance(
responseMapper = this,
strategy = strategy,
- supportDispatchers = supportDispatchers
+ dispatcher = supportDispatchers.io
)
internal fun Scope.onlineController() =
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/koin/Modules.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/koin/Modules.kt
index 8685789f..75716f96 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/koin/Modules.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/arch/koin/Modules.kt
@@ -60,21 +60,29 @@ private val networkModule = module {
private val interceptorModules = module {
factory { (exclusionHeaders: Set) ->
- ChuckerInterceptor(
- context = androidContext(),
+ ChuckerInterceptor.Builder(
+ context = androidContext()
// The previously created Collector
- collector = ChuckerCollector(
- context = androidContext(),
- // Toggles visibility of the push notification
- showNotification = true,
- // Allows to customize the retention period of collected data
- retentionPeriod = RetentionManager.Period.ONE_HOUR
- ),
- // The max body content length in bytes, after this responses will be truncated.
- maxContentLength = 10500L,
- // List of headers to replace with ** in the Chucker UI
- headersToRedact = exclusionHeaders
)
+ .collector(
+ collector = ChuckerCollector(
+ context = androidContext(),
+ // Toggles visibility of the push notification
+ showNotification = true,
+ // Allows to customize the retention period of collected data
+ retentionPeriod = RetentionManager.Period.ONE_HOUR
+ )
+ // The max body content length in bytes, after this responses will be truncated.
+ )
+ .maxContentLength(
+ length = 10500L
+ // List of headers to replace with ** in the Chucker UI
+ )
+ .redactHeaders(
+ headerNames = exclusionHeaders
+ )
+ .alwaysReadResponseBody(false)
+ .build()
}
factory { (interceptorLogLevel: HttpLoggingInterceptor.Level) ->
val okHttpClientBuilder = OkHttpClient.Builder()
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/authentication/settings/IAuthenticationSettings.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/authentication/settings/IAuthenticationSettings.kt
index 9628368f..e0f28501 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/authentication/settings/IAuthenticationSettings.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/authentication/settings/IAuthenticationSettings.kt
@@ -1,7 +1,9 @@
package co.anitrend.retrofit.graphql.data.authentication.settings
+import co.anitrend.arch.extension.settings.contract.AbstractSetting
+
interface IAuthenticationSettings {
- var authenticatedUserId: String
+ val authenticatedUserId: AbstractSetting
companion object {
const val INVALID_USER_ID: String = ""
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/helper/UploadMutationHelper.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/helper/UploadMutationHelper.kt
index 6ad5580a..898a1266 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/helper/UploadMutationHelper.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/helper/UploadMutationHelper.kt
@@ -40,7 +40,7 @@ internal object UploadMutationHelper {
* by checking if is annotated with [GraphMultiPartUpload]
*/
fun Array.supportsFileUpload(): Boolean {
- return filterIsInstance(GraphMultiPartUpload::class.java).isNotEmpty()
+ return filterIsInstance().isNotEmpty()
}
@Throws(Throwable::class)
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/koin/Modules.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/koin/Modules.kt
index d485fa73..1542ddf2 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/koin/Modules.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/koin/Modules.kt
@@ -24,7 +24,7 @@ private val sourceModule = module {
BucketSourceImpl(
remoteSource = api(EndpointType.BUCKET),
strategy = onlineController(),
- dispatchers = get(),
+ dispatcher = get(),
mapper = get()
)
}
@@ -32,7 +32,7 @@ private val sourceModule = module {
BucketUploadSourceImpl(
remoteSource = api(EndpointType.BUCKET),
strategy = onlineController(),
- dispatchers = get(),
+ dispatcher = get(),
mapper = get()
)
}
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/repository/BucketRepositoryImpl.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/repository/BucketRepositoryImpl.kt
index 6f53efb6..619864c2 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/repository/BucketRepositoryImpl.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/repository/BucketRepositoryImpl.kt
@@ -1,8 +1,8 @@
package co.anitrend.retrofit.graphql.data.bucket.repository
-import co.anitrend.arch.data.model.UserInterfaceState
-import co.anitrend.arch.data.model.UserInterfaceState.Companion.create
import co.anitrend.arch.data.repository.SupportRepository
+import co.anitrend.arch.data.state.DataState
+import co.anitrend.arch.data.state.DataState.Companion.create
import co.anitrend.retrofit.graphql.data.bucket.source.browse.contract.BucketSource
import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile
import co.anitrend.retrofit.graphql.domain.repositories.BucketRepository
@@ -16,4 +16,4 @@ internal class BucketRepositoryImpl(
)
}
-typealias BucketRepositoryContract = BucketRepository>>
\ No newline at end of file
+typealias BucketRepositoryContract = BucketRepository>>
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/repository/upload/UploadRepositoryImpl.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/repository/upload/UploadRepositoryImpl.kt
index 98b4b2a1..ae3670be 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/repository/upload/UploadRepositoryImpl.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/repository/upload/UploadRepositoryImpl.kt
@@ -1,8 +1,8 @@
package co.anitrend.retrofit.graphql.data.bucket.repository.upload
-import co.anitrend.arch.data.model.UserInterfaceState
-import co.anitrend.arch.data.model.UserInterfaceState.Companion.create
import co.anitrend.arch.data.repository.SupportRepository
+import co.anitrend.arch.data.state.DataState
+import co.anitrend.arch.data.state.DataState.Companion.create
import co.anitrend.retrofit.graphql.data.bucket.source.upload.contract.BucketUploadSource
import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile
import co.anitrend.retrofit.graphql.domain.models.common.IGraphQuery
@@ -18,4 +18,4 @@ internal class UploadRepositoryImpl(
)
}
-typealias UploadRepositoryContract = UploadRepository>
\ No newline at end of file
+typealias UploadRepositoryContract = UploadRepository>
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/browse/BucketSourceImpl.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/browse/BucketSourceImpl.kt
index 14bae901..a0939de6 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/browse/BucketSourceImpl.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/browse/BucketSourceImpl.kt
@@ -1,6 +1,7 @@
package co.anitrend.retrofit.graphql.data.bucket.source.browse
-import co.anitrend.arch.extension.dispatchers.SupportDispatchers
+import co.anitrend.arch.extension.dispatchers.contract.ISupportDispatcher
+import co.anitrend.arch.request.callback.RequestCallback
import co.anitrend.retrofit.graphql.data.arch.controller.strategy.ControllerStrategy
import co.anitrend.retrofit.graphql.data.arch.extensions.controller
import co.anitrend.retrofit.graphql.data.bucket.datasource.remote.BucketRemoteSource
@@ -8,6 +9,7 @@ import co.anitrend.retrofit.graphql.data.bucket.mapper.BucketResponseMapper
import co.anitrend.retrofit.graphql.data.bucket.source.browse.contract.BucketSource
import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile
import io.github.wax911.library.model.request.QueryContainerBuilder
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
@@ -15,14 +17,13 @@ internal class BucketSourceImpl(
private val mapper: BucketResponseMapper,
private val remoteSource: BucketRemoteSource,
private val strategy: ControllerStrategy>,
- dispatchers: SupportDispatchers
-) : BucketSource(dispatchers) {
+ override val dispatcher: ISupportDispatcher,
+) : BucketSource() {
override val observable =
MutableStateFlow?>(null)
- override suspend fun getStorageBucketFiles() {
- super.getStorageBucketFiles()
+ override suspend fun getStorageBucketFiles(requestCallback: RequestCallback) {
val deferred = async {
val queryBuilder = QueryContainerBuilder()
remoteSource.getStorageBucketFiles(
@@ -31,16 +32,18 @@ internal class BucketSourceImpl(
}
val controller =
- mapper.controller(strategy, dispatchers)
+ mapper.controller(strategy, dispatcher)
- val result = controller(deferred, networkState)
+ val result = controller(deferred, requestCallback)
observable.value = result
}
/**
* Clears data sources (databases, preferences, e.t.c)
+ *
+ * @param context Dispatcher context to run in
*/
- override suspend fun clearDataSource() {
+ override suspend fun clearDataSource(context: CoroutineDispatcher) {
observable.value = null
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/browse/contract/BucketSource.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/browse/contract/BucketSource.kt
index 3ab17254..264cc42d 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/browse/contract/BucketSource.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/browse/contract/BucketSource.kt
@@ -1,28 +1,28 @@
package co.anitrend.retrofit.graphql.data.bucket.source.browse.contract
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.asLiveData
-import androidx.lifecycle.liveData
-import co.anitrend.arch.data.source.coroutine.SupportCoroutineDataSource
-import co.anitrend.arch.extension.dispatchers.SupportDispatchers
+import co.anitrend.arch.data.source.core.SupportCoreDataSource
+import co.anitrend.arch.request.callback.RequestCallback
+import co.anitrend.arch.request.model.Request
import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.launch
-internal abstract class BucketSource(
- dispatchers: SupportDispatchers
-) : SupportCoroutineDataSource(dispatchers) {
+internal abstract class BucketSource : SupportCoreDataSource() {
protected abstract val observable: StateFlow?>
- protected open suspend fun getStorageBucketFiles() {
- retry = { getStorageBucketFiles() }
- }
+ protected abstract suspend fun getStorageBucketFiles(requestCallback: RequestCallback)
- operator fun invoke(): LiveData> =
- liveData {
- getStorageBucketFiles()
- val bucketFlow = observable.mapNotNull { it }
- emitSource(bucketFlow.asLiveData())
+ operator fun invoke(): Flow> {
+ scope.launch {
+ requestHelper.runIfNotRunning(
+ Request.Default("getStorageBucketFiles", Request.Type.INITIAL)
+ ) {
+ getStorageBucketFiles(it)
+ }
}
+ return observable.mapNotNull { it }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/upload/BucketUploadSourceImpl.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/upload/BucketUploadSourceImpl.kt
index 637bb430..e40c260e 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/upload/BucketUploadSourceImpl.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/upload/BucketUploadSourceImpl.kt
@@ -1,6 +1,7 @@
package co.anitrend.retrofit.graphql.data.bucket.source.upload
-import co.anitrend.arch.extension.dispatchers.SupportDispatchers
+import co.anitrend.arch.extension.dispatchers.contract.ISupportDispatcher
+import co.anitrend.arch.request.callback.RequestCallback
import co.anitrend.retrofit.graphql.data.arch.controller.strategy.ControllerStrategy
import co.anitrend.retrofit.graphql.data.arch.extensions.controller
import co.anitrend.retrofit.graphql.data.bucket.datasource.remote.BucketRemoteSource
@@ -9,6 +10,7 @@ import co.anitrend.retrofit.graphql.data.bucket.source.upload.contract.BucketUpl
import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile
import co.anitrend.retrofit.graphql.domain.models.common.IGraphQuery
import io.github.wax911.library.model.request.QueryContainerBuilder
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
@@ -16,14 +18,16 @@ internal class BucketUploadSourceImpl(
private val mapper: UploadResponseMapper,
private val remoteSource: BucketRemoteSource,
private val strategy: ControllerStrategy,
- dispatchers: SupportDispatchers
-) : BucketUploadSource(dispatchers) {
+ override val dispatcher: ISupportDispatcher,
+ ) : BucketUploadSource() {
override val observable =
MutableStateFlow(null)
- override suspend fun uploadToStorageBucket(mutation: IGraphQuery) {
- super.uploadToStorageBucket(mutation)
+ override suspend fun uploadToStorageBucket(
+ mutation: IGraphQuery,
+ requestCallback: RequestCallback,
+ ) {
val deferred = async {
val queryBuilder = QueryContainerBuilder()
.putVariables(mutation.toMap())
@@ -31,16 +35,18 @@ internal class BucketUploadSourceImpl(
}
val controller =
- mapper.controller(strategy, dispatchers)
+ mapper.controller(strategy, dispatcher)
- val result = controller(deferred, networkState)
+ val result = controller(deferred, requestCallback)
observable.value = result
}
/**
* Clears data sources (databases, preferences, e.t.c)
+ *
+ * @param context Dispatcher context to run in
*/
- override suspend fun clearDataSource() {
+ override suspend fun clearDataSource(context: CoroutineDispatcher) {
observable.value = null
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/upload/contract/BucketUploadSource.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/upload/contract/BucketUploadSource.kt
index b3f34bd9..aef69418 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/upload/contract/BucketUploadSource.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/source/upload/contract/BucketUploadSource.kt
@@ -1,28 +1,32 @@
package co.anitrend.retrofit.graphql.data.bucket.source.upload.contract
-import androidx.lifecycle.asLiveData
-import androidx.lifecycle.liveData
-import co.anitrend.arch.data.source.coroutine.SupportCoroutineDataSource
-import co.anitrend.arch.extension.dispatchers.SupportDispatchers
+import co.anitrend.arch.data.source.core.SupportCoreDataSource
+import co.anitrend.arch.request.callback.RequestCallback
+import co.anitrend.arch.request.model.Request
import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile
import co.anitrend.retrofit.graphql.domain.models.common.IGraphQuery
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.launch
-internal abstract class BucketUploadSource(
- dispatchers: SupportDispatchers
-) : SupportCoroutineDataSource(dispatchers) {
+internal abstract class BucketUploadSource: SupportCoreDataSource() {
protected abstract val observable: StateFlow
- protected open suspend fun uploadToStorageBucket(mutation: IGraphQuery) {
- retry = { uploadToStorageBucket(mutation) }
- }
+ protected abstract suspend fun uploadToStorageBucket(
+ mutation: IGraphQuery,
+ requestCallback: RequestCallback
+ )
- operator fun invoke(mutation: IGraphQuery) =
- liveData {
- uploadToStorageBucket(mutation)
- val bucketFlow = observable.mapNotNull { it }
- emitSource(bucketFlow.asLiveData())
+ operator fun invoke(mutation: IGraphQuery): Flow {
+ scope.launch {
+ requestHelper.runIfNotRunning(
+ Request.Default("uploadToStorageBucket", Request.Type.INITIAL)
+ ) {
+ uploadToStorageBucket(mutation, it)
+ }
}
+ return observable.mapNotNull { it }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/usecase/BucketUseCaseImpl.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/usecase/BucketUseCaseImpl.kt
index 98b761f7..acadf214 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/usecase/BucketUseCaseImpl.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/usecase/BucketUseCaseImpl.kt
@@ -1,7 +1,7 @@
package co.anitrend.retrofit.graphql.data.bucket.usecase
-import co.anitrend.arch.data.model.UserInterfaceState
import co.anitrend.arch.data.repository.contract.ISupportRepository
+import co.anitrend.arch.data.state.DataState
import co.anitrend.retrofit.graphql.data.bucket.repository.BucketRepositoryContract
import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile
import co.anitrend.retrofit.graphql.domain.usecases.BucketUseCase
@@ -18,4 +18,4 @@ internal class BucketUseCaseImpl(
}
}
-typealias BucketUseCaseContract = BucketUseCase>>
\ No newline at end of file
+typealias BucketUseCaseContract = BucketUseCase>>
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/usecase/upload/UploadUseCaseImpl.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/usecase/upload/UploadUseCaseImpl.kt
index b1edcb89..618aff8c 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/usecase/upload/UploadUseCaseImpl.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/bucket/usecase/upload/UploadUseCaseImpl.kt
@@ -1,7 +1,7 @@
package co.anitrend.retrofit.graphql.data.bucket.usecase.upload
-import co.anitrend.arch.data.model.UserInterfaceState
import co.anitrend.arch.data.repository.contract.ISupportRepository
+import co.anitrend.arch.data.state.DataState
import co.anitrend.retrofit.graphql.data.bucket.repository.upload.UploadRepositoryContract
import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile
import co.anitrend.retrofit.graphql.domain.usecases.UploadUseCase
@@ -18,4 +18,4 @@ internal class UploadUseCaseImpl(
}
}
-typealias UploadUseCaseContract = UploadUseCase>
\ No newline at end of file
+typealias UploadUseCaseContract = UploadUseCase>
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/converters/MarketPlaceConverters.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/converters/MarketPlaceConverters.kt
index 28fe018d..96463949 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/converters/MarketPlaceConverters.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/converters/MarketPlaceConverters.kt
@@ -1,89 +1,48 @@
package co.anitrend.retrofit.graphql.data.market.converters
import co.anitrend.arch.data.converter.SupportConverter
-import co.anitrend.arch.data.mapper.contract.ISupportMapperHelper
-import co.anitrend.retrofit.graphql.data.arch.common.SampleMapper
import co.anitrend.retrofit.graphql.data.market.entity.MarketPlaceEntity
import co.anitrend.retrofit.graphql.data.market.model.edge.MarketPlaceListingEdge
import co.anitrend.retrofit.graphql.domain.entities.market.MarketPlaceListing
internal class MarketPlaceEntityConverter(
- override val fromType: (MarketPlaceEntity) -> MarketPlaceListing = { from().transform(it) },
- override val toType: (MarketPlaceListing) -> MarketPlaceEntity = { to().transform(it) }
-) : SupportConverter() {
- companion object : SampleMapper() {
- override fun from() =
- object : ISupportMapperHelper {
- /**
- * Transforms the the [source] to the target type
- */
- override fun transform(source: MarketPlaceEntity) =
- MarketPlaceListing(
- id = source.id,
- cursorId = source.cursorId,
- logoUrl = source.logoUrl,
- background = source.logoBackground,
- name = source.name,
- categories = source.categories,
- slug = source.slug,
- description = source.description,
- isPaid = source.isPaid,
- isVerified = source.isVerified
- )
- }
-
- override fun to() =
- object : ISupportMapperHelper {
- /**
- * Transforms the the [source] to the target type
- */
- override fun transform(source: MarketPlaceListing): MarketPlaceEntity {
- throw Throwable("Not yet implemented")
- }
- }
- }
-}
+ override val fromType: (MarketPlaceEntity) -> MarketPlaceListing = {
+ MarketPlaceListing(
+ id = it.id,
+ cursorId = it.cursorId,
+ logoUrl = it.logoUrl,
+ background = it.logoBackground,
+ name = it.name,
+ categories = it.categories,
+ slug = it.slug,
+ description = it.description,
+ isPaid = it.isPaid,
+ isVerified = it.isVerified
+ )
+ },
+ override val toType: (MarketPlaceListing) -> MarketPlaceEntity = { throw NotImplementedError() }
+) : SupportConverter()
internal class MarketPlaceModelConverter(
- override val fromType: (MarketPlaceEntity) -> MarketPlaceListingEdge = { from().transform(it) },
- override val toType: (MarketPlaceListingEdge) -> MarketPlaceEntity = { to().transform(it) }
-) : SupportConverter() {
- companion object : SampleMapper() {
- override fun from() =
- object : ISupportMapperHelper {
- /**
- * Transforms the the [source] to the target type
- */
- override fun transform(source: MarketPlaceEntity): MarketPlaceListingEdge {
- throw Throwable("Not yet implemented")
- }
- }
-
- override fun to() =
- object : ISupportMapperHelper {
- /**
- * Transforms the the [source] to the target type
- */
- override fun transform(source: MarketPlaceListingEdge): MarketPlaceEntity {
- val categories = listOf(
- source.node.primaryCategory.name,
- source.node.secondaryCategory?.name
- ).mapNotNull { it }
+ override val fromType: (MarketPlaceEntity) -> MarketPlaceListingEdge = { throw NotImplementedError() },
+ override val toType: (MarketPlaceListingEdge) -> MarketPlaceEntity = {
+ val categories = listOf(
+ it.node.primaryCategory.name,
+ it.node.secondaryCategory?.name
+ ).mapNotNull { name -> name }
- return MarketPlaceEntity(
- id = source.node.id,
- cursorId = source.cursor,
- logoUrl = source.node.logo,
- logoBackground = source.node.background,
- name = source.node.name,
- categories = categories,
- slug = source.node.slug,
- description = source.node.description,
- isPaid = source.node.isPaid,
- isVerified = source.node.isVerified
- )
- }
- }
+ MarketPlaceEntity(
+ id = it.node.id,
+ cursorId = it.cursor,
+ logoUrl = it.node.logo,
+ logoBackground = it.node.background,
+ name = it.node.name,
+ categories = categories,
+ slug = it.node.slug,
+ description = it.node.description,
+ isPaid = it.node.isPaid,
+ isVerified = it.node.isVerified
+ )
}
-}
+) : SupportConverter()
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/koin/Modules.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/koin/Modules.kt
index 41712259..c08eb7fd 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/koin/Modules.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/koin/Modules.kt
@@ -23,7 +23,7 @@ private val sourceModule = module {
strategy = onlineController(),
mapper = get(),
converter = get(),
- dispatchers = get()
+ dispatcher = get()
)
}
}
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/repository/MarketPlaceRepositoryImpl.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/repository/MarketPlaceRepositoryImpl.kt
index 4b4fa23d..3b57316c 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/repository/MarketPlaceRepositoryImpl.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/repository/MarketPlaceRepositoryImpl.kt
@@ -1,9 +1,9 @@
package co.anitrend.retrofit.graphql.data.market.repository
import androidx.paging.PagedList
-import co.anitrend.arch.data.model.UserInterfaceState
-import co.anitrend.arch.data.model.UserInterfaceState.Companion.create
import co.anitrend.arch.data.repository.SupportRepository
+import co.anitrend.arch.data.state.DataState
+import co.anitrend.arch.data.state.DataState.Companion.create
import co.anitrend.retrofit.graphql.data.market.source.contract.MarketPlaceSource
import co.anitrend.retrofit.graphql.domain.entities.market.MarketPlaceListing
import co.anitrend.retrofit.graphql.domain.repositories.MarketPlaceRepository
@@ -18,4 +18,4 @@ internal class MarketPlaceRepositoryImpl(
)
}
-typealias MarketPlaceRepositoryContract = MarketPlaceRepository>>
\ No newline at end of file
+typealias MarketPlaceRepositoryContract = MarketPlaceRepository>>
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/source/MarketPlaceSourceImpl.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/source/MarketPlaceSourceImpl.kt
index c9206096..c3d01bb2 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/source/MarketPlaceSourceImpl.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/source/MarketPlaceSourceImpl.kt
@@ -1,11 +1,9 @@
package co.anitrend.retrofit.graphql.data.market.source
-import androidx.lifecycle.liveData
-import androidx.paging.PagedList
-import androidx.paging.PagingRequestHelper
-import androidx.paging.toLiveData
-import co.anitrend.arch.data.util.SupportDataKeyStore
-import co.anitrend.arch.extension.dispatchers.SupportDispatchers
+import co.anitrend.arch.extension.dispatchers.contract.ISupportDispatcher
+import co.anitrend.arch.paging.legacy.FlowPagedListBuilder
+import co.anitrend.arch.paging.legacy.util.PAGING_CONFIGURATION
+import co.anitrend.arch.request.callback.RequestCallback
import co.anitrend.retrofit.graphql.data.arch.controller.strategy.ControllerStrategy
import co.anitrend.retrofit.graphql.data.arch.extensions.controller
import co.anitrend.retrofit.graphql.data.market.converters.MarketPlaceEntityConverter
@@ -15,9 +13,10 @@ import co.anitrend.retrofit.graphql.data.market.entity.MarketPlaceEntity
import co.anitrend.retrofit.graphql.data.market.mapper.MarketPlaceResponseMapper
import co.anitrend.retrofit.graphql.data.market.model.query.MarketPlaceListingQuery
import co.anitrend.retrofit.graphql.data.market.source.contract.MarketPlaceSource
-import co.anitrend.retrofit.graphql.domain.entities.market.MarketPlaceListing
import io.github.wax911.library.model.request.QueryContainerBuilder
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.async
+import kotlinx.coroutines.withContext
internal class MarketPlaceSourceImpl(
private val mapper: MarketPlaceResponseMapper,
@@ -25,65 +24,46 @@ internal class MarketPlaceSourceImpl(
private val remoteSource: MarketPlaceRemoteSource,
private val localSource: MarketPlaceLocalSource,
private val strategy: ControllerStrategy>,
- dispatchers: SupportDispatchers
-) : MarketPlaceSource(dispatchers) {
+ override val dispatcher: ISupportDispatcher
+) : MarketPlaceSource() {
- private fun buildMarketPlaceListingQuery(
- requestType: PagingRequestHelper.RequestType,
- model: MarketPlaceListing?
- ): MarketPlaceListingQuery {
- val query = MarketPlaceListingQuery(first = supportPagingHelper.pageSize)
- when (requestType) {
- PagingRequestHelper.RequestType.BEFORE ->
- query.before = model?.cursorId
- PagingRequestHelper.RequestType.AFTER ->
- query.after = model?.cursorId
- PagingRequestHelper.RequestType.INITIAL -> {}
- }
- return query
- }
-
- override val observable = liveData {
- val factory = localSource.findAllByFactory()
- val pagingSource = factory.mapByPage { entities ->
- converter.convertFrom(entities)
- }
-
- val callback: PagedList.BoundaryCallback = this@MarketPlaceSourceImpl
-
- emitSource(
- pagingSource.toLiveData(
- config = SupportDataKeyStore.PAGING_CONFIGURATION,
- boundaryCallback = callback
- )
- )
- }
+ override val observable = FlowPagedListBuilder(
+ dataSourceFactory = localSource.findAllByFactory()
+ .mapByPage { entities ->
+ converter.convertFrom(entities)
+ },
+ config = PAGING_CONFIGURATION,
+ initialLoadKey = null,
+ boundaryCallback = this,
+ ).buildFlow()
/**
* Invoked when a request to the network needs to happen
*/
- override suspend fun invoke(
- callback: PagingRequestHelper.Request.Callback,
- requestType: PagingRequestHelper.RequestType,
- model: MarketPlaceListing?
+ override suspend fun getMarketPlaceListing(
+ requestCallback: RequestCallback,
+ marketPlaceListingQuery: MarketPlaceListingQuery,
) {
- val marketPlaceQuery = buildMarketPlaceListingQuery(requestType, model)
val deferred = async {
val queryBuilder = QueryContainerBuilder()
- .putVariables(marketPlaceQuery.toMap())
+ .putVariables(marketPlaceListingQuery.toMap())
remoteSource.getMarketPlaceApps(queryBuilder)
}
val controller =
- mapper.controller(strategy, dispatchers)
+ mapper.controller(strategy, dispatcher)
- controller(deferred, callback)
+ controller(deferred, requestCallback)
}
/**
* Clears data sources (databases, preferences, e.t.c)
+ *
+ * @param context Dispatcher context to run in
*/
- override suspend fun clearDataSource() {
- localSource.clear()
+ override suspend fun clearDataSource(context: CoroutineDispatcher) {
+ withContext(dispatcher.io) {
+ localSource.clear()
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/source/contract/MarketPlaceSource.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/source/contract/MarketPlaceSource.kt
index a461e1cb..63d3e130 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/source/contract/MarketPlaceSource.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/source/contract/MarketPlaceSource.kt
@@ -1,16 +1,86 @@
package co.anitrend.retrofit.graphql.data.market.source.contract
-import androidx.lifecycle.LiveData
import androidx.paging.PagedList
-import co.anitrend.arch.extension.dispatchers.SupportDispatchers
-import co.anitrend.retrofit.graphql.data.arch.common.SamplePagedSource
+import co.anitrend.arch.paging.legacy.source.SupportPagingDataSource
+import co.anitrend.arch.request.callback.RequestCallback
+import co.anitrend.arch.request.model.Request
+import co.anitrend.retrofit.graphql.data.market.model.query.MarketPlaceListingQuery
import co.anitrend.retrofit.graphql.domain.entities.market.MarketPlaceListing
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
-internal abstract class MarketPlaceSource(
- dispatchers: SupportDispatchers
-) : SamplePagedSource(dispatchers) {
+internal abstract class MarketPlaceSource : SupportPagingDataSource() {
- protected abstract val observable: LiveData>
+ protected abstract val observable: Flow>
- operator fun invoke() = observable
+ protected abstract suspend fun getMarketPlaceListing(
+ requestCallback: RequestCallback,
+ marketPlaceListingQuery: MarketPlaceListingQuery,
+ )
+
+ operator fun invoke(): Flow> {
+ return observable
+ }
+
+ /**
+ * Called when the item at the end of the PagedList has been loaded, and access has
+ * occurred within [Config.prefetchDistance] of it.
+ *
+ *
+ * No more data will be appended to the PagedList after this item.
+ *
+ * @param itemAtEnd The first item of PagedList
+ */
+ override fun onItemAtEndLoaded(itemAtEnd: MarketPlaceListing) {
+ super.onItemAtEndLoaded(itemAtEnd)
+ scope.launch {
+ val request = Request.Default(itemAtEnd.cursorId, Request.Type.AFTER)
+ requestHelper.runIfNotRunning(request) { requestCallback ->
+ val query = MarketPlaceListingQuery(
+ after = itemAtEnd.cursorId,
+ first = supportPagingHelper.pageSize
+ )
+ getMarketPlaceListing(requestCallback, query)
+ }
+ }
+ }
+
+ /**
+ * Called when the item at the front of the PagedList has been loaded, and access has
+ * occurred within [Config.prefetchDistance] of it.
+ *
+ *
+ * No more data will be prepended to the PagedList before this item.
+ *
+ * @param itemAtFront The first item of PagedList
+ */
+ override fun onItemAtFrontLoaded(itemAtFront: MarketPlaceListing) {
+ super.onItemAtFrontLoaded(itemAtFront)
+ scope.launch {
+ val request = Request.Default(itemAtFront.cursorId, Request.Type.BEFORE)
+ requestHelper.runIfNotRunning(request) { requestCallback ->
+ val query = MarketPlaceListingQuery(
+ before = itemAtFront.cursorId,
+ first = supportPagingHelper.pageSize
+ )
+ getMarketPlaceListing(requestCallback, query)
+ }
+ }
+ }
+
+ /**
+ * Called when zero items are returned from an initial load of the PagedList's data source.
+ */
+ override fun onZeroItemsLoaded() {
+ super.onZeroItemsLoaded()
+ scope.launch {
+ val request = Request.Default("initial", Request.Type.INITIAL)
+ requestHelper.runIfNotRunning(request) { requestCallback ->
+ val query = MarketPlaceListingQuery(
+ first = supportPagingHelper.pageSize
+ )
+ getMarketPlaceListing(requestCallback, query)
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/usecase/MarketPlaceUseCaseImpl.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/usecase/MarketPlaceUseCaseImpl.kt
index 30ca90a6..ab2dc410 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/usecase/MarketPlaceUseCaseImpl.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/market/usecase/MarketPlaceUseCaseImpl.kt
@@ -1,8 +1,8 @@
package co.anitrend.retrofit.graphql.data.market.usecase
import androidx.paging.PagedList
-import co.anitrend.arch.data.model.UserInterfaceState
import co.anitrend.arch.data.repository.contract.ISupportRepository
+import co.anitrend.arch.data.state.DataState
import co.anitrend.retrofit.graphql.data.market.repository.MarketPlaceRepositoryContract
import co.anitrend.retrofit.graphql.domain.entities.market.MarketPlaceListing
import co.anitrend.retrofit.graphql.domain.usecases.MarketPlaceUseCase
@@ -20,4 +20,4 @@ internal class MarketPlaceUseCaseImpl(
}
}
-typealias MarketPlaceUseCaseContract = MarketPlaceUseCase>>
\ No newline at end of file
+typealias MarketPlaceUseCaseContract = MarketPlaceUseCase>>
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/converters/UserConverters.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/converters/UserConverters.kt
index 69950163..cc2fdd08 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/converters/UserConverters.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/converters/UserConverters.kt
@@ -1,64 +1,36 @@
package co.anitrend.retrofit.graphql.data.user.converters
import co.anitrend.arch.data.converter.SupportConverter
-import co.anitrend.arch.data.mapper.contract.ISupportMapperHelper
-import co.anitrend.retrofit.graphql.data.arch.common.SampleMapper
import co.anitrend.retrofit.graphql.data.user.entity.UserEntity
import co.anitrend.retrofit.graphql.data.user.model.node.UserNode
import co.anitrend.retrofit.graphql.domain.entities.user.User
internal class UserEntityConverter(
- override val fromType: (UserEntity) -> User = { from().transform(it) },
- override val toType: (User) -> UserEntity = { to().transform(it) }
-) : SupportConverter() {
- companion object : SampleMapper() {
- override fun from() =
- object : ISupportMapperHelper {
- /**
- * Transforms the the [source] to the target type
- */
- override fun transform(source: UserEntity) =
- User(
- id = source.id,
- avatar = source.avatarUrl,
- bio = source.bio.orEmpty(),
- status = User.Status(
- emoji = source.statusEmoji.orEmpty(),
- message = source.statusMessage.orEmpty()
- ),
- username = source.username
- )
- }
-
- override fun to(): ISupportMapperHelper {
- throw Throwable("Not yet implemented")
- }
- }
-}
+ override val fromType: (UserEntity) -> User = {
+ User(
+ id = it.id,
+ avatar = it.avatarUrl,
+ bio = it.bio.orEmpty(),
+ status = User.Status(
+ emoji = it.statusEmoji.orEmpty(),
+ message = it.statusMessage.orEmpty()
+ ),
+ username = it.username
+ )
+ },
+ override val toType: (User) -> UserEntity = { throw NotImplementedError() }
+) : SupportConverter()
internal class UserModelConverter(
- override val fromType: (UserEntity) -> UserNode = { from().transform(it) },
- override val toType: (UserNode) -> UserEntity = { to().transform(it) }
-): SupportConverter() {
- companion object : SampleMapper() {
- override fun from(): ISupportMapperHelper {
- throw Throwable("Not yet implemented")
- }
-
- override fun to() =
- object : ISupportMapperHelper {
- /**
- * Transforms the the [source] to the target type
- */
- override fun transform(source: UserNode) =
- UserEntity(
- id = source.id,
- username = source.login,
- bio = source.bio,
- avatarUrl = source.avatarUrl,
- statusEmoji = source.status?.emoji,
- statusMessage = source.status?.message
- )
- }
+ override val fromType: (UserEntity) -> UserNode = { throw NotImplementedError() },
+ override val toType: (UserNode) -> UserEntity = {
+ UserEntity(
+ id = it.id,
+ username = it.login,
+ bio = it.bio,
+ avatarUrl = it.avatarUrl,
+ statusEmoji = it.status?.emoji,
+ statusMessage = it.status?.message
+ )
}
-}
\ No newline at end of file
+): SupportConverter()
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/koin/Modules.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/koin/Modules.kt
index 85b90ece..7901f56c 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/koin/Modules.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/koin/Modules.kt
@@ -24,7 +24,7 @@ private val sourceModule = module {
strategy = onlineController(),
mapper = get(),
converter = get(),
- dispatchers = get()
+ dispatcher = get()
)
}
}
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/repository/UserRepositoryImpl.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/repository/UserRepositoryImpl.kt
index f9861487..4c51c782 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/repository/UserRepositoryImpl.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/repository/UserRepositoryImpl.kt
@@ -1,8 +1,8 @@
package co.anitrend.retrofit.graphql.data.user.repository
-import co.anitrend.arch.data.model.UserInterfaceState
-import co.anitrend.arch.data.model.UserInterfaceState.Companion.create
import co.anitrend.arch.data.repository.SupportRepository
+import co.anitrend.arch.data.state.DataState
+import co.anitrend.arch.data.state.DataState.Companion.create
import co.anitrend.retrofit.graphql.data.user.source.contract.UserSource
import co.anitrend.retrofit.graphql.domain.entities.user.User
import co.anitrend.retrofit.graphql.domain.repositories.UserRepository
@@ -16,4 +16,4 @@ internal class UserRepositoryImpl(
)
}
-typealias UserRepositoryContract = UserRepository>
\ No newline at end of file
+typealias UserRepositoryContract = UserRepository>
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/source/UserSourceImpl.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/source/UserSourceImpl.kt
index 1776d4b1..69cc72a9 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/source/UserSourceImpl.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/source/UserSourceImpl.kt
@@ -1,6 +1,7 @@
package co.anitrend.retrofit.graphql.data.user.source
-import co.anitrend.arch.extension.dispatchers.SupportDispatchers
+import co.anitrend.arch.extension.dispatchers.contract.ISupportDispatcher
+import co.anitrend.arch.request.callback.RequestCallback
import co.anitrend.retrofit.graphql.data.arch.controller.strategy.ControllerStrategy
import co.anitrend.retrofit.graphql.data.arch.extensions.controller
import co.anitrend.retrofit.graphql.data.authentication.settings.IAuthenticationSettings
@@ -11,10 +12,12 @@ import co.anitrend.retrofit.graphql.data.user.entity.UserEntity
import co.anitrend.retrofit.graphql.data.user.mapper.UserResponseMapper
import co.anitrend.retrofit.graphql.data.user.source.contract.UserSource
import io.github.wax911.library.model.request.QueryContainerBuilder
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
internal class UserSourceImpl(
private val settings: IAuthenticationSettings,
@@ -23,13 +26,13 @@ internal class UserSourceImpl(
private val remoteSource: UserRemoteSource,
private val localSource: UserLocalSource,
private val strategy: ControllerStrategy,
- dispatchers: SupportDispatchers
-) : UserSource(dispatchers) {
+ override val dispatcher: ISupportDispatcher,
+) : UserSource() {
override val observable = flow {
- val userFlowEntity = if (
- settings.authenticatedUserId.isNotEmpty()
- ) localSource.getUserById(settings.authenticatedUserId)
+ val authId = settings.authenticatedUserId.value
+ val userFlowEntity = if (authId.isNotEmpty())
+ localSource.getUserById(authId)
else localSource.getDefaultUser()
val userFlow = userFlowEntity.map { entity ->
@@ -38,28 +41,32 @@ internal class UserSourceImpl(
emitAll(userFlow)
}
- override suspend fun getCurrentUser() {
- super.getCurrentUser()
+ override suspend fun getCurrentUser(requestCallback: RequestCallback) {
+ val authId = settings.authenticatedUserId.value
// simulating some sort of cache refresh policy
- if (settings.authenticatedUserId.isNotEmpty()) return
+ if (authId.isNotEmpty()) return
val deferred = async {
val queryBuilder = QueryContainerBuilder()
remoteSource.getCurrentUser(queryBuilder)
}
val controller =
- mapper.controller(strategy, dispatchers)
+ mapper.controller(strategy, dispatcher)
- val result = controller(deferred, networkState)
+ val result = controller(deferred, requestCallback)
if (result != null)
- settings.authenticatedUserId = result.id
+ settings.authenticatedUserId.value = result.id
}
/**
* Clears data sources (databases, preferences, e.t.c)
+ *
+ * @param context Dispatcher context to run in
*/
- override suspend fun clearDataSource() {
- settings.authenticatedUserId = IAuthenticationSettings.INVALID_USER_ID
- localSource.clear()
+ override suspend fun clearDataSource(context: CoroutineDispatcher) {
+ withContext(context) {
+ settings.authenticatedUserId.value = IAuthenticationSettings.INVALID_USER_ID
+ localSource.clear()
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/source/contract/UserSource.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/source/contract/UserSource.kt
index 3f61cbed..b1a31cf6 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/source/contract/UserSource.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/source/contract/UserSource.kt
@@ -1,26 +1,28 @@
package co.anitrend.retrofit.graphql.data.user.source.contract
-import androidx.lifecycle.asLiveData
-import androidx.lifecycle.liveData
-import co.anitrend.arch.data.source.coroutine.SupportCoroutineDataSource
-import co.anitrend.arch.extension.dispatchers.SupportDispatchers
+import co.anitrend.arch.data.source.core.SupportCoreDataSource
+import co.anitrend.arch.request.callback.RequestCallback
+import co.anitrend.arch.request.model.Request
import co.anitrend.retrofit.graphql.domain.entities.user.User
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.launch
-internal abstract class UserSource(
- dispatchers: SupportDispatchers
-) : SupportCoroutineDataSource(dispatchers) {
+internal abstract class UserSource : SupportCoreDataSource() {
protected abstract val observable: Flow
- protected open suspend fun getCurrentUser() {
- retry = { getCurrentUser() }
- }
+ protected abstract suspend fun getCurrentUser(
+ requestCallback: RequestCallback
+ )
- operator fun invoke() = liveData {
- getCurrentUser()
- val userFlow = observable.mapNotNull { it }
- emitSource(userFlow.asLiveData())
+ operator fun invoke(): Flow {
+ scope.launch {
+ requestHelper.runIfNotRunning(
+ request = Request.Default("getCurrentUser", Request.Type.INITIAL),
+ handleCallback = ::getCurrentUser
+ )
+ }
+ return observable.mapNotNull { it }
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/usecase/UserUseCaseImpl.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/usecase/UserUseCaseImpl.kt
index 70e2300f..c1c14cf4 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/usecase/UserUseCaseImpl.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/data/user/usecase/UserUseCaseImpl.kt
@@ -1,7 +1,7 @@
package co.anitrend.retrofit.graphql.data.user.usecase
-import co.anitrend.arch.data.model.UserInterfaceState
import co.anitrend.arch.data.repository.contract.ISupportRepository
+import co.anitrend.arch.data.state.DataState
import co.anitrend.retrofit.graphql.data.user.repository.UserRepositoryContract
import co.anitrend.retrofit.graphql.domain.entities.user.User
import co.anitrend.retrofit.graphql.domain.usecases.UserUseCase
@@ -18,4 +18,4 @@ internal class UserUseCaseImpl(
}
}
-typealias UserUseCaseContract = UserUseCase>
\ No newline at end of file
+typealias UserUseCaseContract = UserUseCase>
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/BucketRepository.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/BucketRepository.kt
index ce64b3ec..00a3d1fd 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/BucketRepository.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/BucketRepository.kt
@@ -1,5 +1,7 @@
package co.anitrend.retrofit.graphql.domain.repositories
-interface BucketRepository {
+import co.anitrend.arch.domain.state.UiState
+
+interface BucketRepository> {
fun getAllFiles() : D
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/MarketPlaceRepository.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/MarketPlaceRepository.kt
index 7ac5d675..98c4faa8 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/MarketPlaceRepository.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/MarketPlaceRepository.kt
@@ -1,5 +1,7 @@
package co.anitrend.retrofit.graphql.domain.repositories
-interface MarketPlaceRepository {
+import co.anitrend.arch.domain.state.UiState
+
+interface MarketPlaceRepository> {
fun getMarketPlaceListings(): D
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/UploadRepository.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/UploadRepository.kt
index 3102582b..34c76e86 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/UploadRepository.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/UploadRepository.kt
@@ -1,7 +1,8 @@
package co.anitrend.retrofit.graphql.domain.repositories
+import co.anitrend.arch.domain.state.UiState
import co.anitrend.retrofit.graphql.domain.models.common.IGraphQuery
-interface UploadRepository {
+interface UploadRepository> {
fun uploadToBucket(mutation: IGraphQuery): D
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/UserRepository.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/UserRepository.kt
index 783dcb06..51349f9a 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/UserRepository.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/domain/repositories/UserRepository.kt
@@ -1,5 +1,7 @@
package co.anitrend.retrofit.graphql.domain.repositories
-interface UserRepository {
+import co.anitrend.arch.domain.state.UiState
+
+interface UserRepository> {
fun getCurrentUser(): D
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/App.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/App.kt
index e996f466..d97c11e6 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/App.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/App.kt
@@ -7,13 +7,6 @@ import io.wax911.emojify.serializer.kotlinx.KotlinxDeserializer
class App : SampleApp() {
- /**
- * Emoji manager instance
- */
- override val emojiManager: EmojiManager by lazy {
- EmojiManager.create(this, serializer = KotlinxDeserializer())
- }
-
/**
* Called when the application is starting, before any activity, service,
* or receiver objects (excluding content providers) have been created.
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/di/AppModules.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/di/AppModules.kt
index fae3289a..ac2b89d4 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/di/AppModules.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/di/AppModules.kt
@@ -1,6 +1,5 @@
package co.anitrend.retrofit.graphql.sample.di
-import android.content.Context
import android.net.ConnectivityManager
import co.anitrend.arch.core.provider.SupportFileProvider
import co.anitrend.arch.extension.ext.systemServiceOf
@@ -15,10 +14,13 @@ import co.anitrend.retrofit.graphql.sample.view.MainScreen
import co.anitrend.retrofit.graphql.sample.view.content.bucket.BucketContent
import co.anitrend.retrofit.graphql.sample.view.content.bucket.ui.adapter.BucketAdapter
import co.anitrend.retrofit.graphql.sample.view.content.bucket.viewmodel.BucketViewModel
+import co.anitrend.retrofit.graphql.sample.view.content.bucket.viewmodel.UploadViewModel
import co.anitrend.retrofit.graphql.sample.view.content.market.MarketPlaceContent
import co.anitrend.retrofit.graphql.sample.view.content.market.ui.adapter.MarketPlaceAdapter
import co.anitrend.retrofit.graphql.sample.view.content.market.viewmodel.MarketPlaceViewModel
import co.anitrend.retrofit.graphql.sample.viewmodel.MainViewModel
+import io.wax911.emojify.EmojiManager
+import io.wax911.emojify.serializer.kotlinx.KotlinxDeserializer
import org.koin.android.ext.koin.androidApplication
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.fragment.dsl.fragment
@@ -40,9 +42,13 @@ private val appModule = module {
single {
SupportConnectivity(
androidApplication()
- .systemServiceOf(
- Context.CONNECTIVITY_SERVICE
- )
+ .systemServiceOf()
+ )
+ }
+ single(createdAtStart = true) {
+ EmojiManager.create(
+ context = androidApplication(),
+ serializer = KotlinxDeserializer()
)
}
}
@@ -60,8 +66,12 @@ private val viewModelModule = module {
}
viewModel {
BucketViewModel(
- bucketUseCase = get(),
- uploadUseCase = get()
+ useCase = get(),
+ )
+ }
+ viewModel {
+ UploadViewModel(
+ useCase = get(),
)
}
}
@@ -72,7 +82,8 @@ private val presenterModule = module {
MainPresenter(
context = androidContext(),
settings = get(),
- stateLayoutConfig = get()
+ stateLayoutConfig = get(),
+ emojiManager = get(),
)
}
}
@@ -80,7 +91,8 @@ private val presenterModule = module {
scoped {
BucketPresenter(
context = androidContext(),
- settings = get()
+ settings = get(),
+ emojiManager = get(),
)
}
}
@@ -95,7 +107,7 @@ private val fragmentModule = module {
supportViewAdapter = MarketPlaceAdapter(
resources = androidContext().resources,
stateConfiguration = stateConfig
- )
+ ),
)
}
}
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/extensions/AppExtensions.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/extensions/AppExtensions.kt
deleted file mode 100644
index d739c2ef..00000000
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/extensions/AppExtensions.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package co.anitrend.retrofit.graphql.sample.extensions
-
-import android.content.Context
-import co.anitrend.retrofit.graphql.sample.App
-import io.wax911.emojify.EmojiManager
-
-fun Context.emojify(): EmojiManager {
- val app = applicationContext as App
- return app.emojiManager
-}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/presenter/BucketPresenter.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/presenter/BucketPresenter.kt
index ad8d5847..7dbaec5b 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/presenter/BucketPresenter.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/presenter/BucketPresenter.kt
@@ -6,8 +6,11 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import co.anitrend.arch.core.presenter.SupportPresenter
+import co.anitrend.arch.domain.entities.LoadState
+import co.anitrend.arch.domain.entities.RequestError
import co.anitrend.retrofit.graphql.core.settings.Settings
import co.anitrend.retrofit.graphql.data.bucket.model.upload.mutation.UploadMutation
+import io.wax911.emojify.EmojiManager
import timber.log.Timber
import java.io.ByteArrayOutputStream
import java.io.File
@@ -16,7 +19,8 @@ import java.io.InputStream
class BucketPresenter(
context: Context,
- settings: Settings
+ settings: Settings,
+ private val emojiManager: EmojiManager,
) : SupportPresenter(context, settings) {
/**
@@ -33,6 +37,16 @@ class BucketPresenter(
return outputFile?.let { UploadMutation(it.absolutePath) }
}
+ fun loadStateFailure(): LoadState {
+ val emoji = emojiManager.getForAlias("gallery")
+ return LoadState.Error(
+ RequestError(
+ topic = "${emoji?.unicode} No images found",
+ description = "Try to upload some pictures and they will show up here"
+ )
+ )
+ }
+
companion object {
private fun InputStream.optimizeImage(context: Context): File? {
val cache = context.externalCacheDir ?: context.cacheDir
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/presenter/MainPresenter.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/presenter/MainPresenter.kt
index 88b71e64..8d85f04e 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/presenter/MainPresenter.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/presenter/MainPresenter.kt
@@ -7,13 +7,14 @@ import co.anitrend.retrofit.graphql.core.extension.using
import co.anitrend.retrofit.graphql.core.settings.Settings
import co.anitrend.retrofit.graphql.domain.entities.user.User
import co.anitrend.retrofit.graphql.sample.databinding.NavHeaderMainBinding
-import co.anitrend.retrofit.graphql.sample.extensions.emojify
import coil.transform.CircleCropTransformation
+import io.wax911.emojify.EmojiManager
import io.wax911.emojify.parser.parseToUnicode
class MainPresenter(
context: Context,
settings: Settings,
+ private val emojiManager: EmojiManager,
private val stateLayoutConfig: StateLayoutConfig
) : SupportPresenter(context, settings) {
@@ -22,7 +23,6 @@ class MainPresenter(
}
fun updateNavigationHeaderView(user: User, binding: NavHeaderMainBinding) {
- val emojiManager = context.emojify()
binding.navAvatar.using(user.avatar, null, CircleCropTransformation())
binding.navUserName.text = user.username
binding.navUserBio.text = user.bio
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/MainScreen.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/MainScreen.kt
index 31ff5c7c..cb17ab00 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/MainScreen.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/MainScreen.kt
@@ -6,10 +6,10 @@ import android.view.MenuItem
import android.widget.Toast
import androidx.annotation.IdRes
import androidx.annotation.StringRes
-import androidx.lifecycle.Observer
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
-import co.anitrend.arch.domain.entities.NetworkState
-import co.anitrend.arch.extension.ext.LAZY_MODE_UNSAFE
+import androidx.lifecycle.repeatOnLifecycle
+import co.anitrend.arch.extension.ext.UNSAFE
import co.anitrend.retrofit.graphql.core.extension.commit
import co.anitrend.retrofit.graphql.core.model.FragmentItem
import co.anitrend.retrofit.graphql.core.view.SampleActivity
@@ -29,11 +29,11 @@ import timber.log.Timber
class MainScreen : SampleActivity(), NavigationView.OnNavigationItemSelectedListener {
- private val binding by lazy(LAZY_MODE_UNSAFE) {
+ private val binding by lazy(UNSAFE) {
ActivityMainBinding.inflate(layoutInflater)
}
- private val bottomDrawerBehavior by lazy(LAZY_MODE_UNSAFE) {
+ private val bottomDrawerBehavior by lazy(UNSAFE) {
BottomSheetBehavior.from(binding.bottomNavigationDrawer)
}
@@ -44,7 +44,7 @@ class MainScreen : SampleActivity(), NavigationView.OnNavigationItemSelectedList
private val presenter by inject()
- private val headerBinding by lazy(LAZY_MODE_UNSAFE) {
+ private val headerBinding by lazy(UNSAFE) {
val headerView = binding.bottomNavigationView.getHeaderView(0)
NavHeaderMainBinding.bind(headerView)
}
@@ -64,15 +64,13 @@ class MainScreen : SampleActivity(), NavigationView.OnNavigationItemSelectedList
binding.bottomNavigationView.apply {
setCheckedItem(selectedItem)
setNavigationItemSelectedListener(this@MainScreen)
- val observer = Observer {
- headerBinding.navStateLayout.networkMutableStateFlow.value = it
- }
presenter.configureNavigationHeader(headerBinding)
- viewModel.state.networkState.observe(this@MainScreen, observer)
- viewModel.state.refreshState.observe(this@MainScreen, observer)
- viewModel.state.model.observe(this@MainScreen, Observer {
- presenter.updateNavigationHeaderView(it, headerBinding)
- })
+ }
+ viewModelState().combinedLoadState.observe(this@MainScreen) {
+ headerBinding.navStateLayout.loadStateFlow.value = it
+ }
+ viewModelState().model.observe(this@MainScreen) {
+ presenter.updateNavigationHeaderView(it, headerBinding)
}
updateUserInterface()
}
@@ -85,15 +83,20 @@ class MainScreen : SampleActivity(), NavigationView.OnNavigationItemSelectedList
}
override fun onSaveInstanceState(outState: Bundle) {
- outState.putInt(key_navigation_selected, selectedItem)
- outState.putInt(key_navigation_title, selectedTitle)
+ outState.putInt(KEY_NAVIGATION_SELECTED, selectedItem)
+ outState.putInt(KEY_NAVIGATION_TITLE, selectedTitle)
super.onSaveInstanceState(outState)
}
+ /**
+ * Proxy for a view model state if one exists
+ */
+ override fun viewModelState() = viewModel
+
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
- selectedItem = savedInstanceState.getInt(key_navigation_selected)
- selectedTitle = savedInstanceState.getInt(key_navigation_title)
+ selectedItem = savedInstanceState.getInt(KEY_NAVIGATION_SELECTED)
+ selectedTitle = savedInstanceState.getInt(KEY_NAVIGATION_TITLE)
}
override fun onBackPressed() {
@@ -173,19 +176,22 @@ class MainScreen : SampleActivity(), NavigationView.OnNavigationItemSelectedList
}
private fun updateUserInterface() {
- lifecycleScope.launchWhenResumed {
- val stateLayout = headerBinding.navStateLayout
- stateLayout.interactionStateFlow.collect { viewModel.state.retry() }
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ headerBinding.navStateLayout.interactionFlow.collect { viewModel.retry() }
+ }
}
- lifecycleScope.launchWhenResumed {
- viewModel.state()
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ viewModel()
+ }
}
if (selectedItem != 0) onNavigateToTarget(selectedItem)
else onNavigateToTarget(R.id.nav_app_store)
}
companion object {
- private const val key_navigation_selected = "key_navigation_selected"
- private const val key_navigation_title = "key_navigation_title"
+ private const val KEY_NAVIGATION_SELECTED = "key_navigation_selected"
+ private const val KEY_NAVIGATION_TITLE = "key_navigation_title"
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/BucketContent.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/BucketContent.kt
index 6523388a..5d757611 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/BucketContent.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/BucketContent.kt
@@ -2,44 +2,60 @@ package co.anitrend.retrofit.graphql.sample.view.content.bucket
import android.net.Uri
import android.os.Bundle
+import android.view.LayoutInflater
import android.view.View
+import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
-import androidx.lifecycle.Observer
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
-import co.anitrend.arch.domain.entities.NetworkState
-import co.anitrend.arch.domain.extensions.isSuccess
-import co.anitrend.arch.extension.dispatchers.SupportDispatchers
-import co.anitrend.arch.recycler.adapter.contract.ISupportAdapter
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.recyclerview.widget.RecyclerView
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+import co.anitrend.arch.domain.entities.LoadState
+import co.anitrend.arch.domain.entities.RequestError
+import co.anitrend.arch.extension.dispatchers.contract.ISupportDispatcher
+import co.anitrend.arch.recycler.adapter.SupportAdapter
+import co.anitrend.arch.ui.fragment.list.contract.ISupportFragmentList
+import co.anitrend.arch.ui.fragment.list.presenter.SupportListPresenter
+import co.anitrend.arch.ui.view.widget.contract.ISupportStateLayout
import co.anitrend.arch.ui.view.widget.model.StateLayoutConfig
import co.anitrend.retrofit.graphql.core.view.SampleListFragment
import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile
import co.anitrend.retrofit.graphql.sample.R
import co.anitrend.retrofit.graphql.sample.databinding.BucketContentBinding
-import co.anitrend.retrofit.graphql.sample.extensions.emojify
import co.anitrend.retrofit.graphql.sample.presenter.BucketPresenter
import co.anitrend.retrofit.graphql.sample.view.content.bucket.viewmodel.BucketViewModel
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.collect
+import co.anitrend.retrofit.graphql.sample.view.content.bucket.viewmodel.UploadViewModel
+import io.wax911.emojify.EmojiManager
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
-import org.koin.androidx.scope.lifecycleScope as koinScope
class BucketContent(
- override val defaultSpanSize: Int = co.anitrend.arch.ui.R.integer.grid_list_x3,
+ override val defaultSpanSize: Int = co.anitrend.arch.theme.R.integer.grid_list_x3,
override val stateConfig: StateLayoutConfig,
- override val supportViewAdapter: ISupportAdapter,
- override val inflateLayout: Int = R.layout.bucket_content
+ override val supportViewAdapter: SupportAdapter,
+ override val inflateLayout: Int = R.layout.bucket_content,
) : SampleListFragment() {
+ override val listPresenter = object : SupportListPresenter() {
+ override val recyclerView: RecyclerView
+ get() = binding.supportRecyclerView
+ override val stateLayout: ISupportStateLayout
+ get() = binding.supportStateLayout
+ override val swipeRefreshLayout: SwipeRefreshLayout
+ get() = binding.supportRefreshLayout
+ }
+
private lateinit var binding: BucketContentBinding
- private val viewModel by viewModel()
+ private val bucketViewModel by viewModel()
+ private val uploadViewModel by viewModel()
private val presenter by inject()
- private val dispatchers by inject()
+ private val dispatchers by inject()
private val activityResultLauncher =
registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
@@ -48,14 +64,11 @@ class BucketContent(
val mutation = withContext (dispatchers.io) {
presenter.resolve(uri, requireActivity().contentResolver)
}
- if (mutation != null)
- viewModel.uploadState(mutation)
- else
- Toast.makeText(
- context,
- "Unable to resolve content",
- Toast.LENGTH_SHORT
- ).show()
+ mutation?.also(uploadViewModel::invoke) ?: Toast.makeText(
+ context,
+ "Unable to resolve content",
+ Toast.LENGTH_SHORT
+ ).show()
}
}
@@ -67,14 +80,45 @@ class BucketContent(
*/
override fun initializeComponents(savedInstanceState: Bundle?) {
super.initializeComponents(savedInstanceState)
- lifecycleScope.launchWhenResumed {
- binding.uploadStateLayout.interactionStateFlow
- .debounce(16)
- .filterNotNull()
- .collect { viewModel.uploadState.retry() }
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ binding.uploadStateLayout.interactionFlow
+ .debounce(16)
+ .filterNotNull()
+ .collect { uploadViewModel.retry() }
+ }
}
}
+ /**
+ * Called to have the fragment instantiate its user interface view.
+ * This is optional, and non-graphical fragments can return null (which
+ * is the default implementation). This will be called between
+ * [onCreate] and [onActivityCreated].
+ *
+ * If you return a View from here, you will later be called in
+ * [onDestroyView] when the view is being released.
+ *
+ * @param inflater The LayoutInflater object that can be used to inflate
+ * any views in the fragment,
+ * @param container If non-null, this is the parent view that the fragment's
+ * UI should be attached to. The fragment should not add the view itself,
+ * but this can be used to generate the LayoutParams of the view.
+ * @param savedInstanceState If non-null, this fragment is being re-constructed
+ * from a previous saved state as given here.
+ *
+ * @return Return the [View] for the fragment's UI, or null.
+ */
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = BucketContentBinding.inflate(inflater, container, false)
+ listPresenter.onCreateView(this, binding.root)
+ return binding.root
+ }
+
/**
* Called immediately after [onCreateView] has returned, but before any saved state has been
* restored in to the view. This gives subclasses a chance to initialize themselves once
@@ -87,7 +131,6 @@ class BucketContent(
*/
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- binding = BucketContentBinding.bind(view)
binding.uploadStateLayout.stateConfigFlow.value = stateConfig
binding.selectUploadFile.setOnClickListener {
// filter to only allow images to be selected
@@ -104,23 +147,7 @@ class BucketContent(
* @see initializeComponents
*/
override fun onFetchDataInitialize() {
- viewModel.bucketState()
- }
-
- /**
- * Informs the underlying [SupportStateLayout] of changes to the [NetworkState]
- *
- * @param networkState New state from the application
- */
- @ExperimentalCoroutinesApi
- override fun changeLayoutState(networkState: NetworkState?) {
- super.changeLayoutState(networkState)
- if (networkState?.isSuccess() != true) return
- val emoji = context?.emojify()?.getForAlias("gallery")
- binding.supportStateLayout.networkMutableStateFlow.value = NetworkState.Error(
- heading = "${emoji?.unicode} No images found",
- message = "Try to upload some pictures and they will show up here"
- )
+ bucketViewModel()
}
/**
@@ -128,31 +155,30 @@ class BucketContent(
* called in [onViewCreated]
*/
override fun setUpViewModelObserver() {
- val uploadStateObserver = Observer {
- binding.uploadStateLayout.networkMutableStateFlow.value = it
+ uploadViewModel.combinedLoadState.observe(
+ viewLifecycleOwner
+ ) {
+ binding.uploadStateLayout.loadStateFlow.value = it
}
- viewModel.uploadState.networkState.observe(
- viewLifecycleOwner,
- uploadStateObserver
- )
- viewModel.uploadState.refreshState.observe(
- viewLifecycleOwner,
- uploadStateObserver
- )
- viewModel.uploadState.model.observe(
- viewLifecycleOwner,
- Observer { viewModelState().refresh() }
- )
+ uploadViewModel.model.observe(
+ viewLifecycleOwner
+ ) { viewModelState().invoke() }
+
viewModelState().model.observe(
- viewLifecycleOwner,
- Observer { onPostModelChange(it) }
- )
+ viewLifecycleOwner
+ ) { onPostModelChange(it) }
+
+ viewModelState().combinedLoadState.observe(viewLifecycleOwner) {
+ if (it is LoadState.Error) {
+ binding.supportStateLayout.loadStateFlow.value = presenter.loadStateFailure()
+ }
+ }
}
/**
* Proxy for a view model state if one exists
*/
- override fun viewModelState() = viewModel.bucketState
+ override fun viewModelState() = bucketViewModel
/**
* Called when the view previously created by [onCreateView] has
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/ui/adapter/BucketAdapter.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/ui/adapter/BucketAdapter.kt
index 3b94827f..1289d5ef 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/ui/adapter/BucketAdapter.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/ui/adapter/BucketAdapter.kt
@@ -8,18 +8,18 @@ import co.anitrend.arch.core.model.IStateLayoutConfig
import co.anitrend.arch.recycler.action.contract.ISupportSelectionMode
import co.anitrend.arch.recycler.adapter.SupportListAdapter
import co.anitrend.arch.recycler.model.contract.IRecyclerItem
-import co.anitrend.arch.theme.animator.contract.ISupportAnimator
+import co.anitrend.arch.theme.animator.contract.AbstractAnimator
import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile
import co.anitrend.retrofit.graphql.sample.view.content.bucket.ui.controller.helper.DIFFER
import co.anitrend.retrofit.graphql.sample.view.content.bucket.ui.controller.model.BucketFileItem
import co.anitrend.retrofit.graphql.sample.view.content.bucket.ui.controller.model.BucketFileItem.Companion.createViewHolder
class BucketAdapter(
- override val customSupportAnimator: ISupportAnimator? = null,
- override val mapper: (BucketFile?) -> IRecyclerItem = { BucketFileItem(it) },
override val resources: Resources,
override val stateConfiguration: IStateLayoutConfig,
- override val supportAction: ISupportSelectionMode? = null
+ override val mapper: (BucketFile) -> IRecyclerItem = { BucketFileItem(it) },
+ override val supportAction: ISupportSelectionMode? = null,
+ override val customSupportAnimator: AbstractAnimator? = null,
) : SupportListAdapter(DIFFER) {
/**
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/ui/controller/model/BucketFileItem.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/ui/controller/model/BucketFileItem.kt
index 701d7c52..fecfccca 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/ui/controller/model/BucketFileItem.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/ui/controller/model/BucketFileItem.kt
@@ -18,8 +18,8 @@ import coil.transform.RoundedCornersTransformation
import kotlinx.coroutines.flow.MutableStateFlow
internal class BucketFileItem(
- private val entity: BucketFile?
-) : RecyclerItem(entity?.id?.toLong()) {
+ private val entity: BucketFile
+) : RecyclerItem(entity.id.toLong()) {
private var disposable: Disposable? = null
private var binding: BucketFileItemBinding? = null
@@ -38,14 +38,14 @@ internal class BucketFileItem(
view: View,
position: Int,
payloads: List,
- stateFlow: MutableStateFlow,
+ stateFlow: MutableStateFlow,
selectionMode: ISupportSelectionMode?
) {
binding = BucketFileItemBinding.bind(view)
- binding?.bucketImageName?.text = entity?.fileName
- val margin = view.resources.getDimension(co.anitrend.arch.ui.R.dimen.lg_margin)
+ binding?.bucketImageName?.text = entity.fileName
+ val margin = view.resources.getDimension(co.anitrend.arch.theme.R.dimen.lg_margin)
binding?.bucketImage?.using(
- entity?.url,
+ entity.url,
ColorDrawable(-0x333334),
RoundedCornersTransformation(
margin, margin, margin, margin
@@ -61,7 +61,7 @@ internal class BucketFileItem(
* @param resources optionally useful for dynamic size check with different configurations
*/
override fun getSpanSize(spanCount: Int, position: Int, resources: Resources): Int {
- return resources.getInteger(co.anitrend.arch.ui.R.integer.grid_list_x3)
+ return resources.getInteger(co.anitrend.arch.theme.R.integer.grid_list_x3)
}
/**
@@ -79,6 +79,6 @@ internal class BucketFileItem(
viewGroup: ViewGroup
) = BucketFileItemBinding.inflate(
this, viewGroup, false
- ).let { SupportViewHolder(it.root) }
+ ).let { SupportViewHolder(it) }
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/viewmodel/BucketViewModel.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/viewmodel/BucketViewModel.kt
index a7a69185..21d5c4f0 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/viewmodel/BucketViewModel.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/viewmodel/BucketViewModel.kt
@@ -1,18 +1,59 @@
package co.anitrend.retrofit.graphql.sample.view.content.bucket.viewmodel
+import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.asLiveData
+import androidx.lifecycle.switchMap
+import androidx.lifecycle.viewModelScope
+import co.anitrend.arch.core.model.ISupportViewModelState
+import co.anitrend.arch.data.state.DataState
import co.anitrend.retrofit.graphql.data.bucket.usecase.BucketUseCaseContract
-import co.anitrend.retrofit.graphql.data.bucket.usecase.upload.UploadUseCaseContract
-import co.anitrend.retrofit.graphql.sample.view.content.bucket.viewmodel.state.BucketState
-import co.anitrend.retrofit.graphql.sample.view.content.bucket.viewmodel.state.UploadState
+import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile
+import kotlinx.coroutines.flow.merge
class BucketViewModel(
- bucketUseCase: BucketUseCaseContract,
- uploadUseCase: UploadUseCaseContract
-) : ViewModel() {
+ private val useCase: BucketUseCaseContract
+) : ViewModel(), ISupportViewModelState> {
- val bucketState = BucketState(bucketUseCase)
- val uploadState = UploadState(uploadUseCase)
+ private val state = MutableLiveData>>()
+
+ override val model = state.switchMap {
+ it.model.asLiveData(viewModelScope.coroutineContext)
+ }
+
+ override val loadState = state.switchMap {
+ it.loadState.asLiveData(viewModelScope.coroutineContext)
+ }
+
+ override val refreshState = state.switchMap {
+ it.refreshState.asLiveData(viewModelScope.coroutineContext)
+ }
+
+ val combinedLoadState = state.switchMap {
+ val result = merge(it.loadState, it.refreshState)
+ result.asLiveData(viewModelScope.coroutineContext)
+ }
+
+ operator fun invoke() {
+ val result = useCase()
+ state.postValue(result)
+ }
+
+ /**
+ * Triggers use case to perform refresh operation
+ */
+ override suspend fun refresh() {
+ val uiModel = state.value
+ uiModel?.refresh?.invoke()
+ }
+
+ /**
+ * Triggers use case to perform a retry operation
+ */
+ override suspend fun retry() {
+ val uiModel = state.value
+ uiModel?.retry?.invoke()
+ }
/**
* This method will be called when this ViewModel is no longer used and will be destroyed.
@@ -21,8 +62,7 @@ class BucketViewModel(
* prevent a leak of this ViewModel.
*/
override fun onCleared() {
- bucketState.onCleared()
- uploadState.onCleared()
+ useCase.onCleared()
super.onCleared()
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/viewmodel/UploadViewModel.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/viewmodel/UploadViewModel.kt
new file mode 100644
index 00000000..e53b94b1
--- /dev/null
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/viewmodel/UploadViewModel.kt
@@ -0,0 +1,63 @@
+package co.anitrend.retrofit.graphql.sample.view.content.bucket.viewmodel
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.asLiveData
+import androidx.lifecycle.switchMap
+import androidx.lifecycle.viewModelScope
+import co.anitrend.arch.core.model.ISupportViewModelState
+import co.anitrend.arch.data.state.DataState
+import co.anitrend.retrofit.graphql.data.bucket.model.upload.mutation.UploadMutation
+import co.anitrend.retrofit.graphql.data.bucket.usecase.upload.UploadUseCaseContract
+import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile
+import kotlinx.coroutines.flow.merge
+
+class UploadViewModel(
+ private val useCase: UploadUseCaseContract
+) : ViewModel(), ISupportViewModelState {
+
+ private val state = MutableLiveData>()
+
+ override val model = state.switchMap {
+ it.model.asLiveData(viewModelScope.coroutineContext)
+ }
+
+ override val loadState = state.switchMap {
+ it.loadState.asLiveData(viewModelScope.coroutineContext)
+ }
+
+ override val refreshState = state.switchMap {
+ it.refreshState.asLiveData(viewModelScope.coroutineContext)
+ }
+
+ val combinedLoadState = state.switchMap {
+ val result = merge(it.loadState, it.refreshState)
+ result.asLiveData(viewModelScope.coroutineContext)
+ }
+
+ operator fun invoke(mutation: UploadMutation) {
+ val result = useCase(mutation)
+ state.postValue(result)
+ }
+
+ /**
+ * Triggers use case to perform refresh operation
+ */
+ override suspend fun refresh() {
+ val uiModel = state.value
+ uiModel?.refresh?.invoke()
+ }
+
+ /**
+ * Triggers use case to perform a retry operation
+ */
+ override suspend fun retry() {
+ val uiModel = state.value
+ uiModel?.retry?.invoke()
+ }
+
+ override fun onCleared() {
+ useCase.onCleared()
+ super.onCleared()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/viewmodel/state/BucketState.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/viewmodel/state/BucketState.kt
deleted file mode 100644
index f5a66d90..00000000
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/viewmodel/state/BucketState.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-package co.anitrend.retrofit.graphql.sample.view.content.bucket.viewmodel.state
-
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.switchMap
-import co.anitrend.arch.core.model.ISupportViewModelState
-import co.anitrend.arch.data.model.UserInterfaceState
-import co.anitrend.retrofit.graphql.data.bucket.usecase.BucketUseCaseContract
-import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile
-
-class BucketState(
- private val useCase: BucketUseCaseContract
-) : ISupportViewModelState> {
-
- private val useCaseResult = MutableLiveData>>()
-
- override val model =
- useCaseResult.switchMap() { it.model }
- override val networkState =
- useCaseResult.switchMap() { it.networkState }
- override val refreshState =
- useCaseResult.switchMap() { it.refreshState }
-
- operator fun invoke() {
- val result = useCase()
- useCaseResult.postValue(result)
- }
-
- /**
- * Called upon [androidx.lifecycle.ViewModel.onCleared] and should optionally
- * call cancellation of any ongoing jobs.
- *
- * If your use case source is of type [co.anitrend.arch.domain.common.IUseCase]
- * then you could optionally call [co.anitrend.arch.domain.common.IUseCase.onCleared] here
- */
- override fun onCleared() {
- useCase.onCleared()
- }
-
- /**
- * Triggers use case to perform refresh operation
- */
- override fun refresh() {
- val uiModel = useCaseResult.value
- uiModel?.refresh?.invoke()
- }
-
- /**
- * Triggers use case to perform a retry operation
- */
- override fun retry() {
- val uiModel = useCaseResult.value
- uiModel?.retry?.invoke()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/viewmodel/state/UploadState.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/viewmodel/state/UploadState.kt
deleted file mode 100644
index 1c85eb14..00000000
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/bucket/viewmodel/state/UploadState.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-package co.anitrend.retrofit.graphql.sample.view.content.bucket.viewmodel.state
-
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.switchMap
-import co.anitrend.arch.core.model.ISupportViewModelState
-import co.anitrend.arch.data.model.UserInterfaceState
-import co.anitrend.retrofit.graphql.data.bucket.model.upload.mutation.UploadMutation
-import co.anitrend.retrofit.graphql.data.bucket.usecase.upload.UploadUseCaseContract
-import co.anitrend.retrofit.graphql.domain.entities.bucket.BucketFile
-
-class UploadState(
- private val useCase: UploadUseCaseContract
-) : ISupportViewModelState {
-
- private val useCaseResult = MutableLiveData>()
-
- override val model =
- useCaseResult.switchMap() { it.model }
- override val networkState =
- useCaseResult.switchMap() { it.networkState }
- override val refreshState =
- useCaseResult.switchMap() { it.refreshState }
-
- operator fun invoke(mutation: UploadMutation) {
- val result = useCase(mutation)
- useCaseResult.postValue(result)
- }
-
- /**
- * Called upon [androidx.lifecycle.ViewModel.onCleared] and should optionally
- * call cancellation of any ongoing jobs.
- *
- * If your use case source is of type [co.anitrend.arch.domain.common.IUseCase]
- * then you could optionally call [co.anitrend.arch.domain.common.IUseCase.onCleared] here
- */
- override fun onCleared() {
- useCase.onCleared()
- }
-
- /**
- * Triggers use case to perform refresh operation
- */
- override fun refresh() {
- val uiModel = useCaseResult.value
- uiModel?.refresh?.invoke()
- }
-
- /**
- * Triggers use case to perform a retry operation
- */
- override fun retry() {
- val uiModel = useCaseResult.value
- uiModel?.retry?.invoke()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/MarketPlaceContent.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/MarketPlaceContent.kt
index 8fc240bb..9b723e99 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/MarketPlaceContent.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/MarketPlaceContent.kt
@@ -1,49 +1,68 @@
package co.anitrend.retrofit.graphql.sample.view.content.market
import android.os.Bundle
-import android.widget.Toast
-import androidx.lifecycle.Observer
-import androidx.lifecycle.lifecycleScope
-import co.anitrend.arch.recycler.adapter.contract.ISupportAdapter
-import co.anitrend.arch.recycler.common.DefaultClickableItem
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+import co.anitrend.arch.extension.ext.getColorFromAttr
+import co.anitrend.arch.recycler.SupportRecyclerView
+import co.anitrend.arch.recycler.paging.legacy.adapter.SupportPagedListAdapter
+import co.anitrend.arch.recycler.shared.adapter.SupportLoadStateAdapter
+import co.anitrend.arch.ui.fragment.list.contract.ISupportFragmentList
+import co.anitrend.arch.ui.fragment.list.presenter.SupportListPresenter
+import co.anitrend.arch.ui.view.widget.contract.ISupportStateLayout
import co.anitrend.arch.ui.view.widget.model.StateLayoutConfig
import co.anitrend.retrofit.graphql.core.view.SampleListFragment
import co.anitrend.retrofit.graphql.domain.entities.market.MarketPlaceListing
-import co.anitrend.retrofit.graphql.sample.R
+import co.anitrend.retrofit.graphql.sample.databinding.SharedListContentBinding
import co.anitrend.retrofit.graphql.sample.view.content.market.viewmodel.MarketPlaceViewModel
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.flow.filterIsInstance
import org.koin.androidx.viewmodel.ext.android.viewModel
class MarketPlaceContent(
- override val defaultSpanSize: Int = co.anitrend.arch.ui.R.integer.single_list_size,
+ override val inflateLayout: Int = co.anitrend.retrofit.graphql.sample.R.layout.shared_list_content,
+ override val defaultSpanSize: Int = co.anitrend.arch.theme.R.integer.single_list_size,
override val stateConfig: StateLayoutConfig,
- override val supportViewAdapter: ISupportAdapter
+ override val supportViewAdapter: SupportPagedListAdapter
) : SampleListFragment() {
+ override val listPresenter = object : SupportListPresenter() {
+ override val recyclerView: RecyclerView
+ get() = requireNotNull(binding).recyclerView
+ override val stateLayout: ISupportStateLayout
+ get() = requireNotNull(binding).stateLayout
+ override val swipeRefreshLayout: SwipeRefreshLayout
+ get() = requireNotNull(binding).swipeRefreshLayout
+
+ private var binding: SharedListContentBinding? = null
+
+ override fun onCreateView(fragmentList: ISupportFragmentList, view: View?) {
+ binding = SharedListContentBinding.bind(requireNotNull(view))
+ super.onCreateView(fragmentList, view)
+ }
+ }
+
private val viewModel by viewModel()
/**
- * Additional initialization to be done in this method, this method will be called in
- * [androidx.fragment.app.FragmentActivity.onCreate].
- *
- * @param savedInstanceState
+ * Sets the adapter for the recycler view
*/
- override fun initializeComponents(savedInstanceState: Bundle?) {
- super.initializeComponents(savedInstanceState)
- lifecycleScope.launchWhenResumed {
- supportViewAdapter.clickableStateFlow.debounce(16)
- .filterIsInstance>()
- .collect { item ->
- item.data?.also {
- Toast.makeText(
- item.view.context,
- "Item clicked: ${it.name}",
- Toast.LENGTH_SHORT
- ).show()
- }
- }
+ override fun setRecyclerAdapter(recyclerView: SupportRecyclerView) {
+ if (recyclerView.adapter == null) {
+ val header = SupportLoadStateAdapter(resources, stateConfig).apply {
+ registerFlowListener()
+ }
+ val footer = SupportLoadStateAdapter(resources, stateConfig).apply {
+ registerFlowListener()
+ }
+
+ (supportViewAdapter as RecyclerView.Adapter<*>)
+ .stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
+
+ recyclerView.adapter = supportViewAdapter.withLoadStateHeaderAndFooter(
+ header = header, footer = footer
+ )
}
}
@@ -59,6 +78,38 @@ class MarketPlaceContent(
viewModelState().invoke()
}
+ /**
+ * Called to have the fragment instantiate its user interface view.
+ * This is optional, and non-graphical fragments can return null (which
+ * is the default implementation). This will be called between
+ * [onCreate] and [onActivityCreated].
+ *
+ * If you return a View from here, you will later be called in
+ * [onDestroyView] when the view is being released.
+ *
+ * @param inflater The LayoutInflater object that can be used to inflate
+ * any views in the fragment,
+ * @param container If non-null, this is the parent view that the fragment's
+ * UI should be attached to. The fragment should not add the view itself,
+ * but this can be used to generate the LayoutParams of the view.
+ * @param savedInstanceState If non-null, this fragment is being re-constructed
+ * from a previous saved state as given here.
+ *
+ * @return Return the [View] for the fragment's UI, or null.
+ */
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val view = super.onCreateView(inflater, container, savedInstanceState)
+ listPresenter.swipeRefreshLayout?.setColorSchemeColors(
+ requireContext().getColorFromAttr(androidx.appcompat.R.attr.colorPrimary),
+ requireContext().getColorFromAttr(androidx.appcompat.R.attr.colorAccent)
+ )
+ return view
+ }
+
/**
* Invoke view model observer to watch for changes, this will be called
* called in [onViewCreated]
@@ -66,14 +117,12 @@ class MarketPlaceContent(
override fun setUpViewModelObserver() {
viewModelState().model.observe(
viewLifecycleOwner,
- Observer {
- onPostModelChange(it)
- }
+ ::onPostModelChange
)
}
/**
* Proxy for a view model state if one exists
*/
- override fun viewModelState() = viewModel.state
+ override fun viewModelState() = viewModel
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/adapter/MarketPlaceAdapter.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/adapter/MarketPlaceAdapter.kt
index c16e2636..09578814 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/adapter/MarketPlaceAdapter.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/adapter/MarketPlaceAdapter.kt
@@ -6,9 +6,9 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import co.anitrend.arch.core.model.IStateLayoutConfig
import co.anitrend.arch.recycler.action.contract.ISupportSelectionMode
-import co.anitrend.arch.recycler.adapter.SupportPagedListAdapter
import co.anitrend.arch.recycler.model.contract.IRecyclerItem
-import co.anitrend.arch.theme.animator.contract.ISupportAnimator
+import co.anitrend.arch.recycler.paging.legacy.adapter.SupportPagedListAdapter
+import co.anitrend.arch.theme.animator.contract.AbstractAnimator
import co.anitrend.retrofit.graphql.domain.entities.market.MarketPlaceListing
import co.anitrend.retrofit.graphql.sample.view.content.market.ui.controller.helpers.DIFFER
import co.anitrend.retrofit.graphql.sample.view.content.market.ui.controller.model.MarketPlaceListingItem
@@ -17,11 +17,9 @@ import co.anitrend.retrofit.graphql.sample.view.content.market.ui.controller.mod
class MarketPlaceAdapter(
override val resources: Resources,
override val stateConfiguration: IStateLayoutConfig,
- override val customSupportAnimator: ISupportAnimator? = null,
+ override val customSupportAnimator: AbstractAnimator? = null,
override val supportAction: ISupportSelectionMode? = null,
- override val mapper: (MarketPlaceListing?) -> IRecyclerItem = {
- MarketPlaceListingItem(it)
- }
+ override val mapper: (MarketPlaceListing) -> IRecyclerItem = ::MarketPlaceListingItem
) : SupportPagedListAdapter(DIFFER) {
/**
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/adapter/MarketPlaceCategoryAdapter.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/adapter/MarketPlaceCategoryAdapter.kt
index 800d0567..8d80dde9 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/adapter/MarketPlaceCategoryAdapter.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/adapter/MarketPlaceCategoryAdapter.kt
@@ -8,18 +8,18 @@ import co.anitrend.arch.core.model.IStateLayoutConfig
import co.anitrend.arch.recycler.action.contract.ISupportSelectionMode
import co.anitrend.arch.recycler.adapter.SupportListAdapter
import co.anitrend.arch.recycler.model.contract.IRecyclerItem
-import co.anitrend.arch.theme.animator.contract.ISupportAnimator
+import co.anitrend.arch.theme.animator.contract.AbstractAnimator
import co.anitrend.arch.ui.view.widget.model.StateLayoutConfig
import co.anitrend.retrofit.graphql.sample.view.content.market.ui.controller.helpers.CATEGORY_DIFFER
import co.anitrend.retrofit.graphql.sample.view.content.market.ui.controller.model.MarketPlaceCategoryItem
import co.anitrend.retrofit.graphql.sample.view.content.market.ui.controller.model.MarketPlaceCategoryItem.Companion.createViewHolder
class MarketPlaceCategoryAdapter(
- override val customSupportAnimator: ISupportAnimator? = null,
- override val mapper: (String?) -> IRecyclerItem = { MarketPlaceCategoryItem(it) },
override val resources: Resources,
+ override val mapper: (String) -> IRecyclerItem = ::MarketPlaceCategoryItem,
override val stateConfiguration: IStateLayoutConfig = StateLayoutConfig(),
- override val supportAction: ISupportSelectionMode? = null
+ override val supportAction: ISupportSelectionMode? = null,
+ override val customSupportAnimator: AbstractAnimator? = null,
) : SupportListAdapter(CATEGORY_DIFFER) {
/**
* Should provide the required view holder, this function is a substitute for
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/controller/model/MarketPlaceCategoryItem.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/controller/model/MarketPlaceCategoryItem.kt
index 2dc3bc17..5afdd4f5 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/controller/model/MarketPlaceCategoryItem.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/controller/model/MarketPlaceCategoryItem.kt
@@ -8,14 +8,13 @@ import co.anitrend.arch.recycler.action.contract.ISupportSelectionMode
import co.anitrend.arch.recycler.common.ClickableItem
import co.anitrend.arch.recycler.holder.SupportViewHolder
import co.anitrend.arch.recycler.model.RecyclerItem
-import co.anitrend.retrofit.graphql.sample.R
import co.anitrend.retrofit.graphql.sample.databinding.MarketPlaceCategoryItemBinding
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
class MarketPlaceCategoryItem(
- private val entity: String?
-) : RecyclerItem(entity?.hashCode()?.toLong()) {
+ private val entity: String
+) : RecyclerItem(entity.hashCode().toLong()) {
private var binding: MarketPlaceCategoryItemBinding? = null
@@ -29,12 +28,11 @@ class MarketPlaceCategoryItem(
* @param stateFlow observable to broadcast click events
* @param selectionMode action mode helper or null if none was provided
*/
- @ExperimentalCoroutinesApi
override fun bind(
view: View,
position: Int,
payloads: List,
- stateFlow: MutableStateFlow,
+ stateFlow: MutableStateFlow,
selectionMode: ISupportSelectionMode?
) {
binding = MarketPlaceCategoryItemBinding.bind(view)
@@ -49,7 +47,7 @@ class MarketPlaceCategoryItem(
* @param resources optionally useful for dynamic size check with different configurations
*/
override fun getSpanSize(spanCount: Int, position: Int, resources: Resources): Int {
- return resources.getInteger(co.anitrend.arch.ui.R.integer.single_list_size)
+ return resources.getInteger(co.anitrend.arch.theme.R.integer.single_list_size)
}
/**
@@ -65,6 +63,6 @@ class MarketPlaceCategoryItem(
viewGroup: ViewGroup
) = MarketPlaceCategoryItemBinding.inflate(
this, viewGroup, false
- ).let { SupportViewHolder(it.root) }
+ ).let { SupportViewHolder(it) }
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/controller/model/MarketPlaceListingItem.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/controller/model/MarketPlaceListingItem.kt
index 0b2d0618..aeeab986 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/controller/model/MarketPlaceListingItem.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/ui/controller/model/MarketPlaceListingItem.kt
@@ -4,13 +4,13 @@ import android.content.res.Resources
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import co.anitrend.arch.extension.ext.getCompatDrawable
import co.anitrend.arch.extension.ext.gone
import co.anitrend.arch.extension.ext.visible
import co.anitrend.arch.recycler.action.contract.ISupportSelectionMode
import co.anitrend.arch.recycler.common.ClickableItem
-import co.anitrend.arch.recycler.common.DefaultClickableItem
import co.anitrend.arch.recycler.holder.SupportViewHolder
import co.anitrend.arch.recycler.model.RecyclerItem
import co.anitrend.arch.ui.extension.setUpWith
@@ -23,8 +23,8 @@ import coil.request.Disposable
import kotlinx.coroutines.flow.MutableStateFlow
class MarketPlaceListingItem(
- private val entity: MarketPlaceListing?
-) : RecyclerItem(entity?.id?.hashCode()?.toLong()) {
+ private val entity: MarketPlaceListing
+) : RecyclerItem(entity.id.hashCode().toLong()) {
private var disposable: Disposable? = null
private var binding: MarketPlaceItemBinding? = null
@@ -39,7 +39,7 @@ class MarketPlaceListingItem(
)
)
- adapter.submitList(entity?.categories as List)
+ adapter.submitList(entity.categories)
}
}
@@ -57,16 +57,12 @@ class MarketPlaceListingItem(
view: View,
position: Int,
payloads: List,
- stateFlow: MutableStateFlow,
+ stateFlow: MutableStateFlow,
selectionMode: ISupportSelectionMode?
) {
- if (entity == null) return
binding = MarketPlaceItemBinding.bind(view)
binding?.listingContainer?.setOnClickListener {
- stateFlow.value = DefaultClickableItem(
- data = entity,
- view = view
- )
+ Toast.makeText(it.context, entity.name, Toast.LENGTH_SHORT).show()
}
disposable = binding?.listingImage?.using(entity.logoUrl)
binding?.listingName?.text = entity.name
@@ -76,7 +72,7 @@ class MarketPlaceListingItem(
binding?.listingVerification?.setImageDrawable(
view.context.getCompatDrawable(
R.drawable.ic_whatshot_24dp,
- co.anitrend.arch.ui.R.color.colorStateBlue
+ co.anitrend.arch.theme.R.color.colorStateBlue
)
)
}
@@ -93,7 +89,7 @@ class MarketPlaceListingItem(
* @param resources optionally useful for dynamic size check with different configurations
*/
override fun getSpanSize(spanCount: Int, position: Int, resources: Resources): Int {
- return resources.getInteger(co.anitrend.arch.ui.R.integer.single_list_size)
+ return resources.getInteger(co.anitrend.arch.theme.R.integer.single_list_size)
}
/**
@@ -112,6 +108,6 @@ class MarketPlaceListingItem(
viewGroup: ViewGroup
) = MarketPlaceItemBinding.inflate(
this, viewGroup, false
- ).let { SupportViewHolder(it.root) }
+ ).let(::SupportViewHolder)
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/viewmodel/MarketPlaceViewModel.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/viewmodel/MarketPlaceViewModel.kt
index c4f81962..0b503185 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/viewmodel/MarketPlaceViewModel.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/viewmodel/MarketPlaceViewModel.kt
@@ -1,21 +1,62 @@
package co.anitrend.retrofit.graphql.sample.view.content.market.viewmodel
+import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.asLiveData
+import androidx.lifecycle.switchMap
+import androidx.lifecycle.viewModelScope
+import androidx.paging.PagedList
+import co.anitrend.arch.core.model.ISupportViewModelState
+import co.anitrend.arch.data.state.DataState
+import co.anitrend.retrofit.graphql.data.bucket.model.upload.mutation.UploadMutation
import co.anitrend.retrofit.graphql.data.market.usecase.MarketPlaceUseCaseContract
-import co.anitrend.retrofit.graphql.sample.view.content.market.viewmodel.state.MarketPlaceState
+import co.anitrend.retrofit.graphql.domain.entities.market.MarketPlaceListing
+import kotlinx.coroutines.flow.merge
class MarketPlaceViewModel(
private val useCase: MarketPlaceUseCaseContract
-) : ViewModel() {
+) : ViewModel(), ISupportViewModelState> {
- val state = MarketPlaceState(useCase)
+ private val state = MutableLiveData>>()
+
+ override val model = state.switchMap {
+ it.model.asLiveData(viewModelScope.coroutineContext)
+ }
+
+ override val loadState = state.switchMap {
+ it.loadState.asLiveData(viewModelScope.coroutineContext)
+ }
+
+ override val refreshState = state.switchMap {
+ it.refreshState.asLiveData(viewModelScope.coroutineContext)
+ }
+
+ val combinedLoadState = state.switchMap {
+ val result = merge(it.loadState, it.refreshState)
+ result.asLiveData(viewModelScope.coroutineContext)
+ }
+
+ operator fun invoke() {
+ val result = useCase()
+ state.postValue(result)
+ }
+
+ /**
+ * Triggers use case to perform refresh operation
+ */
+ override suspend fun refresh() {
+ val uiModel = state.value
+ uiModel?.refresh?.invoke()
+ }
/**
- * This method will be called when this ViewModel is no longer used and will be destroyed.
- *
- * It is useful when ViewModel observes some data and you need to clear this subscription to
- * prevent a leak of this ViewModel.
+ * Triggers use case to perform a retry operation
*/
+ override suspend fun retry() {
+ val uiModel = state.value
+ uiModel?.retry?.invoke()
+ }
+
override fun onCleared() {
useCase.onCleared()
super.onCleared()
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/viewmodel/state/MarketPlaceState.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/viewmodel/state/MarketPlaceState.kt
deleted file mode 100644
index ced1595b..00000000
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/view/content/market/viewmodel/state/MarketPlaceState.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-package co.anitrend.retrofit.graphql.sample.view.content.market.viewmodel.state
-
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.switchMap
-import androidx.paging.PagedList
-import co.anitrend.arch.core.model.ISupportViewModelState
-import co.anitrend.arch.data.model.UserInterfaceState
-import co.anitrend.retrofit.graphql.data.market.usecase.MarketPlaceUseCaseContract
-import co.anitrend.retrofit.graphql.domain.entities.market.MarketPlaceListing
-
-data class MarketPlaceState(
- private val useCase: MarketPlaceUseCaseContract
-) : ISupportViewModelState> {
-
- private val useCaseResult = MutableLiveData>>()
-
- override val model =
- useCaseResult.switchMap() { it.model }
- override val networkState =
- useCaseResult.switchMap() { it.networkState }
- override val refreshState =
- useCaseResult.switchMap() { it.refreshState }
-
- operator fun invoke() {
- val result = useCase()
- useCaseResult.postValue(result)
- }
-
- /**
- * Called upon [androidx.lifecycle.ViewModel.onCleared] and should optionally
- * call cancellation of any ongoing jobs.
- *
- * If your use case source is of type [co.anitrend.arch.domain.common.IUseCase]
- * then you could optionally call [co.anitrend.arch.domain.common.IUseCase.onCleared] here
- */
- override fun onCleared() {
- useCase.onCleared()
- }
-
- /**
- * Triggers use case to perform refresh operation
- */
- override fun refresh() {
- val uiModel = useCaseResult.value
- uiModel?.refresh?.invoke()
- }
-
- /**
- * Triggers use case to perform a retry operation
- */
- override fun retry() {
- val uiModel = useCaseResult.value
- uiModel?.retry?.invoke()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/viewmodel/MainViewModel.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/viewmodel/MainViewModel.kt
index 4784c4cc..e21cc080 100644
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/viewmodel/MainViewModel.kt
+++ b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/viewmodel/MainViewModel.kt
@@ -1,13 +1,59 @@
package co.anitrend.retrofit.graphql.sample.viewmodel
+import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.asLiveData
+import androidx.lifecycle.switchMap
+import androidx.lifecycle.viewModelScope
+import co.anitrend.arch.core.model.ISupportViewModelState
+import co.anitrend.arch.data.state.DataState
import co.anitrend.retrofit.graphql.data.user.usecase.UserUseCaseContract
-import co.anitrend.retrofit.graphql.sample.viewmodel.model.MainState
+import co.anitrend.retrofit.graphql.domain.entities.user.User
+import kotlinx.coroutines.flow.merge
class MainViewModel(
- useCase: UserUseCaseContract
-) : ViewModel() {
- val state = MainState(useCase)
+ private val useCase: UserUseCaseContract
+) : ViewModel(), ISupportViewModelState {
+
+ private val state = MutableLiveData>()
+
+ override val model = state.switchMap {
+ it.model.asLiveData(viewModelScope.coroutineContext)
+ }
+
+ override val loadState = state.switchMap {
+ it.loadState.asLiveData(viewModelScope.coroutineContext)
+ }
+
+ override val refreshState = state.switchMap {
+ it.refreshState.asLiveData(viewModelScope.coroutineContext)
+ }
+
+ val combinedLoadState = state.switchMap {
+ val result = merge(it.loadState, it.refreshState)
+ result.asLiveData(viewModelScope.coroutineContext)
+ }
+
+ operator fun invoke() {
+ val result = useCase()
+ state.postValue(result)
+ }
+
+ /**
+ * Triggers use case to perform refresh operation
+ */
+ override suspend fun refresh() {
+ val uiModel = state.value
+ uiModel?.refresh?.invoke()
+ }
+
+ /**
+ * Triggers use case to perform a retry operation
+ */
+ override suspend fun retry() {
+ val uiModel = state.value
+ uiModel?.retry?.invoke()
+ }
/**
* This method will be called when this ViewModel is no longer used and will be destroyed.
@@ -16,7 +62,7 @@ class MainViewModel(
* prevent a leak of this ViewModel.
*/
override fun onCleared() {
- state.onCleared()
+ useCase.onCleared()
super.onCleared()
}
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/viewmodel/model/MainState.kt b/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/viewmodel/model/MainState.kt
deleted file mode 100644
index 8d25af4b..00000000
--- a/app/src/main/kotlin/co/anitrend/retrofit/graphql/sample/viewmodel/model/MainState.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package co.anitrend.retrofit.graphql.sample.viewmodel.model
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.switchMap
-import co.anitrend.arch.core.model.ISupportViewModelState
-import co.anitrend.arch.data.model.UserInterfaceState
-import co.anitrend.arch.domain.entities.LoadState
-import co.anitrend.arch.domain.state.UiState
-import co.anitrend.retrofit.graphql.data.user.usecase.UserUseCaseContract
-import co.anitrend.retrofit.graphql.domain.entities.user.User
-
-data class MainState(
- private val useCase: UserUseCaseContract
-) : ISupportViewModelState {
-
- private val useCaseResult = MutableLiveData>()
-
- override val model =
- useCaseResult.switchMap { it.model }
- override val refreshState =
- useCaseResult.switchMap { it.refreshState }
- override val loadState =
- useCaseResult.switchMap { it.loadState }
-
- operator fun invoke() {
- val result = useCase()
- useCaseResult.postValue(result)
- }
-
- /**
- * Called upon [androidx.lifecycle.ViewModel.onCleared] and should optionally
- * call cancellation of any ongoing jobs.
- *
- * If your use case source is of type [co.anitrend.arch.domain.common.IUseCase]
- * then you could optionally call [co.anitrend.arch.domain.common.IUseCase.onCleared] here
- */
- override fun onCleared() {
- useCase.onCleared()
- }
-
- /**
- * Triggers use case to perform refresh operation
- */
- override fun refresh() {
- val uiModel = useCaseResult.value
- uiModel?.refresh?.invoke()
- }
-
- /**
- * Triggers use case to perform a retry operation
- */
- override fun retry() {
- val uiModel = useCaseResult.value
- uiModel?.retry?.invoke()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/res/layout/market_place_item.xml b/app/src/main/res/layout/market_place_item.xml
index 17e01a50..2626b83a 100644
--- a/app/src/main/res/layout/market_place_item.xml
+++ b/app/src/main/res/layout/market_place_item.xml
@@ -19,7 +19,7 @@
+ android:padding="@dimen/xl_margin">
+ android:layout_height="@dimen/lg_margin" />
+ android:layout_height="@dimen/lg_margin" />
+ android:layout_height="@dimen/lg_margin" />
diff --git a/app/src/main/res/layout/shared_list_content.xml b/app/src/main/res/layout/shared_list_content.xml
new file mode 100644
index 00000000..c27dc241
--- /dev/null
+++ b/app/src/main/res/layout/shared_list_content.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/buildSrc/src/main/java/co/anitrend/retrofit/graphql/buildSrc/plugin/components/AndroidConfiguration.kt b/buildSrc/src/main/java/co/anitrend/retrofit/graphql/buildSrc/plugin/components/AndroidConfiguration.kt
index e55685e8..bc3b5f38 100644
--- a/buildSrc/src/main/java/co/anitrend/retrofit/graphql/buildSrc/plugin/components/AndroidConfiguration.kt
+++ b/buildSrc/src/main/java/co/anitrend/retrofit/graphql/buildSrc/plugin/components/AndroidConfiguration.kt
@@ -58,7 +58,7 @@ private fun DefaultConfig.applyAdditionalConfiguration(project: Project) {
internal fun Project.configureAndroid(): Unit = baseExtension().run {
compileSdkVersion(34)
defaultConfig {
- minSdk = if (isSampleModule()) 21 else 17
+ minSdk = if (isSampleModule()) 23 else 17
targetSdk = 34
versionCode = props[PropertyTypes.CODE].toInt()
versionName = props[PropertyTypes.VERSION]
diff --git a/library/proguard-rules.pro b/library/consumer-rules.pro
similarity index 100%
rename from library/proguard-rules.pro
rename to library/consumer-rules.pro