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

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"

View File

@@ -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() {

View File

@@ -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)
)
} }
} }

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>

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>

View File

@@ -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

View File

@@ -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"

View File

@@ -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()
} }

View File

@@ -11,3 +11,5 @@ fun <T> LiveData<T>.observeNonNull(owner: LifecycleOwner, func: (T) -> Unit) {
} }
}) })
} }
fun Int.wasUpdated() = this > 0

View File

@@ -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)

View File

@@ -0,0 +1,7 @@
package com.hako.base.navigation
interface ShowFabButton {
fun shouldShowFabButton(): Boolean
fun fabButtonPressed(): () -> Unit = {}
}

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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() }
} }

View File

@@ -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()
}
}

View File

@@ -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())

View File

@@ -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()
}
}

View File

@@ -0,0 +1,9 @@
package com.hako.userlist.feature
class FavoriteUserlistFragment : UserlistFragment() {
override fun doRequest() {
viewModel.fetchFavoriteUsers()
}
override fun shouldShowFabButton() = false
}

View File

@@ -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)
} }
} }
} }

View File

@@ -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)

View File

@@ -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()
} }

View File

@@ -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)
}) })
} }
} }

View File

@@ -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)
} }
} }

View File

@@ -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>

View File

@@ -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>