mirror of
https://github.com/imcarlost/Friendlists.git
synced 2026-04-10 02:46:54 -04:00
implement the album feature
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
package com.hako.albumlist.di
|
||||
|
||||
import com.hako.albumlist.domain.datasource.AlbumlistRemoteApi
|
||||
import com.hako.albumlist.domain.usecase.GetAlbum
|
||||
import com.hako.albumlist.viewmodel.AlbumlistViewmodel
|
||||
import com.hako.base.domain.network.RemoteClient
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
|
||||
val albumListModules = module {
|
||||
factory { get<RemoteClient>().getClient(AlbumlistRemoteApi::class.java) }
|
||||
factory { GetAlbum(get()) }
|
||||
|
||||
viewModel { AlbumlistViewmodel() }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.hako.albumlist.domain.datasource
|
||||
|
||||
import com.hako.albumlist.model.Album
|
||||
import io.reactivex.Single
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface AlbumlistRemoteApi {
|
||||
|
||||
@GET("/albums")
|
||||
fun getAlbums(
|
||||
@Query("userId") userId: Int
|
||||
): Single<List<Album>>
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.hako.albumlist.domain.usecase
|
||||
|
||||
import com.hako.albumlist.domain.datasource.AlbumlistRemoteApi
|
||||
import com.hako.albumlist.model.AlbumViewable
|
||||
import com.hako.albumlist.model.toAlbumEntity
|
||||
import com.hako.albumlist.model.toUserViewable
|
||||
import com.hako.base.domain.database.dao.AlbumDao
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.get
|
||||
|
||||
class GetAlbum(private val dao: AlbumDao) : KoinComponent {
|
||||
|
||||
private val api: AlbumlistRemoteApi = get()
|
||||
|
||||
fun execute(
|
||||
userId: Int,
|
||||
onSuccess: (List<AlbumViewable>) -> Unit,
|
||||
onError: (Throwable) -> Unit,
|
||||
onLoading: () -> Unit
|
||||
) {
|
||||
Single.fromCallable { dao.getAlbums(userId) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.doOnError { onError(it) }
|
||||
.doOnSuccess { dbAlbum ->
|
||||
if (dbAlbum.isEmpty() || dbAlbum.count() == 0) {
|
||||
api.getAlbums(userId)
|
||||
.doOnSuccess {
|
||||
dao.saveAll(it.map { album -> album.toAlbumEntity() })
|
||||
onSuccess(dao.getAlbums(userId).map { album -> album.toUserViewable() })
|
||||
}
|
||||
.doOnSubscribe { onLoading() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe({}, { onError(it) })
|
||||
} else {
|
||||
onSuccess(dbAlbum.map { it.toUserViewable() })
|
||||
}
|
||||
}
|
||||
.subscribe()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.hako.albumlist.feature
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.hako.albumlist.R
|
||||
import com.hako.albumlist.model.AlbumViewable
|
||||
import com.hako.albumlist.viewmodel.AlbumlistViewmodel
|
||||
import com.hako.albumlist.widget.AlbumlistAdapter
|
||||
import com.hako.base.domain.network.RequestStatus
|
||||
import com.hako.base.extensions.gone
|
||||
import com.hako.base.extensions.observeNonNull
|
||||
import com.hako.base.extensions.toast
|
||||
import com.hako.base.extensions.visible
|
||||
import kotlinx.android.synthetic.main.fragment_albumlist.*
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import timber.log.Timber
|
||||
|
||||
class AlbumlistFragment : Fragment() {
|
||||
|
||||
private val viewModel: AlbumlistViewmodel by viewModel()
|
||||
private val listAdapter by lazy { AlbumlistAdapter() }
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View = inflater.inflate(R.layout.fragment_albumlist, container, false)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setRecycler()
|
||||
setObservers()
|
||||
|
||||
//TODO: Get user by bundle
|
||||
viewModel.fetchAlbums(2)
|
||||
}
|
||||
|
||||
private fun setObservers() {
|
||||
viewModel.data.observeNonNull(this) {
|
||||
it.either(::handleFetchError, ::handleFetchSuccess)
|
||||
}
|
||||
|
||||
viewModel.requestStatus.observeNonNull(this) {
|
||||
when (it) {
|
||||
RequestStatus.Ready -> {
|
||||
fragment_albumlist_error_overlay.gone()
|
||||
fragment_albumlist_loading_overlay.gone()
|
||||
}
|
||||
RequestStatus.Loading -> {
|
||||
fragment_albumlist_error_overlay.gone()
|
||||
fragment_albumlist_loading_overlay.visible()
|
||||
}
|
||||
RequestStatus.Errored -> {
|
||||
fragment_albumlist_error_overlay.visible()
|
||||
fragment_albumlist_loading_overlay.gone()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFetchError(throwable: Throwable) {
|
||||
Timber.e(throwable)
|
||||
}
|
||||
|
||||
private fun handleFetchSuccess(users: List<AlbumViewable>) {
|
||||
listAdapter.addAll(users)
|
||||
}
|
||||
|
||||
private fun setRecycler() {
|
||||
fragment_albumlist_recycler_container.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = listAdapter.apply {
|
||||
onItemClick = {
|
||||
context.toast(it.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.hako.albumlist.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.hako.base.domain.database.entities.AlbumEntity
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class Album(
|
||||
@SerializedName("id") val id: Int,
|
||||
@SerializedName("userId") val userId: Int,
|
||||
@SerializedName("title") val title: String
|
||||
) : Parcelable
|
||||
|
||||
data class AlbumViewable(
|
||||
val id: Int,
|
||||
val userId: Int,
|
||||
val title: String
|
||||
)
|
||||
|
||||
fun Album.toAlbumEntity() = AlbumEntity(this.id, this.userId, this.title)
|
||||
|
||||
fun AlbumEntity.toUserViewable() = AlbumViewable(this.id, this.userId, this.title)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.hako.albumlist.viewmodel
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.hako.albumlist.domain.usecase.GetAlbum
|
||||
import com.hako.albumlist.model.AlbumViewable
|
||||
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 org.koin.core.KoinComponent
|
||||
import org.koin.core.get
|
||||
|
||||
class AlbumlistViewmodel : ViewModel(), KoinComponent {
|
||||
|
||||
val data = MutableLiveData<Either<Throwable, List<AlbumViewable>>>()
|
||||
val requestStatus = MutableLiveData<RequestStatus>()
|
||||
|
||||
private val getUsers: GetAlbum = get()
|
||||
|
||||
fun fetchAlbums(userId: Int) {
|
||||
getUsers.execute(
|
||||
userId,
|
||||
onSuccess = {
|
||||
requestStatus.postValue(Ready)
|
||||
data.postValue(Either.Right(it))
|
||||
},
|
||||
onLoading = {
|
||||
requestStatus.postValue(Loading)
|
||||
},
|
||||
onError = {
|
||||
requestStatus.postValue(Errored)
|
||||
data.postValue(Either.Left(it))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.hako.albumlist.widget
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.hako.albumlist.R
|
||||
import com.hako.albumlist.model.AlbumViewable
|
||||
import com.hako.base.extensions.autoNotify
|
||||
import kotlinx.android.synthetic.main.item_album_card.view.*
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class AlbumlistAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
private var items by Delegates.observable(emptyList<AlbumViewable>()) { _, oldList, newList ->
|
||||
autoNotify(oldList, newList) { old, new -> old.id == new.id }
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
var onItemClick: (AlbumViewable) -> Unit = { }
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
|
||||
UserViewHolder(
|
||||
LayoutInflater
|
||||
.from(parent.context)
|
||||
.inflate(R.layout.item_album_card, parent, false),
|
||||
onItemClick
|
||||
)
|
||||
|
||||
fun getItem(position: Int) = items[position]
|
||||
|
||||
fun addAll(list: List<AlbumViewable>) {
|
||||
items = list
|
||||
}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onBindViewHolder(viewholder: RecyclerView.ViewHolder, position: Int) =
|
||||
when (viewholder) {
|
||||
is UserViewHolder -> viewholder.bind(items[position])
|
||||
else -> throw NoWhenBranchMatchedException("Undefined viewholder")
|
||||
}
|
||||
}
|
||||
|
||||
class UserViewHolder(private val view: View,
|
||||
private val onItemClick: (AlbumViewable) -> Unit) :
|
||||
RecyclerView.ViewHolder(view) {
|
||||
|
||||
fun bind(album: AlbumViewable) = with(view) {
|
||||
item_album_card_album_name.text = album.title
|
||||
item_album_card_container.setOnClickListener {
|
||||
onItemClick(album)
|
||||
}
|
||||
}
|
||||
}
|
||||
38
albumlist/src/main/res/layout/fragment_albumlist.xml
Normal file
38
albumlist/src/main/res/layout/fragment_albumlist.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/fragment_albumlist_base">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/fragment_albumlist_recycler_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.hako.base.widgets.LoadingOverlay
|
||||
android:id="@+id/fragment_albumlist_loading_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" />
|
||||
|
||||
<com.hako.base.widgets.NetworkErrorOverlay
|
||||
android:id="@+id/fragment_albumlist_error_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>
|
||||
27
albumlist/src/main/res/layout/item_album_card.xml
Normal file
27
albumlist/src/main/res/layout/item_album_card.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/item_album_card_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="70dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="@drawable/bg_card"
|
||||
android:elevation="2dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_album_card_album_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Real Name" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
14
albumlist/src/main/res/navigation/albumlist_navigation.xml
Normal file
14
albumlist/src/main/res/navigation/albumlist_navigation.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/albumlist_navigation"
|
||||
app:startDestination="@id/albumlistFragment">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/albumlistFragment"
|
||||
android:name="com.hako.albumlist.feature.AlbumlistFragment"
|
||||
tools:layout="@layout/fragment_albumlist"
|
||||
android:label="AlbumListFragment" />
|
||||
|
||||
</navigation>
|
||||
Reference in New Issue
Block a user