Skip to content

Commit df0fdca

Browse files
nbradburyclaude
andcommitted
Reader: Restyle Discover sub-tabs with MD3-inspired TabLayout style
Apply a new WordPress.TabLayout.Material3 style to the Discover sub-tab strip so it visually matches the Compose PrimaryTabRow used in the new Stats and RS post list screens: text-width indicator with rounded top, elastic animation, MD3 title-small typography, primary-colored selected text, and a surface background flush with the app bar. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9bbf556 commit df0fdca

File tree

14 files changed

+252
-222
lines changed

14 files changed

+252
-222
lines changed

WordPress/src/main/java/org/wordpress/android/models/ReaderTag.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,16 @@ public class ReaderTag implements Serializable, FilterCriteria {
1717
public static final String DISCOVER_PATH = String.format(Locale.US, "read/sites/%d/posts",
1818
ReaderConstants.DISCOVER_SITE_ID);
1919
public static final String FRESHLY_PRESSED_PATH = "/freshly-pressed";
20+
// TODO: replace with real endpoint once the Calypso Recommended path is identified
21+
public static final String RECOMMENDED_PATH = "/read/TODO-recommended";
22+
// TODO: replace with real endpoint once the Calypso Latest path is identified
23+
public static final String LATEST_PATH = "/read/TODO-latest";
2024

2125
public static final String TAG_TITLE_FOLLOWED_SITES = "Followed Sites";
2226
public static final String TAG_TITLE_FRESHLY_PRESSED = "Freshly Pressed";
2327
public static final String TAG_SLUG_FRESHLY_PRESSED = "freshly-pressed";
28+
public static final String TAG_SLUG_RECOMMENDED = "recommended";
29+
public static final String TAG_SLUG_LATEST = "latest";
2430
public static final String TAG_SLUG_P2 = "p2";
2531
public static final String TAG_SLUG_BOOKMARKED = "bookmarked-posts";
2632
public static final String TAG_TITLE_DEFAULT = TAG_TITLE_FOLLOWED_SITES;
@@ -183,6 +189,14 @@ public boolean isFreshlyPressed() {
183189
return tagType == ReaderTagType.DEFAULT && getEndpoint().endsWith(FRESHLY_PRESSED_PATH);
184190
}
185191

192+
public boolean isRecommended() {
193+
return tagType == ReaderTagType.DEFAULT && getEndpoint().endsWith(RECOMMENDED_PATH);
194+
}
195+
196+
public boolean isLatest() {
197+
return tagType == ReaderTagType.DEFAULT && getEndpoint().endsWith(LATEST_PATH);
198+
}
199+
186200
public boolean isDefaultInMemoryTag() {
187201
return tagType == ReaderTagType.DEFAULT && mIsDefaultInMemoryTag;
188202
}

WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ public enum DeletablePrefKey implements PrefKey {
146146
READER_RECOMMENDED_TAGS_DELETED_FOR_LOGGED_OUT_USER,
147147
// Selected Reader feed ID for persisting user preferred feed
148148
READER_TOP_BAR_SELECTED_FEED_ITEM_ID,
149+
// Index of the last-selected sub-tab inside the Reader Discover tabbed UI
150+
READER_DISCOVER_SELECTED_SUB_TAB_INDEX,
149151
MANUAL_FEATURE_CONFIG,
150152
EXPERIMENTAL_FEATURE_CONFIG,
151153
SITE_JETPACK_CAPABILITIES,
@@ -1195,6 +1197,14 @@ public static void setReaderTopBarSelectedFeedItemId(@Nullable String selectedFe
11951197
}
11961198
}
11971199

1200+
public static int getReaderDiscoverSelectedSubTabIndex() {
1201+
return getInt(DeletablePrefKey.READER_DISCOVER_SELECTED_SUB_TAB_INDEX, 0);
1202+
}
1203+
1204+
public static void setReaderDiscoverSelectedSubTabIndex(int index) {
1205+
setInt(DeletablePrefKey.READER_DISCOVER_SELECTED_SUB_TAB_INDEX, index);
1206+
}
1207+
11981208
public static void setShouldShowStorageWarning(boolean shouldShow) {
11991209
setBoolean(UndeletablePrefKey.SHOULD_SHOW_STORAGE_WARNING, shouldShow);
12001210
}

WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefsWrapper.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ class AppPrefsWrapper @Inject constructor(val buildConfigWrapper: BuildConfigWra
7676
get() = AppPrefs.getReaderTopBarSelectedFeedItemId()
7777
set(selectedFeedItemId) = AppPrefs.setReaderTopBarSelectedFeedItemId(selectedFeedItemId)
7878

79+
var readerDiscoverSelectedSubTabIndex: Int
80+
get() = AppPrefs.getReaderDiscoverSelectedSubTabIndex()
81+
set(index) = AppPrefs.setReaderDiscoverSelectedSubTabIndex(index)
82+
7983
var shouldScheduleCreateSiteNotification: Boolean
8084
get() = AppPrefs.shouldScheduleCreateSiteNotification()
8185
set(shouldSchedule) = AppPrefs.setShouldScheduleCreateSiteNotification(shouldSchedule)

WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import org.wordpress.android.ui.main.WPMainActivity.OnScrollToTopListener
3030
import org.wordpress.android.ui.main.WPMainNavigationView.PageType.READER
3131
import org.wordpress.android.ui.mysite.jetpackbadge.JetpackPoweredBottomSheetFragment
3232
import org.wordpress.android.ui.reader.SubfilterBottomSheetFragment.Companion.newInstance
33-
import org.wordpress.android.ui.reader.discover.ReaderDiscoverFragment
33+
import org.wordpress.android.ui.reader.discover.ReaderDiscoverTabsFragment
3434
import org.wordpress.android.ui.reader.discover.interests.ReaderInterestsFragment
3535
import org.wordpress.android.ui.reader.services.update.ReaderUpdateLogic.UpdateTask
3636
import org.wordpress.android.ui.reader.services.update.ReaderUpdateLogic.UpdateTask.FOLLOWED_BLOGS
@@ -314,7 +314,7 @@ class ReaderFragment : Fragment(R.layout.reader_fragment_layout), ScrollableView
314314

315315
val selectedTag = uiState.selectedReaderTag
316316
val fragment = when {
317-
selectedTag.isDiscover -> ReaderDiscoverFragment()
317+
selectedTag.isDiscover -> ReaderDiscoverTabsFragment()
318318
selectedTag.isTags -> ReaderTagsFeedFragment.newInstance(selectedTag)
319319
else -> ReaderPostListFragment.newInstanceForTag(
320320
selectedTag,
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package org.wordpress.android.ui.reader.discover
2+
3+
import android.os.Bundle
4+
import android.view.View
5+
import androidx.fragment.app.Fragment
6+
import androidx.viewpager2.adapter.FragmentStateAdapter
7+
import androidx.viewpager2.widget.ViewPager2
8+
import com.google.android.material.tabs.TabLayoutMediator
9+
import dagger.hilt.android.AndroidEntryPoint
10+
import org.wordpress.android.R
11+
import org.wordpress.android.databinding.ReaderDiscoverTabsFragmentBinding
12+
import org.wordpress.android.models.ReaderTag
13+
import org.wordpress.android.models.ReaderTagType
14+
import org.wordpress.android.ui.ScrollableViewInitializedListener
15+
import org.wordpress.android.ui.ViewPagerFragment
16+
import org.wordpress.android.ui.main.WPMainActivity.OnScrollToTopListener
17+
import org.wordpress.android.ui.reader.ReaderPostListFragment
18+
import org.wordpress.android.ui.reader.ReaderTypes
19+
import org.wordpress.android.ui.prefs.AppPrefsWrapper
20+
import javax.inject.Inject
21+
22+
/**
23+
* Container for the Reader Discover experience. Hosts three sub-tabs
24+
* (Freshly Pressed, Recommended, Latest), each backed by a
25+
* [ReaderPostListFragment] driven by an in-memory [ReaderTag].
26+
*
27+
* Defaults to Freshly Pressed on first open; restores the last selected
28+
* sub-tab from [AppPrefsWrapper] on subsequent opens.
29+
*/
30+
@AndroidEntryPoint
31+
class ReaderDiscoverTabsFragment : ViewPagerFragment(R.layout.reader_discover_tabs_fragment),
32+
OnScrollToTopListener, ScrollableViewInitializedListener {
33+
@Inject
34+
lateinit var appPrefsWrapper: AppPrefsWrapper
35+
36+
private var binding: ReaderDiscoverTabsFragmentBinding? = null
37+
38+
private val tabs: List<ReaderTag> by lazy {
39+
listOf(
40+
createFreshlyPressedTag(),
41+
createRecommendedTag(),
42+
createLatestTag(),
43+
)
44+
}
45+
46+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
47+
super.onViewCreated(view, savedInstanceState)
48+
val binding = ReaderDiscoverTabsFragmentBinding.bind(view).also { this.binding = it }
49+
50+
binding.viewPager.adapter = DiscoverTabsAdapter(this, tabs)
51+
52+
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
53+
tab.text = getString(tabTitleResFor(position))
54+
}.attach()
55+
56+
// Restore last selected sub-tab (defaults to 0 == Freshly Pressed on first open).
57+
val initialIndex = appPrefsWrapper.readerDiscoverSelectedSubTabIndex
58+
.coerceIn(0, tabs.lastIndex)
59+
binding.viewPager.setCurrentItem(initialIndex, false)
60+
61+
binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
62+
override fun onPageSelected(position: Int) {
63+
appPrefsWrapper.readerDiscoverSelectedSubTabIndex = position
64+
}
65+
})
66+
}
67+
68+
override fun onDestroyView() {
69+
super.onDestroyView()
70+
binding = null
71+
}
72+
73+
/**
74+
* The active inner [ReaderPostListFragment] notifies the parent chain of its
75+
* scrollable view id during its own [onResume]; we return null here so we
76+
* don't interfere with that.
77+
*/
78+
override fun getScrollableViewForUniqueIdProvision(): View? = null
79+
80+
/**
81+
* Forwards the scrollable view id notification from the active inner
82+
* [ReaderPostListFragment] up to the outer [org.wordpress.android.ui.reader.ReaderFragment]
83+
* so that the Reader's lift-on-scroll AppBar keeps working.
84+
*/
85+
override fun onScrollableViewInitialized(containerId: Int) {
86+
(parentFragment as? ScrollableViewInitializedListener)
87+
?.onScrollableViewInitialized(containerId)
88+
}
89+
90+
/**
91+
* Forwards scroll-to-top to the currently-visible child fragment using the
92+
* default [FragmentStateAdapter] tag convention ("f{position}").
93+
*/
94+
override fun onScrollToTop() {
95+
val currentItem = binding?.viewPager?.currentItem ?: return
96+
val tag = "f$currentItem"
97+
(childFragmentManager.findFragmentByTag(tag) as? OnScrollToTopListener)?.onScrollToTop()
98+
}
99+
100+
private fun tabTitleResFor(position: Int): Int = when (position) {
101+
INDEX_FRESHLY_PRESSED -> R.string.reader_discover_tab_freshly_pressed
102+
INDEX_RECOMMENDED -> R.string.reader_discover_tab_recommended
103+
INDEX_LATEST -> R.string.reader_discover_tab_latest
104+
else -> R.string.reader_discover_tab_freshly_pressed
105+
}
106+
107+
private class DiscoverTabsAdapter(
108+
fragment: Fragment,
109+
private val tabs: List<ReaderTag>,
110+
) : FragmentStateAdapter(fragment) {
111+
override fun getItemCount(): Int = tabs.size
112+
113+
override fun createFragment(position: Int): Fragment {
114+
return ReaderPostListFragment.newInstanceForTag(
115+
tabs[position],
116+
ReaderTypes.ReaderPostListType.TAG_FOLLOWED,
117+
/* isTopLevel = */ true,
118+
/* isFilterable = */ false,
119+
)
120+
}
121+
}
122+
123+
companion object {
124+
private const val INDEX_FRESHLY_PRESSED = 0
125+
private const val INDEX_RECOMMENDED = 1
126+
private const val INDEX_LATEST = 2
127+
128+
private fun createFreshlyPressedTag(): ReaderTag = ReaderTag(
129+
ReaderTag.TAG_SLUG_FRESHLY_PRESSED,
130+
ReaderTag.TAG_TITLE_FRESHLY_PRESSED,
131+
ReaderTag.TAG_TITLE_FRESHLY_PRESSED,
132+
ReaderTag.FRESHLY_PRESSED_PATH,
133+
ReaderTagType.DEFAULT,
134+
)
135+
136+
private fun createRecommendedTag(): ReaderTag = ReaderTag(
137+
ReaderTag.TAG_SLUG_RECOMMENDED,
138+
ReaderTag.TAG_SLUG_RECOMMENDED,
139+
ReaderTag.TAG_SLUG_RECOMMENDED,
140+
ReaderTag.RECOMMENDED_PATH,
141+
ReaderTagType.DEFAULT,
142+
)
143+
144+
private fun createLatestTag(): ReaderTag = ReaderTag(
145+
ReaderTag.TAG_SLUG_LATEST,
146+
ReaderTag.TAG_SLUG_LATEST,
147+
ReaderTag.TAG_SLUG_LATEST,
148+
ReaderTag.LATEST_PATH,
149+
ReaderTagType.DEFAULT,
150+
)
151+
}
152+
}

