Skip to content

Commit 0e16995

Browse files
committed
Fix grid/list toggle implementation of feed
1 parent 8b9db36 commit 0e16995

19 files changed

Lines changed: 338 additions & 367 deletions

app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt

Lines changed: 87 additions & 159 deletions
Large diffs are not rendered by default.
Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,49 @@
11
package org.schabi.newpipe.local.subscription
22

33
import android.app.Application
4+
import android.content.res.Configuration
45
import androidx.lifecycle.AndroidViewModel
56
import androidx.lifecycle.LiveData
67
import androidx.lifecycle.MutableLiveData
8+
import androidx.preference.PreferenceManager
79
import com.xwray.groupie.Group
10+
import io.reactivex.rxjava3.core.Flowable
11+
import io.reactivex.rxjava3.processors.BehaviorProcessor
812
import io.reactivex.rxjava3.schedulers.Schedulers
13+
import org.schabi.newpipe.R
914
import org.schabi.newpipe.local.feed.FeedDatabaseManager
1015
import org.schabi.newpipe.local.subscription.item.ChannelItem
16+
import org.schabi.newpipe.local.subscription.item.FeedGroupCardGridItem
1117
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
12-
import org.schabi.newpipe.local.subscription.item.FeedGroupCardVerticalItem
1318
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
1419
import java.util.concurrent.TimeUnit
1520

1621
class SubscriptionViewModel(application: Application) : AndroidViewModel(application) {
1722
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(application)
1823
private var subscriptionManager = SubscriptionManager(application)
1924

25+
// true -> list view, false -> grid view
26+
private val listViewMode = BehaviorProcessor.createDefault(!isGridLayout(application))
27+
private val listViewModeFlowable = listViewMode.distinctUntilChanged()
28+
2029
private val mutableStateLiveData = MutableLiveData<SubscriptionState>()
2130
private val mutableFeedGroupsLiveData = MutableLiveData<List<Group>>()
22-
private val mutableFeedGroupsVerticalLiveData = MutableLiveData<List<Group>>()
2331
val stateLiveData: LiveData<SubscriptionState> = mutableStateLiveData
2432
val feedGroupsLiveData: LiveData<List<Group>> = mutableFeedGroupsLiveData
25-
val feedGroupsVerticalLiveData: LiveData<List<Group>> = mutableFeedGroupsVerticalLiveData
2633

27-
private var feedGroupItemsDisposable = feedDatabaseManager.groups()
28-
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
29-
.map { it.map(::FeedGroupCardItem) }
30-
.subscribeOn(Schedulers.io())
31-
.subscribe(
32-
{ mutableFeedGroupsLiveData.postValue(it) },
33-
{ mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) }
34+
private var feedGroupItemsDisposable = Flowable
35+
.combineLatest(
36+
feedDatabaseManager.groups(),
37+
listViewModeFlowable,
38+
::Pair
3439
)
35-
36-
private var feedGroupVerticalItemsDisposable = feedDatabaseManager.groups()
3740
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
38-
.map { it.map(::FeedGroupCardVerticalItem) }
41+
.map { (feedGroups, listViewMode) ->
42+
feedGroups.map(if (listViewMode) ::FeedGroupCardItem else ::FeedGroupCardGridItem)
43+
}
3944
.subscribeOn(Schedulers.io())
4045
.subscribe(
41-
{ mutableFeedGroupsVerticalLiveData.postValue(it) },
46+
{ mutableFeedGroupsLiveData.postValue(it) },
4247
{ mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) }
4348
)
4449

@@ -55,11 +60,38 @@ class SubscriptionViewModel(application: Application) : AndroidViewModel(applica
5560
super.onCleared()
5661
stateItemsDisposable.dispose()
5762
feedGroupItemsDisposable.dispose()
58-
feedGroupVerticalItemsDisposable.dispose()
63+
}
64+
65+
fun setListViewMode(newListViewMode: Boolean) {
66+
listViewMode.onNext(newListViewMode)
67+
}
68+
69+
fun getListViewMode(): Boolean {
70+
return listViewMode.value ?: true
5971
}
6072

