diff --git a/WordPress/build.gradle b/WordPress/build.gradle
index 904035c3af15..286731545494 100644
--- a/WordPress/build.gradle
+++ b/WordPress/build.gradle
@@ -493,6 +493,8 @@ dependencies {
testImplementation(libs.assertj.core)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.turbine)
+ testImplementation(libs.robolectric)
+ testImplementation(libs.androidx.test.core)
androidTestImplementation project(path:':libs:mocks')
diff --git a/WordPress/src/main/AndroidManifest.xml b/WordPress/src/main/AndroidManifest.xml
index f6bdfdc55b21..e77c58834043 100644
--- a/WordPress/src/main/AndroidManifest.xml
+++ b/WordPress/src/main/AndroidManifest.xml
@@ -581,6 +581,82 @@
android:pathPattern="/site-monitoring/.*"
android:scheme="http" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -612,49 +688,71 @@
+ android:scheme="https"
+ tools:ignore="IntentFilterUniqueDataAttributes" >
+ android:scheme="http"
+ tools:ignore="IntentFilterUniqueDataAttributes" >
+ android:scheme="https"
+ tools:ignore="IntentFilterUniqueDataAttributes" >
+ android:scheme="http"
+ tools:ignore="IntentFilterUniqueDataAttributes" >
+ android:scheme="https"
+ tools:ignore="IntentFilterUniqueDataAttributes" >
+ android:scheme="http"
+ tools:ignore="IntentFilterUniqueDataAttributes" >
+ android:scheme="https"
+ tools:ignore="IntentFilterUniqueDataAttributes" >
+ android:scheme="http"
+ tools:ignore="IntentFilterUniqueDataAttributes" >
+
+
+
+
+
+
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java
index 084adad57ae0..c038964765b5 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java
@@ -338,6 +338,43 @@ public static void viewReaderInNewStack(Context context) {
context.startActivity(intent);
}
+ public static void viewReaderDiscoverInNewStack(Context context) {
+ Intent intent = getMainActivityInNewStack(context);
+ intent.putExtra(WPMainActivity.ARG_OPEN_PAGE, WPMainActivity.ARG_READER);
+ intent.putExtra(WPMainActivity.ARG_READER_DISCOVER_TAB, true);
+ context.startActivity(intent);
+ }
+
+ public static void viewReaderFeedInNewStack(Context context, long feedId) {
+ Intent mainActivityIntent = getMainActivityInNewStack(context)
+ .putExtra(WPMainActivity.ARG_OPEN_PAGE, WPMainActivity.ARG_READER);
+ Intent feedIntent = ReaderActivityLauncher.buildReaderFeedIntent(context, feedId, "deeplink");
+ TaskStackBuilder.create(context)
+ .addNextIntent(mainActivityIntent)
+ .addNextIntent(feedIntent)
+ .startActivities();
+ }
+
+ public static void viewReaderSearchInNewStack(Context context) {
+ Intent mainActivityIntent = getMainActivityInNewStack(context)
+ .putExtra(WPMainActivity.ARG_OPEN_PAGE, WPMainActivity.ARG_READER);
+ Intent searchIntent = ReaderActivityLauncher.createReaderSearchIntent(context);
+ TaskStackBuilder.create(context)
+ .addNextIntent(mainActivityIntent)
+ .addNextIntent(searchIntent)
+ .startActivities();
+ }
+
+ public static void viewReaderTagInNewStack(@NonNull Context context, @NonNull String tagSlug) {
+ Intent mainActivityIntent = getMainActivityInNewStack(context)
+ .putExtra(WPMainActivity.ARG_OPEN_PAGE, WPMainActivity.ARG_READER);
+ Intent tagIntent = ReaderActivityLauncher.buildReaderTagIntent(context, tagSlug, "deeplink");
+ TaskStackBuilder.create(context)
+ .addNextIntent(mainActivityIntent)
+ .addNextIntent(tagIntent)
+ .startActivities();
+ }
+
public static void viewPostDeeplinkInNewStack(Context context, Uri uri) {
Intent mainActivityIntent = getMainActivityInNewStack(context)
.putExtra(WPMainActivity.ARG_OPEN_PAGE, WPMainActivity.ARG_READER);
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/deeplinks/DeepLinkNavigator.kt b/WordPress/src/main/java/org/wordpress/android/ui/deeplinks/DeepLinkNavigator.kt
index 8613afa59d1d..eeffd41f9225 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/deeplinks/DeepLinkNavigator.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/deeplinks/DeepLinkNavigator.kt
@@ -18,7 +18,11 @@ import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenN
import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenPages
import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenPagesForSite
import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenQRCodeAuthFlow
+import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenFeedInReader
import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenReader
+import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenReaderDiscover
+import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenReaderSearch
+import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenTagInReader
import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenStats
import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenStatsForSite
import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenStatsForSiteAndTimeframe
@@ -78,6 +82,10 @@ class DeepLinkNavigator
)
OpenReader -> ActivityLauncher.viewReaderInNewStack(activity)
+ OpenReaderDiscover -> ActivityLauncher.viewReaderDiscoverInNewStack(activity)
+ OpenReaderSearch -> ActivityLauncher.viewReaderSearchInNewStack(activity)
+ is OpenFeedInReader -> ActivityLauncher.viewReaderFeedInNewStack(activity, navigateAction.feedId)
+ is OpenTagInReader -> ActivityLauncher.viewReaderTagInNewStack(activity, navigateAction.tagSlug)
is OpenInReader -> ActivityLauncher.viewPostDeeplinkInNewStack(activity, navigateAction.uri.uri)
is ViewPostInReader -> ActivityLauncher.viewReaderPostDetailInNewStack(
activity,
@@ -123,6 +131,10 @@ class DeepLinkNavigator
data class OpenEditorForPost(val site: SiteModel, val postId: Int) : NavigateAction()
data class OpenEditorForSite(val site: SiteModel) : NavigateAction()
object OpenReader : NavigateAction()
+ object OpenReaderDiscover : NavigateAction()
+ object OpenReaderSearch : NavigateAction()
+ data class OpenFeedInReader(val feedId: Long) : NavigateAction()
+ data class OpenTagInReader(val tagSlug: String) : NavigateAction()
data class OpenInReader(val uri: UriWrapper) : NavigateAction()
data class ViewPostInReader(val blogId: Long, val postId: Long, val uri: UriWrapper) : NavigateAction()
object OpenEditor : NavigateAction()
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/deeplinks/handlers/ReaderLinkHandler.kt b/WordPress/src/main/java/org/wordpress/android/ui/deeplinks/handlers/ReaderLinkHandler.kt
index 24b57ced8616..2538e191560a 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/deeplinks/handlers/ReaderLinkHandler.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/deeplinks/handlers/ReaderLinkHandler.kt
@@ -6,8 +6,12 @@ import androidx.lifecycle.MutableLiveData
import org.wordpress.android.R
import org.wordpress.android.analytics.AnalyticsTracker.Stat.READER_VIEWPOST_INTERCEPTED
import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction
+import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenFeedInReader
import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenInReader
import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenReader
+import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenReaderDiscover
+import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenReaderSearch
+import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenTagInReader
import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.ViewPostInReader
import org.wordpress.android.ui.deeplinks.DeepLinkingIntentReceiverViewModel.Companion.APPLINK_SCHEME
import org.wordpress.android.ui.deeplinks.DeepLinkingIntentReceiverViewModel.Companion.HOST_WORDPRESS_COM
@@ -34,18 +38,79 @@ class ReaderLinkHandler
* Other deeplinks handled:
* `wordpress://read`
* `wordpress://viewpost?blogId={blogId}&postId={postId}`
+ * `wordpress.com/read`
+ * `wordpress.com/discover`
*/
override fun shouldHandleUrl(uri: UriWrapper): Boolean {
- return DEEP_LINK_HOST_READ == uri.host || DEEP_LINK_HOST_VIEWPOST == uri.host || intentUtils.canResolveWith(
- ReaderConstants.ACTION_VIEW_POST,
- uri
- )
+ return DEEP_LINK_HOST_READ == uri.host ||
+ DEEP_LINK_HOST_VIEWPOST == uri.host ||
+ isWordPressComReaderUrl(uri) ||
+ isWordPressComDiscoverUrl(uri) ||
+ isWordPressComFeedUrl(uri) ||
+ isWordPressComReaderSearchUrl(uri) ||
+ isWordPressComTagUrl(uri) ||
+ intentUtils.canResolveWith(ReaderConstants.ACTION_VIEW_POST, uri)
+ }
+
+ private fun isWordPressComReaderUrl(uri: UriWrapper): Boolean {
+ return uri.host == HOST_WORDPRESS_COM &&
+ uri.pathSegments.size == 1 &&
+ uri.pathSegments.firstOrNull() == PATH_READ
+ }
+
+ private fun isWordPressComDiscoverUrl(uri: UriWrapper): Boolean {
+ return uri.host == HOST_WORDPRESS_COM &&
+ uri.pathSegments.size == 1 &&
+ uri.pathSegments.firstOrNull() == PATH_DISCOVER
+ }
+
+ /**
+ * Checks if this is a feed URL like wordpress.com/read/feeds/{feedId} or wordpress.com/reader/feeds/{feedId}
+ * but NOT a post URL like wordpress.com/read/feeds/{feedId}/posts/{postId}
+ */
+ private fun isWordPressComFeedUrl(uri: UriWrapper): Boolean {
+ val segments = uri.pathSegments
+ return uri.host == HOST_WORDPRESS_COM &&
+ segments.size == FEED_URL_SEGMENTS &&
+ isReadOrReaderPath(segments.firstOrNull()) &&
+ segments.getOrNull(SECOND_PATH_POSITION) == PATH_FEEDS
+ }
+
+ /**
+ * Checks if this is a reader search URL like wordpress.com/read/search or wordpress.com/reader/search
+ */
+ private fun isWordPressComReaderSearchUrl(uri: UriWrapper): Boolean {
+ val segments = uri.pathSegments
+ return uri.host == HOST_WORDPRESS_COM &&
+ segments.size == SEARCH_URL_SEGMENTS &&
+ isReadOrReaderPath(segments.firstOrNull()) &&
+ segments.getOrNull(SECOND_PATH_POSITION) == PATH_SEARCH
+ }
+
+ private fun isReadOrReaderPath(segment: String?) = segment == PATH_READ || segment == PATH_READER
+
+ /**
+ * Checks if this is a tag URL like wordpress.com/tag/{tagSlug}
+ */
+ private fun isWordPressComTagUrl(uri: UriWrapper): Boolean {
+ val segments = uri.pathSegments
+ return uri.host == HOST_WORDPRESS_COM &&
+ segments.size == TAG_URL_SEGMENTS &&
+ segments.firstOrNull() == PATH_TAG
+ }
+
+ private fun extractFeedId(uri: UriWrapper): Long? {
+ return uri.pathSegments.getOrNull(FEED_ID_POSITION)?.toLongOrNull()
+ }
+
+ private fun extractTagSlug(uri: UriWrapper): String? {
+ return uri.pathSegments.getOrNull(TAG_SLUG_POSITION)
}
override fun buildNavigateAction(uri: UriWrapper): NavigateAction {
- return when (uri.host) {
- DEEP_LINK_HOST_READ -> OpenReader
- DEEP_LINK_HOST_VIEWPOST -> {
+ return when {
+ uri.host == DEEP_LINK_HOST_READ -> OpenReader
+ uri.host == DEEP_LINK_HOST_VIEWPOST -> {
val blogId = uri.getQueryParameter(BLOG_ID)?.toLongOrNull()
val postId = uri.getQueryParameter(POST_ID)?.toLongOrNull()
if (blogId != null && postId != null) {
@@ -56,6 +121,27 @@ class ReaderLinkHandler
OpenReader
}
}
+ isWordPressComReaderUrl(uri) -> OpenReader
+ isWordPressComDiscoverUrl(uri) -> OpenReaderDiscover
+ isWordPressComReaderSearchUrl(uri) -> OpenReaderSearch
+ isWordPressComFeedUrl(uri) -> {
+ val feedId = extractFeedId(uri)
+ if (feedId != null) {
+ OpenFeedInReader(feedId)
+ } else {
+ _toast.value = Event(R.string.error_generic)
+ OpenReader
+ }
+ }
+ isWordPressComTagUrl(uri) -> {
+ val tagSlug = extractTagSlug(uri)
+ if (!tagSlug.isNullOrBlank()) {
+ OpenTagInReader(tagSlug)
+ } else {
+ _toast.value = Event(R.string.error_generic)
+ OpenReader
+ }
+ }
else -> OpenInReader(uri)
}
}
@@ -64,15 +150,18 @@ class ReaderLinkHandler
* URLs handled here
* `wordpress://read`
* `wordpress://viewpost?blogId={blogId}&postId={postId}`
+ * wordpress.com/read
* wordpress.com/read/feeds/feedId/posts/feedItemId
* wordpress.com/read/blogs/feedId/posts/feedItemId
+ * wordpress.com/reader/feeds/feedId/posts/feedItemId
+ * wordpress.com/discover
* domain.wordpress.com/2.../../../postId
* domain.wordpress.com/19../../../postId
*/
override fun stripUrl(uri: UriWrapper): String {
- return when (uri.host) {
- DEEP_LINK_HOST_READ -> "$APPLINK_SCHEME$DEEP_LINK_HOST_READ"
- DEEP_LINK_HOST_VIEWPOST -> {
+ return when {
+ uri.host == DEEP_LINK_HOST_READ -> "$APPLINK_SCHEME$DEEP_LINK_HOST_READ"
+ uri.host == DEEP_LINK_HOST_VIEWPOST -> {
val hasBlogId = uri.getQueryParameter(BLOG_ID) != null
val hasPostId = uri.getQueryParameter(POST_ID) != null
buildString {
@@ -91,13 +180,18 @@ class ReaderLinkHandler
}
}
}
+ isWordPressComReaderUrl(uri) -> "$HOST_WORDPRESS_COM/$PATH_READ"
+ isWordPressComDiscoverUrl(uri) -> "$HOST_WORDPRESS_COM/$PATH_DISCOVER"
+ isWordPressComReaderSearchUrl(uri) -> "$HOST_WORDPRESS_COM/$PATH_READ/$PATH_SEARCH"
+ isWordPressComFeedUrl(uri) -> "$HOST_WORDPRESS_COM/$PATH_READ/$PATH_FEEDS/$FEED_ID"
+ isWordPressComTagUrl(uri) -> "$HOST_WORDPRESS_COM/$PATH_TAG/$TAG_SLUG"
else -> {
buildString {
val segments = uri.pathSegments
// Handled URLs look like this: http[s]://wordpress.com/read/feeds/{feedId}/posts/{feedItemId}
// with the first segment being 'read'.
append(stripHost(uri))
- if (segments.firstOrNull() == "read") {
+ if (isReadOrReaderPath(segments.firstOrNull())) {
appendReadPath(segments)
} else if (segments.size > DATE_URL_SEGMENTS) {
append("/YYYY/MM/DD/$POST_ID")
@@ -120,7 +214,7 @@ class ReaderLinkHandler
private fun StringBuilder.appendReadPath(segments: List) {
append("/read")
- when (segments.getOrNull(BLOGS_FEEDS_PATH_POSITION)) {
+ when (segments.getOrNull(SECOND_PATH_POSITION)) {
"blogs" -> {
append("/blogs/$FEED_ID")
}
@@ -134,14 +228,35 @@ class ReaderLinkHandler
}
companion object {
+ // Applink hosts (wordpress://read, wordpress://viewpost)
private const val DEEP_LINK_HOST_READ = "read"
private const val DEEP_LINK_HOST_VIEWPOST = "viewpost"
+
+ // URL path segments
+ private const val PATH_READ = "read"
+ private const val PATH_READER = "reader"
+ private const val PATH_DISCOVER = "discover"
+ private const val PATH_FEEDS = "feeds"
+ private const val PATH_SEARCH = "search"
+ private const val PATH_TAG = "tag"
+
+ // Query and path parameter names (used for analytics stripping)
private const val BLOG_ID = "blogId"
private const val POST_ID = "postId"
private const val FEED_ID = "feedId"
- private const val CUSTOM_DOMAIN_POSITION = 3
- private const val BLOGS_FEEDS_PATH_POSITION = 1
+ private const val TAG_SLUG = "tagSlug"
+
+ // URL segment positions
+ private const val SECOND_PATH_POSITION = 1
+ private const val FEED_ID_POSITION = 2
+ private const val TAG_SLUG_POSITION = 1
private const val POSTS_PATH_POSITION = 3
+ private const val CUSTOM_DOMAIN_POSITION = 3
+
+ // Expected URL segment counts
private const val DATE_URL_SEGMENTS = 3
+ private const val FEED_URL_SEGMENTS = 3
+ private const val SEARCH_URL_SEGMENTS = 2
+ private const val TAG_URL_SEGMENTS = 2
}
}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java
index 0b0f51460805..0a5715e58030 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java
+++ b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java
@@ -231,6 +231,7 @@ public class WPMainActivity extends BaseAppCompatActivity implements
public static final String ARG_NOTIFICATIONS = "show_notifications";
public static final String ARG_READER = "show_reader";
public static final String ARG_READER_BOOKMARK_TAB = "show_reader_bookmark_tab";
+ public static final String ARG_READER_DISCOVER_TAB = "show_reader_discover_tab";
public static final String ARG_EDITOR = "show_editor";
public static final String ARG_SHOW_ZENDESK_NOTIFICATIONS = "show_zendesk_notifications";
public static final String ARG_STATS = "show_stats";
@@ -901,11 +902,13 @@ private void handleOpenPageIntent(@NonNull Intent intent) {
showJetpackFeatureOverlayAccessedInCorrectly(trackingProperties);
break;
}
+ if (mBottomNav != null) mBottomNav.setCurrentSelectedPage(PageType.READER);
if (intent.getBooleanExtra(ARG_READER_BOOKMARK_TAB, false) && mBottomNav != null && mBottomNav
.getActiveFragment() instanceof ReaderFragment) {
((ReaderFragment) mBottomNav.getActiveFragment()).requestBookmarkTab();
- } else {
- if (mBottomNav != null) mBottomNav.setCurrentSelectedPage(PageType.READER);
+ } else if (intent.getBooleanExtra(ARG_READER_DISCOVER_TAB, false) && mBottomNav != null
+ && mBottomNav.getActiveFragment() instanceof ReaderFragment) {
+ ((ReaderFragment) mBottomNav.getActiveFragment()).requestDiscoverTab();
}
break;
case ARG_EDITOR:
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderActivityLauncher.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderActivityLauncher.kt
index 92c9c87ef372..a2a712802c52 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderActivityLauncher.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderActivityLauncher.kt
@@ -14,6 +14,7 @@ import org.wordpress.android.R
import org.wordpress.android.analytics.AnalyticsTracker
import org.wordpress.android.models.ReaderPost
import org.wordpress.android.models.ReaderTag
+import org.wordpress.android.models.ReaderTagType
import org.wordpress.android.ui.ActivityLauncher
import org.wordpress.android.ui.RequestCodes
import org.wordpress.android.ui.WPWebViewActivity
@@ -194,6 +195,28 @@ object ReaderActivityLauncher {
)
}
+ /**
+ * Build an intent to show posts from a specific feed (for deeplinks)
+ */
+ @JvmStatic
+ fun buildReaderFeedIntent(context: Context, feedId: Long, source: String): Intent {
+ return Intent(context, ReaderPostListActivity::class.java).apply {
+ putExtra(ReaderConstants.ARG_SOURCE, source)
+ putExtra(ReaderConstants.ARG_POST_LIST_TYPE, ReaderPostListType.BLOG_PREVIEW)
+ putExtra(ReaderConstants.ARG_FEED_ID, feedId)
+ putExtra(ReaderConstants.ARG_IS_FEED, true)
+ }
+ }
+
+ /**
+ * Build an intent to show posts with a specific tag (for deeplinks)
+ */
+ @JvmStatic
+ fun buildReaderTagIntent(context: Context, tagSlug: String, source: String): Intent {
+ val tag = ReaderUtils.createTagFromTagName(tagSlug, ReaderTagType.FOLLOWED)
+ return createReaderTagPreviewIntent(context, tag, source)
+ }
+
/*
* show a list of posts with a specific tag
*/
@@ -228,6 +251,7 @@ object ReaderActivityLauncher {
context.startActivity(createReaderSearchIntent(context))
}
+ @JvmStatic
fun createReaderSearchIntent(context: Context): Intent {
return Intent(context, ReaderSearchActivity::class.java)
}
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 18d51e63fdd3..d4d70c4c1a57 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
@@ -402,6 +402,13 @@ class ReaderFragment : Fragment(R.layout.reader_fragment_layout), ScrollableView
viewModel.bookmarkTabRequested()
}
+ fun requestDiscoverTab() {
+ if (!::viewModel.isInitialized) {
+ viewModel = ViewModelProvider(this@ReaderFragment, viewModelFactory)[ReaderViewModel::class.java]
+ }
+ viewModel.discoverTabRequested()
+ }
+
private fun showReaderInterests() {
val readerInterestsFragment = childFragmentManager.findFragmentByTag(ReaderInterestsFragment.TAG)
if (readerInterestsFragment == null) {
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListActivity.kt
index c832c0be9d84..e9f5c65ac5a1 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListActivity.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListActivity.kt
@@ -98,12 +98,12 @@ class ReaderPostListActivity : BaseAppCompatActivity() {
if (postListType == ReaderPostListType.BLOG_PREVIEW) {
setTitle(R.string.reader_activity_title_blog_preview)
if (savedInstanceState == null) {
- val blogId = intent.getLongExtra(ReaderConstants.ARG_BLOG_ID, 0)
val feedId = intent.getLongExtra(ReaderConstants.ARG_FEED_ID, 0)
if (feedId != 0L) {
showListFragmentForFeed(feedId)
siteId = feedId
} else {
+ val blogId = intent.getLongExtra(ReaderConstants.ARG_BLOG_ID, 0)
showListFragmentForBlog(blogId)
siteId = blogId
}
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderPostRepository.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderPostRepository.kt
index 3b6364737d9b..ff41710b4eab 100644
--- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderPostRepository.kt
+++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderPostRepository.kt
@@ -128,12 +128,7 @@ class ReaderPostRepository @Inject constructor(
}
}
val listener = RestRequest.Listener { jsonObject ->
- handleUpdatePostsResponse(
- null,
- jsonObject,
- updateAction,
- resultListener
- )
+ handleUpdatePostsResponse(null, jsonObject, updateAction, resultListener)
}
val errorListener = RestRequest.ErrorListener { volleyError ->
AppLog.e(AppLog.T.READER, volleyError)
@@ -156,12 +151,7 @@ class ReaderPostRepository @Inject constructor(
}
}
val listener = RestRequest.Listener { jsonObject ->
- handleUpdatePostsResponse(
- null,
- jsonObject,
- updateAction,
- resultListener
- )
+ handleUpdatePostsResponse(null, jsonObject, updateAction, resultListener, feedId)
}
val errorListener = RestRequest.ErrorListener { volleyError ->
AppLog.e(AppLog.T.READER, volleyError)
@@ -173,12 +163,17 @@ class ReaderPostRepository @Inject constructor(
/**
* called after requesting posts with a specific tag or in a specific blog/feed
+ *
+ * @param requestedFeedId If provided, ensures all posts have this feedId set. This is needed
+ * because the API response may not include feed_ID for external feeds, but we need it
+ * to properly query posts later.
*/
private fun handleUpdatePostsResponse(
tag: ReaderTag?,
jsonObject: JSONObject?,
updateAction: ReaderPostServiceStarter.UpdateAction,
- resultListener: UpdateResultListener
+ resultListener: UpdateResultListener,
+ requestedFeedId: Long? = null
) {
if (jsonObject == null) {
resultListener.onUpdateResult(ReaderActions.UpdateResult.FAILED)
@@ -190,6 +185,14 @@ class ReaderPostRepository @Inject constructor(
object : Thread() {
override fun run() {
val serverPosts = ReaderPostList.fromJson(jsonObject)
+ // For feed requests, always set the feedId on all posts to the requested feedId.
+ // The API response may not include feed_ID, or may include a different value,
+ // but we need the feedId to match what we'll query for later.
+ if (requestedFeedId != null && requestedFeedId != 0L) {
+ serverPosts.forEach { post ->
+ post.feedId = requestedFeedId
+ }
+ }
val updateResult = localSource.saveUpdatedPosts(serverPosts, updateAction, tag)
resultListener.onUpdateResult(updateResult)
}
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 28cc9cf5600b..e8ac0d171079 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
@@ -85,6 +85,7 @@ class ReaderViewModel @Inject constructor(
private var wasPaused: Boolean = false
private var trackReaderTabJob: Job? = null
private var isQuickStartPromptShown: Boolean = false
+ private var pendingTabRequest: PendingTabRequest? = null
private val _uiState = MutableLiveData()
val uiState: LiveData = _uiState.distinct()
@@ -151,10 +152,22 @@ class ReaderViewModel @Inject constructor(
if (!initialized) {
initialized = true
}
+ applyPendingTabRequest()
}
}
}
+ private fun applyPendingTabRequest() {
+ pendingTabRequest?.let { request ->
+ val tag = when (request) {
+ PendingTabRequest.BOOKMARK -> readerTagsList.find { it.isBookmarked }
+ PendingTabRequest.DISCOVER -> readerTagsList.find { it.isDiscover }
+ }
+ tag?.let { updateSelectedContent(it) }
+ pendingTabRequest = null
+ }
+ }
+
fun onTagChanged(selectedTag: ReaderTag?) {
selectedTag?.let {
trackReaderTabShownIfNecessary(it)
@@ -204,8 +217,20 @@ class ReaderViewModel @Inject constructor(
}
fun bookmarkTabRequested() {
- readerTagsList.find { it.isBookmarked }?.let {
- updateSelectedContent(it)
+ val tag = readerTagsList.find { it.isBookmarked }
+ if (tag != null) {
+ updateSelectedContent(tag)
+ } else {
+ pendingTabRequest = PendingTabRequest.BOOKMARK
+ }
+ }
+
+ fun discoverTabRequested() {
+ val tag = readerTagsList.find { it.isDiscover }
+ if (tag != null) {
+ updateSelectedContent(tag)
+ } else {
+ pendingTabRequest = PendingTabRequest.DISCOVER
}
}
@@ -574,3 +599,8 @@ class ReaderViewModel @Inject constructor(
}
data class TabNavigation(val position: Int, val smoothAnimation: Boolean)
+
+enum class PendingTabRequest {
+ BOOKMARK,
+ DISCOVER
+}
diff --git a/WordPress/src/test/java/org/wordpress/android/ui/deeplinks/handlers/ReaderLinkHandlerTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/deeplinks/handlers/ReaderLinkHandlerTest.kt
index fe7936a1c266..49442eeef735 100644
--- a/WordPress/src/test/java/org/wordpress/android/ui/deeplinks/handlers/ReaderLinkHandlerTest.kt
+++ b/WordPress/src/test/java/org/wordpress/android/ui/deeplinks/handlers/ReaderLinkHandlerTest.kt
@@ -9,8 +9,12 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.wordpress.android.BaseUnitTest
import org.wordpress.android.analytics.AnalyticsTracker.Stat.READER_VIEWPOST_INTERCEPTED
+import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenFeedInReader
import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenInReader
import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenReader
+import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenReaderDiscover
+import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenReaderSearch
+import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.OpenTagInReader
import org.wordpress.android.ui.deeplinks.DeepLinkNavigator.NavigateAction.ViewPostInReader
import org.wordpress.android.ui.deeplinks.buildUri
import org.wordpress.android.ui.reader.ReaderConstants
@@ -28,6 +32,7 @@ class ReaderLinkHandlerTest : BaseUnitTest() {
val blogId: Long = 111
val postId: Long = 222
val feedId: Long = 333
+ val tagSlug: String = "dogs"
@Before
fun setUp() {
@@ -224,4 +229,175 @@ class ReaderLinkHandlerTest : BaseUnitTest() {
assertThat(strippedUrl).isEqualTo("www.wordpress.com/read")
}
+
+ @Test
+ fun `handles wordpress com read path`() {
+ val uri = buildUri("wordpress.com", "read")
+
+ val isReaderUri = readerLinkHandler.shouldHandleUrl(uri)
+
+ assertThat(isReaderUri).isTrue()
+ }
+
+ @Test
+ fun `handles wordpress com discover path`() {
+ val uri = buildUri("wordpress.com", "discover")
+
+ val isReaderUri = readerLinkHandler.shouldHandleUrl(uri)
+
+ assertThat(isReaderUri).isTrue()
+ }
+
+ @Test
+ fun `wordpress com read opens reader`() {
+ val uri = buildUri("wordpress.com", "read")
+
+ val navigateAction = readerLinkHandler.buildNavigateAction(uri)
+
+ assertThat(navigateAction).isEqualTo(OpenReader)
+ }
+
+ @Test
+ fun `wordpress com discover opens reader discover`() {
+ val uri = buildUri("wordpress.com", "discover")
+
+ val navigateAction = readerLinkHandler.buildNavigateAction(uri)
+
+ assertThat(navigateAction).isEqualTo(OpenReaderDiscover)
+ }
+
+ @Test
+ fun `correctly strips wordpress com read URI`() {
+ val uri = buildUri("wordpress.com", "read")
+
+ val strippedUrl = readerLinkHandler.stripUrl(uri)
+
+ assertThat(strippedUrl).isEqualTo("wordpress.com/read")
+ }
+
+ @Test
+ fun `correctly strips wordpress com discover URI`() {
+ val uri = buildUri("wordpress.com", "discover")
+
+ val strippedUrl = readerLinkHandler.stripUrl(uri)
+
+ assertThat(strippedUrl).isEqualTo("wordpress.com/discover")
+ }
+
+ @Test
+ fun `handles wordpress com read feeds path`() {
+ val uri = buildUri("wordpress.com", "read", "feeds", feedId.toString())
+
+ val isReaderUri = readerLinkHandler.shouldHandleUrl(uri)
+
+ assertThat(isReaderUri).isTrue()
+ }
+
+ @Test
+ fun `handles wordpress com reader feeds path`() {
+ val uri = buildUri("wordpress.com", "reader", "feeds", feedId.toString())
+
+ val isReaderUri = readerLinkHandler.shouldHandleUrl(uri)
+
+ assertThat(isReaderUri).isTrue()
+ }
+
+ @Test
+ fun `wordpress com read feeds opens feed in reader`() {
+ val uri = buildUri("wordpress.com", "read", "feeds", feedId.toString())
+
+ val navigateAction = readerLinkHandler.buildNavigateAction(uri)
+
+ assertThat(navigateAction).isEqualTo(OpenFeedInReader(feedId))
+ }
+
+ @Test
+ fun `wordpress com reader feeds opens feed in reader`() {
+ val uri = buildUri("wordpress.com", "reader", "feeds", feedId.toString())
+
+ val navigateAction = readerLinkHandler.buildNavigateAction(uri)
+
+ assertThat(navigateAction).isEqualTo(OpenFeedInReader(feedId))
+ }
+
+ @Test
+ fun `correctly strips wordpress com feed URI`() {
+ val uri = buildUri("wordpress.com", "read", "feeds", feedId.toString())
+
+ val strippedUrl = readerLinkHandler.stripUrl(uri)
+
+ assertThat(strippedUrl).isEqualTo("wordpress.com/read/feeds/feedId")
+ }
+
+ @Test
+ fun `handles wordpress com read search path`() {
+ val uri = buildUri("wordpress.com", "read", "search")
+
+ val isReaderUri = readerLinkHandler.shouldHandleUrl(uri)
+
+ assertThat(isReaderUri).isTrue()
+ }
+
+ @Test
+ fun `wordpress com read search opens reader search`() {
+ val uri = buildUri("wordpress.com", "read", "search")
+
+ val navigateAction = readerLinkHandler.buildNavigateAction(uri)
+
+ assertThat(navigateAction).isEqualTo(OpenReaderSearch)
+ }
+
+ @Test
+ fun `correctly strips wordpress com read search URI`() {
+ val uri = buildUri("wordpress.com", "read", "search")
+
+ val strippedUrl = readerLinkHandler.stripUrl(uri)
+
+ assertThat(strippedUrl).isEqualTo("wordpress.com/read/search")
+ }
+
+ @Test
+ fun `handles wordpress com reader search path`() {
+ val uri = buildUri("wordpress.com", "reader", "search")
+
+ val isReaderUri = readerLinkHandler.shouldHandleUrl(uri)
+
+ assertThat(isReaderUri).isTrue()
+ }
+
+ @Test
+ fun `wordpress com reader search opens reader search`() {
+ val uri = buildUri("wordpress.com", "reader", "search")
+
+ val navigateAction = readerLinkHandler.buildNavigateAction(uri)
+
+ assertThat(navigateAction).isEqualTo(OpenReaderSearch)
+ }
+
+ @Test
+ fun `handles wordpress com tag path`() {
+ val uri = buildUri("wordpress.com", "tag", tagSlug)
+
+ val isReaderUri = readerLinkHandler.shouldHandleUrl(uri)
+
+ assertThat(isReaderUri).isTrue()
+ }
+
+ @Test
+ fun `wordpress com tag opens tag in reader`() {
+ val uri = buildUri("wordpress.com", "tag", tagSlug)
+
+ val navigateAction = readerLinkHandler.buildNavigateAction(uri)
+
+ assertThat(navigateAction).isEqualTo(OpenTagInReader(tagSlug))
+ }
+
+ @Test
+ fun `correctly strips wordpress com tag URI`() {
+ val uri = buildUri("wordpress.com", "tag", tagSlug)
+
+ val strippedUrl = readerLinkHandler.stripUrl(uri)
+
+ assertThat(strippedUrl).isEqualTo("wordpress.com/tag/tagSlug")
+ }
}
diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/ReaderActivityLauncherTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/ReaderActivityLauncherTest.kt
new file mode 100644
index 000000000000..e0752d4bad9e
--- /dev/null
+++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/ReaderActivityLauncherTest.kt
@@ -0,0 +1,95 @@
+package org.wordpress.android.ui.reader
+
+import androidx.test.core.app.ApplicationProvider
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.wordpress.android.models.ReaderTag
+import org.wordpress.android.ui.reader.ReaderTypes.ReaderPostListType
+
+@RunWith(RobolectricTestRunner::class)
+@Config(application = android.app.Application::class)
+class ReaderActivityLauncherTest {
+ private val context = ApplicationProvider.getApplicationContext()
+ private val feedId = 12345L
+ private val tagSlug = "dogs"
+ private val source = "deeplink"
+
+ @Test
+ fun `buildReaderFeedIntent creates intent with correct feed id`() {
+ val intent = ReaderActivityLauncher.buildReaderFeedIntent(context, feedId, source)
+
+ assertThat(intent.getLongExtra(ReaderConstants.ARG_FEED_ID, 0L)).isEqualTo(feedId)
+ }
+
+ @Test
+ fun `buildReaderFeedIntent creates intent with is feed flag set to true`() {
+ val intent = ReaderActivityLauncher.buildReaderFeedIntent(context, feedId, source)
+
+ assertThat(intent.getBooleanExtra(ReaderConstants.ARG_IS_FEED, false)).isTrue()
+ }
+
+ @Test
+ fun `buildReaderFeedIntent creates intent with correct source`() {
+ val intent = ReaderActivityLauncher.buildReaderFeedIntent(context, feedId, source)
+
+ assertThat(intent.getStringExtra(ReaderConstants.ARG_SOURCE)).isEqualTo(source)
+ }
+
+ @Test
+ fun `buildReaderFeedIntent creates intent with blog preview list type`() {
+ val intent = ReaderActivityLauncher.buildReaderFeedIntent(context, feedId, source)
+
+ @Suppress("DEPRECATION")
+ val listType = intent.getSerializableExtra(ReaderConstants.ARG_POST_LIST_TYPE) as ReaderPostListType
+ assertThat(listType).isEqualTo(ReaderPostListType.BLOG_PREVIEW)
+ }
+
+ @Test
+ fun `buildReaderFeedIntent creates intent for ReaderPostListActivity`() {
+ val intent = ReaderActivityLauncher.buildReaderFeedIntent(context, feedId, source)
+
+ assertThat(intent.component?.className).isEqualTo(ReaderPostListActivity::class.java.name)
+ }
+
+ @Test
+ fun `buildReaderTagIntent creates intent with correct tag slug`() {
+ val intent = ReaderActivityLauncher.buildReaderTagIntent(context, tagSlug, source)
+
+ @Suppress("DEPRECATION")
+ val tag = intent.getSerializableExtra(ReaderConstants.ARG_TAG) as ReaderTag
+ assertThat(tag.tagSlug).isEqualTo(tagSlug)
+ }
+
+ @Test
+ fun `buildReaderTagIntent creates intent with correct source`() {
+ val intent = ReaderActivityLauncher.buildReaderTagIntent(context, tagSlug, source)
+
+ assertThat(intent.getStringExtra(ReaderConstants.ARG_SOURCE)).isEqualTo(source)
+ }
+
+ @Test
+ fun `buildReaderTagIntent creates intent with tag preview list type`() {
+ val intent = ReaderActivityLauncher.buildReaderTagIntent(context, tagSlug, source)
+
+ @Suppress("DEPRECATION")
+ val listType = intent.getSerializableExtra(ReaderConstants.ARG_POST_LIST_TYPE) as ReaderPostListType
+ assertThat(listType).isEqualTo(ReaderPostListType.TAG_PREVIEW)
+ }
+
+ @Test
+ fun `buildReaderTagIntent creates intent for ReaderPostListActivity`() {
+ val intent = ReaderActivityLauncher.buildReaderTagIntent(context, tagSlug, source)
+
+ assertThat(intent.component?.className).isEqualTo(ReaderPostListActivity::class.java.name)
+ }
+
+ @Test
+ fun `createReaderSearchIntent creates intent for ReaderSearchActivity`() {
+ val intent = ReaderActivityLauncher.createReaderSearchIntent(context)
+
+ assertThat(intent.component?.className).isEqualTo(ReaderSearchActivity::class.java.name)
+ }
+}