diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ba6cfa1..8eebcdc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,7 +11,8 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="false" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:hardwareAccelerated="true"> ) + @Query("UPDATE ${UserEntity.TABLE_NAME} SET isFavorite = :favorite WHERE id = :id") + fun saveFavorite(id: Int, favorite: Boolean): Int + @get:Query("SELECT * FROM ${UserEntity.TABLE_NAME}") val all: List - @Query("SELECT * FROM ${UserEntity.TABLE_NAME}") + @Query("SELECT * FROM ${UserEntity.TABLE_NAME} ORDER BY id ASC") fun getAllUsers(): List + @Query("SELECT * FROM ${UserEntity.TABLE_NAME} WHERE isFavorite = 1 ORDER BY id ASC") + fun getFavoriteUsers(): List + @Query("SELECT COUNT(*) FROM ${UserEntity.TABLE_NAME}") fun count(): Int diff --git a/base/src/main/java/com/hako/base/domain/database/entities/User.kt b/base/src/main/java/com/hako/base/domain/database/entities/User.kt index c359911..14e5be0 100644 --- a/base/src/main/java/com/hako/base/domain/database/entities/User.kt +++ b/base/src/main/java/com/hako/base/domain/database/entities/User.kt @@ -12,7 +12,8 @@ data class UserEntity( val userName: String, val email: String, val phone: String, - val website: String + val website: String, + val isFavorite: Boolean = true ) { companion object { const val TABLE_NAME = "users" diff --git a/base/src/main/java/com/hako/base/domain/network/RequestStatus.kt b/base/src/main/java/com/hako/base/domain/network/RequestStatus.kt index d34b084..f599907 100644 --- a/base/src/main/java/com/hako/base/domain/network/RequestStatus.kt +++ b/base/src/main/java/com/hako/base/domain/network/RequestStatus.kt @@ -4,4 +4,5 @@ sealed class RequestStatus { object Ready : RequestStatus() object Loading : RequestStatus() object Errored : RequestStatus() + object Empty : RequestStatus() } diff --git a/base/src/main/java/com/hako/base/extensions/DataExtensions.kt b/base/src/main/java/com/hako/base/extensions/DataExtensions.kt index f4ad2c3..f469a26 100644 --- a/base/src/main/java/com/hako/base/extensions/DataExtensions.kt +++ b/base/src/main/java/com/hako/base/extensions/DataExtensions.kt @@ -11,3 +11,5 @@ fun LiveData.observeNonNull(owner: LifecycleOwner, func: (T) -> Unit) { } }) } + +fun Int.wasUpdated() = this > 0 \ No newline at end of file diff --git a/base/src/main/java/com/hako/base/navigation/ShowFabButton.kt b/base/src/main/java/com/hako/base/navigation/ShowFabButton.kt new file mode 100644 index 0000000..20475f8 --- /dev/null +++ b/base/src/main/java/com/hako/base/navigation/ShowFabButton.kt @@ -0,0 +1,3 @@ +package com.hako.base.navigation + +interface ShowFabButton \ No newline at end of file diff --git a/base/src/main/java/com/hako/base/widgets/EmptyOverlay.kt b/base/src/main/java/com/hako/base/widgets/EmptyOverlay.kt new file mode 100644 index 0000000..cf4ace9 --- /dev/null +++ b/base/src/main/java/com/hako/base/widgets/EmptyOverlay.kt @@ -0,0 +1,23 @@ +package com.hako.base.widgets + +import android.content.Context +import android.util.AttributeSet +import android.widget.FrameLayout +import com.hako.base.R +import com.hako.base.extensions.inflate +import kotlinx.android.synthetic.main.empty_overlay.view.* + +class EmptyOverlay @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr) { + + init { + inflate(R.layout.empty_overlay, true) + } + + fun setLabel(message: String) { + empty_overlay_label.text = message + } +} \ No newline at end of file diff --git a/base/src/main/java/com/hako/base/widgets/LikeButton.kt b/base/src/main/java/com/hako/base/widgets/LikeButton.kt index 357dbe1..3757df0 100644 --- a/base/src/main/java/com/hako/base/widgets/LikeButton.kt +++ b/base/src/main/java/com/hako/base/widgets/LikeButton.kt @@ -10,7 +10,7 @@ import kotlinx.android.synthetic.main.like_button.view.* private const val LIKE_MIN_FRAME = 0 private const val LIKE_MAX_FRAME = 28 private const val LIKE_ANIM_SPEED = 1f -private const val DISLIKE_MIN_FRAME = 28 +private const val DISLIKE_MIN_FRAME = 29 private const val DISLIKE_MAX_FRAME = 70 private const val DISLIKE_ANIM_SPEED = 2f @@ -29,16 +29,24 @@ class LikeButton @JvmOverloads constructor( } fun dislike() { - like_button_animation_view.frame = LIKE_MIN_FRAME + like_button_animation_view.frame = DISLIKE_MAX_FRAME } - fun playLike() { + fun play() { + if (like_button_animation_view.frame <= LIKE_MAX_FRAME){ + playDislike() + } else { + playLike() + } + } + + private fun playLike() { like_button_animation_view.setMinAndMaxFrame(LIKE_MIN_FRAME, LIKE_MAX_FRAME) like_button_animation_view.speed = LIKE_ANIM_SPEED like_button_animation_view.playAnimation() } - fun playDislike() { + private fun playDislike() { like_button_animation_view.setMinAndMaxFrame(DISLIKE_MIN_FRAME, DISLIKE_MAX_FRAME) like_button_animation_view.speed = DISLIKE_ANIM_SPEED like_button_animation_view.playAnimation() diff --git a/base/src/main/res/layout/empty_overlay.xml b/base/src/main/res/layout/empty_overlay.xml new file mode 100644 index 0000000..5f7eb83 --- /dev/null +++ b/base/src/main/res/layout/empty_overlay.xml @@ -0,0 +1,33 @@ + + + + + + + + \ No newline at end of file diff --git a/base/src/main/res/layout/network_error_overlay.xml b/base/src/main/res/layout/network_error_overlay.xml index 72ee319..0aee1cc 100644 --- a/base/src/main/res/layout/network_error_overlay.xml +++ b/base/src/main/res/layout/network_error_overlay.xml @@ -6,7 +6,7 @@ android:background="@color/soft_background"> + app:layout_constraintEnd_toEndOf="@+id/network_error_overlay_animation_view" + app:layout_constraintStart_toStartOf="@+id/network_error_overlay_animation_view" + app:layout_constraintTop_toBottomOf="@+id/network_error_overlay_animation_view" /> \ No newline at end of file diff --git a/base/src/main/res/raw/empty_animation.json b/base/src/main/res/raw/empty_animation.json new file mode 100644 index 0000000..f79bf38 --- /dev/null +++ b/base/src/main/res/raw/empty_animation.json @@ -0,0 +1 @@ +{"v":"4.7.0","fr":25,"ip":0,"op":50,"w":120,"h":120,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"ruoi","ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.967]},"o":{"x":[0.167],"y":[0.033]},"n":["0p833_0p967_0p167_0p033"],"t":35,"s":[100],"e":[0]},{"t":49}]},"r":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0,"y":0},"n":"0p833_0p833_0_0","t":0,"s":[57.361,61.016,0],"e":[57.699,41.796,0],"to":[-4.67500305175781,-4.12800598144531,0],"ti":[-13.9099960327148,5.27300262451172,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":10.219,"s":[57.699,41.796,0],"e":[79.084,33.982,0],"to":[12.8159942626953,-4.85800170898438,0],"ti":[-4.54498291015625,3.73400115966797,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":19.445,"s":[79.084,33.982,0],"e":[59.691,9.121,0],"to":[6.61601257324219,-5.43799591064453,0],"ti":[20.0290069580078,1.20700073242188,0]},{"t":35}]},"a":{"a":0,"k":[60.531,10.945,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.994,0],[0,-0.994],[0.995,0],[0,0.994]],"o":[[0.995,0],[0,0.994],[-0.994,0],[0,-0.994]],"v":[[-0.001,-1.801],[1.801,-0.001],[-0.001,1.801],[-1.801,-0.001]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":0,"k":[0.529,0.529,0.529,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[62.4,13.144],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.422,0],[0,-1.422],[1.421,0],[0,1.422]],"o":[[1.421,0],[0,1.422],[-1.422,0],[0,-1.422]],"v":[[0.001,-2.574],[2.574,0],[0.001,2.574],[-2.574,0]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"st","c":{"a":0,"k":[0.529,0.529,0.529,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0.7},"lc":1,"lj":1,"ml":10,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[64.145,9.606],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"ix":2,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.996,0],[0,-1.996],[1.996,0],[0,1.996]],"o":[[1.996,0],[0,1.996],[-1.996,0],[0,-1.996]],"v":[[0,-3.614],[3.614,0],[0,3.614],[-3.614,0]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"st","c":{"a":0,"k":[0.529,0.529,0.529,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":0.7},"lc":1,"lj":1,"ml":10,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"fl","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[57.957,10.552],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":3,"cix":2,"ix":3,"mn":"ADBE Vector Group"},{"ty":"tr","p":{"a":0,"k":[60.531,10.941],"ix":2},"a":{"a":0,"k":[60.531,10.941],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"ruoi","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":50,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 2","ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.967]},"o":{"x":[0.167],"y":[0.033]},"n":["0p833_0p967_0p167_0p033"],"t":35,"s":[100],"e":[0]},{"t":49}]},"r":{"a":0,"k":0},"p":{"a":0,"k":[-0.75,-0.75,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-13.91,5.273],[-4.545,3.734],[20.029,1.207]],"o":[[-4.675,-4.128],[12.816,-4.858],[6.616,-5.438],[0,0]],"v":[[-7.383,24.76],[-7.046,5.54],[14.34,-2.273],[-3.178,-24.76]],"c":false}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"st","c":{"a":0,"k":[0.627,0.627,0.627,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":1},"lc":2,"lj":2,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":2.028}},{"n":"g","nm":"gap","v":{"a":0,"k":2.028}},{"n":"o","nm":"offset","v":{"a":0,"k":0}}],"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"tr","p":{"a":0,"k":[67.87,37.631],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 6","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group"},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.953]},"o":{"x":[0.167],"y":[0.033]},"n":["0p833_0p953_0p167_0p033"],"t":0,"s":[0],"e":[100]},{"t":35}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim"}],"ip":0,"op":50,"st":0,"bm":0,"sr":1},{"ddd":0,"ind":3,"ty":4,"nm":"im_emptyBox Outlines","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[60,60,0]},"a":{"a":0,"k":[60,60,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-0.001,-16.607],[-32.143,-0.002],[-0.001,16.607],[32.144,-0.002]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":0,"k":[0.8,0.82,0.851,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[60,55.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[12.856,-23.249],[0,-16.605],[-12.857,-23.249],[-45,-6.641],[-32.144,0.001],[-45,6.645],[-12.857,23.249],[0,16.609],[12.856,23.249],[45,6.645],[32.143,0.001],[45,-6.641]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":0,"k":[0.957,0.957,0.957,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[60,55.748],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":2,"cix":2,"ix":2,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-16.072,24.171],[16.072,11.312],[16.072,-24.171],[-16.072,-24.171]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":0,"k":[0.902,0.914,0.929,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[76.072,83.33],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":2,"cix":2,"ix":3,"mn":"ADBE Vector Group"},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-32.143,-24.171],[-32.143,11.311],[-0.001,24.171],[32.144,11.311],[32.144,-24.171]],"c":true}},"nm":"Path 1","mn":"ADBE Vector Shape - Group"},{"ty":"fl","c":{"a":0,"k":[0.8,0.82,0.851,1]},"o":{"a":0,"k":100},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"tr","p":{"a":0,"k":[60,83.33],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":2,"cix":2,"ix":4,"mn":"ADBE Vector Group"},{"ty":"tr","p":{"a":0,"k":[60,60.186],"ix":2},"a":{"a":0,"k":[60,60.186],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"box","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group"}],"ip":0,"op":50,"st":0,"bm":0,"sr":1}]} \ No newline at end of file diff --git a/userlist/src/main/java/com/hako/userlist/di/UserlistModules.kt b/userlist/src/main/java/com/hako/userlist/di/UserlistModules.kt index d9a58bb..f31580e 100644 --- a/userlist/src/main/java/com/hako/userlist/di/UserlistModules.kt +++ b/userlist/src/main/java/com/hako/userlist/di/UserlistModules.kt @@ -2,7 +2,9 @@ package com.hako.userlist.di import com.hako.base.domain.network.RemoteClient import com.hako.userlist.domain.datasource.UserlistRemoteApi +import com.hako.userlist.domain.usecase.GetFavoriteUsers import com.hako.userlist.domain.usecase.GetUsers +import com.hako.userlist.domain.usecase.SetFavoriteStatus import com.hako.userlist.viewmodel.UserlistViewmodel import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module @@ -10,6 +12,8 @@ import org.koin.dsl.module val userlistModules = module { factory { get().getClient(UserlistRemoteApi::class.java) } factory { GetUsers(get()) } + factory { GetFavoriteUsers(get()) } + factory { SetFavoriteStatus(get()) } viewModel { UserlistViewmodel() } } \ No newline at end of file diff --git a/userlist/src/main/java/com/hako/userlist/domain/usecase/GetFavoriteUsers.kt b/userlist/src/main/java/com/hako/userlist/domain/usecase/GetFavoriteUsers.kt new file mode 100644 index 0000000..c656e6e --- /dev/null +++ b/userlist/src/main/java/com/hako/userlist/domain/usecase/GetFavoriteUsers.kt @@ -0,0 +1,29 @@ +package com.hako.userlist.domain.usecase + +import com.hako.base.domain.database.dao.UserDao +import com.hako.userlist.model.UserViewable +import com.hako.userlist.model.toUserViewable +import io.reactivex.Single +import io.reactivex.schedulers.Schedulers + +class GetFavoriteUsers(private val dao: UserDao) { + + fun execute( + onSuccess: (List) -> Unit, + onEmpty: () -> Unit, + onError: (Throwable) -> Unit + ) { + Single.fromCallable { dao.getFavoriteUsers() } + .subscribeOn(Schedulers.io()) + .doOnSuccess { dbUsers -> + if (dbUsers.isEmpty()) { + onEmpty() + } else { + onSuccess(dbUsers.map { it.toUserViewable() }) + } + } + .doOnError { onError(it) } + .onErrorReturn { emptyList() } + .subscribe() + } +} diff --git a/userlist/src/main/java/com/hako/userlist/domain/usecase/GetUsers.kt b/userlist/src/main/java/com/hako/userlist/domain/usecase/GetUsers.kt index 864a693..28ec6e3 100644 --- a/userlist/src/main/java/com/hako/userlist/domain/usecase/GetUsers.kt +++ b/userlist/src/main/java/com/hako/userlist/domain/usecase/GetUsers.kt @@ -17,7 +17,8 @@ class GetUsers(private val dao: UserDao) : KoinComponent { fun execute( onSuccess: (List) -> Unit, onError: (Throwable) -> Unit, - onLoading: () -> Unit + onLoading: () -> Unit, + onEmpty: () -> Unit ) { Single.fromCallable { dao.getAllUsers() } .subscribeOn(Schedulers.io()) @@ -26,8 +27,12 @@ class GetUsers(private val dao: UserDao) : KoinComponent { if (dbUsers.isEmpty()) { api.getUsers() .doOnSuccess { - dao.saveAll(it.map { user -> user.toUserEntity() }) - onSuccess(dao.getAllUsers().map { user -> user.toUserViewable() }) + if (it.isEmpty()) { + onEmpty() + } else { + dao.saveAll(it.map { user -> user.toUserEntity() }) + onSuccess(dao.getAllUsers().map { user -> user.toUserViewable() }) + } } .doOnSubscribe { onLoading() } .subscribeOn(Schedulers.io()) diff --git a/userlist/src/main/java/com/hako/userlist/domain/usecase/SetFavoriteStatus.kt b/userlist/src/main/java/com/hako/userlist/domain/usecase/SetFavoriteStatus.kt new file mode 100644 index 0000000..f067ead --- /dev/null +++ b/userlist/src/main/java/com/hako/userlist/domain/usecase/SetFavoriteStatus.kt @@ -0,0 +1,26 @@ +package com.hako.userlist.domain.usecase + +import com.hako.base.domain.database.dao.UserDao +import com.hako.base.extensions.wasUpdated +import com.hako.userlist.domain.datasource.UserlistRemoteApi +import com.hako.userlist.model.UserViewable +import com.hako.userlist.model.toUserEntity +import com.hako.userlist.model.toUserViewable +import io.reactivex.Single +import io.reactivex.schedulers.Schedulers +import org.koin.core.KoinComponent +import org.koin.core.get + +class SetFavoriteStatus(private val dao: UserDao) { + + fun execute( + userId: Int, + favoriteStatus: Boolean, + onError: (userId: Int) -> Unit + ) { + Single.fromCallable { dao.saveFavorite(userId, favoriteStatus) } + .subscribeOn(Schedulers.io()) + .doOnError { onError(userId) } + .subscribe() + } +} diff --git a/userlist/src/main/java/com/hako/userlist/feature/UserlistFragment.kt b/userlist/src/main/java/com/hako/userlist/feature/UserlistFragment.kt index 64cda99..b837a41 100644 --- a/userlist/src/main/java/com/hako/userlist/feature/UserlistFragment.kt +++ b/userlist/src/main/java/com/hako/userlist/feature/UserlistFragment.kt @@ -12,6 +12,7 @@ import com.hako.base.extensions.observeNonNull import com.hako.base.extensions.toast import com.hako.base.extensions.visible import com.hako.base.navigation.NavigationRouter +import com.hako.base.navigation.ShowFabButton import com.hako.userlist.model.UserViewable import com.hako.userlist.viewmodel.UserlistViewmodel import com.hako.userlist.widget.UserlistAdapter @@ -22,7 +23,9 @@ import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import timber.log.Timber -class UserlistFragment : Fragment() { +const val ALBUMLIST_FRAGMENT_BUNDLE_FAVORITES = "ALBUMLIST_FRAGMENT_BUNDLE_FAVORITES" + +class UserlistFragment : Fragment(), ShowFabButton { private val viewModel: UserlistViewmodel by viewModel() private val listAdapter by lazy { UserlistAdapter() } @@ -40,7 +43,7 @@ class UserlistFragment : Fragment() { } private fun setObservers() { - viewModel.data.observeNonNull(this) { + viewModel.userList.observeNonNull(this) { it.either(::handleFetchError, ::handleFetchSuccess) } @@ -49,17 +52,29 @@ class UserlistFragment : Fragment() { RequestStatus.Ready -> { fragment_userlist_error_overlay.gone() fragment_userlist_loading_overlay.gone() + fragment_userlist_empty_overlay.gone() } RequestStatus.Loading -> { fragment_userlist_error_overlay.gone() fragment_userlist_loading_overlay.visible() + fragment_userlist_empty_overlay.gone() } RequestStatus.Errored -> { fragment_userlist_error_overlay.visible() fragment_userlist_loading_overlay.gone() + fragment_userlist_empty_overlay.gone() + } + RequestStatus.Empty -> { + fragment_userlist_error_overlay.gone() + fragment_userlist_loading_overlay.gone() + fragment_userlist_empty_overlay.visible() } } } + + viewModel.emptyMessage.observeNonNull(this) { + fragment_userlist_empty_overlay.setLabel(it) + } } private fun handleFetchError(throwable: Throwable) { @@ -79,7 +94,7 @@ class UserlistFragment : Fragment() { } onFavoriteClick = { - context.toast(it.userName) + viewModel.updateUserFavoriteStatus(it.id, !it.isFavorite) } } } diff --git a/userlist/src/main/java/com/hako/userlist/model/UserModels.kt b/userlist/src/main/java/com/hako/userlist/model/UserModels.kt index b89a2aa..0fe1412 100644 --- a/userlist/src/main/java/com/hako/userlist/model/UserModels.kt +++ b/userlist/src/main/java/com/hako/userlist/model/UserModels.kt @@ -19,10 +19,10 @@ data class UserViewable( val id: Int, val realName: String, val userName: String, - var isFavorite: Boolean = false + var isFavorite: Boolean ) fun User.toUserEntity() = UserEntity(this.id, this.realName, this.userName, this.email, this.phone, this.website) -fun UserEntity.toUserViewable() = UserViewable(this.id, this.realName, this.userName) +fun UserEntity.toUserViewable() = UserViewable(this.id, this.realName, this.userName, this.isFavorite) diff --git a/userlist/src/main/java/com/hako/userlist/viewmodel/UserlistViewmodel.kt b/userlist/src/main/java/com/hako/userlist/viewmodel/UserlistViewmodel.kt index abe56e5..600cca4 100644 --- a/userlist/src/main/java/com/hako/userlist/viewmodel/UserlistViewmodel.kt +++ b/userlist/src/main/java/com/hako/userlist/viewmodel/UserlistViewmodel.kt @@ -3,34 +3,65 @@ package com.hako.userlist.viewmodel import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.hako.base.domain.network.RequestStatus -import com.hako.base.domain.network.RequestStatus.Ready -import com.hako.base.domain.network.RequestStatus.Loading -import com.hako.base.domain.network.RequestStatus.Errored import com.hako.base.domain.Either +import com.hako.base.domain.network.RequestStatus.* +import com.hako.userlist.domain.usecase.GetFavoriteUsers import com.hako.userlist.domain.usecase.GetUsers +import com.hako.userlist.domain.usecase.SetFavoriteStatus import com.hako.userlist.model.UserViewable import org.koin.core.KoinComponent import org.koin.core.get class UserlistViewmodel : ViewModel(), KoinComponent { - val data = MutableLiveData>>() + val userList = MutableLiveData>>() + val favoriteError = MutableLiveData() + val emptyMessage = MutableLiveData() val requestStatus = MutableLiveData() private val getUsers: GetUsers = get() + private val getFavoriteUsers: GetFavoriteUsers = get() + private val setFavoriteStatus: SetFavoriteStatus = get() fun fetchUsers() { getUsers.execute( onSuccess = { requestStatus.postValue(Ready) - data.postValue(Either.Right(it)) + userList.postValue(Either.Right(it)) }, onLoading = { requestStatus.postValue(Loading) }, onError = { requestStatus.postValue(Errored) - data.postValue(Either.Left(it)) + userList.postValue(Either.Left(it)) + }, + onEmpty = { + requestStatus.postValue(Empty) + emptyMessage.postValue("No se encontró ningún usuario") + }) + } + + fun fetchFavoriteUsers() { + getFavoriteUsers.execute( + onSuccess = { + requestStatus.postValue(Ready) + userList.postValue(Either.Right(it)) + }, + onError = { + requestStatus.postValue(Errored) + userList.postValue(Either.Left(it)) + }, + onEmpty = { + requestStatus.postValue(Empty) + emptyMessage.postValue("No tienes favoritos!") + }) + } + + fun updateUserFavoriteStatus(userId: Int, status: Boolean) { + setFavoriteStatus.execute(userId, status, + onError = { + favoriteError.postValue(it) }) } } diff --git a/userlist/src/main/java/com/hako/userlist/widget/UserlistAdapter.kt b/userlist/src/main/java/com/hako/userlist/widget/UserlistAdapter.kt index fe5f979..25dd99f 100644 --- a/userlist/src/main/java/com/hako/userlist/widget/UserlistAdapter.kt +++ b/userlist/src/main/java/com/hako/userlist/widget/UserlistAdapter.kt @@ -44,18 +44,26 @@ class UserlistAdapter : RecyclerView.Adapter() { } } -class UserViewHolder(private val view: View, - private val onItemClick: (UserViewable) -> Unit, - private val onFavoriteClick: (UserViewable) -> Unit) : +class UserViewHolder( + private val view: View, + private val onItemClick: (UserViewable) -> Unit, + private val onFavoriteClick: (UserViewable) -> Unit +) : RecyclerView.ViewHolder(view) { fun bind(user: UserViewable) = with(view) { item_user_card_real_name.text = user.realName item_user_card_user_name.text = user.userName + if (user.isFavorite) { + item_user_card_like_button.like() + } else { + item_user_card_like_button.dislike() + } item_user_card_container.setOnClickListener { onItemClick(user) } item_user_card_like_button.setOnClickListener { + item_user_card_like_button.play() onFavoriteClick(user) } } diff --git a/userlist/src/main/res/layout/fragment_userlist.xml b/userlist/src/main/res/layout/fragment_userlist.xml index ef7295d..9f9d635 100644 --- a/userlist/src/main/res/layout/fragment_userlist.xml +++ b/userlist/src/main/res/layout/fragment_userlist.xml @@ -35,4 +35,14 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + \ No newline at end of file