diff --git a/WordPress/jetpack_metadata/PlayStoreStrings.po b/WordPress/jetpack_metadata/PlayStoreStrings.po index 70e831a3281d..922329f02b85 100644 --- a/WordPress/jetpack_metadata/PlayStoreStrings.po +++ b/WordPress/jetpack_metadata/PlayStoreStrings.po @@ -10,6 +10,14 @@ msgstr "" "X-Generator: VsCode\n" "Project-Id-Version: Jetpack - Apps - Android - Release Notes\n" +msgctxt "release_note_250" +msgid "" +"25.0:\n" +"The Tags feed is live! You can now see content with specific tags, all in one place. Tag, you’re it.\n" +"\n" +"We fixed assorted crashes on the login and Posts List screens, as well as actions associated with blogging reminders, feature images, and user removal. Less crashing? How smashing.\n" +msgstr "" + msgctxt "release_note_249" msgid "" "24.9:\n" @@ -19,13 +27,6 @@ msgid "" "- Site names and URLs are properly positioned for right-to-left language users.\n" msgstr "" -msgctxt "release_note_248" -msgid "" -"24.8:\n" -"On the Stats screen, percentages now appear in their correct position for right-to-left language users. Right on.\n" -"\n" -msgstr "" - #. translators: Release notes for this version to be displayed in the Play Store. Limit to 500 characters including spaces and commas! #. translators: Title to be displayed in the Play Store. Limit to 30 characters including spaces and commas! msgctxt "play_store_app_title" diff --git a/WordPress/jetpack_metadata/release_notes.txt b/WordPress/jetpack_metadata/release_notes.txt index b5b9a502778e..928c57d6d98a 100644 --- a/WordPress/jetpack_metadata/release_notes.txt +++ b/WordPress/jetpack_metadata/release_notes.txt @@ -1,10 +1,3 @@ -* [*] Fixed a rare crash on Posts List screen [https://github.com/wordpress-mobile/WordPress-Android/pull/20813] -* [*] Fixed a rare crash on the Login screen [https://github.com/wordpress-mobile/WordPress-Android/pull/20821] -* [*] Fix a crash that occurs when remove a user [https://github.com/wordpress-mobile/WordPress-Android/pull/20837] -* [*] Fixed a rare crash on the featured image confirmation dialog [https://github.com/wordpress-mobile/WordPress-Android/pull/20836] -* [*] Fixed an ANR issue on the Post List screen [https://github.com/wordpress-mobile/WordPress-Android/pull/20833] -* [*] Fixed a crash that occurs with Blogging Reminders [https://github.com/wordpress-mobile/WordPress-Android/pull/20845] -* [*] [internal] Block Editor: Upgrade target sdk version to Android API 34 [https://github.com/wordpress-mobile/WordPress-Android/pull/20841] -* [*] [internal] In-app updates feature [https://github.com/wordpress-mobile/WordPress-Android/pull/20822] -* [**] [Jetpack-only] Reader: Add a new feed dedicated to tags [https://github.com/wordpress-mobile/WordPress-Android/pull/20812] +The Tags feed is live! You can now see content with specific tags, all in one place. Tag, you’re it. +We fixed assorted crashes on the login and Posts List screens, as well as actions associated with blogging reminders, feature images, and user removal. Less crashing? How smashing. diff --git a/WordPress/metadata/PlayStoreStrings.po b/WordPress/metadata/PlayStoreStrings.po index bbbc05be18df..6aae2baad674 100644 --- a/WordPress/metadata/PlayStoreStrings.po +++ b/WordPress/metadata/PlayStoreStrings.po @@ -10,16 +10,16 @@ msgstr "" "X-Generator: VsCode\n" "Project-Id-Version: Release Notes & Play Store Descriptions\n" -msgctxt "release_note_249" +msgctxt "release_note_250" msgid "" -"24.9:\n" -"Site names and URLs are now properly positioned for right-to-left language users. Right on.\n" +"25.0:\n" +"We fixed assorted crashes on the login and Posts List screens, as well as actions associated with blogging reminders, feature images, and user removal. Less crashing? How smashing.\n" msgstr "" -msgctxt "release_note_248" +msgctxt "release_note_249" msgid "" -"24.8:\n" -"April showers bring May flowers, but they unfortunately don’t bring release notes. Stay Juned for the next update.\n" +"24.9:\n" +"Site names and URLs are now properly positioned for right-to-left language users. Right on.\n" msgstr "" #. translators: Release notes for this version to be displayed in the Play Store. Limit to 500 characters including spaces and commas! diff --git a/WordPress/metadata/release_notes.txt b/WordPress/metadata/release_notes.txt index d479897db1ec..257e18b25fcd 100644 --- a/WordPress/metadata/release_notes.txt +++ b/WordPress/metadata/release_notes.txt @@ -1,9 +1 @@ -* [*] Fixed a rare crash on Posts List screen [https://github.com/wordpress-mobile/WordPress-Android/pull/20813] -* [*] Fixed a rare crash on the Login screen [https://github.com/wordpress-mobile/WordPress-Android/pull/20821] -* [*] Fix a crash that occurs when remove a user [https://github.com/wordpress-mobile/WordPress-Android/pull/20837] -* [*] Fixed a rare crash on the featured image confirmation dialog [https://github.com/wordpress-mobile/WordPress-Android/pull/20836] -* [*] Fixed an ANR issue on the Post List screen [https://github.com/wordpress-mobile/WordPress-Android/pull/20833] -* [*] Fixed a crash that occurs with Blogging Reminders [https://github.com/wordpress-mobile/WordPress-Android/pull/20845] -* [*] [internal] Block Editor: Upgrade target sdk version to Android API 34 [https://github.com/wordpress-mobile/WordPress-Android/pull/20841] -* [*] [internal] In-app updates feature [https://github.com/wordpress-mobile/WordPress-Android/pull/20822] - +We fixed assorted crashes on the login and Posts List screens, as well as actions associated with blogging reminders, feature images, and user removal. Less crashing? How smashing. diff --git a/WordPress/src/jetpack/assets/reader_text_events.js b/WordPress/src/jetpack/assets/reader_text_events.js new file mode 100644 index 000000000000..68cc132b5eeb --- /dev/null +++ b/WordPress/src/jetpack/assets/reader_text_events.js @@ -0,0 +1,26 @@ +/* + * wvHandler is the name of the message handler in the webview, added via createJsObject inside + * ReaderPostRenderer. It handles the `postMessage` calls from the WebView. + */ + +function debounce(fn, timeout) { + let timer; + return () => { + clearTimeout(timer); + timer = setTimeout(fn, timeout); + } +} + +const textHighlighted = debounce( + () => wvHandler.postMessage("articleTextHighlighted"), + 1000 +); + +document.addEventListener('selectionchange', function(event) { + const selection = document.getSelection().toString(); + if (selection.length > 0) { + textHighlighted(); + } +}); + +document.addEventListener('copy', event => wvHandler.postMessage("articleTextCopied")); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostDetailFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostDetailFragment.kt index 56fa4dcf5df6..12898f1f38de 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostDetailFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostDetailFragment.kt @@ -94,6 +94,7 @@ import org.wordpress.android.ui.pages.SnackbarMessageHolder import org.wordpress.android.ui.reader.ReaderActivityLauncher.OpenUrlType import org.wordpress.android.ui.reader.ReaderActivityLauncher.PhotoViewerOption import org.wordpress.android.ui.reader.ReaderPostPagerActivity.DirectOperation +import org.wordpress.android.ui.reader.ReaderPostRenderer.ReaderPostMessageListener import org.wordpress.android.ui.reader.ReaderTypes.ReaderPostListType import org.wordpress.android.ui.reader.actions.ReaderActions import org.wordpress.android.ui.reader.actions.ReaderPostActions @@ -1607,7 +1608,17 @@ class ReaderPostDetailFragment : ViewPagerFragment(), viewModel.post, readerCssProvider, getReadingPreferences() - ) + ).also { + it.setPostMessageListener(object : ReaderPostMessageListener { + override fun onArticleTextCopied() { + viewModel.onArticleTextCopied() + } + + override fun onArticleTextHighlighted() { + viewModel.onArticleTextHighlighted() + } + }) + } // if the post is from private atomic site postpone render until we have a special access cookie if (post.isPrivateAtomic && privateAtomicCookie.isCookieRefreshRequired()) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostRenderer.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostRenderer.java index 2ac457f79167..c7157a096ebe 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostRenderer.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostRenderer.java @@ -3,14 +3,17 @@ import android.annotation.SuppressLint; import android.net.Uri; import android.os.Handler; +import android.webkit.WebView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.jsoup.Jsoup; import org.wordpress.android.R; import org.wordpress.android.WordPress; import org.wordpress.android.models.ReaderPost; import org.wordpress.android.models.ReaderPostDiscoverData; +import org.wordpress.android.support.JsObjectKt; import org.wordpress.android.ui.reader.models.ReaderReadingPreferences; import org.wordpress.android.ui.reader.models.ReaderReadingPreferences.ThemeValues; import org.wordpress.android.ui.reader.utils.ImageSizeMap; @@ -44,6 +47,7 @@ * http://developer.android.com/guide/webapps/targeting.html */ public class ReaderPostRenderer { + private static final String JAVASCRIPT_MESSAGE_HANDLER = "wvHandler"; private final ReaderResourceVars mResourceVars; private final ReaderPost mPost; private final int mMinFullSizeWidthDp; @@ -56,6 +60,8 @@ public class ReaderPostRenderer { private ReaderCssProvider mCssProvider; private ReaderReadingPreferences mReadingPreferences; private ReaderReadingPreferences.ThemeValues mReadingPreferencesTheme; + @Nullable + private ReaderPostMessageListener mPostMessageListener = null; @SuppressLint("SetJavaScriptEnabled") public ReaderPostRenderer(ReaderWebView webView, ReaderPost post, ReaderCssProvider cssProvider, @@ -80,6 +86,7 @@ public ReaderPostRenderer(ReaderWebView webView, ReaderPost post, ReaderCssProvi // enable JavaScript in the webView, otherwise videos and other embedded content won't // work - note that the content is scrubbed on the backend so this is considered safe webView.getSettings().setJavaScriptEnabled(true); + setWebViewMessageHandler(webView); } public void beginRender() { @@ -543,6 +550,7 @@ private String formatPostContentForWebView(final String content, final Set") + .append("") .append(contentCustomised) .append(""); @@ -635,4 +643,39 @@ private String getContentTextProperties() { + "font-weight: 400; " + "font-size: " + mReadingPreferences.getFontSize().getValue() + "px; "; } + + private void setWebViewMessageHandler(@NonNull WebView webView) { + Set allowedOrigins = new HashSet<>(); + allowedOrigins.add("*"); + + JsObjectKt.createJsObject( + webView, JAVASCRIPT_MESSAGE_HANDLER, allowedOrigins, + (message) -> { + if (mPostMessageListener == null) { + return null; + } + + switch (message) { + case ReaderPostMessageListener.MSG_ARTICLE_TEXT_COPIED: + mPostMessageListener.onArticleTextCopied(); + break; + case ReaderPostMessageListener.MSG_ARTICLE_TEXT_HIGHLIGHTED: + mPostMessageListener.onArticleTextHighlighted(); + break; + } + return null; + }); + } + + void setPostMessageListener(@Nullable ReaderPostMessageListener listener) { + mPostMessageListener = listener; + } + + interface ReaderPostMessageListener { + String MSG_ARTICLE_TEXT_COPIED = "articleTextCopied"; + String MSG_ARTICLE_TEXT_HIGHLIGHTED = "articleTextHighlighted"; + + void onArticleTextCopied(); + void onArticleTextHighlighted(); + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderTagsFeedFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderTagsFeedFragment.kt index 531c9742fc38..22cc052a1c61 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderTagsFeedFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderTagsFeedFragment.kt @@ -15,6 +15,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.core.view.ViewCompat.animate import androidx.core.view.isVisible +import androidx.fragment.app.Fragment import androidx.fragment.app.commitNow import androidx.fragment.app.viewModels import androidx.lifecycle.ViewModelProvider @@ -26,7 +27,6 @@ import org.wordpress.android.analytics.AnalyticsTracker import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.databinding.ReaderTagFeedFragmentLayoutBinding import org.wordpress.android.models.ReaderTag -import org.wordpress.android.ui.ViewPagerFragment import org.wordpress.android.ui.compose.theme.AppThemeWithoutBackground import org.wordpress.android.ui.main.WPMainActivity import org.wordpress.android.ui.reader.adapters.ReaderMenuAdapter @@ -55,7 +55,7 @@ import javax.inject.Inject * main content of the ReaderFragment (e.g.: initializing the SubFilterViewModel), so a few changes might be needed. */ @AndroidEntryPoint -class ReaderTagsFeedFragment : ViewPagerFragment(R.layout.reader_tag_feed_fragment_layout), +class ReaderTagsFeedFragment : Fragment(R.layout.reader_tag_feed_fragment_layout), WPMainActivity.OnScrollToTopListener { private val tagsFeedTag by lazy { // TODO maybe we can just create a static function somewhere that returns the Tags Feed ReaderTag, since it's @@ -374,10 +374,6 @@ class ReaderTagsFeedFragment : ViewPagerFragment(R.layout.reader_tag_feed_fragme } } - override fun getScrollableViewForUniqueIdProvision(): View { - return binding.composeView - } - override fun onScrollToTop() { // TODO scroll current content to top } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderPostDetailViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderPostDetailViewModel.kt index 05aeae289c44..884564b2d79d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderPostDetailViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/viewmodels/ReaderPostDetailViewModel.kt @@ -13,7 +13,6 @@ import kotlinx.coroutines.withContext import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode.MAIN import org.wordpress.android.R -import org.wordpress.android.analytics.AnalyticsTracker import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.datasets.wrappers.ReaderCommentTableWrapper import org.wordpress.android.datasets.wrappers.ReaderPostTableWrapper @@ -587,9 +586,9 @@ class ReaderPostDetailViewModel @Inject constructor( private fun trackRelatedPostClickAction(postId: Long, blogId: Long, isGlobal: Boolean) { val stat = if (isGlobal) { - AnalyticsTracker.Stat.READER_GLOBAL_RELATED_POST_CLICKED + Stat.READER_GLOBAL_RELATED_POST_CLICKED } else { - AnalyticsTracker.Stat.READER_LOCAL_RELATED_POST_CLICKED + Stat.READER_LOCAL_RELATED_POST_CLICKED } readerTracker.trackPost(stat, findPost(blogId, postId)) } @@ -625,7 +624,7 @@ class ReaderPostDetailViewModel @Inject constructor( private fun updatePostDetailsUi() { post?.let { - readerTracker.trackPost(AnalyticsTracker.Stat.READER_ARTICLE_RENDERED, it) + readerTracker.trackPost(Stat.READER_ARTICLE_RENDERED, it) _navigationEvents.postValue(Event(ShowPostInWebView(it))) _uiState.value = convertPostToUiState(it) } @@ -687,9 +686,9 @@ class ReaderPostDetailViewModel @Inject constructor( private fun trackNotAuthorisedState() { if (shouldOfferSignIn) { - post?.let { readerTracker.trackPost(AnalyticsTracker.Stat.READER_WPCOM_SIGN_IN_NEEDED, it) } + post?.let { readerTracker.trackPost(Stat.READER_WPCOM_SIGN_IN_NEEDED, it) } } - post?.let { readerTracker.trackPost(AnalyticsTracker.Stat.READER_USER_UNAUTHORIZED, it) } + post?.let { readerTracker.trackPost(Stat.READER_USER_UNAUTHORIZED, it) } } private fun getNotAuthorisedErrorMessageRes() = if (!shouldOfferSignIn) { @@ -1012,6 +1011,14 @@ class ReaderPostDetailViewModel @Inject constructor( } } + fun onArticleTextCopied() { + readerTracker.track(Stat.READER_ARTICLE_TEXT_COPIED) + } + + fun onArticleTextHighlighted() { + readerTracker.track(Stat.READER_ARTICLE_TEXT_HIGHLIGHTED) + } + companion object { @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) const val MAX_NUM_LIKES_FACES_WITH_SELF = 6 diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeed.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeed.kt index 7b30f1f0aa0b..587c22cca418 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeed.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/views/compose/tagsfeed/ReaderTagsFeed.kt @@ -43,6 +43,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.clearAndSetSemantics @@ -98,7 +100,6 @@ private fun Loaded(uiState: UiState.Loaded) { uiState.onRefresh() } ) - Box( modifier = Modifier .fillMaxSize() @@ -106,7 +107,8 @@ private fun Loaded(uiState: UiState.Loaded) { ) { LazyColumn( modifier = Modifier - .fillMaxSize(), + .fillMaxSize() + .nestedScroll(rememberNestedScrollInteropConnection()), ) { uiState.announcementItem?.let { announcementItem -> item(key = "reader-announcement-card") { diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/viewmodels/ReaderPostDetailViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/viewmodels/ReaderPostDetailViewModelTest.kt index a2a7ea471207..ee39aae42d1c 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/viewmodels/ReaderPostDetailViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/viewmodels/ReaderPostDetailViewModelTest.kt @@ -27,6 +27,7 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.wordpress.android.BaseUnitTest import org.wordpress.android.R +import org.wordpress.android.analytics.AnalyticsTracker import org.wordpress.android.datasets.wrappers.ReaderCommentTableWrapper import org.wordpress.android.datasets.wrappers.ReaderPostTableWrapper import org.wordpress.android.fluxc.model.AccountModel @@ -1100,6 +1101,18 @@ class ReaderPostDetailViewModelTest : BaseUnitTest() { ) } + @Test + fun `onArticleTextCopied tracks the reader_article_text_copied event`() { + viewModel.onArticleTextCopied() + verify(readerTracker).track(AnalyticsTracker.Stat.READER_ARTICLE_TEXT_COPIED) + } + + @Test + fun `onArticleTextHighlighted tracks the reader_article_text_highlighted event`() { + viewModel.onArticleTextHighlighted() + verify(readerTracker).track(AnalyticsTracker.Stat.READER_ARTICLE_TEXT_HIGHLIGHTED) + } + private fun testWithoutLocalPost(block: suspend CoroutineScope.() -> T) { test { whenever(readerGetPostUseCase.get(any(), any(), any())).thenReturn(Pair(null, false)) diff --git a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java index 83135f79892e..699f1078283e 100644 --- a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java +++ b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java @@ -42,6 +42,10 @@ public enum Stat { READER_ARTICLE_DETAIL_UNLIKED, READER_ARTICLE_RENDERED, READER_ARTICLE_VISITED, + READER_ARTICLE_TEXT_HIGHLIGHTED, + READER_ARTICLE_TEXT_COPIED, + READER_COMMENT_TEXT_HIGHLIGHTED, + READER_COMMENT_TEXT_COPIED, READER_USER_BLOCKED, READER_BLOG_BLOCKED, READER_BLOG_FOLLOWED("reader_site_followed"),