Merge pull request #8 from hakodeveloper/feature/favorites

Implement Favorites
This commit is contained in:
Carlos Martinez
2020-02-06 11:40:52 -03:00
committed by GitHub
33 changed files with 333 additions and 34 deletions
+2 -1
View File
@@ -11,7 +11,8 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="false" android:supportsRtl="false"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme"
android:hardwareAccelerated="true">
<activity <activity
android:name=".view.MainActivity" android:name=".view.MainActivity"
@@ -4,18 +4,21 @@ import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.ui.NavigationUI.setupActionBarWithNavController import androidx.navigation.ui.NavigationUI.setupActionBarWithNavController
import com.hako.base.extensions.observeNonNull import com.hako.base.extensions.*
import com.hako.base.navigation.NavigationRouter import com.hako.base.navigation.NavigationRouter
import com.hako.base.navigation.ShowFabButton
import com.hako.friendlists.BuildConfig import com.hako.friendlists.BuildConfig
import com.hako.friendlists.R import com.hako.friendlists.R
import com.hako.friendlists.viewmodel.NavigationViewmodel import com.hako.friendlists.viewmodel.NavigationViewmodel
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.activity_main.*
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private val navController by lazy { findNavController(R.id.main_fragment_container) } private val navController by lazy { findNavController(R.id.main_fragment_container) }
private val navHostFragment by lazy { findNavHostFragment(R.id.main_fragment_container) }
private val navRouter: NavigationRouter by inject() private val navRouter: NavigationRouter by inject()
private val picasso: Picasso by inject() private val picasso: Picasso by inject()
private val viewModel: NavigationViewmodel by viewModel() private val viewModel: NavigationViewmodel by viewModel()
@@ -39,6 +42,28 @@ class MainActivity : AppCompatActivity() {
} }
setupActionBarWithNavController(this, navController) setupActionBarWithNavController(this, navController)
navHostFragment.registerOnFragmentViewCreated { currentFragment ->
initializeViews()
when (currentFragment) {
is ShowFabButton -> fabButtonBehaviour(currentFragment)
}
}
}
private fun initializeViews() {
main_fragment_fab.hide()
}
private fun fabButtonBehaviour(currentFragment: ShowFabButton) {
main_fragment_fab.setOnClickListener {
currentFragment.fabButtonPressed().invoke()
}
if (currentFragment.shouldShowFabButton()) {
main_fragment_fab.show()
} else {
main_fragment_fab.hide()
}
} }
private fun setupPicasso() { private fun setupPicasso() {
@@ -35,6 +35,9 @@ class NavigationViewmodel : ViewModel() {
putString(FRAGMENT_TITLE, event.userName) putString(FRAGMENT_TITLE, event.userName)
}) })
) )
is UserlistNavigation.ClickedOnFab -> navigate.postValue(
buildNavigation(R.id.action_userlistFragment_to_favoriteUserlistFragment)
)
} }
} }
+10
View File
@@ -15,4 +15,14 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/main_fragment_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="24dp"
android:src="@drawable/ic_heart"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
+1 -1
View File
@@ -2,7 +2,7 @@
<resources> <resources>
<color name="colorPrimary">#37474F</color> <color name="colorPrimary">#37474F</color>
<color name="colorPrimaryDark">#324047</color> <color name="colorPrimaryDark">#324047</color>
<color name="colorAccent">#CBCFD1</color> <color name="colorAccent">#D32F2F</color>
<color name="colorRed">#D32F2F</color> <color name="colorRed">#D32F2F</color>
<color name="colorDarkGray">#333333</color> <color name="colorDarkGray">#333333</color>
</resources> </resources>
@@ -15,12 +15,18 @@ interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun saveAll(entities: List<UserEntity>) fun saveAll(entities: List<UserEntity>)
@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}") @get:Query("SELECT * FROM ${UserEntity.TABLE_NAME}")
val all: List<UserEntity> val all: List<UserEntity>
@Query("SELECT * FROM ${UserEntity.TABLE_NAME}") @Query("SELECT * FROM ${UserEntity.TABLE_NAME} ORDER BY id ASC")
fun getAllUsers(): List<UserEntity> fun getAllUsers(): List<UserEntity>
@Query("SELECT * FROM ${UserEntity.TABLE_NAME} WHERE isFavorite = 1 ORDER BY id ASC")
fun getFavoriteUsers(): List<UserEntity>
@Query("SELECT COUNT(*) FROM ${UserEntity.TABLE_NAME}") @Query("SELECT COUNT(*) FROM ${UserEntity.TABLE_NAME}")
fun count(): Int fun count(): Int
@@ -12,7 +12,8 @@ data class UserEntity(
val userName: String, val userName: String,
val email: String, val email: String,
val phone: String, val phone: String,
val website: String val website: String,
val isFavorite: Boolean = true
) { ) {
companion object { companion object {
const val TABLE_NAME = "users" const val TABLE_NAME = "users"
@@ -4,4 +4,5 @@ sealed class RequestStatus {
object Ready : RequestStatus() object Ready : RequestStatus()
object Loading : RequestStatus() object Loading : RequestStatus()
object Errored : RequestStatus() object Errored : RequestStatus()
object Empty : RequestStatus()
} }
@@ -11,3 +11,5 @@ fun <T> LiveData<T>.observeNonNull(owner: LifecycleOwner, func: (T) -> Unit) {
} }
}) })
} }
fun Int.wasUpdated() = this > 0
@@ -1,6 +1,32 @@
package com.hako.base.extensions package com.hako.base.extensions
import android.os.Bundle import android.os.Bundle
import android.view.View
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.navigation.fragment.NavHostFragment
fun AppCompatActivity.findNavHostFragment(@IdRes id: Int) =
supportFragmentManager.findFragmentById(id) as NavHostFragment
fun NavHostFragment.registerOnFragmentViewCreated(
recursive: Boolean = true,
listener: (currentFragment: Fragment) -> Unit
) {
childFragmentManager
.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentViewCreated(
fm: FragmentManager,
f: Fragment,
v: View,
savedInstanceState: Bundle?
) {
super.onFragmentViewCreated(fm, f, v, savedInstanceState)
listener(f)
}
}, recursive)
}
fun buildNavigation(@IdRes id: Int, bundle: Bundle = Bundle()) = Pair(id, bundle) fun buildNavigation(@IdRes id: Int, bundle: Bundle = Bundle()) = Pair(id, bundle)
@@ -0,0 +1,7 @@
package com.hako.base.navigation
interface ShowFabButton {
fun shouldShowFabButton(): Boolean
fun fabButtonPressed(): () -> Unit = {}
}
@@ -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
}
}
@@ -10,7 +10,7 @@ import kotlinx.android.synthetic.main.like_button.view.*
private const val LIKE_MIN_FRAME = 0 private const val LIKE_MIN_FRAME = 0
private const val LIKE_MAX_FRAME = 28 private const val LIKE_MAX_FRAME = 28
private const val LIKE_ANIM_SPEED = 1f 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_MAX_FRAME = 70
private const val DISLIKE_ANIM_SPEED = 2f private const val DISLIKE_ANIM_SPEED = 2f
@@ -29,16 +29,24 @@ class LikeButton @JvmOverloads constructor(
} }
fun dislike() { 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.setMinAndMaxFrame(LIKE_MIN_FRAME, LIKE_MAX_FRAME)
like_button_animation_view.speed = LIKE_ANIM_SPEED like_button_animation_view.speed = LIKE_ANIM_SPEED
like_button_animation_view.playAnimation() 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.setMinAndMaxFrame(DISLIKE_MIN_FRAME, DISLIKE_MAX_FRAME)
like_button_animation_view.speed = DISLIKE_ANIM_SPEED like_button_animation_view.speed = DISLIKE_ANIM_SPEED
like_button_animation_view.playAnimation() like_button_animation_view.playAnimation()
Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1023 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/soft_background">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/empty_overlay_animation_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.39"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_rawRes="@raw/empty_animation" />
<TextView
android:id="@+id/empty_overlay_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="No tienes favoritos!"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="@+id/empty_overlay_animation_view"
app:layout_constraintStart_toStartOf="@+id/empty_overlay_animation_view"
app:layout_constraintTop_toBottomOf="@+id/empty_overlay_animation_view" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -6,7 +6,7 @@
android:background="@color/soft_background"> android:background="@color/soft_background">
<com.airbnb.lottie.LottieAnimationView <com.airbnb.lottie.LottieAnimationView
android:id="@+id/loading_overlay_animation_view" android:id="@+id/network_error_overlay_animation_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:scaleType="centerInside" android:scaleType="centerInside"
@@ -20,14 +20,14 @@
app:lottie_rawRes="@raw/network_animation" /> app:lottie_rawRes="@raw/network_animation" />
<TextView <TextView
android:id="@+id/textView" android:id="@+id/network_error_overlay_label"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="Network error!" android:text="Network error!"
android:textSize="24sp" android:textSize="24sp"
app:layout_constraintEnd_toEndOf="@+id/loading_overlay_animation_view" app:layout_constraintEnd_toEndOf="@+id/network_error_overlay_animation_view"
app:layout_constraintStart_toStartOf="@+id/loading_overlay_animation_view" app:layout_constraintStart_toStartOf="@+id/network_error_overlay_animation_view"
app:layout_constraintTop_toBottomOf="@+id/loading_overlay_animation_view" /> app:layout_constraintTop_toBottomOf="@+id/network_error_overlay_animation_view" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
File diff suppressed because one or more lines are too long
@@ -2,7 +2,9 @@ package com.hako.userlist.di
import com.hako.base.domain.network.RemoteClient import com.hako.base.domain.network.RemoteClient
import com.hako.userlist.domain.datasource.UserlistRemoteApi 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.GetUsers
import com.hako.userlist.domain.usecase.SetFavoriteStatus
import com.hako.userlist.viewmodel.UserlistViewmodel import com.hako.userlist.viewmodel.UserlistViewmodel
import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module import org.koin.dsl.module
@@ -10,6 +12,8 @@ import org.koin.dsl.module
val userlistModules = module { val userlistModules = module {
factory { get<RemoteClient>().getClient(UserlistRemoteApi::class.java) } factory { get<RemoteClient>().getClient(UserlistRemoteApi::class.java) }
factory { GetUsers(get()) } factory { GetUsers(get()) }
factory { GetFavoriteUsers(get()) }
factory { SetFavoriteStatus(get()) }
viewModel { UserlistViewmodel() } viewModel { UserlistViewmodel() }
} }
@@ -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<UserViewable>) -> 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()
}
}
@@ -17,7 +17,8 @@ class GetUsers(private val dao: UserDao) : KoinComponent {
fun execute( fun execute(
onSuccess: (List<UserViewable>) -> Unit, onSuccess: (List<UserViewable>) -> Unit,
onError: (Throwable) -> Unit, onError: (Throwable) -> Unit,
onLoading: () -> Unit onLoading: () -> Unit,
onEmpty: () -> Unit
) { ) {
Single.fromCallable { dao.getAllUsers() } Single.fromCallable { dao.getAllUsers() }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
@@ -26,8 +27,12 @@ class GetUsers(private val dao: UserDao) : KoinComponent {
if (dbUsers.isEmpty()) { if (dbUsers.isEmpty()) {
api.getUsers() api.getUsers()
.doOnSuccess { .doOnSuccess {
dao.saveAll(it.map { user -> user.toUserEntity() }) if (it.isEmpty()) {
onSuccess(dao.getAllUsers().map { user -> user.toUserViewable() }) onEmpty()
} else {
dao.saveAll(it.map { user -> user.toUserEntity() })
onSuccess(dao.getAllUsers().map { user -> user.toUserViewable() })
}
} }
.doOnSubscribe { onLoading() } .doOnSubscribe { onLoading() }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
@@ -0,0 +1,19 @@
package com.hako.userlist.domain.usecase
import com.hako.base.domain.database.dao.UserDao
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
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()
}
}
@@ -0,0 +1,9 @@
package com.hako.userlist.feature
class FavoriteUserlistFragment : UserlistFragment() {
override fun doRequest() {
viewModel.fetchFavoriteUsers()
}
override fun shouldShowFabButton() = false
}
@@ -9,9 +9,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.hako.base.domain.network.RequestStatus import com.hako.base.domain.network.RequestStatus
import com.hako.base.extensions.gone import com.hako.base.extensions.gone
import com.hako.base.extensions.observeNonNull import com.hako.base.extensions.observeNonNull
import com.hako.base.extensions.toast
import com.hako.base.extensions.visible import com.hako.base.extensions.visible
import com.hako.base.navigation.NavigationRouter import com.hako.base.navigation.NavigationRouter
import com.hako.base.navigation.ShowFabButton
import com.hako.userlist.model.UserViewable import com.hako.userlist.model.UserViewable
import com.hako.userlist.viewmodel.UserlistViewmodel import com.hako.userlist.viewmodel.UserlistViewmodel
import com.hako.userlist.widget.UserlistAdapter import com.hako.userlist.widget.UserlistAdapter
@@ -22,9 +22,9 @@ import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import timber.log.Timber import timber.log.Timber
class UserlistFragment : Fragment() { open class UserlistFragment : Fragment(), ShowFabButton {
private val viewModel: UserlistViewmodel by viewModel() val viewModel: UserlistViewmodel by viewModel()
private val listAdapter by lazy { UserlistAdapter() } private val listAdapter by lazy { UserlistAdapter() }
private val navigation: NavigationRouter by inject() private val navigation: NavigationRouter by inject()
@@ -36,11 +36,21 @@ class UserlistFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setRecycler() setRecycler()
setObservers() setObservers()
doRequest()
}
open fun doRequest() {
viewModel.fetchUsers() viewModel.fetchUsers()
} }
override fun shouldShowFabButton() = true
override fun fabButtonPressed(): () -> Unit = {
navigation.sendNavigation(UserlistNavigation.ClickedOnFab)
}
private fun setObservers() { private fun setObservers() {
viewModel.data.observeNonNull(this) { viewModel.userList.observeNonNull(this) {
it.either(::handleFetchError, ::handleFetchSuccess) it.either(::handleFetchError, ::handleFetchSuccess)
} }
@@ -49,17 +59,29 @@ class UserlistFragment : Fragment() {
RequestStatus.Ready -> { RequestStatus.Ready -> {
fragment_userlist_error_overlay.gone() fragment_userlist_error_overlay.gone()
fragment_userlist_loading_overlay.gone() fragment_userlist_loading_overlay.gone()
fragment_userlist_empty_overlay.gone()
} }
RequestStatus.Loading -> { RequestStatus.Loading -> {
fragment_userlist_error_overlay.gone() fragment_userlist_error_overlay.gone()
fragment_userlist_loading_overlay.visible() fragment_userlist_loading_overlay.visible()
fragment_userlist_empty_overlay.gone()
} }
RequestStatus.Errored -> { RequestStatus.Errored -> {
fragment_userlist_error_overlay.visible() fragment_userlist_error_overlay.visible()
fragment_userlist_loading_overlay.gone() 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) { private fun handleFetchError(throwable: Throwable) {
@@ -79,7 +101,7 @@ class UserlistFragment : Fragment() {
} }
onFavoriteClick = { onFavoriteClick = {
context.toast(it.userName) viewModel.updateUserFavoriteStatus(it.id, !it.isFavorite)
} }
} }
} }
@@ -19,10 +19,10 @@ data class UserViewable(
val id: Int, val id: Int,
val realName: String, val realName: String,
val userName: 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 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)
@@ -4,4 +4,5 @@ import com.hako.base.navigation.NavigationEvent
sealed class UserlistNavigation : NavigationEvent { sealed class UserlistNavigation : NavigationEvent {
data class ClickedOnUser(val userId: Int, val userName: String) : UserlistNavigation() data class ClickedOnUser(val userId: Int, val userName: String) : UserlistNavigation()
object ClickedOnFab : UserlistNavigation()
} }
@@ -3,34 +3,65 @@ package com.hako.userlist.viewmodel
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.hako.base.domain.network.RequestStatus 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.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.GetUsers
import com.hako.userlist.domain.usecase.SetFavoriteStatus
import com.hako.userlist.model.UserViewable import com.hako.userlist.model.UserViewable
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
import org.koin.core.get import org.koin.core.get
class UserlistViewmodel : ViewModel(), KoinComponent { class UserlistViewmodel : ViewModel(), KoinComponent {
val data = MutableLiveData<Either<Throwable, List<UserViewable>>>() val userList = MutableLiveData<Either<Throwable, List<UserViewable>>>()
val favoriteError = MutableLiveData<Int>()
val emptyMessage = MutableLiveData<String>()
val requestStatus = MutableLiveData<RequestStatus>() val requestStatus = MutableLiveData<RequestStatus>()
private val getUsers: GetUsers = get() private val getUsers: GetUsers = get()
private val getFavoriteUsers: GetFavoriteUsers = get()
private val setFavoriteStatus: SetFavoriteStatus = get()
fun fetchUsers() { fun fetchUsers() {
getUsers.execute( getUsers.execute(
onSuccess = { onSuccess = {
requestStatus.postValue(Ready) requestStatus.postValue(Ready)
data.postValue(Either.Right(it)) userList.postValue(Either.Right(it))
}, },
onLoading = { onLoading = {
requestStatus.postValue(Loading) requestStatus.postValue(Loading)
}, },
onError = { onError = {
requestStatus.postValue(Errored) 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)
}) })
} }
} }
@@ -44,18 +44,26 @@ class UserlistAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
} }
} }
class UserViewHolder(private val view: View, class UserViewHolder(
private val onItemClick: (UserViewable) -> Unit, private val view: View,
private val onFavoriteClick: (UserViewable) -> Unit) : private val onItemClick: (UserViewable) -> Unit,
private val onFavoriteClick: (UserViewable) -> Unit
) :
RecyclerView.ViewHolder(view) { RecyclerView.ViewHolder(view) {
fun bind(user: UserViewable) = with(view) { fun bind(user: UserViewable) = with(view) {
item_user_card_real_name.text = user.realName item_user_card_real_name.text = user.realName
item_user_card_user_name.text = user.userName 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 { item_user_card_container.setOnClickListener {
onItemClick(user) onItemClick(user)
} }
item_user_card_like_button.setOnClickListener { item_user_card_like_button.setOnClickListener {
item_user_card_like_button.play()
onFavoriteClick(user) onFavoriteClick(user)
} }
} }
@@ -35,4 +35,14 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<com.hako.base.widgets.EmptyOverlay
android:id="@+id/fragment_userlist_empty_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@@ -9,6 +9,20 @@
android:id="@+id/userlistFragment" android:id="@+id/userlistFragment"
android:name="com.hako.userlist.feature.UserlistFragment" android:name="com.hako.userlist.feature.UserlistFragment"
tools:layout="@layout/fragment_userlist" tools:layout="@layout/fragment_userlist"
android:label="Friendlist" /> android:label="Friendlist" >
<action
android:id="@+id/action_userlistFragment_to_favoriteUserlistFragment"
app:destination="@id/favoriteUserlistFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"/>
</fragment>
<fragment
android:id="@+id/favoriteUserlistFragment"
android:name="com.hako.userlist.feature.FavoriteUserlistFragment"
tools:layout="@layout/fragment_userlist"
android:label="Favoritos" />
</navigation> </navigation>