WordPress/src/main/java/org/wordpress/android/ui/reader/usecases/LoadReaderItemsUseCase.kt

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,6 @@ class LoadReaderItemsUseCase @Inject constructor(
5151

5252
val orderedList = ReaderUtils.getOrderedTagsList(tagList, ReaderUtils.getDefaultTagInfo())
5353

54-
// Add "Freshly Pressed" after ordering to ensure it appears after Discover in the list
55-
if (orderedList.none { it.isFreshlyPressed }) {
56-
// Find Discover index and insert Freshly Pressed right after it
57-
val discoverIndex = orderedList.indexOfFirst { it.isDiscover }
58-
val insertIndex = if (discoverIndex >= 0) discoverIndex + 1 else orderedList.size
59-
orderedList.add(insertIndex, ReaderTag(
60-
ReaderTag.TAG_SLUG_FRESHLY_PRESSED,
61-
ReaderTag.TAG_TITLE_FRESHLY_PRESSED,
62-
ReaderTag.TAG_TITLE_FRESHLY_PRESSED,
63-
ReaderTag.FRESHLY_PRESSED_PATH,
64-
ReaderTagType.DEFAULT
65-
))
66-
}
67-
6854
orderedList
6955
}
7056
}

WordPress/src/main/java/org/wordpress/android/ui/reader/utils/ReaderTopBarMenuHelper.kt

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ class ReaderTopBarMenuHelper @Inject constructor(
2222
readerTagsList.indexOrNull { it.isDiscover }?.let { discoverIndex ->
2323
add(createDiscoverItem(getMenuItemIdFromReaderTagIndex(discoverIndex)))
2424
}
25-
readerTagsList.indexOrNull { it.isFreshlyPressed }?.let { freshlyPressedIndex ->
26-
add(createFreshlyPressedItem(getMenuItemIdFromReaderTagIndex(freshlyPressedIndex)))
27-
}
2825
readerTagsList.indexOrNull { it.isFollowedSites }?.let { followingIndex ->
2926
add(createSubscriptionsItem(getMenuItemIdFromReaderTagIndex(followingIndex)))
3027
}
@@ -90,14 +87,6 @@ class ReaderTopBarMenuHelper @Inject constructor(
9087
)
9188
}
9289