6173
sealed class SubscriptionState {
6274
data class LoadedState(val subscriptions: List<Group>) : SubscriptionState()
6375
data class ErrorState(val error: Throwable? = null) : SubscriptionState()
6476
}
77+
78+
companion object {
79+
private fun isGridLayout(application: Application): Boolean {
80+
val listMode = PreferenceManager.getDefaultSharedPreferences(application)
81+
.getString(
82+
application.getString(R.string.list_view_mode_key),
83+
application.getString(R.string.list_view_mode_value)
84+
)
85+
86+
return if ("auto" == listMode) {
87+
val configuration: Configuration = application.resources.configuration
88+
(
89+
configuration.orientation == Configuration.ORIENTATION_LANDSCAPE &&
90+
configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE)
91+
)
92+
} else {
93+
"grid" == listMode
94+
}
95+
}
96+
}
6597
}

app/src/main/java/org/schabi/newpipe/local/subscription/decoration/FeedGroupCarouselDecoration.kt

Lines changed: 0 additions & 35 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.schabi.newpipe.local.subscription.item
2+
3+
import android.view.View
4+
import com.xwray.groupie.viewbinding.BindableItem
5+
import org.schabi.newpipe.R
6+
import org.schabi.newpipe.databinding.FeedGroupAddNewGridItemBinding
7+
8+
class FeedGroupAddNewGridItem : BindableItem<FeedGroupAddNewGridItemBinding>() {
9+
override fun getLayout(): Int = R.layout.feed_group_add_new_grid_item
10+
override fun bind(viewBinding: FeedGroupAddNewGridItemBinding, position: Int) {}
11+
override fun initializeViewBinding(view: View) = FeedGroupAddNewGridItemBinding.bind(view)
12+
}

app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddItem.kt renamed to app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddNewItem.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import com.xwray.groupie.viewbinding.BindableItem
55
import org.schabi.newpipe.R
66
import org.schabi.newpipe.databinding.FeedGroupAddNewItemBinding
77

