diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index 9e7cb757c..633ba5d78 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -5,7 +5,6 @@ import static org.schabi.newpipe.ktx.ViewUtils.animateHideRecyclerViewAllowingSc import android.content.Context; import android.content.SharedPreferences; -import android.content.res.Configuration; import android.content.res.Resources; import android.os.Bundle; import android.util.Log; @@ -31,6 +30,7 @@ import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; import org.schabi.newpipe.util.StateSaver; +import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.SuperScrollLayoutManager; import java.util.List; @@ -476,15 +476,6 @@ public abstract class BaseListFragment extends BaseStateFragment } protected boolean isGridLayout() { - final String listMode = PreferenceManager.getDefaultSharedPreferences(activity) - .getString(getString(R.string.list_view_mode_key), - getString(R.string.list_view_mode_value)); - if ("auto".equals(listMode)) { - final Configuration configuration = getResources().getConfiguration(); - return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE - && configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); - } else { - return "grid".equals(listMode); - } + return ThemeHelper.shouldUseGridLayout(activity); } } 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 20f8a01c1..ca0a7b8d4 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 @@ -22,7 +22,6 @@ import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.GridLayoutManager 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 @@ -43,11 +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.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.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 @@ -74,9 +75,9 @@ 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 val requestExportLauncher = @@ -90,7 +91,7 @@ class SubscriptionFragment : BaseStateFragment() { @State @JvmField - var feedGroupsListState: Parcelable? = null + var feedGroupsCarouselState: Parcelable? = null init { setHasOptionsMenu(true) @@ -100,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()) @@ -117,7 +113,7 @@ class SubscriptionFragment : BaseStateFragment() { override fun onPause() { super.onPause() itemsListState = binding.itemsList.layoutManager?.onSaveInstanceState() - feedGroupsListState = feedGroupsCarousel?.onSaveInstanceState() + feedGroupsCarouselState = feedGroupsCarousel.onSaveInstanceState() } override fun onDestroy() { @@ -184,7 +180,7 @@ class SubscriptionFragment : BaseStateFragment() { menuItem: MenuItem, onClick: Runnable ): MenuItem { - menuItem.setOnMenuItemClickListener { _ -> + menuItem.setOnMenuItemClickListener { onClick.run() true } @@ -245,51 +241,6 @@ class SubscriptionFragment : BaseStateFragment() { // Fragment Views // //////////////////////////////////////////////////////////////////////// - private fun setupInitialLayout() { - 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) - feedGroupsSortMenuItem = HeaderWithMenuItem( - getString(R.string.feed_groups_header_title), - R.drawable.ic_sort, - menuItemOnClickListener = ::openReorderDialog - ) - add(Section(feedGroupsSortMenuItem, listOf(feedGroupsCarousel))) - - groupAdapter.add(this) - } - - subscriptionsSection.setPlaceholder(EmptyPlaceholderItem()) - subscriptionsSection.setHideWhenEmpty(true) - - groupAdapter.add( - Section( - HeaderWithMenuItem( - getString(R.string.tab_subscriptions) - ), - listOf(subscriptionsSection) - ) - ) - } - override fun initViews(rootView: View, savedInstanceState: Bundle?) { super.initViews(rootView, savedInstanceState) _binding = FragmentSubscriptionBinding.bind(rootView) @@ -299,10 +250,83 @@ class SubscriptionFragment : BaseStateFragment() { spanSizeLookup = groupAdapter.spanSizeLookup } binding.itemsList.adapter = groupAdapter + binding.itemsList.itemAnimator = null - 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) } + + 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) { @@ -346,21 +370,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 listenerChannelItem = object : OnClickGesture { override fun selected(selectedItem: ChannelInfoItem) = NavigationHelper.openChannelFragment( fm, @@ -403,15 +412,39 @@ class SubscriptionFragment : BaseStateFragment() { } private fun handleFeedGroups(groups: List) { - feedGroupsSection.update(groups) + val listViewMode = viewModel.getListViewMode() - if (feedGroupsListState != null) { - feedGroupsCarousel?.onRestoreInstanceState(feedGroupsListState) - feedGroupsListState = null + if (feedGroupsCarouselState != null) { + feedGroupsCarousel.onRestoreInstanceState(feedGroupsCarouselState) + feedGroupsCarouselState = 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 { + if (context == null) { + // since this part was posted to the next UI cycle, the fragment might have been + // removed in the meantime + return@post + } + + feedGroupsCarousel.notifyChanged(FeedGroupCarouselItem.PAYLOAD_UPDATE_LIST_VIEW_MODE) + feedGroupsSortMenuItem.notifyChanged(GroupsHeader.PAYLOAD_UPDATE_ICONS) + + // update items here to prevent flickering + carouselAdapter.apply { + clear() + if (listViewMode) { + add(FeedGroupAddNewItem()) + add(FeedGroupCardItem(-1, getString(R.string.all), FeedGroupIcon.RSS)) + } else { + add(FeedGroupAddNewGridItem()) + add(FeedGroupCardGridItem(-1, getString(R.string.all), FeedGroupIcon.RSS)) + } + addAll(groups) + } + } } // ///////////////////////////////////////////////////////////////////////// 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 da009e1a0..cb14b33a6 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 @@ -5,25 +5,42 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData 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.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.util.DEFAULT_THROTTLE_TIMEOUT +import org.schabi.newpipe.util.ThemeHelper import java.util.concurrent.TimeUnit class SubscriptionViewModel(application: Application) : AndroidViewModel(application) { private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(application) private var subscriptionManager = SubscriptionManager(application) + // true -> list view, false -> grid view + private val listViewMode = BehaviorProcessor.createDefault( + !ThemeHelper.shouldUseGridLayout(application) + ) + private val listViewModeFlowable = listViewMode.distinctUntilChanged() + private val mutableStateLiveData = MutableLiveData() private val mutableFeedGroupsLiveData = MutableLiveData>() val stateLiveData: LiveData = mutableStateLiveData val feedGroupsLiveData: LiveData> = mutableFeedGroupsLiveData - 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) }, @@ -45,6 +62,14 @@ class SubscriptionViewModel(application: Application) : AndroidViewModel(applica feedGroupItemsDisposable.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() 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 7b7490eaa..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..a2870b849 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupAddNewGridItem.kt @@ -0,0 +1,14 @@ +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 initializeViewBinding(view: View) = FeedGroupAddNewGridItemBinding.bind(view) + override fun bind(viewBinding: FeedGroupAddNewGridItemBinding, position: Int) { + // this is a static item, nothing to do here + } +} 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 75% 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..e06e578f8 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,8 +5,10 @@ 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) + override fun bind(viewBinding: FeedGroupAddNewItemBinding, position: Int) { + // this is a static item, nothing to do here + } } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardGridItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardGridItem.kt new file mode 100644 index 000000000..5a9d6887b --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/FeedGroupCardGridItem.kt @@ -0,0 +1,32 @@ +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.database.feed.model.FeedGroupEntity +import org.schabi.newpipe.databinding.FeedGroupCardGridItemBinding +import org.schabi.newpipe.local.subscription.FeedGroupIcon + +data class FeedGroupCardGridItem( + val groupId: Long = FeedGroupEntity.GROUP_ALL_ID, + val name: String, + val icon: FeedGroupIcon, +) : BindableItem() { + constructor (feedGroupEntity: FeedGroupEntity) : this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon) + + override fun getId(): Long { + return when (groupId) { + FeedGroupEntity.GROUP_ALL_ID -> super.getId() + else -> groupId + } + } + + override fun getLayout(): Int = R.layout.feed_group_card_grid_item + + override fun bind(viewBinding: FeedGroupCardGridItemBinding, position: Int) { + viewBinding.title.text = name + viewBinding.icon.setImageResource(icon.getDrawableRes()) + } + + override fun initializeViewBinding(view: View) = FeedGroupCardGridItemBinding.bind(view) +} 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 44af16280..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,60 +1,85 @@ package org.schabi.newpipe.local.subscription.item -import android.content.Context import android.os.Parcelable import android.view.View +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView import com.xwray.groupie.GroupAdapter 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 val carouselAdapter: GroupAdapter>, + 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, RecyclerView.HORIZONTAL, 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 } - 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 79a272178..000000000 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/HeaderWithMenuItem.kt +++ /dev/null @@ -1,50 +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, - var showMenuItem: Boolean = true, - private val onClickListener: (() -> 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.headerMenuItem.setImageResource(itemIcon) - - val listener = onClickListener?.let { OnClickListener { onClickListener.invoke() } } - viewBinding.root.setOnClickListener(listener) - - 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/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index 389af80ee..ea22e9368 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -349,7 +349,7 @@ public final class ThemeHelper { return false; } else if (listMode.equals(context.getString(R.string.list_view_mode_grid_key))) { return true; - } else { + } else /* listMode.equals("auto") */ { final Configuration configuration = context.getResources().getConfiguration(); return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); diff --git a/app/src/main/res/layout/feed_group_add_new_grid_item.xml b/app/src/main/res/layout/feed_group_add_new_grid_item.xml new file mode 100644 index 000000000..e0c336d75 --- /dev/null +++ b/app/src/main/res/layout/feed_group_add_new_grid_item.xml @@ -0,0 +1,45 @@ + + + + + + + + + + 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 0dfe819a6..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_grid_item.xml b/app/src/main/res/layout/feed_group_card_grid_item.xml new file mode 100644 index 000000000..f50724ba2 --- /dev/null +++ b/app/src/main/res/layout/feed_group_card_grid_item.xml @@ -0,0 +1,46 @@ + + + + + + + + + + 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 e57a8167f..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 51% rename from app/src/main/res/layout/header_with_menu_item.xml rename to app/src/main/res/layout/subscription_groups_header.xml index fcf888ed5..e59e63c73 100644 --- a/app/src/main/res/layout/header_with_menu_item.xml +++ b/app/src/main/res/layout/subscription_groups_header.xml @@ -2,11 +2,7 @@ + android:layout_height="wrap_content"> + android:contentDescription="@string/list_view_mode" + android:padding="8dp" + tools:src="@drawable/ic_apps" /> + + \ 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 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 419b3ca43..9f7e50c19 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -125,7 +125,7 @@ 12dp - 2dp + 6dp 4dp 16sp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fd1add6c9..3fa37155a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -754,4 +754,5 @@ Unknown quality Show future items Hide future items + Sort \ No newline at end of file