diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 45b565415..88ea3aa1e 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -1,8 +1,5 @@
-
-
-
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 7ac24c777..7b3006b6e 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -1,17 +1,19 @@
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 703e5d4b8..c855577e5 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
index 7f68460d8..72f00edd3 100644
--- a/.idea/runConfigurations.xml
+++ b/.idea/runConfigurations.xml
@@ -3,6 +3,14 @@
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index b36a55b63..7e0e64d51 100644
--- a/README.md
+++ b/README.md
@@ -20,3 +20,22 @@ Com o passar do tempo identificamos alguns problemas que impedem esse aplicativo
Boa sorte! =)
Ps.: Fique à vontade para editar o projeto inteiro, organização de pastas e módulos, bem como as dependências utilizadas
+
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
+Olá, eu sou a Ana Carolina e é um prazer imenso estar fazendo parte desse processo seletivo.
+
+Aqui está a minha solução para o desafio proposto.
+
+# Desafio PicPay
+- O aplicativo foi desenvolvido em Kotlin.
+- Foi utilizado o padrão de arquitetura MVVM + Clean Architecture, dividindo nos packages data e ui (como domain é opcional e não escolhi fazer o uso de UseCases, esta camada não foi inserida na solução proposta mas é uma ótima abordagem
+para escalabilidade da aplicação quando houver um maior dluxo de dados com serviços externos. Futuramente, essas camadas podem até mesmo ser dividadas em modulos para maior escalabilidade da aplicação.
+- Foi utilizado o Retrofit para requisições HTTP.
+- Foi utilizado o Room para persistência de dados.
+- Foi utilizado o Picasso para carregamento de imagens.
+- Foi utilizado o Koin para injeção de dependências.
+- Foi utilizado o Compose para a declaração da UI.
+- Foi utilizado o Kotlin Coroutines para a execução de tarefas assíncronas.
+- Foi utilizado o Kotlin Flow para a persistência do estado da tela no ViewModel e para uma programação reativa a fim de manter a UI atualizada.
+
diff --git a/app/build.gradle b/app/build.gradle
index a7fbdc0e9..66f0cbf7a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,17 +1,18 @@
-apply plugin: 'com.android.application'
-
-apply plugin: 'kotlin-android'
-
-apply plugin: 'kotlin-android-extensions'
-
-apply plugin: 'kotlin-kapt'
+plugins {
+ id 'com.android.application'
+ id 'kotlin-android'
+ id 'kotlin-kapt'
+ id 'kotlin-parcelize'
+}
android {
- compileSdkVersion 29
+ namespace "com.picpay.desafio.android"
+
defaultConfig {
applicationId "com.picpay.desafio.android"
- minSdkVersion 21
- targetSdkVersion 29
+ minSdkVersion 30
+ compileSdk 34
+ targetSdkVersion 34
versionCode 1
versionName "1.0"
@@ -28,14 +29,25 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
+ buildFeatures {
+ compose true
+ }
compileOptions {
- sourceCompatibility 1.8
- targetCompatibility 1.8
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.5.12"
+ }
kotlinOptions {
- jvmTarget = JavaVersion.VERSION_1_8.toString()
+ jvmTarget = JavaVersion.VERSION_17
+ }
+ kapt {
+ arguments {
+ arg("room.schemaLocation", "$projectDir/schemas".toString())
+ }
}
}
@@ -45,18 +57,22 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "androidx.core:core-ktx:$core_ktx_version"
+ implementation "androidx.compose.ui:ui"
+ implementation 'androidx.compose.material3:material3'
+ implementation 'androidx.compose.material3:material3-window-size-class:1.3.1'
+ implementation "androidx.compose.material:material-icons-extended"
+ implementation "androidx.compose.ui:ui-tooling-preview"
+ implementation "com.google.android.material:material:1.12.0"
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7'
+ implementation 'androidx.activity:activity-compose:1.9.3'
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.constraintlayout:constraintlayout:$constraintlayout_version"
implementation "com.google.android.material:material:$material_version"
- implementation "org.koin:koin-core:$koin_version"
- implementation "org.koin:koin-android:$koin_version"
- implementation "org.koin:koin-androidx-viewmodel:$koin_version"
-
- implementation "com.google.dagger:dagger:$dagger_version"
- kapt "com.google.dagger:dagger-compiler:$dagger_version"
+ implementation "io.insert-koin:koin-core:$koin_version"
+ implementation "io.insert-koin:koin-android:$koin_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
@@ -69,7 +85,7 @@ dependencies {
implementation "io.reactivex.rxjava2:rxjava:$rxjava_version"
implementation "io.reactivex.rxjava2:rxandroid:$rxandroid_version"
- implementation 'com.google.code.gson:gson:2.8.6'
+ implementation 'com.google.code.gson:gson:2.10.1'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit_version"
@@ -80,13 +96,19 @@ dependencies {
implementation "com.squareup.picasso:picasso:$picasso_version"
implementation "de.hdodenhof:circleimageview:$circleimageview_version"
+ implementation "androidx.room:room-runtime:2.6.1"
+ implementation "androidx.room:room-ktx:2.6.1"
+ kapt "androidx.room:room-compiler:2.6.1"
+
testImplementation "junit:junit:$junit_version"
testImplementation "org.mockito:mockito-core:$mockito_version"
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$mockito_kotlin_version"
testImplementation "androidx.arch.core:core-testing:$core_testing_version"
- implementation "org.koin:koin-test:$koin_version"
+ testImplementation "androidx.room:room-testing:2.6.1"
+ implementation "io.insert-koin:koin-test:$koin_version"
androidTestImplementation "androidx.test:runner:$test_runner_version"
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
androidTestImplementation "androidx.test:core-ktx:$core_ktx_test_version"
+ debugImplementation "androidx.compose.ui:ui-tooling"
}
diff --git a/app/src/androidTest/java/com/picpay/desafio/android/MainActivityTest.kt b/app/src/androidTest/java/com/picpay/desafio/android/MainActivityTest.kt
index e4a4978eb..a942cdc09 100644
--- a/app/src/androidTest/java/com/picpay/desafio/android/MainActivityTest.kt
+++ b/app/src/androidTest/java/com/picpay/desafio/android/MainActivityTest.kt
@@ -7,6 +7,7 @@ import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
+import com.picpay.desafio.android.ui.MainActivity
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7bdf2ce38..985f1ec47 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,11 +1,11 @@
+ xmlns:tools="http://schemas.android.com/tools">
-
+
diff --git a/app/src/main/java/com/picpay/desafio/android/MainActivity.kt b/app/src/main/java/com/picpay/desafio/android/MainActivity.kt
deleted file mode 100644
index 2447de98d..000000000
--- a/app/src/main/java/com/picpay/desafio/android/MainActivity.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-package com.picpay.desafio.android
-
-import android.view.View
-import android.widget.ProgressBar
-import android.widget.Toast
-import androidx.appcompat.app.AppCompatActivity
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import com.google.gson.Gson
-import com.google.gson.GsonBuilder
-import okhttp3.OkHttpClient
-import retrofit2.Call
-import retrofit2.Callback
-import retrofit2.Response
-import retrofit2.Retrofit
-import retrofit2.converter.gson.GsonConverterFactory
-
-class MainActivity : AppCompatActivity(R.layout.activity_main) {
-
- private lateinit var recyclerView: RecyclerView
- private lateinit var progressBar: ProgressBar
- private lateinit var adapter: UserListAdapter
-
- private val url = "https://609a908e0f5a13001721b74e.mockapi.io/picpay/api/"
-
- private val gson: Gson by lazy { GsonBuilder().create() }
-
- private val okHttp: OkHttpClient by lazy {
- OkHttpClient.Builder()
- .build()
- }
-
- private val retrofit: Retrofit by lazy {
- Retrofit.Builder()
- .baseUrl(url)
- .client(okHttp)
- .addConverterFactory(GsonConverterFactory.create(gson))
- .build()
- }
-
- private val service: PicPayService by lazy {
- retrofit.create(PicPayService::class.java)
- }
-
- override fun onResume() {
- super.onResume()
-
- recyclerView = findViewById(R.id.recyclerView)
- progressBar = findViewById(R.id.user_list_progress_bar)
-
- adapter = UserListAdapter()
- recyclerView.adapter = adapter
- recyclerView.layoutManager = LinearLayoutManager(this)
-
- progressBar.visibility = View.VISIBLE
- service.getUsers()
- .enqueue(object : Callback> {
- override fun onFailure(call: Call>, t: Throwable) {
- val message = getString(R.string.error)
-
- progressBar.visibility = View.GONE
- recyclerView.visibility = View.GONE
-
- Toast.makeText(this@MainActivity, message, Toast.LENGTH_SHORT)
- .show()
- }
-
- override fun onResponse(call: Call>, response: Response>) {
- progressBar.visibility = View.GONE
-
- adapter.users = response.body()!!
- }
- })
- }
-}
diff --git a/app/src/main/java/com/picpay/desafio/android/PicPayApplication.kt b/app/src/main/java/com/picpay/desafio/android/PicPayApplication.kt
new file mode 100644
index 000000000..b864bb3d6
--- /dev/null
+++ b/app/src/main/java/com/picpay/desafio/android/PicPayApplication.kt
@@ -0,0 +1,21 @@
+package com.picpay.desafio.android
+
+import android.app.Application
+import com.picpay.desafio.android.data.database.di.databaseModule
+import com.picpay.desafio.android.data.di.PicPayServiceFactory
+import com.picpay.desafio.android.data.di.dataModule
+import com.picpay.desafio.android.ui.di.appModule
+import org.koin.android.ext.koin.androidContext
+import org.koin.core.context.startKoin
+
+class PicPayApplication : Application() {
+
+ override fun onCreate() {
+ super.onCreate()
+
+ startKoin {
+ androidContext(this@PicPayApplication)
+ modules(appModule, dataModule, PicPayServiceFactory().picPayServiceModule(), databaseModule)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/picpay/desafio/android/PicPayService.kt b/app/src/main/java/com/picpay/desafio/android/PicPayService.kt
deleted file mode 100644
index c26edac1f..000000000
--- a/app/src/main/java/com/picpay/desafio/android/PicPayService.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.picpay.desafio.android
-
-import retrofit2.Call
-import retrofit2.http.GET
-
-
-interface PicPayService {
-
- @GET("users")
- fun getUsers(): Call>
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/picpay/desafio/android/UserListAdapter.kt b/app/src/main/java/com/picpay/desafio/android/UserListAdapter.kt
deleted file mode 100644
index 538c98a4a..000000000
--- a/app/src/main/java/com/picpay/desafio/android/UserListAdapter.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.picpay.desafio.android
-
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.RecyclerView
-
-class UserListAdapter : RecyclerView.Adapter() {
-
- var users = emptyList()
- set(value) {
- val result = DiffUtil.calculateDiff(
- UserListDiffCallback(
- field,
- value
- )
- )
- result.dispatchUpdatesTo(this)
- field = value
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserListItemViewHolder {
- val view = LayoutInflater.from(parent.context)
- .inflate(R.layout.list_item_user, parent, false)
-
- return UserListItemViewHolder(view)
- }
-
- override fun onBindViewHolder(holder: UserListItemViewHolder, position: Int) {
- holder.bind(users[position])
- }
-
- override fun getItemCount(): Int = users.size
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/picpay/desafio/android/UserListDiffCallback.kt b/app/src/main/java/com/picpay/desafio/android/UserListDiffCallback.kt
deleted file mode 100644
index 7c734d37b..000000000
--- a/app/src/main/java/com/picpay/desafio/android/UserListDiffCallback.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.picpay.desafio.android
-
-import androidx.recyclerview.widget.DiffUtil
-import com.picpay.desafio.android.User
-
-class UserListDiffCallback(
- private val oldList: List,
- private val newList: List
-) : DiffUtil.Callback() {
-
- override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
- return oldList[oldItemPosition].username.equals(newList[newItemPosition].username)
- }
-
- override fun getOldListSize(): Int {
- return oldList.size
- }
-
- override fun getNewListSize(): Int {
- return newList.size
- }
-
- override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
- return true
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/picpay/desafio/android/UserListItemViewHolder.kt b/app/src/main/java/com/picpay/desafio/android/UserListItemViewHolder.kt
deleted file mode 100644
index 1d8240eb3..000000000
--- a/app/src/main/java/com/picpay/desafio/android/UserListItemViewHolder.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.picpay.desafio.android
-
-import android.view.View
-import androidx.recyclerview.widget.RecyclerView
-import com.squareup.picasso.Callback
-import com.squareup.picasso.Picasso
-import kotlinx.android.synthetic.main.list_item_user.view.*
-
-class UserListItemViewHolder(
- itemView: View
-) : RecyclerView.ViewHolder(itemView) {
-
- fun bind(user: User) {
- itemView.name.text = user.name
- itemView.username.text = user.username
- itemView.progressBar.visibility = View.VISIBLE
- Picasso.get()
- .load(user.img)
- .error(R.drawable.ic_round_account_circle)
- .into(itemView.picture, object : Callback {
- override fun onSuccess() {
- itemView.progressBar.visibility = View.GONE
- }
-
- override fun onError(e: Exception?) {
- itemView.progressBar.visibility = View.GONE
- }
- })
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/picpay/desafio/android/data/UserRepository.kt b/app/src/main/java/com/picpay/desafio/android/data/UserRepository.kt
new file mode 100644
index 000000000..39351bff6
--- /dev/null
+++ b/app/src/main/java/com/picpay/desafio/android/data/UserRepository.kt
@@ -0,0 +1,14 @@
+package com.picpay.desafio.android.data
+
+import com.picpay.desafio.android.data.model.User
+import com.picpay.desafio.android.data.network.PicPayService
+
+interface UserRepository {
+ suspend fun getUsers(): List
+}
+
+class UserRepositoryImpl(
+ private val picPayService: PicPayService
+) : UserRepository {
+ override suspend fun getUsers(): List = picPayService.getUsers()
+}
diff --git a/app/src/main/java/com/picpay/desafio/android/data/coroutine/DispatcherProvider.kt b/app/src/main/java/com/picpay/desafio/android/data/coroutine/DispatcherProvider.kt
new file mode 100644
index 000000000..9d92c75ae
--- /dev/null
+++ b/app/src/main/java/com/picpay/desafio/android/data/coroutine/DispatcherProvider.kt
@@ -0,0 +1,16 @@
+package com.picpay.desafio.android.data.coroutine
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+
+interface DispatcherProvider {
+ val Main: CoroutineDispatcher
+ val IO: CoroutineDispatcher
+ val Default: CoroutineDispatcher
+}
+
+class DispatcherProviderImpl : DispatcherProvider {
+ override val Main: CoroutineDispatcher = Dispatchers.Main
+ override val IO: CoroutineDispatcher = Dispatchers.IO
+ override val Default: CoroutineDispatcher = Dispatchers.Default
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/picpay/desafio/android/data/database/OfflineDataRepository.kt b/app/src/main/java/com/picpay/desafio/android/data/database/OfflineDataRepository.kt
new file mode 100644
index 000000000..029a141a4
--- /dev/null
+++ b/app/src/main/java/com/picpay/desafio/android/data/database/OfflineDataRepository.kt
@@ -0,0 +1,12 @@
+package com.picpay.desafio.android.data.database
+
+class OfflineDataRepository(private val userDao: UserDao) : UserDatabaseRepository {
+
+ override suspend fun insert(users: List) {
+ userDao.insert(users)
+ }
+
+ override suspend fun getUsers(): List {
+ return userDao.getUsers()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/picpay/desafio/android/data/database/UserDao.kt b/app/src/main/java/com/picpay/desafio/android/data/database/UserDao.kt
new file mode 100644
index 000000000..2b042e8a6
--- /dev/null
+++ b/app/src/main/java/com/picpay/desafio/android/data/database/UserDao.kt
@@ -0,0 +1,15 @@
+package com.picpay.desafio.android.data.database
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+
+@Dao
+interface UserDao {
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insert(users: List)
+
+ @Query("SELECT * FROM users")
+ suspend fun getUsers(): List
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/picpay/desafio/android/data/database/UserDatabase.kt b/app/src/main/java/com/picpay/desafio/android/data/database/UserDatabase.kt
new file mode 100644
index 000000000..fc5f5776e
--- /dev/null
+++ b/app/src/main/java/com/picpay/desafio/android/data/database/UserDatabase.kt
@@ -0,0 +1,24 @@
+package com.picpay.desafio.android.data.database
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+
+@Database(entities = [UserEntity::class], version = 1, exportSchema = false)
+abstract class PicPayDatabase : RoomDatabase() {
+ abstract fun userDao(): UserDao
+}
+
+fun getDatabase(context: Context): PicPayDatabase {
+ return Room.databaseBuilder(
+ context.applicationContext,
+ PicPayDatabase::class.java,
+ "picpay_database"
+ )
+ .fallbackToDestructiveMigration()
+ .build()
+}
+
+
+fun getDao(database: PicPayDatabase) = database.userDao()
diff --git a/app/src/main/java/com/picpay/desafio/android/data/database/UserDatabaseRepository.kt b/app/src/main/java/com/picpay/desafio/android/data/database/UserDatabaseRepository.kt
new file mode 100644
index 000000000..3a221b4a8
--- /dev/null
+++ b/app/src/main/java/com/picpay/desafio/android/data/database/UserDatabaseRepository.kt
@@ -0,0 +1,8 @@
+package com.picpay.desafio.android.data.database
+
+interface UserDatabaseRepository {
+
+ suspend fun insert(users: List)
+
+ suspend fun getUsers(): List
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/picpay/desafio/android/data/database/UserEntity.kt b/app/src/main/java/com/picpay/desafio/android/data/database/UserEntity.kt
new file mode 100644
index 000000000..35b81f21c
--- /dev/null
+++ b/app/src/main/java/com/picpay/desafio/android/data/database/UserEntity.kt
@@ -0,0 +1,13 @@
+package com.picpay.desafio.android.data.database
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "users")
+data class UserEntity(
+ @PrimaryKey val id: Int,
+ @ColumnInfo(name = "username") val username: String,
+ @ColumnInfo(name = "name") val name: String,
+ @ColumnInfo(name = "img") val img: String?
+)
diff --git a/app/src/main/java/com/picpay/desafio/android/data/database/di/DatabaseModule.kt b/app/src/main/java/com/picpay/desafio/android/data/database/di/DatabaseModule.kt
new file mode 100644
index 000000000..649270eaf
--- /dev/null
+++ b/app/src/main/java/com/picpay/desafio/android/data/database/di/DatabaseModule.kt
@@ -0,0 +1,14 @@
+package com.picpay.desafio.android.data.database.di
+
+import com.picpay.desafio.android.data.database.OfflineDataRepository
+import com.picpay.desafio.android.data.database.UserDatabaseRepository
+import com.picpay.desafio.android.data.database.getDao
+import com.picpay.desafio.android.data.database.getDatabase
+import org.koin.android.ext.koin.androidContext
+import org.koin.dsl.module
+
+val databaseModule = module {
+ single { getDatabase(androidContext()) }
+ single { getDao(get()) }
+ single { OfflineDataRepository(get()) }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/picpay/desafio/android/data/di/DataModule.kt b/app/src/main/java/com/picpay/desafio/android/data/di/DataModule.kt
new file mode 100644
index 000000000..400c98bd4
--- /dev/null
+++ b/app/src/main/java/com/picpay/desafio/android/data/di/DataModule.kt
@@ -0,0 +1,43 @@
+package com.picpay.desafio.android.data.di
+
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import com.picpay.desafio.android.data.UserRepository
+import com.picpay.desafio.android.data.UserRepositoryImpl
+import com.picpay.desafio.android.data.network.PicPayService
+import okhttp3.Cache
+import okhttp3.OkHttpClient
+import org.koin.core.component.KoinComponent
+import org.koin.core.module.Module
+import org.koin.dsl.module
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import java.io.File
+
+val dataModule = module {
+ single { UserRepositoryImpl(get()) }
+}
+
+class PicPayServiceFactory : KoinComponent {
+ private val baseUrl = "https://609a908e0f5a13001721b74e.mockapi.io/picpay/api/"
+
+ private val gson: Gson by lazy { GsonBuilder().create() }
+
+ val cacheSize = (10 * 1021 * 1024).toLong() // 10MB
+ val cache = Cache(File("cacheDir"), cacheSize)
+ val okHttpClient = OkHttpClient.Builder()
+ .cache(cache)
+ .build()
+
+ private val retrofit: Retrofit = Retrofit.Builder()
+ .addConverterFactory(GsonConverterFactory.create(gson))
+ .client(okHttpClient)
+ .baseUrl(baseUrl)
+ .build()
+
+ fun picPayServiceModule(): Module {
+ return module {
+ single { retrofit.create(PicPayService::class.java) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/picpay/desafio/android/User.kt b/app/src/main/java/com/picpay/desafio/android/data/model/User.kt
similarity index 53%
rename from app/src/main/java/com/picpay/desafio/android/User.kt
rename to app/src/main/java/com/picpay/desafio/android/data/model/User.kt
index aa28171c9..da5f50b28 100644
--- a/app/src/main/java/com/picpay/desafio/android/User.kt
+++ b/app/src/main/java/com/picpay/desafio/android/data/model/User.kt
@@ -1,13 +1,13 @@
-package com.picpay.desafio.android
+package com.picpay.desafio.android.data.model
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
-import kotlinx.android.parcel.Parcelize
+import kotlinx.parcelize.Parcelize
@Parcelize
data class User(
- @SerializedName("img") val img: String,
- @SerializedName("name") val name: String,
@SerializedName("id") val id: Int,
- @SerializedName("username") val username: String
+ @SerializedName("username") val username: String,
+ @SerializedName("name") val name: String,
+ @SerializedName("img") val img: String?
) : Parcelable
\ No newline at end of file
diff --git a/app/src/main/java/com/picpay/desafio/android/data/model/UserUiData.kt b/app/src/main/java/com/picpay/desafio/android/data/model/UserUiData.kt
new file mode 100644
index 000000000..6eeb10bb6
--- /dev/null
+++ b/app/src/main/java/com/picpay/desafio/android/data/model/UserUiData.kt
@@ -0,0 +1,10 @@
+package com.picpay.desafio.android.data.model
+
+import android.graphics.Bitmap
+
+data class UserUiData(
+ val id: Int,
+ val username: String,
+ val name: String,
+ val imgBitmap: Bitmap?
+)
diff --git a/app/src/main/java/com/picpay/desafio/android/data/network/PicPayService.kt b/app/src/main/java/com/picpay/desafio/android/data/network/PicPayService.kt
new file mode 100644
index 000000000..9e24e05b3
--- /dev/null
+++ b/app/src/main/java/com/picpay/desafio/android/data/network/PicPayService.kt
@@ -0,0 +1,10 @@
+package com.picpay.desafio.android.data.network
+
+import com.picpay.desafio.android.data.model.User
+import retrofit2.http.GET
+
+interface PicPayService {
+
+ @GET("users")
+ suspend fun getUsers(): List
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/picpay/desafio/android/ui/MainActivity.kt b/app/src/main/java/com/picpay/desafio/android/ui/MainActivity.kt
new file mode 100644
index 000000000..1291167e3
--- /dev/null
+++ b/app/src/main/java/com/picpay/desafio/android/ui/MainActivity.kt
@@ -0,0 +1,24 @@
+package com.picpay.desafio.android.ui
+
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.appcompat.app.AppCompatActivity
+import com.picpay.desafio.android.ui.contacts.ContactsScreen
+import com.picpay.desafio.android.ui.contacts.ContactsViewModel
+import org.koin.androidx.viewmodel.ext.android.viewModel
+
+class MainActivity : AppCompatActivity() {
+
+ private val activityViewModel by viewModel()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ activityViewModel.getUsers()
+
+ setContent {
+ ContactsScreen()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/picpay/desafio/android/ui/contacts/ContactsScreen.kt b/app/src/main/java/com/picpay/desafio/android/ui/contacts/ContactsScreen.kt
new file mode 100644
index 000000000..bf14a538d
--- /dev/null
+++ b/app/src/main/java/com/picpay/desafio/android/ui/contacts/ContactsScreen.kt
@@ -0,0 +1,155 @@
+package com.picpay.desafio.android.ui.contacts
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+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.foundation.shape.CircleShape
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.picpay.desafio.android.R
+import com.picpay.desafio.android.data.model.UserUiData
+
+@Composable
+fun ContactsScreen(activityViewModel: ContactsViewModel = viewModel()) {
+ val userList by activityViewModel.usersUiData.collectAsState()
+ val isLoading by activityViewModel.isLoading.collectAsState()
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.Red),
+ ) {
+ Scaffold(
+ topBar = {
+ Row(
+ modifier = Modifier.padding(start = 24.dp, top = 48.dp, bottom = 24.dp)
+ ) {
+ Text(
+ text = stringResource(R.string.title),
+ color = Color.White,
+ fontSize = 28.sp,
+ fontWeight = FontWeight.Bold,
+ )
+ }
+ },
+ containerColor = colorResource(R.color.colorPrimaryDark)
+ ) { paddingValues ->
+ UserList(userList, paddingValues)
+
+ if(isLoading) {
+ ProgressBar()
+ }
+ }
+ }
+}
+
+@Composable
+fun UserList(
+ userList: List,
+ paddingValues: PaddingValues
+) {
+ LazyColumn(modifier = Modifier.padding(paddingValues)) {
+ items(userList) { user ->
+ UserItem(user)
+ }
+ }
+
+}
+
+@Composable
+fun UserItem(
+ user: UserUiData
+) {
+ Row(
+ modifier = Modifier.fillMaxSize(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ if (user.imgBitmap != null) {
+ Image(
+ bitmap = user.imgBitmap.asImageBitmap(),
+ contentDescription = "Imagem do usuário",
+ modifier = Modifier
+ .padding(vertical = 12.dp, horizontal = 24.dp)
+ .size(52.dp)
+ .clip(CircleShape)
+ )
+ } else {
+ Image(
+ painter = painterResource(R.drawable.ic_round_account_circle),
+ contentDescription = "Ícone de usuário",
+ modifier = Modifier
+ .padding(vertical = 12.dp, horizontal = 24.dp)
+ .size(52.dp)
+ )
+ }
+
+ Column {
+ Text(
+ text = "@${user.username}",
+ color = Color.White,
+ fontSize = 14.sp,
+ modifier = Modifier.padding(top = 8.dp, end = 16.dp)
+ )
+ Text(
+ text = user.name,
+ color = colorResource(R.color.colorDetail),
+ fontSize = 16.sp,
+ modifier = Modifier.padding(bottom = 8.dp)
+ )
+ }
+ }
+}
+
+@Composable
+fun ProgressBar(modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier
+ .clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ onClick = {},
+ )
+ .fillMaxSize()
+ .padding(top = 100.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ CircularProgressIndicator(
+ color = Color.Green,
+ modifier = modifier
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun ContactsScreenPreview() {
+ ContactsScreen()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/picpay/desafio/android/ui/contacts/ContactsViewModel.kt b/app/src/main/java/com/picpay/desafio/android/ui/contacts/ContactsViewModel.kt
new file mode 100644
index 000000000..4698fe355
--- /dev/null
+++ b/app/src/main/java/com/picpay/desafio/android/ui/contacts/ContactsViewModel.kt
@@ -0,0 +1,109 @@
+package com.picpay.desafio.android.ui.contacts
+
+import android.graphics.Bitmap
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.picpay.desafio.android.data.UserRepository
+import com.picpay.desafio.android.data.coroutine.DispatcherProvider
+import com.picpay.desafio.android.data.database.UserDatabaseRepository
+import com.picpay.desafio.android.data.database.UserEntity
+import com.picpay.desafio.android.data.model.User
+import com.picpay.desafio.android.data.model.UserUiData
+import com.squareup.picasso.Picasso
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
+
+class ContactsViewModel : ViewModel(), KoinComponent {
+
+ private val userRepository: UserRepository by inject()
+ private val dispatcherProvider: DispatcherProvider by inject()
+ private val userDatabaseRepository: UserDatabaseRepository by inject()
+
+ internal val users = MutableStateFlow(emptyList())
+
+ private val _isLoading = MutableStateFlow(false)
+ val isLoading: StateFlow get() = _isLoading.asStateFlow()
+
+ val usersUiData: StateFlow> = users.map { users ->
+ users.map { user ->
+ val image = if (user.img != null) getBitmapFromUrl(user.img) else null
+ user.toUserUiData(image)
+ }
+ }.stateIn(
+ viewModelScope,
+ SharingStarted.Eagerly,
+ emptyList()
+ )
+
+ private fun List.toUserEntity(): List = map { user ->
+ UserEntity(user.id, user.username, user.name, user.img)
+ }
+
+ private suspend fun getBitmapFromUrl(url: String): Bitmap? {
+ return withContext(dispatcherProvider.IO) {
+ Picasso.get().load(url).get()
+ }
+ }
+
+ private fun User.toUserUiData(imgBitmap: Bitmap?): UserUiData {
+ return UserUiData(id, username, name, imgBitmap)
+ }
+
+ private fun List.toUser(): List = map { userEntity ->
+ User(userEntity.id, userEntity.username, userEntity.name, userEntity.img)
+ }
+
+ fun getUsers() {
+ viewModelScope.launch {
+ try {
+ _isLoading.value = true
+
+ val newUsers = userRepository.getUsers()
+
+ users.value = newUsers
+
+ addUsersToDatabase(newUsers)
+
+ _isLoading.value = false
+ } catch (e: Exception) {
+ println("Error: ${e.message}")
+ users.update { getUsersFromDatabase() }
+ _isLoading.value = false
+ }
+ }
+ }
+
+ private suspend fun getUsersFromDatabase(): List =
+ userDatabaseRepository.getUsers().toUser()
+
+ private fun addUsersToDatabase(newUsers: List) {
+ viewModelScope.launch(dispatcherProvider.IO) {
+ val currentUsers = getUsersFromDatabase()
+
+ if (currentUsers.isNotEmpty()) {
+ if (currentUsers.size == newUsers.size &&
+ currentUsers.containsAll(newUsers)
+ ) {
+ return@launch
+ } else {
+ insertUsersToDatabase(newUsers)
+ }
+ }
+ }
+ }
+
+ private fun insertUsersToDatabase(users: List) {
+ viewModelScope.launch(dispatcherProvider.IO) {
+ userDatabaseRepository.insert(users.toUserEntity())
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/picpay/desafio/android/ui/di/AppModule.kt b/app/src/main/java/com/picpay/desafio/android/ui/di/AppModule.kt
new file mode 100644
index 000000000..56f260ac4
--- /dev/null
+++ b/app/src/main/java/com/picpay/desafio/android/ui/di/AppModule.kt
@@ -0,0 +1,14 @@
+package com.picpay.desafio.android.ui.di
+
+import com.picpay.desafio.android.data.coroutine.DispatcherProvider
+import com.picpay.desafio.android.data.coroutine.DispatcherProviderImpl
+import com.picpay.desafio.android.ui.contacts.ContactsViewModel
+import org.koin.androidx.viewmodel.dsl.viewModelOf
+import org.koin.dsl.module
+
+val appModule = module {
+
+ viewModelOf(::ContactsViewModel)
+
+ single { DispatcherProviderImpl() }
+}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 487ac549e..000000000
--- a/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/list_item_user.xml b/app/src/main/res/layout/list_item_user.xml
deleted file mode 100644
index 587d40cc8..000000000
--- a/app/src/main/res/layout/list_item_user.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/test/java/com/picpay/desafio/android/ExampleService.kt b/app/src/test/java/com/picpay/desafio/android/ExampleService.kt
deleted file mode 100644
index 0199c5e4a..000000000
--- a/app/src/test/java/com/picpay/desafio/android/ExampleService.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.picpay.desafio.android
-
-class ExampleService(
- private val service: PicPayService
-) {
-
- fun example(): List {
- val users = service.getUsers().execute()
-
- return users.body() ?: emptyList()
- }
-}
\ No newline at end of file
diff --git a/app/src/test/java/com/picpay/desafio/android/ExampleServiceTest.kt b/app/src/test/java/com/picpay/desafio/android/ExampleServiceTest.kt
deleted file mode 100644
index 843c0e776..000000000
--- a/app/src/test/java/com/picpay/desafio/android/ExampleServiceTest.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.picpay.desafio.android
-
-import com.nhaarman.mockitokotlin2.mock
-import com.nhaarman.mockitokotlin2.whenever
-import junit.framework.Assert.assertEquals
-import org.junit.Test
-import retrofit2.Call
-import retrofit2.Response
-
-class ExampleServiceTest {
-
- private val api = mock()
-
- private val service = ExampleService(api)
-
- @Test
- fun exampleTest() {
- // given
- val call = mock>>()
- val expectedUsers = emptyList()
-
- whenever(call.execute()).thenReturn(Response.success(expectedUsers))
- whenever(api.getUsers()).thenReturn(call)
-
- // when
- val users = service.example()
-
- // then
- assertEquals(users, expectedUsers)
- }
-}
\ No newline at end of file
diff --git a/app/src/test/java/com/picpay/desafio/android/ui/contacts/ContactsViewModelTest.kt b/app/src/test/java/com/picpay/desafio/android/ui/contacts/ContactsViewModelTest.kt
new file mode 100644
index 000000000..d80fd9506
--- /dev/null
+++ b/app/src/test/java/com/picpay/desafio/android/ui/contacts/ContactsViewModelTest.kt
@@ -0,0 +1,123 @@
+package com.picpay.desafio.android.ui.contacts
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import com.nhaarman.mockitokotlin2.whenever
+import com.picpay.desafio.android.data.UserRepository
+import com.picpay.desafio.android.data.coroutine.DispatcherProvider
+import com.picpay.desafio.android.data.database.UserDatabaseRepository
+import com.picpay.desafio.android.data.model.User
+import com.picpay.desafio.android.data.model.UserUiData
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestRule
+import org.koin.dsl.module
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import kotlin.test.assertFalse
+
+class ContactsViewModelTest {
+
+ private lateinit var sut: ContactsViewModel
+
+ @get:Rule
+ val rule: TestRule = InstantTaskExecutorRule()
+
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule()
+
+ private val userRepositoryMocked = mock()
+ private val userDatabaseRepositoryMocked = mock()
+
+ @get:Rule
+ val koinRule = KoinTestRule(
+ module {
+ single { userRepositoryMocked }
+ single { userDatabaseRepositoryMocked }
+ single { TestDispatcherProvider() }
+ }
+ )
+
+ @Before
+ fun setup() {
+ sut = ContactsViewModel()
+ }
+
+ @Test
+ fun `on init, assert loading is false`() {
+ assertFalse(sut.isLoading.value)
+ }
+
+ @Test
+ fun `on init, assert usersUiData is emptyList`() {
+ assertEquals(emptyList(), sut.usersUiData.value)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun `on getUsers, when endpoint returns success, assert that usersUiData is set`() = runTest {
+ whenever(userDatabaseRepositoryMocked.getUsers()).thenReturn(emptyList())
+
+ val usersMocked = listOf(
+ User(
+ 1,
+ "username",
+ "name",
+ null
+ ),
+ User(
+ 2,
+ "username",
+ "name",
+ null
+ )
+ )
+
+ val expectedResult = listOf(
+ UserUiData(
+ 1,
+ "username",
+ "name",
+ null
+ ),
+ UserUiData(
+ 2,
+ "username",
+ "name",
+ null
+ )
+ )
+
+ backgroundScope.launch(UnconfinedTestDispatcher()) { sut.users.collect() }
+ backgroundScope.launch(UnconfinedTestDispatcher()) { sut.usersUiData.collect() }
+
+ whenever(userRepositoryMocked.getUsers()).thenReturn(usersMocked)
+
+ sut.getUsers()
+
+ assertEquals(expectedResult, sut.usersUiData.value)
+ verify(userDatabaseRepositoryMocked, times(1)).getUsers()
+ }
+
+// @OptIn(ExperimentalCoroutinesApi::class)
+// @Test
+// fun `on getUsers, when endpoint returns failure and no cache data, assert that usersUiData is set`() = runTest {
+// backgroundScope.launch(UnconfinedTestDispatcher()) { sut.users.collect() }
+// backgroundScope.launch(UnconfinedTestDispatcher()) { sut.usersUiData.collect() }
+//
+// whenever(userRepositoryMocked.getUsers()).thenThrow(HttpException(mock()))
+// whenever(userDatabaseRepositoryMocked.getUsers()).thenReturn(emptyList())
+//
+// sut.getUsers()
+//
+// assertEquals(emptyList(), sut.usersUiData.value)
+// verify(userDatabaseRepositoryMocked, never()).getUsers()
+// }
+}
\ No newline at end of file
diff --git a/app/src/test/java/com/picpay/desafio/android/ui/contacts/KoinTestRule.kt b/app/src/test/java/com/picpay/desafio/android/ui/contacts/KoinTestRule.kt
new file mode 100644
index 000000000..870548a27
--- /dev/null
+++ b/app/src/test/java/com/picpay/desafio/android/ui/contacts/KoinTestRule.kt
@@ -0,0 +1,21 @@
+package com.picpay.desafio.android.ui.contacts
+
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import org.koin.core.context.startKoin
+import org.koin.core.context.stopKoin
+import org.koin.core.module.Module
+
+class KoinTestRule(
+ private val module: Module
+) : TestWatcher() {
+ override fun starting(description: Description) {
+ startKoin {
+ modules(module)
+ }
+ }
+
+ override fun finished(description: Description) {
+ stopKoin()
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/com/picpay/desafio/android/ui/contacts/MainDispacherRule.kt b/app/src/test/java/com/picpay/desafio/android/ui/contacts/MainDispacherRule.kt
new file mode 100644
index 000000000..2d6af1dfe
--- /dev/null
+++ b/app/src/test/java/com/picpay/desafio/android/ui/contacts/MainDispacherRule.kt
@@ -0,0 +1,23 @@
+package com.picpay.desafio.android.ui.contacts
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class MainDispatcherRule(
+ private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
+) : TestWatcher() {
+ override fun starting(description: Description) {
+ Dispatchers.setMain(testDispatcher)
+ }
+
+ override fun finished(description: Description) {
+ Dispatchers.resetMain()
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/com/picpay/desafio/android/ui/contacts/TestDispatcherProvider.kt b/app/src/test/java/com/picpay/desafio/android/ui/contacts/TestDispatcherProvider.kt
new file mode 100644
index 000000000..a05419140
--- /dev/null
+++ b/app/src/test/java/com/picpay/desafio/android/ui/contacts/TestDispatcherProvider.kt
@@ -0,0 +1,16 @@
+package com.picpay.desafio.android.ui.contacts
+
+import com.picpay.desafio.android.data.coroutine.DispatcherProvider
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class TestDispatcherProvider(
+ testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
+) : DispatcherProvider {
+ override val Default: CoroutineDispatcher = testDispatcher
+ override val IO: CoroutineDispatcher = testDispatcher
+ override val Main: CoroutineDispatcher = testDispatcher
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 7d1b94f34..38773a572 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,42 +2,44 @@
buildscript {
ext {
- kotlin_version = '1.3.61'
+ kotlin_version = '1.9.23'
- appcompat_version = '1.1.0'
- core_ktx_version = '1.2.0'
- core_testing_version = '2.1.0'
- constraintlayout_version = '1.1.3'
- material_version = "1.1.0"
+ appcompat_version = '1.7.0'
+ core_ktx_version = '1.13.1'
+ core_testing_version = '2.2.0'
+ constraintlayout_version = '2.2.0'
+ material_version = "1.12.0"
moshi_version = '1.8.0'
- retrofit_version = '2.7.1'
- okhttp_version = '4.3.1'
+ retrofit_version = '2.9.0'
+ okhttp_version = '4.11.0'
picasso_version = '2.71828'
circleimageview_version = '3.0.0'
- junit_version = '4.12'
- mockito_version = '2.27.0'
+ junit_version = '4.13.2'
+ mockito_version = '5.7.0'
mockito_kotlin_version = '2.1.0'
- test_runner_version = '1.1.1'
- espresso_version = '3.1.1'
+ test_runner_version = '1.6.2'
+ espresso_version = '3.6.1'
- koin_version = "2.0.1"
- dagger_version = "2.23.2"
- lifecycle_version = "2.2.0"
- coroutines_version = "1.3.3"
+ koin_version = "3.2.0"
+ lifecycle_version = "2.8.7"
+ coroutines_version = "1.8.0"
rxjava_version = "2.2.17"
rxandroid_version = "2.1.1"
- core_ktx_test_version = "1.2.0"
+ core_ktx_test_version = "1.6.1"
+ agp_version = '8.5.0'
+ agp_version1 = '8.7.0'
+ agp_version2 = '8.3.0'
}
repositories {
google()
- jcenter()
+ mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.5.3'
+ classpath "com.android.tools.build:gradle:$agp_version2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -47,11 +49,11 @@ buildscript {
allprojects {
repositories {
google()
- jcenter()
+ mavenCentral()
}
}
-task clean(type: Delete) {
- delete rootProject.buildDir
+tasks.register('clean', Delete) {
+ delete("${rootProject.projectDir.path}/build")
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 31680f1d6..d8b4cb144 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sun Feb 16 19:36:17 BRT 2020
+#Mon Dec 16 20:32:29 BRT 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip