Skip to content

Commit bdede08

Browse files
committed
Add horizontal adapter
1 parent afda62d commit bdede08

File tree

8 files changed

+426
-54
lines changed

8 files changed

+426
-54
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.hoc.pagination_mvi.ui.main
2+
3+
import android.view.LayoutInflater
4+
import android.view.View
5+
import android.view.ViewGroup
6+
import androidx.annotation.LayoutRes
7+
import androidx.core.view.isInvisible
8+
import androidx.recyclerview.widget.DiffUtil
9+
import androidx.recyclerview.widget.ListAdapter
10+
import androidx.recyclerview.widget.RecyclerView
11+
import com.hoc.pagination_mvi.R
12+
import com.hoc.pagination_mvi.ui.main.MainContract.Item.HorizontalList.HorizontalItem
13+
import com.hoc.pagination_mvi.ui.main.MainContract.PlaceholderState
14+
import com.hoc.pagination_mvi.ui.main.MainContract.PostVS
15+
import kotlinx.android.synthetic.main.recycler_item_horizontal_placeholder.view.*
16+
import kotlinx.android.synthetic.main.recycler_item_horizontal_post.view.*
17+
18+
private object HorizontalItemItemCallback : DiffUtil.ItemCallback<HorizontalItem>() {
19+
override fun areItemsTheSame(oldItem: HorizontalItem, newItem: HorizontalItem): Boolean {
20+
return when {
21+
oldItem is HorizontalItem.Post && newItem is HorizontalItem.Post -> oldItem.post.id == newItem.post.id
22+
oldItem is HorizontalItem.Placeholder && newItem is HorizontalItem.Placeholder -> true
23+
else -> oldItem == newItem
24+
}
25+
}
26+
27+
override fun areContentsTheSame(oldItem: HorizontalItem, newItem: HorizontalItem) =
28+
oldItem == newItem
29+
30+
override fun getChangePayload(oldItem: HorizontalItem, newItem: HorizontalItem): Any? {
31+
return when {
32+
oldItem is HorizontalItem.Post && newItem is HorizontalItem.Post -> newItem.post
33+
oldItem is HorizontalItem.Placeholder && newItem is HorizontalItem.Placeholder -> newItem.state
34+
else -> null
35+
}
36+
}
37+
}
38+
39+
class HorizontalAdapter :
40+
ListAdapter<HorizontalItem, HorizontalAdapter.VH>(HorizontalItemItemCallback) {
41+
42+
override fun onCreateViewHolder(parent: ViewGroup, @LayoutRes viewType: Int): VH {
43+
val itemView = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
44+
return when (viewType) {
45+
R.layout.recycler_item_horizontal_post -> PostVH(itemView)
46+
R.layout.recycler_item_horizontal_placeholder -> PlaceholderVH(itemView)
47+
else -> error("Unknown viewType=$viewType")
48+
}
49+
}
50+
51+
override fun onBindViewHolder(holder: VH, position: Int) = holder.bind(getItem(position))
52+
53+
override fun onBindViewHolder(holder: VH, position: Int, payloads: MutableList<Any>) {
54+
if (payloads.isEmpty()) return holder.bind(getItem(position))
55+
payloads.forEach { payload ->
56+
when {
57+
payload is PostVS && holder is PostVH -> holder.update(payload)
58+
payload is PlaceholderState && holder is PlaceholderVH -> holder.update(payload)
59+
}
60+
}
61+
}
62+
63+
@LayoutRes
64+
override fun getItemViewType(position: Int) = getItem(position).viewType
65+
66+
abstract class VH(itemView: View) : RecyclerView.ViewHolder(itemView) {
67+
abstract fun bind(item: HorizontalItem)
68+
}
69+
70+
private class PostVH(itemView: View) : VH(itemView) {
71+
private val textTitle = itemView.text_title!!
72+
private val textBody = itemView.text_body!!
73+
74+
override fun bind(item: HorizontalItem) {
75+
if (item !is HorizontalItem.Post) return
76+
update(item.post)
77+
}
78+
79+
fun update(post: PostVS) {
80+
textTitle.text = post.title
81+
textBody.text = post.body
82+
}
83+
}
84+
85+
private class PlaceholderVH(itemView: View) : VH(itemView) {
86+
private val progressBar = itemView.progress_bar!!
87+
private val textError = itemView.text_error!!
88+
private val buttonRetry = itemView.button_retry!!
89+
90+
91+
override fun bind(item: HorizontalItem) {
92+
if (item !is HorizontalItem.Placeholder) return
93+
update(item.state)
94+
}
95+
96+
fun update(state: PlaceholderState) {
97+
when (state) {
98+
PlaceholderState.Loading -> {
99+
progressBar.isInvisible = false
100+
textError.isInvisible = true
101+
buttonRetry.isInvisible = true
102+
}
103+
PlaceholderState.Idle -> {
104+
progressBar.isInvisible = true
105+
textError.isInvisible = true
106+
buttonRetry.isInvisible = true
107+
}
108+
is PlaceholderState.Error -> {
109+
progressBar.isInvisible = true
110+
textError.isInvisible = false
111+
buttonRetry.isInvisible = false
112+
textError.text = state.error.message
113+
}
114+
}
115+
}
116+
}
117+
}