8-
class FeedGroupAddItem : BindableItem<FeedGroupAddNewItemBinding>() {
8+
class FeedGroupAddNewItem : BindableItem<FeedGroupAddNewItemBinding>() {
99
override fun getLayout(): Int = R.layout.feed_group_add_new_item
1010
override fun bind(viewBinding: FeedGroupAddNewItemBinding, position: Int) {}
1111
override fun initializeViewBinding(view: View) = FeedGroupAddNewItemBinding.bind(view)

app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddVerticalItem.kt

Lines changed: 0 additions & 12 deletions
This file was deleted.

app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardVerticalItem.kt renamed to app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardGridItem.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import android.view.View
44
import com.xwray.groupie.viewbinding.BindableItem
55
import org.schabi.newpipe.R
66
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
7-
import org.schabi.newpipe.databinding.FeedGroupCardVerticalItemBinding
7+
import org.schabi.newpipe.databinding.FeedGroupCardGridItemBinding
88
import org.schabi.newpipe.local.subscription.FeedGroupIcon
99

10-
data class FeedGroupCardVerticalItem(
10+
data class FeedGroupCardGridItem(
1111
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
1212
val name: String,
13-
val icon: FeedGroupIcon
14-
) : BindableItem<FeedGroupCardVerticalItemBinding>() {
13+
val icon: FeedGroupIcon,
14+
) : BindableItem<FeedGroupCardGridItemBinding>() {
1515
constructor (feedGroupEntity: FeedGroupEntity) : this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon)
1616

1717
override fun getId(): Long {
@@ -21,12 +21,12 @@ data class FeedGroupCardVerticalItem(
2121
}
2222
}
2323

24-
override fun getLayout(): Int = R.layout.feed_group_card_vertical_item
24+
override fun getLayout(): Int = R.layout.feed_group_card_grid_item
2525

26-
override fun bind(viewBinding: FeedGroupCardVerticalItemBinding, position: Int) {
26+
override fun bind(viewBinding: FeedGroupCardGridItemBinding, position: Int) {
2727
viewBinding.title.text = name
2828
viewBinding.icon.setImageResource(icon.getDrawableRes())
2929
}
3030

31-
override fun initializeViewBinding(view: View) = FeedGroupCardVerticalItemBinding.bind(view)
31+
override fun initializeViewBinding(view: View) = FeedGroupCardGridItemBinding.bind(view)
3232
}

app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardItem.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import org.schabi.newpipe.local.subscription.FeedGroupIcon
1010
data class FeedGroupCardItem(
1111
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
1212
val name: String,
13-
val icon: FeedGroupIcon,
13+
val icon: FeedGroupIcon
1414
) : BindableItem<FeedGroupCardItemBinding>() {
1515
constructor (feedGroupEntity: FeedGroupEntity) : this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon)
1616

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.schabi.newpipe.local.subscription.item
22

3-
import android.content.Context
43
import android.os.Parcelable
54
import android.view.View
65
import androidx.recyclerview.widget.GridLayoutManager
@@ -10,54 +9,77 @@ import com.xwray.groupie.viewbinding.BindableItem
109
import com.xwray.groupie.viewbinding.GroupieViewHolder
1110
import org.schabi.newpipe.R
1211
import org.schabi.newpipe.databinding.FeedItemCarouselBinding
13-
import org.schabi.newpipe.local.subscription.decoration.FeedGroupCarouselDecoration
12+
import org.schabi.newpipe.util.DeviceUtils
13+
import java.lang.Integer.max
1414

1515
class FeedGroupCarouselItem(
16-
context: Context,
1716
private val carouselAdapter: GroupAdapter<GroupieViewHolder<FeedItemCarouselBinding>>,
18-
private var listView: Int,
19-
private var isGridLayout: Boolean
17+
var listViewMode: Boolean
2018
) : BindableItem<FeedItemCarouselBinding>() {
21-
private val feedGroupCarouselDecoration = FeedGroupCarouselDecoration(context)
19+
companion object {
20+
const val PAYLOAD_UPDATE_LIST_VIEW_MODE = 2
21+
}
2222

23-
private var linearLayoutManager: LinearLayoutManager? = null
23+
private var carouselLayoutManager: LinearLayoutManager? = null
2424
private var listState: Parcelable? = null
2525

2626
override fun getLayout() = R.layout.feed_item_carousel
2727

2828
fun onSaveInstanceState(): Parcelable? {
29-
listState = linearLayoutManager?.onSaveInstanceState()
29+
listState = carouselLayoutManager?.onSaveInstanceState()
3030
return listState
3131
}
3232

3333
fun onRestoreInstanceState(state: Parcelable?) {
34-
linearLayoutManager?.onRestoreInstanceState(state)
34+
carouselLayoutManager?.onRestoreInstanceState(state)
3535
listState = state
3636
}
3737

3838
override fun initializeViewBinding(view: View): FeedItemCarouselBinding {
39-
val viewHolder = FeedItemCarouselBinding.bind(view)
40-
41-
linearLayoutManager = LinearLayoutManager(view.context, listView, false)
39+
val viewBinding = FeedItemCarouselBinding.bind(view)
40+
updateViewMode(viewBinding)
41+
return viewBinding
42+
}
4243

43-
viewHolder.recyclerView.apply {
44-
layoutManager = linearLayoutManager
45-
adapter = carouselAdapter
46-
addItemDecoration(feedGroupCarouselDecoration)
44+
override fun bind(
45+
viewBinding: FeedItemCarouselBinding,
46+
position: Int,
47+
payloads: MutableList<Any>
48+
) {
49+
if (payloads.contains(PAYLOAD_UPDATE_LIST_VIEW_MODE)) {
50+
updateViewMode(viewBinding)
51+
return
4752
}
48-
if (isGridLayout)
49-
viewHolder.recyclerView.setLayoutManager(GridLayoutManager(view.context, 3))
50-
return viewHolder
53+
54+
super.bind(viewBinding, position, payloads)
5155
}
5256

5357
override fun bind(viewBinding: FeedItemCarouselBinding, position: Int) {
5458
viewBinding.recyclerView.apply { adapter = carouselAdapter }
55-
linearLayoutManager?.onRestoreInstanceState(listState)
59+
carouselLayoutManager?.onRestoreInstanceState(listState)
5660
}
5761

5862
override fun unbind(viewHolder: GroupieViewHolder<FeedItemCarouselBinding>) {
5963
super.unbind(viewHolder)
64+
listState = carouselLayoutManager?.onSaveInstanceState()
65+
}
6066

61-
listState = linearLayoutManager?.onSaveInstanceState()
67+
private fun updateViewMode(viewBinding: FeedItemCarouselBinding) {
68+
viewBinding.recyclerView.apply { adapter = carouselAdapter }
69+
70+
val context = viewBinding.root.context
71+
carouselLayoutManager = if (listViewMode) {
72+
LinearLayoutManager(context)
73+
} else {
74+
GridLayoutManager(
75+
context,
76+
max(1, viewBinding.recyclerView.width / DeviceUtils.dpToPx(112, context))
77+
)
78+
}
79+
80+
viewBinding.recyclerView.apply {
81+
layoutManager = carouselLayoutManager
82+
adapter = carouselAdapter
83+
}
6284
}
6385
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.schabi.newpipe.local.subscription.item
2+
3+
import android.view.View
4+
import androidx.core.view.isVisible
5+
import com.xwray.groupie.viewbinding.BindableItem
6+
import org.schabi.newpipe.R
7+
import org.schabi.newpipe.databinding.SubscriptionGroupsHeaderBinding
8+
9+
class GroupsHeader(
10+
private val title: String,
11+
private val onSortClicked: () -> Unit,
12+
private val onToggleListViewModeClicked: () -> Unit,
13+
var showSortButton: Boolean = true,
14+
var listViewMode: Boolean = true
15+
) : BindableItem<SubscriptionGroupsHeaderBinding>() {
16+
companion object {
17+
const val PAYLOAD_UPDATE_ICONS = 1
18+
}
19+
20+
override fun getLayout(): Int = R.layout.subscription_groups_header
21+
22+
override fun bind(
23+
viewBinding: SubscriptionGroupsHeaderBinding,
24+
position: Int,
25+
payloads: MutableList<Any>
26+
) {
27+
if (payloads.contains(PAYLOAD_UPDATE_ICONS)) {
28+
updateIcons(viewBinding)
29+
return
30+
}
31+
32+
super.bind(viewBinding, position, payloads)
33+
}
34+
35+
override fun bind(viewBinding: SubscriptionGroupsHeaderBinding, position: Int) {
36+
viewBinding.headerTitle.text = title
37+
viewBinding.headerSort.setOnClickListener { onSortClicked() }
38+
viewBinding.headerToggleViewMode.setOnClickListener { onToggleListViewModeClicked() }
39+
updateIcons(viewBinding)
40+
}
41+
42+
override fun initializeViewBinding(view: View) = SubscriptionGroupsHeaderBinding.bind(view)
43+
44+
private fun updateIcons(viewBinding: SubscriptionGroupsHeaderBinding) {
45+
viewBinding.headerToggleViewMode.setImageResource(
46+
if (listViewMode) R.drawable.ic_apps else R.drawable.ic_list
47+
)
48+
viewBinding.headerSort.isVisible = showSortButton
49+
}
50+
}

0 commit comments

Comments
 (0)