From 0e169951f7a05c64a7b63d211045b9cecb441099 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 26 Oct 2022 23:20:32 +0200 Subject: [PATCH] Fix grid/list toggle implementation of feed --- .../subscription/SubscriptionFragment.kt | 278 +++++++----------- .../subscription/SubscriptionViewModel.kt | 62 +++- .../decoration/FeedGroupCarouselDecoration.kt | 35 --- .../item/FeedGroupAddNewGridItem.kt | 12 + ...GroupAddItem.kt => FeedGroupAddNewItem.kt} | 2 +- .../item/FeedGroupAddVerticalItem.kt | 12 - ...rticalItem.kt => FeedGroupCardGridItem.kt} | 14 +- .../subscription/item/FeedGroupCardItem.kt | 2 +- .../item/FeedGroupCarouselItem.kt | 64 ++-- .../local/subscription/item/GroupsHeader.kt | 50 ++++ .../newpipe/local/subscription/item/Header.kt | 17 ++ .../subscription/item/HeaderWithMenuItem.kt | 56 ---- ...m.xml => feed_group_add_new_grid_item.xml} | 16 +- .../res/layout/feed_group_add_new_item.xml | 23 +- ...item.xml => feed_group_card_grid_item.xml} | 33 ++- .../main/res/layout/feed_group_card_item.xml | 37 ++- .../main/res/layout/feed_item_carousel.xml | 1 + ...tem.xml => subscription_groups_header.xml} | 8 +- .../main/res/layout/subscription_header.xml | 15 + 19 files changed, 354 insertions(+), 383 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/local/subscription/decoration/FeedGroupCarouselDecoration.kt create mode 100644 app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddNewGridItem.kt rename app/src/main/java/org/schabi/newpipe/local/subscription/item/{FeedGroupAddItem.kt => FeedGroupAddNewItem.kt} (86%) delete mode 100644 app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddVerticalItem.kt rename app/src/main/java/org/schabi/newpipe/local/subscription/item/{FeedGroupCardVerticalItem.kt => FeedGroupCardGridItem.kt} (68%) create mode 100644 app/src/main/java/org/schabi/newpipe/local/subscription/item/GroupsHeader.kt create mode 100644 app/src/main/java/org/schabi/newpipe/local/subscription/item/Header.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt rename app/src/main/res/layout/{feed_group_add_new_vertical_item.xml => feed_group_add_new_grid_item.xml} (82%) rename app/src/main/res/layout/{feed_group_card_vertical_item.xml => feed_group_card_grid_item.xml} (65%) rename app/src/main/res/layout/{header_with_menu_item.xml => subscription_groups_header.xml} (87%) create mode 100644 app/src/main/res/layout/subscription_header.xml diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt index 0ee77b4ee..7bee7f7ee 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt @@ -20,10 +20,8 @@ import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.RecyclerView import com.xwray.groupie.Group import com.xwray.groupie.GroupAdapter -import com.xwray.groupie.Item import com.xwray.groupie.Section import com.xwray.groupie.viewbinding.GroupieViewHolder import icepick.State @@ -44,13 +42,13 @@ import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialog import org.schabi.newpipe.local.subscription.item.ChannelItem import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem -import org.schabi.newpipe.local.subscription.item.FeedGroupAddItem -import org.schabi.newpipe.local.subscription.item.FeedGroupAddVerticalItem +import org.schabi.newpipe.local.subscription.item.FeedGroupAddNewGridItem +import org.schabi.newpipe.local.subscription.item.FeedGroupAddNewItem +import org.schabi.newpipe.local.subscription.item.FeedGroupCardGridItem import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem -import org.schabi.newpipe.local.subscription.item.FeedGroupCardVerticalItem import org.schabi.newpipe.local.subscription.item.FeedGroupCarouselItem -import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem -import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem.Companion.PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM +import org.schabi.newpipe.local.subscription.item.GroupsHeader +import org.schabi.newpipe.local.subscription.item.Header import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE @@ -77,11 +75,10 @@ class SubscriptionFragment : BaseStateFragment() { private val disposables: CompositeDisposable = CompositeDisposable() private val groupAdapter = GroupAdapter>() - private val feedGroupsSection = Section() - private var feedGroupsCarousel: FeedGroupCarouselItem? = null - private lateinit var feedGroupsSortMenuItem: HeaderWithMenuItem + private lateinit var carouselAdapter: GroupAdapter> + private lateinit var feedGroupsCarousel: FeedGroupCarouselItem + private lateinit var feedGroupsSortMenuItem: GroupsHeader private val subscriptionsSection = Section() - private var defaultListView: Boolean = true private val requestExportLauncher = registerForActivityResult(StartActivityForResult(), this::requestExportResult) @@ -94,11 +91,7 @@ class SubscriptionFragment : BaseStateFragment() { @State @JvmField - var feedGroupsListState: Parcelable? = null - - @State - @JvmField - var feedGroupsListVerticalState: Parcelable? = null + var feedGroupsCarouselState: Parcelable? = null init { setHasOptionsMenu(true) @@ -108,11 +101,6 @@ class SubscriptionFragment : BaseStateFragment() { // Fragment LifeCycle // ///////////////////////////////////////////////////////////////////////// - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setupInitialLayout() - } - override fun onAttach(context: Context) { super.onAttach(context) subscriptionManager = SubscriptionManager(requireContext()) @@ -125,8 +113,7 @@ class SubscriptionFragment : BaseStateFragment() { override fun onPause() { super.onPause() itemsListState = binding.itemsList.layoutManager?.onSaveInstanceState() - feedGroupsListState = feedGroupsCarousel?.onSaveInstanceState() - feedGroupsListVerticalState = feedGroupsCarousel?.onSaveInstanceState() + feedGroupsCarouselState = feedGroupsCarousel.onSaveInstanceState() } override fun onDestroy() { @@ -193,7 +180,7 @@ class SubscriptionFragment : BaseStateFragment() { menuItem: MenuItem, onClick: Runnable ): MenuItem { - menuItem.setOnMenuItemClickListener { _ -> + menuItem.setOnMenuItemClickListener { onClick.run() true } @@ -254,105 +241,6 @@ class SubscriptionFragment : BaseStateFragment() { // Fragment Views // //////////////////////////////////////////////////////////////////////// - private fun setupInitialLayout() { - defaultListView = true - Section().apply { - val carouselAdapter = GroupAdapter>() - - carouselAdapter.add(FeedGroupCardItem(-1, getString(R.string.all), FeedGroupIcon.RSS)) - carouselAdapter.add(feedGroupsSection) - carouselAdapter.add(FeedGroupAddItem()) - - carouselAdapter.setOnItemClickListener { item, _ -> - listenerFeedGroups.selected(item) - } - carouselAdapter.setOnItemLongClickListener { item, _ -> - if (item is FeedGroupCardItem) { - if (item.groupId == FeedGroupEntity.GROUP_ALL_ID) { - return@setOnItemLongClickListener false - } - } - listenerFeedGroups.held(item) - return@setOnItemLongClickListener true - } - - feedGroupsCarousel = FeedGroupCarouselItem(requireContext(), carouselAdapter, RecyclerView.HORIZONTAL, true) - - feedGroupsSortMenuItem = HeaderWithMenuItem( - getString(R.string.feed_groups_header_title), - R.drawable.ic_list, - R.drawable.ic_sort, - listViewOnClickListener = ::changeVerticalLayout, - menuItemOnClickListener = ::openReorderDialog - ) - - add(Section(feedGroupsSortMenuItem, listOf(feedGroupsCarousel))) - groupAdapter.clear() - groupAdapter.add(this) - } - - subscriptionsSection.setPlaceholder(EmptyPlaceholderItem()) - subscriptionsSection.setHideWhenEmpty(true) - - groupAdapter.add( - Section( - HeaderWithMenuItem( - getString(R.string.tab_subscriptions) - ), - listOf(subscriptionsSection) - ) - ) - view?.let { initViews(it, savedInstanceState = Bundle()) } - } - - private fun changeVerticalLayout() { - defaultListView = false - Section().apply { - val carouselAdapter = GroupAdapter>() - - carouselAdapter.add(FeedGroupCardVerticalItem(-1, getString(R.string.all), FeedGroupIcon.RSS)) - carouselAdapter.add(feedGroupsSection) - carouselAdapter.add(FeedGroupAddVerticalItem()) - - carouselAdapter.setOnItemClickListener { item, _ -> - listenerFeedVerticalGroups.selected(item) - } - carouselAdapter.setOnItemLongClickListener { item, _ -> - if (item is FeedGroupCardVerticalItem) { - if (item.groupId == FeedGroupEntity.GROUP_ALL_ID) { - return@setOnItemLongClickListener false - } - } - listenerFeedVerticalGroups.held(item) - return@setOnItemLongClickListener true - } - feedGroupsCarousel = FeedGroupCarouselItem(requireContext(), carouselAdapter, RecyclerView.VERTICAL, false) - - feedGroupsSortMenuItem = HeaderWithMenuItem( - getString(R.string.feed_groups_header_title), - R.drawable.ic_apps, - R.drawable.ic_sort, - listViewOnClickListener = ::setupInitialLayout, - menuItemOnClickListener = ::openReorderDialog - ) - add(Section(feedGroupsSortMenuItem, listOf(feedGroupsCarousel))) - groupAdapter.clear() - groupAdapter.add(this) - } - subscriptionsSection.setPlaceholder(EmptyPlaceholderItem()) - subscriptionsSection.setHideWhenEmpty(true) - - groupAdapter.add( - Section( - HeaderWithMenuItem( - getString(R.string.tab_subscriptions) - ), - listOf(subscriptionsSection) - ) - ) - view?.let { initViews(it, savedInstanceState = Bundle()) } - } - override fun initViews(rootView: View, savedInstanceState: Bundle?) { super.initViews(rootView, savedInstanceState) _binding = FragmentSubscriptionBinding.bind(rootView) @@ -363,10 +251,81 @@ class SubscriptionFragment : BaseStateFragment() { } binding.itemsList.adapter = groupAdapter - viewModel = ViewModelProvider(this).get(SubscriptionViewModel::class.java) + viewModel = ViewModelProvider(this)[SubscriptionViewModel::class.java] viewModel.stateLiveData.observe(viewLifecycleOwner) { it?.let(this::handleResult) } viewModel.feedGroupsLiveData.observe(viewLifecycleOwner) { it?.let(this::handleFeedGroups) } - viewModel.feedGroupsVerticalLiveData.observe(viewLifecycleOwner) { it?.let(this::handleFeedGroupsVertical) } + + setupInitialLayout() + } + + private fun setupInitialLayout() { + Section().apply { + carouselAdapter = GroupAdapter>() + + carouselAdapter.setOnItemClickListener { item, _ -> + when (item) { + is FeedGroupCardItem -> + NavigationHelper.openFeedFragment(fm, item.groupId, item.name) + is FeedGroupCardGridItem -> + NavigationHelper.openFeedFragment(fm, item.groupId, item.name) + is FeedGroupAddNewItem -> + FeedGroupDialog.newInstance().show(fm, null) + is FeedGroupAddNewGridItem -> + FeedGroupDialog.newInstance().show(fm, null) + } + } + carouselAdapter.setOnItemLongClickListener { item, _ -> + if (( + item is FeedGroupCardItem && + item.groupId == FeedGroupEntity.GROUP_ALL_ID + ) || + ( + item is FeedGroupCardGridItem && + item.groupId == FeedGroupEntity.GROUP_ALL_ID + ) + ) { + return@setOnItemLongClickListener false + } + + when (item) { + is FeedGroupCardItem -> + FeedGroupDialog.newInstance(item.groupId).show(fm, null) + is FeedGroupCardGridItem -> + FeedGroupDialog.newInstance(item.groupId).show(fm, null) + } + return@setOnItemLongClickListener true + } + + feedGroupsCarousel = FeedGroupCarouselItem( + carouselAdapter = carouselAdapter, + listViewMode = viewModel.getListViewMode() + ) + + feedGroupsSortMenuItem = GroupsHeader( + title = getString(R.string.feed_groups_header_title), + onSortClicked = ::openReorderDialog, + onToggleListViewModeClicked = ::toggleListViewMode, + listViewMode = viewModel.getListViewMode(), + ) + + add(Section(feedGroupsSortMenuItem, listOf(feedGroupsCarousel))) + groupAdapter.clear() + groupAdapter.add(this) + } + + subscriptionsSection.setPlaceholder(EmptyPlaceholderItem()) + subscriptionsSection.setHideWhenEmpty(true) + + groupAdapter.add( + Section( + Header(getString(R.string.tab_subscriptions)), + listOf(subscriptionsSection) + ) + ) + } + + private fun toggleListViewMode() { + viewModel.setListViewMode(!viewModel.getListViewMode()) } private fun showLongTapDialog(selectedItem: ChannelInfoItem) { @@ -410,36 +369,6 @@ class SubscriptionFragment : BaseStateFragment() { override fun doInitialLoadLogic() = Unit override fun startLoading(forceLoad: Boolean) = Unit - private val listenerFeedGroups = object : OnClickGesture> { - override fun selected(selectedItem: Item<*>?) { - when (selectedItem) { - is FeedGroupCardItem -> NavigationHelper.openFeedFragment(fm, selectedItem.groupId, selectedItem.name) - is FeedGroupAddItem -> FeedGroupDialog.newInstance().show(fm, null) - } - } - - override fun held(selectedItem: Item<*>?) { - when (selectedItem) { - is FeedGroupCardItem -> FeedGroupDialog.newInstance(selectedItem.groupId).show(fm, null) - } - } - } - - private val listenerFeedVerticalGroups = object : OnClickGesture> { - override fun selected(selectedItem: Item<*>?) { - when (selectedItem) { - is FeedGroupCardVerticalItem -> NavigationHelper.openFeedFragment(fm, selectedItem.groupId, selectedItem.name) - is FeedGroupAddVerticalItem -> FeedGroupDialog.newInstance().show(fm, null) - } - } - - override fun held(selectedItem: Item<*>?) { - when (selectedItem) { - is FeedGroupCardVerticalItem -> FeedGroupDialog.newInstance(selectedItem.groupId).show(fm, null) - } - } - } - private val listenerChannelItem = object : OnClickGesture { override fun selected(selectedItem: ChannelInfoItem) = NavigationHelper.openChannelFragment( fm, @@ -482,30 +411,29 @@ class SubscriptionFragment : BaseStateFragment() { } private fun handleFeedGroups(groups: List) { - if (defaultListView) { - feedGroupsSection.update(groups) + val listViewMode = viewModel.getListViewMode() - if (feedGroupsListState != null) { - feedGroupsCarousel?.onRestoreInstanceState(feedGroupsListState) - feedGroupsListState = null - } + carouselAdapter.clear() + carouselAdapter.add( + if (listViewMode) + FeedGroupCardItem(-1, getString(R.string.all), FeedGroupIcon.RSS) + else + FeedGroupCardGridItem(-1, getString(R.string.all), FeedGroupIcon.RSS) + ) + carouselAdapter.addAll(groups) + carouselAdapter.add(if (listViewMode) FeedGroupAddNewItem() else FeedGroupAddNewGridItem()) - feedGroupsSortMenuItem.showMenuItem = groups.size > 1 - binding.itemsList.post { feedGroupsSortMenuItem.notifyChanged(PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM) } + if (feedGroupsCarouselState != null) { + feedGroupsCarousel.onRestoreInstanceState(feedGroupsCarouselState) + feedGroupsCarouselState = null } - } - private fun handleFeedGroupsVertical(groups: List) { - if (!defaultListView) { - feedGroupsSection.update(groups) - - if (feedGroupsListVerticalState != null) { - feedGroupsCarousel?.onRestoreInstanceState(feedGroupsListVerticalState) - feedGroupsListVerticalState = null - } - - feedGroupsSortMenuItem.showMenuItem = groups.size > 1 - binding.itemsList.post { feedGroupsSortMenuItem.notifyChanged(PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM) } + feedGroupsCarousel.listViewMode = listViewMode + feedGroupsSortMenuItem.showSortButton = groups.size > 1 + feedGroupsSortMenuItem.listViewMode = listViewMode + binding.itemsList.post { + feedGroupsCarousel.notifyChanged(FeedGroupCarouselItem.PAYLOAD_UPDATE_LIST_VIEW_MODE) + feedGroupsSortMenuItem.notifyChanged(GroupsHeader.PAYLOAD_UPDATE_ICONS) } } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionViewModel.kt index 3f378929e..814e697ce 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionViewModel.kt @@ -1,15 +1,20 @@ package org.schabi.newpipe.local.subscription import android.app.Application +import android.content.res.Configuration import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.preference.PreferenceManager import com.xwray.groupie.Group +import io.reactivex.rxjava3.core.Flowable +import io.reactivex.rxjava3.processors.BehaviorProcessor import io.reactivex.rxjava3.schedulers.Schedulers +import org.schabi.newpipe.R import org.schabi.newpipe.local.feed.FeedDatabaseManager import org.schabi.newpipe.local.subscription.item.ChannelItem +import org.schabi.newpipe.local.subscription.item.FeedGroupCardGridItem import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem -import org.schabi.newpipe.local.subscription.item.FeedGroupCardVerticalItem import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT import java.util.concurrent.TimeUnit @@ -17,31 +22,31 @@ class SubscriptionViewModel(application: Application) : AndroidViewModel(applica private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(application) private var subscriptionManager = SubscriptionManager(application) + // true -> list view, false -> grid view + private val listViewMode = BehaviorProcessor.createDefault(!isGridLayout(application)) + private val listViewModeFlowable = listViewMode.distinctUntilChanged() + private val mutableStateLiveData = MutableLiveData() private val mutableFeedGroupsLiveData = MutableLiveData>() - private val mutableFeedGroupsVerticalLiveData = MutableLiveData>() val stateLiveData: LiveData = mutableStateLiveData val feedGroupsLiveData: LiveData> = mutableFeedGroupsLiveData - val feedGroupsVerticalLiveData: LiveData> = mutableFeedGroupsVerticalLiveData - private var feedGroupItemsDisposable = feedDatabaseManager.groups() + private var feedGroupItemsDisposable = Flowable + .combineLatest( + feedDatabaseManager.groups(), + listViewModeFlowable, + ::Pair + ) .throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS) - .map { it.map(::FeedGroupCardItem) } + .map { (feedGroups, listViewMode) -> + feedGroups.map(if (listViewMode) ::FeedGroupCardItem else ::FeedGroupCardGridItem) + } .subscribeOn(Schedulers.io()) .subscribe( { mutableFeedGroupsLiveData.postValue(it) }, { mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) } ) - private var feedGroupVerticalItemsDisposable = feedDatabaseManager.groups() - .throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS) - .map { it.map(::FeedGroupCardVerticalItem) } - .subscribeOn(Schedulers.io()) - .subscribe( - { mutableFeedGroupsVerticalLiveData.postValue(it) }, - { mutableStateLiveData.postValue(SubscriptionState.ErrorState(it)) } - ) - private var stateItemsDisposable = subscriptionManager.subscriptions() .throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS) .map { it.map { entity -> ChannelItem(entity.toChannelInfoItem(), entity.uid, ChannelItem.ItemVersion.MINI) } } @@ -55,11 +60,38 @@ class SubscriptionViewModel(application: Application) : AndroidViewModel(applica super.onCleared() stateItemsDisposable.dispose() feedGroupItemsDisposable.dispose() - feedGroupVerticalItemsDisposable.dispose() + } + + fun setListViewMode(newListViewMode: Boolean) { + listViewMode.onNext(newListViewMode) + } + + fun getListViewMode(): Boolean { + return listViewMode.value ?: true } sealed class SubscriptionState { data class LoadedState(val subscriptions: List) : SubscriptionState() data class ErrorState(val error: Throwable? = null) : SubscriptionState() } + + companion object { + private fun isGridLayout(application: Application): Boolean { + val listMode = PreferenceManager.getDefaultSharedPreferences(application) + .getString( + application.getString(R.string.list_view_mode_key), + application.getString(R.string.list_view_mode_value) + ) + + return if ("auto" == listMode) { + val configuration: Configuration = application.resources.configuration + ( + configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && + configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE) + ) + } else { + "grid" == listMode + } + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/decoration/FeedGroupCarouselDecoration.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/decoration/FeedGroupCarouselDecoration.kt deleted file mode 100644 index a113a8b41..000000000 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/decoration/FeedGroupCarouselDecoration.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.schabi.newpipe.local.subscription.decoration - -import android.content.Context -import android.graphics.Rect -import android.view.View -import androidx.recyclerview.widget.RecyclerView -import org.schabi.newpipe.R - -class FeedGroupCarouselDecoration(context: Context) : RecyclerView.ItemDecoration() { - - private val marginStartEnd: Int - private val marginTopBottom: Int - private val marginBetweenItems: Int - - init { - with(context.resources) { - marginStartEnd = getDimensionPixelOffset(R.dimen.feed_group_carousel_start_end_margin) - marginTopBottom = getDimensionPixelOffset(R.dimen.feed_group_carousel_top_bottom_margin) - marginBetweenItems = getDimensionPixelOffset(R.dimen.feed_group_carousel_between_items_margin) - } - } - - override fun getItemOffsets(outRect: Rect, child: View, parent: RecyclerView, state: RecyclerView.State) { - val childAdapterPosition = parent.getChildAdapterPosition(child) - val childAdapterCount = parent.adapter?.itemCount ?: 0 - - outRect.set(marginBetweenItems, marginTopBottom, 0, marginTopBottom) - - if (childAdapterPosition >= 0) { - outRect.left = marginStartEnd - } else if (childAdapterPosition == childAdapterCount - 1) { - outRect.right = marginStartEnd - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddNewGridItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddNewGridItem.kt new file mode 100644 index 000000000..55d1c6097 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddNewGridItem.kt @@ -0,0 +1,12 @@ +package org.schabi.newpipe.local.subscription.item + +import android.view.View +import com.xwray.groupie.viewbinding.BindableItem +import org.schabi.newpipe.R +import org.schabi.newpipe.databinding.FeedGroupAddNewGridItemBinding + +class FeedGroupAddNewGridItem : BindableItem() { + override fun getLayout(): Int = R.layout.feed_group_add_new_grid_item + override fun bind(viewBinding: FeedGroupAddNewGridItemBinding, position: Int) {} + override fun initializeViewBinding(view: View) = FeedGroupAddNewGridItemBinding.bind(view) +} diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddNewItem.kt similarity index 86% rename from app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddItem.kt rename to app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddNewItem.kt index 434b4f29a..735df80df 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddNewItem.kt @@ -5,7 +5,7 @@ import com.xwray.groupie.viewbinding.BindableItem import org.schabi.newpipe.R import org.schabi.newpipe.databinding.FeedGroupAddNewItemBinding -class FeedGroupAddItem : BindableItem() { +class FeedGroupAddNewItem : BindableItem() { override fun getLayout(): Int = R.layout.feed_group_add_new_item override fun bind(viewBinding: FeedGroupAddNewItemBinding, position: Int) {} override fun initializeViewBinding(view: View) = FeedGroupAddNewItemBinding.bind(view) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddVerticalItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddVerticalItem.kt deleted file mode 100644 index 049728591..000000000 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddVerticalItem.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.schabi.newpipe.local.subscription.item - -import android.view.View -import com.xwray.groupie.viewbinding.BindableItem -import org.schabi.newpipe.R -import org.schabi.newpipe.databinding.FeedGroupAddNewVerticalItemBinding - -class FeedGroupAddVerticalItem : BindableItem() { - override fun getLayout(): Int = R.layout.feed_group_add_new_vertical_item - override fun bind(viewBinding: FeedGroupAddNewVerticalItemBinding, position: Int) {} - override fun initializeViewBinding(view: View) = FeedGroupAddNewVerticalItemBinding.bind(view) -} diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardVerticalItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardGridItem.kt similarity index 68% rename from app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardVerticalItem.kt rename to app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardGridItem.kt index 9750da7b4..5a9d6887b 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardVerticalItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardGridItem.kt @@ -4,14 +4,14 @@ import android.view.View import com.xwray.groupie.viewbinding.BindableItem import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity -import org.schabi.newpipe.databinding.FeedGroupCardVerticalItemBinding +import org.schabi.newpipe.databinding.FeedGroupCardGridItemBinding import org.schabi.newpipe.local.subscription.FeedGroupIcon -data class FeedGroupCardVerticalItem( +data class FeedGroupCardGridItem( val groupId: Long = FeedGroupEntity.GROUP_ALL_ID, val name: String, - val icon: FeedGroupIcon -) : BindableItem() { + val icon: FeedGroupIcon, +) : BindableItem() { constructor (feedGroupEntity: FeedGroupEntity) : this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon) override fun getId(): Long { @@ -21,12 +21,12 @@ data class FeedGroupCardVerticalItem( } } - override fun getLayout(): Int = R.layout.feed_group_card_vertical_item + override fun getLayout(): Int = R.layout.feed_group_card_grid_item - override fun bind(viewBinding: FeedGroupCardVerticalItemBinding, position: Int) { + override fun bind(viewBinding: FeedGroupCardGridItemBinding, position: Int) { viewBinding.title.text = name viewBinding.icon.setImageResource(icon.getDrawableRes()) } - override fun initializeViewBinding(view: View) = FeedGroupCardVerticalItemBinding.bind(view) + override fun initializeViewBinding(view: View) = FeedGroupCardGridItemBinding.bind(view) } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardItem.kt index a4fa84798..7b78b3d95 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardItem.kt @@ -10,7 +10,7 @@ import org.schabi.newpipe.local.subscription.FeedGroupIcon data class FeedGroupCardItem( val groupId: Long = FeedGroupEntity.GROUP_ALL_ID, val name: String, - val icon: FeedGroupIcon, + val icon: FeedGroupIcon ) : BindableItem() { constructor (feedGroupEntity: FeedGroupEntity) : this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCarouselItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCarouselItem.kt index e53693cc8..ad1e7e690 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCarouselItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCarouselItem.kt @@ -1,6 +1,5 @@ package org.schabi.newpipe.local.subscription.item -import android.content.Context import android.os.Parcelable import android.view.View import androidx.recyclerview.widget.GridLayoutManager @@ -10,54 +9,77 @@ import com.xwray.groupie.viewbinding.BindableItem import com.xwray.groupie.viewbinding.GroupieViewHolder import org.schabi.newpipe.R import org.schabi.newpipe.databinding.FeedItemCarouselBinding -import org.schabi.newpipe.local.subscription.decoration.FeedGroupCarouselDecoration +import org.schabi.newpipe.util.DeviceUtils +import java.lang.Integer.max class FeedGroupCarouselItem( - context: Context, private val carouselAdapter: GroupAdapter>, - private var listView: Int, - private var isGridLayout: Boolean + var listViewMode: Boolean ) : BindableItem() { - private val feedGroupCarouselDecoration = FeedGroupCarouselDecoration(context) + companion object { + const val PAYLOAD_UPDATE_LIST_VIEW_MODE = 2 + } - private var linearLayoutManager: LinearLayoutManager? = null + private var carouselLayoutManager: LinearLayoutManager? = null private var listState: Parcelable? = null override fun getLayout() = R.layout.feed_item_carousel fun onSaveInstanceState(): Parcelable? { - listState = linearLayoutManager?.onSaveInstanceState() + listState = carouselLayoutManager?.onSaveInstanceState() return listState } fun onRestoreInstanceState(state: Parcelable?) { - linearLayoutManager?.onRestoreInstanceState(state) + carouselLayoutManager?.onRestoreInstanceState(state) listState = state } override fun initializeViewBinding(view: View): FeedItemCarouselBinding { - val viewHolder = FeedItemCarouselBinding.bind(view) + val viewBinding = FeedItemCarouselBinding.bind(view) + updateViewMode(viewBinding) + return viewBinding + } - linearLayoutManager = LinearLayoutManager(view.context, listView, false) - - viewHolder.recyclerView.apply { - layoutManager = linearLayoutManager - adapter = carouselAdapter - addItemDecoration(feedGroupCarouselDecoration) + override fun bind( + viewBinding: FeedItemCarouselBinding, + position: Int, + payloads: MutableList + ) { + if (payloads.contains(PAYLOAD_UPDATE_LIST_VIEW_MODE)) { + updateViewMode(viewBinding) + return } - if (isGridLayout) - viewHolder.recyclerView.setLayoutManager(GridLayoutManager(view.context, 3)) - return viewHolder + + super.bind(viewBinding, position, payloads) } override fun bind(viewBinding: FeedItemCarouselBinding, position: Int) { viewBinding.recyclerView.apply { adapter = carouselAdapter } - linearLayoutManager?.onRestoreInstanceState(listState) + carouselLayoutManager?.onRestoreInstanceState(listState) } override fun unbind(viewHolder: GroupieViewHolder) { super.unbind(viewHolder) + listState = carouselLayoutManager?.onSaveInstanceState() + } - listState = linearLayoutManager?.onSaveInstanceState() + private fun updateViewMode(viewBinding: FeedItemCarouselBinding) { + viewBinding.recyclerView.apply { adapter = carouselAdapter } + + val context = viewBinding.root.context + carouselLayoutManager = if (listViewMode) { + LinearLayoutManager(context) + } else { + GridLayoutManager( + context, + max(1, viewBinding.recyclerView.width / DeviceUtils.dpToPx(112, context)) + ) + } + + viewBinding.recyclerView.apply { + layoutManager = carouselLayoutManager + adapter = carouselAdapter + } } } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/GroupsHeader.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/GroupsHeader.kt new file mode 100644 index 000000000..8d5088890 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/GroupsHeader.kt @@ -0,0 +1,50 @@ +package org.schabi.newpipe.local.subscription.item + +import android.view.View +import androidx.core.view.isVisible +import com.xwray.groupie.viewbinding.BindableItem +import org.schabi.newpipe.R +import org.schabi.newpipe.databinding.SubscriptionGroupsHeaderBinding + +class GroupsHeader( + private val title: String, + private val onSortClicked: () -> Unit, + private val onToggleListViewModeClicked: () -> Unit, + var showSortButton: Boolean = true, + var listViewMode: Boolean = true +) : BindableItem() { + companion object { + const val PAYLOAD_UPDATE_ICONS = 1 + } + + override fun getLayout(): Int = R.layout.subscription_groups_header + + override fun bind( + viewBinding: SubscriptionGroupsHeaderBinding, + position: Int, + payloads: MutableList + ) { + if (payloads.contains(PAYLOAD_UPDATE_ICONS)) { + updateIcons(viewBinding) + return + } + + super.bind(viewBinding, position, payloads) + } + + override fun bind(viewBinding: SubscriptionGroupsHeaderBinding, position: Int) { + viewBinding.headerTitle.text = title + viewBinding.headerSort.setOnClickListener { onSortClicked() } + viewBinding.headerToggleViewMode.setOnClickListener { onToggleListViewModeClicked() } + updateIcons(viewBinding) + } + + override fun initializeViewBinding(view: View) = SubscriptionGroupsHeaderBinding.bind(view) + + private fun updateIcons(viewBinding: SubscriptionGroupsHeaderBinding) { + viewBinding.headerToggleViewMode.setImageResource( + if (listViewMode) R.drawable.ic_apps else R.drawable.ic_list + ) + viewBinding.headerSort.isVisible = showSortButton + } +} diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/Header.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/Header.kt new file mode 100644 index 000000000..87a3ac768 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/Header.kt @@ -0,0 +1,17 @@ +package org.schabi.newpipe.local.subscription.item + +import android.view.View +import com.xwray.groupie.viewbinding.BindableItem +import org.schabi.newpipe.R +import org.schabi.newpipe.databinding.SubscriptionHeaderBinding + +class Header(private val title: String) : BindableItem() { + + override fun getLayout(): Int = R.layout.subscription_header + + override fun bind(viewBinding: SubscriptionHeaderBinding, position: Int) { + viewBinding.root.text = title + } + + override fun initializeViewBinding(view: View) = SubscriptionHeaderBinding.bind(view) +} diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt deleted file mode 100644 index 338083c87..000000000 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt +++ /dev/null @@ -1,56 +0,0 @@ -package org.schabi.newpipe.local.subscription.item - -import android.view.View -import android.view.View.OnClickListener -import androidx.annotation.DrawableRes -import androidx.core.view.isVisible -import com.xwray.groupie.viewbinding.BindableItem -import org.schabi.newpipe.R -import org.schabi.newpipe.databinding.HeaderWithMenuItemBinding - -class HeaderWithMenuItem( - val title: String, - @DrawableRes val itemIcon: Int = 0, - @DrawableRes val itemIconListView: Int = 0, - var showMenuItem: Boolean = true, - private val onClickListener: (() -> Unit)? = null, - private val listViewOnClickListener: (() -> Unit)? = null, - private val menuItemOnClickListener: (() -> Unit)? = null -) : BindableItem() { - companion object { - const val PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM = 1 - } - - override fun getLayout(): Int = R.layout.header_with_menu_item - - override fun bind(viewBinding: HeaderWithMenuItemBinding, position: Int, payloads: MutableList) { - if (payloads.contains(PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM)) { - updateMenuItemVisibility(viewBinding) - return - } - - super.bind(viewBinding, position, payloads) - } - - override fun bind(viewBinding: HeaderWithMenuItemBinding, position: Int) { - viewBinding.headerTitle.text = title - viewBinding.headerMenuItem2.setImageResource(itemIcon) - viewBinding.headerMenuItem.setImageResource(itemIconListView) - - val listener = onClickListener?.let { OnClickListener { onClickListener.invoke() } } - viewBinding.root.setOnClickListener(listener) - - val listViewListener = listViewOnClickListener?.let { OnClickListener { listViewOnClickListener.invoke() } } - viewBinding.headerMenuItem2.setOnClickListener(listViewListener) - - val menuItemListener = menuItemOnClickListener?.let { OnClickListener { menuItemOnClickListener.invoke() } } - viewBinding.headerMenuItem.setOnClickListener(menuItemListener) - updateMenuItemVisibility(viewBinding) - } - - override fun initializeViewBinding(view: View) = HeaderWithMenuItemBinding.bind(view) - - private fun updateMenuItemVisibility(viewBinding: HeaderWithMenuItemBinding) { - viewBinding.headerMenuItem.isVisible = showMenuItem - } -} diff --git a/app/src/main/res/layout/feed_group_add_new_vertical_item.xml b/app/src/main/res/layout/feed_group_add_new_grid_item.xml similarity index 82% rename from app/src/main/res/layout/feed_group_add_new_vertical_item.xml rename to app/src/main/res/layout/feed_group_add_new_grid_item.xml index 43427f291..e0c336d75 100644 --- a/app/src/main/res/layout/feed_group_add_new_vertical_item.xml +++ b/app/src/main/res/layout/feed_group_add_new_grid_item.xml @@ -2,9 +2,10 @@ @@ -31,15 +33,13 @@ android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="2dp" android:ellipsize="end" android:gravity="center" android:maxLines="1" android:text="@string/feed_create_new_group_button_title" android:textAllCaps="true" android:textColor="?attr/colorAccent" - android:textSize="10sp" - android:textStyle="bold" - tools:ignore="SmallSp" /> + android:textSize="14sp" + android:textStyle="bold" /> diff --git a/app/src/main/res/layout/feed_group_add_new_item.xml b/app/src/main/res/layout/feed_group_add_new_item.xml index a20cf71c0..b8f39542f 100644 --- a/app/src/main/res/layout/feed_group_add_new_item.xml +++ b/app/src/main/res/layout/feed_group_add_new_item.xml @@ -2,9 +2,10 @@ + android:orientation="horizontal"> @@ -31,15 +31,14 @@ android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="2dp" + android:layout_gravity="center_vertical" android:ellipsize="end" - android:gravity="center" android:maxLines="1" + android:padding="10dp" android:text="@string/feed_create_new_group_button_title" android:textAllCaps="true" android:textColor="?attr/colorAccent" - android:textSize="10sp" - android:textStyle="bold" - tools:ignore="SmallSp" /> + android:textSize="14sp" + android:textStyle="bold" /> diff --git a/app/src/main/res/layout/feed_group_card_vertical_item.xml b/app/src/main/res/layout/feed_group_card_grid_item.xml similarity index 65% rename from app/src/main/res/layout/feed_group_card_vertical_item.xml rename to app/src/main/res/layout/feed_group_card_grid_item.xml index 2924483c2..f50724ba2 100644 --- a/app/src/main/res/layout/feed_group_card_vertical_item.xml +++ b/app/src/main/res/layout/feed_group_card_grid_item.xml @@ -2,44 +2,45 @@ + android:orientation="vertical" + android:paddingTop="2dp"> - diff --git a/app/src/main/res/layout/feed_group_card_item.xml b/app/src/main/res/layout/feed_group_card_item.xml index 5e87216cb..29e553345 100644 --- a/app/src/main/res/layout/feed_group_card_item.xml +++ b/app/src/main/res/layout/feed_group_card_item.xml @@ -2,9 +2,10 @@ + android:layout_height="wrap_content" + android:orientation="horizontal"> + tools:text="All" /> + diff --git a/app/src/main/res/layout/feed_item_carousel.xml b/app/src/main/res/layout/feed_item_carousel.xml index 4e8fc2c90..22389e9fa 100644 --- a/app/src/main/res/layout/feed_item_carousel.xml +++ b/app/src/main/res/layout/feed_item_carousel.xml @@ -3,4 +3,5 @@ android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_margin="4dp" android:scrollbars="none" /> diff --git a/app/src/main/res/layout/header_with_menu_item.xml b/app/src/main/res/layout/subscription_groups_header.xml similarity index 87% rename from app/src/main/res/layout/header_with_menu_item.xml rename to app/src/main/res/layout/subscription_groups_header.xml index 170c40098..b290d9dda 100644 --- a/app/src/main/res/layout/header_with_menu_item.xml +++ b/app/src/main/res/layout/subscription_groups_header.xml @@ -23,18 +23,18 @@ tools:text="Header" /> + tools:src="@drawable/ic_apps" /> + android:src="@drawable/ic_sort" /> \ No newline at end of file diff --git a/app/src/main/res/layout/subscription_header.xml b/app/src/main/res/layout/subscription_header.xml new file mode 100644 index 000000000..c00fae036 --- /dev/null +++ b/app/src/main/res/layout/subscription_header.xml @@ -0,0 +1,15 @@ + + \ No newline at end of file