diff --git a/Android Developer-Practical Task-Updated-June-2020-pdf.pdf b/Android Developer-Practical Task-Updated-June-2020-pdf.pdf
deleted file mode 100644
index c938e59..0000000
Binary files a/Android Developer-Practical Task-Updated-June-2020-pdf.pdf and /dev/null differ
diff --git a/README.md b/README.md
index ee54a92..8f8efa1 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,5 @@
# Pokemons
Exam task VMedia
+
+
+TODO: animate screen changes
diff --git a/app/build.gradle b/app/build.gradle
deleted file mode 100644
index 16aeb75..0000000
--- a/app/build.gradle
+++ /dev/null
@@ -1,80 +0,0 @@
-plugins {
- id 'com.android.application'
- id 'kotlin-android'
- id 'kotlin-kapt'
- id 'androidx.navigation.safeargs.kotlin'
-}
-
-android {
- compileSdkVersion project.compile
- buildToolsVersion project.tools
-
- defaultConfig {
- applicationId "com.mdgd.pokemon"
- minSdkVersion project.min
- targetSdkVersion project.target
- versionCode 1
- versionName "1.0"
- multiDexEnabled true
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-}
-
-dependencies {
- implementation project(':mvi')
- implementation project(':models')
- implementation project(':models_impl')
- implementation fileTree(dir: "libs", include: ["*.jar"])
- implementation "androidx.multidex:multidex:2.0.1"
-
- implementation "androidx.appcompat:appcompat:$compat"
- implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
- implementation 'androidx.recyclerview:recyclerview:1.2.1'
- implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
- implementation 'com.google.android.material:material:1.4.0'
-
- // navigation
- implementation "androidx.navigation:navigation-fragment:$nav_version"
- implementation "androidx.navigation:navigation-ui:$nav_version"
-
- // images
- implementation 'com.squareup.picasso:picasso:2.71828'
-
- // json
- implementation "com.google.code.gson:gson:$gson"
-
- // lifecycle
- implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_ktx"
- implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_ktx"
-
- implementation "androidx.work:work-runtime-ktx:$work_version"
-
- testImplementation "junit:junit:$junit"
- testImplementation "org.mockito:mockito-core:3.7.0"
- testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
- testImplementation "android.arch.core:core-testing:1.1.1"
- testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.0"
- testImplementation "com.google.code.gson:gson:$gson"
-
- androidTestImplementation "androidx.test.ext:junit:$junit_android"
- androidTestImplementation "androidx.test.espresso:espresso-core:$espresso"
- androidTestImplementation "androidx.test.ext:junit:1.1.3"
- androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
- androidTestImplementation "com.android.support.test.espresso:espresso-contrib:3.3.0"
-
- implementation "androidx.core:core-ktx:$ktx"
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutins"
-}
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000..3512d2b
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,98 @@
+plugins {
+ id("com.android.application")
+ kotlin("android")
+ id("com.google.devtools.ksp")
+ id("androidx.navigation.safeargs.kotlin")
+ id("dagger.hilt.android.plugin")
+}
+
+android {
+ namespace = "com.mdgd.pokemon"
+ compileSdk = rootProject.extra["compile"] as Int?
+
+ defaultConfig {
+ applicationId = "com.mdgd.pokemon"
+ minSdk = rootProject.extra["min"] as Int?
+
+ versionCode = 1
+ versionName = "1.0"
+ multiDexEnabled = true
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+ buildFeatures {
+ compose = true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.4.6"
+ }
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_17.toString()
+ }
+ packaging {
+ resources {
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ }
+ }
+}
+
+dependencies {
+ implementation(project(":mvi"))
+ implementation(project(":models"))
+ implementation(project(":models_impl"))
+ implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
+
+ // swipe-refresh
+ implementation("com.google.accompanist:accompanist-swiperefresh:0.19.0")
+
+ // navigation
+ implementation("androidx.navigation:navigation-fragment-ktx:${rootProject.extra["nav_version"]}")
+ implementation("androidx.navigation:navigation-ui-ktx:${rootProject.extra["nav_version"]}")
+
+ // image loading
+ implementation("io.coil-kt:coil-compose:1.3.0")
+
+ // json
+ implementation("com.google.code.gson:gson:${rootProject.extra["gson"]}")
+
+ // hilt
+ implementation("com.google.dagger:hilt-android:${rootProject.extra["hilt"]}")
+ ksp("com.google.dagger:hilt-android-compiler:${rootProject.extra["hilt"]}")
+
+ implementation("androidx.hilt:hilt-navigation-fragment:${rootProject.extra["hilt_jetpack"]}")
+ ksp("androidx.hilt:hilt-compiler:${rootProject.extra["hilt_jetpack"]}")
+ implementation("androidx.hilt:hilt-work:1.0.0")
+
+ // lifecycle
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${rootProject.extra["lifecycle_ktx"]}")
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:${rootProject.extra["lifecycle_ktx"]}")
+
+ implementation("androidx.work:work-runtime-ktx:${rootProject.extra["work_version"]}")
+
+ testImplementation("junit:junit:${rootProject.extra["ver_junit"]}")
+ testImplementation("org.mockito:mockito-core:${rootProject.extra["mockito_core"]}")
+ testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:${rootProject.extra["mockito_kotlin"]}")
+ testImplementation("android.arch.core:core-testing:${rootProject.extra["testing_core"]}")
+ testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${rootProject.extra["testing_coroutine"]}")
+ testImplementation("com.google.code.gson:gson:${rootProject.extra["gson"]}")
+
+ androidTestImplementation("androidx.test.ext:junit:${rootProject.extra["junit_android"]}")
+ androidTestImplementation("androidx.test.espresso:espresso-core:${rootProject.extra["espresso"]}")
+ androidTestImplementation("com.android.support.test.espresso:espresso-contrib:3.3.0")
+
+ implementation("androidx.core:core-ktx:${rootProject.extra["ktx"]}")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:${rootProject.extra["coroutines"]}")
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index f1b4245..2f9dc5a 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -1,6 +1,6 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
+# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
diff --git a/app/src/androidTest/java/com/mdgd/pokemon/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/mdgd/pokemon/ExampleInstrumentedTest.java
index 9102944..98ee159 100644
--- a/app/src/androidTest/java/com/mdgd/pokemon/ExampleInstrumentedTest.java
+++ b/app/src/androidTest/java/com/mdgd/pokemon/ExampleInstrumentedTest.java
@@ -1,15 +1,15 @@
package com.mdgd.pokemon;
+import static org.junit.Assert.assertEquals;
+
import android.content.Context;
-import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
-import static org.junit.Assert.*;
-
/**
* Instrumented test, which will execute on an Android device.
*
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6e21701..91303f9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,20 +1,22 @@
+ xmlns:tools="http://schemas.android.com/tools">
+ android:theme="@style/Theme.Pokemons">
-
+
@@ -22,6 +24,11 @@
+
+
diff --git a/app/src/main/java/com/mdgd/pokemon/PokemonsApp.kt b/app/src/main/java/com/mdgd/pokemon/PokemonsApp.kt
index 9678c3a..79237ae 100644
--- a/app/src/main/java/com/mdgd/pokemon/PokemonsApp.kt
+++ b/app/src/main/java/com/mdgd/pokemon/PokemonsApp.kt
@@ -1,22 +1,28 @@
package com.mdgd.pokemon
+import androidx.hilt.work.HiltWorkerFactory
import androidx.multidex.MultiDexApplication
-import com.mdgd.pokemon.models.AppModule
-import com.mdgd.pokemon.models_impl.DefaultAppModule
+import androidx.work.Configuration
+import dagger.hilt.android.HiltAndroidApp
+import javax.inject.Inject
-class PokemonsApp : MultiDexApplication() {
+@HiltAndroidApp
+class PokemonsApp : MultiDexApplication(), Configuration.Provider {
- var appComponent: AppModule? = null
- private set
+ companion object {
+ var instance: PokemonsApp? = null
+ private set
+ }
+
+ @Inject
+ lateinit var workerFactory: HiltWorkerFactory
override fun onCreate() {
super.onCreate()
instance = this
- appComponent = DefaultAppModule(this)
}
- companion object {
- var instance: PokemonsApp? = null
- private set
- }
+ override fun getWorkManagerConfiguration() = Configuration.Builder()
+ .setWorkerFactory(workerFactory)
+ .build()
}
diff --git a/app/src/main/java/com/mdgd/pokemon/bg/LoadPokemonsContract.kt b/app/src/main/java/com/mdgd/pokemon/bg/LoadPokemonsContract.kt
index 7f6bf0f..04aa55e 100644
--- a/app/src/main/java/com/mdgd/pokemon/bg/LoadPokemonsContract.kt
+++ b/app/src/main/java/com/mdgd/pokemon/bg/LoadPokemonsContract.kt
@@ -3,5 +3,3 @@ package com.mdgd.pokemon.bg
interface ServiceModel {
fun load()
}
-
-interface LoadPokemonContext
diff --git a/app/src/main/java/com/mdgd/pokemon/bg/LoadPokemonsModel.kt b/app/src/main/java/com/mdgd/pokemon/bg/LoadPokemonsModel.kt
index 192124a..fc48c31 100644
--- a/app/src/main/java/com/mdgd/pokemon/bg/LoadPokemonsModel.kt
+++ b/app/src/main/java/com/mdgd/pokemon/bg/LoadPokemonsModel.kt
@@ -8,10 +8,8 @@ import kotlinx.coroutines.*
class LoadPokemonsModel(private val repo: PokemonsRepo, private val cache: Cache) : ServiceModel {
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private val exceptionHandler = CoroutineExceptionHandler { _, e ->
- coroutineScope.launch {
- cache.putLoadingProgress(Result(e))
- cancelScope()
- }
+ cache.putLoadingProgress(Result(e))
+ coroutineScope.cancel()
}
override fun load() {
@@ -20,13 +18,9 @@ class LoadPokemonsModel(private val repo: PokemonsRepo, private val cache: Cache
repo.loadInitialPages(initialAmount)
cache.putLoadingProgress(Result(initialAmount))
- cache.putLoadingProgress(Result(repo.loadPokemons(initialAmount)))
- cancelScope()
+ val newAmount = repo.loadPokemons(initialAmount)
+ cache.putLoadingProgress(Result(newAmount))
+ coroutineScope.cancel()
}
}
-
- private suspend fun cancelScope() {
- delay(50)
- coroutineScope.cancel()
- }
}
diff --git a/app/src/main/java/com/mdgd/pokemon/bg/PokemonsLoadingModelFactory.kt b/app/src/main/java/com/mdgd/pokemon/bg/PokemonsLoadingModelFactory.kt
deleted file mode 100644
index 89498a1..0000000
--- a/app/src/main/java/com/mdgd/pokemon/bg/PokemonsLoadingModelFactory.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.mdgd.pokemon.bg
-
-import com.mdgd.pokemon.models.AppModule
-
-class PokemonsLoadingModelFactory(var appComponent: AppModule) {
-
- fun create(): ServiceModel {
- return LoadPokemonsModel(appComponent.getPokemonsRepo(), appComponent.getCache())
- }
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/bg/UploadWorker.kt b/app/src/main/java/com/mdgd/pokemon/bg/UploadWorker.kt
index a539df4..6e90784 100644
--- a/app/src/main/java/com/mdgd/pokemon/bg/UploadWorker.kt
+++ b/app/src/main/java/com/mdgd/pokemon/bg/UploadWorker.kt
@@ -1,12 +1,20 @@
package com.mdgd.pokemon.bg
import android.content.Context
+import androidx.hilt.work.HiltWorker
import androidx.work.Worker
import androidx.work.WorkerParameters
-import com.mdgd.pokemon.PokemonsApp.Companion.instance
+import com.mdgd.pokemon.models.cache.Cache
+import com.mdgd.pokemon.models.repo.PokemonsRepo
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedInject
-class UploadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
- private val model: ServiceModel = PokemonsLoadingModelFactory(instance!!.appComponent!!).create()
+@HiltWorker
+class UploadWorker @AssistedInject constructor(
+ @Assisted context: Context, @Assisted params: WorkerParameters,
+ repo: PokemonsRepo, cache: Cache
+) : Worker(context, params) {
+ private val model: ServiceModel = LoadPokemonsModel(repo, cache)
override fun doWork(): Result {
model.load()
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/MainActivity.kt b/app/src/main/java/com/mdgd/pokemon/ui/MainActivity.kt
index 6c54699..faea836 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/MainActivity.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/MainActivity.kt
@@ -6,12 +6,13 @@ import androidx.navigation.NavOptions
import androidx.navigation.Navigation
import com.mdgd.mvi.HostActivity
import com.mdgd.pokemon.R
-import com.mdgd.pokemon.ui.error.ErrorFragment.Companion.newInstance
import com.mdgd.pokemon.ui.pokemon.PokemonDetailsContract
import com.mdgd.pokemon.ui.pokemon.PokemonDetailsFragmentArgs
import com.mdgd.pokemon.ui.pokemons.PokemonsContract
import com.mdgd.pokemon.ui.splash.SplashContract
+import dagger.hilt.android.AndroidEntryPoint
+@AndroidEntryPoint
class MainActivity : HostActivity(), SplashContract.Host, PokemonsContract.Host, PokemonDetailsContract.Host {
private var navController: NavController? = null
@@ -27,23 +28,14 @@ class MainActivity : HostActivity(), SplashContract.Host, PokemonsContract.Host,
override fun proceedToPokemonsScreen() {
navController!!.navigate(R.id.action_splashFragment_to_pokemonsFragment, null, // doesn't work from xml...
- NavOptions.Builder()
- .setPopUpTo(R.id.splashFragment, true)
- .build()
+ NavOptions.Builder()
+ .setPopUpTo(R.id.splashFragment, true)
+ .build()
)
}
override fun proceedToPokemonScreen(pokemonId: Long?) {
navController!!.navigate(R.id.action_pokemonsFragment_to_pokemonDetailsFragment, PokemonDetailsFragmentArgs(pokemonId
- ?: -1).toBundle())
- }
-
- override fun showError(error: Throwable?) {
- error?.printStackTrace()
- if (supportFragmentManager.findFragmentByTag("error") == null) {
- val errorFragment = newInstance(R.string.dialog_error_title, R.string.dialog_error_message)
- errorFragment.setError(error)
- errorFragment.show(supportFragmentManager, "error")
- }
+ ?: -1).toBundle())
}
}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/adapter/ClickData.kt b/app/src/main/java/com/mdgd/pokemon/ui/adapter/ClickData.kt
deleted file mode 100644
index 468aae3..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/adapter/ClickData.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.mdgd.pokemon.ui.adapter
-
-sealed class ClickEvent {
-
- class EmptyData() : ClickEvent()
-
- class ClickData(val data: T) : ClickEvent()
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/adapter/RecyclerAdapter.kt b/app/src/main/java/com/mdgd/pokemon/ui/adapter/RecyclerAdapter.kt
deleted file mode 100644
index 8804e16..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/adapter/RecyclerAdapter.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.mdgd.pokemon.ui.adapter
-
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.RecyclerView
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-
-abstract class RecyclerAdapter : RecyclerView.Adapter>() {
- protected val items: MutableList = ArrayList()
- protected val clicksSubject = MutableStateFlow>(ClickEvent.EmptyData())
-
- fun getItemClickFlow(): Flow> {
- return clicksSubject
- }
-
- override fun getItemViewType(position: Int): Int {
- if (items.isEmpty()) {
- return EMPTY_VIEW
- }
- return super.getItemViewType(position)
- }
-
- override fun getItemCount(): Int {
- return if (items.isEmpty()) 1 else items.size
- }
-
- override fun onBindViewHolder(holder: RecyclerVH, position: Int) {
- if (items.isNotEmpty()) {
- holder.bindItem(items[position], position)
- }
- }
-
- override fun onViewAttachedToWindow(holder: RecyclerVH) {
- super.onViewAttachedToWindow(holder)
- holder.setupSubscriptions()
- }
-
- override fun onViewDetachedFromWindow(holder: RecyclerVH) {
- holder.clearSubscriptions()
- super.onViewDetachedFromWindow(holder)
- }
-
- fun setItems(items: List) {
- if (this.items.isEmpty()) {
- this.items.addAll(items)
- notifyDataSetChanged()
- } else {
- val oldList: List = ArrayList(this.items)
- this.items.clear()
- this.items.addAll(items)
- dispatchChanges(oldList, items)
- }
- }
-
- protected fun dispatchChanges(oldList: List, items: List) {
- DiffUtil.calculateDiff(object : DiffUtil.Callback() {
- override fun getOldListSize(): Int {
- return oldList.size
- }
-
- override fun getNewListSize(): Int {
- return items.size
- }
-
- override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
- return oldList[oldItemPosition] == items[newItemPosition]
- }
-
- override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
- return oldList[oldItemPosition] == items[newItemPosition]
- }
- }).dispatchUpdatesTo(this)
- }
-
-
- companion object {
- const val EMPTY_VIEW = 1
- }
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/adapter/RecyclerVH.kt b/app/src/main/java/com/mdgd/pokemon/ui/adapter/RecyclerVH.kt
deleted file mode 100644
index fb69630..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/adapter/RecyclerVH.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.mdgd.pokemon.ui.adapter
-
-import android.view.View
-import androidx.recyclerview.widget.RecyclerView
-
-open class RecyclerVH(itemView: View) : RecyclerView.ViewHolder(itemView) {
-
- open fun setupSubscriptions() {
- }
-
- open fun clearSubscriptions() {
- }
-
- open fun bindItem(item: T, position: Int) {
- }
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/error/DefaultErrorParams.kt b/app/src/main/java/com/mdgd/pokemon/ui/error/DefaultErrorParams.kt
new file mode 100644
index 0000000..549bc71
--- /dev/null
+++ b/app/src/main/java/com/mdgd/pokemon/ui/error/DefaultErrorParams.kt
@@ -0,0 +1,10 @@
+package com.mdgd.pokemon.ui.error
+
+data class DefaultErrorParams(
+ override val isVisible: Boolean = false,
+ override val title: String = "",
+ override val message: String = ""
+) : ErrorParams {
+
+ override fun hide() = copy(isVisible = false)
+}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/error/ErrorContract.kt b/app/src/main/java/com/mdgd/pokemon/ui/error/ErrorContract.kt
deleted file mode 100644
index 193f1cd..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/error/ErrorContract.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.mdgd.pokemon.ui.error
-
-import com.mdgd.mvi.fragments.FragmentContract
-import com.mdgd.pokemon.ui.error.state.ErrorFragmentAction
-import com.mdgd.pokemon.ui.error.state.ErrorFragmentState
-
-class ErrorContract {
- interface ViewModel : FragmentContract.ViewModel
- interface View : FragmentContract.View
- interface Host : FragmentContract.Host
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/error/ErrorFragment.kt b/app/src/main/java/com/mdgd/pokemon/ui/error/ErrorFragment.kt
deleted file mode 100644
index c0804aa..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/error/ErrorFragment.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-package com.mdgd.pokemon.ui.error
-
-import android.app.Dialog
-import android.content.DialogInterface
-import android.os.Bundle
-import androidx.appcompat.app.AlertDialog
-import com.mdgd.pokemon.R
-import com.mdgd.pokemon.ui.error.state.ErrorFragmentAction
-import com.mdgd.pokemon.ui.error.state.ErrorFragmentState
-
-class ErrorFragment : MessageDialog<
- ErrorContract.View,
- ErrorFragmentState,
- ErrorFragmentAction,
- ErrorContract.ViewModel,
- ErrorContract.Host>(),
- ErrorContract.View, DialogInterface.OnClickListener {
- private var error: Throwable? = null
-
- override fun createModel(): ErrorContract.ViewModel? {
- return null
- }
-
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val builder = AlertDialog.Builder(requireActivity())
- val args = arguments
- // todo add trace printing, add retry
- if (args != null) {
- val type = args.getInt(KEY_TYPE)
- if (TYPE_INT == type) {
- val titleResId = args.getInt(KEY_TITLE, R.string.empty)
- if (titleResId != 0) {
- builder.setTitle(titleResId)
- }
- val messageResId = args.getInt(KEY_MSG, R.string.empty)
- if (messageResId != 0) {
- val message = getString(messageResId) + if (error == null) "" else " " + error!!.message
- builder.setMessage(message)
- }
- } else if (TYPE_STR == type) {
- builder.setTitle(args.getString(KEY_TITLE_STR, ""))
- val message = args.getString(KEY_MSG_STR, "") + if (error == null) "" else " " + error!!.message
- builder.setMessage(message)
- }
- }
- builder.setPositiveButton(android.R.string.ok, this)
- return builder.create()
- }
-
- override fun onClick(dialog: DialogInterface, which: Int) {
- when (which) {
- DialogInterface.BUTTON_POSITIVE -> {
- dismissAllowingStateLoss()
- }
- }
- }
-
- fun setError(error: Throwable?) {
- this.error = error
- }
-
- companion object {
- fun newInstance(title: String?, message: String?): ErrorFragment {
- val b = Bundle()
- b.putString(KEY_TITLE_STR, title)
- b.putString(KEY_MSG_STR, message)
- b.putInt(KEY_TYPE, TYPE_STR)
- val errorFragment = ErrorFragment()
- errorFragment.arguments = b
- return errorFragment
- }
-
- fun newInstance(title: Int, message: Int): ErrorFragment {
- val b = Bundle()
- b.putInt(KEY_TITLE, title)
- b.putInt(KEY_MSG, message)
- b.putInt(KEY_TYPE, TYPE_INT)
- val errorFragment = ErrorFragment()
- errorFragment.arguments = b
- return errorFragment
- }
- }
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/error/ErrorParams.kt b/app/src/main/java/com/mdgd/pokemon/ui/error/ErrorParams.kt
new file mode 100644
index 0000000..e4e4b61
--- /dev/null
+++ b/app/src/main/java/com/mdgd/pokemon/ui/error/ErrorParams.kt
@@ -0,0 +1,9 @@
+package com.mdgd.pokemon.ui.error
+
+interface ErrorParams {
+ val title: String
+ val message: String
+ val isVisible: Boolean
+
+ fun hide(): ErrorParams
+}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/error/ErrorScreen.kt b/app/src/main/java/com/mdgd/pokemon/ui/error/ErrorScreen.kt
new file mode 100644
index 0000000..0247b99
--- /dev/null
+++ b/app/src/main/java/com/mdgd/pokemon/ui/error/ErrorScreen.kt
@@ -0,0 +1,73 @@
+package com.mdgd.pokemon.ui.error
+
+import android.content.res.Configuration
+import androidx.compose.material.AlertDialog
+import androidx.compose.material.Button
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+
+@Composable
+fun ErrorScreen(params: MutableState) {
+ if (params.value.isVisible) {
+ AlertDialog(
+ title = {
+ Text(text = params.value.title)
+ },
+ text = {
+ Text(text = params.value.message)
+ },
+ confirmButton = {
+ Button(
+ onClick = {
+ params.value = params.value.hide()
+ },
+ ) {
+ Text(text = stringResource(id = android.R.string.ok))
+ }
+ },
+ onDismissRequest = {
+ // Dismiss the dialog when the user clicks outside the dialog or on the back
+ // button. If you want to disable that functionality, simply use an empty
+ // onCloseRequest.
+ params.value = params.value.hide()
+ }
+ )
+ }
+}
+
+
+@Preview(
+ uiMode = Configuration.UI_MODE_NIGHT_NO,
+ showBackground = true,
+ name = "Light Mode"
+)
+@Composable
+fun ErrorPreviewThemeLight() {
+ MaterialTheme {
+ val state: MutableState = remember {
+ mutableStateOf(DefaultErrorParams())
+ }
+ ErrorScreen(state)
+ }
+}
+
+@Preview(
+ uiMode = Configuration.UI_MODE_NIGHT_YES,
+ showBackground = true,
+ name = "Dark Mode"
+)
+@Composable
+fun ErrorPreviewThemeDark() {
+ MaterialTheme {
+ val state: MutableState = remember {
+ mutableStateOf(DefaultErrorParams(true))
+ }
+ ErrorScreen(state)
+ }
+}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/error/MessageDialog.kt b/app/src/main/java/com/mdgd/pokemon/ui/error/MessageDialog.kt
deleted file mode 100644
index 50e9b1e..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/error/MessageDialog.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.mdgd.pokemon.ui.error
-
-import com.mdgd.mvi.fragments.FragmentContract
-import com.mdgd.mvi.fragments.HostedDialogFragment
-import com.mdgd.mvi.states.AbstractAction
-import com.mdgd.mvi.states.ScreenState
-
-abstract class MessageDialog<
- VIEW : FragmentContract.View,
- STATE : ScreenState,
- ACTION : AbstractAction,
- VIEW_MODEL : FragmentContract.ViewModel,
- HOST : FragmentContract.Host>
- : HostedDialogFragment() {
-
- companion object {
- const val KEY_TITLE = "key_title"
- const val KEY_MSG = "key_msg"
- const val KEY_TITLE_STR = "key_title_str"
- const val KEY_MSG_STR = "key_msg_str"
- const val KEY_TYPE = "key_type"
- const val TYPE_INT = 1
- const val TYPE_STR = 2
- }
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/error/state/ErrorFragmentAction.kt b/app/src/main/java/com/mdgd/pokemon/ui/error/state/ErrorFragmentAction.kt
deleted file mode 100644
index 2ba0a18..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/error/state/ErrorFragmentAction.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.mdgd.pokemon.ui.error.state
-
-import com.mdgd.mvi.states.AbstractAction
-import com.mdgd.pokemon.ui.error.ErrorContract
-
-sealed class ErrorFragmentAction : AbstractAction() {
- override fun handle(screen: ErrorContract.View) {}
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/error/state/ErrorFragmentState.kt b/app/src/main/java/com/mdgd/pokemon/ui/error/state/ErrorFragmentState.kt
deleted file mode 100644
index 7f79c0a..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/error/state/ErrorFragmentState.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.mdgd.pokemon.ui.error.state
-
-import com.mdgd.mvi.states.ScreenState
-import com.mdgd.pokemon.ui.error.ErrorContract
-
-sealed class ErrorFragmentState : ScreenState {
- override fun visit(screen: ErrorContract.View) {}
-
- override fun merge(prevState: ErrorFragmentState) {}
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsContract.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsContract.kt
index 7c94e41..e19138b 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsContract.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsContract.kt
@@ -1,18 +1,20 @@
package com.mdgd.pokemon.ui.pokemon
import com.mdgd.mvi.fragments.FragmentContract
-import com.mdgd.pokemon.ui.pokemon.infra.PokemonProperty
-import com.mdgd.pokemon.ui.pokemon.state.PokemonDetailsScreenAction
-import com.mdgd.pokemon.ui.pokemon.state.PokemonDetailsScreenState
+import com.mdgd.pokemon.ui.pokemon.dto.PokemonProperty
class PokemonDetailsContract {
- interface ViewModel : FragmentContract.ViewModel {
+ interface ViewModel : FragmentContract.ViewModel {
fun setPokemonId(pokemonId: Long)
+ fun onBackPressed()
}
interface View : FragmentContract.View {
fun setItems(items: List)
+ fun goBack()
}
- interface Host : FragmentContract.Host
+ interface Host : FragmentContract.Host {
+ fun onBackPressed()
+ }
}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsFragment.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsFragment.kt
index 41ea658..4e16ba2 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsFragment.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsFragment.kt
@@ -1,51 +1,342 @@
package com.mdgd.pokemon.ui.pokemon
+import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.lifecycle.ViewModelProvider
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.fragment.app.viewModels
+import coil.compose.rememberImagePainter
+import coil.request.ImageRequest
import com.mdgd.mvi.fragments.HostedFragment
-import com.mdgd.pokemon.PokemonsApp.Companion.instance
import com.mdgd.pokemon.R
-import com.mdgd.pokemon.ui.pokemon.adapter.PokemonPropertiesAdapter
-import com.mdgd.pokemon.ui.pokemon.infra.PokemonProperty
-import com.mdgd.pokemon.ui.pokemon.state.PokemonDetailsScreenAction
-import com.mdgd.pokemon.ui.pokemon.state.PokemonDetailsScreenState
+import com.mdgd.pokemon.models.repo.dao.schemas.PokemonFullDataSchema
+import com.mdgd.pokemon.models.repo.dao.schemas.PokemonSchema
+import com.mdgd.pokemon.models.repo.schemas.Stat
+import com.mdgd.pokemon.models.repo.schemas.Stat_
+import com.mdgd.pokemon.ui.error.ErrorParams
+import com.mdgd.pokemon.ui.error.ErrorScreen
+import com.mdgd.pokemon.ui.pokemon.dto.ImageProperty
+import com.mdgd.pokemon.ui.pokemon.dto.ImagePropertyData
+import com.mdgd.pokemon.ui.pokemon.dto.LabelProperty
+import com.mdgd.pokemon.ui.pokemon.dto.LabelPropertyData
+import com.mdgd.pokemon.ui.pokemon.dto.PokemonProperty
+import com.mdgd.pokemon.ui.pokemon.dto.TextProperty
+import com.mdgd.pokemon.ui.pokemon.dto.TextPropertyData
+import com.mdgd.pokemon.ui.pokemon.dto.TitleProperty
+import com.mdgd.pokemon.ui.pokemon.dto.TitlePropertyData
+import dagger.hilt.android.AndroidEntryPoint
+@AndroidEntryPoint
class PokemonDetailsFragment : HostedFragment<
PokemonDetailsContract.View,
- PokemonDetailsScreenState,
- PokemonDetailsScreenAction,
PokemonDetailsContract.ViewModel,
PokemonDetailsContract.Host>(), PokemonDetailsContract.View {
- private val adapter = PokemonPropertiesAdapter()
+
+ private val screenState = mutableStateOf(PokemonUiState())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- if (arguments != null) {
- model?.setPokemonId(PokemonDetailsFragmentArgs.fromBundle(requireArguments()).pokemonId)
+ arguments?.let {
+ viewModel?.setPokemonId(PokemonDetailsFragmentArgs.fromBundle(requireArguments()).pokemonId)
}
}
override fun createModel(): PokemonDetailsContract.ViewModel {
- return ViewModelProvider(this, PokemonDetailsViewModelFactory(instance!!.appComponent!!)).get(PokemonDetailsViewModel::class.java)
+ val model: PokemonDetailsViewModel by viewModels()
+ return model
}
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- return inflater.inflate(R.layout.fragment_pokemon_properties, container, false)
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
+ ): View {
+ val view = ComposeView(requireContext())
+ view.setContent {
+ PokemonScreen(screenState, viewModel)
+ }
+ return view
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- val recycler: RecyclerView = view.findViewById(R.id.pokemon_details_recycler)
- recycler.layoutManager = LinearLayoutManager(activity)
- recycler.adapter = adapter
+ override fun setItems(items: List) {
+ screenState.value = screenState.value.copy(properties = items)
}
- override fun setItems(items: List) {
- adapter.setItems(items)
+ override fun goBack() {
+ fragmentHost?.onBackPressed()
+ }
+}
+
+@Composable
+fun PokemonScreen(
+ screenState: MutableState,
+ model: PokemonDetailsContract.ViewModel?
+) {
+ val errorDialogTrigger = remember { screenState as MutableState }
+ MaterialTheme {
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text(text = stringResource(id = R.string.app_name)) },
+ navigationIcon = {
+ IconButton(onClick = { model?.onBackPressed() }) {
+ Icon(
+ Icons.Filled.ArrowBack,
+ contentDescription = stringResource(id = R.string.button_back)
+ )
+ }
+ }
+ )
+ }
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxHeight()
+ .fillMaxWidth()
+ ) {
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxWidth()
+ .fillMaxHeight()
+ ) {
+ if (screenState.value.properties.isNullOrEmpty()) {
+ items(items = listOf(System.currentTimeMillis()), key = { it }) {
+ Column(
+ modifier = Modifier
+ .fillParentMaxHeight()
+ .fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ ) {
+ Text(
+ textAlign = TextAlign.Center,
+ text = stringResource(id = R.string.no_pokemons),
+ )
+ }
+ }
+ } else {
+ items(items = screenState.value.properties) { PokemonDetailItem(it) }
+ }
+ }
+ ErrorScreen(errorDialogTrigger)
+ }
+ }
+ }
+}
+
+@Composable
+fun PokemonDetailItem(property: PokemonProperty) {
+ when (property.type) {
+ PokemonProperty.PROPERTY_IMAGE -> {
+ val p = property as ImageProperty
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ ) {
+ Image(
+ contentDescription = stringResource(id = R.string.fragment_pokemon_picture),
+ contentScale = ContentScale.Inside,
+ modifier = Modifier.size(200.dp),
+ painter = rememberImagePainter(
+ data = p.imageUrl,
+ builder = {
+ ImageRequest.Builder(LocalContext.current)
+ .placeholder(R.drawable.ic_pokemon)
+ .error(R.drawable.ic_pokemon)
+ .build()
+ }
+ ))
+ }
+ }
+ PokemonProperty.PROPERTY_LABEL -> {
+ val startPadding = dimensionResource(id = R.dimen.pokemon_details_nesting_level_padding)
+ val p = property as LabelProperty
+
+ val title = if (p.titleResId == 0) {
+ p.titleStr
+ } else {
+ stringResource(id = p.titleResId)
+ }
+ Row(modifier = Modifier.fillMaxWidth()) {
+ Text(
+ text = title,
+ fontSize = 16.sp,
+ style = TextStyle(fontWeight = FontWeight.Bold),
+ modifier = Modifier
+ .weight(1F)
+ .padding(
+ PaddingValues(
+ startPadding * (1 + p.nestingLevel),
+ 0.dp, 0.dp, 0.dp
+ )
+ )
+ )
+ Text(
+ text = p.text,
+ fontSize = 16.sp,
+ style = TextStyle(textAlign = TextAlign.Center),
+ modifier = Modifier
+ .weight(1F)
+ .padding(5.dp)
+ )
+ }
+ }
+ PokemonProperty.PROPERTY_TEXT -> {
+ val startPadding = dimensionResource(id = R.dimen.pokemon_details_nesting_level_padding)
+ val p = property as TextProperty
+ Text(
+ text = p.text,
+ modifier = Modifier
+ .padding(PaddingValues(startPadding * (2 + p.nestingLevel), 5.dp, 5.dp, 5.dp))
+ .fillMaxWidth()
+ )
+ }
+ PokemonProperty.PROPERTY_TITLE -> {
+ val startPadding = dimensionResource(id = R.dimen.pokemon_details_nesting_level_padding)
+ val p = property as TitleProperty
+
+ val params = if (p.nestingLevel == 0) {
+ Pair(
+ TextStyle(
+ textAlign = TextAlign.Center,
+ fontWeight = FontWeight.Bold,
+ fontFamily = FontFamily.Default
+ ),
+ Modifier
+ .padding(0.dp)
+ .fillMaxWidth()
+ )
+ } else {
+ Pair(
+ TextStyle(
+ textAlign = TextAlign.Start,
+ fontWeight = FontWeight.Normal,
+ fontFamily = FontFamily.Serif
+ ),
+ Modifier
+ .padding(
+ PaddingValues(
+ startPadding * (2 + p.nestingLevel),
+ 5.dp, 5.dp, 5.dp
+ )
+ )
+ .fillMaxWidth()
+ )
+ }
+ Text(
+ text = if (p.titleResId == 0) {
+ ""
+ } else {
+ stringResource(id = p.titleResId)
+ },
+ fontSize = 18.sp,
+ style = params.first,
+ modifier = params.second
+ )
+ }
+ }
+}
+
+@Preview(
+ uiMode = Configuration.UI_MODE_NIGHT_NO,
+ showBackground = true,
+ name = "Pokemon Light Mode"
+)
+@Composable
+fun PokemonPreviewThemeLight() {
+ val pokemon = PokemonFullDataSchema()
+ pokemon.pokemonSchema = PokemonSchema()
+ pokemon.pokemonSchema?.name = "SlowPock"
+ pokemon.stats = mutableListOf()
+
+ val attack = Stat()
+ attack.stat = Stat_()
+ attack.stat?.name = "attack"
+ attack.baseStat = 100500
+ pokemon.stats.add(attack)
+
+ val defence = Stat()
+ defence.stat = Stat_()
+ defence.stat?.name = "defense"
+ defence.baseStat = 100501
+ pokemon.stats.add(defence)
+
+ val speed = Stat()
+ speed.stat = Stat_()
+ speed.stat?.name = "speed"
+ speed.baseStat = 100502
+ pokemon.stats.add(speed)
+ MaterialTheme {
+ val state: MutableState = remember {
+ mutableStateOf(PokemonUiState(properties = listOf()))
+ }
+ PokemonScreen(state, null)
+ }
+}
+
+@Preview(
+ uiMode = Configuration.UI_MODE_NIGHT_YES,
+ showBackground = true,
+ name = "Pokemon Dark Mode"
+)
+@Composable
+fun PokemonPreviewThemeDark() {
+ val properties: MutableList = ArrayList()
+ properties.add(ImagePropertyData("https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/154.png"))
+ properties.add(LabelPropertyData(R.string.pokemon_detail_name, "SlowPock"))
+ properties.add(LabelPropertyData(R.string.pokemon_detail_height, "100"))
+ properties.add(LabelPropertyData(R.string.pokemon_detail_weight, "90"))
+ properties.add(TitlePropertyData(R.string.pokemon_detail_stats))
+ properties.add(LabelPropertyData("Speed", "50", 1))
+ properties.add(TitlePropertyData(R.string.pokemon_detail_abilities))
+ properties.add(TextPropertyData("wololo, wololo", 1))
+ properties.add(TitlePropertyData(R.string.pokemon_detail_forms))
+ properties.add(TextPropertyData("wololo, wololo", 1))
+ properties.add(TitlePropertyData(R.string.pokemon_detail_types))
+ properties.add(TextPropertyData("bro, king", 1))
+ properties.add(TitlePropertyData(R.string.pokemon_detail_game_indicies))
+ properties.add(TextPropertyData("some indicies here", 1))
+ MaterialTheme {
+ val state: MutableState = remember {
+ mutableStateOf(PokemonUiState(properties = properties))
+ }
+ PokemonScreen(state, null)
}
}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsViewModel.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsViewModel.kt
index effc623..a6e9bb5 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsViewModel.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsViewModel.kt
@@ -1,7 +1,6 @@
package com.mdgd.pokemon.ui.pokemon
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.viewModelScope
import com.mdgd.mvi.MviViewModel
import com.mdgd.pokemon.R
@@ -11,38 +10,47 @@ import com.mdgd.pokemon.models.repo.schemas.Ability
import com.mdgd.pokemon.models.repo.schemas.Form
import com.mdgd.pokemon.models.repo.schemas.GameIndex
import com.mdgd.pokemon.models.repo.schemas.Type
-import com.mdgd.pokemon.ui.pokemon.infra.*
-import com.mdgd.pokemon.ui.pokemon.state.PokemonDetailsScreenAction
+import com.mdgd.pokemon.ui.pokemon.dto.ImagePropertyData
+import com.mdgd.pokemon.ui.pokemon.dto.LabelPropertyData
+import com.mdgd.pokemon.ui.pokemon.dto.PokemonProperty
+import com.mdgd.pokemon.ui.pokemon.dto.TextPropertyData
+import com.mdgd.pokemon.ui.pokemon.dto.TitlePropertyData
+import com.mdgd.pokemon.ui.pokemon.state.PokemonDetailsScreenEffect
import com.mdgd.pokemon.ui.pokemon.state.PokemonDetailsScreenState
+import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
-import java.util.*
-import kotlin.collections.ArrayList
+import java.util.LinkedList
+import javax.inject.Inject
-class PokemonDetailsViewModel(private val repo: PokemonsRepo)
- : MviViewModel(), PokemonDetailsContract.ViewModel {
+@HiltViewModel
+class PokemonDetailsViewModel @Inject constructor(
+ private val repo: PokemonsRepo
+) : MviViewModel(),
+ PokemonDetailsContract.ViewModel {
private val pokemonIdFlow = MutableStateFlow(-1L)
private var pokemonLoadingJob: Job? = null
override fun setPokemonId(pokemonId: Long) {
- viewModelScope.launch {
- pokemonIdFlow.emit(pokemonId)
- }
+ pokemonIdFlow.tryEmit(pokemonId)
}
- public override fun onAny(owner: LifecycleOwner?, event: Lifecycle.Event) {
- super.onAny(owner, event)
+ override fun onStateChanged(event: Lifecycle.Event) {
+ super.onStateChanged(event)
if (event == Lifecycle.Event.ON_CREATE && pokemonLoadingJob == null) {
pokemonLoadingJob = viewModelScope.launch {
pokemonIdFlow
- .filter { it != -1L }
- .map { id: Long -> repo.getPokemonById(id) }
- .map { details: PokemonFullDataSchema? -> if (details == null) LinkedList() else mapToListPokemon(details) }
- .flowOn(Dispatchers.IO)
- .collect { setState(PokemonDetailsScreenState.SetData(it)) }
+ .filter { it != -1L }
+ .map { repo.getPokemonById(it) }
+ .map { it?.let { mapToListPokemon(it) } ?: LinkedList() }
+ .flowOn(Dispatchers.IO)
+ .collect { setState(PokemonDetailsScreenState.SetData(it)) }
}
}
}
@@ -101,6 +109,10 @@ class PokemonDetailsViewModel(private val repo: PokemonsRepo)
return properties
}
+ override fun onBackPressed() {
+ setEffect(PokemonDetailsScreenEffect.EffectBack())
+ }
+
override fun onCleared() {
super.onCleared()
pokemonLoadingJob = null
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsViewModelFactory.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsViewModelFactory.kt
deleted file mode 100644
index d9325ab..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsViewModelFactory.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.mdgd.pokemon.ui.pokemon
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import com.mdgd.pokemon.models.AppModule
-
-class PokemonDetailsViewModelFactory(private val appComponent: AppModule) : ViewModelProvider.NewInstanceFactory() {
-
- override fun create(modelClass: Class): T {
- return if (modelClass == PokemonDetailsViewModel::class.java) {
- PokemonDetailsViewModel(appComponent.getPokemonsRepo()) as T
- } else super.create(modelClass)
- }
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonUiState.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonUiState.kt
new file mode 100644
index 0000000..edac5c0
--- /dev/null
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/PokemonUiState.kt
@@ -0,0 +1,15 @@
+package com.mdgd.pokemon.ui.pokemon
+
+import com.mdgd.pokemon.ui.error.ErrorParams
+import com.mdgd.pokemon.ui.pokemon.dto.PokemonProperty
+
+data class PokemonUiState(
+ val properties: List = listOf(),
+
+ override val title: String = "",
+ override val message: String = "",
+ override val isVisible: Boolean = false
+) : ErrorParams {
+
+ override fun hide() = copy(isVisible = false)
+}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/PokemonPropertiesAdapter.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/PokemonPropertiesAdapter.kt
deleted file mode 100644
index 91ee132..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/PokemonPropertiesAdapter.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.mdgd.pokemon.ui.pokemon.adapter
-
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import com.mdgd.pokemon.R
-import com.mdgd.pokemon.ui.adapter.RecyclerAdapter
-import com.mdgd.pokemon.ui.adapter.RecyclerVH
-import com.mdgd.pokemon.ui.pokemon.adapter.holders.*
-import com.mdgd.pokemon.ui.pokemon.infra.PokemonProperty
-import java.util.*
-
-class PokemonPropertiesAdapter : RecyclerAdapter() {
- private val resolver: MutableMap
-
- init {
- resolver = HashMap()
- resolver[PokemonProperty.PROPERTY_IMAGE] = object : ViewHolderFactory {
- override fun create(parent: ViewGroup?): RecyclerVH {
- return PokemonImageViewHolder(LayoutInflater.from(parent?.context).inflate(R.layout.item_pokemon_image, parent, false))
- }
- }
- resolver[PokemonProperty.PROPERTY_LABEL] = object : ViewHolderFactory {
- override fun create(parent: ViewGroup?): RecyclerVH {
- return PokemonLabelViewHolder(LayoutInflater.from(parent?.context).inflate(R.layout.item_pokemon_label, parent, false))
- }
- }
- resolver[PokemonProperty.PROPERTY_TITLE] = object : ViewHolderFactory {
- override fun create(parent: ViewGroup?): RecyclerVH {
- return PokemonTitleViewHolder(LayoutInflater.from(parent?.context).inflate(R.layout.item_pokemon_title, parent, false))
- }
- }
- resolver[PokemonProperty.PROPERTY_TEXT] = object : ViewHolderFactory {
- override fun create(parent: ViewGroup?): RecyclerVH {
- return PokemonTextViewHolder(LayoutInflater.from(parent?.context).inflate(R.layout.item_pokemon_label_image, parent, false))
- }
- }
- }
-
- override fun getItemViewType(position: Int): Int {
- return items[position].type
- }
-
- @Suppress("UNCHECKED_CAST")
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerVH {
- if (viewType == EMPTY_VIEW) {
- return EmptyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_empty, parent, false))
- }
- return resolver[viewType]!!.create(parent) as RecyclerVH
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/ViewHolderFactory.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/ViewHolderFactory.kt
deleted file mode 100644
index 03be239..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/ViewHolderFactory.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.mdgd.pokemon.ui.pokemon.adapter
-
-import android.view.ViewGroup
-import com.mdgd.pokemon.ui.adapter.RecyclerVH
-import com.mdgd.pokemon.ui.pokemon.infra.PokemonProperty
-
-interface ViewHolderFactory {
- fun create(parent: ViewGroup?): RecyclerVH
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/holders/EmptyViewHolder.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/holders/EmptyViewHolder.kt
deleted file mode 100644
index ad78d7e..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/holders/EmptyViewHolder.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.mdgd.pokemon.ui.pokemon.adapter.holders
-
-import android.view.View
-import com.mdgd.pokemon.ui.adapter.RecyclerVH
-import com.mdgd.pokemon.ui.pokemon.infra.PokemonProperty
-
-class EmptyViewHolder(itemView: View) : RecyclerVH(itemView)
\ No newline at end of file
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/holders/PokemonImageViewHolder.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/holders/PokemonImageViewHolder.kt
deleted file mode 100644
index 4130862..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/holders/PokemonImageViewHolder.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.mdgd.pokemon.ui.pokemon.adapter.holders
-
-import android.text.TextUtils
-import android.view.View
-import android.widget.ImageView
-import com.mdgd.pokemon.R
-import com.mdgd.pokemon.ui.adapter.RecyclerVH
-import com.mdgd.pokemon.ui.pokemon.infra.ImageProperty
-import com.squareup.picasso.Picasso
-
-class PokemonImageViewHolder(view: View) : RecyclerVH(view) {
- private val image: ImageView = view.findViewById(R.id.pokemon_details_image)
-
- override fun bindItem(item: ImageProperty, position: Int) {
- if (!TextUtils.isEmpty(item.imageUrl)) {
- Picasso.get().load(item.imageUrl).into(image)
- }
- }
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/holders/PokemonLabelViewHolder.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/holders/PokemonLabelViewHolder.kt
deleted file mode 100644
index 01e62de..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/holders/PokemonLabelViewHolder.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.mdgd.pokemon.ui.pokemon.adapter.holders
-
-import android.view.View
-import android.widget.TextView
-import com.mdgd.pokemon.R
-import com.mdgd.pokemon.ui.adapter.RecyclerVH
-import com.mdgd.pokemon.ui.pokemon.infra.LabelProperty
-
-class PokemonLabelViewHolder(view: View) : RecyclerVH(view) {
- private val label: TextView = view.findViewById(R.id.pokemon_details_label_text)
- private val value: TextView = view.findViewById(R.id.pokemon_details_label_value)
-
- override fun bindItem(item: LabelProperty, position: Int) {
- label.setPaddingRelative(
- label.resources.getDimensionPixelSize(R.dimen.pokemon_details_nesting_level_padding) * (1 + item.nestingLevel),
- 0, 0, 0)
-
- if (item.titleResId == 0) {
- label.text = item.titleStr
- } else {
- label.setText(item.titleResId)
- }
- value.text = item.text
- }
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/holders/PokemonTextViewHolder.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/holders/PokemonTextViewHolder.kt
deleted file mode 100644
index 18be12e..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/holders/PokemonTextViewHolder.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.mdgd.pokemon.ui.pokemon.adapter.holders
-
-import android.view.View
-import android.widget.TextView
-import com.mdgd.pokemon.R
-import com.mdgd.pokemon.ui.adapter.RecyclerVH
-import com.mdgd.pokemon.ui.pokemon.infra.TextProperty
-
-class PokemonTextViewHolder(view: View) : RecyclerVH(view) {
- private val label: TextView = view.findViewById(R.id.pokemon_details_text)
-
- override fun bindItem(item: TextProperty, position: Int) {
- label.text = item.text
- label.setPaddingRelative(
- label.resources.getDimensionPixelSize(R.dimen.pokemon_details_nesting_level_padding) * (2 + item.nestingLevel),
- label.paddingTop, label.paddingEnd, label.paddingBottom)
- }
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/holders/PokemonTitleViewHolder.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/holders/PokemonTitleViewHolder.kt
deleted file mode 100644
index 4cee724..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/adapter/holders/PokemonTitleViewHolder.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.mdgd.pokemon.ui.pokemon.adapter.holders
-
-import android.graphics.Typeface
-import android.view.Gravity
-import android.view.View
-import android.widget.TextView
-import com.mdgd.pokemon.R
-import com.mdgd.pokemon.ui.adapter.RecyclerVH
-import com.mdgd.pokemon.ui.pokemon.infra.TitleProperty
-
-class PokemonTitleViewHolder(view: View) : RecyclerVH(view) {
- private val title: TextView = view.findViewById(R.id.pokemon_property_title)
-
- override fun bindItem(item: TitleProperty, position: Int) {
- if (item.titleResId != 0) {
- title.setText(item.titleResId)
- }
- if (item.nestingLevel == 0) {
- title.typeface = Typeface.DEFAULT_BOLD
- title.gravity = Gravity.CENTER
- title.setPaddingRelative(0, 0, 0, 0)
- } else {
- title.typeface = Typeface.SERIF
- title.gravity = Gravity.NO_GRAVITY
- title.setPaddingRelative(
- title.resources.getDimensionPixelSize(R.dimen.pokemon_details_nesting_level_padding) * (2 + item.nestingLevel),
- 0, 0, 0)
- }
- }
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/ImageProperty.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/ImageProperty.kt
similarity index 63%
rename from app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/ImageProperty.kt
rename to app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/ImageProperty.kt
index 32e661d..d1234d3 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/ImageProperty.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/ImageProperty.kt
@@ -1,4 +1,4 @@
-package com.mdgd.pokemon.ui.pokemon.infra
+package com.mdgd.pokemon.ui.pokemon.dto
interface ImageProperty : PokemonProperty {
val imageUrl: String
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/ImagePropertyData.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/ImagePropertyData.kt
similarity index 50%
rename from app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/ImagePropertyData.kt
rename to app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/ImagePropertyData.kt
index 73aa3d0..9c0008d 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/ImagePropertyData.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/ImagePropertyData.kt
@@ -1,6 +1,6 @@
-package com.mdgd.pokemon.ui.pokemon.infra
+package com.mdgd.pokemon.ui.pokemon.dto
class ImagePropertyData(override val imageUrl: String) : ImageProperty {
override val type: Int
- get() = PokemonProperty.Companion.PROPERTY_IMAGE
+ get() = PokemonProperty.PROPERTY_IMAGE
}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/LabelProperty.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/LabelProperty.kt
similarity index 68%
rename from app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/LabelProperty.kt
rename to app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/LabelProperty.kt
index 9a05949..6e64e09 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/LabelProperty.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/LabelProperty.kt
@@ -1,4 +1,4 @@
-package com.mdgd.pokemon.ui.pokemon.infra
+package com.mdgd.pokemon.ui.pokemon.dto
interface LabelProperty : TitleProperty {
val text: String
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/LabelPropertyData.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/LabelPropertyData.kt
similarity index 81%
rename from app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/LabelPropertyData.kt
rename to app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/LabelPropertyData.kt
index 66e0b26..75296ea 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/LabelPropertyData.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/LabelPropertyData.kt
@@ -1,4 +1,4 @@
-package com.mdgd.pokemon.ui.pokemon.infra
+package com.mdgd.pokemon.ui.pokemon.dto
class LabelPropertyData : TitlePropertyData, LabelProperty {
override val text: String
@@ -15,5 +15,5 @@ class LabelPropertyData : TitlePropertyData, LabelProperty {
}
override val type: Int
- get() = PokemonProperty.Companion.PROPERTY_LABEL
+ get() = PokemonProperty.PROPERTY_LABEL
}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/PokemonProperty.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/PokemonProperty.kt
similarity index 86%
rename from app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/PokemonProperty.kt
rename to app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/PokemonProperty.kt
index 7a3442d..0e0eb8a 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/PokemonProperty.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/PokemonProperty.kt
@@ -1,4 +1,4 @@
-package com.mdgd.pokemon.ui.pokemon.infra
+package com.mdgd.pokemon.ui.pokemon.dto
interface PokemonProperty {
val type: Int
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/TextProperty.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/TextProperty.kt
similarity index 61%
rename from app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/TextProperty.kt
rename to app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/TextProperty.kt
index cd0c289..7a18248 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/TextProperty.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/TextProperty.kt
@@ -1,4 +1,4 @@
-package com.mdgd.pokemon.ui.pokemon.infra
+package com.mdgd.pokemon.ui.pokemon.dto
interface TextProperty : PokemonProperty {
val text: String
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/TextPropertyData.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/TextPropertyData.kt
similarity index 56%
rename from app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/TextPropertyData.kt
rename to app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/TextPropertyData.kt
index f5c8c2a..19c6b54 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/TextPropertyData.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/TextPropertyData.kt
@@ -1,6 +1,6 @@
-package com.mdgd.pokemon.ui.pokemon.infra
+package com.mdgd.pokemon.ui.pokemon.dto
class TextPropertyData(override val text: String, override val nestingLevel: Int) : TextProperty {
override val type: Int
- get() = PokemonProperty.Companion.PROPERTY_TEXT
+ get() = PokemonProperty.PROPERTY_TEXT
}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/TitleProperty.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/TitleProperty.kt
similarity index 62%
rename from app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/TitleProperty.kt
rename to app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/TitleProperty.kt
index 49bb7b8..2e51cde 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/TitleProperty.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/TitleProperty.kt
@@ -1,4 +1,4 @@
-package com.mdgd.pokemon.ui.pokemon.infra
+package com.mdgd.pokemon.ui.pokemon.dto
interface TitleProperty : PokemonProperty {
val titleResId: Int
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/TitlePropertyData.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/TitlePropertyData.kt
similarity index 83%
rename from app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/TitlePropertyData.kt
rename to app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/TitlePropertyData.kt
index cde3183..8beb262 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/infra/TitlePropertyData.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/dto/TitlePropertyData.kt
@@ -1,4 +1,4 @@
-package com.mdgd.pokemon.ui.pokemon.infra
+package com.mdgd.pokemon.ui.pokemon.dto
open class TitlePropertyData @JvmOverloads constructor(override val titleResId: Int, override val nestingLevel: Int = 0) : TitleProperty {
override val type: Int
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/state/PokemonDetailsScreenAction.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/state/PokemonDetailsScreenAction.kt
deleted file mode 100644
index 3d9aefb..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/state/PokemonDetailsScreenAction.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.mdgd.pokemon.ui.pokemon.state
-
-import com.mdgd.mvi.states.AbstractAction
-import com.mdgd.pokemon.ui.pokemon.PokemonDetailsContract
-
-sealed class PokemonDetailsScreenAction : AbstractAction()
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/state/PokemonDetailsScreenEffect.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/state/PokemonDetailsScreenEffect.kt
new file mode 100644
index 0000000..65300b4
--- /dev/null
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/state/PokemonDetailsScreenEffect.kt
@@ -0,0 +1,13 @@
+package com.mdgd.pokemon.ui.pokemon.state
+
+import com.mdgd.mvi.states.AbstractEffect
+import com.mdgd.pokemon.ui.pokemon.PokemonDetailsContract
+
+sealed class PokemonDetailsScreenEffect : AbstractEffect() {
+
+ class EffectBack : PokemonDetailsScreenEffect() {
+ override fun handle(screen: PokemonDetailsContract.View) {
+ screen.goBack()
+ }
+ }
+}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/state/PokemonDetailsScreenState.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/state/PokemonDetailsScreenState.kt
index 812a57a..e8e5aa6 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemon/state/PokemonDetailsScreenState.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemon/state/PokemonDetailsScreenState.kt
@@ -1,18 +1,20 @@
package com.mdgd.pokemon.ui.pokemon.state
-import com.mdgd.mvi.states.ScreenState
+import com.mdgd.mvi.states.AbstractState
import com.mdgd.pokemon.ui.pokemon.PokemonDetailsContract
-import com.mdgd.pokemon.ui.pokemon.infra.PokemonProperty
+import com.mdgd.pokemon.ui.pokemon.dto.PokemonProperty
-sealed class PokemonDetailsScreenState : ScreenState {
+open class PokemonDetailsScreenState(
+ val items: List
+) : AbstractState() {
- override fun merge(prevState: PokemonDetailsScreenState) {}
+ override fun visit(screen: PokemonDetailsContract.View) {
+ screen.setItems(items)
+ }
+ // PARTIAL STATES
- class SetData(val items: List) : PokemonDetailsScreenState() {
+ class SetData(items: List) : PokemonDetailsScreenState(items)
- override fun visit(screen: PokemonDetailsContract.View) {
- screen.setItems(items)
- }
- }
+ // EOF: PARTIAL STATES
}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsContract.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsContract.kt
index eb2e3e3..2e1d76d 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsContract.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsContract.kt
@@ -2,20 +2,18 @@ package com.mdgd.pokemon.ui.pokemons
import com.mdgd.mvi.fragments.FragmentContract
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonFullDataSchema
-import com.mdgd.pokemon.ui.pokemons.state.PokemonsScreenAction
-import com.mdgd.pokemon.ui.pokemons.state.PokemonsScreenState
class PokemonsContract {
- interface ViewModel : FragmentContract.ViewModel {
+ interface ViewModel : FragmentContract.ViewModel {
fun reload()
- fun loadPage(page: Int)
fun sort(filter: String)
fun onItemClicked(pokemon: PokemonFullDataSchema)
+ fun onScroll(firstVisibleIndex: Int, lastVisibleIndex: Int)
+ fun firstVisible(): Int
}
interface View : FragmentContract.View {
- fun showProgress()
- fun hideProgress()
+ fun setProgressVisibility(isProgressVisible: Boolean)
fun setItems(list: List)
fun showError(error: Throwable?)
fun proceedToNextScreen(pokemonId: Long?)
@@ -25,6 +23,5 @@ class PokemonsContract {
interface Host : FragmentContract.Host {
fun proceedToPokemonScreen(pokemonId: Long?)
- fun showError(error: Throwable?)
}
}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsFragment.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsFragment.kt
index 01a9a6b..cdafd67 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsFragment.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsFragment.kt
@@ -1,167 +1,450 @@
package com.mdgd.pokemon.ui.pokemons
+import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.widget.ImageButton
-import androidx.core.content.ContextCompat
-import androidx.lifecycle.ViewModelProvider
-import androidx.lifecycle.lifecycleScope
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Card
+import androidx.compose.material.FloatingActionButton
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.fragment.app.viewModels
+import coil.compose.rememberImagePainter
+import coil.request.ImageRequest
+import com.google.accompanist.swiperefresh.SwipeRefresh
+import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import com.mdgd.mvi.fragments.HostedFragment
-import com.mdgd.pokemon.PokemonsApp
import com.mdgd.pokemon.R
import com.mdgd.pokemon.models.filters.FilterData
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonFullDataSchema
-import com.mdgd.pokemon.ui.adapter.ClickEvent
-import com.mdgd.pokemon.ui.pokemons.adapter.PokemonsAdapter
-import com.mdgd.pokemon.ui.pokemons.infra.EndlessScrollListener
-import com.mdgd.pokemon.ui.pokemons.state.PokemonsScreenAction
-import com.mdgd.pokemon.ui.pokemons.state.PokemonsScreenState
-import kotlinx.coroutines.flow.collect
+import com.mdgd.pokemon.models.repo.dao.schemas.PokemonSchema
+import com.mdgd.pokemon.models.repo.schemas.Stat
+import com.mdgd.pokemon.models.repo.schemas.Stat_
+import com.mdgd.pokemon.ui.error.ErrorParams
+import com.mdgd.pokemon.ui.error.ErrorScreen
+import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
+@AndroidEntryPoint
class PokemonsFragment : HostedFragment<
PokemonsContract.View,
- PokemonsScreenState,
- PokemonsScreenAction,
PokemonsContract.ViewModel,
PokemonsContract.Host>(),
- PokemonsContract.View, View.OnClickListener, SwipeRefreshLayout.OnRefreshListener {
+ PokemonsContract.View {
- private val adapter = PokemonsAdapter(lifecycleScope)
- private var refreshSwipe: SwipeRefreshLayout? = null
+ private val screenState = mutableStateOf(PokemonsUiState(isLoading = true))
- // maybe paging library?
- private val scrollListener: EndlessScrollListener = object : EndlessScrollListener() {
- override fun onLoadMore(page: Int, totalItemsCount: Int, view: RecyclerView?) {
- model!!.loadPage(page)
- }
+ override fun createModel(): PokemonsContract.ViewModel {
+ val model: PokemonsViewModel by viewModels()
+ return model
}
- private var recyclerView: RecyclerView? = null
- private var filterAttack: ImageButton? = null
- private var filterDefence: ImageButton? = null
- private var filterSpeed: ImageButton? = null
- private var refresh: View? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- lifecycleScope.launch {
- adapter.getItemClickFlow().collect {
- if (it is ClickEvent.ClickData) {
- model!!.onItemClicked(it.data)
- }
- }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
+ ): View {
+ val view = ComposeView(requireContext())
+ view.setContent {
+ PokemonsScreen(screenState, viewModel)
}
+ return view
}
- override fun createModel(): PokemonsContract.ViewModel {
- return ViewModelProvider(this, PokemonsViewModelFactory(PokemonsApp.instance?.appComponent!!)).get(PokemonsViewModel::class.java)
+ override fun proceedToNextScreen(pokemonId: Long?) {
+ fragmentHost?.proceedToPokemonScreen(pokemonId)
}
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- return inflater.inflate(R.layout.fragment_pokemons, container, false)
+ override fun updateFilterButtons(activateFilter: Boolean, filter: String) {
+ val value = when (filter) {
+ FilterData.FILTER_ATTACK -> screenState.value.copy(isAttackActive = activateFilter)
+ FilterData.FILTER_DEFENCE -> screenState.value.copy(isDefenceActive = activateFilter)
+ FilterData.FILTER_SPEED -> screenState.value.copy(isSpeedActive = activateFilter)
+ else -> return
+ }
+ screenState.value = value
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- recyclerView = view.findViewById(R.id.pokemons_recycler)
- recyclerView?.layoutManager = LinearLayoutManager(activity)
- recyclerView?.addOnScrollListener(scrollListener)
- recyclerView?.adapter = adapter
-
- refresh = view.findViewById(R.id.pokemons_refresh)
- refreshSwipe = view.findViewById(R.id.pokemons_swipe_refresh)
- filterAttack = view.findViewById(R.id.pokemons_filter_attack)
- filterDefence = view.findViewById(R.id.pokemons_filter_defence)
- filterSpeed = view.findViewById(R.id.pokemons_filter_movement)
-
- refresh?.setOnClickListener(this)
- refreshSwipe?.setOnRefreshListener(this)
- filterAttack?.setOnClickListener(this)
- filterDefence?.setOnClickListener(this)
- filterSpeed?.setOnClickListener(this)
+ override fun setProgressVisibility(isProgressVisible: Boolean) {
+ screenState.value = screenState.value.copy(isLoading = isProgressVisible)
}
- override fun proceedToNextScreen(pokemonId: Long?) {
- if (hasHost()) {
- fragmentHost!!.proceedToPokemonScreen(pokemonId)
- }
+ override fun setItems(list: List) {
+ screenState.value = screenState.value.copy(
+ isLoading = false, pokemons = list, isVisible = false
+ )
}
- override fun updateFilterButtons(activateFilter: Boolean, filter: String) {
- val view = when (filter) {
- FilterData.FILTER_ATTACK -> {
- filterAttack
- }
- FilterData.FILTER_DEFENCE -> {
- filterDefence
- }
- FilterData.FILTER_SPEED -> {
- filterSpeed
- }
- else -> null
- }
+ override fun scrollToStart() {
+ screenState.value = screenState.value.copy()
+ }
- if (activateFilter) {
- view?.setColorFilter(ContextCompat.getColor(requireContext(), R.color.filter_active))
- } else {
- view?.setColorFilter(ContextCompat.getColor(requireContext(), R.color.filter_inactive))
- }
+ override fun showError(error: Throwable?) {
+ screenState.value = screenState.value.copy(
+ isLoading = false, isVisible = true, title = getString(R.string.dialog_error_title),
+ message = error?.let {
+ getString(R.string.dialog_error_message) + " " + error.message
+ } ?: kotlin.run {
+ getString(R.string.dialog_error_message)
+ })
}
+}
- override fun onClick(view: View) {
- if (view === refresh) {
- if (!refreshSwipe!!.isRefreshing) {
- refreshSwipe!!.isRefreshing = true
- }
- } else {
- when {
- filterAttack === view -> {
- model!!.sort(FilterData.FILTER_ATTACK)
- }
- filterDefence === view -> {
- model!!.sort(FilterData.FILTER_DEFENCE)
+
+@Composable
+fun PokemonsScreen(screenState: MutableState, model: PokemonsContract.ViewModel?) {
+ val errorDialogTrigger = remember { screenState as MutableState }
+ val scope = rememberCoroutineScope()
+ val scrollState = rememberLazyListState()
+ MaterialTheme {
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text(text = stringResource(id = R.string.app_name)) },
+ )
+ },
+ floatingActionButton = {
+ FloatingActionButton(
+ onClick = { model?.reload() },
+ modifier = Modifier.padding(0.dp, 50.dp)
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.ic_refresh),
+ contentDescription = stringResource(R.string.screen_pokemons_refresh)
+ )
}
- filterSpeed === view -> {
- model!!.sort(FilterData.FILTER_SPEED)
+ }
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxHeight()
+ .fillMaxWidth()
+ ) {
+ SwipeRefresh(
+ modifier = Modifier
+ .fillMaxHeight(0.94F)
+ .fillMaxWidth(),
+ state = rememberSwipeRefreshState(screenState.value.isLoading),
+ onRefresh = { model?.reload() },
+ ) {
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxWidth()
+ .fillMaxHeight(),
+ state = scrollState,
+ ) {
+ if (model?.firstVisible() == 0 && scrollState.firstVisibleItemIndex != 0) {
+ scope.launch {
+ scrollState.scrollToItem(0)
+ }
+ }
+ if (scrollState.isScrollInProgress) {
+ model?.onScroll(
+ scrollState.firstVisibleItemIndex,
+ scrollState.layoutInfo.visibleItemsInfo.last().index
+ )
+ }
+
+ if (screenState.value.pokemons.isNullOrEmpty()) {
+ items(items = listOf(System.currentTimeMillis()), key = { it }) {
+ Column(
+ modifier = Modifier
+ .fillParentMaxHeight()
+ .fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ ) {
+ Text(
+ textAlign = TextAlign.Center,
+ text = stringResource(id = R.string.no_pokemons),
+ )
+ }
+ }
+ } else {
+ items(
+ items = screenState.value.pokemons,
+ key = { it.pokemonSchema?.id ?: 0L }
+ ) { PokemonItem(it, model) }
+ }
+ }
}
+ BottomBar(screenState, model)
+ ErrorScreen(errorDialogTrigger)
}
}
}
+}
- override fun onRefresh() {
- scrollListener.resetState()
- model!!.reload()
+@Composable
+fun PokemonItem(item: PokemonFullDataSchema, model: PokemonsContract.ViewModel?) {
+ var attackVal = "--"
+ var defenceVal = "--"
+ var speedVal = "--"
+ for (s in item.stats) {
+ s.stat?.name?.let {
+ when (it) {
+ FilterData.FILTER_DEFENCE -> defenceVal = s.baseStat.toString()
+ FilterData.FILTER_ATTACK -> attackVal = s.baseStat.toString()
+ FilterData.FILTER_SPEED -> speedVal = s.baseStat.toString()
+ }
+ }
}
-
- override fun showProgress() {
- if (refreshSwipe != null && !refreshSwipe!!.isRefreshing) {
- refreshSwipe!!.isRefreshing = true
+ defenceVal = LocalContext.current.getString(R.string.item_pokemon_defence, defenceVal)
+ attackVal = LocalContext.current.getString(R.string.item_pokemon_attack, attackVal)
+ speedVal = LocalContext.current.getString(R.string.item_pokemon_speed, speedVal)
+ Card(
+ shape = RoundedCornerShape(3.dp),
+ elevation = 5.dp,
+ modifier = Modifier
+ .clickable { model?.onItemClicked(item) }
+ .background(color = Color.Cyan)
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(5.dp)
+ ) {
+ Image(
+ painter = item.pokemonSchema?.sprites?.other?.officialArtwork?.frontDefault?.let {
+ rememberImagePainter(
+ data = it,
+ builder = {
+ ImageRequest.Builder(LocalContext.current)
+ .placeholder(R.drawable.ic_pokemon)
+ .error(R.drawable.ic_pokemon)
+ .build()
+ })
+ } ?: kotlin.run {
+ painterResource(R.drawable.ic_pokemon)
+ },
+ contentDescription = stringResource(id = R.string.screen_pokemons_icon),
+ modifier = Modifier
+ .fillMaxWidth(0.3F)
+ .aspectRatio(1F),
+ contentScale = ContentScale.Inside
+ )
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ ) {
+ Text(
+ style = TextStyle(fontWeight = FontWeight.Bold, textAlign = TextAlign.Center),
+ modifier = Modifier
+ .padding(5.dp)
+ .fillMaxHeight(0.4F),
+ text = item.pokemonSchema?.name ?: "",
+ )
+ Row(modifier = Modifier.fillMaxWidth()) {
+ Text(
+ style = TextStyle(textAlign = TextAlign.Center),
+ modifier = Modifier.weight(1F),
+ text = attackVal,
+ maxLines = 2
+ )
+ Text(
+ style = TextStyle(textAlign = TextAlign.Center),
+ modifier = Modifier.weight(1F),
+ text = defenceVal,
+ maxLines = 2
+ )
+ Text(
+ style = TextStyle(textAlign = TextAlign.Center),
+ modifier = Modifier.weight(1F),
+ text = speedVal,
+ maxLines = 2
+ )
+ }
+ }
}
}
+}
- override fun hideProgress() {
- if (refreshSwipe != null && refreshSwipe!!.isRefreshing) {
- refreshSwipe!!.isRefreshing = false
+@Composable
+fun BottomBar(screenState: MutableState, model: PokemonsContract.ViewModel?) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .fillMaxHeight()
+ .padding(5.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ IconButton(
+ onClick = { model?.sort(FilterData.FILTER_ATTACK) },
+ modifier = Modifier.weight(1F),
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.ic_attack),
+ contentDescription = stringResource(R.string.screen_filter_attack),
+ tint = colorResource(
+ id = if (screenState.value.isAttackActive) {
+ R.color.filter_active
+ } else {
+ R.color.filter_inactive
+ }
+ )
+ )
+ }
+ IconButton(
+ onClick = { model?.sort(FilterData.FILTER_DEFENCE) },
+ modifier = Modifier.weight(1F),
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.ic_defense),
+ contentDescription = stringResource(R.string.screen_filter_defence),
+ modifier = Modifier.weight(1F),
+ tint = colorResource(
+ id = if (screenState.value.isDefenceActive) {
+ R.color.filter_active
+ } else {
+ R.color.filter_inactive
+ }
+ ),
+ )
+ }
+ IconButton(
+ onClick = { model?.sort(FilterData.FILTER_SPEED) },
+ modifier = Modifier.weight(1F),
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.ic_speed),
+ contentDescription = stringResource(R.string.screen_filter_speed),
+ modifier = Modifier.weight(1F),
+ tint = colorResource(
+ id = if (screenState.value.isSpeedActive) {
+ R.color.filter_active
+ } else {
+ R.color.filter_inactive
+ }
+ ),
+ )
}
}
+}
- override fun setItems(list: List) {
- adapter.setItems(list)
+
+@Preview(
+ uiMode = Configuration.UI_MODE_NIGHT_YES,
+ showBackground = true,
+ name = "Pokemons Item Dark Mode"
+)
+@Composable
+fun PokemonItemPreviewThemeDark() {
+ val pokemon = PokemonFullDataSchema()
+ pokemon.pokemonSchema = PokemonSchema()
+ pokemon.pokemonSchema?.name = "SlowPock"
+ pokemon.stats = mutableListOf()
+
+ val attack = Stat()
+ attack.stat = Stat_()
+ attack.stat?.name = "attack"
+ attack.baseStat = 100500
+ pokemon.stats.add(attack)
+
+ val defence = Stat()
+ defence.stat = Stat_()
+ defence.stat?.name = "defense"
+ defence.baseStat = 100501
+ pokemon.stats.add(defence)
+
+ val speed = Stat()
+ speed.stat = Stat_()
+ speed.stat?.name = "speed"
+ speed.baseStat = 100502
+ pokemon.stats.add(speed)
+
+ MaterialTheme {
+ PokemonItem(pokemon, null)
}
+}
- override fun scrollToStart() {
- recyclerView!!.scrollToPosition(0)
+@Preview(
+ uiMode = Configuration.UI_MODE_NIGHT_NO,
+ showBackground = true,
+ name = "Pokemons Light Mode"
+)
+@Composable
+fun PokemonsPreviewThemeLight() {
+ val pokemon = PokemonFullDataSchema()
+ pokemon.pokemonSchema = PokemonSchema()
+ pokemon.pokemonSchema?.name = "SlowPock"
+ pokemon.stats = mutableListOf()
+
+ val attack = Stat()
+ attack.stat = Stat_()
+ attack.stat?.name = "attack"
+ attack.baseStat = 100500
+ pokemon.stats.add(attack)
+
+ val defence = Stat()
+ defence.stat = Stat_()
+ defence.stat?.name = "defense"
+ defence.baseStat = 100501
+ pokemon.stats.add(defence)
+
+ val speed = Stat()
+ speed.stat = Stat_()
+ speed.stat?.name = "speed"
+ speed.baseStat = 100502
+ pokemon.stats.add(speed)
+
+ MaterialTheme {
+ val state: MutableState = remember {
+ mutableStateOf(PokemonsUiState(pokemons = listOf(pokemon)))
+ }
+ PokemonsScreen(state, null)
}
+}
- override fun showError(error: Throwable?) {
- if (hasHost()) {
- fragmentHost!!.showError(error)
+@Preview(
+ uiMode = Configuration.UI_MODE_NIGHT_YES,
+ showBackground = true,
+ name = "Pokemons Dark Mode"
+)
+@Composable
+fun PokemonsPreviewThemeDark() {
+ MaterialTheme {
+ val state: MutableState = remember {
+ mutableStateOf(PokemonsUiState())
}
+ PokemonsScreen(state, null)
}
}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsUiState.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsUiState.kt
new file mode 100644
index 0000000..d142ef0
--- /dev/null
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsUiState.kt
@@ -0,0 +1,21 @@
+package com.mdgd.pokemon.ui.pokemons
+
+import com.mdgd.pokemon.models.repo.dao.schemas.PokemonFullDataSchema
+import com.mdgd.pokemon.ui.error.ErrorParams
+
+data class PokemonsUiState(
+ val isLoading: Boolean = false,
+
+ val isAttackActive: Boolean = false,
+ val isDefenceActive: Boolean = false,
+ val isSpeedActive: Boolean = false,
+
+ val pokemons: List = listOf(),
+
+ override val isVisible: Boolean = false,
+ override val title: String = "",
+ override val message: String = ""
+) : ErrorParams {
+
+ override fun hide() = copy(isVisible = false)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsViewModel.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsViewModel.kt
index 3728f1a..f077493 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsViewModel.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsViewModel.kt
@@ -1,66 +1,88 @@
package com.mdgd.pokemon.ui.pokemons
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.viewModelScope
import com.mdgd.mvi.MviViewModel
-import com.mdgd.mvi.util.DispatchersHolder
import com.mdgd.pokemon.models.filters.FilterData
import com.mdgd.pokemon.models.filters.StatsFilter
import com.mdgd.pokemon.models.repo.PokemonsRepo
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonFullDataSchema
-import com.mdgd.pokemon.ui.pokemons.state.PokemonsScreenAction
+import com.mdgd.pokemon.models.util.DispatchersHolder
+import com.mdgd.pokemon.ui.pokemons.state.PokemonsScreenEffect
import com.mdgd.pokemon.ui.pokemons.state.PokemonsScreenState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
-import java.util.*
+import javax.inject.Inject
-class PokemonsViewModel(private val repo: PokemonsRepo, private val filtersFactory: StatsFilter, private val dispatchers: DispatchersHolder)
- : MviViewModel(), PokemonsContract.ViewModel {
+@HiltViewModel
+class PokemonsViewModel @Inject constructor(
+ private val repo: PokemonsRepo,
+ private val filtersFactory: StatsFilter,
+ private val dispatchers: DispatchersHolder
+) : MviViewModel(),
+ PokemonsContract.ViewModel {
+ private var firstVisibleIndex: Int = 0
+ private val exceptionHandler = CoroutineExceptionHandler { _, e ->
+ setEffect(PokemonsScreenEffect.Error(e))
+ }
private val pageFlow = MutableStateFlow(0)
private val filterFlow = MutableStateFlow(FilterData())
private var launch: Job? = null
- public override fun onAny(owner: LifecycleOwner?, event: Lifecycle.Event) {
- super.onAny(owner, event)
+ override fun onStateChanged(event: Lifecycle.Event) {
+ super.onStateChanged(event)
if (event == Lifecycle.Event.ON_CREATE && launch == null) {
- launch = viewModelScope.launch {
+ launch = viewModelScope.launch(exceptionHandler) {
pageFlow
- .onEach { setState(PokemonsScreenState.Loading()) }
- .flowOn(dispatchers.getMain())
- .map { page -> Pair(page, repo.getPage(page)) }
- .flowOn(dispatchers.getIO())
- .catch { e: Throwable -> setAction(PokemonsScreenAction.Error(e)) }
- .collect { pagePair: Pair> ->
- if (pagePair.first == 0) {
- setState(PokemonsScreenState.SetData(pagePair.second, filtersFactory.getAvailableFilters()))
- } else {
- setState(PokemonsScreenState.AddData(pagePair.second))
- }
+ .onEach { setState(PokemonsScreenState.Loading()) }
+ .flowOn(dispatchers.getMain())
+ .map { page -> Pair(page, repo.getPage(page)) }
+ .flowOn(dispatchers.getIO())
+ .catch { e: Throwable -> setEffect(PokemonsScreenEffect.Error(e)) }
+ .collect { pagePair: Pair> ->
+ if (pagePair.first == 0) {
+ setState(
+ PokemonsScreenState.SetData(
+ pagePair.second, filtersFactory.getAvailableFilters()
+ )
+ )
+ } else {
+ setState(PokemonsScreenState.AddData(pagePair.second))
}
+ }
}
- viewModelScope.launch {
+ viewModelScope.launch(exceptionHandler) {
filterFlow
- .map { sort(it, repo.getPokemons()) }
- .collect { sortedList ->
- setState(PokemonsScreenState.UpdateData(sortedList))
- }
+ .map { sort(it, repo.getPokemons()) }
+ .collect { sortedList ->
+ firstVisibleIndex = 0
+ setState(PokemonsScreenState.UpdateData(sortedList))
+ setEffect(PokemonsScreenEffect.ScrollToStart())
+ }
}
}
}
- private fun sort(filters: FilterData, pokemons: List): List {
- // potentially, we can create a custom list of filters in separate model. In UI we can show them in recyclerView
+ private fun sort(
+ filters: FilterData, pokemons: List
+ ): List {
+ val list = ArrayList(pokemons)
if (!filters.isEmpty) {
val comparators = filtersFactory.getFilters()
- Collections.sort(pokemons) { pokemon1: PokemonFullDataSchema?, pokemon2: PokemonFullDataSchema? ->
+ list.sortWith { pokemon1: PokemonFullDataSchema?, pokemon2: PokemonFullDataSchema? ->
var compare = 0
for (filter in filters.filters) {
- compare = comparators[filter]?.compare(pokemon2!!, pokemon1!!)
- ?: 0 // swap, instead of multiply on -1
+ // swap, instead of multiply on -1
+ compare = comparators[filter]?.compare(pokemon2!!, pokemon1!!) ?: 0
if (compare != 0) {
break
}
@@ -68,32 +90,38 @@ class PokemonsViewModel(private val repo: PokemonsRepo, private val filtersFacto
compare
}
}
- return pokemons
+ return list
}
override fun reload() {
- viewModelScope.launch {
- pageFlow.emit(0)
- }
- }
-
- override fun loadPage(page: Int) {
- viewModelScope.launch {
- pageFlow.emit(page)
- }
+ pageFlow.tryEmit(0)
}
override fun sort(filter: String) {
- setState(PokemonsScreenState.ChangeFilterState(filter))
- viewModelScope.launch {
- filterFlow.emit(FilterData(getState()?.getActiveFilters() ?: listOf()))
+ val filters = getState()?.activeFilters?.toMutableList() ?: mutableListOf()
+ if (filters.contains(filter)) {
+ filters.remove(filter)
+ } else {
+ filters.add(filter)
}
+ setState(PokemonsScreenState.ChangeFilterState(filters))
+ filterFlow.tryEmit(FilterData(filters))
}
override fun onItemClicked(pokemon: PokemonFullDataSchema) {
- setAction(PokemonsScreenAction.ShowDetails(pokemon.pokemonSchema?.id))
+ setEffect(PokemonsScreenEffect.ShowDetails(pokemon.pokemonSchema?.id))
+ }
+
+ override fun onScroll(firstVisibleIndex: Int, lastVisibleIndex: Int) {
+ this.firstVisibleIndex = firstVisibleIndex
+ val page = pageFlow.value + 1
+ if (lastVisibleIndex >= page * PokemonsRepo.PAGE_SIZE - 8) {
+ pageFlow.tryEmit(page)
+ }
}
+ override fun firstVisible() = firstVisibleIndex
+
public override fun onCleared() {
launch = null
super.onCleared()
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsViewModelFactory.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsViewModelFactory.kt
deleted file mode 100644
index ad9fe7e..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/PokemonsViewModelFactory.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.mdgd.pokemon.ui.pokemons
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import com.mdgd.mvi.util.DispatchersHolderImpl
-import com.mdgd.pokemon.models.AppModule
-
-class PokemonsViewModelFactory(private val appComponent: AppModule) : ViewModelProvider.NewInstanceFactory() {
-
- override fun create(modelClass: Class): T {
- return if (modelClass == PokemonsViewModel::class.java) {
- PokemonsViewModel(appComponent.getPokemonsRepo(), appComponent.getFiltersFactory(), DispatchersHolderImpl()) as T
- } else super.create(modelClass)
- }
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/adapter/EmptyViewHolder.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/adapter/EmptyViewHolder.kt
deleted file mode 100644
index 18eae11..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/adapter/EmptyViewHolder.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.mdgd.pokemon.ui.pokemons.adapter
-
-import android.view.View
-import com.mdgd.pokemon.models.repo.dao.schemas.PokemonFullDataSchema
-import com.mdgd.pokemon.ui.adapter.RecyclerVH
-
-class EmptyViewHolder(itemView: View) : RecyclerVH(itemView)
\ No newline at end of file
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/adapter/PokemonViewHolder.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/adapter/PokemonViewHolder.kt
deleted file mode 100644
index f638b79..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/adapter/PokemonViewHolder.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.mdgd.pokemon.ui.pokemons.adapter
-
-import android.view.View
-import android.widget.ImageView
-import android.widget.TextView
-import androidx.lifecycle.LifecycleCoroutineScope
-import com.mdgd.pokemon.R
-import com.mdgd.pokemon.models.repo.dao.schemas.PokemonFullDataSchema
-import com.mdgd.pokemon.ui.adapter.ClickEvent
-import com.mdgd.pokemon.ui.adapter.RecyclerVH
-import com.squareup.picasso.Picasso
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.launch
-
-
-class PokemonViewHolder(itemView: View, private val clicksSubject: MutableStateFlow>,
- private val lifecycleScope: LifecycleCoroutineScope)
- : RecyclerVH(itemView), View.OnClickListener {
-
- private val image: ImageView = itemView.findViewById(R.id.item_pokemon_image)
- private val name: TextView = itemView.findViewById(R.id.item_pokemon_name)
- private val attack: TextView = itemView.findViewById(R.id.item_pokemon_attack)
- private val defence: TextView = itemView.findViewById(R.id.item_pokemon_defence)
- private val speed: TextView = itemView.findViewById(R.id.item_pokemon_speed)
- private var item: PokemonFullDataSchema? = null
-
- override fun bindItem(item: PokemonFullDataSchema, position: Int) {
- this.item = item
- val url = item.pokemonSchema!!.sprites!!.other!!.officialArtwork!!.frontDefault
- Picasso.get().load(url).into(image)
- name.text = item.pokemonSchema!!.name
- val resources = itemView.context.resources
- var attackVal = "--"
- for (s in item.stats) {
- if ("attack" == s.stat!!.name) {
- attackVal = s.baseStat.toString()
- }
- }
- attack.text = resources.getString(R.string.item_pokemon_attack, attackVal)
- var defenceVal = "--"
- for (s in item.stats) {
- if ("defense" == s.stat!!.name) {
- defenceVal = s.baseStat.toString()
- }
- }
- defence.text = resources.getString(R.string.item_pokemon_defence, defenceVal)
- var speedVal = "--"
- for (s in item.stats) {
- if ("speed" == s.stat!!.name) {
- speedVal = s.baseStat.toString()
- }
- }
- speed.text = resources.getString(R.string.item_pokemon_speed, speedVal)
- }
-
- override fun onClick(view: View) {
- if (item != null) {
- lifecycleScope.launch {
- clicksSubject.emit(ClickEvent.ClickData(item!!))
- }
- }
- }
-
- override fun setupSubscriptions() {
- itemView.setOnClickListener(this)
- }
-
- override fun clearSubscriptions() {
- itemView.setOnClickListener(null)
- }
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/adapter/PokemonsAdapter.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/adapter/PokemonsAdapter.kt
deleted file mode 100644
index edf5fe3..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/adapter/PokemonsAdapter.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.mdgd.pokemon.ui.pokemons.adapter
-
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import androidx.lifecycle.LifecycleCoroutineScope
-import com.mdgd.pokemon.R
-import com.mdgd.pokemon.models.repo.dao.schemas.PokemonFullDataSchema
-import com.mdgd.pokemon.ui.adapter.RecyclerAdapter
-import com.mdgd.pokemon.ui.adapter.RecyclerVH
-
-class PokemonsAdapter(private val lifecycleScope: LifecycleCoroutineScope) : RecyclerAdapter() {
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerVH {
- if (viewType == EMPTY_VIEW) {
- return EmptyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_empty, parent, false))
- }
- return PokemonViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_pokemon, parent, false), clicksSubject, lifecycleScope)
- }
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/infra/EndlessScrollListener.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/infra/EndlessScrollListener.kt
deleted file mode 100644
index 0c106ac..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/infra/EndlessScrollListener.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-package com.mdgd.pokemon.ui.pokemons.infra
-
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-
-abstract class EndlessScrollListener : RecyclerView.OnScrollListener {
- // The minimum amount of items to have below your current scroll position
- // before loading more.
- private val visibleThreshold = 5
-
- // The current offset index of data you have loaded
- private var currentPage = 0
-
- // The total number of items in the dataset after the last load
- private var previousTotalItemCount = 0
-
- // True if we are still waiting for the last set of data to load.
- private var loading = true
-
- // Sets the starting page index
- private val startingPageIndex = 0
- private var reverseDirection = false
-
- internal constructor() {}
- constructor(reverseDirection: Boolean) {
- this.reverseDirection = reverseDirection
- }
-
- override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
- super.onScrolled(recyclerView, dx, dy)
-
- //only trigger action if scrolling down
- if (if (!reverseDirection) dy > 0 else dy < 0) {
- val lm = recyclerView.layoutManager
- val totalItemCount = lm!!.itemCount
- val lastVisibleItemPosition = (lm as LinearLayoutManager?)!!.findLastVisibleItemPosition()
-
- // If the total item count is zero and the previous isn't, assume the
- // list is invalidated and should be reset back to initial state
- if (totalItemCount < previousTotalItemCount) {
- currentPage = startingPageIndex
- previousTotalItemCount = totalItemCount
- if (totalItemCount == 0) {
- loading = true
- }
- }
-
- // If it’s still loading, we check to see if the dataset count has
- // changed, if so we conclude it has finished loading and update the current page
- // number and total item count.
- if (loading && totalItemCount > previousTotalItemCount) {
- loading = false
- previousTotalItemCount = totalItemCount
- }
-
- // If it isn’t currently loading, we check to see if we have breached
- // the visibleThreshold and need to reload more data.
- // If we do need to reload some more data, we execute onLoadMore to fetch the data.
- // threshold should reflect how many total columns there are too
- if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) {
- currentPage++
- onLoadMore(currentPage, totalItemCount, recyclerView)
- loading = true
- }
- }
- }
-
- // Call this method whenever performing new searches
- fun resetState() {
- currentPage = startingPageIndex
- previousTotalItemCount = 0
- loading = true
- }
-
- // Defines the process for actually loading more data based on page
- abstract fun onLoadMore(page: Int, totalItemsCount: Int, view: RecyclerView?)
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/state/PokemonsScreenAction.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/state/PokemonsScreenAction.kt
deleted file mode 100644
index d842543..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/state/PokemonsScreenAction.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.mdgd.pokemon.ui.pokemons.state
-
-import com.mdgd.mvi.states.AbstractAction
-import com.mdgd.pokemon.ui.pokemons.PokemonsContract
-
-sealed class PokemonsScreenAction() : AbstractAction() {
-
- class Error(val error: Throwable?) : PokemonsScreenAction() {
-
- override fun handle(screen: PokemonsContract.View) {
- screen.hideProgress()
- screen.showError(error)
- }
- }
-
- class ShowDetails(val id: Long?) : PokemonsScreenAction() {
-
- override fun handle(screen: PokemonsContract.View) {
- screen.hideProgress()
- screen.proceedToNextScreen(id)
- }
- }
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/state/PokemonsScreenEffect.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/state/PokemonsScreenEffect.kt
new file mode 100644
index 0000000..ed2865e
--- /dev/null
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/state/PokemonsScreenEffect.kt
@@ -0,0 +1,30 @@
+package com.mdgd.pokemon.ui.pokemons.state
+
+import com.mdgd.mvi.states.AbstractEffect
+import com.mdgd.pokemon.ui.pokemons.PokemonsContract
+
+sealed class PokemonsScreenEffect : AbstractEffect() {
+
+ class Error(val error: Throwable?) : PokemonsScreenEffect() {
+
+ override fun handle(screen: PokemonsContract.View) {
+ screen.setProgressVisibility(false)
+ screen.showError(error)
+ }
+ }
+
+ class ShowDetails(val id: Long?) : PokemonsScreenEffect() {
+
+ override fun handle(screen: PokemonsContract.View) {
+ screen.setProgressVisibility(false)
+ screen.proceedToNextScreen(id)
+ }
+ }
+
+ class ScrollToStart : PokemonsScreenEffect() {
+
+ override fun handle(screen: PokemonsContract.View) {
+ screen.scrollToStart()
+ }
+ }
+}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/state/PokemonsScreenState.kt b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/state/PokemonsScreenState.kt
index 5a1505d..ed0851a 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/pokemons/state/PokemonsScreenState.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/pokemons/state/PokemonsScreenState.kt
@@ -1,31 +1,18 @@
package com.mdgd.pokemon.ui.pokemons.state
-import com.mdgd.mvi.states.ScreenState
+import com.mdgd.mvi.states.AbstractState
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonFullDataSchema
import com.mdgd.pokemon.ui.pokemons.PokemonsContract
-sealed class PokemonsScreenState(
- private val isProgressVisible: Boolean = false,
- protected val list: MutableList = mutableListOf(),
- protected val availableFilters: MutableList = mutableListOf(),
- protected val activeFilters: MutableList = mutableListOf())
- : ScreenState {
-
- fun getItems(): List {
- return ArrayList(list)
- }
-
- @JvmName("getActiveFilters1")
- fun getActiveFilters(): List {
- return ArrayList(activeFilters)
- }
+open class PokemonsScreenState(
+ val isProgressVisible: Boolean = false,
+ val list: List = listOf(),
+ protected val availableFilters: List = listOf(),
+ val activeFilters: List = listOf()
+) : AbstractState() {
override fun visit(screen: PokemonsContract.View) {
- if (isProgressVisible) {
- screen.showProgress()
- } else {
- screen.hideProgress()
- }
+ screen.setProgressVisibility(isProgressVisible)
screen.setItems(list)
for (filter in availableFilters) {
screen.updateFilterButtons(activeFilters.contains(filter), filter)
@@ -33,58 +20,60 @@ sealed class PokemonsScreenState(
}
- class Loading() : PokemonsScreenState(true) {
+ // PARTIAL STATES
+ class Loading : PokemonsScreenState(true) {
- override fun merge(prevState: PokemonsScreenState) {
- list.addAll(prevState.list)
- availableFilters.addAll(prevState.availableFilters)
- activeFilters.addAll(prevState.activeFilters)
+ override fun merge(prevState: PokemonsScreenState): PokemonsScreenState {
+ return PokemonsScreenState(
+ isProgressVisible, prevState.list,
+ prevState.availableFilters, prevState.activeFilters
+ )
}
}
- class SetData(list: List, availableFilters: List)
- : PokemonsScreenState(false, ArrayList(list), ArrayList(availableFilters)) {
+ class SetData(
+ list: List, availableFilters: List
+ ) : PokemonsScreenState(false, list, availableFilters) {
- override fun merge(prevState: PokemonsScreenState) {
- activeFilters.addAll(prevState.activeFilters)
+ override fun merge(prevState: PokemonsScreenState): PokemonsScreenState {
+ return PokemonsScreenState(
+ isProgressVisible, list, availableFilters, prevState.activeFilters
+ )
}
}
- class AddData(list: List) : PokemonsScreenState(false, ArrayList(list)) {
+ class AddData(
+ list: List
+ ) : PokemonsScreenState(false, list) {
- override fun merge(prevState: PokemonsScreenState) {
- list.addAll(0, prevState.list)
- availableFilters.addAll(prevState.availableFilters)
- activeFilters.addAll(prevState.activeFilters)
+ override fun merge(prevState: PokemonsScreenState): PokemonsScreenState {
+ val items = list.toMutableList()
+ items.addAll(0, prevState.list)
+ return PokemonsScreenState(
+ isProgressVisible, items, prevState.availableFilters, prevState.activeFilters
+ )
}
}
- class UpdateData(items: List)
- : PokemonsScreenState(false, ArrayList(items)) {
+ class UpdateData(
+ items: List
+ ) : PokemonsScreenState(false, items) {
- override fun merge(prevState: PokemonsScreenState) {
- availableFilters.addAll(prevState.availableFilters)
- activeFilters.addAll(prevState.activeFilters)
- }
-
- override fun visit(screen: PokemonsContract.View) {
- super.visit(screen)
- screen.scrollToStart()
+ override fun merge(prevState: PokemonsScreenState): PokemonsScreenState {
+ return PokemonsScreenState(
+ isProgressVisible, list, prevState.availableFilters, prevState.activeFilters
+ )
}
}
- class ChangeFilterState(private val filter: String) : PokemonsScreenState() {
+ class ChangeFilterState(filters: List) : PokemonsScreenState(activeFilters = filters) {
- override fun merge(prevState: PokemonsScreenState) {
- list.addAll(prevState.list)
- availableFilters.addAll(prevState.availableFilters)
- activeFilters.addAll(prevState.activeFilters)
-
- if (activeFilters.contains(filter)) {
- activeFilters.remove(filter)
- } else {
- activeFilters.add(filter)
- }
+ override fun merge(prevState: PokemonsScreenState): PokemonsScreenState {
+ return PokemonsScreenState(
+ isProgressVisible, prevState.list, prevState.availableFilters, activeFilters
+ )
}
}
+
+ // EOF: PARTIAL STATES
}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/splash/SplashContract.kt b/app/src/main/java/com/mdgd/pokemon/ui/splash/SplashContract.kt
index e3cd178..834aff5 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/splash/SplashContract.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/splash/SplashContract.kt
@@ -1,8 +1,6 @@
package com.mdgd.pokemon.ui.splash
import com.mdgd.mvi.fragments.FragmentContract
-import com.mdgd.pokemon.ui.splash.state.SplashScreenAction
-import com.mdgd.pokemon.ui.splash.state.SplashScreenState
class SplashContract {
companion object {
@@ -10,7 +8,7 @@ class SplashContract {
}
- interface ViewModel : FragmentContract.ViewModel
+ interface ViewModel : FragmentContract.ViewModel
interface View : FragmentContract.View {
fun proceedToNextScreen()
@@ -20,6 +18,5 @@ class SplashContract {
interface Host : FragmentContract.Host {
fun proceedToPokemonsScreen()
- fun showError(error: Throwable?)
}
}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/splash/SplashFragment.kt b/app/src/main/java/com/mdgd/pokemon/ui/splash/SplashFragment.kt
index c78de0d..8dd5730 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/splash/SplashFragment.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/splash/SplashFragment.kt
@@ -1,46 +1,138 @@
package com.mdgd.pokemon.ui.splash
+import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.lifecycle.ViewModelProvider
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.fragment.app.viewModels
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
-import androidx.work.WorkRequest
import com.mdgd.mvi.fragments.HostedFragment
-import com.mdgd.pokemon.PokemonsApp
import com.mdgd.pokemon.R
import com.mdgd.pokemon.bg.UploadWorker
-import com.mdgd.pokemon.ui.splash.state.SplashScreenAction
-import com.mdgd.pokemon.ui.splash.state.SplashScreenState
+import com.mdgd.pokemon.ui.error.DefaultErrorParams
+import com.mdgd.pokemon.ui.error.ErrorParams
+import com.mdgd.pokemon.ui.error.ErrorScreen
+import dagger.hilt.android.AndroidEntryPoint
-class SplashFragment : HostedFragment(), SplashContract.View {
+@AndroidEntryPoint
+class SplashFragment :
+ HostedFragment(),
+ SplashContract.View {
+
+ private val errorDialogTrigger = mutableStateOf(DefaultErrorParams())
override fun createModel(): SplashContract.ViewModel {
- return ViewModelProvider(this, SplashViewModelFactory(PokemonsApp.instance?.appComponent!!)).get(SplashViewModel::class.java)
+ val model: SplashViewModel by viewModels()
+ return model
}
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- return inflater.inflate(R.layout.fragment_splash, container, false)
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
+ ): View {
+ val view = ComposeView(requireContext())
+ view.setContent {
+ SplashScreen(errorDialogTrigger)
+ }
+ return view
}
override fun proceedToNextScreen() {
- if (hasHost()) {
- fragmentHost!!.proceedToPokemonsScreen()
- }
+ fragmentHost?.proceedToPokemonsScreen()
}
override fun launchWorker() {
- if (hasHost()) {
- val uploadWorkRequest: WorkRequest = OneTimeWorkRequest.Builder(UploadWorker::class.java).build()
- WorkManager.getInstance(requireContext()).enqueue(uploadWorkRequest)
+ context?.let {
+ WorkManager.getInstance(it)
+ .enqueue(OneTimeWorkRequest.Builder(UploadWorker::class.java).build())
}
}
override fun showError(error: Throwable?) {
- if (hasHost()) {
- fragmentHost!!.showError(error)
+ errorDialogTrigger.value = DefaultErrorParams(
+ true, getString(R.string.dialog_error_title),
+ error?.let {
+ getString(R.string.dialog_error_message) + " " + error.message
+ } ?: kotlin.run {
+ getString(R.string.dialog_error_message)
+ })
+ }
+}
+
+@Composable
+fun SplashScreen(errorParams: MutableState) {
+ val errorDialogTrigger = remember { errorParams }
+ MaterialTheme {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier = Modifier
+ .fillMaxHeight()
+ .fillMaxWidth()
+ // .scrollable(
+ // enabled = true, orientation = Orientation.Vertical,
+ // state = ScrollableState { 0F })
+ ) {
+ Image(
+ painter = painterResource(R.drawable.logo_splash),
+ contentDescription = stringResource(R.string.screen_splash_logo),
+ modifier = Modifier.weight(3F)
+ )
+ Text(
+ style = MaterialTheme.typography.h5,
+ text = stringResource(R.string.screen_splash_advertisement),
+ modifier = Modifier.weight(1F)
+ )
+ ErrorScreen(errorDialogTrigger)
+ }
+ }
+}
+
+
+@Preview(
+ uiMode = Configuration.UI_MODE_NIGHT_NO,
+ showBackground = true,
+ name = "Splash Light Mode"
+)
+@Composable
+fun SplashPreviewThemeLight() {
+ MaterialTheme {
+ val state: MutableState = remember {
+ mutableStateOf(DefaultErrorParams(false))
+ }
+ SplashScreen(state)
+ }
+}
+
+@Preview(
+ uiMode = Configuration.UI_MODE_NIGHT_YES,
+ showBackground = true,
+ name = "Splash Dark Mode"
+)
+@Composable
+fun SplashPreviewThemeDark() {
+ MaterialTheme {
+ val state: MutableState = remember {
+ mutableStateOf(DefaultErrorParams(true))
}
+ SplashScreen(state)
}
}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/splash/SplashViewModel.kt b/app/src/main/java/com/mdgd/pokemon/ui/splash/SplashViewModel.kt
index 4efc655..3a03015 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/splash/SplashViewModel.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/splash/SplashViewModel.kt
@@ -1,38 +1,52 @@
package com.mdgd.pokemon.ui.splash
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.viewModelScope
import com.mdgd.mvi.MviViewModel
import com.mdgd.pokemon.models.cache.Cache
-import com.mdgd.pokemon.ui.splash.state.SplashScreenAction
+import com.mdgd.pokemon.models.infra.Result
+import com.mdgd.pokemon.ui.splash.state.SplashScreenEffect
import com.mdgd.pokemon.ui.splash.state.SplashScreenState
+import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
+import javax.inject.Inject
-class SplashViewModel(private val cache: Cache) : MviViewModel(), SplashContract.ViewModel {
+@HiltViewModel
+class SplashViewModel @Inject constructor(
+ private val cache: Cache
+) : MviViewModel(),
+ SplashContract.ViewModel {
private val exceptionHandler = CoroutineExceptionHandler { _, e ->
- setAction(SplashScreenAction.ShowError(e))
+ setEffect(SplashScreenEffect.ShowError(e))
}
private var progressJob: Job? = null
- public override fun onAny(owner: LifecycleOwner?, event: Lifecycle.Event) {
- super.onAny(owner, event)
+ override fun onStateChanged(event: Lifecycle.Event) {
+ super.onStateChanged(event)
if (event == Lifecycle.Event.ON_START && progressJob == null) {
progressJob = viewModelScope.launch(exceptionHandler) {
- delay(SplashContract.SPLASH_DELAY)
- val value = cache.getProgressChanel().receive()
- if (value.isError()) {
- setAction(SplashScreenAction.ShowError(value.getError()))
- } else if (value.getValue() != 0L) {
- setAction(SplashScreenAction.NextScreen)
+ flow {
+ delay(SplashContract.SPLASH_DELAY)
+ emit(System.currentTimeMillis())
+ }.combine(cache.getProgressChanel()) { _: Long, result: Result ->
+ result
+ // Result(Throwable("Dummy"))
+ }.collect {
+ if (it.isError()) {
+ setEffect(SplashScreenEffect.ShowError(it.getError()))
+ } else if (it.getValue() != 0L) {
+ setEffect(SplashScreenEffect.NextScreen)
+ }
}
}
- setAction(SplashScreenAction.LaunchWorker)
+ setEffect(SplashScreenEffect.LaunchWorker)
}
}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/splash/SplashViewModelFactory.kt b/app/src/main/java/com/mdgd/pokemon/ui/splash/SplashViewModelFactory.kt
deleted file mode 100644
index fdd1fbb..0000000
--- a/app/src/main/java/com/mdgd/pokemon/ui/splash/SplashViewModelFactory.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.mdgd.pokemon.ui.splash
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import com.mdgd.pokemon.models.AppModule
-
-class SplashViewModelFactory(private val appComponent: AppModule) : ViewModelProvider.NewInstanceFactory() {
-
- override fun create(modelClass: Class): T {
- return if (modelClass == SplashViewModel::class.java) {
- SplashViewModel(appComponent.getCache()) as T
- } else super.create(modelClass)
- }
-}
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/splash/state/SplashScreenAction.kt b/app/src/main/java/com/mdgd/pokemon/ui/splash/state/SplashScreenEffect.kt
similarity index 62%
rename from app/src/main/java/com/mdgd/pokemon/ui/splash/state/SplashScreenAction.kt
rename to app/src/main/java/com/mdgd/pokemon/ui/splash/state/SplashScreenEffect.kt
index c761213..1f414c4 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/splash/state/SplashScreenAction.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/splash/state/SplashScreenEffect.kt
@@ -1,26 +1,25 @@
package com.mdgd.pokemon.ui.splash.state
-import com.mdgd.mvi.states.AbstractAction
+import com.mdgd.mvi.states.AbstractEffect
import com.mdgd.pokemon.ui.splash.SplashContract
-sealed class SplashScreenAction : AbstractAction() {
+sealed class SplashScreenEffect : AbstractEffect() {
- class ShowError(val e: Throwable) : SplashScreenAction() {
+ class ShowError(val e: Throwable) : SplashScreenEffect() {
override fun handle(screen: SplashContract.View) {
screen.showError(e)
}
}
-
- object LaunchWorker : SplashScreenAction() {
+ object LaunchWorker : SplashScreenEffect() {
override fun handle(screen: SplashContract.View) {
screen.launchWorker()
}
}
- object NextScreen : SplashScreenAction() {
+ object NextScreen : SplashScreenEffect() {
override fun handle(screen: SplashContract.View) {
screen.proceedToNextScreen()
diff --git a/app/src/main/java/com/mdgd/pokemon/ui/splash/state/SplashScreenState.kt b/app/src/main/java/com/mdgd/pokemon/ui/splash/state/SplashScreenState.kt
index 793e5d0..a2ee06a 100644
--- a/app/src/main/java/com/mdgd/pokemon/ui/splash/state/SplashScreenState.kt
+++ b/app/src/main/java/com/mdgd/pokemon/ui/splash/state/SplashScreenState.kt
@@ -1,11 +1,6 @@
package com.mdgd.pokemon.ui.splash.state
-import com.mdgd.mvi.states.ScreenState
+import com.mdgd.mvi.states.AbstractState
import com.mdgd.pokemon.ui.splash.SplashContract
-sealed class SplashScreenState : ScreenState {
-
- override fun visit(screen: SplashContract.View) {}
-
- override fun merge(prevState: SplashScreenState) {}
-}
+sealed class SplashScreenState : AbstractState()
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
index 7706ab9..2b068d1 100644
--- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -27,4 +27,4 @@
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
-
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_defense.png b/app/src/main/res/drawable/ic_defense.png
deleted file mode 100644
index 60272f9..0000000
Binary files a/app/src/main/res/drawable/ic_defense.png and /dev/null differ
diff --git a/app/src/main/res/drawable/ic_defense.xml b/app/src/main/res/drawable/ic_defense.xml
new file mode 100644
index 0000000..a98c703
--- /dev/null
+++ b/app/src/main/res/drawable/ic_defense.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index c9481f2..e017ab7 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,29 +1,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ app:defaultNavHost="true"
+ app:navGraph="@navigation/navigation_graph" />
diff --git a/app/src/main/res/layout/fragment_pokemon_properties.xml b/app/src/main/res/layout/fragment_pokemon_properties.xml
deleted file mode 100644
index 81965ad..0000000
--- a/app/src/main/res/layout/fragment_pokemon_properties.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
diff --git a/app/src/main/res/layout/fragment_pokemons.xml b/app/src/main/res/layout/fragment_pokemons.xml
deleted file mode 100644
index c330e78..0000000
--- a/app/src/main/res/layout/fragment_pokemons.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/fragment_splash.xml b/app/src/main/res/layout/fragment_splash.xml
deleted file mode 100644
index 27ccd79..0000000
--- a/app/src/main/res/layout/fragment_splash.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/item_empty.xml b/app/src/main/res/layout/item_empty.xml
deleted file mode 100644
index 8436b9c..0000000
--- a/app/src/main/res/layout/item_empty.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/item_pokemon.xml b/app/src/main/res/layout/item_pokemon.xml
deleted file mode 100644
index 28dbf3b..0000000
--- a/app/src/main/res/layout/item_pokemon.xml
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/item_pokemon_image.xml b/app/src/main/res/layout/item_pokemon_image.xml
deleted file mode 100644
index 5a48b56..0000000
--- a/app/src/main/res/layout/item_pokemon_image.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/item_pokemon_label.xml b/app/src/main/res/layout/item_pokemon_label.xml
deleted file mode 100644
index bad2162..0000000
--- a/app/src/main/res/layout/item_pokemon_label.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/item_pokemon_label_image.xml b/app/src/main/res/layout/item_pokemon_label_image.xml
deleted file mode 100644
index a0db2fd..0000000
--- a/app/src/main/res/layout/item_pokemon_label_image.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
diff --git a/app/src/main/res/layout/item_pokemon_title.xml b/app/src/main/res/layout/item_pokemon_title.xml
deleted file mode 100644
index daa2f43..0000000
--- a/app/src/main/res/layout/item_pokemon_title.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 6b78462..eca70cf 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -2,4 +2,4 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index 6b78462..eca70cf 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -2,4 +2,4 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index a571e60..0000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index 61da551..0000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index c41dd28..0000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index db5080a..0000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 6dba46d..0000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index da31a87..0000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 15ac681..0000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index b216f2d..0000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index f25a419..0000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index e96783c..0000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/navigation/navigation_graph.xml b/app/src/main/res/navigation/navigation_graph.xml
index 32688dd..df29971 100644
--- a/app/src/main/res/navigation/navigation_graph.xml
+++ b/app/src/main/res/navigation/navigation_graph.xml
@@ -1,15 +1,13 @@
+ android:label="SplashFragment">
+ android:label="PokemonsFragment">
@@ -30,8 +27,7 @@
+ android:label="PokemonDetailsFragment">
-
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..1f85999
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 30a2b63..f57685a 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,8 +1,13 @@
- #6200EE
- #3700B3
- #03DAC5
- #000
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
+ @color/purple_700
#737373
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0f0ab85..016aab8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -19,4 +19,12 @@
Some error happened
Details:
Oooops!\nThere is no pokemons for some reason
+ splash logo
+ Filter by defence
+ Filter by attack
+ Filter by speed
+ Refresh pokemons list
+ Pokemon\'s icon
+ Pokemon\'s image
+ Back button
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
deleted file mode 100644
index 391ec9a..0000000
--- a/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..1ffbd64
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/app/src/test/java/com/mdgd/pokemon/MainDispatcherRule.kt b/app/src/test/java/com/mdgd/pokemon/MainDispatcherRule.kt
new file mode 100644
index 0000000..a3dbad4
--- /dev/null
+++ b/app/src/test/java/com/mdgd/pokemon/MainDispatcherRule.kt
@@ -0,0 +1,21 @@
+package com.mdgd.pokemon
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+class MainDispatcherRule(
+ private val testDispatcher: CoroutineDispatcher = Dispatchers.Unconfined,
+) : TestWatcher() {
+
+ override fun starting(description: Description) {
+ Dispatchers.setMain(testDispatcher)
+ }
+
+ override fun finished(description: Description) {
+ Dispatchers.resetMain()
+ }
+}
diff --git a/app/src/test/java/com/mdgd/pokemon/TestSuit.kt b/app/src/test/java/com/mdgd/pokemon/TestSuit.kt
index e0201f8..4e803c1 100644
--- a/app/src/test/java/com/mdgd/pokemon/TestSuit.kt
+++ b/app/src/test/java/com/mdgd/pokemon/TestSuit.kt
@@ -3,27 +3,27 @@ package com.mdgd.pokemon
import com.mdgd.pokemon.bg.LoadPokemonsModelTest
import com.mdgd.pokemon.ui.pokemon.PokemonDetailsScreenStateTest
import com.mdgd.pokemon.ui.pokemon.PokemonDetailsViewModelTest
-import com.mdgd.pokemon.ui.pokemons.PokemonsScreenActionTest
+import com.mdgd.pokemon.ui.pokemons.PokemonsScreenEffectTest
import com.mdgd.pokemon.ui.pokemons.PokemonsScreenStateTest
import com.mdgd.pokemon.ui.pokemons.PokemonsViewModelTest
-import com.mdgd.pokemon.ui.splash.SplashScreenActionTest
+import com.mdgd.pokemon.ui.splash.SplashScreenEffectTest
import com.mdgd.pokemon.ui.splash.SplashViewModelTest
import org.junit.runner.RunWith
import org.junit.runners.Suite
@RunWith(Suite::class)
@Suite.SuiteClasses(
- SplashScreenActionTest::class,
- SplashViewModelTest::class,
+ SplashScreenEffectTest::class,
+ SplashViewModelTest::class,
- PokemonDetailsScreenStateTest::class,
- PokemonDetailsViewModelTest::class,
+ PokemonDetailsScreenStateTest::class,
+ PokemonDetailsViewModelTest::class,
- PokemonsScreenStateTest::class,
- PokemonsScreenActionTest::class,
- PokemonsViewModelTest::class,
+ PokemonsScreenStateTest::class,
+ PokemonsScreenEffectTest::class,
+ PokemonsViewModelTest::class,
- LoadPokemonsModelTest::class
+ LoadPokemonsModelTest::class
)
class TestSuit {
companion object {
diff --git a/app/src/test/java/com/mdgd/pokemon/bg/LoadPokemonsModelTest.kt b/app/src/test/java/com/mdgd/pokemon/bg/LoadPokemonsModelTest.kt
index 29706dc..df7c676 100644
--- a/app/src/test/java/com/mdgd/pokemon/bg/LoadPokemonsModelTest.kt
+++ b/app/src/test/java/com/mdgd/pokemon/bg/LoadPokemonsModelTest.kt
@@ -1,14 +1,13 @@
package com.mdgd.pokemon.bg
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import com.mdgd.pokemon.MainDispatcherRule
import com.mdgd.pokemon.TestSuit
import com.mdgd.pokemon.models.cache.Cache
import com.mdgd.pokemon.models.infra.Result
import com.mdgd.pokemon.models.repo.PokemonsRepo
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.test.resetMain
-import kotlinx.coroutines.test.runBlockingTest
-import kotlinx.coroutines.test.setMain
+import com.nhaarman.mockitokotlin2.argumentCaptor
+import kotlinx.coroutines.runBlocking
import org.junit.*
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -16,6 +15,9 @@ import org.mockito.Mockito
@RunWith(JUnit4::class)
class LoadPokemonsModelTest {
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@get:Rule
val rule = InstantTaskExecutorRule()
@@ -25,27 +27,21 @@ class LoadPokemonsModelTest {
@Before
fun setup() {
- Dispatchers.setMain(Dispatchers.Unconfined)
cache = Mockito.mock(Cache::class.java)
repo = Mockito.mock(PokemonsRepo::class.java)
model = LoadPokemonsModel(repo, cache)
}
- @After
- fun tearDown() {
- Dispatchers.resetMain()
- }
-
private fun verifyNoMoreInteractions() {
Mockito.verifyNoMoreInteractions(cache)
Mockito.verifyNoMoreInteractions(repo)
}
@Test
- fun test_LoadPokemonsCrash() = runBlockingTest {
+ fun test_LoadPokemonsCrash() = runBlocking {
val initialLoadingAmount = (PokemonsRepo.PAGE_SIZE * 2).toLong()
val error = RuntimeException("TestError")
- val resultCaptor = com.nhaarman.mockitokotlin2.argumentCaptor>()
+ val resultCaptor = argumentCaptor>()
Mockito.`when`(repo.loadInitialPages(initialLoadingAmount)).thenThrow(error)
model.load()
@@ -60,7 +56,7 @@ class LoadPokemonsModelTest {
}
@Test
- fun test_LoadPokemonsOk() = runBlockingTest {
+ fun test_LoadPokemonsOk() = runBlocking {
val initialLoadingAmount = (PokemonsRepo.PAGE_SIZE * 2).toLong()
val totalLoadingAmount = initialLoadingAmount * 2
Mockito.`when`(repo.loadPokemons(initialLoadingAmount)).thenReturn(totalLoadingAmount)
diff --git a/app/src/test/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsScreenStateTest.kt b/app/src/test/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsScreenStateTest.kt
index c3eebee..d73ac46 100644
--- a/app/src/test/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsScreenStateTest.kt
+++ b/app/src/test/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsScreenStateTest.kt
@@ -1,9 +1,9 @@
package com.mdgd.pokemon.ui.pokemon
-import com.mdgd.pokemon.ui.pokemon.infra.PokemonProperty
+import com.mdgd.pokemon.ui.pokemon.dto.PokemonProperty
import com.mdgd.pokemon.ui.pokemon.state.PokemonDetailsScreenState
import com.nhaarman.mockitokotlin2.argumentCaptor
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.Before
import org.junit.Test
@@ -25,7 +25,7 @@ class PokemonDetailsScreenStateTest {
}
@Test
- fun testSetDataState() = runBlockingTest {
+ fun testSetDataState() = runBlocking {
val detailsCaptor = argumentCaptor>()
val list = ArrayList(0)
PokemonDetailsScreenState.SetData(list).visit(view)
diff --git a/app/src/test/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsViewModelTest.kt b/app/src/test/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsViewModelTest.kt
index 2953d94..8ee1e7b 100644
--- a/app/src/test/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsViewModelTest.kt
+++ b/app/src/test/java/com/mdgd/pokemon/ui/pokemon/PokemonDetailsViewModelTest.kt
@@ -3,20 +3,18 @@ package com.mdgd.pokemon.ui.pokemon
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Observer
+import com.mdgd.mvi.states.ScreenState
+import com.mdgd.pokemon.MainDispatcherRule
import com.mdgd.pokemon.Mocks
import com.mdgd.pokemon.R
import com.mdgd.pokemon.models.repo.PokemonsRepo
-import com.mdgd.pokemon.ui.pokemon.infra.ImagePropertyData
-import com.mdgd.pokemon.ui.pokemon.infra.LabelPropertyData
-import com.mdgd.pokemon.ui.pokemon.infra.TextPropertyData
-import com.mdgd.pokemon.ui.pokemon.infra.TitlePropertyData
-import com.mdgd.pokemon.ui.pokemon.state.PokemonDetailsScreenAction
+import com.mdgd.pokemon.ui.pokemon.dto.ImagePropertyData
+import com.mdgd.pokemon.ui.pokemon.dto.LabelPropertyData
+import com.mdgd.pokemon.ui.pokemon.dto.TextPropertyData
+import com.mdgd.pokemon.ui.pokemon.dto.TitlePropertyData
import com.mdgd.pokemon.ui.pokemon.state.PokemonDetailsScreenState
import com.nhaarman.mockitokotlin2.argumentCaptor
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.test.resetMain
-import kotlinx.coroutines.test.runBlockingTest
-import kotlinx.coroutines.test.setMain
+import kotlinx.coroutines.runBlocking
import org.junit.*
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -24,6 +22,9 @@ import org.mockito.Mockito
@RunWith(JUnit4::class)
class PokemonDetailsViewModelTest {
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@get:Rule
val rule = InstantTaskExecutorRule()
private lateinit var model: PokemonDetailsViewModel
@@ -31,54 +32,52 @@ class PokemonDetailsViewModelTest {
@Before
fun setup() {
- Dispatchers.setMain(Dispatchers.Unconfined)
repo = Mockito.mock(PokemonsRepo::class.java)
model = PokemonDetailsViewModel(repo)
}
- @After
- fun tearDown() {
- Dispatchers.resetMain()
- }
-
private fun verifyNoMoreInteractions() {
Mockito.verifyNoMoreInteractions(repo)
}
@Test
- fun testSetup_NotingHappened() = runBlockingTest {
- val observerMock = Mockito.mock(Observer::class.java) as Observer
+ fun testSetup_NotingHappened() = runBlocking {
+ val observerMock =
+ Mockito.mock(Observer::class.java) as Observer>
model.getStateObservable().observeForever(observerMock)
- val actionObserverMock = Mockito.mock(Observer::class.java) as Observer
- model.getActionObservable().observeForever(actionObserverMock)
+ val actionObserverMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ model.getEffectObservable().observeForever(actionObserverMock)
- model.onAny(null, Lifecycle.Event.ON_START)
- model.onAny(null, Lifecycle.Event.ON_RESUME)
- model.onAny(null, Lifecycle.Event.ON_PAUSE)
- model.onAny(null, Lifecycle.Event.ON_STOP)
- model.onAny(null, Lifecycle.Event.ON_DESTROY)
- model.onAny(null, Lifecycle.Event.ON_ANY)
+ model.onStateChanged(Lifecycle.Event.ON_START)
+ model.onStateChanged(Lifecycle.Event.ON_RESUME)
+ model.onStateChanged(Lifecycle.Event.ON_PAUSE)
+ model.onStateChanged(Lifecycle.Event.ON_STOP)
+ model.onStateChanged(Lifecycle.Event.ON_DESTROY)
+ model.onStateChanged(Lifecycle.Event.ON_ANY)
Mockito.verifyNoMoreInteractions(observerMock)
Mockito.verifyNoMoreInteractions(actionObserverMock)
verifyNoMoreInteractions()
model.getStateObservable().removeObserver(observerMock)
- model.getActionObservable().removeObserver(actionObserverMock)
+ model.getEffectObservable().removeObserver(actionObserverMock)
}
@Test
- fun testSetup_LaunchError() = runBlockingTest {
+ fun testSetup_LaunchError() = runBlocking {
val error = RuntimeException("TestError")
Mockito.`when`(repo.getPokemonById(0)).thenThrow(error)
- val observerMock = Mockito.mock(Observer::class.java) as Observer
+ val observerMock =
+ Mockito.mock(Observer::class.java) as Observer>
model.getStateObservable().observeForever(observerMock)
- val actionObserverMock = Mockito.mock(Observer::class.java) as Observer
- model.getActionObservable().observeForever(actionObserverMock)
+ val actionObserverMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ model.getEffectObservable().observeForever(actionObserverMock)
- model.onAny(null, Lifecycle.Event.ON_CREATE)
+ model.onStateChanged(Lifecycle.Event.ON_CREATE)
model.setPokemonId(0)
Thread.sleep(2000)
@@ -87,23 +86,25 @@ class PokemonDetailsViewModelTest {
Mockito.verifyNoMoreInteractions(actionObserverMock)
verifyNoMoreInteractions()
model.getStateObservable().removeObserver(observerMock)
- model.getActionObservable().removeObserver(actionObserverMock)
+ model.getEffectObservable().removeObserver(actionObserverMock)
}
@Test
- fun testSetup_LaunchOk() = runBlockingTest {
+ fun testSetup_LaunchOk() = runBlocking {
val pokemon = Mocks.getPokemon()
Mockito.`when`(repo.getPokemonById(0)).thenReturn(pokemon)
- val observerMock = Mockito.mock(Observer::class.java) as Observer
+ val observerMock =
+ Mockito.mock(Observer::class.java) as Observer>
val stateCaptor = argumentCaptor()
model.getStateObservable().observeForever(observerMock)
- val actionObserverMock = Mockito.mock(Observer::class.java) as Observer
- model.getActionObservable().observeForever(actionObserverMock)
+ val actionObserverMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ model.getEffectObservable().observeForever(actionObserverMock)
- model.onAny(null, Lifecycle.Event.ON_CREATE)
+ model.onStateChanged(Lifecycle.Event.ON_CREATE)
model.setPokemonId(0)
Thread.sleep(2000)
@@ -201,6 +202,6 @@ class PokemonDetailsViewModelTest {
Mockito.verifyNoMoreInteractions(actionObserverMock)
verifyNoMoreInteractions()
model.getStateObservable().removeObserver(observerMock)
- model.getActionObservable().removeObserver(actionObserverMock)
+ model.getEffectObservable().removeObserver(actionObserverMock)
}
}
diff --git a/app/src/test/java/com/mdgd/pokemon/ui/pokemons/PokemonsScreenActionTest.kt b/app/src/test/java/com/mdgd/pokemon/ui/pokemons/PokemonsScreenEffectTest.kt
similarity index 57%
rename from app/src/test/java/com/mdgd/pokemon/ui/pokemons/PokemonsScreenActionTest.kt
rename to app/src/test/java/com/mdgd/pokemon/ui/pokemons/PokemonsScreenEffectTest.kt
index b093b9c..67a3b66 100644
--- a/app/src/test/java/com/mdgd/pokemon/ui/pokemons/PokemonsScreenActionTest.kt
+++ b/app/src/test/java/com/mdgd/pokemon/ui/pokemons/PokemonsScreenEffectTest.kt
@@ -1,7 +1,7 @@
package com.mdgd.pokemon.ui.pokemons
-import com.mdgd.pokemon.ui.pokemons.state.PokemonsScreenAction
-import kotlinx.coroutines.test.runBlockingTest
+import com.mdgd.pokemon.ui.pokemons.state.PokemonsScreenEffect
+import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -9,7 +9,7 @@ import org.junit.runners.JUnit4
import org.mockito.Mockito
@RunWith(JUnit4::class)
-class PokemonsScreenActionTest {
+class PokemonsScreenEffectTest {
private lateinit var view: PokemonsContract.View
@Before
@@ -22,33 +22,33 @@ class PokemonsScreenActionTest {
}
@Test
- fun test_ErrorState() = runBlockingTest {
+ fun test_ErrorState() = runBlocking {
val error = Throwable("TestError")
- val state = PokemonsScreenAction.Error(error)
+ val state = PokemonsScreenEffect.Error(error)
state.visit(view)
- Mockito.verify(view, Mockito.times(1)).hideProgress()
+ Mockito.verify(view, Mockito.times(1)).setProgressVisibility(false)
Mockito.verify(view, Mockito.times(1)).showError(error)
state.visit(view)
- Mockito.verify(view, Mockito.times(1)).hideProgress()
+ Mockito.verify(view, Mockito.times(1)).setProgressVisibility(false)
verifyNoMoreInteractions()
}
@Test
- fun test_ShowDetailsState() = runBlockingTest {
+ fun test_ShowDetailsState() = runBlocking {
val pokemonId = 1L
- val state = PokemonsScreenAction.ShowDetails(pokemonId)
+ val state = PokemonsScreenEffect.ShowDetails(pokemonId)
state.visit(view)
- Mockito.verify(view, Mockito.times(1)).hideProgress()
+ Mockito.verify(view, Mockito.times(1)).setProgressVisibility(false)
Mockito.verify(view, Mockito.times(1)).proceedToNextScreen(pokemonId)
state.visit(view)
- Mockito.verify(view, Mockito.times(1)).hideProgress()
+ Mockito.verify(view, Mockito.times(1)).setProgressVisibility(false)
verifyNoMoreInteractions()
}
diff --git a/app/src/test/java/com/mdgd/pokemon/ui/pokemons/PokemonsScreenStateTest.kt b/app/src/test/java/com/mdgd/pokemon/ui/pokemons/PokemonsScreenStateTest.kt
index 710fad6..78efeb0 100644
--- a/app/src/test/java/com/mdgd/pokemon/ui/pokemons/PokemonsScreenStateTest.kt
+++ b/app/src/test/java/com/mdgd/pokemon/ui/pokemons/PokemonsScreenStateTest.kt
@@ -4,7 +4,7 @@ import com.mdgd.pokemon.models.filters.FilterData
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonFullDataSchema
import com.mdgd.pokemon.ui.pokemons.state.PokemonsScreenState
import com.nhaarman.mockitokotlin2.argumentCaptor
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.Before
import org.junit.Test
@@ -26,7 +26,7 @@ class PokemonsScreenStateTest {
}
@Test
- fun test_LoadingState() = runBlockingTest {
+ fun test_LoadingState() = runBlocking {
val list = ArrayList()
val filters = ArrayList()
val prevState = PokemonsScreenState.SetData(list, filters)
@@ -36,28 +36,26 @@ class PokemonsScreenStateTest {
state.visit(view)
- Mockito.verify(view, Mockito.times(1)).showProgress()
+ Mockito.verify(view, Mockito.times(1)).setProgressVisibility(true)
Mockito.verify(view, Mockito.times(1)).setItems(list)
verifyNoMoreInteractions()
}
@Test
- fun test_SetDataState() = runBlockingTest {
+ fun test_SetDataState() = runBlocking {
val list = ArrayList()
val filters = ArrayList()
val state = PokemonsScreenState.SetData(list, filters)
-
state.visit(view)
-
- Mockito.verify(view, Mockito.times(1)).hideProgress()
+ Mockito.verify(view, Mockito.times(1)).setProgressVisibility(false)
Mockito.verify(view, Mockito.times(1)).setItems(list)
verifyNoMoreInteractions()
}
@Test
- fun test_AddDataState() = runBlockingTest {
+ fun test_AddDataState() = runBlocking {
val list = ArrayList()
val filters = ArrayList()
val prevState = PokemonsScreenState.SetData(list, filters)
@@ -72,7 +70,7 @@ class PokemonsScreenStateTest {
state.visit(view)
- Mockito.verify(view, Mockito.times(1)).hideProgress()
+ Mockito.verify(view, Mockito.times(1)).setProgressVisibility(false)
Mockito.verify(view, Mockito.times(1)).setItems(listCaptor.capture())
val capturedList = listCaptor.firstValue
Assert.assertEquals(1, capturedList.size)
@@ -82,7 +80,7 @@ class PokemonsScreenStateTest {
}
@Test
- fun test_UpdateDataState() = runBlockingTest {
+ fun test_UpdateDataState() = runBlocking {
val list = ArrayList()
val filters = ArrayList()
val prevState = PokemonsScreenState.SetData(list, filters)
@@ -97,9 +95,8 @@ class PokemonsScreenStateTest {
state.visit(view)
- Mockito.verify(view, Mockito.times(1)).hideProgress()
+ Mockito.verify(view, Mockito.times(1)).setProgressVisibility(false)
Mockito.verify(view, Mockito.times(1)).setItems(listCaptor.capture())
- Mockito.verify(view, Mockito.times(1)).scrollToStart()
val capturedList = listCaptor.firstValue
Assert.assertEquals(1, capturedList.size)
Assert.assertEquals(newList[0], capturedList[0])
@@ -108,27 +105,28 @@ class PokemonsScreenStateTest {
}
@Test
- fun test_ChangeFilterState() = runBlockingTest {
+ fun test_ChangeFilterState() = runBlocking {
val list = ArrayList()
val availableFilters = listOf(FilterData.FILTER_ATTACK, FilterData.FILTER_SPEED)
val prevState = PokemonsScreenState.SetData(list, availableFilters)
- val filter = FilterData.FILTER_ATTACK
+ val filter = listOf(FilterData.FILTER_ATTACK)
val activeStateCaptor = argumentCaptor()
val filterTypeCaptor = argumentCaptor()
- val state = PokemonsScreenState.ChangeFilterState(filter)
+ var state: PokemonsScreenState = PokemonsScreenState.ChangeFilterState(filter)
- state.merge(prevState)
+ state = state.merge(prevState)
state.visit(view)
Mockito.verify(view, Mockito.times(1)).setItems(list)
- Mockito.verify(view, Mockito.times(1)).hideProgress()
- Mockito.verify(view, Mockito.times(2)).updateFilterButtons(activeStateCaptor.capture(), filterTypeCaptor.capture())
+ Mockito.verify(view, Mockito.times(1)).setProgressVisibility(false)
+ Mockito.verify(view, Mockito.times(2))
+ .updateFilterButtons(activeStateCaptor.capture(), filterTypeCaptor.capture())
for (i in 0..1) {
Assert.assertEquals(availableFilters[i], filterTypeCaptor.allValues[i])
- Assert.assertEquals(availableFilters[i] == filter, activeStateCaptor.allValues[i])
+ Assert.assertEquals(availableFilters[i] == filter[0], activeStateCaptor.allValues[i])
}
verifyNoMoreInteractions()
diff --git a/app/src/test/java/com/mdgd/pokemon/ui/pokemons/PokemonsViewModelTest.kt b/app/src/test/java/com/mdgd/pokemon/ui/pokemons/PokemonsViewModelTest.kt
index 529d4d0..bd96c83 100644
--- a/app/src/test/java/com/mdgd/pokemon/ui/pokemons/PokemonsViewModelTest.kt
+++ b/app/src/test/java/com/mdgd/pokemon/ui/pokemons/PokemonsViewModelTest.kt
@@ -3,7 +3,8 @@ package com.mdgd.pokemon.ui.pokemons
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Observer
-import com.mdgd.mvi.util.DispatchersHolder
+import com.mdgd.mvi.states.ScreenState
+import com.mdgd.pokemon.MainDispatcherRule
import com.mdgd.pokemon.Mocks
import com.mdgd.pokemon.TestSuit
import com.mdgd.pokemon.models.filters.FilterData
@@ -12,25 +13,26 @@ import com.mdgd.pokemon.models.repo.PokemonsRepo
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonFullDataSchema
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonSchema
import com.mdgd.pokemon.models.repo.schemas.Stat
+import com.mdgd.pokemon.models.util.DispatchersHolder
import com.mdgd.pokemon.models_impl.filters.StatsFiltersFactory
-import com.mdgd.pokemon.ui.pokemons.state.PokemonsScreenAction
+import com.mdgd.pokemon.ui.pokemons.state.PokemonsScreenEffect
import com.mdgd.pokemon.ui.pokemons.state.PokemonsScreenState
-import com.nhaarman.mockitokotlin2.firstValue
+import com.nhaarman.mockitokotlin2.argumentCaptor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.resetMain
-import kotlinx.coroutines.test.setMain
import org.junit.*
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
import org.mockito.Mockito
@RunWith(JUnit4::class)
class PokemonsViewModelTest {
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
@get:Rule
val rule = InstantTaskExecutorRule()
+
private val PAGE_SIZE = 5
private lateinit var model: PokemonsViewModel
private lateinit var repo: PokemonsRepo
@@ -39,7 +41,6 @@ class PokemonsViewModelTest {
@Before
fun setup() {
- Dispatchers.setMain(Dispatchers.Unconfined)
dispatchers = Mockito.mock(DispatchersHolder::class.java)
Mockito.`when`(dispatchers.getIO()).thenReturn(Dispatchers.Unconfined)
Mockito.`when`(dispatchers.getMain()).thenReturn(Dispatchers.Unconfined)
@@ -49,11 +50,6 @@ class PokemonsViewModelTest {
model = PokemonsViewModel(repo, filtersFactory, dispatchers)
}
- @After
- fun tearDown() {
- Dispatchers.resetMain()
- }
-
private fun verifyNoMoreInteractions() {
Mockito.verifyNoMoreInteractions(repo)
Mockito.verifyNoMoreInteractions(filtersFactory)
@@ -61,26 +57,28 @@ class PokemonsViewModelTest {
@Test
fun testSetup_NotingHappened() = runBlocking {
- val observerMock = Mockito.mock(Observer::class.java) as Observer
+ val observerMock =
+ Mockito.mock(Observer::class.java) as Observer>
model.getStateObservable().observeForever(observerMock)
- val actionObserverMock = Mockito.mock(Observer::class.java) as Observer
- model.getActionObservable().observeForever(actionObserverMock)
+ val actionObserverMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ model.getEffectObservable().observeForever(actionObserverMock)
- model.onAny(null, Lifecycle.Event.ON_START)
- model.onAny(null, Lifecycle.Event.ON_RESUME)
- model.onAny(null, Lifecycle.Event.ON_PAUSE)
- model.onAny(null, Lifecycle.Event.ON_STOP)
- model.onAny(null, Lifecycle.Event.ON_DESTROY)
- model.onAny(null, Lifecycle.Event.ON_ANY)
+ model.onStateChanged(Lifecycle.Event.ON_START)
+ model.onStateChanged(Lifecycle.Event.ON_RESUME)
+ model.onStateChanged(Lifecycle.Event.ON_PAUSE)
+ model.onStateChanged(Lifecycle.Event.ON_STOP)
+ model.onStateChanged(Lifecycle.Event.ON_DESTROY)
+ model.onStateChanged(Lifecycle.Event.ON_ANY)
Mockito.verifyNoMoreInteractions(observerMock)
Mockito.verifyNoMoreInteractions(actionObserverMock)
verifyNoMoreInteractions()
model.getStateObservable().removeObserver(observerMock)
- model.getActionObservable().removeObserver(actionObserverMock)
+ model.getEffectObservable().removeObserver(actionObserverMock)
}
@Test
@@ -90,48 +88,53 @@ class PokemonsViewModelTest {
Mockito.`when`(repo.getPokemons()).thenReturn(ArrayList())
Mockito.`when`(filtersFactory.getAvailableFilters()).thenReturn(listOf())
- val observerMock = Mockito.mock(Observer::class.java) as Observer
- val stateCaptor = ArgumentCaptor.forClass(PokemonsScreenState::class.java)
+ val observerMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ val stateCaptor = argumentCaptor()
model.getStateObservable().observeForever(observerMock)
- val actionObserverMock = Mockito.mock(Observer::class.java) as Observer
- val actionCaptor = ArgumentCaptor.forClass(PokemonsScreenAction::class.java)
- model.getActionObservable().observeForever(actionObserverMock)
+ val actionObserverMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ val actionCaptor = argumentCaptor()
+ model.getEffectObservable().observeForever(actionObserverMock)
- model.onAny(null, Lifecycle.Event.ON_CREATE)
- model.loadPage(0)
+ model.onStateChanged(Lifecycle.Event.ON_CREATE)
+ model.reload()
Thread.sleep(TestSuit.DELAY)
Mockito.verify(observerMock, Mockito.times(2)).onChanged(stateCaptor.capture())
- Mockito.verify(actionObserverMock, Mockito.times(1)).onChanged(actionCaptor.capture())
+ Mockito.verify(actionObserverMock, Mockito.times(2)).onChanged(actionCaptor.capture())
+
var loadingCounter = 0
var updatesCounter = 0
var errorCounter = 0
+ var scrollCounter = 0
for (state in stateCaptor.allValues) {
- when (state) {
- is PokemonsScreenState.Loading -> {
- loadingCounter += 1
- }
- is PokemonsScreenState.UpdateData -> {
- updatesCounter += 1
- Assert.assertTrue(state.getItems().isEmpty())
- }
+ if (state.isProgressVisible) {
+ loadingCounter += 1
+ } else if (state.activeFilters.isEmpty()) {
+ Assert.assertTrue(state.list.isEmpty())
+ updatesCounter += 1
}
}
for (action in actionCaptor.allValues) {
when (action) {
- is PokemonsScreenAction.Error -> {
+ is PokemonsScreenEffect.Error -> {
errorCounter += 1
Assert.assertEquals(error.message, action.error?.message)
}
+
+ is PokemonsScreenEffect.ScrollToStart -> scrollCounter += 1
+ else -> {}
}
}
Assert.assertEquals(1, loadingCounter)
Assert.assertEquals(1, updatesCounter)
Assert.assertEquals(1, errorCounter)
+ Assert.assertEquals(1, scrollCounter)
Mockito.verify(repo, Mockito.times(1)).getPage(0)
Mockito.verify(repo, Mockito.times(1)).getPokemons()
@@ -139,7 +142,7 @@ class PokemonsViewModelTest {
Mockito.verifyNoMoreInteractions(actionObserverMock)
verifyNoMoreInteractions()
model.getStateObservable().removeObserver(observerMock)
- model.getActionObservable().removeObserver(actionObserverMock)
+ model.getEffectObservable().removeObserver(actionObserverMock)
}
@Test
@@ -151,12 +154,14 @@ class PokemonsViewModelTest {
Mockito.`when`(filtersFactory.getAvailableFilters()).thenReturn(listOf())
- val observerMock = Mockito.mock(Observer::class.java) as Observer
+ val observerMock =
+ Mockito.mock(Observer::class.java) as Observer>
model.getStateObservable().observeForever(observerMock)
- val actionObserverMock = Mockito.mock(Observer::class.java) as Observer
- val actionCaptor = ArgumentCaptor.forClass(PokemonsScreenAction::class.java)
- model.getActionObservable().observeForever(actionObserverMock)
+ val actionObserverMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ val actionCaptor = argumentCaptor()
+ model.getEffectObservable().observeForever(actionObserverMock)
model.onItemClicked(pokemon)
@@ -164,24 +169,26 @@ class PokemonsViewModelTest {
Mockito.verify(actionObserverMock, Mockito.times(1)).onChanged(actionCaptor.capture())
val capturedAction = actionCaptor.firstValue
- Assert.assertTrue(capturedAction is PokemonsScreenAction.ShowDetails)
- Assert.assertEquals(testId, (capturedAction as PokemonsScreenAction.ShowDetails).id)
+ Assert.assertTrue(capturedAction is PokemonsScreenEffect.ShowDetails)
+ Assert.assertEquals(testId, (capturedAction as PokemonsScreenEffect.ShowDetails).id)
Mockito.verifyNoMoreInteractions(observerMock)
Mockito.verifyNoMoreInteractions(actionObserverMock)
verifyNoMoreInteractions()
model.getStateObservable().removeObserver(observerMock)
- model.getActionObservable().removeObserver(actionObserverMock)
+ model.getEffectObservable().removeObserver(actionObserverMock)
}
@Test
fun testSetup_Ok() = runBlocking {
- val observerMock = Mockito.mock(Observer::class.java) as Observer
- val stateCaptor = ArgumentCaptor.forClass(PokemonsScreenState::class.java)
+ val observerMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ val stateCaptor = argumentCaptor()
model.getStateObservable().observeForever(observerMock)
- val actionObserverMock = Mockito.mock(Observer::class.java) as Observer
- val actionCaptor = ArgumentCaptor.forClass(PokemonsScreenAction::class.java)
- model.getActionObservable().observeForever(actionObserverMock)
+ val actionObserverMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ val actionCaptor = argumentCaptor()
+ model.getEffectObservable().observeForever(actionObserverMock)
Mockito.`when`(filtersFactory.getAvailableFilters()).thenReturn(listOf())
val pokemons = getPage(0)
@@ -191,28 +198,27 @@ class PokemonsViewModelTest {
}
- model.onAny(null, Lifecycle.Event.ON_CREATE)
- model.loadPage(0)
+ model.onStateChanged(Lifecycle.Event.ON_CREATE)
+ model.reload()
Thread.sleep(TestSuit.DELAY)
Mockito.verify(observerMock, Mockito.times(3)).onChanged(stateCaptor.capture())
+ Mockito.verify(actionObserverMock, Mockito.times(1)).onChanged(actionCaptor.capture())
+ Assert.assertTrue(actionCaptor.firstValue is PokemonsScreenEffect.ScrollToStart)
+
var loadingCounter = 0
var setsCounter = 0
var updatesCounter = 0
for (state in stateCaptor.allValues) {
- when (state) {
- is PokemonsScreenState.Loading -> {
- loadingCounter += 1
- }
- is PokemonsScreenState.SetData -> {
- setsCounter += 1
- Assert.assertEquals(pokemons, state.getItems())
- }
- is PokemonsScreenState.UpdateData -> {
- Assert.assertEquals(pokemons, state.getItems())
- updatesCounter += 1
- }
+ if (state.isProgressVisible) {
+ loadingCounter += 1
+ } else if (setsCounter == 1 && state.list.isNotEmpty() && state.activeFilters.isEmpty()) {
+ Assert.assertEquals(pokemons, state.list)
+ updatesCounter += 1
+ } else if (state.list.isNotEmpty() && state.activeFilters.isEmpty()) {
+ setsCounter += 1
+ Assert.assertEquals(pokemons, state.list)
}
}
Assert.assertEquals(1, loadingCounter)
@@ -226,7 +232,7 @@ class PokemonsViewModelTest {
Mockito.verifyNoMoreInteractions(actionObserverMock)
verifyNoMoreInteractions()
model.getStateObservable().removeObserver(observerMock)
- model.getActionObservable().removeObserver(actionObserverMock)
+ model.getEffectObservable().removeObserver(actionObserverMock)
}
private fun getPage(page: Int): List {
@@ -245,13 +251,15 @@ class PokemonsViewModelTest {
@Test
fun test_NextPageOk() = runBlocking {
- val observerMock = Mockito.mock(Observer::class.java) as Observer
- val stateCaptor = ArgumentCaptor.forClass(PokemonsScreenState::class.java)
+ val observerMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ val stateCaptor = argumentCaptor()
model.getStateObservable().observeForever(observerMock)
- val actionObserverMock = Mockito.mock(Observer::class.java) as Observer
- val actionCaptor = ArgumentCaptor.forClass(PokemonsScreenAction::class.java)
- model.getActionObservable().observeForever(actionObserverMock)
+ val actionObserverMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ val actionCaptor = argumentCaptor()
+ model.getEffectObservable().observeForever(actionObserverMock)
Mockito.`when`(filtersFactory.getAvailableFilters()).thenReturn(listOf())
@@ -269,37 +277,34 @@ class PokemonsViewModelTest {
}
- model.onAny(null, Lifecycle.Event.ON_CREATE)
- model.loadPage(0)
+ model.onStateChanged(Lifecycle.Event.ON_CREATE)
+ model.reload()
Thread.sleep(TestSuit.DELAY)
- model.loadPage(1)
+ model.onScroll(19, 24)
Thread.sleep(TestSuit.DELAY)
Mockito.verify(observerMock, Mockito.times(5)).onChanged(stateCaptor.capture())
+ Mockito.verify(actionObserverMock, Mockito.times(1)).onChanged(actionCaptor.capture())
+ Assert.assertTrue(actionCaptor.firstValue is PokemonsScreenEffect.ScrollToStart)
var loadingCounter = 0
var setsCounter = 0
var addingsCounter = 0
var updatesCounter = 0
for (state in stateCaptor.allValues) {
- when (state) {
- is PokemonsScreenState.Loading -> {
- loadingCounter += 1
- }
- is PokemonsScreenState.SetData -> {
- setsCounter += 1
- Assert.assertEquals(page1, state.getItems())
- }
- is PokemonsScreenState.AddData -> {
- addingsCounter += 1
- Assert.assertEquals(list, state.getItems())
- }
- is PokemonsScreenState.UpdateData -> {
- updatesCounter += 1
- Assert.assertEquals(page1, state.getItems())
- }
+ if (state.isProgressVisible) {
+ loadingCounter += 1
+ } else if (setsCounter != 0 && state.list.isNotEmpty() && state.list == list && state.activeFilters.isEmpty()) {
+ Assert.assertEquals(list, state.list)
+ addingsCounter += 1
+ } else if (setsCounter != 0 && state.list.isNotEmpty() && state.activeFilters.isEmpty()) {
+ Assert.assertEquals(page1, state.list)
+ updatesCounter += 1
+ } else if (state.list.isNotEmpty() && state.activeFilters.isEmpty()) {
+ setsCounter += 1
+ Assert.assertEquals(page1, state.list)
}
}
Assert.assertEquals(2, loadingCounter)
@@ -315,22 +320,25 @@ class PokemonsViewModelTest {
Mockito.verifyNoMoreInteractions(actionObserverMock)
verifyNoMoreInteractions()
model.getStateObservable().removeObserver(observerMock)
- model.getActionObservable().removeObserver(actionObserverMock)
+ model.getEffectObservable().removeObserver(actionObserverMock)
}
@Test
fun test_Filter_Add() = runBlocking {
- Mockito.`when`(filtersFactory.getAvailableFilters()).thenReturn(listOf(FilterData.FILTER_ATTACK, FilterData.FILTER_SPEED))
+ Mockito.`when`(filtersFactory.getAvailableFilters())
+ .thenReturn(listOf(FilterData.FILTER_ATTACK, FilterData.FILTER_SPEED))
Mockito.`when`(filtersFactory.getFilters()).thenReturn(StatsFiltersFactory().getFilters())
val testFilter = FilterData.FILTER_ATTACK
- val observerMock = Mockito.mock(Observer::class.java) as Observer
- val stateCaptor = ArgumentCaptor.forClass(PokemonsScreenState::class.java)
+ val observerMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ val stateCaptor = argumentCaptor()
model.getStateObservable().observeForever(observerMock)
- val actionObserverMock = Mockito.mock(Observer::class.java) as Observer
- val actionCaptor = ArgumentCaptor.forClass(PokemonsScreenAction::class.java)
- model.getActionObservable().observeForever(actionObserverMock)
+ val actionObserverMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ val actionCaptor = argumentCaptor()
+ model.getEffectObservable().observeForever(actionObserverMock)
val page1 = getPage(0)
Mockito.`when`(repo.getPage(0)).then {
@@ -339,8 +347,8 @@ class PokemonsViewModelTest {
}
- model.onAny(null, Lifecycle.Event.ON_CREATE)
- model.loadPage(0)
+ model.onStateChanged(Lifecycle.Event.ON_CREATE)
+ model.reload()
Thread.sleep(TestSuit.DELAY)
model.sort(testFilter)
@@ -348,57 +356,58 @@ class PokemonsViewModelTest {
Mockito.verify(observerMock, Mockito.times(5)).onChanged(stateCaptor.capture())
+ Mockito.verify(actionObserverMock, Mockito.times(2)).onChanged(actionCaptor.capture())
+ for (effect in actionCaptor.allValues) {
+ Assert.assertTrue(effect is PokemonsScreenEffect.ScrollToStart)
+ }
var loadingCounter = 0
var setsCounter = 0
var filtersCounter = 0
var updatesCounter = 0
for (state in stateCaptor.allValues) {
- when (state) {
- is PokemonsScreenState.SetData -> {
- Assert.assertEquals(page1, state.getItems())
- setsCounter += 1
- }
- is PokemonsScreenState.UpdateData -> {
- if (updatesCounter == 0) {
- Assert.assertEquals(page1, state.getItems())
- } else {
- val items = state.getItems()
- Assert.assertEquals(PAGE_SIZE, items.size)
- Assert.assertNotEquals(page1, items)
- for ((idx, item) in items.withIndex()) {
- if (idx == 0) {
- continue
- }
- var prevStat: Stat? = null
- for (ps in items[idx - 1].stats) {
- if (testFilter == ps.stat?.name) {
- prevStat = ps
- break
- }
+ if (state.isProgressVisible) {
+ loadingCounter += 1
+ } else if (filtersCounter == 0 && state.activeFilters.isNotEmpty()) {
+ filtersCounter += 1
+ val items = state.list
+ Assert.assertEquals(PAGE_SIZE, items.size)
+ Assert.assertTrue(state.activeFilters.contains(testFilter))
+ } else if (setsCounter != 0 && state.list.isNotEmpty()
+ && ((updatesCounter == 0 && state.activeFilters.isEmpty()) || state.activeFilters.isNotEmpty())
+ ) {
+ updatesCounter += 1
+ if (updatesCounter == 1) {
+ Assert.assertEquals(page1, state.list)
+ } else {
+ val items = state.list
+ Assert.assertEquals(PAGE_SIZE, items.size)
+ Assert.assertNotEquals(page1, items)
+ for ((idx, item) in items.withIndex()) {
+ if (idx == 0) {
+ continue
+ }
+ var prevStat: Stat? = null
+ for (ps in items[idx - 1].stats) {
+ if (testFilter == ps.stat?.name) {
+ prevStat = ps
+ break
}
+ }
- var stat: Stat? = null
- for (ps in item.stats) {
- if (testFilter == ps.stat?.name) {
- stat = ps
- break
- }
+ var stat: Stat? = null
+ for (ps in item.stats) {
+ if (testFilter == ps.stat?.name) {
+ stat = ps
+ break
}
- Assert.assertTrue(prevStat!!.baseStat!! >= stat!!.baseStat!!)
}
+ Assert.assertTrue(prevStat!!.baseStat!! >= stat!!.baseStat!!)
}
- updatesCounter += 1
- }
- is PokemonsScreenState.ChangeFilterState -> {
- filtersCounter += 1
- val items = state.getItems()
- Assert.assertEquals(PAGE_SIZE, items.size)
- Assert.assertTrue(state.getActiveFilters().contains(testFilter))
- }
- is PokemonsScreenState.Loading -> {
- loadingCounter += 1
}
+ } else if (state.list.isNotEmpty() && state.activeFilters.isEmpty()) {
+ setsCounter += 1
+ Assert.assertEquals(page1, state.list)
}
}
Assert.assertEquals(1, loadingCounter)
@@ -414,22 +423,25 @@ class PokemonsViewModelTest {
Mockito.verifyNoMoreInteractions(actionObserverMock)
verifyNoMoreInteractions()
model.getStateObservable().removeObserver(observerMock)
- model.getActionObservable().removeObserver(actionObserverMock)
+ model.getEffectObservable().removeObserver(actionObserverMock)
}
@Test
fun test_Filter_Remove() = runBlocking {
- Mockito.`when`(filtersFactory.getAvailableFilters()).thenReturn(listOf(FilterData.FILTER_ATTACK, FilterData.FILTER_SPEED))
+ Mockito.`when`(filtersFactory.getAvailableFilters())
+ .thenReturn(listOf(FilterData.FILTER_ATTACK, FilterData.FILTER_SPEED))
Mockito.`when`(filtersFactory.getFilters()).thenReturn(StatsFiltersFactory().getFilters())
val testFilter = FilterData.FILTER_ATTACK
- val observerMock = Mockito.mock(Observer::class.java) as Observer
- val stateCaptor = ArgumentCaptor.forClass(PokemonsScreenState::class.java)
+ val observerMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ val stateCaptor = argumentCaptor()
model.getStateObservable().observeForever(observerMock)
- val actionObserverMock = Mockito.mock(Observer::class.java) as Observer
- val actionCaptor = ArgumentCaptor.forClass(PokemonsScreenAction::class.java)
- model.getActionObservable().observeForever(actionObserverMock)
+ val actionObserverMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ val actionCaptor = argumentCaptor()
+ model.getEffectObservable().observeForever(actionObserverMock)
val page1 = getPage(0)
Mockito.`when`(repo.getPage(0)).then {
@@ -438,8 +450,8 @@ class PokemonsViewModelTest {
}
- model.onAny(null, Lifecycle.Event.ON_CREATE)
- model.loadPage(0)
+ model.onStateChanged(Lifecycle.Event.ON_CREATE)
+ model.reload()
Thread.sleep(TestSuit.DELAY)
model.sort(testFilter)
@@ -450,67 +462,68 @@ class PokemonsViewModelTest {
Mockito.verify(observerMock, Mockito.times(7)).onChanged(stateCaptor.capture())
+ Mockito.verify(actionObserverMock, Mockito.times(3)).onChanged(actionCaptor.capture())
+ for (effect in actionCaptor.allValues) {
+ Assert.assertTrue(effect is PokemonsScreenEffect.ScrollToStart)
+ }
var loadingCounter = 0
var setsCounter = 0
var filtersCounter = 0
var updatesCounter = 0
- for (state in stateCaptor.allValues) {
- when (state) {
- is PokemonsScreenState.Loading -> {
- loadingCounter += 1
- }
- is PokemonsScreenState.SetData -> {
- setsCounter += 1
- Assert.assertEquals(page1, state.getItems())
+ for (idx in stateCaptor.allValues.indices) {
+ val state = stateCaptor.allValues[idx]
+ if (state.isProgressVisible) {
+ loadingCounter += 1
+ } else if (idx == 3 || idx == 5) {
+ filtersCounter += 1
+ val items = state.list
+ Assert.assertEquals(PAGE_SIZE, items.size)
+ if (filtersCounter == 1) {
+ Assert.assertTrue(state.activeFilters.contains(testFilter))
+ } else {
+ Assert.assertFalse(state.activeFilters.contains(testFilter))
}
- is PokemonsScreenState.ChangeFilterState -> {
- val items = state.getItems()
+ } else if (idx == 2 || idx == 4 || idx == 6) {
+ updatesCounter += 1
+ if (updatesCounter == 1) {
+ Assert.assertEquals(page1, state.list)
+ } else if (updatesCounter == 2) {
+ val items = state.list
Assert.assertEquals(PAGE_SIZE, items.size)
- if (filtersCounter == 0) {
- Assert.assertTrue(state.getActiveFilters().contains(testFilter))
- } else {
- Assert.assertFalse(state.getActiveFilters().contains(testFilter))
- }
- filtersCounter += 1
- }
- is PokemonsScreenState.UpdateData -> {
- if (updatesCounter == 0) {
- Assert.assertEquals(page1, state.getItems())
- } else if (updatesCounter == 1) {
- val items = state.getItems()
- Assert.assertNotEquals(page1, items)
- Assert.assertEquals(PAGE_SIZE, items.size)
- for ((idx, item) in items.withIndex()) {
- if (idx == 0) {
- continue
- }
- var prevStat: Stat? = null
- for (ps in items[idx - 1].stats) {
- if (FilterData.FILTER_ATTACK == ps.stat?.name) {
- prevStat = ps
- break
- }
+ Assert.assertNotEquals(page1, items)
+ for ((idx, item) in items.withIndex()) {
+ if (idx == 0) {
+ continue
+ }
+ var prevStat: Stat? = null
+ for (ps in items[idx - 1].stats) {
+ if (testFilter == ps.stat?.name) {
+ prevStat = ps
+ break
}
+ }
- var stat: Stat? = null
- for (ps in item.stats) {
- if (FilterData.FILTER_ATTACK == ps.stat?.name) {
- stat = ps
- break
- }
+ var stat: Stat? = null
+ for (ps in item.stats) {
+ if (testFilter == ps.stat?.name) {
+ stat = ps
+ break
}
- Assert.assertTrue(prevStat!!.baseStat!! >= stat!!.baseStat!!)
}
- } else if (updatesCounter == 2) {
- val items = state.getItems()
- Assert.assertEquals(page1, items)
- Assert.assertEquals(PAGE_SIZE, items.size)
+ Assert.assertTrue(prevStat!!.baseStat!! >= stat!!.baseStat!!)
}
- updatesCounter += 1
+ } else if (updatesCounter == 3) {
+ val items = state.list
+ Assert.assertEquals(page1, items)
+ Assert.assertEquals(PAGE_SIZE, items.size)
}
+ } else if (idx == 1) {
+ setsCounter += 1
+ Assert.assertEquals(page1, state.list)
}
}
+
Assert.assertEquals(1, loadingCounter)
Assert.assertEquals(1, setsCounter)
Assert.assertEquals(2, filtersCounter)
@@ -525,6 +538,6 @@ class PokemonsViewModelTest {
Mockito.verifyNoMoreInteractions(actionObserverMock)
verifyNoMoreInteractions()
model.getStateObservable().removeObserver(observerMock)
- model.getActionObservable().removeObserver(actionObserverMock)
+ model.getEffectObservable().removeObserver(actionObserverMock)
}
}
diff --git a/app/src/test/java/com/mdgd/pokemon/ui/splash/SplashScreenActionTest.kt b/app/src/test/java/com/mdgd/pokemon/ui/splash/SplashScreenEffectTest.kt
similarity index 82%
rename from app/src/test/java/com/mdgd/pokemon/ui/splash/SplashScreenActionTest.kt
rename to app/src/test/java/com/mdgd/pokemon/ui/splash/SplashScreenEffectTest.kt
index d6456f4..712a6cb 100644
--- a/app/src/test/java/com/mdgd/pokemon/ui/splash/SplashScreenActionTest.kt
+++ b/app/src/test/java/com/mdgd/pokemon/ui/splash/SplashScreenEffectTest.kt
@@ -1,6 +1,6 @@
package com.mdgd.pokemon.ui.splash
-import com.mdgd.pokemon.ui.splash.state.SplashScreenAction
+import com.mdgd.pokemon.ui.splash.state.SplashScreenEffect
import org.junit.Assert
import org.junit.Before
import org.junit.Test
@@ -10,7 +10,7 @@ import org.mockito.ArgumentCaptor
import org.mockito.Mockito
@RunWith(JUnit4::class)
-class SplashScreenActionTest {
+class SplashScreenEffectTest {
private lateinit var view: SplashContract.View
@@ -25,7 +25,7 @@ class SplashScreenActionTest {
@Test
fun testLaunchWorkerState() {
- SplashScreenAction.LaunchWorker.visit(view)
+ SplashScreenEffect.LaunchWorker.visit(view)
Mockito.verify(view, Mockito.times(1)).launchWorker()
verifyNoMoreInteractions()
@@ -35,7 +35,7 @@ class SplashScreenActionTest {
fun testErrorState() {
val error = Throwable("TestError")
val errorCaptor = ArgumentCaptor.forClass(Throwable::class.java)
- SplashScreenAction.ShowError(error).visit(view)
+ SplashScreenEffect.ShowError(error).visit(view)
Mockito.verify(view, Mockito.times(1)).showError(errorCaptor.capture())
Assert.assertEquals(error, errorCaptor.value)
@@ -45,7 +45,7 @@ class SplashScreenActionTest {
@Test
fun testProceedToNextState() {
- SplashScreenAction.NextScreen.visit(view)
+ SplashScreenEffect.NextScreen.visit(view)
Mockito.verify(view, Mockito.times(1)).proceedToNextScreen()
verifyNoMoreInteractions()
diff --git a/app/src/test/java/com/mdgd/pokemon/ui/splash/SplashViewModelTest.kt b/app/src/test/java/com/mdgd/pokemon/ui/splash/SplashViewModelTest.kt
index 1b13d4e..119d70f 100644
--- a/app/src/test/java/com/mdgd/pokemon/ui/splash/SplashViewModelTest.kt
+++ b/app/src/test/java/com/mdgd/pokemon/ui/splash/SplashViewModelTest.kt
@@ -3,166 +3,173 @@ package com.mdgd.pokemon.ui.splash
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Observer
+import com.mdgd.mvi.states.ScreenState
+import com.mdgd.pokemon.MainDispatcherRule
import com.mdgd.pokemon.models.cache.Cache
import com.mdgd.pokemon.models.infra.Result
-import com.mdgd.pokemon.ui.splash.state.SplashScreenAction
-import com.mdgd.pokemon.ui.splash.state.SplashScreenState
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.test.resetMain
-import kotlinx.coroutines.test.runBlockingTest
-import kotlinx.coroutines.test.setMain
+import com.mdgd.pokemon.ui.splash.state.SplashScreenEffect
+import com.nhaarman.mockitokotlin2.argumentCaptor
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.*
import org.junit.*
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
import org.mockito.Mockito
@RunWith(JUnit4::class)
class SplashViewModelTest {
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
@get:Rule
val rule = InstantTaskExecutorRule()
+
private lateinit var model: SplashViewModel
private lateinit var cache: Cache
@Before
fun setup() {
- Dispatchers.setMain(Dispatchers.Unconfined)
cache = Mockito.mock(Cache::class.java)
model = SplashViewModel(cache)
}
- @After
- fun tearDown() {
- Dispatchers.resetMain()
- }
-
private fun verifyNoMoreInteractions() {
Mockito.verifyNoMoreInteractions(cache)
}
@Test
- fun testSetup_NotingHappened() = runBlockingTest {
- val stateObserverMock = Mockito.mock(Observer::class.java) as Observer
+ fun testSetup_NotingHappened() = runBlocking {
+ val stateObserverMock =
+ Mockito.mock(Observer::class.java) as Observer>
model.getStateObservable().observeForever(stateObserverMock)
- val actionObserverMock = Mockito.mock(Observer::class.java) as Observer
- model.getActionObservable().observeForever(actionObserverMock)
+ val actionObserverMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ model.getEffectObservable().observeForever(actionObserverMock)
- model.onAny(null, Lifecycle.Event.ON_CREATE)
- model.onAny(null, Lifecycle.Event.ON_RESUME)
- model.onAny(null, Lifecycle.Event.ON_PAUSE)
- model.onAny(null, Lifecycle.Event.ON_STOP)
- model.onAny(null, Lifecycle.Event.ON_DESTROY)
- model.onAny(null, Lifecycle.Event.ON_ANY)
+ model.onStateChanged(Lifecycle.Event.ON_CREATE)
+ model.onStateChanged(Lifecycle.Event.ON_RESUME)
+ model.onStateChanged(Lifecycle.Event.ON_PAUSE)
+ model.onStateChanged(Lifecycle.Event.ON_STOP)
+ model.onStateChanged(Lifecycle.Event.ON_DESTROY)
+ model.onStateChanged(Lifecycle.Event.ON_ANY)
Mockito.verifyNoMoreInteractions(stateObserverMock)
Mockito.verifyNoMoreInteractions(actionObserverMock)
verifyNoMoreInteractions()
model.getStateObservable().removeObserver(stateObserverMock)
- model.getActionObservable().removeObserver(actionObserverMock)
+ model.getEffectObservable().removeObserver(actionObserverMock)
}
@Test
- fun testSetup_LaunchError() = runBlockingTest {
+ fun testSetup_LaunchError() = runBlocking {
val error = Throwable("TestError")
- val progressChanel = Channel>(Channel.Factory.CONFLATED)
+ val progressChanel = MutableSharedFlow>(
+ extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST
+ )
Mockito.`when`(cache.getProgressChanel()).thenReturn(progressChanel)
- val stateObserverMock = Mockito.mock(Observer::class.java) as Observer
+ val stateObserverMock =
+ Mockito.mock(Observer::class.java) as Observer>
model.getStateObservable().observeForever(stateObserverMock)
- val actionObserverMock = Mockito.mock(Observer::class.java) as Observer
- val actionCaptor = ArgumentCaptor.forClass(SplashScreenAction::class.java)
- model.getActionObservable().observeForever(actionObserverMock)
-
+ val actionObserverMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ val actionCaptor = argumentCaptor()
+ model.getEffectObservable().observeForever(actionObserverMock)
- model.onAny(null, Lifecycle.Event.ON_START)
+ model.onStateChanged(Lifecycle.Event.ON_START)
Mockito.verify(actionObserverMock, Mockito.times(1)).onChanged(actionCaptor.capture())
- Assert.assertTrue(actionCaptor.value is SplashScreenAction.LaunchWorker)
+ Assert.assertTrue(actionCaptor.firstValue is SplashScreenEffect.LaunchWorker)
- progressChanel.send(Result(error))
+ progressChanel.tryEmit(Result(error))
Thread.sleep(SplashContract.SPLASH_DELAY * 2)
Mockito.verify(cache, Mockito.times(1)).getProgressChanel()
Mockito.verify(actionObserverMock, Mockito.times(2)).onChanged(actionCaptor.capture())
- val errorState = actionCaptor.value
- Assert.assertTrue(errorState is SplashScreenAction.ShowError)
- Assert.assertEquals((errorState as SplashScreenAction.ShowError).e, error)
+ val errorState = actionCaptor.thirdValue
+ Assert.assertTrue(errorState is SplashScreenEffect.ShowError)
+ Assert.assertEquals((errorState as SplashScreenEffect.ShowError).e, error)
Mockito.verifyNoInteractions(stateObserverMock)
Mockito.verifyNoMoreInteractions(actionObserverMock)
verifyNoMoreInteractions()
model.getStateObservable().removeObserver(stateObserverMock)
- model.getActionObservable().removeObserver(actionObserverMock)
+ model.getEffectObservable().removeObserver(actionObserverMock)
}
@Test
- fun testSetup_LaunchCrash() = runBlockingTest {
+ fun testSetup_LaunchCrash() = runBlocking {
val error = RuntimeException("TestError")
Mockito.`when`(cache.getProgressChanel()).thenThrow(error)
- val observerMock = Mockito.mock(Observer::class.java) as Observer
+ val observerMock =
+ Mockito.mock(Observer::class.java) as Observer>
model.getStateObservable().observeForever(observerMock)
- val actionObserverMock = Mockito.mock(Observer::class.java) as Observer
- val actionCaptor = ArgumentCaptor.forClass(SplashScreenAction::class.java)
- model.getActionObservable().observeForever(actionObserverMock)
+ val actionObserverMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ val actionCaptor = argumentCaptor()
+ model.getEffectObservable().observeForever(actionObserverMock)
- model.onAny(null, Lifecycle.Event.ON_START)
+ model.onStateChanged(Lifecycle.Event.ON_START)
- Mockito.verify(actionObserverMock, Mockito.times(1)).onChanged(actionCaptor.capture())
- Assert.assertTrue(actionCaptor.value is SplashScreenAction.LaunchWorker)
-
Thread.sleep(SplashContract.SPLASH_DELAY * 2)
Mockito.verify(cache, Mockito.times(1)).getProgressChanel()
Mockito.verify(actionObserverMock, Mockito.times(2)).onChanged(actionCaptor.capture())
- val errorState = actionCaptor.value
- Assert.assertTrue(errorState is SplashScreenAction.ShowError)
- Assert.assertEquals((errorState as SplashScreenAction.ShowError).e, error)
+ val errorEffect = actionCaptor.allValues
+ Assert.assertTrue(errorEffect[0] is SplashScreenEffect.ShowError)
+ Assert.assertEquals((errorEffect[0] as SplashScreenEffect.ShowError).e, error)
+ Assert.assertTrue(errorEffect[1] is SplashScreenEffect.LaunchWorker)
Mockito.verifyNoInteractions(observerMock)
Mockito.verifyNoMoreInteractions(actionObserverMock)
verifyNoMoreInteractions()
model.getStateObservable().removeObserver(observerMock)
- model.getActionObservable().removeObserver(actionObserverMock)
+ model.getEffectObservable().removeObserver(actionObserverMock)
}
@Test
- fun testSetup_LaunchOk() = runBlockingTest {
- val progressChanel = Channel>(Channel.Factory.CONFLATED)
+ fun testSetup_LaunchOk() = runBlocking {
+ val progressChanel = MutableSharedFlow>(
+ extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST
+ )
Mockito.`when`(cache.getProgressChanel()).thenReturn(progressChanel)
- val observerMock = Mockito.mock(Observer::class.java) as Observer
+ val observerMock =
+ Mockito.mock(Observer::class.java) as Observer>
model.getStateObservable().observeForever(observerMock)
- val actionObserverMock = Mockito.mock(Observer::class.java) as Observer
- val actionCaptor = ArgumentCaptor.forClass(SplashScreenAction::class.java)
- model.getActionObservable().observeForever(actionObserverMock)
+ val actionObserverMock =
+ Mockito.mock(Observer::class.java) as Observer>
+ val actionCaptor = argumentCaptor()
+ model.getEffectObservable().observeForever(actionObserverMock)
- model.onAny(null, Lifecycle.Event.ON_START)
+ model.onStateChanged(Lifecycle.Event.ON_START)
Mockito.verify(actionObserverMock, Mockito.times(1)).onChanged(actionCaptor.capture())
- Assert.assertTrue(actionCaptor.value is SplashScreenAction.LaunchWorker)
+ Assert.assertTrue(actionCaptor.firstValue is SplashScreenEffect.LaunchWorker)
- progressChanel.send(Result(90L))
+ progressChanel.tryEmit(Result(90L))
Thread.sleep(SplashContract.SPLASH_DELAY * 2)
Mockito.verify(cache, Mockito.times(1)).getProgressChanel()
Mockito.verify(actionObserverMock, Mockito.times(2)).onChanged(actionCaptor.capture())
- val errorState = actionCaptor.value
- Assert.assertTrue(errorState is SplashScreenAction.NextScreen)
+ val errorState = actionCaptor.thirdValue
+ Assert.assertTrue(errorState is SplashScreenEffect.NextScreen)
Mockito.verifyNoInteractions(observerMock)
Mockito.verifyNoMoreInteractions(actionObserverMock)
verifyNoMoreInteractions()
model.getStateObservable().removeObserver(observerMock)
- model.getActionObservable().removeObserver(actionObserverMock)
+ model.getEffectObservable().removeObserver(actionObserverMock)
}
}
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index 3e08e48..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,53 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-buildscript {
- ext.kotlin_version = '1.5.21'
- ext.lifecycle_ktx = "2.3.1"
- ext.nav_version = "2.3.5"
- ext.work_version = "2.6.0"
- ext.room = "2.3.0"
- ext.room_compiler = "2.2.5"
- ext.compat = "1.3.1"
- ext.gson = "2.8.6"
- ext.retrofit = "2.9.0"
- ext.retrofit_gson = "2.9.0"
- ext.okhttp_log = "4.9.0"
- ext.okhttp = "4.9.0"
- ext.junit = "4.13.2"
- ext.junit_android = "1.1.3"
- ext.espresso = "3.4.0"
- ext.ktx = "1.6.0"
- ext.coroutins = "1.5.0"
-
- project.ext {
- min = 20
- target = 30
- compile = 30
- tools = "30.0.2"
- }
-
- repositories {
- mavenCentral()
- google()
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:7.0.2'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-allprojects {
- repositories {
- mavenCentral()
- google()
- jcenter()
- }
-}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..d2e7e48
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,54 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ extra.apply {
+ set("kotlin_version", "1.8.20") // update according to composeVersion
+ set("lifecycle_ktx", "2.6.1")
+ set("nav_version", "2.7.2")
+ set("work_version", "2.8.1")
+ set("room", "2.5.2")
+ set("room_compiler", "2.5.2")
+ set("gson", "2.9.0")
+ set("retrofit", "2.9.0")
+ set("retrofit_gson", "2.9.0")
+ set("okhttp_log", "4.10.0")
+ set("okhttp", "4.10.0")
+ set("ver_junit", "4.13.2")
+ set("junit_android", "1.1.5")
+ set("espresso", "3.5.1")
+
+ set("hilt", "2.46.1")
+ set("hilt_jetpack", "1.0.0")
+ set("testing_core", "1.1.1")
+ set("testing_coroutine", "1.6.0")
+
+ // update kotlinCompilerExtensionVersion when composeVersion updated
+ // update kotlinCompilerExtensionVersion when composeVersionTheme updated
+ set("composeVersion", "1.4.2")
+ set("composeVersionThemeMaterial", "0.30.1")
+
+ set("ktx", "1.12.0")
+ set("coroutines", "1.7.3")
+ set("mockito_core", "5.3.1")
+ set("mockito_kotlin", "2.2.0")
+ set("testing", "1.1.1")
+ }
+
+ project.extra.apply {
+ set("min", 20)
+ set("target", 34)
+ set("compile", 34)
+ }
+}
+
+plugins {
+ id("com.android.application") version "8.1.1" apply false
+ id("org.jetbrains.kotlin.android") version "1.9.0" apply false
+ id("androidx.navigation.safeargs") version "2.7.2" apply false
+ id("com.android.library") version "8.1.1" apply false
+ id("com.google.devtools.ksp") version "1.9.10-1.0.13" apply false
+ id("com.google.dagger.hilt.android") version "2.46.1" apply false
+}
+
+tasks.register("clean", Delete::class) {
+ delete(rootProject.buildDir)
+}
diff --git a/gradle.properties b/gradle.properties
index 2f26404..3ed8aa7 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -17,3 +17,13 @@ org.gradle.jvmargs=-Xmx2048m
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
+org.gradle.parallel=true
+org.gradle.configureondemand=true
+org.gradle.caching=true
+kotlin.code.style=official
+-Pkapt.use.worker.api=true
+kapt.incremental.apt=true
+android.enableR8.fullMode=false
+android.defaults.buildfeatures.buildconfig=true
+android.nonTransitiveRClass=false
+android.nonFinalResIds=false
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2be7f07..9cb1828 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
diff --git a/models/build.gradle b/models/build.gradle
deleted file mode 100644
index 000ec1c..0000000
--- a/models/build.gradle
+++ /dev/null
@@ -1,47 +0,0 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-
-android {
- compileSdkVersion project.compile
- buildToolsVersion project.tools
-
- defaultConfig {
- minSdkVersion project.min
- targetSdkVersion project.target
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- consumerProguardFiles "consumer-rules.pro"
- }
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-}
-
-dependencies {
- implementation fileTree(dir: "libs", include: ["*.jar"])
- implementation "androidx.appcompat:appcompat:$compat"
-
- // room
- implementation "androidx.room:room-runtime:$room"
- annotationProcessor "androidx.room:room-compiler:$room_compiler"
-
- implementation "com.google.code.gson:gson:$gson"
-
- implementation "androidx.core:core-ktx:$ktx"
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
-
- // coroutins
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutins"
-}
-
-repositories {
- mavenCentral()
-}
diff --git a/models/build.gradle.kts b/models/build.gradle.kts
new file mode 100644
index 0000000..854f920
--- /dev/null
+++ b/models/build.gradle.kts
@@ -0,0 +1,54 @@
+plugins {
+ id("com.android.library")
+ kotlin("android")
+ id("com.google.devtools.ksp")
+}
+
+android {
+ namespace = "com.mdgd.pokemon.models"
+ compileSdk = rootProject.extra["compile"] as Int?
+
+ defaultConfig {
+ minSdk = rootProject.extra["min"] as Int?
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_17.toString()
+ }
+ packaging {
+ resources {
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ }
+ }
+}
+
+dependencies {
+ implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
+
+ // room
+ implementation("androidx.room:room-runtime:${rootProject.extra["room"]}")
+ ksp("androidx.room:room-compiler:${rootProject.extra["room_compiler"]}")
+
+ implementation("com.google.code.gson:gson:${rootProject.extra["gson"]}")
+
+ implementation("androidx.core:core-ktx:${rootProject.extra["ktx"]}")
+
+ // coroutines
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:${rootProject.extra["coroutines"]}")
+}
diff --git a/models/proguard-rules.pro b/models/proguard-rules.pro
index f1b4245..2f9dc5a 100644
--- a/models/proguard-rules.pro
+++ b/models/proguard-rules.pro
@@ -1,6 +1,6 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
+# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
diff --git a/models/src/main/AndroidManifest.xml b/models/src/main/AndroidManifest.xml
index 1731195..cc947c5 100644
--- a/models/src/main/AndroidManifest.xml
+++ b/models/src/main/AndroidManifest.xml
@@ -1 +1 @@
-
+
diff --git a/models/src/main/java/com/mdgd/pokemon/models/AppModule.kt b/models/src/main/java/com/mdgd/pokemon/models/AppModule.kt
deleted file mode 100644
index 71b985c..0000000
--- a/models/src/main/java/com/mdgd/pokemon/models/AppModule.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.mdgd.pokemon.models
-
-import android.content.Context
-import com.mdgd.pokemon.models.cache.Cache
-import com.mdgd.pokemon.models.filters.StatsFilter
-import com.mdgd.pokemon.models.repo.PokemonsRepo
-import com.mdgd.pokemon.models.repo.dao.PokemonsDao
-import com.mdgd.pokemon.models.repo.network.Network
-
-interface AppModule {
- fun getAppContext(): Context
- fun getPokemonsNetwork(): Network
- fun getPokemonsDao(): PokemonsDao
- fun getPokemonsRepo(): PokemonsRepo
- fun getCache(): Cache
- fun getFiltersFactory(): StatsFilter;
-}
diff --git a/models/src/main/java/com/mdgd/pokemon/models/cache/Cache.kt b/models/src/main/java/com/mdgd/pokemon/models/cache/Cache.kt
index b54390f..1d004c4 100644
--- a/models/src/main/java/com/mdgd/pokemon/models/cache/Cache.kt
+++ b/models/src/main/java/com/mdgd/pokemon/models/cache/Cache.kt
@@ -1,9 +1,9 @@
package com.mdgd.pokemon.models.cache
import com.mdgd.pokemon.models.infra.Result
-import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.Flow
interface Cache {
- suspend fun putLoadingProgress(value: Result)
- fun getProgressChanel(): Channel>
+ fun putLoadingProgress(value: Result)
+ fun getProgressChanel(): Flow>
}
diff --git a/models/src/main/java/com/mdgd/pokemon/models/filters/FilterData.kt b/models/src/main/java/com/mdgd/pokemon/models/filters/FilterData.kt
index 7761313..da27495 100644
--- a/models/src/main/java/com/mdgd/pokemon/models/filters/FilterData.kt
+++ b/models/src/main/java/com/mdgd/pokemon/models/filters/FilterData.kt
@@ -18,7 +18,7 @@ class FilterData {
companion object {
const val FILTER_ATTACK = "attack"
- const val FILTER_DEFENCE = "defence"
+ const val FILTER_DEFENCE = "defense"
const val FILTER_SPEED = "speed"
}
}
diff --git a/models/src/main/java/com/mdgd/pokemon/models/infra/Result.kt b/models/src/main/java/com/mdgd/pokemon/models/infra/Result.kt
index 47e24a7..3d44f81 100644
--- a/models/src/main/java/com/mdgd/pokemon/models/infra/Result.kt
+++ b/models/src/main/java/com/mdgd/pokemon/models/infra/Result.kt
@@ -15,19 +15,10 @@ class Result {
this.error = error
}
- fun isError(): Boolean {
- return error != null
- }
+ fun isError() = error != null
+ fun hasValue() = error == null
- fun getValue(): T {
- return value!!
- }
+ fun getValue() = value!!
- fun getError(): Throwable {
- return error!!
- }
-
- fun hasValue(): Boolean {
- return error == null
- }
+ fun getError() = error!!
}
diff --git a/models/src/main/java/com/mdgd/pokemon/models/repo/cache/PokemonsCache.kt b/models/src/main/java/com/mdgd/pokemon/models/repo/cache/PokemonsCache.kt
index 914900c..61ce04f 100644
--- a/models/src/main/java/com/mdgd/pokemon/models/repo/cache/PokemonsCache.kt
+++ b/models/src/main/java/com/mdgd/pokemon/models/repo/cache/PokemonsCache.kt
@@ -7,4 +7,4 @@ interface PokemonsCache {
fun addPokemons(list: List)
fun setPokemons(list: List)
fun getPokemons(): List
-}
\ No newline at end of file
+}
diff --git a/models/src/main/java/com/mdgd/pokemon/models/repo/dao/schemas/MoveSchema.kt b/models/src/main/java/com/mdgd/pokemon/models/repo/dao/schemas/MoveSchema.kt
index 5300bb6..0b9f220 100644
--- a/models/src/main/java/com/mdgd/pokemon/models/repo/dao/schemas/MoveSchema.kt
+++ b/models/src/main/java/com/mdgd/pokemon/models/repo/dao/schemas/MoveSchema.kt
@@ -6,7 +6,7 @@ import com.google.gson.annotations.SerializedName
import com.mdgd.pokemon.models.repo.schemas.Move_
@Entity(
- tableName = "moves", indices = [Index("id")],
+ tableName = "moves", indices = [Index("id"), Index("pokemonId")],
foreignKeys = [ForeignKey(
entity = PokemonSchema::class, parentColumns = ["id"],
childColumns = ["pokemonId"], onDelete = ForeignKey.CASCADE
diff --git a/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/Ability.kt b/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/Ability.kt
index a1278d3..881963c 100644
--- a/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/Ability.kt
+++ b/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/Ability.kt
@@ -6,7 +6,7 @@ import com.google.gson.annotations.SerializedName
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonSchema
@Entity(
- tableName = "abilities", indices = [Index("id")],
+ tableName = "abilities", indices = [Index("id"), Index("pokemonId")],
foreignKeys = [ForeignKey(
entity = PokemonSchema::class,
parentColumns = ["id"],
diff --git a/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/Form.kt b/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/Form.kt
index 6063af2..9174ae7 100644
--- a/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/Form.kt
+++ b/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/Form.kt
@@ -9,7 +9,7 @@ import com.google.gson.annotations.SerializedName
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonSchema
@Entity(
- tableName = "forms", indices = [Index("id")],
+ tableName = "forms", indices = [Index("id"), Index("pokemonId")],
foreignKeys = [ForeignKey(
entity = PokemonSchema::class,
parentColumns = ["id"],
diff --git a/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/GameIndex.kt b/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/GameIndex.kt
index 922806e..14eecc6 100644
--- a/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/GameIndex.kt
+++ b/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/GameIndex.kt
@@ -6,7 +6,7 @@ import com.google.gson.annotations.SerializedName
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonSchema
@Entity(
- tableName = "game_indexes", indices = [Index("id")],
+ tableName = "game_indexes", indices = [Index("id"), Index("pokemonId")],
foreignKeys = [ForeignKey(
entity = PokemonSchema::class,
parentColumns = ["id"],
diff --git a/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/Stat.kt b/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/Stat.kt
index 878d026..4299067 100644
--- a/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/Stat.kt
+++ b/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/Stat.kt
@@ -6,7 +6,7 @@ import com.google.gson.annotations.SerializedName
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonSchema
@Entity(
- tableName = "stats", indices = [Index("id")],
+ tableName = "stats", indices = [Index("id"), Index("pokemonId")],
foreignKeys = [ForeignKey(
entity = PokemonSchema::class,
parentColumns = ["id"],
diff --git a/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/Type.kt b/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/Type.kt
index 907498a..9e769ca 100644
--- a/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/Type.kt
+++ b/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/Type.kt
@@ -6,7 +6,7 @@ import com.google.gson.annotations.SerializedName
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonSchema
@Entity(
- tableName = "types", indices = [Index("id")],
+ tableName = "types", indices = [Index("id"), Index("pokemonId")],
foreignKeys = [ForeignKey(
entity = PokemonSchema::class,
parentColumns = ["id"],
diff --git a/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/VersionGroupDetail.kt b/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/VersionGroupDetail.kt
index 435e672..3ab77be 100644
--- a/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/VersionGroupDetail.kt
+++ b/models/src/main/java/com/mdgd/pokemon/models/repo/schemas/VersionGroupDetail.kt
@@ -6,7 +6,7 @@ import com.google.gson.annotations.SerializedName
import com.mdgd.pokemon.models.repo.dao.schemas.MoveSchema
@Entity(
- tableName = "VersionGroupDetails", indices = [Index("id")],
+ tableName = "VersionGroupDetails", indices = [Index("id"), Index("moveId")],
foreignKeys = [ForeignKey(
entity = MoveSchema::class, parentColumns = ["id"],
childColumns = ["moveId"], onDelete = ForeignKey.CASCADE
diff --git a/mvi/src/main/java/com/mdgd/mvi/util/DispatchersHolder.kt b/models/src/main/java/com/mdgd/pokemon/models/util/DispatchersHolder.kt
similarity index 80%
rename from mvi/src/main/java/com/mdgd/mvi/util/DispatchersHolder.kt
rename to models/src/main/java/com/mdgd/pokemon/models/util/DispatchersHolder.kt
index b251be0..dd523b8 100644
--- a/mvi/src/main/java/com/mdgd/mvi/util/DispatchersHolder.kt
+++ b/models/src/main/java/com/mdgd/pokemon/models/util/DispatchersHolder.kt
@@ -1,4 +1,4 @@
-package com.mdgd.mvi.util
+package com.mdgd.pokemon.models.util
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/models_impl/build.gradle b/models_impl/build.gradle
deleted file mode 100644
index 0ba316e..0000000
--- a/models_impl/build.gradle
+++ /dev/null
@@ -1,63 +0,0 @@
-plugins {
- id 'com.android.library'
- id 'kotlin-android'
- id 'kotlin-kapt'
-}
-android {
- compileSdkVersion project.compile
- buildToolsVersion project.tools
-
- defaultConfig {
- minSdkVersion project.min
- targetSdkVersion project.target
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- consumerProguardFiles "consumer-rules.pro"
- }
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-}
-
-dependencies {
- implementation project(':models')
- implementation fileTree(dir: "libs", include: ["*.jar"])
- implementation "androidx.appcompat:appcompat:$compat"
-
- // room
- implementation "androidx.room:room-runtime:$room"
- kapt "androidx.room:room-compiler:$room_compiler"
-
- // retrofit
- implementation "com.squareup.retrofit2:retrofit:$retrofit"
- implementation "com.squareup.retrofit2:converter-gson:$retrofit_gson"
-
- // okHttp
- implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_log"
- implementation "com.squareup.okhttp3:okhttp:$okhttp"
-
- implementation "androidx.core:core-ktx:$ktx"
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
-
- // coroutins
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutins"
-
- testImplementation "junit:junit:$junit"
- testImplementation "org.mockito:mockito-core:3.7.0"
- testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
- testImplementation "android.arch.core:core-testing:1.1.1"
- testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.0"
- testImplementation "com.google.code.gson:gson:$gson"
-}
-
-repositories {
- mavenCentral()
-}
diff --git a/models_impl/build.gradle.kts b/models_impl/build.gradle.kts
new file mode 100644
index 0000000..cdbc0a6
--- /dev/null
+++ b/models_impl/build.gradle.kts
@@ -0,0 +1,72 @@
+plugins {
+ id("com.android.library")
+ kotlin("android")
+ id("com.google.devtools.ksp")
+ id("dagger.hilt.android.plugin")
+}
+
+android {
+ namespace = "com.mdgd.pokemon.models_impl"
+ compileSdk = rootProject.extra["compile"] as Int?
+
+ defaultConfig {
+ minSdk = rootProject.extra["min"] as Int?
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_17.toString()
+ }
+ packaging {
+ resources {
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ }
+ }
+}
+
+dependencies {
+ implementation(project(":models"))
+ implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
+
+ // room
+ implementation("androidx.room:room-runtime:${rootProject.extra["room"]}")
+ ksp("androidx.room:room-compiler:${rootProject.extra["room_compiler"]}")
+
+ // retrofit
+ implementation("com.squareup.retrofit2:retrofit:${rootProject.extra["retrofit"]}")
+ implementation("com.squareup.retrofit2:converter-gson:${rootProject.extra["retrofit_gson"]}")
+
+ // okHttp
+ implementation("com.squareup.okhttp3:logging-interceptor:${rootProject.extra["okhttp_log"]}")
+ implementation("com.squareup.okhttp3:okhttp:${rootProject.extra["okhttp"]}")
+
+ implementation("androidx.core:core-ktx:${rootProject.extra["ktx"]}")
+
+ // coroutines
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:${rootProject.extra["coroutines"]}")
+
+ implementation("com.google.dagger:hilt-android:${rootProject.extra["hilt"]}")
+ ksp("com.google.dagger:hilt-android-compiler:${rootProject.extra["hilt"]}")
+
+ testImplementation("junit:junit:${rootProject.extra["ver_junit"]}")
+ testImplementation("org.mockito:mockito-core:${rootProject.extra["mockito_core"]}")
+ testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:${rootProject.extra["mockito_kotlin"]}")
+ testImplementation("android.arch.core:core-testing:${rootProject.extra["testing"]}")
+ testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${rootProject.extra["coroutines"]}")
+ testImplementation("com.google.code.gson:gson:${rootProject.extra["gson"]}")
+}
diff --git a/models_impl/proguard-rules.pro b/models_impl/proguard-rules.pro
index f1b4245..2f9dc5a 100644
--- a/models_impl/proguard-rules.pro
+++ b/models_impl/proguard-rules.pro
@@ -1,6 +1,6 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
+# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
diff --git a/models_impl/src/main/AndroidManifest.xml b/models_impl/src/main/AndroidManifest.xml
index 03c44a2..cc947c5 100644
--- a/models_impl/src/main/AndroidManifest.xml
+++ b/models_impl/src/main/AndroidManifest.xml
@@ -1 +1 @@
-
+
diff --git a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/DefaultAppModule.kt b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/DefaultAppModule.kt
index f1d9a6f..ed03f24 100644
--- a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/DefaultAppModule.kt
+++ b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/DefaultAppModule.kt
@@ -1,44 +1,55 @@
package com.mdgd.pokemon.models_impl
import android.content.Context
-import com.mdgd.pokemon.models.AppModule
import com.mdgd.pokemon.models.cache.Cache
import com.mdgd.pokemon.models.filters.StatsFilter
import com.mdgd.pokemon.models.repo.PokemonsRepo
import com.mdgd.pokemon.models.repo.dao.PokemonsDao
import com.mdgd.pokemon.models.repo.network.Network
+import com.mdgd.pokemon.models.util.DispatchersHolder
import com.mdgd.pokemon.models_impl.cache.CacheImpl
import com.mdgd.pokemon.models_impl.filters.StatsFiltersFactory
import com.mdgd.pokemon.models_impl.repo.PokemonsRepository
import com.mdgd.pokemon.models_impl.repo.cache.PokemonsCacheImpl
import com.mdgd.pokemon.models_impl.repo.dao.PokemonsDaoImpl
import com.mdgd.pokemon.models_impl.repo.network.PokemonsNetwork
-
-class DefaultAppModule(val app: Context) : AppModule {
- private val cache: Cache = CacheImpl()
+import com.mdgd.pokemon.models_impl.util.DispatchersHolderImpl
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+class DefaultAppModule {
+ private val cache = CacheImpl()
private val pokemonCache = PokemonsCacheImpl()
- override fun getAppContext(): Context {
- return app
- }
+ @Provides
+ @Singleton
+ fun providePokemonsNetwork() = PokemonsNetwork() as Network
- override fun getPokemonsNetwork(): Network {
- return PokemonsNetwork()
- }
+ @Provides
+ @Singleton
+ fun providePokemonsDao(@ApplicationContext ctx: Context) = PokemonsDaoImpl(ctx) as PokemonsDao
- override fun getPokemonsDao(): PokemonsDao {
- return PokemonsDaoImpl(app)
- }
+ @Provides
+ @Singleton
+ fun providePokemonsRepo(dao: PokemonsDao, network: Network) = PokemonsRepository(
+ dao, network, pokemonCache
+ ) as PokemonsRepo
- override fun getPokemonsRepo(): PokemonsRepo {
- return PokemonsRepository(getPokemonsDao(), getPokemonsNetwork(), pokemonCache)
- }
+ @Provides
+ @Singleton
+ fun provideCache() = cache as Cache
- override fun getCache(): Cache {
- return cache
- }
+ @Provides
+ @Singleton
+ fun provideFiltersFactory() = StatsFiltersFactory() as StatsFilter
- override fun getFiltersFactory(): StatsFilter {
- return StatsFiltersFactory()
- }
+ @Provides
+ @Singleton
+ fun provideDispatchers() = DispatchersHolderImpl() as DispatchersHolder
}
diff --git a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/cache/CacheImpl.kt b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/cache/CacheImpl.kt
index cdb0b95..ad375a9 100644
--- a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/cache/CacheImpl.kt
+++ b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/cache/CacheImpl.kt
@@ -2,16 +2,20 @@ package com.mdgd.pokemon.models_impl.cache
import com.mdgd.pokemon.models.cache.Cache
import com.mdgd.pokemon.models.infra.Result
-import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
class CacheImpl : Cache {
- private val progressChanel = Channel>(Channel.Factory.CONFLATED)
+ private val progressChanel = MutableSharedFlow>(
+ extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST
+ )
- override suspend fun putLoadingProgress(value: Result) {
- progressChanel.send(value)
+ override fun putLoadingProgress(value: Result) {
+ progressChanel.tryEmit(value)
}
- override fun getProgressChanel(): Channel> {
+ override fun getProgressChanel(): Flow> {
return progressChanel
}
}
diff --git a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/filters/StatsFiltersFactory.kt b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/filters/StatsFiltersFactory.kt
index bcfb2ce..fa622c1 100644
--- a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/filters/StatsFiltersFactory.kt
+++ b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/filters/StatsFiltersFactory.kt
@@ -4,19 +4,21 @@ import com.mdgd.pokemon.models.filters.CharacteristicComparator
import com.mdgd.pokemon.models.filters.FilterData
import com.mdgd.pokemon.models.filters.StatsFilter
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonFullDataSchema
-import java.util.*
class StatsFiltersFactory : StatsFilter {
- override fun getAvailableFilters(): List {
- return listOf(FilterData.FILTER_ATTACK, FilterData.FILTER_DEFENCE, FilterData.FILTER_SPEED)
- }
+ override fun getAvailableFilters(): List = listOf(
+ FilterData.FILTER_ATTACK, FilterData.FILTER_DEFENCE, FilterData.FILTER_SPEED
+ )
override fun getFilters(): Map {
return object : HashMap() {
init {
put(FilterData.FILTER_ATTACK, object : CharacteristicComparator {
- override fun compare(p1: PokemonFullDataSchema, p2: PokemonFullDataSchema): Int {
+ override fun compare(
+ p1: PokemonFullDataSchema,
+ p2: PokemonFullDataSchema
+ ): Int {
return compareProperty(FilterData.FILTER_ATTACK, p1, p2)
}
})
@@ -37,18 +39,18 @@ class StatsFiltersFactory : StatsFilter {
private fun compareProperty(property: String, p1: PokemonFullDataSchema, p2: PokemonFullDataSchema): Int {
var val1 = -1
for (s in p1.stats) {
- if (property == s.stat!!.name) {
+ if (property == s.stat?.name) {
val1 = s.baseStat!!
break
}
}
var val2 = -1
for (s in p2.stats) {
- if (property == s.stat!!.name) {
+ if (property == s.stat?.name) {
val2 = s.baseStat!!
break
}
}
return val1.compareTo(val2)
}
-}
\ No newline at end of file
+}
diff --git a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/PokemonsRepository.kt b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/PokemonsRepository.kt
index cd8b034..22b7385 100644
--- a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/PokemonsRepository.kt
+++ b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/PokemonsRepository.kt
@@ -6,13 +6,17 @@ import com.mdgd.pokemon.models.repo.dao.PokemonsDao
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonFullDataSchema
import com.mdgd.pokemon.models.repo.network.Network
-class PokemonsRepository(private val dao: PokemonsDao, private val network: Network, private val cache: PokemonsCache) : PokemonsRepo {
+class PokemonsRepository(
+ private val dao: PokemonsDao,
+ private val network: Network,
+ private val cache: PokemonsCache
+) : PokemonsRepo {
override fun getPokemons() = cache.getPokemons()
override suspend fun getPage(page: Int): List {
val list = getPageFromDao(page)
- return if (list.isEmpty()) loadPage(page) else list
+ return list.ifEmpty { loadPage(page) }
}
private suspend fun loadPage(page: Int): List {
@@ -37,16 +41,12 @@ class PokemonsRepository(private val dao: PokemonsDao, private val network: Netw
val count = dao.getCount()
val pokemonsCount = network.getPokemonsCount()
return when (count) {
- pokemonsCount -> {
- count
- }
- else -> {
- loadPokemonsInner(pokemonsCount, initialAmount)
- }
+ pokemonsCount -> count
+ else -> loadPokemonsInner(pokemonsCount, initialAmount)
}
}
- private suspend fun loadPokemonsInner(pokemonsCount: Long, offset: Long): Long { // = withContext(Dispatchers.IO)
+ private suspend fun loadPokemonsInner(pokemonsCount: Long, offset: Long): Long {
val page = network.loadPokemons(pokemonsCount, offset)
dao.save(page)
return page.size.toLong()
diff --git a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/cache/PokemonsCacheImpl.kt b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/cache/PokemonsCacheImpl.kt
index cb8c89a..f6cbe20 100644
--- a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/cache/PokemonsCacheImpl.kt
+++ b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/cache/PokemonsCacheImpl.kt
@@ -2,7 +2,6 @@ package com.mdgd.pokemon.models_impl.repo.cache
import com.mdgd.pokemon.models.repo.cache.PokemonsCache
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonFullDataSchema
-import java.util.*
class PokemonsCacheImpl : PokemonsCache {
private val pokemons = mutableListOf()
@@ -11,9 +10,7 @@ class PokemonsCacheImpl : PokemonsCache {
pokemons.addAll(list)
}
- override fun getPokemons(): List {
- return ArrayList(pokemons)
- }
+ override fun getPokemons(): List = ArrayList(pokemons)
override fun setPokemons(list: List) {
pokemons.clear()
diff --git a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/dao/AppDatabase.kt b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/dao/AppDatabase.kt
index 7e51158..ec83794 100644
--- a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/dao/AppDatabase.kt
+++ b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/dao/AppDatabase.kt
@@ -4,11 +4,18 @@ import androidx.room.Database
import androidx.room.RoomDatabase
import com.mdgd.pokemon.models.repo.dao.schemas.MoveSchema
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonSchema
-import com.mdgd.pokemon.models.repo.schemas.*
+import com.mdgd.pokemon.models.repo.schemas.Ability
+import com.mdgd.pokemon.models.repo.schemas.Form
+import com.mdgd.pokemon.models.repo.schemas.GameIndex
+import com.mdgd.pokemon.models.repo.schemas.Stat
+import com.mdgd.pokemon.models.repo.schemas.Type
+import com.mdgd.pokemon.models.repo.schemas.VersionGroupDetail
-@Database(version = 1, exportSchema = false,
- entities = [PokemonSchema::class, Ability::class, Form::class, GameIndex::class,
- MoveSchema::class, Stat::class, Type::class, VersionGroupDetail::class])
+@Database(
+ version = 1, exportSchema = false,
+ entities = [PokemonSchema::class, Ability::class, Form::class, GameIndex::class,
+ MoveSchema::class, Stat::class, Type::class, VersionGroupDetail::class]
+)
abstract class AppDatabase : RoomDatabase() {
abstract fun pokemonsDao(): PokemonsRoomDao?
}
diff --git a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/dao/PokemonsDaoImpl.kt b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/dao/PokemonsDaoImpl.kt
index 82a44ec..7d701ed 100644
--- a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/dao/PokemonsDaoImpl.kt
+++ b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/dao/PokemonsDaoImpl.kt
@@ -5,36 +5,30 @@ import androidx.room.Room
import com.mdgd.pokemon.models.repo.dao.PokemonsDao
import com.mdgd.pokemon.models.repo.dao.schemas.PokemonFullDataSchema
import com.mdgd.pokemon.models.repo.network.schemas.PokemonDetails
-import java.util.*
class PokemonsDaoImpl(ctx: Context) : PokemonsDao {
- private val pokemonsRoomDao: PokemonsRoomDao? = Room.databaseBuilder(ctx, AppDatabase::class.java, "PokemonsAppDB").build().pokemonsDao()
+ private val pokemonsRoomDao: PokemonsRoomDao? = Room.databaseBuilder(
+ ctx, AppDatabase::class.java, "PokemonsAppDB"
+ ).build().pokemonsDao()
override suspend fun save(list: List) {
- pokemonsRoomDao!!.save(list)
+ pokemonsRoomDao?.save(list)
}
override suspend fun getPage(page: Int, pageSize: Int): List {
val offset = page * pageSize
- val rows = pokemonsRoomDao!!.countRows()
+ val rows = pokemonsRoomDao?.countRows() ?: 0
return when {
- rows == 0 -> {
- ArrayList(0)
- }
- pokemonsRoomDao.countRows() <= offset -> {
- throw Exception(PokemonsDao.NO_MORE_POKEMONS_MSG)
- }
- else -> {
- pokemonsRoomDao.getPage(offset, pageSize)
- }
+ rows == 0 -> ArrayList(0)
+ (pokemonsRoomDao?.countRows()
+ ?: 0) <= offset -> throw Exception(PokemonsDao.NO_MORE_POKEMONS_MSG)
+
+ else -> pokemonsRoomDao?.getPage(offset, pageSize) ?: listOf()
}
}
- override suspend fun getCount(): Long {
- return pokemonsRoomDao!!.countRows().toLong()
- }
+ override suspend fun getCount() = pokemonsRoomDao?.countRows()?.toLong() ?: 0
- override suspend fun getPokemonById(pokemonId: Long): PokemonFullDataSchema? {
- return pokemonsRoomDao!!.getPokemonById(pokemonId)
- }
+ override suspend fun getPokemonById(pokemonId: Long) =
+ pokemonsRoomDao?.getPokemonById(pokemonId)
}
diff --git a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/dao/PokemonsRoomDao.kt b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/dao/PokemonsRoomDao.kt
index 9f21981..cd7905c 100644
--- a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/dao/PokemonsRoomDao.kt
+++ b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/dao/PokemonsRoomDao.kt
@@ -8,7 +8,6 @@ import com.mdgd.pokemon.models.repo.dao.schemas.PokemonSchema
import com.mdgd.pokemon.models.repo.network.schemas.PokemonDetails
import com.mdgd.pokemon.models.repo.schemas.*
import java.util.*
-import kotlin.collections.ArrayList
@Dao
abstract class PokemonsRoomDao {
@@ -111,8 +110,8 @@ abstract class PokemonsRoomDao {
fun getPage(offset: Int, pageSize: Int): List {
val pokemons = getPokemonsForPage(offset, pageSize)
- val schemas: List = ArrayList(mapPokemons(pokemons))
- Collections.shuffle(schemas)
+ val schemas: MutableList = ArrayList(mapPokemons(pokemons))
+ schemas.shuffle()
return schemas
}
diff --git a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/network/PokemonsNetwork.kt b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/network/PokemonsNetwork.kt
index b72be92..bfb8901 100644
--- a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/network/PokemonsNetwork.kt
+++ b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/repo/network/PokemonsNetwork.kt
@@ -11,9 +11,8 @@ import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
-import java.util.*
+import java.util.Vector
import java.util.concurrent.TimeUnit
-import kotlin.collections.ArrayList
import kotlin.math.max
class PokemonsNetwork : Network {
@@ -21,17 +20,18 @@ class PokemonsNetwork : Network {
init {
val logging = HttpLoggingInterceptor()
- logging.level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BASIC else HttpLoggingInterceptor.Level.NONE
+ logging.level =
+ if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BASIC else HttpLoggingInterceptor.Level.NONE
val httpClient = OkHttpClient.Builder()
httpClient.addInterceptor(logging)
httpClient.readTimeout(10, TimeUnit.SECONDS)
httpClient.writeTimeout(10, TimeUnit.SECONDS)
httpClient.connectTimeout(10, TimeUnit.SECONDS)
val retrofit = Retrofit.Builder()
- .addConverterFactory(GsonConverterFactory.create())
- .client(httpClient.build())
- .baseUrl("https://pokeapi.co/api/v2/")
- .build()
+ .addConverterFactory(GsonConverterFactory.create())
+ .client(httpClient.build())
+ .baseUrl("https://pokeapi.co/api/v2/")
+ .build()
service = retrofit.create(PokemonsRetrofitApi::class.java)
}
diff --git a/models_impl/src/main/java/com/mdgd/pokemon/models_impl/util/DispatchersHolderImpl.kt b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/util/DispatchersHolderImpl.kt
new file mode 100644
index 0000000..81dddfb
--- /dev/null
+++ b/models_impl/src/main/java/com/mdgd/pokemon/models_impl/util/DispatchersHolderImpl.kt
@@ -0,0 +1,11 @@
+package com.mdgd.pokemon.models_impl.util
+
+import com.mdgd.pokemon.models.util.DispatchersHolder
+import kotlinx.coroutines.Dispatchers
+
+class DispatchersHolderImpl : DispatchersHolder {
+
+ override fun getMain() = Dispatchers.Main
+
+ override fun getIO() = Dispatchers.IO
+}
diff --git a/models_impl/src/test/java/com/mdgd/pokemon/models_impl/TestSuit.kt b/models_impl/src/test/java/com/mdgd/pokemon/models_impl/TestSuit.kt
new file mode 100644
index 0000000..ed6bd37
--- /dev/null
+++ b/models_impl/src/test/java/com/mdgd/pokemon/models_impl/TestSuit.kt
@@ -0,0 +1,11 @@
+package com.mdgd.pokemon.models_impl
+
+import com.mdgd.pokemon.models_impl.repo.PokemonsRepositoryTest
+import org.junit.runner.RunWith
+import org.junit.runners.Suite
+
+@RunWith(Suite::class)
+@Suite.SuiteClasses(
+ PokemonsRepositoryTest::class
+)
+class TestSuit
diff --git a/models_impl/src/test/java/com/mdgd/pokemon/models_impl/repo/PokemonsRepositoryTest.kt b/models_impl/src/test/java/com/mdgd/pokemon/models_impl/repo/PokemonsRepositoryTest.kt
index 4875eb4..9f7e14e 100644
--- a/models_impl/src/test/java/com/mdgd/pokemon/models_impl/repo/PokemonsRepositoryTest.kt
+++ b/models_impl/src/test/java/com/mdgd/pokemon/models_impl/repo/PokemonsRepositoryTest.kt
@@ -7,8 +7,7 @@ import com.mdgd.pokemon.models.repo.dao.schemas.PokemonFullDataSchema
import com.mdgd.pokemon.models.repo.network.Network
import com.mdgd.pokemon.models.repo.network.schemas.PokemonDetails
import com.mdgd.pokemon.models_impl.Mocks
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.runTest
import org.junit.Assert
import org.junit.Before
import org.junit.Test
@@ -16,7 +15,6 @@ import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mockito
import java.util.*
-import kotlin.collections.ArrayList
@RunWith(JUnit4::class)
class PokemonsRepositoryTest {
@@ -53,7 +51,7 @@ class PokemonsRepositoryTest {
}
@Test
- fun test_getPageFromDao_InitialPage() = runBlockingTest {
+ fun test_getPageFromDao_InitialPage() = runTest {
val list = listOf(Mocks.getPokemon())
Mockito.`when`(dao.getPage(0, PokemonsRepo.PAGE_SIZE)).thenReturn(list)
@@ -66,7 +64,7 @@ class PokemonsRepositoryTest {
}
@Test
- fun test_getPageFromDao() = runBlockingTest {
+ fun test_getPageFromDao() = runTest {
val list = listOf(Mocks.getPokemon())
Mockito.`when`(dao.getPage(1, PokemonsRepo.PAGE_SIZE)).thenReturn(list)
@@ -79,17 +77,21 @@ class PokemonsRepositoryTest {
}
@Test
- fun test_getPageFromNetwork() = runBlockingTest {
+ fun test_getPageFromNetwork() = runTest {
val emptyList = listOf()
val list = listOf(Mocks.getPokemon())
val networkList = listOf()
- Mockito.`when`(dao.getPage(1, PokemonsRepo.PAGE_SIZE)).thenReturn(emptyList)
- Mockito.`when`(network.loadPokemons(1, PokemonsRepo.PAGE_SIZE)).then {
- launch {
- Mockito.`when`(dao.getPage(1, PokemonsRepo.PAGE_SIZE)).thenReturn(list)
+
+ val invocations = Array(1) { 0 }
+ Mockito.`when`(dao.getPage(1, PokemonsRepo.PAGE_SIZE)).then {
+ invocations[0]++
+ if (invocations[0] == 1) {
+ emptyList
+ } else {
+ list
}
- networkList
}
+ Mockito.`when`(network.loadPokemons(1, PokemonsRepo.PAGE_SIZE)).thenReturn(networkList)
val pokemons = repo.getPage(1)
@@ -102,7 +104,7 @@ class PokemonsRepositoryTest {
}
@Test
- fun test_loadPokemons_AllLoaded() = runBlockingTest {
+ fun test_loadPokemons_AllLoaded() = runTest {
val initialAmount = 10L
Mockito.`when`(dao.getCount()).thenReturn(initialAmount)
Mockito.`when`(network.getPokemonsCount()).thenReturn(initialAmount)
@@ -116,10 +118,11 @@ class PokemonsRepositoryTest {
}
@Test
- fun test_loadPokemons_NothingLoaded() = runBlockingTest {
+ fun test_loadPokemons_NothingLoaded() = runTest {
val initialAmount = 10L
val totalAmount = 20L
- val networkList = ArrayList(Collections.nCopies(totalAmount.toInt(), Mocks.getPokemonDetails()))
+ val networkList =
+ ArrayList(Collections.nCopies(totalAmount.toInt(), Mocks.getPokemonDetails()))
Mockito.`when`(dao.getCount()).thenReturn(initialAmount)
Mockito.`when`(network.getPokemonsCount()).thenReturn(totalAmount)
Mockito.`when`(network.loadPokemons(totalAmount, initialAmount)).thenReturn(networkList)
@@ -135,9 +138,10 @@ class PokemonsRepositoryTest {
}
@Test
- fun test_loadInitialPages_NoData() = runBlockingTest {
+ fun test_loadInitialPages_NoData() = runTest {
val initialAmount = 10L
- val networkList = ArrayList(Collections.nCopies(initialAmount.toInt(), Mocks.getPokemonDetails()))
+ val networkList =
+ ArrayList(Collections.nCopies(initialAmount.toInt(), Mocks.getPokemonDetails()))
Mockito.`when`(dao.getCount()).thenReturn(0)
Mockito.`when`(network.loadPokemons(initialAmount, 0)).thenReturn(networkList)
@@ -150,7 +154,7 @@ class PokemonsRepositoryTest {
}
@Test
- fun test_loadInitialPages() = runBlockingTest {
+ fun test_loadInitialPages() = runTest {
val initialAmount = 10L
Mockito.`when`(dao.getCount()).thenReturn(initialAmount)
@@ -161,7 +165,7 @@ class PokemonsRepositoryTest {
}
@Test
- fun test_getPokemonById_Cached() = runBlockingTest {
+ fun test_getPokemonById_Cached() = runTest {
val pokemonId = 0L
val pokemon = Mocks.getPokemon()
pokemon.pokemonSchema?.id = pokemonId
@@ -175,7 +179,7 @@ class PokemonsRepositoryTest {
}
@Test
- fun test_getPokemonById_NotCached() = runBlockingTest {
+ fun test_getPokemonById_NotCached() = runTest {
val pokemonId = 0L
val pokemon = Mocks.getPokemon()
Mockito.`when`(dao.getPokemonById(pokemonId)).thenReturn(pokemon)
diff --git a/mvi/build.gradle b/mvi/build.gradle
deleted file mode 100644
index b34e4d0..0000000
--- a/mvi/build.gradle
+++ /dev/null
@@ -1,37 +0,0 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-
-android {
- compileSdkVersion project.compile
- buildToolsVersion project.tools
-
- defaultConfig {
- minSdkVersion project.min
- targetSdkVersion project.target
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- consumerProguardFiles "consumer-rules.pro"
- }
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- }
-}
-
-dependencies {
- implementation fileTree(dir: "libs", include: ["*.jar"])
- implementation "androidx.appcompat:appcompat:$compat"
-
- // lifecycle
- implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_ktx"
-
- // navigation
- implementation "androidx.navigation:navigation-fragment:$nav_version"
- implementation "androidx.navigation:navigation-ui:$nav_version"
-
- implementation "androidx.core:core-ktx:$ktx"
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
-}
diff --git a/mvi/build.gradle.kts b/mvi/build.gradle.kts
new file mode 100644
index 0000000..338bc27
--- /dev/null
+++ b/mvi/build.gradle.kts
@@ -0,0 +1,51 @@
+plugins {
+ id("com.android.library")
+ kotlin("android")
+}
+
+android {
+ namespace = "com.mdgd.mvi"
+ compileSdk = rootProject.extra["compile"] as Int?
+
+ defaultConfig {
+ minSdk = rootProject.extra["min"] as Int?
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_17.toString()
+ }
+ packaging {
+ resources {
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ }
+ }
+}
+
+dependencies {
+ implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
+
+ // lifecycle
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${rootProject.extra["lifecycle_ktx"]}")
+
+ // navigation
+ implementation("androidx.navigation:navigation-fragment-ktx:${rootProject.extra["nav_version"]}")
+ implementation("androidx.navigation:navigation-ui-ktx:${rootProject.extra["nav_version"]}")
+
+ implementation("androidx.core:core-ktx:${rootProject.extra["ktx"]}")
+}
diff --git a/mvi/proguard-rules.pro b/mvi/proguard-rules.pro
index 8207eb6..3e9b0c8 100644
--- a/mvi/proguard-rules.pro
+++ b/mvi/proguard-rules.pro
@@ -1,6 +1,6 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
+# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
@@ -19,4 +19,3 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFil
-e
\ No newline at end of file
diff --git a/mvi/src/main/AndroidManifest.xml b/mvi/src/main/AndroidManifest.xml
index a9bceb7..cc947c5 100644
--- a/mvi/src/main/AndroidManifest.xml
+++ b/mvi/src/main/AndroidManifest.xml
@@ -1 +1 @@
-
+
diff --git a/mvi/src/main/java/com/mdgd/mvi/MviViewModel.kt b/mvi/src/main/java/com/mdgd/mvi/MviViewModel.kt
index 2c8ca18..0f98d11 100644
--- a/mvi/src/main/java/com/mdgd/mvi/MviViewModel.kt
+++ b/mvi/src/main/java/com/mdgd/mvi/MviViewModel.kt
@@ -1,37 +1,40 @@
package com.mdgd.mvi
-import androidx.lifecycle.*
+import androidx.annotation.CallSuper
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
import com.mdgd.mvi.fragments.FragmentContract
+import com.mdgd.mvi.states.AbstractEffect
+import com.mdgd.mvi.states.AbstractState
import com.mdgd.mvi.states.ScreenState
-abstract class MviViewModel, A> : ViewModel(), FragmentContract.ViewModel {
- private val stateHolder = MutableLiveData() // TODO: use StateFlow: val uiState: StateFlow