93-
private fun createFreshlyPressedItem(id: String): JetpackMenuElementData.Item.Single {
94-
return JetpackMenuElementData.Item.Single(
95-
id = id,
96-
text = UiString.UiStringRes(R.string.reader_dropdown_menu_freshly_pressed),
97-
leadingIcon = R.drawable.ic_star_white_24dp,
98-
)
99-
}
100-
10190
private fun createSubscriptionsItem(id: String): JetpackMenuElementData.Item.Single {
10291
return JetpackMenuElementData.Item.Single(
10392
id = id,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:shape="rectangle">
4+
<solid android:color="?attr/colorPrimary" />
5+
<corners
6+
android:topLeftRadius="3dp"
7+
android:topRightRadius="3dp" />
8+
</shape>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
android:layout_width="match_parent"
5+
android:layout_height="match_parent"
6+
android:orientation="vertical">
7+
8+
<com.google.android.material.tabs.TabLayout
9+
android:id="@+id/tabLayout"
10+
style="@style/WordPress.TabLayout.Material3"
11+
android:layout_width="match_parent"
12+
android:layout_height="wrap_content"
13+
app:tabGravity="fill"
14+
app:tabMaxWidth="0dp"
15+
app:tabMode="fixed" />
16+
17+
<androidx.viewpager2.widget.ViewPager2
18+
android:id="@+id/viewPager"
19+
android:layout_width="match_parent"
20+
android:layout_height="0dp"
21+
android:layout_weight="1" />
22+
23+
</LinearLayout>

WordPress/src/main/res/values/strings.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1959,8 +1959,10 @@
19591959
<string name="reader_error_changing_seen_status_of_unsupported_post">Can\'t toggle seen status of this post</string>
19601960
<string name="reader_search_content_description">Search</string>
19611961
<string name="reader_dropdown_menu_discover">Discover</string>
1962-
<string name="reader_dropdown_menu_freshly_pressed">Freshly Pressed</string>
19631962
<string name="reader_dropdown_menu_subscriptions">Subscriptions</string>
1963+
<string name="reader_discover_tab_freshly_pressed">Freshly Pressed</string>
1964+
<string name="reader_discover_tab_recommended">Recommended</string>
1965+
<string name="reader_discover_tab_latest">Latest</string>
19641966
<string name="reader_dropdown_menu_saved">Saved</string>
19651967
<string name="reader_dropdown_menu_liked">Liked</string>
19661968
<string name="reader_dropdown_menu_automattic">Automattic</string>

0 commit comments

Comments
 (0)