From cf47cc104ead975cad582ebba6ffa88b5b27a5c4 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Thu, 28 Dec 2023 14:43:12 -0300 Subject: [PATCH 01/25] Initial changes to open the filter bottom sheet --- .../android/ui/reader/ReaderFragment.kt | 30 +- .../ui/reader/ReaderPostListFragment.java | 303 +++++++++--------- .../ui/reader/subfilter/SubFilterViewModel.kt | 8 +- 3 files changed, 176 insertions(+), 165 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt index 3d24e9d6bf28..f2f97455a9c7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt @@ -33,9 +33,11 @@ import org.wordpress.android.ui.reader.discover.interests.ReaderInterestsFragmen import org.wordpress.android.ui.reader.services.update.ReaderUpdateLogic.UpdateTask.FOLLOWED_BLOGS import org.wordpress.android.ui.reader.services.update.ReaderUpdateLogic.UpdateTask.TAGS import org.wordpress.android.ui.reader.services.update.ReaderUpdateServiceStarter +import org.wordpress.android.ui.reader.subfilter.SubfilterCategory import org.wordpress.android.ui.reader.viewmodels.ReaderViewModel import org.wordpress.android.ui.reader.viewmodels.ReaderViewModel.ReaderUiState.ContentUiState import org.wordpress.android.ui.reader.views.compose.ReaderTopAppBar +import org.wordpress.android.ui.reader.views.compose.filter.ReaderFilterType import org.wordpress.android.ui.utils.UiHelpers import org.wordpress.android.ui.utils.UiString.UiStringText import org.wordpress.android.util.JetpackBrandingUtils @@ -72,7 +74,7 @@ class ReaderFragment : Fragment(R.layout.reader_fragment_layout), MenuProvider, private var settingsMenuItemFocusPoint: QuickStartFocusPoint? = null private var binding: ReaderFragmentLayoutBinding? = null - + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { requireActivity().addMenuProvider(this, viewLifecycleOwner) binding = ReaderFragmentLayoutBinding.bind(view).apply { @@ -120,10 +122,12 @@ class ReaderFragment : Fragment(R.layout.reader_fragment_layout), MenuProvider, viewModel.onSearchActionClicked() true } + R.id.menu_settings -> { viewModel.onSettingsActionClicked() true } + else -> false } @@ -138,7 +142,14 @@ class ReaderFragment : Fragment(R.layout.reader_fragment_layout), MenuProvider, ReaderTopAppBar( topBarUiState = state, onMenuItemClick = viewModel::onTopBarMenuItemClick, - onFilterClick = viewModel::onTopBarFilterClick, + onFilterClick = { + tryOpenFilterList( + when (it) { + ReaderFilterType.BLOG -> SubfilterCategory.SITES + ReaderFilterType.TAG -> SubfilterCategory.TAGS + } + ) + }, onClearFilterClick = viewModel::onTopBarClearFilterClick, onSearchClick = {} ) @@ -149,10 +160,10 @@ class ReaderFragment : Fragment(R.layout.reader_fragment_layout), MenuProvider, private fun ReaderFragmentLayoutBinding.initViewModel(savedInstanceState: Bundle?) { viewModel = ViewModelProvider(this@ReaderFragment, viewModelFactory).get(ReaderViewModel::class.java) - startObserving(savedInstanceState) + startReaderViewModel(savedInstanceState) } - private fun ReaderFragmentLayoutBinding.startObserving(savedInstanceState: Bundle?) { + private fun ReaderFragmentLayoutBinding.startReaderViewModel(savedInstanceState: Bundle?) { viewModel.uiState.observe(viewLifecycleOwner) { uiState -> uiState?.let { updateUiState(it) } } @@ -347,4 +358,15 @@ class ReaderFragment : Fragment(R.layout.reader_fragment_layout), MenuProvider, viewModel.onQuickStartEventReceived(event) EventBus.getDefault().removeStickyEvent(event) } + + private fun getCurrentFeedFragment(): Fragment? { + return childFragmentManager.findFragmentById(R.id.container) + } + + private fun tryOpenFilterList(category: SubfilterCategory) { + val currentFeedFragment = getCurrentFeedFragment() + if (currentFeedFragment is ReaderPostListFragment) { + currentFeedFragment.openFilterList(category) + } + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java index fa810d139608..9f8a7fc41a4d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java @@ -62,6 +62,7 @@ import org.wordpress.android.fluxc.store.ReaderStore.OnReaderSitesSearched; import org.wordpress.android.fluxc.store.ReaderStore.ReaderSearchSitesPayload; import org.wordpress.android.models.FilterCriteria; +import org.wordpress.android.models.JetpackPoweredScreen; import org.wordpress.android.models.ReaderBlog; import org.wordpress.android.models.ReaderPost; import org.wordpress.android.models.ReaderPostDiscoverData; @@ -110,6 +111,7 @@ import org.wordpress.android.ui.reader.subfilter.ActionType.OpenSubsAtPage; import org.wordpress.android.ui.reader.subfilter.BottomSheetUiState.BottomSheetVisible; import org.wordpress.android.ui.reader.subfilter.SubFilterViewModel; +import org.wordpress.android.ui.reader.subfilter.SubfilterCategory; import org.wordpress.android.ui.reader.subfilter.SubfilterListItem.Site; import org.wordpress.android.ui.reader.subfilter.SubfilterListItem.SiteAll; import org.wordpress.android.ui.reader.tracker.ReaderTracker; @@ -126,7 +128,6 @@ import org.wordpress.android.util.DisplayUtils; import org.wordpress.android.util.DisplayUtilsWrapper; import org.wordpress.android.util.JetpackBrandingUtils; -import org.wordpress.android.models.JetpackPoweredScreen; import org.wordpress.android.util.NetworkUtils; import org.wordpress.android.util.QuickStartUtilsWrapper; import org.wordpress.android.util.SnackbarItem; @@ -165,34 +166,43 @@ public class ReaderPostListFragment extends ViewPagerFragment private static final int TAB_POSTS = 0; private static final int TAB_SITES = 1; private static final int NO_POSITION = -1; - + private static final String SUBFILTER_BOTTOM_SHEET_TAG = "SUBFILTER_BOTTOM_SHEET_TAG"; + private static boolean mHasPurgedReaderDb; + private final HistoryStack mTagPreviewHistory = new HistoryStack("tag_preview_history"); + @Inject ViewModelProvider.Factory mViewModelFactory; + @Inject AccountStore mAccountStore; + @Inject ReaderStore mReaderStore; + @Inject Dispatcher mDispatcher; + @Inject ImageManager mImageManager; + @Inject UiHelpers mUiHelpers; + @Inject TagUpdateClientUtilsProvider mTagUpdateClientUtilsProvider; + @Inject QuickStartUtilsWrapper mQuickStartUtilsWrapper; + @Inject SeenUnseenWithCounterFeatureConfig mSeenUnseenWithCounterFeatureConfig; + @Inject JetpackBrandingUtils mJetpackBrandingUtils; + @Inject QuickStartRepository mQuickStartRepository; + @Inject ReaderTracker mReaderTracker; + @Inject SnackbarSequencer mSnackbarSequencer; + @Inject DisplayUtilsWrapper mDisplayUtilsWrapper; + @Inject ReaderImprovementsFeatureConfig mReaderImprovementsFeatureConfig; private ReaderPostAdapter mPostAdapter; private ReaderSiteSearchAdapter mSiteSearchAdapter; private ReaderSearchSuggestionAdapter mSearchSuggestionAdapter; private ReaderSearchSuggestionRecyclerAdapter mSearchSuggestionRecyclerAdapter; - private FilteredRecyclerView mRecyclerView; private boolean mFirstLoad = true; - private View mNewPostsBar; private ActionableEmptyView mActionableEmptyView; private ProgressBar mProgress; private TabLayout mSearchTabs; - private SearchView mSearchView; private MenuItem mSearchMenuItem; - private View mSubFilterComponent; private View mSubFiltersListButton; private TextView mSubFilterTitle; private View mRemoveFilterButton; private View mJetpackBanner; - private boolean mIsTopLevel = false; - private static final String SUBFILTER_BOTTOM_SHEET_TAG = "SUBFILTER_BOTTOM_SHEET_TAG"; - private BottomNavController mBottomNavController; - private ReaderTag mCurrentTag; private ReaderTag mTagFragmentStartedWith = null; private long mCurrentBlogId; @@ -200,77 +210,71 @@ public class ReaderPostListFragment extends ViewPagerFragment private String mCurrentSearchQuery; private ReaderPostListType mPostListType; private ReaderSiteModel mLastTappedSiteSearchResult; - private int mRestorePosition; private int mSiteSearchRestorePosition; private int mPostSearchAdapterPos; private int mSiteSearchAdapterPos; private int mSearchTabsPos = NO_POSITION; - private boolean mIsUpdating; + /* + * called by post adapter to load older posts when user scrolls to the last post + */ + private final ReaderActions.DataRequestedListener mDataRequestedListener = + new ReaderActions.DataRequestedListener() { + @Override + public void onRequestData() { + // skip if update is already in progress + if (isUpdating()) { + return; + } + + // request older posts unless we already have the max # to show + switch (getPostListType()) { + case TAG_FOLLOWED: + // fall through to TAG_PREVIEW + case TAG_PREVIEW: + if (ReaderPostTable.getNumPostsWithTag(mCurrentTag) + < ReaderConstants.READER_MAX_POSTS_TO_DISPLAY) { + // request older posts + updatePostsWithTag(getCurrentTag(), UpdateAction.REQUEST_OLDER); + mReaderTracker.track(AnalyticsTracker.Stat.READER_INFINITE_SCROLL); + } + break; + + case BLOG_PREVIEW: + int numPosts; + if (mCurrentFeedId != 0) { + numPosts = ReaderPostTable.getNumPostsInFeed(mCurrentFeedId); + } else { + numPosts = ReaderPostTable.getNumPostsInBlog(mCurrentBlogId); + } + if (numPosts < ReaderConstants.READER_MAX_POSTS_TO_DISPLAY) { + updatePostsInCurrentBlogOrFeed(UpdateAction.REQUEST_OLDER); + mReaderTracker.track(AnalyticsTracker.Stat.READER_INFINITE_SCROLL); + } + break; + + case SEARCH_RESULTS: + ReaderTag searchTag = ReaderUtils.getTagForSearchQuery(mCurrentSearchQuery); + int offset = ReaderPostTable.getNumPostsWithTag(searchTag); + if (offset < ReaderConstants.READER_MAX_POSTS_TO_DISPLAY) { + updatePostsInCurrentSearch(offset); + mReaderTracker.track(AnalyticsTracker.Stat.READER_INFINITE_SCROLL); + } + break; + } + } + }; private boolean mWasPaused; private boolean mHasRequestedPosts; private boolean mHasUpdatedPosts; private boolean mIsAnimatingOutNewPostsBar; - - private static boolean mHasPurgedReaderDb; - - private final HistoryStack mTagPreviewHistory = new HistoryStack("tag_preview_history"); - private AlertDialog mBookmarksSavedLocallyDialog; - private ReaderPostListViewModel mViewModel; // This VM is initialized only on the Following tab private SubFilterViewModel mSubFilterViewModel; private ReaderViewModel mReaderViewModel = null; - @Inject ViewModelProvider.Factory mViewModelFactory; - @Inject AccountStore mAccountStore; - @Inject ReaderStore mReaderStore; - @Inject Dispatcher mDispatcher; - @Inject ImageManager mImageManager; - @Inject UiHelpers mUiHelpers; - @Inject TagUpdateClientUtilsProvider mTagUpdateClientUtilsProvider; - @Inject QuickStartUtilsWrapper mQuickStartUtilsWrapper; - @Inject SeenUnseenWithCounterFeatureConfig mSeenUnseenWithCounterFeatureConfig; - @Inject JetpackBrandingUtils mJetpackBrandingUtils; - @Inject QuickStartRepository mQuickStartRepository; - @Inject ReaderTracker mReaderTracker; - @Inject SnackbarSequencer mSnackbarSequencer; - @Inject DisplayUtilsWrapper mDisplayUtilsWrapper; - @Inject ReaderImprovementsFeatureConfig mReaderImprovementsFeatureConfig; - - private enum ActionableEmptyViewButtonType { - DISCOVER, - FOLLOWED - } - - private static class HistoryStack extends Stack { - private final String mKeyName; - - HistoryStack(@SuppressWarnings("SameParameterValue") String keyName) { - mKeyName = keyName; - } - - void restoreInstance(Bundle bundle) { - clear(); - if (bundle.containsKey(mKeyName)) { - ArrayList history = bundle.getStringArrayList(mKeyName); - if (history != null) { - this.addAll(history); - } - } - } - - void saveInstance(Bundle bundle) { - if (!isEmpty()) { - ArrayList history = new ArrayList<>(); - history.addAll(this); - bundle.putStringArrayList(mKeyName, history); - } - } - } - /* * show posts with a specific tag (either TAG_FOLLOWED or TAG_PREVIEW) */ @@ -679,14 +683,19 @@ private void initSubFilterViewModel(@Nullable Bundle savedInstanceState) { mSubFilterViewModel.start(mTagFragmentStartedWith, mCurrentTag, savedInstanceState); } - private void initSubFilterViews(ViewGroup rootView, LayoutInflater inflater) { + void openFilterList(@NonNull SubfilterCategory category) { + mSubFilterViewModel.onSubFiltersListButtonClicked(category); + } + + private void initSubFilterViews(@NonNull ViewGroup rootView, @NonNull LayoutInflater inflater) { mSubFilterComponent = inflater.inflate(R.layout.subfilter_component, rootView, false); ((ViewGroup) rootView.findViewById(R.id.sub_filter_component_container)).addView(mSubFilterComponent); + // TODO thomashorta remove mSubFiltersListButton and move isFilterableScreen logic to ReaderFragment/VM mSubFiltersListButton = mSubFilterComponent.findViewById(R.id.filter_selection); - mSubFiltersListButton.setOnClickListener(v -> { - mSubFilterViewModel.onSubFiltersListButtonClicked(); - }); +// mSubFiltersListButton.setOnClickListener(v -> { +// mSubFilterViewModel.onSubFiltersListButtonClicked(); +// }); mSubFilterTitle = mSubFilterComponent.findViewById(R.id.selected_filter_name); @@ -1584,7 +1593,6 @@ private void populateSearchSuggestionAdapter(String query) { mSearchSuggestionAdapter.setFilter(query); } - private void createSearchSuggestionRecyclerAdapter() { mSearchSuggestionRecyclerAdapter = new ReaderSearchSuggestionRecyclerAdapter(); mRecyclerView.setSearchSuggestionAdapter(mSearchSuggestionRecyclerAdapter); @@ -1909,7 +1917,22 @@ private void setCurrentTagFromEmptyViewButton(ActionableEmptyViewButtonType butt mViewModel.onEmptyStateButtonTapped(tag); } - /* + private void announceListStateForAccessibility() { + if (getView() != null) { + getView().announceForAccessibility(getString(R.string.reader_acessibility_list_loaded, + getPostAdapter().getItemCount())); + } + } + + private void showBookmarksSavedLocallyDialog(ShowBookmarkedSavedOnlyLocallyDialog holder) { + mBookmarksSavedLocallyDialog = new MaterialAlertDialogBuilder(requireActivity()) + .setTitle(getString(holder.getTitle())) + .setMessage(getString(holder.getMessage())) + .setPositiveButton(holder.getButtonLabel(), (dialog, which) -> holder.getOkButtonAction().invoke()) + .setCancelable(false) + .create(); + mBookmarksSavedLocallyDialog.show(); + } /* * called by post adapter when data has been loaded */ private final ReaderInterfaces.DataLoadedListener mDataLoadedListener = new ReaderInterfaces.DataLoadedListener() { @@ -1942,78 +1965,11 @@ public void onDataLoaded(boolean isEmpty) { } }; - private void announceListStateForAccessibility() { - if (getView() != null) { - getView().announceForAccessibility(getString(R.string.reader_acessibility_list_loaded, - getPostAdapter().getItemCount())); - } - } - - private void showBookmarksSavedLocallyDialog(ShowBookmarkedSavedOnlyLocallyDialog holder) { - mBookmarksSavedLocallyDialog = new MaterialAlertDialogBuilder(requireActivity()) - .setTitle(getString(holder.getTitle())) - .setMessage(getString(holder.getMessage())) - .setPositiveButton(holder.getButtonLabel(), (dialog, which) -> holder.getOkButtonAction().invoke()) - .setCancelable(false) - .create(); - mBookmarksSavedLocallyDialog.show(); - } - private boolean isBookmarksList() { return getPostListType() == ReaderPostListType.TAG_FOLLOWED && (mCurrentTag != null && mCurrentTag.isBookmarked()); } - /* - * called by post adapter to load older posts when user scrolls to the last post - */ - private final ReaderActions.DataRequestedListener mDataRequestedListener = - new ReaderActions.DataRequestedListener() { - @Override - public void onRequestData() { - // skip if update is already in progress - if (isUpdating()) { - return; - } - - // request older posts unless we already have the max # to show - switch (getPostListType()) { - case TAG_FOLLOWED: - // fall through to TAG_PREVIEW - case TAG_PREVIEW: - if (ReaderPostTable.getNumPostsWithTag(mCurrentTag) - < ReaderConstants.READER_MAX_POSTS_TO_DISPLAY) { - // request older posts - updatePostsWithTag(getCurrentTag(), UpdateAction.REQUEST_OLDER); - mReaderTracker.track(AnalyticsTracker.Stat.READER_INFINITE_SCROLL); - } - break; - - case BLOG_PREVIEW: - int numPosts; - if (mCurrentFeedId != 0) { - numPosts = ReaderPostTable.getNumPostsInFeed(mCurrentFeedId); - } else { - numPosts = ReaderPostTable.getNumPostsInBlog(mCurrentBlogId); - } - if (numPosts < ReaderConstants.READER_MAX_POSTS_TO_DISPLAY) { - updatePostsInCurrentBlogOrFeed(UpdateAction.REQUEST_OLDER); - mReaderTracker.track(AnalyticsTracker.Stat.READER_INFINITE_SCROLL); - } - break; - - case SEARCH_RESULTS: - ReaderTag searchTag = ReaderUtils.getTagForSearchQuery(mCurrentSearchQuery); - int offset = ReaderPostTable.getNumPostsWithTag(searchTag); - if (offset < ReaderConstants.READER_MAX_POSTS_TO_DISPLAY) { - updatePostsInCurrentSearch(offset); - mReaderTracker.track(AnalyticsTracker.Stat.READER_INFINITE_SCROLL); - } - break; - } - } - }; - private ReaderPostAdapter getPostAdapter() { if (mPostAdapter == null) { AppLog.d(T.READER, "reader post list > creating post adapter"); @@ -2091,14 +2047,6 @@ private ReaderTag getCurrentTag() { return mCurrentTag; } - private String getCurrentTagName() { - return (mCurrentTag != null ? mCurrentTag.getTagSlug() : ""); - } - - private boolean hasCurrentTag() { - return mCurrentTag != null; - } - private void setCurrentTag(final ReaderTag tag) { if (tag == null) { return; @@ -2169,6 +2117,14 @@ && getPostAdapter().isCurrentTag(tag)) { updateCurrentTagIfTime(); } + private String getCurrentTagName() { + return (mCurrentTag != null ? mCurrentTag.getTagSlug() : ""); + } + + private boolean hasCurrentTag() { + return mCurrentTag != null; + } + @Override public View getScrollableViewForUniqueIdProvision() { return mRecyclerView.getInternalRecyclerView(); @@ -2434,18 +2390,6 @@ private boolean isNewPostsBarShowing() { return (mNewPostsBar != null && mNewPostsBar.getVisibility() == View.VISIBLE); } - /* - * scroll listener assigned to the recycler when the "new posts" bar is shown to hide - * it upon scrolling - */ - private final RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); - hideNewPostsBar(); - } - }; - private void showNewPostsBar() { if (!isAdded() || isNewPostsBarShowing()) { return; @@ -2594,7 +2538,17 @@ public void onPostSelected(ReaderPost post) { ReaderActivityLauncher.showReaderPostDetail(getActivity(), post.blogId, post.postId); break; } - } + } /* + * scroll listener assigned to the recycler when the "new posts" bar is shown to hide + * it upon scrolling + */ + private final RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + hideNewPostsBar(); + } + }; /* * called when user selects a tag from the tag toolbar @@ -2849,4 +2803,35 @@ private boolean isFilterableScreen() { private boolean isFilterableTag(ReaderTag tag) { return tag != null && tag.isFilterable(); } + + private enum ActionableEmptyViewButtonType { + DISCOVER, + FOLLOWED + } + + private static class HistoryStack extends Stack { + private final String mKeyName; + + HistoryStack(@SuppressWarnings("SameParameterValue") String keyName) { + mKeyName = keyName; + } + + void restoreInstance(Bundle bundle) { + clear(); + if (bundle.containsKey(mKeyName)) { + ArrayList history = bundle.getStringArrayList(mKeyName); + if (history != null) { + this.addAll(history); + } + } + } + + void saveInstance(Bundle bundle) { + if (!isEmpty()) { + ArrayList history = new ArrayList<>(); + history.addAll(this); + bundle.putStringArrayList(mKeyName, history); + } + } + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt index f4464202f3ea..79fe4614175a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt @@ -213,7 +213,9 @@ class SubFilterViewModel @Inject constructor( ) } - fun onSubFiltersListButtonClicked() { + fun onSubFiltersListButtonClicked( + category: SubfilterCategory, + ) { _updateTagsAndSites.value = Event( EnumSet.of( UpdateTask.TAGS, @@ -224,7 +226,9 @@ class SubFilterViewModel @Inject constructor( mTagFragmentStartedWith?.let { UiStringText(it.label) } ?: UiStringRes(R.string.reader_filter_main_title), - if (mTagFragmentStartedWith?.organization == NO_ORGANIZATION) listOf(SITES, TAGS) else listOf(SITES) + listOf(category) // TODO thomashorta this should accept only a single category + // TODO thomashorta move this to ReaderViewModel when a filter chip is selected +// if (mTagFragmentStartedWith?.organization == NO_ORGANIZATION) listOf(SITES, TAGS) else listOf(SITES) )) } From e31adb289e831e4bfef0693d4ffdfc757bd8c3d3 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Thu, 28 Dec 2023 14:58:36 -0300 Subject: [PATCH 02/25] Don't reset topbar / selection state if list doesn't change --- .../ui/reader/viewmodels/ReaderViewModel.kt | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt index 8226dc7e4818..fb7b1d5631d0 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt @@ -125,9 +125,9 @@ class ReaderViewModel @Inject constructor( launch { val currentContentUiState = _uiState.value as? ContentUiState val tagList = loadReaderTabsUseCase.loadTabs() - if (tagList.isNotEmpty()) { + if (tagList.isNotEmpty() && readerTagsList != tagList) { updateReaderTagsList(tagList) - initializeTopBarUiState() + updateTopBarUiState() _uiState.value = ContentUiState( tabUiStates = tagList.map { TabUiState(label = UiStringText(it.label)) }, selectedReaderTag = selectedReaderTag(), @@ -369,14 +369,26 @@ class ReaderViewModel @Inject constructor( readerTagsList[readerTopBarMenuHelper.getReaderTagIndexFromMenuItem(it.selectedItem)] } - private suspend fun initializeTopBarUiState(/*tagList: ReaderTagList*/) { + private suspend fun updateTopBarUiState() { withContext(bgDispatcher) { val menuItems = readerTopBarMenuHelper.createMenu(readerTagsList) + + // if menu is exactly the same as before, don't update + if (_topBarUiState.value?.menuItems == menuItems) return@withContext + + // if there's already a selected item, use it, otherwise use the first item + val selectedItem = _topBarUiState.value?.selectedItem + ?: menuItems.first { it is MenuElementData.Item.Single } as MenuElementData.Item.Single + + // if there's a selected item and filter state, also use the filter state + val filterUiState = _topBarUiState.value?.filterUiState + ?.takeIf { _topBarUiState.value?.selectedItem != null } + _topBarUiState.postValue( TopBarUiState( menuItems = menuItems, - selectedItem = menuItems.first { it is MenuElementData.Item.Single } as MenuElementData.Item.Single, - filterUiState = null, + selectedItem = selectedItem, + filterUiState = filterUiState, ) ) } From 55ac1e0f2f270734482f17d8edf3062a45ee3a67 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Thu, 28 Dec 2023 15:33:43 -0300 Subject: [PATCH 03/25] Use the SubFilterViewModel directly from ReaderFragment --- .../android/ui/reader/ReaderFragment.kt | 33 ++++++++++++------- .../ui/reader/ReaderPostListFragment.java | 9 ++--- .../ui/reader/subfilter/SubFilterViewModel.kt | 5 +++ 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt index f2f97455a9c7..2ad066759365 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt @@ -20,6 +20,7 @@ import org.greenrobot.eventbus.ThreadMode.MAIN import org.wordpress.android.R import org.wordpress.android.databinding.ReaderFragmentLayoutBinding import org.wordpress.android.models.JetpackPoweredScreen +import org.wordpress.android.models.ReaderTag import org.wordpress.android.ui.ScrollableViewInitializedListener import org.wordpress.android.ui.compose.theme.AppTheme import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureFullScreenOverlayFragment @@ -33,6 +34,7 @@ import org.wordpress.android.ui.reader.discover.interests.ReaderInterestsFragmen import org.wordpress.android.ui.reader.services.update.ReaderUpdateLogic.UpdateTask.FOLLOWED_BLOGS import org.wordpress.android.ui.reader.services.update.ReaderUpdateLogic.UpdateTask.TAGS import org.wordpress.android.ui.reader.services.update.ReaderUpdateServiceStarter +import org.wordpress.android.ui.reader.subfilter.SubFilterViewModel import org.wordpress.android.ui.reader.subfilter.SubfilterCategory import org.wordpress.android.ui.reader.viewmodels.ReaderViewModel import org.wordpress.android.ui.reader.viewmodels.ReaderViewModel.ReaderUiState.ContentUiState @@ -142,14 +144,7 @@ class ReaderFragment : Fragment(R.layout.reader_fragment_layout), MenuProvider, ReaderTopAppBar( topBarUiState = state, onMenuItemClick = viewModel::onTopBarMenuItemClick, - onFilterClick = { - tryOpenFilterList( - when (it) { - ReaderFilterType.BLOG -> SubfilterCategory.SITES - ReaderFilterType.TAG -> SubfilterCategory.TAGS - } - ) - }, + onFilterClick = ::tryOpenFilterList, onClearFilterClick = viewModel::onTopBarClearFilterClick, onSearchClick = {} ) @@ -363,10 +358,24 @@ class ReaderFragment : Fragment(R.layout.reader_fragment_layout), MenuProvider, return childFragmentManager.findFragmentById(R.id.container) } - private fun tryOpenFilterList(category: SubfilterCategory) { - val currentFeedFragment = getCurrentFeedFragment() - if (currentFeedFragment is ReaderPostListFragment) { - currentFeedFragment.openFilterList(category) + // The view model is started by the ReaderPostListFragment for feeds that support filtering + private fun getSubFilterViewModel(): SubFilterViewModel? { + val currentFragment = getCurrentFeedFragment() ?: return null + val selectedTag = (viewModel.uiState.value as? ContentUiState)?.selectedReaderTag ?: return null + return ViewModelProvider(currentFragment, viewModelFactory).get( + SubFilterViewModel.getViewModelKeyForTag(selectedTag), + SubFilterViewModel::class.java + ) + } + + private fun tryOpenFilterList(type: ReaderFilterType) { + val viewModel = getSubFilterViewModel() ?: return + + val category = when (type) { + ReaderFilterType.BLOG -> SubfilterCategory.SITES + ReaderFilterType.TAG -> SubfilterCategory.TAGS } + + viewModel.onSubFiltersListButtonClicked(category) } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java index 9f8a7fc41a4d..99c91faf4a28 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java @@ -594,8 +594,9 @@ private void addWebViewCachingFragment(Long blogId, Long postId) { private void initSubFilterViewModel(@Nullable Bundle savedInstanceState) { WPMainActivityViewModel wpMainActivityViewModel = new ViewModelProvider(requireActivity(), mViewModelFactory) .get(WPMainActivityViewModel.class); + mSubFilterViewModel = new ViewModelProvider(this, mViewModelFactory).get( - SubFilterViewModel.SUBFILTER_VM_BASE_KEY + mTagFragmentStartedWith.getKeyString(), + SubFilterViewModel.getViewModelKeyForTag(mTagFragmentStartedWith), SubFilterViewModel.class ); @@ -640,7 +641,7 @@ private void initSubFilterViewModel(@Nullable Bundle savedInstanceState) { mSubFilterViewModel.loadSubFilters(); BottomSheetVisible visibleState = (BottomSheetVisible) uiState; bottomSheet = SubfilterBottomSheetFragment.newInstance( - SubFilterViewModel.SUBFILTER_VM_BASE_KEY + mTagFragmentStartedWith.getKeyString(), + SubFilterViewModel.getViewModelKeyForTag(mTagFragmentStartedWith), visibleState.getCategories(), mUiHelpers.getTextOfUiString(requireContext(), visibleState.getTitle()) ); @@ -683,10 +684,6 @@ private void initSubFilterViewModel(@Nullable Bundle savedInstanceState) { mSubFilterViewModel.start(mTagFragmentStartedWith, mCurrentTag, savedInstanceState); } - void openFilterList(@NonNull SubfilterCategory category) { - mSubFilterViewModel.onSubFiltersListButtonClicked(category); - } - private void initSubFilterViews(@NonNull ViewGroup rootView, @NonNull LayoutInflater inflater) { mSubFilterComponent = inflater.inflate(R.layout.subfilter_component, rootView, false); ((ViewGroup) rootView.findViewById(R.id.sub_filter_component_container)).addView(mSubFilterComponent); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt index 79fe4614175a..ab09d6ae4db2 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt @@ -388,5 +388,10 @@ class SubFilterViewModel @Inject constructor( const val ARG_IS_FIRST_LOAD = "is_first_load" const val TRACK_TAB = "tab" + + @JvmStatic + fun getViewModelKeyForTag(tag: ReaderTag): String { + return SUBFILTER_VM_BASE_KEY + tag.keyString + } } } From 23eb4269e6927cc27aea5dd84d36f3dd71955cc7 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Thu, 28 Dec 2023 15:57:06 -0300 Subject: [PATCH 04/25] Select and clear filters --- .../android/ui/reader/ReaderFragment.kt | 8 +++++-- .../ui/reader/ReaderPostListFragment.java | 4 +++- .../ui/reader/viewmodels/ReaderViewModel.kt | 23 ++++++++++--------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt index 2ad066759365..cda713faf34f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt @@ -20,7 +20,6 @@ import org.greenrobot.eventbus.ThreadMode.MAIN import org.wordpress.android.R import org.wordpress.android.databinding.ReaderFragmentLayoutBinding import org.wordpress.android.models.JetpackPoweredScreen -import org.wordpress.android.models.ReaderTag import org.wordpress.android.ui.ScrollableViewInitializedListener import org.wordpress.android.ui.compose.theme.AppTheme import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureFullScreenOverlayFragment @@ -145,7 +144,7 @@ class ReaderFragment : Fragment(R.layout.reader_fragment_layout), MenuProvider, topBarUiState = state, onMenuItemClick = viewModel::onTopBarMenuItemClick, onFilterClick = ::tryOpenFilterList, - onClearFilterClick = viewModel::onTopBarClearFilterClick, + onClearFilterClick = ::clearFilter, onSearchClick = {} ) } @@ -378,4 +377,9 @@ class ReaderFragment : Fragment(R.layout.reader_fragment_layout), MenuProvider, viewModel.onSubFiltersListButtonClicked(category) } + + private fun clearFilter() { + val viewModel = getSubFilterViewModel() ?: return + viewModel.setDefaultSubfilter() + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java index 99c91faf4a28..986c380e5543 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java @@ -111,7 +111,6 @@ import org.wordpress.android.ui.reader.subfilter.ActionType.OpenSubsAtPage; import org.wordpress.android.ui.reader.subfilter.BottomSheetUiState.BottomSheetVisible; import org.wordpress.android.ui.reader.subfilter.SubFilterViewModel; -import org.wordpress.android.ui.reader.subfilter.SubfilterCategory; import org.wordpress.android.ui.reader.subfilter.SubfilterListItem.Site; import org.wordpress.android.ui.reader.subfilter.SubfilterListItem.SiteAll; import org.wordpress.android.ui.reader.tracker.ReaderTracker; @@ -603,10 +602,13 @@ private void initSubFilterViewModel(@Nullable Bundle savedInstanceState) { mSubFilterViewModel.getCurrentSubFilter().observe(getViewLifecycleOwner(), subfilterListItem -> { if (getPostListType() != ReaderPostListType.SEARCH_RESULTS) { mSubFilterViewModel.onSubfilterSelected(subfilterListItem); + if (shouldShowEmptyViewForSelfHostedCta()) { setEmptyTitleDescriptionAndButton(false); showEmptyView(); } + + if (mReaderViewModel != null) mReaderViewModel.onSubFilterSelected(subfilterListItem); } }); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt index fb7b1d5631d0..16bb800fe81f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt @@ -28,6 +28,7 @@ import org.wordpress.android.ui.mysite.cards.quickstart.QuickStartRepository import org.wordpress.android.ui.prefs.AppPrefsWrapper import org.wordpress.android.ui.quickstart.QuickStartEvent import org.wordpress.android.ui.reader.ReaderEvents +import org.wordpress.android.ui.reader.subfilter.SubfilterListItem import org.wordpress.android.ui.reader.tracker.ReaderTab import org.wordpress.android.ui.reader.tracker.ReaderTracker import org.wordpress.android.ui.reader.tracker.ReaderTrackerType.MAIN_READER @@ -435,26 +436,26 @@ class ReaderViewModel @Inject constructor( } } - fun onTopBarFilterClick(type: ReaderFilterType) { - // TODO actual logic needs to be created (opening filter bottom sheet). - // The current logic is for initial implementation and UI review only. - val itemText = when (type) { - ReaderFilterType.BLOG -> UiStringText("Selected Blog") - ReaderFilterType.TAG -> UiStringText("Selected Site") + fun onSubFilterSelected(item: SubfilterListItem) { + when (item) { + is SubfilterListItem.SiteAll -> clearTopBarFilter() + is SubfilterListItem.Site -> updateTopBarFilter(item.blog.name, ReaderFilterType.BLOG) + is SubfilterListItem.Tag -> updateTopBarFilter(item.tag.tagDisplayName, ReaderFilterType.TAG) + else -> Unit // do nothing } + } + private fun clearTopBarFilter() { val filterUiState = _topBarUiState.value?.filterUiState - ?.copy(selectedItem = ReaderFilterSelectedItem(itemText, type)) + ?.copy(selectedItem = null) _topBarUiState.value = _topBarUiState.value ?.copy(filterUiState = filterUiState) } - fun onTopBarClearFilterClick() { - // TODO actual logic needs to be created (clearing filter). - // The current logic is for initial implementation and UI review only. + private fun updateTopBarFilter(itemName: String, type: ReaderFilterType) { val filterUiState = _topBarUiState.value?.filterUiState - ?.copy(selectedItem = null) + ?.copy(selectedItem = ReaderFilterSelectedItem(UiStringText(itemName), type)) _topBarUiState.value = _topBarUiState.value ?.copy(filterUiState = filterUiState) From 51b6989a76bac41462e9c858eef4987f956f1519 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Thu, 28 Dec 2023 16:15:09 -0300 Subject: [PATCH 05/25] Make clicking on current filter open the filter list --- .../android/ui/reader/views/compose/ReaderTopAppBar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/ReaderTopAppBar.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/ReaderTopAppBar.kt index 4ca9c648c80f..79bd953ed13e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/ReaderTopAppBar.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/ReaderTopAppBar.kt @@ -152,7 +152,7 @@ private fun Filter( followedBlogsCount = filterUiState.followedBlogsCount, followedTagsCount = filterUiState.followedTagsCount, onFilterClick = onFilterClick, - onSelectedItemClick = onClearFilterClick, + onSelectedItemClick = { filterUiState.selectedItem?.type?.let(onFilterClick) }, onSelectedItemDismissClick = onClearFilterClick, chipHeight = chipHeight, ) From 412c56f7b6832475e0182ee78fb3b2ce82651e28 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Thu, 28 Dec 2023 16:15:28 -0300 Subject: [PATCH 06/25] Add small delay on filter update for fluid animation --- .../ui/reader/viewmodels/ReaderViewModel.kt | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt index 16bb800fe81f..4a038d0e1c8e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt @@ -4,9 +4,11 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe @@ -449,16 +451,26 @@ class ReaderViewModel @Inject constructor( val filterUiState = _topBarUiState.value?.filterUiState ?.copy(selectedItem = null) - _topBarUiState.value = _topBarUiState.value - ?.copy(filterUiState = filterUiState) + viewModelScope.launch(mainDispatcher) { + delay(FILTER_UPDATE_DELAY) // small delay to achieve a fluid animation since other UI updates are happening + _topBarUiState.postValue( + _topBarUiState.value + ?.copy(filterUiState = filterUiState) + ) + } } private fun updateTopBarFilter(itemName: String, type: ReaderFilterType) { val filterUiState = _topBarUiState.value?.filterUiState ?.copy(selectedItem = ReaderFilterSelectedItem(UiStringText(itemName), type)) - _topBarUiState.value = _topBarUiState.value - ?.copy(filterUiState = filterUiState) + viewModelScope.launch(mainDispatcher) { + delay(FILTER_UPDATE_DELAY) // small delay to achieve a fluid animation since other UI updates are happening + _topBarUiState.postValue( + _topBarUiState.value + ?.copy(filterUiState = filterUiState) + ) + } } data class TopBarUiState( @@ -511,6 +523,7 @@ class ReaderViewModel @Inject constructor( companion object { private const val QUICK_START_DISCOVER_TAB_STEP_DELAY = 2000L private const val QUICK_START_PROMPT_DURATION = 5000 + private const val FILTER_UPDATE_DELAY = 50L } } From dc2ebbf1e6aaa15e65990527cbf39936f7c755b0 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Thu, 28 Dec 2023 16:45:47 -0300 Subject: [PATCH 07/25] Pass isFilterableScreen via argument to Feed --- .../android/ui/reader/ReaderConstants.java | 1 + .../android/ui/reader/ReaderFragment.kt | 1 + .../ui/reader/ReaderPostListFragment.java | 40 +++++++++++-------- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderConstants.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderConstants.java index 3df1d62b6aa8..b62e6c6934c1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderConstants.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderConstants.java @@ -56,6 +56,7 @@ public class ReaderConstants { static final String ARG_VIDEO_URL = "video_url"; static final String ARG_IS_TOP_LEVEL = "is_top_level"; static final String ARG_SUBS_TAB_POSITION = "subs_tab_position"; + static final String ARG_IS_FILTERABLE = "is_filterable"; static final String KEY_POST_SLUGS_RESOLUTION_UNDERWAY = "post_slugs_resolution_underway"; static final String KEY_ALREADY_UPDATED = "already_updated"; diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt index cda713faf34f..52518de6653e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt @@ -239,6 +239,7 @@ class ReaderFragment : Fragment(R.layout.reader_fragment_layout), MenuProvider, uiState.selectedReaderTag, ReaderTypes.ReaderPostListType.TAG_FOLLOWED, true, + uiState.selectedReaderTag.isFilterable ) } replace(R.id.container, fragment) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java index 986c380e5543..57b1b3db9cc0 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java @@ -215,6 +215,7 @@ public class ReaderPostListFragment extends ViewPagerFragment private int mSiteSearchAdapterPos; private int mSearchTabsPos = NO_POSITION; private boolean mIsUpdating; + private boolean mIsFilterableScreen; /* * called by post adapter to load older posts when user scrolls to the last post */ @@ -278,13 +279,14 @@ public void onRequestData() { * show posts with a specific tag (either TAG_FOLLOWED or TAG_PREVIEW) */ static ReaderPostListFragment newInstanceForTag(@NonNull ReaderTag tag, ReaderPostListType listType) { - return newInstanceForTag(tag, listType, false); + return newInstanceForTag(tag, listType, false, false); } static ReaderPostListFragment newInstanceForTag( @NonNull ReaderTag tag, ReaderPostListType listType, - boolean isTopLevel + boolean isTopLevel, + boolean isFilterable ) { AppLog.d(T.READER, "reader post list > newInstance (tag)"); @@ -295,6 +297,7 @@ static ReaderPostListFragment newInstanceForTag( args.putSerializable(ReaderConstants.ARG_TAG, tag); args.putSerializable(ReaderConstants.ARG_POST_LIST_TYPE, listType); args.putBoolean(ReaderConstants.ARG_IS_TOP_LEVEL, isTopLevel); + args.putBoolean(ReaderConstants.ARG_IS_FILTERABLE, isFilterable); ReaderPostListFragment fragment = new ReaderPostListFragment(); fragment.setArguments(args); @@ -372,6 +375,9 @@ public void setArguments(Bundle args) { if (args.containsKey(ReaderConstants.ARG_IS_TOP_LEVEL)) { mIsTopLevel = args.getBoolean(ReaderConstants.ARG_IS_TOP_LEVEL); } + if (args.containsKey(ReaderConstants.ARG_IS_FILTERABLE)) { + mIsFilterableScreen = args.getBoolean(ReaderConstants.ARG_IS_FILTERABLE); + } mCurrentBlogId = args.getLong(ReaderConstants.ARG_BLOG_ID); mCurrentFeedId = args.getLong(ReaderConstants.ARG_FEED_ID); @@ -412,6 +418,9 @@ public void onCreate(@Nullable Bundle savedInstanceState) { if (savedInstanceState.containsKey(ReaderConstants.ARG_IS_TOP_LEVEL)) { mIsTopLevel = savedInstanceState.getBoolean(ReaderConstants.ARG_IS_TOP_LEVEL); } + if (savedInstanceState.containsKey(ReaderConstants.ARG_IS_FILTERABLE)) { + mIsFilterableScreen = savedInstanceState.getBoolean(ReaderConstants.ARG_IS_FILTERABLE); + } if (savedInstanceState.containsKey(ReaderConstants.ARG_ORIGINAL_TAG)) { mTagFragmentStartedWith = @@ -439,7 +448,7 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) { .get(ReaderViewModel.class); } - if (isFilterableScreen()) { + if (mIsFilterableScreen) { initSubFilterViewModel(savedInstanceState); } @@ -704,7 +713,7 @@ private void initSubFilterViews(@NonNull ViewGroup rootView, @NonNull LayoutInfl mSubFilterViewModel.setDefaultSubfilter(); }); // TODO As part of Reader IA changes this view is going to be replaced -// mSubFilterComponent.setVisibility(isFilterableScreen() ? View.VISIBLE : View.GONE); +// mSubFilterComponent.setVisibility(mIsFilterableScreen ? View.VISIBLE : View.GONE); mSubFilterComponent.setVisibility(View.GONE); ElevationOverlayProvider elevationOverlayProvider = new ElevationOverlayProvider(mRecyclerView.getContext()); @@ -755,7 +764,7 @@ public void onPause() { } mWasPaused = true; - mViewModel.onFragmentPause(mIsTopLevel, isSearching(), isFilterableScreen()); + mViewModel.onFragmentPause(mIsTopLevel, isSearching(), mIsFilterableScreen); } @Override @@ -802,8 +811,8 @@ public void onResume() { showEmptyView(); } - mViewModel.onFragmentResume(mIsTopLevel, isSearching(), isFilterableScreen(), - isFilterableScreen() ? mSubFilterViewModel.getCurrentSubfilterValue() : null); + mViewModel.onFragmentResume(mIsTopLevel, isSearching(), mIsFilterableScreen, + mIsFilterableScreen ? mSubFilterViewModel.getCurrentSubfilterValue() : null); } /* @@ -831,7 +840,7 @@ private void resumeFollowedTag() { private Site getSiteIfBlogPreview() { Site currentSite = null; - if (isFilterableScreen() && getPostListType() == ReaderPostListType.BLOG_PREVIEW) { + if (mIsFilterableScreen && getPostListType() == ReaderPostListType.BLOG_PREVIEW) { currentSite = mSubFilterViewModel.getCurrentSubfilterValue() instanceof Site ? (Site) (mSubFilterViewModel .getCurrentSubfilterValue()) : null; } @@ -853,7 +862,7 @@ private void resumeFollowedSite(Site currentSite) { if (isSiteStillAvailable) { refreshPosts(); } else { - if (isFilterableScreen()) { + if (mIsFilterableScreen) { mSubFilterViewModel.setDefaultSubfilter(); } } @@ -988,6 +997,7 @@ public void onSaveInstanceState(@NonNull Bundle outState) { } outState.putSerializable(ReaderConstants.ARG_POST_LIST_TYPE, getPostListType()); outState.putBoolean(ReaderConstants.ARG_IS_TOP_LEVEL, mIsTopLevel); + outState.putBoolean(ReaderConstants.ARG_IS_FILTERABLE, mIsFilterableScreen); if (isSearchTabsShowing()) { int tabPosition = getSearchTabsPosition(); @@ -996,7 +1006,7 @@ public void onSaveInstanceState(@NonNull Bundle outState) { outState.putInt(ReaderConstants.KEY_SITE_SEARCH_RESTORE_POSITION, siteSearchPosition); } - if (isFilterableTag(mTagFragmentStartedWith) && mSubFilterViewModel != null) { + if (mIsFilterableScreen && mSubFilterViewModel != null) { mSubFilterViewModel.onSaveInstanceState(outState); } @@ -1210,7 +1220,7 @@ public void onShowCustomEmptyView(EmptyViewMessageType emptyViewMsgType) { } - if (isFilterableScreen()) { + if (mIsFilterableScreen) { initSubFilterViews(rootView, inflater); } @@ -1818,7 +1828,7 @@ private void setEmptyTitleAndDescriptionForBookmarksList() { } private boolean shouldShowEmptyViewForSelfHostedCta() { - return isFilterableScreen() && !mAccountStore.hasAccessToken() && mSubFilterViewModel + return mIsFilterableScreen && !mAccountStore.hasAccessToken() && mSubFilterViewModel .getCurrentSubfilterValue() instanceof SiteAll; } @@ -2060,7 +2070,7 @@ && getPostAdapter().isCurrentTag(tag)) { mCurrentTag = tag; - if (isFilterableScreen()) { + if (mIsFilterableScreen) { if (isFilterableTag(mCurrentTag) || mCurrentTag.isDefaultInMemoryTag()) { mSubFilterViewModel.onSubfilterReselected(); } else { @@ -2795,10 +2805,6 @@ private boolean isFollowingScreen() { return mTagFragmentStartedWith != null && mTagFragmentStartedWith.isFollowedSites(); } - private boolean isFilterableScreen() { - return isFilterableTag(mTagFragmentStartedWith); - } - private boolean isFilterableTag(ReaderTag tag) { return tag != null && tag.isFilterable(); } From a327835d97470dd4301afcaa86c6622df0da2c42 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Thu, 28 Dec 2023 16:46:10 -0300 Subject: [PATCH 08/25] Show filter chip group based on isFilterable --- .../android/ui/reader/viewmodels/ReaderViewModel.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt index 4a038d0e1c8e..6def9a715f4d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt @@ -422,19 +422,18 @@ class ReaderViewModel @Inject constructor( } fun onTopBarMenuItemClick(item: MenuElementData.Item.Single) { - // TODO actual logic needs to be created + val selectedReaderTag = readerTagsList[readerTopBarMenuHelper.getReaderTagIndexFromMenuItem(item)] + + // TODO thomashorta actual logic needs to be created // The current logic is for initial implementation and UI review only. val filterUiState = TopBarUiState.FilterUiState( followedBlogsCount = 23, followedTagsCount = 41, - ).takeIf { item.id == "0" } + ).takeIf { selectedReaderTag.isFilterable } // Avoid reloading a content stream that is already loaded if (item.id != _topBarUiState.value?.selectedItem?.id) { - readerTagsList[readerTopBarMenuHelper.getReaderTagIndexFromMenuItem(item)] - ?.let { selectedReaderTag -> - updateSelectedContent(selectedReaderTag, filterUiState) - } + selectedReaderTag?.let { updateSelectedContent(it, filterUiState) } } } From cbd839bae225e89d8e18238869d7ea236fb3978f Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Thu, 28 Dec 2023 16:48:35 -0300 Subject: [PATCH 09/25] Rename ReaderViewModel function --- .../org/wordpress/android/ui/reader/ReaderPostListFragment.java | 2 +- .../wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java index 57b1b3db9cc0..3b2abbba5fe0 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java @@ -617,7 +617,7 @@ private void initSubFilterViewModel(@Nullable Bundle savedInstanceState) { showEmptyView(); } - if (mReaderViewModel != null) mReaderViewModel.onSubFilterSelected(subfilterListItem); + if (mReaderViewModel != null) mReaderViewModel.onSubFilterItemSelected(subfilterListItem); } }); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt index 6def9a715f4d..d704751cb336 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt @@ -437,7 +437,7 @@ class ReaderViewModel @Inject constructor( } } - fun onSubFilterSelected(item: SubfilterListItem) { + fun onSubFilterItemSelected(item: SubfilterListItem) { when (item) { is SubfilterListItem.SiteAll -> clearTopBarFilter() is SubfilterListItem.Site -> updateTopBarFilter(item.blog.name, ReaderFilterType.BLOG) From 84fc5354f6c2563afea8c18f95ddd322a27ef9eb Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Thu, 28 Dec 2023 17:01:37 -0300 Subject: [PATCH 10/25] Add the ability to show only Blogs or Tags filter --- .../ui/reader/subfilter/SubFilterViewModel.kt | 5 ----- .../ui/reader/viewmodels/ReaderViewModel.kt | 21 +++++++++++++++---- .../reader/views/compose/ReaderTopAppBar.kt | 6 ++++-- .../compose/filter/ReaderFilterChipGroup.kt | 18 +++++++++------- 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt index ab09d6ae4db2..d457716c7c3f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt @@ -17,15 +17,12 @@ import org.wordpress.android.fluxc.store.AccountStore import org.wordpress.android.models.ReaderTag import org.wordpress.android.modules.BG_THREAD import org.wordpress.android.modules.UI_THREAD -import org.wordpress.android.ui.Organization.NO_ORGANIZATION import org.wordpress.android.ui.prefs.AppPrefsWrapper import org.wordpress.android.ui.reader.ReaderEvents import org.wordpress.android.ui.reader.ReaderTypes.ReaderPostListType import org.wordpress.android.ui.reader.services.update.ReaderUpdateLogic.UpdateTask import org.wordpress.android.ui.reader.subfilter.BottomSheetUiState.BottomSheetHidden import org.wordpress.android.ui.reader.subfilter.BottomSheetUiState.BottomSheetVisible -import org.wordpress.android.ui.reader.subfilter.SubfilterCategory.SITES -import org.wordpress.android.ui.reader.subfilter.SubfilterCategory.TAGS import org.wordpress.android.ui.reader.subfilter.SubfilterListItem.Site import org.wordpress.android.ui.reader.subfilter.SubfilterListItem.SiteAll import org.wordpress.android.ui.reader.subfilter.SubfilterListItem.Tag @@ -227,8 +224,6 @@ class SubFilterViewModel @Inject constructor( UiStringText(it.label) } ?: UiStringRes(R.string.reader_filter_main_title), listOf(category) // TODO thomashorta this should accept only a single category - // TODO thomashorta move this to ReaderViewModel when a filter chip is selected -// if (mTagFragmentStartedWith?.organization == NO_ORGANIZATION) listOf(SITES, TAGS) else listOf(SITES) )) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt index d704751cb336..828350c6574f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt @@ -22,6 +22,7 @@ import org.wordpress.android.models.ReaderTag import org.wordpress.android.models.ReaderTagList import org.wordpress.android.modules.BG_THREAD import org.wordpress.android.modules.UI_THREAD +import org.wordpress.android.ui.Organization import org.wordpress.android.ui.compose.components.menu.dropdown.MenuElementData import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalOverlayUtil import org.wordpress.android.ui.jetpackoverlay.JetpackOverlayConnectedFeature.READER @@ -427,8 +428,10 @@ class ReaderViewModel @Inject constructor( // TODO thomashorta actual logic needs to be created // The current logic is for initial implementation and UI review only. val filterUiState = TopBarUiState.FilterUiState( - followedBlogsCount = 23, - followedTagsCount = 41, + blogsFilterCount = 23, + tagsFilterCount = 41, + showBlogsFilter = shouldShowBlogsFilter(selectedReaderTag), + showTagsFilter = shouldShowTagsFilter(selectedReaderTag), ).takeIf { selectedReaderTag.isFilterable } // Avoid reloading a content stream that is already loaded @@ -472,15 +475,25 @@ class ReaderViewModel @Inject constructor( } } + private fun shouldShowBlogsFilter(readerTag: ReaderTag): Boolean { + return readerTag.isFilterable + } + + private fun shouldShowTagsFilter(readerTag: ReaderTag): Boolean { + return readerTag.isFilterable && readerTag.organization == Organization.NO_ORGANIZATION + } + data class TopBarUiState( val menuItems: List, val selectedItem: MenuElementData.Item.Single, val filterUiState: FilterUiState? = null, ) { data class FilterUiState( - val followedBlogsCount: Int, - val followedTagsCount: Int, + val blogsFilterCount: Int, + val tagsFilterCount: Int, val selectedItem: ReaderFilterSelectedItem? = null, + val showBlogsFilter: Boolean = blogsFilterCount > 0, + val showTagsFilter: Boolean = tagsFilterCount > 0, ) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/ReaderTopAppBar.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/ReaderTopAppBar.kt index 79bd953ed13e..a1582e7d3681 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/ReaderTopAppBar.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/ReaderTopAppBar.kt @@ -149,8 +149,10 @@ private fun Filter( ReaderFilterChipGroup( modifier = modifier, selectedItem = filterUiState.selectedItem, - followedBlogsCount = filterUiState.followedBlogsCount, - followedTagsCount = filterUiState.followedTagsCount, + blogsFilterCount = filterUiState.blogsFilterCount, + tagsFilterCount = filterUiState.tagsFilterCount, + showBlogsFilter = filterUiState.showBlogsFilter, + showTagsFilter = filterUiState.showTagsFilter, onFilterClick = onFilterClick, onSelectedItemClick = { filterUiState.selectedItem?.type?.let(onFilterClick) }, onSelectedItemDismissClick = onClearFilterClick, diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/filter/ReaderFilterChipGroup.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/filter/ReaderFilterChipGroup.kt index 8e279413086d..75f308b1b69a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/filter/ReaderFilterChipGroup.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/filter/ReaderFilterChipGroup.kt @@ -48,13 +48,15 @@ private val roundedShape = RoundedCornerShape(100) @Composable fun ReaderFilterChipGroup( - followedBlogsCount: Int, - followedTagsCount: Int, + blogsFilterCount: Int, + tagsFilterCount: Int, onFilterClick: (ReaderFilterType) -> Unit, onSelectedItemClick: () -> Unit, onSelectedItemDismissClick: () -> Unit, modifier: Modifier = Modifier, selectedItem: ReaderFilterSelectedItem? = null, + showBlogsFilter: Boolean = blogsFilterCount > 0, + showTagsFilter: Boolean = tagsFilterCount > 0, chipHeight: Dp = 36.dp, ) { Row( @@ -63,8 +65,8 @@ fun ReaderFilterChipGroup( ) { val blogSelected = selectedItem?.type == ReaderFilterType.BLOG val tagSelected = selectedItem?.type == ReaderFilterType.TAG - val blogChipVisible = selectedItem == null || blogSelected - val tagChipVisible = selectedItem == null || tagSelected + val blogChipVisible = showBlogsFilter && (selectedItem == null || blogSelected) + val tagChipVisible = showTagsFilter && (selectedItem == null || tagSelected) val blogChipText: UiString = remember(selectedItem) { if (blogSelected) { @@ -74,7 +76,7 @@ fun ReaderFilterChipGroup( zeroRes = R.string.reader_filter_chip_blog_zero, oneRes = R.string.reader_filter_chip_blog_one, otherRes = R.string.reader_filter_chip_blog_other, - count = followedBlogsCount, + count = blogsFilterCount, ) } } @@ -87,7 +89,7 @@ fun ReaderFilterChipGroup( zeroRes = R.string.reader_filter_chip_tag_zero, oneRes = R.string.reader_filter_chip_tag_one, otherRes = R.string.reader_filter_chip_tag_other, - count = followedTagsCount, + count = tagsFilterCount, ) } } @@ -223,8 +225,8 @@ fun ReaderFilterChipGroupPreview() { ReaderFilterChipGroup( modifier = Modifier.padding(Margin.Medium.value), selectedItem = selectedItem, - followedBlogsCount = 23, - followedTagsCount = 41, + blogsFilterCount = 23, + tagsFilterCount = 41, onFilterClick = { type -> selectedItem = ReaderFilterSelectedItem( text = UiString.UiStringText("Amazing ${type.name.lowercase()}"), From 4d251dff323dc23614200de2c488155faba7a426 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Thu, 28 Dec 2023 18:02:33 -0300 Subject: [PATCH 11/25] Track when filter is cleared --- .../wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt index d457716c7c3f..674f019765ec 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt @@ -202,6 +202,7 @@ class SubFilterViewModel @Inject constructor( } fun setDefaultSubfilter() { + readerTracker.track(Stat.READER_FILTER_SHEET_CLEARED) updateSubfilter( SiteAll( onClickAction = ::onSubfilterClicked, From dbba180bfbf86bc499d2203489e1b34829743bf8 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Thu, 28 Dec 2023 18:03:21 -0300 Subject: [PATCH 12/25] Show the Filter Group with correct count for filterable tabs --- .../ui/reader/ReaderPostListFragment.java | 9 +++ .../ui/reader/subfilter/SubFilterViewModel.kt | 16 +++-- .../ui/reader/viewmodels/ReaderViewModel.kt | 62 ++++++++++++++----- 3 files changed, 65 insertions(+), 22 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java index 3b2abbba5fe0..44e9e60775de 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java @@ -692,6 +692,15 @@ private void initSubFilterViewModel(@Nullable Bundle savedInstanceState) { }); }); + if (mIsFilterableScreen) { + mSubFilterViewModel.getSubFilters().observe(getViewLifecycleOwner(), subFilters -> { + mReaderViewModel.showTopBarFilterGroup(mTagFragmentStartedWith, subFilters); + }); + mSubFilterViewModel.updateTagsAndSites(); + } else { + mReaderViewModel.hideTopBarFilterGroup(mTagFragmentStartedWith); + } + mSubFilterViewModel.start(mTagFragmentStartedWith, mCurrentTag, savedInstanceState); } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt index 674f019765ec..28c124cec93d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt @@ -214,12 +214,7 @@ class SubFilterViewModel @Inject constructor( fun onSubFiltersListButtonClicked( category: SubfilterCategory, ) { - _updateTagsAndSites.value = Event( - EnumSet.of( - UpdateTask.TAGS, - UpdateTask.FOLLOWED_BLOGS - ) - ) + updateTagsAndSites() _bottomSheetUiState.value = Event(BottomSheetVisible( mTagFragmentStartedWith?.let { UiStringText(it.label) @@ -228,6 +223,15 @@ class SubFilterViewModel @Inject constructor( )) } + fun updateTagsAndSites() { + _updateTagsAndSites.value = Event( + EnumSet.of( + UpdateTask.TAGS, + UpdateTask.FOLLOWED_BLOGS + ) + ) + } + fun onBottomSheetCancelled() { readerTracker.track(Stat.READER_FILTER_SHEET_DISMISSED) _bottomSheetUiState.value = Event(BottomSheetHidden) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt index 828350c6574f..44af12cd9da8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModel.kt @@ -193,17 +193,13 @@ class ReaderViewModel @Inject constructor( return now - lastUpdated > UPDATE_TAGS_THRESHOLD } - fun updateSelectedContent( - selectedTag: ReaderTag, - // TODO replace with real logic - filterUiState: TopBarUiState.FilterUiState? = null - ) { + fun updateSelectedContent(selectedTag: ReaderTag) { getMenuItemFromReaderTag(selectedTag)?.let { newSelectedMenuItem -> // Update top bar UI state so menu is updated with new selected item _topBarUiState.value?.let { _topBarUiState.value = it.copy( selectedItem = newSelectedMenuItem, - filterUiState = filterUiState, + filterUiState = null, ) } // Updated post list content @@ -425,18 +421,9 @@ class ReaderViewModel @Inject constructor( fun onTopBarMenuItemClick(item: MenuElementData.Item.Single) { val selectedReaderTag = readerTagsList[readerTopBarMenuHelper.getReaderTagIndexFromMenuItem(item)] - // TODO thomashorta actual logic needs to be created - // The current logic is for initial implementation and UI review only. - val filterUiState = TopBarUiState.FilterUiState( - blogsFilterCount = 23, - tagsFilterCount = 41, - showBlogsFilter = shouldShowBlogsFilter(selectedReaderTag), - showTagsFilter = shouldShowTagsFilter(selectedReaderTag), - ).takeIf { selectedReaderTag.isFilterable } - // Avoid reloading a content stream that is already loaded if (item.id != _topBarUiState.value?.selectedItem?.id) { - selectedReaderTag?.let { updateSelectedContent(it, filterUiState) } + selectedReaderTag?.let { updateSelectedContent(it) } } } @@ -475,6 +462,49 @@ class ReaderViewModel @Inject constructor( } } + fun hideTopBarFilterGroup(readerTab: ReaderTag) { + val selectedReaderTag = _topBarUiState.value?.selectedItem?.let { + readerTagsList[readerTopBarMenuHelper.getReaderTagIndexFromMenuItem(it)] + } ?: return + + if (readerTab != selectedReaderTag) return + + _topBarUiState.postValue( + topBarUiState.value?.copy(filterUiState = null) + ) + } + + fun showTopBarFilterGroup(readerTab: ReaderTag, subFilterItems: List) { + val selectedReaderTag = _topBarUiState.value?.selectedItem?.let { + readerTagsList[readerTopBarMenuHelper.getReaderTagIndexFromMenuItem(it)] + } ?: return + + if (readerTab != selectedReaderTag) return + + val blogsFilterCount = subFilterItems.filterIsInstance().size + val tagsFilterCount = subFilterItems.filterIsInstance().size + + // TODO thomashorta if filter is currently selected, check if item still exists and keep it, otherwise clear + + val filterState = _topBarUiState.value?.filterUiState + ?.copy( + blogsFilterCount = blogsFilterCount, + tagsFilterCount = tagsFilterCount, + showBlogsFilter = shouldShowBlogsFilter(selectedReaderTag), + showTagsFilter = shouldShowTagsFilter(selectedReaderTag), + ) + ?: TopBarUiState.FilterUiState( + blogsFilterCount = blogsFilterCount, + tagsFilterCount = tagsFilterCount, + showBlogsFilter = shouldShowBlogsFilter(selectedReaderTag), + showTagsFilter = shouldShowTagsFilter(selectedReaderTag), + ) + + _topBarUiState.postValue( + topBarUiState.value?.copy(filterUiState = filterState) + ) + } + private fun shouldShowBlogsFilter(readerTag: ReaderTag): Boolean { return readerTag.isFilterable } From 1f03e789ffc520c892a1dbd5e072cb98c14bfeae Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Thu, 28 Dec 2023 18:03:44 -0300 Subject: [PATCH 13/25] Fix filter count not updating when it was the only state change --- .../ui/reader/views/compose/filter/ReaderFilterChipGroup.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/filter/ReaderFilterChipGroup.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/filter/ReaderFilterChipGroup.kt index 75f308b1b69a..96077b39aa76 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/filter/ReaderFilterChipGroup.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/filter/ReaderFilterChipGroup.kt @@ -68,7 +68,7 @@ fun ReaderFilterChipGroup( val blogChipVisible = showBlogsFilter && (selectedItem == null || blogSelected) val tagChipVisible = showTagsFilter && (selectedItem == null || tagSelected) - val blogChipText: UiString = remember(selectedItem) { + val blogChipText: UiString = remember(selectedItem, blogsFilterCount) { if (blogSelected) { selectedItem?.text ?: UiString.UiStringText("") } else { @@ -81,7 +81,7 @@ fun ReaderFilterChipGroup( } } - val tagChipText: UiString = remember(selectedItem) { + val tagChipText: UiString = remember(selectedItem, tagsFilterCount) { if (tagSelected) { selectedItem?.text ?: UiString.UiStringText("") } else { From 4a15d157ef36a7ab58acb5dd105764c70fa8794d Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Thu, 28 Dec 2023 18:18:08 -0300 Subject: [PATCH 14/25] Remove old subfilter component --- .../ui/reader/ReaderPostListFragment.java | 56 +-------------- .../res/layout/reader_fragment_post_cards.xml | 9 --- .../main/res/layout/subfilter_component.xml | 71 ------------------- 3 files changed, 3 insertions(+), 133 deletions(-) delete mode 100644 WordPress/src/main/res/layout/subfilter_component.xml diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java index 44e9e60775de..6012786ca656 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java @@ -31,7 +31,6 @@ import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.elevation.ElevationOverlayProvider; import com.google.android.material.snackbar.Snackbar; import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayout.OnTabSelectedListener; @@ -195,10 +194,6 @@ public class ReaderPostListFragment extends ViewPagerFragment private TabLayout mSearchTabs; private SearchView mSearchView; private MenuItem mSearchMenuItem; - private View mSubFilterComponent; - private View mSubFiltersListButton; - private TextView mSubFilterTitle; - private View mRemoveFilterButton; private View mJetpackBanner; private boolean mIsTopLevel = false; private BottomNavController mBottomNavController; @@ -216,6 +211,7 @@ public class ReaderPostListFragment extends ViewPagerFragment private int mSearchTabsPos = NO_POSITION; private boolean mIsUpdating; private boolean mIsFilterableScreen; + private boolean mIsFiltered = false; /* * called by post adapter to load older posts when user scrolls to the last post */ @@ -624,21 +620,6 @@ private void initSubFilterViewModel(@Nullable Bundle savedInstanceState) { mSubFilterViewModel.getReaderModeInfo().observe(getViewLifecycleOwner(), readerModeInfo -> { if (readerModeInfo != null) { changeReaderMode(readerModeInfo, true); - - if (readerModeInfo.getLabel() != null) { - mSubFilterTitle.setText( - mUiHelpers.getTextOfUiString( - requireActivity(), - readerModeInfo.getLabel() - ) - ); - } - - if (readerModeInfo.isFiltered()) { - mRemoveFilterButton.setVisibility(View.VISIBLE); - } else { - mRemoveFilterButton.setVisibility(View.GONE); - } } }); @@ -704,33 +685,6 @@ private void initSubFilterViewModel(@Nullable Bundle savedInstanceState) { mSubFilterViewModel.start(mTagFragmentStartedWith, mCurrentTag, savedInstanceState); } - private void initSubFilterViews(@NonNull ViewGroup rootView, @NonNull LayoutInflater inflater) { - mSubFilterComponent = inflater.inflate(R.layout.subfilter_component, rootView, false); - ((ViewGroup) rootView.findViewById(R.id.sub_filter_component_container)).addView(mSubFilterComponent); - - // TODO thomashorta remove mSubFiltersListButton and move isFilterableScreen logic to ReaderFragment/VM - mSubFiltersListButton = mSubFilterComponent.findViewById(R.id.filter_selection); -// mSubFiltersListButton.setOnClickListener(v -> { -// mSubFilterViewModel.onSubFiltersListButtonClicked(); -// }); - - mSubFilterTitle = mSubFilterComponent.findViewById(R.id.selected_filter_name); - - mRemoveFilterButton = mSubFilterComponent.findViewById(R.id.remove_filter_button); - mRemoveFilterButton.setOnClickListener(v -> { - mReaderTracker.track(Stat.READER_FILTER_SHEET_CLEARED); - mSubFilterViewModel.setDefaultSubfilter(); - }); - // TODO As part of Reader IA changes this view is going to be replaced -// mSubFilterComponent.setVisibility(mIsFilterableScreen ? View.VISIBLE : View.GONE); - mSubFilterComponent.setVisibility(View.GONE); - - ElevationOverlayProvider elevationOverlayProvider = new ElevationOverlayProvider(mRecyclerView.getContext()); - float cardElevation = getResources().getDimension(R.dimen.card_elevation); - int elevatedCardColor = elevationOverlayProvider.compositeOverlayWithThemeSurfaceColorIfNeeded(cardElevation); - mSubFilterComponent.setBackgroundColor(elevatedCardColor); - } - private void changeReaderMode(ReaderModeInfo readerModeInfo, boolean onlyOnChanges) { boolean changesDetected = false; @@ -757,6 +711,7 @@ private void changeReaderMode(ReaderModeInfo readerModeInfo, boolean onlyOnChang mPostListType = readerModeInfo.getListType(); mCurrentBlogId = readerModeInfo.getBlogId(); mCurrentFeedId = readerModeInfo.getFeedId(); + mIsFiltered = readerModeInfo.isFiltered(); resetPostAdapter(mPostListType); if (readerModeInfo.getRequestNewerPosts()) { @@ -1228,11 +1183,6 @@ public void onShowCustomEmptyView(EmptyViewMessageType emptyViewMsgType) { mRecyclerView.setRefreshing(true); } - - if (mIsFilterableScreen) { - initSubFilterViews(rootView, inflater); - } - return rootView; } @@ -2091,7 +2041,7 @@ && getPostAdapter().isCurrentTag(tag)) { false, null, false, - mRemoveFilterButton.getVisibility() == View.VISIBLE), + mIsFiltered), false ); } diff --git a/WordPress/src/main/res/layout/reader_fragment_post_cards.xml b/WordPress/src/main/res/layout/reader_fragment_post_cards.xml index 89f3f7860663..3e17c47c1f79 100644 --- a/WordPress/src/main/res/layout/reader_fragment_post_cards.xml +++ b/WordPress/src/main/res/layout/reader_fragment_post_cards.xml @@ -6,19 +6,11 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - - - - - - - - - - - - From 798d5d8fe8c81e36bb86361a24b4458c765ba905 Mon Sep 17 00:00:00 2001 From: Thomas Horta Date: Thu, 28 Dec 2023 18:48:09 -0300 Subject: [PATCH 15/25] Update bottomsheet UI title and hide pager tabs --- .../ui/reader/SubfilterBottomSheetFragment.kt | 12 +------- .../ui/reader/subfilter/SubFilterViewModel.kt | 30 ++++++------------- .../reader/subfilter/SubfilterPageFragment.kt | 1 - .../res/layout/subfilter_bottom_sheet.xml | 26 +++------------- WordPress/src/main/res/values/strings.xml | 5 ++-- WordPress/src/main/res/values/styles.xml | 6 ++-- 6 files changed, 20 insertions(+), 60 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/SubfilterBottomSheetFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/SubfilterBottomSheetFragment.kt index 7f4ae2e3cdc8..20a5ae90c556 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/SubfilterBottomSheetFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/SubfilterBottomSheetFragment.kt @@ -77,8 +77,8 @@ class SubfilterBottomSheetFragment : BottomSheetDialogFragment() { viewModelFactory )[subfilterVmKey, SubFilterViewModel::class.java] + // TODO thomashortadev: remove the pager and support only one category val pager = view.findViewById(R.id.view_pager) - val tabLayout = view.findViewById(R.id.tab_layout) val title = view.findViewById(R.id.title) title.text = bottomSheetTitle pager.adapter = SubfilterPagerAdapter( @@ -87,7 +87,6 @@ class SubfilterBottomSheetFragment : BottomSheetDialogFragment() { subfilterVmKey, categories.toList() ) - tabLayout.setupWithViewPager(pager) pager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { // NO OP @@ -108,15 +107,6 @@ class SubfilterBottomSheetFragment : BottomSheetDialogFragment() { else -> SITES.ordinal } - viewModel.filtersMatchCount.observe(this, Observer { - for (category in it.keys) { - val tab = tabLayout.getTabAt(category.ordinal) - tab?.let { sectionTab -> - sectionTab.text = "${view.context.getString(category.titleRes)} (${it[category]})" - } - } - }) - dialog?.setOnShowListener { dialogInterface -> val sheetDialog = dialogInterface as? BottomSheetDialog diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt index 28c124cec93d..e51c08b59021 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubFilterViewModel.kt @@ -1,6 +1,5 @@ package org.wordpress.android.ui.reader.subfilter -import android.annotation.SuppressLint import android.os.Bundle import androidx.annotation.VisibleForTesting import androidx.lifecycle.LiveData @@ -9,7 +8,6 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode -import org.wordpress.android.R import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.datasets.ReaderBlogTable import org.wordpress.android.datasets.ReaderTagTable @@ -31,7 +29,6 @@ import org.wordpress.android.ui.reader.tracker.ReaderTrackerType import org.wordpress.android.ui.reader.utils.ReaderUtils import org.wordpress.android.ui.reader.viewmodels.ReaderModeInfo import org.wordpress.android.ui.utils.UiString.UiStringRes -import org.wordpress.android.ui.utils.UiString.UiStringText import org.wordpress.android.util.AppLog import org.wordpress.android.util.AppLog.T import org.wordpress.android.util.EventBusWrapper @@ -63,9 +60,6 @@ class SubFilterViewModel @Inject constructor( private val _bottomSheetUiState = MutableLiveData>() val bottomSheetUiState: LiveData> = _bottomSheetUiState - private val _filtersMatchCount = MutableLiveData>() - val filtersMatchCount: LiveData> = _filtersMatchCount - private val _bottomSheetEmptyViewAction = MutableLiveData>() val bottomSheetEmptyViewAction: LiveData> = _bottomSheetEmptyViewAction @@ -111,8 +105,6 @@ class SubFilterViewModel @Inject constructor( updateSubfilter(currentSubfilter ?: getCurrentSubfilterValue()) initSubfiltersTracking(tag.isFilterable) } - - _filtersMatchCount.value = hashMapOf() } fun loadSubFilters() { @@ -215,12 +207,12 @@ class SubFilterViewModel @Inject constructor( category: SubfilterCategory, ) { updateTagsAndSites() - _bottomSheetUiState.value = Event(BottomSheetVisible( - mTagFragmentStartedWith?.let { - UiStringText(it.label) - } ?: UiStringRes(R.string.reader_filter_main_title), - listOf(category) // TODO thomashorta this should accept only a single category - )) + _bottomSheetUiState.value = Event( + BottomSheetVisible( + UiStringRes(category.titleRes), + listOf(category) // TODO thomashorta this should accept only a single category + ) + ) } fun updateTagsAndSites() { @@ -247,6 +239,7 @@ class SubFilterViewModel @Inject constructor( SubfilterListItem.ItemType.DIVIDER -> { // nop } + SubfilterListItem.ItemType.SITE_ALL -> _readerModeInfo.value = (ReaderModeInfo( streamTag ?: ReaderUtils.getDefaultTag(), ReaderPostListType.TAG_FOLLOWED, @@ -257,6 +250,7 @@ class SubFilterViewModel @Inject constructor( isFirstLoad, false )) + SubfilterListItem.ItemType.SITE -> { val currentFeedId = (subfilterListItem as Site).blog.feedId val currentBlogId = if (subfilterListItem.blog.hasFeedUrl()) { @@ -276,6 +270,7 @@ class SubFilterViewModel @Inject constructor( true )) } + SubfilterListItem.ItemType.TAG -> _readerModeInfo.value = (ReaderModeInfo( (subfilterListItem as Tag).tag, ReaderPostListType.TAG_FOLLOWED, @@ -299,13 +294,6 @@ class SubFilterViewModel @Inject constructor( changeSubfilter(getCurrentSubfilterValue(), false, mTagFragmentStartedWith) } - @SuppressLint("NullSafeMutableLiveData") - fun onSubfilterPageUpdated(category: SubfilterCategory, count: Int) { - val currentValue = _filtersMatchCount.value - currentValue?.put(category, count) - _filtersMatchCount.postValue(currentValue) - } - fun onBottomSheetActionClicked(action: ActionType) { _bottomSheetUiState.postValue(Event(BottomSheetHidden)) _bottomSheetEmptyViewAction.postValue(Event(action)) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubfilterPageFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubfilterPageFragment.kt index d95e8c22407e..0701c79e378a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubfilterPageFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/subfilter/SubfilterPageFragment.kt @@ -115,7 +115,6 @@ class SubfilterPageFragment : Fragment() { viewModel.onSubFiltersChanged(items.isEmpty()) adapter.update(items) - subFilterViewModel.onSubfilterPageUpdated(category, items.size) } } diff --git a/WordPress/src/main/res/layout/subfilter_bottom_sheet.xml b/WordPress/src/main/res/layout/subfilter_bottom_sheet.xml index b378fe80bfd2..e805798c667f 100644 --- a/WordPress/src/main/res/layout/subfilter_bottom_sheet.xml +++ b/WordPress/src/main/res/layout/subfilter_bottom_sheet.xml @@ -16,28 +16,10 @@ android:id="@+id/title" style="@style/SiteTagBottomSheetTitle" android:layout_width="match_parent" - android:text="@string/reader_filter_main_title" /> + android:text="@string/reader_filter_sites_title" /> - - - - - - + android:layout_height="match_parent" /> diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index a2fa63a7e544..d9dd45154705 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -1623,9 +1623,8 @@ Reader Filter - Sites - Topics - Following + Filter by blog + Filter by tag See the newest posts from sites you follow You can follow posts on a specific subject by adding a topic Log in to WordPress.com to see the latest posts from sites you follow diff --git a/WordPress/src/main/res/values/styles.xml b/WordPress/src/main/res/values/styles.xml index d3321af390fa..e2716276466e 100644 --- a/WordPress/src/main/res/values/styles.xml +++ b/WordPress/src/main/res/values/styles.xml @@ -1179,10 +1179,12 @@ @dimen/one_line_list_item_height @dimen/content_margin_bottom_sheet_row_start @dimen/content_margin_bottom_sheet_row_start - sans-serif-medium + sans-serif + bold 0dp 0dp - true + false + @dimen/text_sz_larger - -