app/src/main/java/com/hoc/pagination_mvi/ui/main/MainAdapter.kt

Lines changed: 53 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,49 @@ import android.view.View
66
import android.view.ViewGroup
77
import androidx.annotation.LayoutRes
88
import androidx.core.view.isInvisible
9-
import androidx.core.view.isVisible
109
import androidx.recyclerview.widget.DiffUtil
10+
import androidx.recyclerview.widget.LinearLayoutManager
1111
import androidx.recyclerview.widget.ListAdapter
1212
import androidx.recyclerview.widget.RecyclerView
1313
import coil.api.load
1414
import com.hoc.pagination_mvi.R
1515
import com.hoc.pagination_mvi.asObservable
16-
import com.hoc.pagination_mvi.ui.main.MainContract.*
16+
import com.hoc.pagination_mvi.ui.main.MainContract.Item
17+
import com.hoc.pagination_mvi.ui.main.MainContract.PlaceholderState
1718
import com.jakewharton.rxbinding3.view.clicks
1819
import com.jakewharton.rxbinding3.view.detaches
1920
import io.reactivex.disposables.CompositeDisposable
2021
import io.reactivex.rxkotlin.addTo
2122
import io.reactivex.rxkotlin.subscribeBy
2223
import io.reactivex.subjects.PublishSubject
24+
import kotlinx.android.synthetic.main.recycler_item_horizontal_list.view.*
2325
import kotlinx.android.synthetic.main.recycler_item_photo.view.*
2426
import kotlinx.android.synthetic.main.recycler_item_placeholder.view.*
2527

