diff --git a/WordPress/jetpack_metadata/PlayStoreStrings.po b/WordPress/jetpack_metadata/PlayStoreStrings.po index 486ae0460084..46c0d8b9741a 100644 --- a/WordPress/jetpack_metadata/PlayStoreStrings.po +++ b/WordPress/jetpack_metadata/PlayStoreStrings.po @@ -10,6 +10,13 @@ msgstr "" "X-Generator: VsCode\n" "Project-Id-Version: Jetpack - Apps - Android - Release Notes\n" +msgctxt "release_note_243" +msgid "" +"24.3:\n" +"Log in to your site with Google credentials, physical devices, and third-party passkeys. Don’t worry, the site’s still locked down tighter than the Colonel’s secret recipe.\n" +"We hid the menu icon on Stats screens so it’s only visible from the Insights tab. Ninja mode: activated.\n" +msgstr "" + msgctxt "release_note_242" msgid "" "24.2:\n" @@ -19,17 +26,6 @@ msgid "" "- We added new Site Monitoring menu items like metrics, PHP logs, and web server logs.\n" msgstr "" -msgctxt "release_note_241" -msgid "" -"24.1:\n" -"- Get notified when you’re working offline.\n" -"- Image block uploads pause/resume with internet connection.\n" -"- See custom gradient selections in the editor.\n" -"- Fixed forward/back arrows for right-to-left readers.\n" -"- Daily Prompt tags work properly.\n" -"- Tap Site Domain cards to manage domains.\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 be8790a36766..17d689ccd4c5 100644 --- a/WordPress/jetpack_metadata/release_notes.txt +++ b/WordPress/jetpack_metadata/release_notes.txt @@ -1,3 +1,2 @@ -* [**] Added support to use third-party passkey providers and other devices passkeys as a WordPress.com login option [https://github.com/wordpress-mobile/WordPress-Android/pull/20174] -* [*] [Jetpack-only] Fix the visibility issue with the menu button on the stats [https://github.com/wordpress-mobile/WordPress-Android/pull/20175] - +Log in to your site with Google credentials, physical devices, and third-party passkeys. Don’t worry, the site’s still locked down tighter than the Colonel’s secret recipe. +We hid the menu icon on Stats screens so it’s only visible from the Insights tab. Ninja mode: activated. diff --git a/WordPress/metadata/PlayStoreStrings.po b/WordPress/metadata/PlayStoreStrings.po index c7dffa2f5f85..9e2282892dec 100644 --- a/WordPress/metadata/PlayStoreStrings.po +++ b/WordPress/metadata/PlayStoreStrings.po @@ -10,6 +10,12 @@ msgstr "" "X-Generator: VsCode\n" "Project-Id-Version: Release Notes & Play Store Descriptions\n" +msgctxt "release_note_243" +msgid "" +"24.3:\n" +"You can log in to your WordPress website using Google credentials, physical devices, and other third-party passkeys. Don’t worry, your site is still locked down tighter than the Colonel’s secret recipe.\n" +msgstr "" + msgctxt "release_note_242" msgid "" "24.2:\n" @@ -18,15 +24,6 @@ msgid "" "The editor won’t crash anymore when you’re working on large posts. That’s right, we’ve saved your drafts and your sanity.\n" msgstr "" -msgctxt "release_note_241" -msgid "" -"24.1:\n" -"- Get notified when you’re working offline.\n" -"- Image block uploads pause when you lose internet and resume when you reconnect.\n" -"- Select a custom gradient in the editor and see a color indicator.\n" -"- “Forward” and “back” arrows are correct for right-to-left readers.\n" -msgstr "" - #. translators: Release notes for this version to be displayed in the Play Store. Limit to 500 characters including spaces and commas! #. translators: Shorter Release notes for this version to be displayed in the Play Store. Limit to 500 characters including spaces and commas! msgctxt "sample_post_content" diff --git a/WordPress/metadata/release_notes.txt b/WordPress/metadata/release_notes.txt index d16d3ea03efd..89c2c07ee6ac 100644 --- a/WordPress/metadata/release_notes.txt +++ b/WordPress/metadata/release_notes.txt @@ -1,2 +1 @@ -* [**] Added support to use third-party passkey providers and other devices passkeys as a WordPress.com login option [https://github.com/wordpress-mobile/WordPress-Android/pull/20174] - +You can log in to your WordPress website using Google credentials, physical devices, and other third-party passkeys. Don’t worry, your site is still locked down tighter than the Colonel’s secret recipe. diff --git a/WordPress/src/main/java/org/wordpress/android/datasets/ReaderTagTable.java b/WordPress/src/main/java/org/wordpress/android/datasets/ReaderTagTable.java index 977f7eaf0f2e..7c7530756b12 100644 --- a/WordPress/src/main/java/org/wordpress/android/datasets/ReaderTagTable.java +++ b/WordPress/src/main/java/org/wordpress/android/datasets/ReaderTagTable.java @@ -232,6 +232,10 @@ public static ReaderTagList getBookmarkTags() { return getTagsOfType(ReaderTagType.BOOKMARKED); } + public static ReaderTagList getDiscoverPostCardsTags() { + return getTagsOfType(ReaderTagType.DISCOVER_POST_CARDS); + } + private static ReaderTagList getTagsOfType(ReaderTagType tagType) { String[] args = {Integer.toString(tagType.toInt())}; Cursor c = ReaderDatabase.getReadableDb() diff --git a/WordPress/src/main/java/org/wordpress/android/models/ReaderTagList.java b/WordPress/src/main/java/org/wordpress/android/models/ReaderTagList.java index 6a9633c44441..12da31783833 100644 --- a/WordPress/src/main/java/org/wordpress/android/models/ReaderTagList.java +++ b/WordPress/src/main/java/org/wordpress/android/models/ReaderTagList.java @@ -44,6 +44,8 @@ public boolean isSameList(ReaderTagList otherList) { return false; } else if (!otherTag.getTagTitle().equals(this.get(i).getTagTitle())) { return false; + } else if (!otherTag.getTagDisplayName().equals(this.get(i).getTagDisplayName())) { + return false; } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 4869d87b7aa3..d739a18b867b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -3934,7 +3934,12 @@ public void onMediaModelsCreatedFromOptimizedUris(@NonNull Map followed tags changed"); getPageAdapter().refreshFollowedTagFragment(); } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderTagActions.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderTagActions.java index 9577553148fa..8e9ec0895f73 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderTagActions.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderTagActions.java @@ -13,7 +13,7 @@ import org.wordpress.android.models.ReaderTagList; import org.wordpress.android.models.ReaderTagType; import org.wordpress.android.ui.reader.ReaderConstants; -import org.wordpress.android.ui.reader.ReaderEvents; +import org.wordpress.android.ui.reader.ReaderEvents.FollowedTagsFetched; import org.wordpress.android.ui.reader.actions.ReaderActions.ActionListener; import org.wordpress.android.ui.reader.utils.ReaderUtils; import org.wordpress.android.util.AppLog; @@ -51,7 +51,7 @@ public static boolean deleteTag(final ReaderTag tag, private static boolean deleteTagsLocallyOnly(ActionListener actionListener, ReaderTag tag) { ReaderTagTable.deleteTag(tag); ReaderActions.callActionListener(actionListener, true); - EventBus.getDefault().post(new ReaderEvents.FollowedTagsChanged(true)); + EventBus.getDefault().post(new FollowedTagsFetched(true)); return true; } @@ -130,7 +130,7 @@ public static boolean addTags(@NonNull final List tags, private static boolean saveTagsLocallyOnly(ActionListener actionListener, ReaderTagList newTags) { ReaderTagTable.addOrUpdateTags(newTags); ReaderActions.callActionListener(actionListener, true); - EventBus.getDefault().post(new ReaderEvents.FollowedTagsChanged(true)); + EventBus.getDefault().post(new FollowedTagsFetched(true)); return true; } @@ -147,7 +147,7 @@ private static boolean saveTagsLocallyAndRemotely(ActionListener actionListener, if (actionListener != null) { ReaderActions.callActionListener(actionListener, true); } - EventBus.getDefault().post(new ReaderEvents.FollowedTagsChanged(true)); + EventBus.getDefault().post(new FollowedTagsFetched(true)); }; RestRequest.ErrorListener errorListener = volleyError -> { @@ -159,7 +159,7 @@ private static boolean saveTagsLocallyAndRemotely(ActionListener actionListener, if (actionListener != null) { ReaderActions.callActionListener(actionListener, true); } - EventBus.getDefault().post(new ReaderEvents.FollowedTagsChanged(true)); + EventBus.getDefault().post(new FollowedTagsFetched(true)); return; } @@ -171,7 +171,7 @@ private static boolean saveTagsLocallyAndRemotely(ActionListener actionListener, if (actionListener != null) { ReaderActions.callActionListener(actionListener, false); } - EventBus.getDefault().post(new ReaderEvents.FollowedTagsChanged(false)); + EventBus.getDefault().post(new FollowedTagsFetched(false)); }; ReaderTagTable.addOrUpdateTags(newTags); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt index a1a20185e26d..27b8cb35b031 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProvider.kt @@ -14,7 +14,7 @@ import org.wordpress.android.models.discover.ReaderDiscoverCards import org.wordpress.android.modules.IO_THREAD import org.wordpress.android.modules.UI_THREAD import org.wordpress.android.ui.reader.ReaderEvents.FetchDiscoverCardsEnded -import org.wordpress.android.ui.reader.ReaderEvents.FollowedTagsChanged +import org.wordpress.android.ui.reader.ReaderEvents.FollowedTagsFetched import org.wordpress.android.ui.reader.actions.ReaderActions.UpdateResult.CHANGED import org.wordpress.android.ui.reader.actions.ReaderActions.UpdateResult.FAILED import org.wordpress.android.ui.reader.actions.ReaderActions.UpdateResult.HAS_NEW @@ -211,7 +211,7 @@ class ReaderDiscoverDataProvider @Inject constructor( @Suppress("unused", "UNUSED_PARAMETER") @Subscribe(threadMode = BACKGROUND) - fun onFollowedTagsChanged(event: FollowedTagsChanged) { + fun onFollowedTagsFetched(event: FollowedTagsFetched) { launch { refreshCards() } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/tags/FetchFollowedTagsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/tags/FetchFollowedTagsUseCase.kt index 3afd6a0d2cf2..1d109666337d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/tags/FetchFollowedTagsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/tags/FetchFollowedTagsUseCase.kt @@ -2,7 +2,7 @@ package org.wordpress.android.ui.reader.repository.usecases.tags import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode -import org.wordpress.android.ui.reader.ReaderEvents.FollowedTagsChanged +import org.wordpress.android.ui.reader.ReaderEvents.FollowedTagsFetched import org.wordpress.android.ui.reader.repository.ReaderRepositoryCommunication import org.wordpress.android.ui.reader.repository.ReaderRepositoryCommunication.Error.NetworkUnavailable import org.wordpress.android.ui.reader.repository.ReaderRepositoryCommunication.Error.RemoteRequestFailure @@ -48,7 +48,7 @@ class FetchFollowedTagsUseCase @Inject constructor( @Suppress("unused") @Subscribe(threadMode = ThreadMode.BACKGROUND) - fun onFollowedTagsChanged(event: FollowedTagsChanged) { + fun onFollowedTagsFetched(event: FollowedTagsFetched) { val result = if (event.didSucceed()) { Success } else { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/tags/FollowInterestTagsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/tags/FollowInterestTagsUseCase.kt index 3002e258d450..8e171e262261 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/tags/FollowInterestTagsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/repository/usecases/tags/FollowInterestTagsUseCase.kt @@ -4,7 +4,7 @@ import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import org.wordpress.android.fluxc.store.AccountStore import org.wordpress.android.models.ReaderTag -import org.wordpress.android.ui.reader.ReaderEvents.FollowedTagsChanged +import org.wordpress.android.ui.reader.ReaderEvents.FollowedTagsFetched import org.wordpress.android.ui.reader.actions.ReaderTagActions import org.wordpress.android.ui.reader.repository.ReaderRepositoryCommunication import org.wordpress.android.ui.reader.repository.ReaderRepositoryCommunication.Error.NetworkUnavailable @@ -43,7 +43,7 @@ class FollowInterestTagsUseCase @Inject constructor( @Suppress("unused") @Subscribe(threadMode = ThreadMode.BACKGROUND) - fun onFollowedTagsChanged(event: FollowedTagsChanged) { + fun onFollowedTagsFetched(event: FollowedTagsFetched) { val result = if (event.didSucceed()) { Success } else { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/services/update/ReaderUpdateLogic.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/services/update/ReaderUpdateLogic.java index cde09194380f..e743d7f52bfa 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/reader/services/update/ReaderUpdateLogic.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/services/update/ReaderUpdateLogic.java @@ -24,6 +24,7 @@ import org.wordpress.android.ui.prefs.AppPrefs; import org.wordpress.android.ui.reader.ReaderConstants; import org.wordpress.android.ui.reader.ReaderEvents; +import org.wordpress.android.ui.reader.ReaderEvents.FollowedTagsFetched; import org.wordpress.android.ui.reader.ReaderEvents.InterestTagsFetchEnded; import org.wordpress.android.ui.reader.services.ServiceCompletionListener; import org.wordpress.android.util.AppLog; @@ -122,24 +123,21 @@ public void onErrorResponse(VolleyError volleyError) { .get("read/menu", params, null, listener, errorListener); } - private boolean displayNameUpdateWasNeeded(ReaderTagList serverTopics) { - boolean updateDone = false; - + /** + * Update the display names of the default tags (such as Subscribed and Discover) in the serverTopics list. + * + * @param serverTopics The list of default tags. + */ + private void updateDisplayNamesIfNeeded(@NonNull ReaderTagList serverTopics) { for (ReaderTag tag : serverTopics) { - String tagNameBefore = tag.getTagDisplayName(); if (tag.isFollowedSites()) { tag.setTagDisplayName(mContext.getString(R.string.reader_subscribed_display_name)); - if (!tagNameBefore.equals(tag.getTagDisplayName())) updateDone = true; } else if (tag.isDiscover()) { tag.setTagDisplayName(mContext.getString(R.string.reader_discover_display_name)); - if (!tagNameBefore.equals(tag.getTagDisplayName())) updateDone = true; } else if (tag.isPostsILike()) { tag.setTagDisplayName(mContext.getString(R.string.reader_my_likes_display_name)); - if (!tagNameBefore.equals(tag.getTagDisplayName())) updateDone = true; } } - - return updateDone; } private void handleUpdateTagsResponse(final JSONObject jsonObject) { @@ -151,7 +149,7 @@ public void run() { ReaderTagList serverTopics = new ReaderTagList(); serverTopics.addAll(parseTags(jsonObject, "default", ReaderTagType.DEFAULT)); - boolean displayNameUpdateWasNeeded = displayNameUpdateWasNeeded(serverTopics); + updateDisplayNamesIfNeeded(serverTopics); serverTopics.addAll(parseTags(jsonObject, "subscribed", ReaderTagType.FOLLOWED)); @@ -176,13 +174,11 @@ public void run() { localTopics.addAll(ReaderTagTable.getFollowedTags()); localTopics.addAll(ReaderTagTable.getBookmarkTags()); localTopics.addAll(ReaderTagTable.getCustomListTags()); + localTopics.addAll(ReaderTagTable.getDiscoverPostCardsTags()); - if ( - !localTopics.isSameList(serverTopics) - || displayNameUpdateWasNeeded - ) { - AppLog.d(AppLog.T.READER, "reader service > followed topics changed " - + "updatedDisplayNames [" + displayNameUpdateWasNeeded + "]"); + boolean didChangeFollowedTags = false; + if (!localTopics.isSameList(serverTopics)) { + AppLog.d(AppLog.T.READER, "reader service > followed topics changed"); if (!mAccountStore.hasAccessToken()) { // Do not delete locally saved tags for logged out user @@ -195,8 +191,9 @@ public void run() { ReaderTagTable.replaceTags(serverTopics); } // broadcast the fact that there are changes - EventBus.getDefault().post(new ReaderEvents.FollowedTagsChanged(true)); + didChangeFollowedTags = true; } + EventBus.getDefault().post(new FollowedTagsFetched(true, didChangeFollowedTags)); AppPrefs.setReaderTagsUpdatedTimestamp(new Date().getTime()); taskCompleted(UpdateTask.TAGS); 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 e331ecaeb231..a9dd37639a36 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 @@ -393,7 +393,7 @@ class SubFilterViewModel @Inject constructor( @Suppress("unused", "UNUSED_PARAMETER") @Subscribe(threadMode = ThreadMode.MAIN) - fun onEventMainThread(event: ReaderEvents.FollowedTagsChanged) { + fun onEventMainThread(event: ReaderEvents.FollowedTagsFetched) { AppLog.d(T.READER, "Subfilter bottom sheet > followed tags changed") loadSubFilters() } 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 a6e26e3f648a..0e13b2f17e0b 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 @@ -135,7 +135,8 @@ class ReaderViewModel @Inject constructor( // _showJetpackPoweredBottomSheet.value = Event(true) } - private fun loadTabs(savedInstanceState: Bundle? = null) { + @JvmOverloads + fun loadTabs(savedInstanceState: Bundle? = null) { launch { val tagList = loadReaderTabsUseCase.loadTabs() if (tagList.isNotEmpty() && readerTagsList != tagList) { @@ -215,7 +216,7 @@ class ReaderViewModel @Inject constructor( @Suppress("unused", "UNUSED_PARAMETER") @Subscribe(threadMode = MAIN) - fun onTagsUpdated(event: ReaderEvents.FollowedTagsChanged) { + fun onTagsUpdated(event: ReaderEvents.FollowedTagsFetched) { loadTabs() } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/StatsModule.kt b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/StatsModule.kt index 08881ec68a44..407b7ab2b6fe 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/StatsModule.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/StatsModule.kt @@ -126,7 +126,8 @@ class StatsModule { managementNewsCardUseCase: ManagementNewsCardUseCase, actionCardGrowUseCase: ActionCardGrowUseCase, actionCardReminderUseCase: ActionCardReminderUseCase, - actionCardScheduleUseCase: ActionCardScheduleUseCase + actionCardScheduleUseCase: ActionCardScheduleUseCase, + trafficTabFeatureConfig: StatsTrafficTabFeatureConfig ): List<@JvmSuppressWildcards BaseStatsUseCase<*, *>> { val useCases = mutableListOf>() if (BuildConfig.IS_JETPACK_APP) { @@ -140,11 +141,15 @@ class StatsModule { } else { useCases.add(followerTotalsUseCase) } + + if (!trafficTabFeatureConfig.isEnabled()) { + useCases.add(todayStatsUseCase) + } + useCases.addAll( listOf( allTimeStatsUseCase, latestPostSummaryUseCase, - todayStatsUseCase, followersUseCaseFactory.build(BLOCK), commentsUseCase, mostPopularInsightsUseCase, diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/lists/sections/insights/management/InsightsManagementMapper.kt b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/lists/sections/insights/management/InsightsManagementMapper.kt index 2ce68123cd8d..d0f9ecfe1c45 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/lists/sections/insights/management/InsightsManagementMapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/lists/sections/insights/management/InsightsManagementMapper.kt @@ -2,7 +2,6 @@ package org.wordpress.android.ui.stats.refresh.lists.sections.insights.managemen import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext -import org.wordpress.android.BuildConfig import org.wordpress.android.R import org.wordpress.android.fluxc.store.StatsStore.InsightType import org.wordpress.android.fluxc.store.StatsStore.InsightType.ACTION_GROW @@ -33,6 +32,7 @@ import org.wordpress.android.ui.stats.refresh.lists.sections.insights.management import org.wordpress.android.ui.stats.refresh.lists.sections.insights.management.InsightsManagementViewModel.InsightListItem.InsightModel.Status.ADDED import org.wordpress.android.ui.stats.refresh.lists.sections.insights.management.InsightsManagementViewModel.InsightListItem.InsightModel.Status.REMOVED import org.wordpress.android.ui.utils.ListItemInteraction +import org.wordpress.android.util.config.StatsTrafficTabFeatureConfig import javax.inject.Inject import javax.inject.Named @@ -47,18 +47,21 @@ private val ACTIVITY_INSIGHTS = mutableListOf( private val GENERAL_INSIGHTS = mutableListOf( ALL_TIME_STATS, MOST_POPULAR_DAY_AND_HOUR, - ANNUAL_SITE_STATS, - TODAY_STATS + ANNUAL_SITE_STATS ) class InsightsManagementMapper @Inject constructor( - @Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher + @Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher, + private val trafficTabFeatureConfig: StatsTrafficTabFeatureConfig ) { suspend fun buildUIModel(addedTypes: Set, onClick: (InsightType) -> Unit) = withContext(bgDispatcher) { val insightListItems = mutableListOf() insightListItems += Header(R.string.stats_insights_management_general) - if (BuildConfig.IS_JETPACK_APP && !GENERAL_INSIGHTS.contains(VIEWS_AND_VISITORS)) { + if (!trafficTabFeatureConfig.isEnabled() && !GENERAL_INSIGHTS.contains(TODAY_STATS)) { + GENERAL_INSIGHTS.add(TODAY_STATS) + } + if (!GENERAL_INSIGHTS.contains(VIEWS_AND_VISITORS)) { GENERAL_INSIGHTS.add(0, VIEWS_AND_VISITORS) } insightListItems += GENERAL_INSIGHTS.map { type -> @@ -70,7 +73,7 @@ class InsightsManagementMapper @Inject constructor( } insightListItems += Header(R.string.stats_insights_management_activity) - if (BuildConfig.IS_JETPACK_APP && ACTIVITY_INSIGHTS.contains(FOLLOWER_TOTALS)) { + if (ACTIVITY_INSIGHTS.contains(FOLLOWER_TOTALS)) { // Replace FOLLOWER_TOTALS with Stats revamp v2 total insights val followerTotalsIndex = ACTIVITY_INSIGHTS.indexOf(FOLLOWER_TOTALS) ACTIVITY_INSIGHTS.remove(FOLLOWER_TOTALS) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/FetchFollowedTagsUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/FetchFollowedTagsUseCaseTest.kt index a581bc43584f..5c0c086000e0 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/FetchFollowedTagsUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/FetchFollowedTagsUseCaseTest.kt @@ -9,7 +9,7 @@ import org.mockito.Mock import org.mockito.junit.MockitoJUnitRunner import org.mockito.kotlin.whenever import org.wordpress.android.BaseUnitTest -import org.wordpress.android.ui.reader.ReaderEvents.FollowedTagsChanged +import org.wordpress.android.ui.reader.ReaderEvents.FollowedTagsFetched import org.wordpress.android.ui.reader.repository.ReaderRepositoryCommunication.Error.NetworkUnavailable import org.wordpress.android.ui.reader.repository.ReaderRepositoryCommunication.Error.RemoteRequestFailure import org.wordpress.android.ui.reader.repository.ReaderRepositoryCommunication.Success @@ -61,12 +61,12 @@ class FetchFollowedTagsUseCaseTest : BaseUnitTest() { } @Test - fun `Success returned when FollowedTagsChanged event is posted with true`() = test { + fun `Success returned when FollowedTagsFetched event is posted with success`() = test { // Given whenever(networkUtilsWrapper.isNetworkAvailable()).thenReturn(true) - val event = FollowedTagsChanged(true) + val event = FollowedTagsFetched(true) whenever(readerUpdateServiceStarterWrapper.startService(contextProvider.getContext(), EnumSet.of(TAGS))) - .then { useCase.onFollowedTagsChanged(event) } + .then { useCase.onFollowedTagsFetched(event) } // When val result = useCase.fetch() @@ -76,12 +76,12 @@ class FetchFollowedTagsUseCaseTest : BaseUnitTest() { } @Test - fun `RemoteRequestFailure returned when FollowedTagsChanged event is posted with false`() = test { + fun `RemoteRequestFailure returned when FollowedTagsFetched event is posted with failure`() = test { // Given whenever(networkUtilsWrapper.isNetworkAvailable()).thenReturn(true) - val event = FollowedTagsChanged(false) + val event = FollowedTagsFetched(false) whenever(readerUpdateServiceStarterWrapper.startService(contextProvider.getContext(), EnumSet.of(TAGS))) - .then { useCase.onFollowedTagsChanged(event) } + .then { useCase.onFollowedTagsFetched(event) } // When val result = useCase.fetch() diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt index 18f0d5704887..3a87558edee4 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/repository/ReaderDiscoverDataProviderTest.kt @@ -16,7 +16,7 @@ import org.wordpress.android.models.ReaderPost import org.wordpress.android.models.discover.ReaderDiscoverCard.ReaderPostCard import org.wordpress.android.models.discover.ReaderDiscoverCards import org.wordpress.android.ui.reader.ReaderEvents.FetchDiscoverCardsEnded -import org.wordpress.android.ui.reader.ReaderEvents.FollowedTagsChanged +import org.wordpress.android.ui.reader.ReaderEvents.FollowedTagsFetched import org.wordpress.android.ui.reader.actions.ReaderActions.UpdateResult.FAILED import org.wordpress.android.ui.reader.actions.ReaderActions.UpdateResult.HAS_NEW import org.wordpress.android.ui.reader.actions.ReaderActions.UpdateResult.UNCHANGED @@ -249,7 +249,11 @@ class ReaderDiscoverDataProviderTest : BaseUnitTest() { @Test fun `when followed tags change the discover feed gets refreshed`() = test { // Act - dataProvider.onFollowedTagsChanged(FollowedTagsChanged(true)) + dataProvider.onFollowedTagsFetched( + FollowedTagsFetched( + true + ) + ) // Assert verify(fetchDiscoverCardsUseCase).fetch(REQUEST_FIRST_PAGE) } diff --git a/WordPress/src/test/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModelTest.kt index 29780c984015..2f437f24465c 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/reader/viewmodels/ReaderViewModelTest.kt @@ -177,7 +177,7 @@ class ReaderViewModelTest : BaseUnitTest() { } @Test - fun `Tags are reloaded when FollowedTagsChanged event is received`() = testWithNonEmptyTags { + fun `Tags are reloaded when FollowedTagsFetched event is received`() = testWithNonEmptyTags { // Arrange var state: ReaderUiState? = null viewModel.uiState.observeForever { diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/lists/sections/insights/management/InsightsManagementMapperTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/lists/sections/insights/management/InsightsManagementMapperTest.kt index b1b0a69db246..2e9df36f6232 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/lists/sections/insights/management/InsightsManagementMapperTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/lists/sections/insights/management/InsightsManagementMapperTest.kt @@ -4,34 +4,43 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test +import org.mockito.Mock import org.wordpress.android.BaseUnitTest import org.wordpress.android.R import org.wordpress.android.fluxc.store.StatsStore.InsightType import org.wordpress.android.fluxc.store.StatsStore.InsightType.ALL_TIME_STATS import org.wordpress.android.fluxc.store.StatsStore.InsightType.ANNUAL_SITE_STATS import org.wordpress.android.fluxc.store.StatsStore.InsightType.FOLLOWERS -import org.wordpress.android.fluxc.store.StatsStore.InsightType.FOLLOWER_TOTALS import org.wordpress.android.fluxc.store.StatsStore.InsightType.LATEST_POST_SUMMARY import org.wordpress.android.fluxc.store.StatsStore.InsightType.MOST_POPULAR_DAY_AND_HOUR import org.wordpress.android.fluxc.store.StatsStore.InsightType.POSTING_ACTIVITY import org.wordpress.android.fluxc.store.StatsStore.InsightType.PUBLICIZE import org.wordpress.android.fluxc.store.StatsStore.InsightType.TAGS_AND_CATEGORIES import org.wordpress.android.fluxc.store.StatsStore.InsightType.TODAY_STATS +import org.wordpress.android.fluxc.store.StatsStore.InsightType.TOTAL_COMMENTS +import org.wordpress.android.fluxc.store.StatsStore.InsightType.TOTAL_FOLLOWERS +import org.wordpress.android.fluxc.store.StatsStore.InsightType.TOTAL_LIKES +import org.wordpress.android.fluxc.store.StatsStore.InsightType.VIEWS_AND_VISITORS import org.wordpress.android.ui.stats.refresh.lists.sections.insights.management.InsightsManagementViewModel.InsightListItem import org.wordpress.android.ui.stats.refresh.lists.sections.insights.management.InsightsManagementViewModel.InsightListItem.Header import org.wordpress.android.ui.stats.refresh.lists.sections.insights.management.InsightsManagementViewModel.InsightListItem.InsightModel import org.wordpress.android.ui.stats.refresh.lists.sections.insights.management.InsightsManagementViewModel.InsightListItem.InsightModel.Status +import org.wordpress.android.util.config.StatsTrafficTabFeatureConfig @ExperimentalCoroutinesApi class InsightsManagementMapperTest : BaseUnitTest() { + @Mock + private lateinit var trafficTabFeatureConfig: StatsTrafficTabFeatureConfig + private lateinit var insightsManagementMapper: InsightsManagementMapper - private val insightTypeCount = 10 // POSTS_AND_PAGES_INSIGHTS.size + ACTIVITY_INSIGHTS.size + GENERAL_INSIGHTS.size + private var insightTypeCount = 13 // POSTS_AND_PAGES_INSIGHTS.size + ACTIVITY_INSIGHTS.size + GENERAL_INSIGHTS.size private val sectionsCount = 3 @Before fun setUp() { insightsManagementMapper = InsightsManagementMapper( - testDispatcher() + testDispatcher(), + trafficTabFeatureConfig ) } @@ -46,18 +55,21 @@ class InsightsManagementMapperTest : BaseUnitTest() { // Then assertThat(result).hasSize(insightTypeCount + sectionsCount) assertHeader(result[0], R.string.stats_insights_management_general) - assertInsight(result[1], ALL_TIME_STATS, true) - assertInsight(result[2], MOST_POPULAR_DAY_AND_HOUR, true) - assertInsight(result[3], ANNUAL_SITE_STATS, true) - assertInsight(result[4], TODAY_STATS, true) - assertHeader(result[5], R.string.stats_insights_management_posts_and_pages) - assertInsight(result[6], LATEST_POST_SUMMARY, true) - assertInsight(result[7], POSTING_ACTIVITY, true) - assertInsight(result[8], TAGS_AND_CATEGORIES, true) - assertHeader(result[9], R.string.stats_insights_management_activity) - assertInsight(result[10], FOLLOWERS, true) - assertInsight(result[11], FOLLOWER_TOTALS, true) - assertInsight(result[12], PUBLICIZE, true) + assertInsight(result[1], VIEWS_AND_VISITORS, true) + assertInsight(result[2], ALL_TIME_STATS, true) + assertInsight(result[3], MOST_POPULAR_DAY_AND_HOUR, true) + assertInsight(result[4], ANNUAL_SITE_STATS, true) + assertInsight(result[5], TODAY_STATS, true) + assertHeader(result[6], R.string.stats_insights_management_posts_and_pages) + assertInsight(result[7], LATEST_POST_SUMMARY, true) + assertInsight(result[8], POSTING_ACTIVITY, true) + assertInsight(result[9], TAGS_AND_CATEGORIES, true) + assertHeader(result[10], R.string.stats_insights_management_activity) + assertInsight(result[11], FOLLOWERS, true) + assertInsight(result[12], TOTAL_LIKES, true) + assertInsight(result[13], TOTAL_COMMENTS, true) + assertInsight(result[14], TOTAL_FOLLOWERS, true) + assertInsight(result[15], PUBLICIZE, true) } @Test @@ -71,18 +83,21 @@ class InsightsManagementMapperTest : BaseUnitTest() { // Then assertThat(result).hasSize(insightTypeCount + sectionsCount) assertHeader(result[0], R.string.stats_insights_management_general) - assertInsight(result[1], ALL_TIME_STATS, true) - assertInsight(result[2], MOST_POPULAR_DAY_AND_HOUR, false) - assertInsight(result[3], ANNUAL_SITE_STATS, false) - assertInsight(result[4], TODAY_STATS, false) - assertHeader(result[5], R.string.stats_insights_management_posts_and_pages) - assertInsight(result[6], LATEST_POST_SUMMARY, false) - assertInsight(result[7], POSTING_ACTIVITY, false) - assertInsight(result[8], TAGS_AND_CATEGORIES, false) - assertHeader(result[9], R.string.stats_insights_management_activity) - assertInsight(result[10], FOLLOWERS, false) - assertInsight(result[11], FOLLOWER_TOTALS, false) - assertInsight(result[12], PUBLICIZE, true) + assertInsight(result[1], VIEWS_AND_VISITORS, false) + assertInsight(result[2], ALL_TIME_STATS, true) + assertInsight(result[3], MOST_POPULAR_DAY_AND_HOUR, false) + assertInsight(result[4], ANNUAL_SITE_STATS, false) + assertInsight(result[5], TODAY_STATS, false) + assertHeader(result[6], R.string.stats_insights_management_posts_and_pages) + assertInsight(result[7], LATEST_POST_SUMMARY, false) + assertInsight(result[8], POSTING_ACTIVITY, false) + assertInsight(result[9], TAGS_AND_CATEGORIES, false) + assertHeader(result[10], R.string.stats_insights_management_activity) + assertInsight(result[11], FOLLOWERS, false) + assertInsight(result[12], TOTAL_LIKES, false) + assertInsight(result[13], TOTAL_COMMENTS, false) + assertInsight(result[14], TOTAL_FOLLOWERS, false) + assertInsight(result[15], PUBLICIZE, true) } @Test @@ -93,18 +108,21 @@ class InsightsManagementMapperTest : BaseUnitTest() { // Then assertThat(result).hasSize(insightTypeCount + sectionsCount) assertHeader(result[0], R.string.stats_insights_management_general) - assertInsight(result[1], ALL_TIME_STATS, false) - assertInsight(result[2], MOST_POPULAR_DAY_AND_HOUR, false) - assertInsight(result[3], ANNUAL_SITE_STATS, false) - assertInsight(result[4], TODAY_STATS, false) - assertHeader(result[5], R.string.stats_insights_management_posts_and_pages) - assertInsight(result[6], LATEST_POST_SUMMARY, false) - assertInsight(result[7], POSTING_ACTIVITY, false) - assertInsight(result[8], TAGS_AND_CATEGORIES, false) - assertHeader(result[9], R.string.stats_insights_management_activity) - assertInsight(result[10], FOLLOWERS, false) - assertInsight(result[11], FOLLOWER_TOTALS, false) - assertInsight(result[12], PUBLICIZE, false) + assertInsight(result[1], VIEWS_AND_VISITORS, false) + assertInsight(result[2], ALL_TIME_STATS, false) + assertInsight(result[3], MOST_POPULAR_DAY_AND_HOUR, false) + assertInsight(result[4], ANNUAL_SITE_STATS, false) + assertInsight(result[5], TODAY_STATS, false) + assertHeader(result[6], R.string.stats_insights_management_posts_and_pages) + assertInsight(result[7], LATEST_POST_SUMMARY, false) + assertInsight(result[8], POSTING_ACTIVITY, false) + assertInsight(result[9], TAGS_AND_CATEGORIES, false) + assertHeader(result[10], R.string.stats_insights_management_activity) + assertInsight(result[11], FOLLOWERS, false) + assertInsight(result[12], TOTAL_LIKES, false) + assertInsight(result[13], TOTAL_COMMENTS, false) + assertInsight(result[14], TOTAL_FOLLOWERS, false) + assertInsight(result[15], PUBLICIZE, false) } private fun assertHeader(item: InsightListItem, text: Int) { diff --git a/build.gradle b/build.gradle index b567a596602b..b978e54055d5 100644 --- a/build.gradle +++ b/build.gradle @@ -97,7 +97,7 @@ ext { // test assertjVersion = '3.23.1' junitVersion = '4.13.2' - mockitoVersion = '5.3.1' + mockitoVersion = '5.10.0' mockitoKotlinVersion = '4.1.0' // android test diff --git a/libs/processors/build.gradle b/libs/processors/build.gradle index 8e338bd2ea76..77eb4965cfe2 100644 --- a/libs/processors/build.gradle +++ b/libs/processors/build.gradle @@ -14,6 +14,8 @@ dependencies { kapt "com.google.auto.service:auto-service:$googleAutoServiceVersion" implementation "com.squareup:kotlinpoet:$squareupKotlinPoetVersion" + testImplementation "com.github.tschuchortdev:kotlin-compile-testing:1.5.0" testImplementation "junit:junit:$junitVersion" testImplementation "org.assertj:assertj-core:$assertjVersion" + testImplementation "org.jetbrains.kotlin:kotlin-reflect:$gradle.ext.kotlinVersion" } diff --git a/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt b/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt new file mode 100644 index 000000000000..b4ee3385c125 --- /dev/null +++ b/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt @@ -0,0 +1,151 @@ +package org.wordpress.android.processor + +import com.tschuchort.compiletesting.KotlinCompilation +import com.tschuchort.compiletesting.SourceFile +import org.assertj.core.api.Assertions.assertThat +import org.jetbrains.kotlin.utils.addToStdlib.cast +import org.junit.Test + +class RemoteConfigProcessorTest { + @Test + fun `given a class with features annotation, when compiling, generate expected configuration check`() { + // when + val result = compile(listOf(featureA)) + + // then + assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK) + assertThat(result.classLoader.loadClass("org.wordpress.android.util.config.RemoteFeatureConfigCheck")) + .hasDeclaredMethods("checkRemoteFields") + } + + @Test + fun `given a class with remote field annotation, when compiling, generate expected config defaults class`() { + // given + val remoteFieldA = SourceFile.kotlin( + "RemoteField.kt", """ + import org.wordpress.android.annotation.RemoteFieldDefaultGenerater + import org.wordpress.android.util.config.AppConfig + + @RemoteFieldDefaultGenerater(remoteField = "remoteField", defaultValue = "default") + class RemoteFieldA + """ + ) + + // when + val result = compile( + listOf( + remoteFieldA, + featureA, /* adding a feature, as without it, annotation processor won't start */ + ) + ) + + // then + assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK) + val remoteFieldConfigDefaultsClass = + result.classLoader.loadClass("org.wordpress.android.util.config.RemoteFieldConfigDefaults") + val remoteFieldConfigDefaultsObject = remoteFieldConfigDefaultsClass.kotlin.objectInstance + + assertThat( + remoteFieldConfigDefaultsClass.getDeclaredField("remoteFieldConfigDefaults") + .apply { isAccessible = true } + .get(remoteFieldConfigDefaultsObject) + .cast>() + ).containsEntry("remoteField", "default") + } + + @Test + fun `given class with feature and experiment annotation, when compiling, generate config defaults class`() { + // given + val experiment = SourceFile.kotlin( + "Experiment.kt", """ + import org.wordpress.android.annotation.Experiment + import org.wordpress.android.util.config.AppConfig + + @Experiment("experimentFeature", "defaultVariant") + class Experiment + """ + ) + + // when + val result = compile(listOf(featureA, experiment)) + + // then + assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK) + val remoteFieldConfigDefaultsClass = + result.classLoader.loadClass("org.wordpress.android.util.config.RemoteFeatureConfigDefaults") + val remoteFieldConfigDefaultsObject = remoteFieldConfigDefaultsClass.kotlin.objectInstance + + assertThat( + remoteFieldConfigDefaultsClass.getDeclaredField("remoteFeatureConfigDefaults") + .apply { isAccessible = true } + .get(remoteFieldConfigDefaultsObject) + .cast>() + ).containsExactlyInAnyOrderEntriesOf( + mapOf( + "experimentFeature" to "defaultVariant", + "remoteField" to "false" + ) + ) + } + + @Test + fun `given class with feature in development annotation, when compiling, generate expected list of classes`() { + // given + val experiment = SourceFile.kotlin( + "Experiment.kt", """ + import org.wordpress.android.annotation.FeatureInDevelopment + import org.wordpress.android.util.config.AppConfig + + @FeatureInDevelopment + class DevFeature + """ + ) + + // when + val result = compile( + listOf( + experiment, + featureA, /* adding a feature, as without it, annotation processor won't start */ + ) + ) + + // then + + val featuresInDevelopmentClass = + result.classLoader.loadClass("org.wordpress.android.util.config.FeaturesInDevelopment") + val featuresInDevelopmentObject = featuresInDevelopmentClass.kotlin.objectInstance + assertThat( + featuresInDevelopmentClass.getDeclaredField("featuresInDevelopment") + .apply { isAccessible = true } + .get(featuresInDevelopmentObject) + .cast>() + ).containsOnly("DevFeature") + } + + private fun compile(src: List) = KotlinCompilation().apply { + sources = src + fakeAppConfig + annotationProcessors = listOf(RemoteConfigProcessor()) + inheritClassPath = true + messageOutputStream = System.out + }.compile() + + // Fake AppConfig is needed, as it's a class that is expected to be present in the classpath. Originally, this class + // is placed in `WordPress` module. + private val fakeAppConfig = SourceFile.kotlin( + "AppConfig.kt", """ + package org.wordpress.android.util.config + + class AppConfig + """ + ) + + private val featureA = SourceFile.kotlin( + "Feature.kt", """ + import org.wordpress.android.annotation.Feature + import org.wordpress.android.util.config.AppConfig + + @Feature("remoteField", false) + class FeatureA(appConfig: AppConfig, val remoteField: String ="foo") + """ + ) +} diff --git a/settings.gradle b/settings.gradle index a092afee5eb6..2809053345a7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,11 +2,11 @@ pluginManagement { gradle.ext.kotlinVersion = '1.9.10' gradle.ext.agpVersion = '8.1.0' gradle.ext.googleServicesVersion = '4.3.15' - gradle.ext.navigationVersion = '2.5.3' + gradle.ext.navigationVersion = '2.7.7' gradle.ext.sentryVersion = '3.14.0' gradle.ext.daggerVersion = "2.50" gradle.ext.detektVersion = '1.23.0' - gradle.ext.violationCommentsVersion = '1.67' + gradle.ext.violationCommentsVersion = '1.70.0' gradle.ext.measureBuildsVersion = '2.0.3' gradle.ext.koverVersion = '0.7.5'