26-
27-
class MainAdapter(private val compositeDisposable: CompositeDisposable) :
28-
ListAdapter<Item, MainAdapter.VH>(object : DiffUtil.ItemCallback<Item>() {
29-
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
30-
return when {
31-
oldItem is Item.Placeholder && newItem is Item.Placeholder -> true
32-
oldItem is Item.HorizontalList && newItem is Item.HorizontalList -> true
33-
oldItem is Item.Photo && newItem is Item.Photo -> oldItem.photo.id == newItem.photo.id
34-
else -> oldItem == newItem
35-
}
28+
private object DiffUtilItemCallback : DiffUtil.ItemCallback<Item>() {
29+
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
30+
return when {
31+
oldItem is Item.Placeholder && newItem is Item.Placeholder -> true
32+
oldItem is Item.HorizontalList && newItem is Item.HorizontalList -> true
33+
oldItem is Item.Photo && newItem is Item.Photo -> oldItem.photo.id == newItem.photo.id
34+
else -> oldItem == newItem
3635
}
36+
}
3737

38-
override fun areContentsTheSame(oldItem: Item, newItem: Item) = oldItem == newItem
38+
override fun areContentsTheSame(oldItem: Item, newItem: Item) = oldItem == newItem
3939

40-
override fun getChangePayload(oldItem: Item, newItem: Item): Any? {
41-
return when {
42-
oldItem is Item.Placeholder && newItem is Item.Placeholder -> newItem.state
43-
oldItem is Item.HorizontalList && newItem is Item.HorizontalList -> newItem
44-
oldItem is Item.Photo && newItem is Item.Photo -> newItem.photo
45-
else -> null
46-
}
40+
override fun getChangePayload(oldItem: Item, newItem: Item): Any? {
41+
return when {
42+
oldItem is Item.Placeholder && newItem is Item.Placeholder -> newItem.state
43+
oldItem is Item.HorizontalList && newItem is Item.HorizontalList -> newItem
44+
oldItem is Item.Photo && newItem is Item.Photo -> newItem.photo
45+
else -> null
4746
}
48-
}) {
47+
}
48+
}
49+
50+
class MainAdapter(private val compositeDisposable: CompositeDisposable) :
51+
ListAdapter<Item, MainAdapter.VH>(DiffUtilItemCallback) {
4952

5053
private val retryS = PublishSubject.create<Unit>()
5154
val retryObservable get() = retryS.asObservable()
@@ -63,17 +66,14 @@ class MainAdapter(private val compositeDisposable: CompositeDisposable) :
6366
override fun onBindViewHolder(holder: VH, position: Int) = holder.bind(getItem(position))
6467

6568
override fun onBindViewHolder(holder: VH, position: Int, payloads: MutableList<Any>) {
66-
val payload = payloads.firstOrNull() ?: return holder.bind(getItem(position))
67-
Log.d("###", "[PAYLOAD] $payload")
68-
69-
if (payload is PlaceholderState && holder is PlaceHolderVH) {
70-
return holder.update(payload)
71-
}
72-
if (payload is Item.HorizontalList && holder is HorizontalListVH) {
73-
return holder.update(payload)
74-
}
75-
if (payload is Item.Photo && holder is PhotoVH) {
76-
return holder.update(payload)
69+
if (payloads.isEmpty()) return holder.bind(getItem(position))
70+
payloads.forEach { payload ->
71+
Log.d("###", "[PAYLOAD] $payload")
72+
when {
73+
payload is PlaceholderState && holder is PlaceHolderVH -> holder.update(payload)
74+
payload is Item.HorizontalList && holder is HorizontalListVH -> holder.update(payload)
75+
payload is Item.Photo && holder is PhotoVH -> holder.update(payload)
76+
}
7777
}
7878
}
7979

@@ -152,13 +152,33 @@ class MainAdapter(private val compositeDisposable: CompositeDisposable) :
152152
}
153153

154154
private class HorizontalListVH(itemView: View) : VH(itemView) {
155+
private val recycler = itemView.recycler_horizontal!!
156+
private val progressBar = itemView.progress_bar_horizontal!!
157+
private val textError = itemView.text_error_horizontal!!
158+
private val buttonRetry = itemView.button_retry_horizontal!!
159+
private val adapter = HorizontalAdapter()
160+
161+
init {
162+
recycler.run {
163+
setHasFixedSize(true)
164+
adapter = this@HorizontalListVH.adapter
165+
layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
166+
}
167+
}
168+
155169
override fun bind(item: Item) {
156170
if (item !is Item.HorizontalList) return
157171
update(item)
158172
}
159173

160174
fun update(item: Item.HorizontalList) {
161-
//TODO
175+
progressBar.isInvisible = !item.isLoading
176+
177+
textError.isInvisible = item.error == null
178+
buttonRetry.isInvisible = item.error == null
179+
textError.text = item.error?.message
180+
181+
adapter.submitList(item.items)
162182
}
163183
}
164184
}

app/src/main/java/com/hoc/pagination_mvi/ui/main/MainContract.kt

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package com.hoc.pagination_mvi.ui.main
22

33
import androidx.annotation.LayoutRes
44
import com.hoc.pagination_mvi.R
5+
import com.hoc.pagination_mvi.ui.main.MainContract.Item.HorizontalList.HorizontalItem
56
import io.reactivex.Observable
67
import com.hoc.pagination_mvi.domain.entity.Photo as PhotoDomain
8+
import com.hoc.pagination_mvi.domain.entity.Post as PostDomain
79

810
interface MainContract {
911
data class ViewState(
@@ -29,7 +31,8 @@ interface MainContract {
2931
Item.HorizontalList(
3032
items = emptyList(),
3133
isLoading = true,
32-
error = null
34+
error = null,
35+
postItems = emptyList()
3336
)
3437
),
3538
photoItems = emptyList()
@@ -42,12 +45,16 @@ interface MainContract {
4245
data class HorizontalList(
4346
val items: List<HorizontalItem>,
4447
val isLoading: Boolean,
45-
val error: Throwable?
48+
val error: Throwable?,
49+
val postItems: List<HorizontalItem.Post>
4650
) : Item(R.layout.recycler_item_horizontal_list) {
47-
sealed class HorizontalItem {
48-
data class Item(val s: String) : HorizontalItem()
49-
data class Placeholder(val state: PlaceholderState) : HorizontalItem()
51+
52+
sealed class HorizontalItem(@LayoutRes val viewType: Int) {
53+
data class Post(val post: PostVS) : HorizontalItem(TODO())
54+
55+
data class Placeholder(val state: PlaceholderState) : HorizontalItem(TODO())
5056
}
57+
5158
}
5259

5360
data class Photo(val photo: PhotoVS) : Item(R.layout.recycler_item_photo)
@@ -71,6 +78,20 @@ interface MainContract {
7178
)
7279
}
7380

81+
data class PostVS(
82+
val body: String,
83+
val id: Int,
84+
val title: String,
85+
val userId: Int
86+
) {
87+
constructor(domain: PostDomain) : this(
88+
body = domain.body,
89+
userId = domain.userId,
90+
id = domain.id,
91+
title = domain.title
92+
)
93+
}
94+
7495
sealed class PlaceholderState {
7596
object Loading : PlaceholderState()
7697
object Idle : PlaceholderState()
@@ -154,6 +175,66 @@ interface MainContract {
154175
}
155176
}
156177
}
178+
179+
sealed class PostFirstPage : PartialStateChange() {
180+
data class Data(val posts: List<PostVS>) : PostFirstPage()
181+
data class Error(val error: Throwable) : PostFirstPage()
182+
object Loading : PostFirstPage()
183+
184+
override fun reduce(vs: ViewState): ViewState {
185+
return when (this) {
186+
is Data -> {
187+
vs.copy(
188+
items = vs.items.map {
189+
if (it is Item.HorizontalList) {
190+
val postItems = this.posts.map { HorizontalItem.Post(it) }
191+
it.copy(
192+
items = postItems + HorizontalItem.Placeholder(PlaceholderState.Idle),
193+
isLoading = false,
194+
error = null,
195+
postItems = postItems
196+
)
197+
} else {
198+
it
199+
}
200+
}
201+
)
202+
}
203+
is Error -> {
204+
vs.copy(
205+
items = vs.items.map {
206+
if (it is Item.HorizontalList) {
207+
it.copy(
208+
items = emptyList(),
209+
isLoading = false,
210+
error = error,
211+
postItems = emptyList()
212+
)
213+
} else {
214+
it
215+
}
216+
}
217+
)
218+
}
219+
Loading -> {
220+
vs.copy(
221+
items = vs.items.map {
222+
if (it is Item.HorizontalList) {
223+
it.copy(
224+
items = emptyList(),
225+
isLoading = true,
226+
error = null,
227+
postItems = emptyList()
228+
)
229+
} else {
230+
it
231+
}
232+
}
233+
)
234+
}
235+
}
236+
}
237+
}
157238
}
158239

159240
sealed class SingleEvent {
@@ -163,5 +244,7 @@ interface MainContract {
163244
interface Interactor {
164245
fun photoNextPageChanges(start: Int, limit: Int): Observable<PartialStateChange.PhotoNextPage>
165246
fun photoFirstPageChanges(limit: Int): Observable<PartialStateChange.PhotoFirstPage>
247+
248+
fun postFirstPageChanges(limit: Int): Observable<PartialStateChange.PostFirstPage>
166249
}
167250
}

0 commit comments

Comments
 (0)