diff --git a/.buildkite/commands/danger-pr-check.sh b/.buildkite/commands/danger-pr-check.sh deleted file mode 100755 index 0ad81cfede4c..000000000000 --- a/.buildkite/commands/danger-pr-check.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -eu - -echo "--- :rubygems: Setting up Gems" -bundle install - -echo "--- Running Danger: PR Check" -bundle exec danger --fail-on-errors=true --remove-previous-comments --danger_id=pr-check diff --git a/.buildkite/commands/run-unit-tests.sh b/.buildkite/commands/run-unit-tests.sh index c2326b1e6731..c8ee2cf56788 100755 --- a/.buildkite/commands/run-unit-tests.sh +++ b/.buildkite/commands/run-unit-tests.sh @@ -3,14 +3,17 @@ echo "--- 🧪 Testing" set +e if [ "$1" == "wordpress" ]; then - test_suite="testWordpressVanillaRelease" + test_suite="testWordpressVanillaRelease koverXmlReportWordpressVanillaRelease" test_log_dir="WordPress/build/test-results/*/*.xml" + code_coverage_report="WordPress/build/reports/kover/reportWordpressVanillaRelease.xml" elif [ "$1" == "processors" ]; then - test_suite=":libs:processors:test" + test_suite=":libs:processors:test :libs:processors:koverXmlReport" test_log_dir="libs/processors/build/test-results/test/*.xml" + code_coverage_report="libs/processors/build/reports/kover/report.xml" elif [ "$1" == "image-editor" ]; then - test_suite=":libs:image-editor:testReleaseUnitTest" + test_suite=":libs:image-editor:testReleaseUnitTest :libs:image-editor:koverXmlReportRelease" test_log_dir="libs/image-editor/build/test-results/testReleaseUnitTest/*.xml" + code_coverage_report="libs/image-editor/build/reports/kover/reportRelease.xml" else echo "Invalid Test Suite! Expected 'wordpress', 'processors', or 'image-editor', received '$1' instead" exit 1 @@ -46,4 +49,7 @@ echo "--- 🧪 Copying test logs for test collector" mkdir buildkite-test-analytics cp $test_log_dir buildkite-test-analytics +echo "--- ⚒️ Uploading code coverage" +.buildkite/commands/upload-code-coverage.sh $code_coverage_report + exit $TESTS_EXIT_STATUS diff --git a/.buildkite/commands/upload-code-coverage.sh b/.buildkite/commands/upload-code-coverage.sh new file mode 100755 index 000000000000..1d3c343f6319 --- /dev/null +++ b/.buildkite/commands/upload-code-coverage.sh @@ -0,0 +1,10 @@ +#!/bin/bash -eu + +curl https://keybase.io/codecovsecurity/pgp_keys.asc | gpg --no-default-keyring --keyring trustedkeys.gpg --import +curl -Os https://uploader.codecov.io/latest/linux/codecov +curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM +curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM.sig +gpgv codecov.SHA256SUM.sig codecov.SHA256SUM +sha256sum -c codecov.SHA256SUM +chmod +x codecov +./codecov -t "$CODECOV_TOKEN" -f "$1" diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 2e99e2e5c64e..87d96c8032e3 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -29,19 +29,6 @@ steps: ################# - group: "🕵️‍♂️ Linters" steps: - - label: "☢️ Danger - PR Check" - command: .buildkite/commands/danger-pr-check.sh - plugins: - - docker#v5.8.0: - image: "public.ecr.aws/docker/library/ruby:3.2.2" - propagate-environment: true - environment: - - "DANGER_GITHUB_API_TOKEN" - if: "build.pull_request.id != null" - retry: - manual: - permit_on_passed: true - - label: "🕵️ checkstyle" command: | cp gradle.properties-example gradle.properties diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 41d49f8c3817..f0c244ca5c84 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -36,7 +36,10 @@ updates: # dependapot. - dependency-name: "org.wordpress:fluxc" - dependency-name: "org.wordpress:utils" + # org.wordpress-mobile.gutenberg-mobile is deprecated and org.wordpress.gutenberg-mobile is used instead. + # Temporarily leaving this declaration during transition, but this should be removed soon. - dependency-name: "org.wordpress-mobile.gutenberg-mobile:react-native-gutenberg-bridge" + - dependency-name: "org.wordpress.gutenberg-mobile:react-native-gutenberg-bridge" - dependency-name: "org.wordpress:login" - dependency-name: "com.automattic:stories" - dependency-name: "com.automattic.stories:mp4compose" diff --git a/.github/workflows/run-danger.yml b/.github/workflows/run-danger.yml new file mode 100644 index 000000000000..d61c242186c8 --- /dev/null +++ b/.github/workflows/run-danger.yml @@ -0,0 +1,11 @@ +name: ☢️ Danger + +on: + pull_request: + types: [opened, synchronize, edited, review_requested, review_request_removed, labeled, unlabeled, milestoned, demilestoned] + +jobs: + dangermattic: + uses: Automattic/dangermattic/.github/workflows/reusable-run-danger.yml@trunk + secrets: + github-token: ${{ secrets.DANGERMATTIC_GITHUB_TOKEN }} diff --git a/.github/workflows/submit-gradle-dependencies.yml b/.github/workflows/submit-gradle-dependencies.yml new file mode 100644 index 000000000000..c1d2a5bfe5fe --- /dev/null +++ b/.github/workflows/submit-gradle-dependencies.yml @@ -0,0 +1,24 @@ +name: Submit dependencies to GitHub Dependency Graph +on: + push: + branches: + - trunk + - release/* +permissions: + contents: write +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + - run: cp gradle.properties-example gradle.properties + - name: Setup Gradle to generate and submit dependency graphs + uses: gradle/gradle-build-action@v2 + with: + dependency-graph: generate-and-submit + - name: Generate the dependency graph which will be submitted post-job + run: ./gradlew :WordPress:dependencies --no-configure-on-demand diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index a2152b86784b..0e1b80833633 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,7 +1,13 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] +24.2 +----- + + 24.1 ----- +* [**] Disabled the ability of creating new Story posts. [#20014] +* [**] Disable Story block [https://github.com/wordpress-mobile/WordPress-Android/pull/20005] 24.0.2 ----- @@ -11,6 +17,13 @@ ----- * [**] Fix crash when RichText values are not defined [https://github.com/wordpress-mobile/gutenberg-mobile/pull/6563] +* [*] Block Editor: Fix missing custom color indicator for custom gradients [https://github.com/WordPress/gutenberg/pull/57605] +* [**] Block Editor: Display a notice when a network connection is unavailable [https://github.com/WordPress/gutenberg/pull/56934] +* [**] Block Editor: Image block media uploads display a custom error message when there is no internet connection [https://github.com/wordpress-mobile/WordPress-Android/pull/19878] +* [**] Block Editor: Media uploads that failed due to lack of internet connectivity automatically retry once a connection is re-established [https://github.com/wordpress-mobile/WordPress-Android/pull/19803] +* [*] [Jetpack-only] Added opening domain management from the Site Domain screen by tapping on the domain cards [https://github.com/wordpress-mobile/WordPress-Android/pull/19910] +* [*] [Jetpack-only] Fix Prompt response posts not having one of the `dailyprompt` tags [https://github.com/wordpress-mobile/WordPress-Android/pull/19971] +* [*] Reader: Fix the issue with icons displaying incorrectly in RTL languages. [https://github.com/wordpress-mobile/WordPress-Android/pull/19962] 24.0 ----- diff --git a/WordPress/build.gradle b/WordPress/build.gradle index bb75f28e5ff4..93370a88f9e6 100644 --- a/WordPress/build.gradle +++ b/WordPress/build.gradle @@ -12,6 +12,7 @@ plugins { id "se.bjurr.violations.violation-comments-to-github-gradle-plugin" id "com.google.gms.google-services" id 'dagger.hilt.android.plugin' + id "org.jetbrains.kotlinx.kover" } sentry { @@ -35,12 +36,16 @@ repositories { includeGroup "org.wordpress.aztec" includeGroup "org.wordpress.fluxc" includeGroup "org.wordpress.wellsql" - includeGroup "org.wordpress-mobile" - includeGroup "org.wordpress-mobile.gutenberg-mobile" - includeGroupByRegex "org.wordpress-mobile.react-native-libraries.*" + includeGroup "org.wordpress.gutenberg-mobile" + includeGroupByRegex "org.wordpress.react-native-libraries.*" includeGroup "com.automattic" includeGroup "com.automattic.stories" includeGroup "com.automattic.tracks" + // 'org.wordpress-mobile' group is deprecated. It's kept for now for smoother transition + // but it should be removed soon (within couple weeks) + includeGroup "org.wordpress-mobile" + includeGroup "org.wordpress-mobile.gutenberg-mobile" + includeGroupByRegex "org.wordpress-mobile.react-native-libraries.*" } } maven { @@ -145,6 +150,7 @@ android { buildConfigField "boolean", "BLOGANUARY_DASHBOARD_NUDGE", "false" buildConfigField "boolean", "IN_APP_REVIEWS", "false" buildConfigField "boolean", "DYNAMIC_DASHBOARD_CARDS", "false" + buildConfigField "boolean", "STATS_TRAFFIC_TAB", "false" // Override these constants in jetpack product flavor to enable/ disable features buildConfigField "boolean", "ENABLE_SITE_CREATION", "true" diff --git a/WordPress/jetpackJalapeno/release/org.wordpress.android-jetpack-jalapeno-release.aab b/WordPress/jetpackJalapeno/release/org.wordpress.android-jetpack-jalapeno-release.aab new file mode 100644 index 000000000000..34a4040e6f89 Binary files /dev/null and b/WordPress/jetpackJalapeno/release/org.wordpress.android-jetpack-jalapeno-release.aab differ diff --git a/WordPress/jetpack_metadata/PlayStoreStrings.po b/WordPress/jetpack_metadata/PlayStoreStrings.po index e43690e0aeb0..6ff287e9d2e9 100644 --- a/WordPress/jetpack_metadata/PlayStoreStrings.po +++ b/WordPress/jetpack_metadata/PlayStoreStrings.po @@ -10,6 +10,17 @@ msgstr "" "X-Generator: VsCode\n" "Project-Id-Version: Jetpack - Apps - Android - Release Notes\n" +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 "" + msgctxt "release_note_240" msgid "" "24.0:\n" @@ -20,15 +31,6 @@ msgid "" "- Share different media types to the app at once without crashing\n" msgstr "" -msgctxt "release_note_239" -msgid "" -"23.9:\n" -"No updates this week! Enjoy these reindeer facts:\n" -"- Male reindeer shed their antlers in fall and females in spring, so Santa’s reindeer are female.\n" -"- Reindeer don’t actually have red noses.\n" -"- Santa hasn’t eaten milk and cookies since 2003 because Blitzen always gets there first.\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 8d42c196ea59..e7b4006244cd 100644 --- a/WordPress/jetpack_metadata/release_notes.txt +++ b/WordPress/jetpack_metadata/release_notes.txt @@ -1,5 +1,6 @@ -- Get a WordPress plan with a free domain in the domain dashboard -- Device keyboard remains when editing text blocks -- Editor screen auto-scrolls to newly inserted blocks -- Unselect blocks by pressing device’s back button -- Share different media types to the app at once without crashing +- Get notified when you’re working offline. +- Image block uploads pause/resume with internet connection. +- See custom gradient selections in the editor. +- Fixed forward/back arrows for right-to-left readers. +- Daily Prompt tags work properly. +- Tap Site Domain cards to manage domains. diff --git a/WordPress/metadata/PlayStoreStrings.po b/WordPress/metadata/PlayStoreStrings.po index 2c9ea203e4e1..cacae63dd61d 100644 --- a/WordPress/metadata/PlayStoreStrings.po +++ b/WordPress/metadata/PlayStoreStrings.po @@ -10,6 +10,15 @@ msgstr "" "X-Generator: VsCode\n" "Project-Id-Version: Release Notes & Play Store Descriptions\n" +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 "" + msgctxt "release_note_240" msgid "" "24.0:\n" @@ -20,15 +29,6 @@ msgid "" "- Share different media types to the app at once without crashing\n" msgstr "" -msgctxt "release_note_239" -msgid "" -"23.9:\n" -"No updates this week! Enjoy these reindeer facts:\n" -"- Male reindeer shed their antlers in fall and females in spring, so Santa’s reindeer are female.\n" -"- Reindeer don’t actually have red noses.\n" -"- Santa hasn’t eaten milk and cookies since 2003 because Blitzen always gets there first.\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 33452ef2e9ee..ab3a2cba51aa 100644 --- a/WordPress/metadata/release_notes.txt +++ b/WordPress/metadata/release_notes.txt @@ -1,5 +1,4 @@ -Block Editor updates: -- Device keyboard remains when editing text blocks -- Screen auto-scrolls to newly inserted blocks -- Unselect blocks by pressing your device’s back button -- Share different media types to the app at once without crashing +- Get notified when you’re working offline. +- Image block uploads pause when you lose internet and resume when you reconnect. +- Select a custom gradient in the editor and see a color indicator. +- “Forward” and “back” arrows are correct for right-to-left readers. diff --git a/WordPress/src/debug/AndroidManifest.xml b/WordPress/src/debug/AndroidManifest.xml index a68f9e14c56f..e961d134552f 100644 --- a/WordPress/src/debug/AndroidManifest.xml +++ b/WordPress/src/debug/AndroidManifest.xml @@ -34,6 +34,9 @@ + - diff --git a/WordPress/src/main/AndroidManifest.xml b/WordPress/src/main/AndroidManifest.xml index 56d049968bb8..c866c2b082f2 100644 --- a/WordPress/src/main/AndroidManifest.xml +++ b/WordPress/src/main/AndroidManifest.xml @@ -196,6 +196,11 @@ android:configChanges="locale|orientation|screenSize" android:label="@string/me_btn_app_settings" android:theme="@style/WordPress.NoActionBar" /> + + SelectOptionButton( + labelResourceId = item, + onClick = {} + ) + } + } + } + } +} + +@Composable +fun SelectOptionButton( + @StringRes labelResourceId: Int, + onClick: () -> Unit, + modifier: Modifier = Modifier +){ + Button( + onClick = onClick, + modifier = modifier.widthIn(min = 250.dp), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary) + ) { + Text(stringResource(labelResourceId)) + } +} + +@Preview +@Composable +fun StartDesignSystemPreview() { + DesignSystemStartScreen( + modifier = Modifier + .fillMaxSize() + .padding(dimensionResource(R.dimen.button_container_shadow_height)) + ) +} +@Composable +fun DesignSystem() { + DesignSystemStartScreen( + modifier = Modifier + .fillMaxSize() + .padding(dimensionResource(R.dimen.button_container_shadow_height)) + ) +} diff --git a/WordPress/src/main/java/org/wordpress/android/localcontentmigration/UserFlagsProviderHelper.kt b/WordPress/src/main/java/org/wordpress/android/localcontentmigration/UserFlagsProviderHelper.kt index fb11f809225b..839474dc2f14 100644 --- a/WordPress/src/main/java/org/wordpress/android/localcontentmigration/UserFlagsProviderHelper.kt +++ b/WordPress/src/main/java/org/wordpress/android/localcontentmigration/UserFlagsProviderHelper.kt @@ -66,7 +66,6 @@ class UserFlagsProviderHelper @Inject constructor( DeletablePrefKey.RECENTLY_PICKED_SITE_IDS.name, UndeletablePrefKey.THEME_IMAGE_SIZE_WIDTH.name, UndeletablePrefKey.BOOKMARKS_SAVED_LOCALLY_DIALOG_SHOWN.name, - UndeletablePrefKey.IMAGE_OPTIMIZE_PROMO_REQUIRED.name, UndeletablePrefKey.SWIPE_TO_NAVIGATE_NOTIFICATIONS.name, UndeletablePrefKey.SWIPE_TO_NAVIGATE_READER.name, UndeletablePrefKey.SHOULD_SHOW_STORIES_INTRO.name, 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 f9d3048c03d6..ab51fe1aad2e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java @@ -54,6 +54,7 @@ import org.wordpress.android.ui.comments.unified.UnifiedCommentsActivity; import org.wordpress.android.ui.comments.unified.UnifiedCommentsDetailsActivity; import org.wordpress.android.ui.debug.cookies.DebugCookiesActivity; +import org.wordpress.android.ui.debug.preferences.DebugSharedPreferenceFlagsActivity; import org.wordpress.android.ui.domains.DomainRegistrationActivity; import org.wordpress.android.ui.domains.DomainRegistrationActivity.DomainRegistrationPurpose; import org.wordpress.android.ui.domains.DomainsDashboardActivity; @@ -1803,6 +1804,9 @@ public static void viewDebugCookies(@NonNull Context context) { context.startActivity(new Intent(context, DebugCookiesActivity.class)); } + public static void viewDebugSharedPreferenceFlags(@NonNull Context context) { + context.startActivity(new Intent(context, DebugSharedPreferenceFlagsActivity.class)); + } public static void startQRCodeAuthFlow(@NonNull Context context) { QRCodeAuthActivity.start(context); } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/bloggingprompts/BloggingPromptsPostTagProvider.kt b/WordPress/src/main/java/org/wordpress/android/ui/bloggingprompts/BloggingPromptsPostTagProvider.kt index 4aef7e8c43dd..9bac2972def3 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/bloggingprompts/BloggingPromptsPostTagProvider.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/bloggingprompts/BloggingPromptsPostTagProvider.kt @@ -10,15 +10,15 @@ class BloggingPromptsPostTagProvider @Inject constructor( private val readerUtilsWrapper: ReaderUtilsWrapper, ) { fun promptIdTag( - tagUrl: String - ): String = readerUtilsWrapper.getTagFromTagUrl(tagUrl) - .takeIf { it.isNotBlank() } + id: Int + ): String = "$BLOGGING_PROMPT_ID_TAG_PREFIX$id" + .takeIf { id > 0 } ?: BLOGGING_PROMPT_TAG - fun promptIdSearchReaderTag( + fun promptSearchReaderTag( tagUrl: String ): ReaderTag { - val promptIdTag = promptIdTag(tagUrl) + val promptIdTag = promptTagFromUrl(tagUrl) return ReaderTag( promptIdTag, promptIdTag, @@ -28,8 +28,15 @@ class BloggingPromptsPostTagProvider @Inject constructor( ) } + private fun promptTagFromUrl( + tagUrl: String + ): String = readerUtilsWrapper.getTagFromTagUrl(tagUrl) + .takeIf { it.isNotBlank() } + ?: BLOGGING_PROMPT_TAG + companion object { const val BLOGGING_PROMPT_TAG = "dailyprompt" + const val BLOGGING_PROMPT_ID_TAG_PREFIX = "dailyprompt-" const val BLOGANUARY_TAG = "bloganuary" } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/debug/DebugSettingsActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/debug/DebugSettingsActivity.kt index 11320aa5fb45..bd2ed4d6c75d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/debug/DebugSettingsActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/debug/DebugSettingsActivity.kt @@ -45,6 +45,8 @@ class DebugSettingsActivity : LocaleAwareActivity() { is DebugSettingsViewModel.NavigationAction.PreviewFragment -> { previewFragmentInActivity(it.name) } + is DebugSettingsViewModel.NavigationAction.DebugFlags -> + ActivityLauncher.viewDebugSharedPreferenceFlags(this@DebugSettingsActivity) } } } @@ -68,6 +70,7 @@ class DebugSettingsActivity : LocaleAwareActivity() { R.id.menu_debug_cookies -> viewModel.onDebugCookiesClick() R.id.menu_restart_app -> viewModel.onRestartAppClick() R.id.menu_show_weekly_notifications -> viewModel.onForceShowWeeklyRoundupClick() + R.id.menu_debug_flags -> viewModel.onDebugFlagsClick() } return true } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/debug/DebugSettingsViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/debug/DebugSettingsViewModel.kt index 86c0d2055977..754d291afd70 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/debug/DebugSettingsViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/debug/DebugSettingsViewModel.kt @@ -87,6 +87,10 @@ class DebugSettingsViewModel } } + fun onDebugFlagsClick() { + _onNavigation.value = Event(NavigationAction.DebugFlags) + } + private fun buildDevelopedFeatures(): List { return FeaturesInDevelopment.featuresInDevelopment.map { name -> val value = if (manualFeatureConfig.hasManualSetup(name)) { @@ -156,6 +160,7 @@ class DebugSettingsViewModel sealed class NavigationAction { object DebugCookies : NavigationAction() + object DebugFlags : NavigationAction() data class PreviewFragment(val name: String) : NavigationAction() } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/debug/preferences/DebugSharedPreferenceFlagsActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/debug/preferences/DebugSharedPreferenceFlagsActivity.kt new file mode 100644 index 000000000000..bffe6787bee3 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/debug/preferences/DebugSharedPreferenceFlagsActivity.kt @@ -0,0 +1,29 @@ +package org.wordpress.android.ui.debug.preferences + +import android.os.Bundle +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import dagger.hilt.android.AndroidEntryPoint +import org.wordpress.android.ui.domains.management.M3Theme +import org.wordpress.android.util.extensions.setContent + +@AndroidEntryPoint +class DebugSharedPreferenceFlagsActivity : AppCompatActivity() { + private val viewModel: DebugSharedPreferenceFlagsViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + M3Theme { + val uiState by viewModel.uiStateFlow.collectAsState() + DebugSharedPreferenceFlagsScreen( + flags = uiState, + onFlagChanged = viewModel::setFlag, + onBackTapped = { onBackPressedDispatcher.onBackPressed() }, + ) + } + } + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/debug/preferences/DebugSharedPreferenceFlagsScreen.kt b/WordPress/src/main/java/org/wordpress/android/ui/debug/preferences/DebugSharedPreferenceFlagsScreen.kt new file mode 100644 index 000000000000..0ce6fde1c676 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/debug/preferences/DebugSharedPreferenceFlagsScreen.kt @@ -0,0 +1,95 @@ +package org.wordpress.android.ui.debug.preferences + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Checkbox +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment.Companion.CenterVertically +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.wordpress.android.R +import org.wordpress.android.ui.compose.components.MainTopAppBar +import org.wordpress.android.ui.compose.components.NavigationIcons +import org.wordpress.android.ui.domains.management.M3Theme + +@Composable +fun DebugSharedPreferenceFlagsScreen( + flags: Map, + onBackTapped: () -> Unit, + onFlagChanged: (String, Boolean) -> Unit, +) { + Scaffold( + topBar = { + MainTopAppBar( + title = stringResource(R.string.debug_settings_debug_flags_screen), + navigationIcon = NavigationIcons.BackIcon, + onNavigationIconClick = onBackTapped, + backgroundColor = MaterialTheme.colorScheme.surface, + contentColor = MaterialTheme.colorScheme.onSurface, + ) + }, + ) { paddingValues -> + LazyColumn(modifier = Modifier.padding(paddingValues)) { + items(flags.toList()) { (key, value) -> + DebugFlagRow( + key = key, + value = value, + onFlagChanged = onFlagChanged, + ) + } + } + } +} + +@Composable +fun DebugFlagRow( + key: String, + value: Boolean, + onFlagChanged: (String, Boolean) -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .padding(vertical = 4.dp) + .clickable { onFlagChanged(key, !value) } + ) { + Text( + text = key, + modifier = Modifier + .weight(1f) + .align(CenterVertically) + .padding(start = 16.dp) + ) + Checkbox( + checked = value, + modifier = Modifier.align(CenterVertically).padding(end = 8.dp), + onCheckedChange = { onFlagChanged(key, it) } + ) + } +} + +@Preview +@Composable +fun DebugFlagsScreenPreview() { + M3Theme { + DebugSharedPreferenceFlagsScreen( + flags = mapOf( + "EXAMPLE_FEATURE_FLAG" to true, + "RANDOM_FLAG" to false, + "ANOTHER_FLAG" to true, + "YET_ANOTHER_FLAG" to false, + + ), + onBackTapped = {}, + onFlagChanged = { _, _ -> }, + ) + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/debug/preferences/DebugSharedPreferenceFlagsViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/debug/preferences/DebugSharedPreferenceFlagsViewModel.kt new file mode 100644 index 000000000000..b7c99e4487e6 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/debug/preferences/DebugSharedPreferenceFlagsViewModel.kt @@ -0,0 +1,28 @@ +package org.wordpress.android.ui.debug.preferences + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import org.wordpress.android.ui.prefs.AppPrefsWrapper +import javax.inject.Inject + +@HiltViewModel +class DebugSharedPreferenceFlagsViewModel @Inject constructor( + private val prefsWrapper: AppPrefsWrapper +) : ViewModel() { + private val _uiStateFlow = MutableStateFlow>(emptyMap()) + val uiStateFlow = _uiStateFlow.asStateFlow() + + init { + val flags = prefsWrapper.getAllPrefs().mapNotNull { (key, value) -> + if (value is Boolean) key to value else null + }.toMap() + _uiStateFlow.value = flags + } + + fun setFlag(key: String, value: Boolean) { + prefsWrapper.putBoolean({ key }, value) + _uiStateFlow.value = _uiStateFlow.value.toMutableMap().apply { this[key] = value } + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardFragment.kt index bb60d9f91380..e2249a88f91c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardFragment.kt @@ -25,6 +25,7 @@ import org.wordpress.android.ui.domains.DomainRegistrationActivity.DomainRegistr import org.wordpress.android.ui.domains.DomainsDashboardNavigationAction.ClaimDomain import org.wordpress.android.ui.domains.DomainsDashboardNavigationAction.GetDomain import org.wordpress.android.ui.domains.DomainsDashboardNavigationAction.GetPlan +import org.wordpress.android.ui.domains.management.details.DomainManagementDetailsActivity import org.wordpress.android.ui.utils.UiHelpers import org.wordpress.android.util.config.DomainManagementFeatureConfig import org.wordpress.android.util.extensions.getSerializableExtraCompat @@ -100,16 +101,22 @@ class DomainsDashboardFragment : Fragment(R.layout.domains_dashboard_fragment), action.site, DOMAIN_PURCHASE ) + is ClaimDomain -> ActivityLauncher.viewDomainRegistrationActivityForResult( this, action.site, CTA_DOMAIN_CREDIT_REDEMPTION ) + is GetPlan -> ActivityLauncher.viewDomainRegistrationActivityForResult( this, action.site, FREE_DOMAIN_WITH_ANNUAL_PLAN ) + + is DomainsDashboardNavigationAction.OpenDomainManagement -> startActivity( + DomainManagementDetailsActivity.createIntent(requireContext(), action.domain, action.detailUrl) + ) } @Suppress("DEPRECATION", "OVERRIDE_DEPRECATION") diff --git a/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardItem.kt b/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardItem.kt index 7b21d0734492..bfcc350cf475 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardItem.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardItem.kt @@ -1,5 +1,6 @@ package org.wordpress.android.ui.domains +import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import org.wordpress.android.ui.domains.DomainsDashboardItem.Type.ADD_DOMAIN import org.wordpress.android.ui.domains.DomainsDashboardItem.Type.PURCHASE_DOMAIN @@ -22,8 +23,11 @@ sealed class DomainsDashboardItem(val type: Type) { data class SiteDomains( val domain: UiString, - val expiry: UiString, val isPrimary: Boolean, + val domainStatus: UiString, + @ColorRes val domainStatusColor: Int, + val expiry: UiString?, + val onDomainClick: ListItemInteraction? = null ) : DomainsDashboardItem(SITE_DOMAINS) data class AddDomain(val onClick: ListItemInteraction) : DomainsDashboardItem(ADD_DOMAIN) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardNavigationAction.kt b/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardNavigationAction.kt index 60d32b8105d5..722c664ef9bd 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardNavigationAction.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardNavigationAction.kt @@ -3,6 +3,7 @@ package org.wordpress.android.ui.domains import org.wordpress.android.fluxc.model.SiteModel sealed class DomainsDashboardNavigationAction { + data class OpenDomainManagement(val domain: String, val detailUrl: String) : DomainsDashboardNavigationAction() data class GetDomain(val site: SiteModel) : DomainsDashboardNavigationAction() data class ClaimDomain(val site: SiteModel) : DomainsDashboardNavigationAction() data class GetPlan(val site: SiteModel) : DomainsDashboardNavigationAction() diff --git a/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardViewHolder.kt b/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardViewHolder.kt index 4b7038a6a13b..d41bc35105d9 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardViewHolder.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardViewHolder.kt @@ -41,6 +41,11 @@ sealed class DomainsDashboardViewHolder( uiHelpers.setTextOrHide(siteDomain, item.domain) uiHelpers.setTextOrHide(siteDomainExpiryDate, item.expiry) primarySiteDomainChip.isVisible = item.isPrimary + uiHelpers.setTextOrHide(siteDomainStatus, item.domainStatus) + siteDomainStatus.compoundDrawablesRelative.first().setTint( + siteDomainStatus.context.getColor(item.domainStatusColor) + ) + item.onDomainClick?.let { interaction -> root.setOnClickListener { interaction.click() } } } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardViewModel.kt index 10133b6a9206..9c4992f6cb95 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/domains/DomainsDashboardViewModel.kt @@ -12,7 +12,9 @@ import org.wordpress.android.analytics.AnalyticsTracker.Stat.DOMAINS_DASHBOARD_V import org.wordpress.android.analytics.AnalyticsTracker.Stat.DOMAIN_CREDIT_REDEMPTION_TAPPED import org.wordpress.android.fluxc.model.PlanModel import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.network.rest.wpcom.site.AllDomainsDomain import org.wordpress.android.fluxc.network.rest.wpcom.site.Domain +import org.wordpress.android.fluxc.network.rest.wpcom.site.StatusType import org.wordpress.android.fluxc.store.SiteStore import org.wordpress.android.modules.BG_THREAD import org.wordpress.android.ui.domains.DomainsDashboardItem.AddDomain @@ -23,6 +25,10 @@ import org.wordpress.android.ui.domains.DomainsDashboardItem.SiteDomainsHeader import org.wordpress.android.ui.domains.DomainsDashboardNavigationAction.ClaimDomain import org.wordpress.android.ui.domains.DomainsDashboardNavigationAction.GetDomain import org.wordpress.android.ui.domains.DomainsDashboardNavigationAction.GetPlan +import org.wordpress.android.ui.domains.DomainsDashboardNavigationAction.OpenDomainManagement +import org.wordpress.android.ui.domains.management.getDomainDetailsUrl +import org.wordpress.android.ui.domains.usecases.AllDomains +import org.wordpress.android.ui.domains.usecases.FetchAllDomainsUseCase import org.wordpress.android.ui.domains.usecases.FetchPlansUseCase import org.wordpress.android.ui.plans.isDomainCreditAvailable import org.wordpress.android.ui.utils.HtmlMessageUtils @@ -46,6 +52,7 @@ class DomainsDashboardViewModel @Inject constructor( private val analyticsTrackerWrapper: AnalyticsTrackerWrapper, private val htmlMessageUtils: HtmlMessageUtils, private val fetchPlansUseCase: FetchPlansUseCase, + private val fetchAllDomainsUseCase: FetchAllDomainsUseCase, @Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher ) : ScopedViewModel(bgDispatcher) { lateinit var site: SiteModel @@ -80,9 +87,11 @@ class DomainsDashboardViewModel @Inject constructor( val deferredPlansResult = async { fetchPlansUseCase.execute(site) } val deferredDomainsResult = async { siteStore.fetchSiteDomains(site) } + val deferredAllDomainsResult = async { fetchAllDomainsUseCase.execute() } val plansResult = deferredPlansResult.await() val domainsResult = deferredDomainsResult.await() + val allDomainsResult = deferredAllDomainsResult.await() if (plansResult.isError) { AppLog.e(DOMAIN_REGISTRATION, "An error occurred while fetching plans: ${plansResult.error.message}") @@ -92,10 +101,17 @@ class DomainsDashboardViewModel @Inject constructor( AppLog.e(DOMAIN_REGISTRATION, "An error occurred while fetching domains: ${domainsResult.error.message}") } - buildDashboardItems(site, plansResult.plans.orEmpty(), domainsResult.domains.orEmpty()) + val allDomains = if (allDomainsResult is AllDomains.Success) allDomainsResult.domains else emptyList() + + buildDashboardItems(site, plansResult.plans.orEmpty(), domainsResult.domains.orEmpty(), allDomains) } - private fun buildDashboardItems(site: SiteModel, plans: List, domains: List) { + private fun buildDashboardItems( + site: SiteModel, + plans: List, + domains: List, + allDomains: List + ) { val listItems = mutableListOf() listItems += SiteDomainsHeader(UiStringRes(R.string.domains_free_domain)) @@ -106,8 +122,10 @@ class DomainsDashboardViewModel @Inject constructor( listItems += SiteDomains( UiStringText(freeDomainUrl), - UiStringRes(R.string.domains_site_domain_never_expires), - freeDomainIsPrimary + freeDomainIsPrimary, + UiStringRes(R.string.active), + getStatusColor(StatusType.SUCCESS), + UiStringRes(R.string.domains_site_domain_never_expires) ) val customDomains = domains.filter { !it.wpcomDomain } @@ -116,7 +134,7 @@ class DomainsDashboardViewModel @Inject constructor( val hasPaidPlan = !SiteUtils.onFreePlan(site) if (hasCustomDomains) { - listItems += buildCustomDomainItems(site, customDomains) + listItems += buildCustomDomainItems(site, customDomains, allDomains) } listItems += buildCtaItems(hasCustomDomains, hasDomainCredit, hasPaidPlan) @@ -125,6 +143,13 @@ class DomainsDashboardViewModel @Inject constructor( _uiModel.postValue(listItems) } + private fun getStatusColor(statusType: StatusType?) = when (statusType) { + StatusType.SUCCESS -> R.color.jetpack_green_50 + StatusType.NEUTRAL -> R.color.gray_50 + StatusType.WARNING -> R.color.orange_50 + else -> R.color.red_50 + } + private fun buildCtaItems( hasCustomDomains: Boolean, hasDomainCredit: Boolean, @@ -161,7 +186,11 @@ class DomainsDashboardViewModel @Inject constructor( return listItems } - private fun buildCustomDomainItems(site: SiteModel, customDomains: List): List { + private fun buildCustomDomainItems( + site: SiteModel, + customDomains: List, + allDomains: List + ): List { val listItems = mutableListOf() listItems += SiteDomainsHeader( UiStringResWithParams( @@ -170,9 +199,18 @@ class DomainsDashboardViewModel @Inject constructor( ) ) listItems += customDomains.map { + val allDomainsDomain = allDomains.find { allDomainsItem -> it.domain == allDomainsItem.domain } + SiteDomains( UiStringText(it.domain.orEmpty()), - if (it.expirySoon) { + it.primaryDomain, + allDomainsDomain?.domainStatus?.status?.let { status -> + UiStringText(status) + } ?: UiStringRes(R.string.error), + getStatusColor(allDomainsDomain?.domainStatus?.statusType), + if (!it.hasRegistration) { + null + } else if (it.expirySoon) { UiStringText( htmlMessageUtils.getHtmlMessageFromStringFormatResId( R.string.domains_site_domain_expires_soon, @@ -185,7 +223,7 @@ class DomainsDashboardViewModel @Inject constructor( listOf(UiStringText(it.expiry.orEmpty())) ) }, - it.primaryDomain + allDomainsDomain?.let { ListItemInteraction.create(allDomainsDomain, this::onDomainClick) } ) } return listItems @@ -193,6 +231,15 @@ class DomainsDashboardViewModel @Inject constructor( private fun getCleanUrl(url: String?) = StringUtils.removeTrailingSlash(UrlUtils.removeScheme(url)) + private fun onDomainClick(allDomainsDomain: AllDomainsDomain) { + _onNavigation.value = Event( + OpenDomainManagement( + allDomainsDomain.domain ?: return, + allDomainsDomain.getDomainDetailsUrl() ?: return + ) + ) + } + private fun onGetDomainClick() { analyticsTrackerWrapper.track(DOMAINS_DASHBOARD_GET_DOMAIN_TAPPED, site) _onNavigation.value = Event(GetDomain(site)) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/domains/usecases/FetchAllDomainsUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/domains/usecases/FetchAllDomainsUseCase.kt index f1849cde1e61..4f7c6f50e261 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/domains/usecases/FetchAllDomainsUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/domains/usecases/FetchAllDomainsUseCase.kt @@ -2,6 +2,7 @@ package org.wordpress.android.ui.domains.usecases import org.wordpress.android.fluxc.network.rest.wpcom.site.AllDomainsDomain import org.wordpress.android.fluxc.store.SiteStore +import org.wordpress.android.util.AppLog import javax.inject.Inject class FetchAllDomainsUseCase @Inject constructor( @@ -10,7 +11,11 @@ class FetchAllDomainsUseCase @Inject constructor( suspend fun execute(): AllDomains { val result = siteStore.fetchAllDomains() return when { - result.isError -> AllDomains.Error + result.isError -> { + AppLog.e(AppLog.T.API, "An error occurred while fetching all domains: ${result.error.message}") + AllDomains.Error + } + result.domains.isNullOrEmpty() -> AllDomains.Empty else -> AllDomains.Success(requireNotNull(result.domains)) } @@ -22,7 +27,7 @@ sealed interface AllDomains { val domains: List, ) : AllDomains - object Empty : AllDomains + data object Empty : AllDomains - object Error : AllDomains + data object Error : AllDomains } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/jetpackoverlay/JetpackFeatureRemovalPhaseHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/jetpackoverlay/JetpackFeatureRemovalPhaseHelper.kt index 878340d521d4..49ec25df3d7f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/jetpackoverlay/JetpackFeatureRemovalPhaseHelper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/jetpackoverlay/JetpackFeatureRemovalPhaseHelper.kt @@ -86,13 +86,8 @@ class JetpackFeatureRemovalPhaseHelper @Inject constructor( } } - fun shouldShowStoryPost(): Boolean { - val currentPhase = getCurrentPhase() ?: return true - return when (currentPhase) { - is PhaseStaticPosters, PhaseFour, PhaseNewUsers, PhaseSelfHostedUsers -> false - else -> true - } - } + @Suppress("FunctionOnlyReturningConstant") + fun shouldShowStoryPost(): Boolean = false fun shouldShowJetpackPoweredEditorFeatures(): Boolean { val currentPhase = getCurrentPhase() ?: return true diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/MeFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/main/MeFragment.kt index c1ed56e074f4..0528ccae8684 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/MeFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/MeFragment.kt @@ -37,6 +37,7 @@ import org.wordpress.android.analytics.AnalyticsTracker.Stat.ME_GRAVATAR_SHOT_NE import org.wordpress.android.analytics.AnalyticsTracker.Stat.ME_GRAVATAR_TAPPED import org.wordpress.android.analytics.AnalyticsTracker.Stat.ME_GRAVATAR_UPLOADED import org.wordpress.android.databinding.MeFragmentBinding +import org.wordpress.android.designsystem.DesignSystemActivity import org.wordpress.android.fluxc.Dispatcher import org.wordpress.android.fluxc.store.AccountStore import org.wordpress.android.fluxc.store.AccountStore.OnAccountChanged @@ -208,6 +209,11 @@ class MeFragment : Fragment(R.layout.me_fragment), OnScrollToTopListener { rowDebugSettings.setOnClickListener { requireContext().startActivity(Intent(requireContext(), DebugSettingsActivity::class.java)) } + rowDesignSystem.isVisible = true + designSystemDivider.isVisible = true + rowDesignSystem.setOnClickListener { + requireContext().startActivity(Intent(requireContext(), DesignSystemActivity::class.java)) + } } rowAboutTheApp.setOnClickListener { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaBrowserActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaBrowserActivity.java index 520773af12f0..d434f5024f09 100755 --- a/WordPress/src/main/java/org/wordpress/android/ui/media/MediaBrowserActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/media/MediaBrowserActivity.java @@ -508,24 +508,14 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { case RequestCodes.PICTURE_LIBRARY: case RequestCodes.VIDEO_LIBRARY: case RequestCodes.AUDIO_LIBRARY: - handlePickerResult(data, resultCode); - break; case RequestCodes.FILE_LIBRARY: if (resultCode == Activity.RESULT_OK && data != null) { - if (WPMediaUtils.shouldAdvertiseImageOptimization(this)) { - WPMediaUtils.advertiseImageOptimization(this, () -> handlePickerResult(data, resultCode)); - } else { - handlePickerResult(data, resultCode); - } + handlePickerResult(data, resultCode); } break; case RequestCodes.TAKE_PHOTO: if (resultCode == Activity.RESULT_OK) { - if (WPMediaUtils.shouldAdvertiseImageOptimization(this)) { - WPMediaUtils.advertiseImageOptimization(this, this::addLastTakenPicture); - } else { - addLastTakenPicture(); - } + addLastTakenPicture(); } break; case RequestCodes.TAKE_VIDEO: diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteCardAndItem.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteCardAndItem.kt index 81026f4db9a4..9c5f226eda9c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteCardAndItem.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteCardAndItem.kt @@ -176,6 +176,7 @@ sealed class MySiteCardAndItem(open val type: Type, open val activeQuickStartIte val views: UiString, val visitors: UiString, val likes: UiString, + val comments: UiString, val onCardClick: () -> Unit, val message: TextWithLinks? = null, val moreMenuOptions: MoreMenuOptions diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptAttribution.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptAttribution.kt index 178c7da39b36..dd052afc58ac 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptAttribution.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptAttribution.kt @@ -13,7 +13,7 @@ enum class BloggingPromptAttribution( NO_ATTRIBUTION("", -1, -1), DAY_ONE( "dayone", - R.string.my_site_blogging_prompt_card_attribution_dayone, + R.string.my_site_blogging_prompt_card_attribution_day_one, R.drawable.ic_dayone_24dp, ), BLOGANUARY( diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptCardViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptCardViewModelSlice.kt index 93bc45fe6f0a..5aa88a57aff7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptCardViewModelSlice.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptCardViewModelSlice.kt @@ -95,7 +95,7 @@ class BloggingPromptCardViewModelSlice @Inject constructor( private fun onBloggingPromptViewAnswersClick(tagUrl: String) { bloggingPromptsCardAnalyticsTracker.trackMySiteCardViewAnswersClicked() - val tag = bloggingPromptsPostTagProvider.promptIdSearchReaderTag(tagUrl) + val tag = bloggingPromptsPostTagProvider.promptSearchReaderTag(tagUrl) _onNavigation.value = Event(BloggingPromptCardNavigationAction.ViewAnswers(tag)) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsCardBuilder.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsCardBuilder.kt index 58409e5892f2..75cf4cefb5e7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsCardBuilder.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsCardBuilder.kt @@ -47,6 +47,7 @@ class TodaysStatsCardBuilder @Inject constructor( views = statToUiString(model.views), visitors = statToUiString(model.visitors), likes = statToUiString(model.likes), + comments = statToUiString(model.comments), onCardClick = params.onTodaysStatsCardClick, message = model.takeIf { it.isEmptyStats() }?.let { TextWithLinks( diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsCardViewHolder.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsCardViewHolder.kt index 94418e26468a..97a575f78424 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsCardViewHolder.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsCardViewHolder.kt @@ -44,6 +44,7 @@ class TodaysStatsCardViewHolder( uiHelpers.setTextOrHide(viewsCount, card.views) uiHelpers.setTextOrHide(visitorsCount, card.visitors) uiHelpers.setTextOrHide(likesCount, card.likes) + uiHelpers.setTextOrHide(commentsCount, card.comments) uiHelpers.setTextOrHide(getMoreViewsMessage, card.message?.text) card.message?.links?.let { getMoreViewsMessage.updateLink(it) } mySiteTodaysStatCard.setOnClickListener { 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 f37bd022b9ce..1604569719e9 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 @@ -261,9 +261,6 @@ import static org.wordpress.android.imageeditor.preview.PreviewImageFragment.PREVIEW_IMAGE_REDUCED_SIZE_FACTOR; import static org.wordpress.android.ui.history.HistoryDetailContainerFragment.KEY_REVISION; -import kotlin.Unit; -import kotlin.jvm.functions.Function0; - public class EditPostActivity extends LocaleAwareActivity implements EditorFragmentActivity, EditorImageSettingsListener, @@ -1310,7 +1307,7 @@ public void onPhotoPickerHidden() { @Override public void onPhotoPickerMediaChosen(@NonNull final List uriList) { mEditorPhotoPicker.hidePhotoPicker(); - mEditorMedia.onPhotoPickerMediaChosen(uriList); + mEditorMedia.addNewMediaItemsToEditorAsync(uriList, false); } /* @@ -2491,7 +2488,6 @@ private GutenbergPropsBuilder getGutenbergPropsBuilder() { false, false, false, - false, true, false, !isFreeWPCom, @@ -2514,7 +2510,6 @@ private GutenbergPropsBuilder getGutenbergPropsBuilder() { SiteUtils.supportsEmbedVariationFeature(mSite, SiteUtils.WP_INSTAGRAM_EMBED_JETPACK_VERSION), SiteUtils.supportsEmbedVariationFeature(mSite, SiteUtils.WP_LOOM_EMBED_JETPACK_VERSION), SiteUtils.supportsEmbedVariationFeature(mSite, SiteUtils.WP_SMARTFRAME_EMBED_JETPACK_VERSION), - SiteUtils.supportsStoriesFeature(mSite, mJetpackFeatureRemovalPhaseHelper), mSite.isUsingWpComRestApi(), enableXPosts, isUnsupportedBlockEditorEnabled, @@ -2843,12 +2838,6 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { // handleMediaPickerResult -> addExistingMediaToEditorAndSave break; case RequestCodes.PHOTO_PICKER: - if (WPMediaUtils.shouldAdvertiseImageOptimization(this)) { - WPMediaUtils.advertiseImageOptimization(this, () -> handlePhotoPickerResult(data)); - } else { - handlePhotoPickerResult(data); - } - break; case RequestCodes.STOCK_MEDIA_PICKER_SINGLE_SELECT: handlePhotoPickerResult(data); break; @@ -2862,18 +2851,12 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { break; case RequestCodes.MEDIA_LIBRARY: case RequestCodes.PICTURE_LIBRARY: - mEditorMedia.advertiseImageOptimisationAndAddMedia(WPMediaUtils.retrieveMediaUris(data)); - break; - case RequestCodes.TAKE_PHOTO: - if (WPMediaUtils.shouldAdvertiseImageOptimization(this)) { - WPMediaUtils.advertiseImageOptimization(this, this::addLastTakenPicture); - } else { - addLastTakenPicture(); - } - break; case RequestCodes.VIDEO_LIBRARY: mEditorMedia.addNewMediaItemsToEditorAsync(WPMediaUtils.retrieveMediaUris(data), false); break; + case RequestCodes.TAKE_PHOTO: + addLastTakenPicture(); + break; case RequestCodes.TAKE_VIDEO: Uri videoUri = data.getData(); mEditorMedia.addNewMediaToEditorAsync(videoUri, true); @@ -3348,7 +3331,7 @@ public boolean onMediaRetryClicked(final String mediaId) { mEditorMedia.retryFailedMediaAsync(Collections.singletonList(media.getId())); } - AnalyticsTracker.track(Stat.EDITOR_UPLOAD_MEDIA_RETRIED); + AnalyticsUtils.trackWithSiteDetails(Stat.EDITOR_UPLOAD_MEDIA_RETRIED, mSite); return true; } @@ -3683,6 +3666,11 @@ public void onMediaUploaded(OnMediaUploaded event) { return; } + if (event.isError() && !NetworkUtils.isNetworkAvailable(this)) { + mEditorMedia.onMediaUploadPaused(mEditorMediaUploadListener, event.media, event.error); + return; + } + // event for unknown media, ignoring if (event.media == null) { AppLog.w(AppLog.T.MEDIA, "Media event carries null media object, not recognized"); @@ -3701,7 +3689,10 @@ public void onMediaUploaded(OnMediaUploaded event) { mEditorMedia.onMediaUploadError(mEditorMediaUploadListener, event.media, event.error); } else if (event.completed) { // if the remote url on completed is null, we consider this upload wasn't successful - if (TextUtils.isEmpty(event.media.getUrl())) { + if (TextUtils.isEmpty(event.media.getUrl()) && !NetworkUtils.isNetworkAvailable(this)) { + MediaError error = new MediaError(MediaErrorType.GENERIC_ERROR); + mEditorMedia.onMediaUploadPaused(mEditorMediaUploadListener, event.media, error); + } else if (TextUtils.isEmpty(event.media.getUrl())) { MediaError error = new MediaError(MediaErrorType.GENERIC_ERROR); mEditorMedia.onMediaUploadError(mEditorMediaUploadListener, event.media, error); } else { @@ -3935,10 +3926,6 @@ public void syncPostObjectWithUiAndSaveIt(@Nullable OnPostUpdatedFromUIListener updateAndSavePostAsync(listener); } - @Override public void advertiseImageOptimization(@NonNull Function0 listener) { - WPMediaUtils.advertiseImageOptimization(this, listener::invoke); - } - @Override public void onMediaModelsCreatedFromOptimizedUris(@NonNull Map oldUriToMediaModels) { // no op - we're not doing any special handling on MediaModels in EditPostActivity diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditorBloggingPromptsViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditorBloggingPromptsViewModel.kt index d3c2edfceeb5..5904b86980bf 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditorBloggingPromptsViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditorBloggingPromptsViewModel.kt @@ -55,7 +55,7 @@ class EditorBloggingPromptsViewModel private fun createPromptTags(prompt: BloggingPromptModel): List = mutableListOf().apply { add(BloggingPromptsPostTagProvider.BLOGGING_PROMPT_TAG) - add(bloggingPromptsPostTagProvider.promptIdTag(prompt.answeredLink)) + add(bloggingPromptsPostTagProvider.promptIdTag(prompt.id)) prompt.bloganuaryId?.let { bloganuaryIdTag -> add(BloggingPromptsPostTagProvider.BLOGANUARY_TAG) add(bloganuaryIdTag) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/EditorMedia.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/EditorMedia.kt index 4332f516783a..658ad80e7bac 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/EditorMedia.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/EditorMedia.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.wordpress.android.R import org.wordpress.android.analytics.AnalyticsTracker.Stat.EDITOR_UPLOAD_MEDIA_FAILED +import org.wordpress.android.analytics.AnalyticsTracker.Stat.EDITOR_UPLOAD_MEDIA_PAUSED import org.wordpress.android.editor.EditorMediaUploadListener import org.wordpress.android.fluxc.Dispatcher import org.wordpress.android.fluxc.generated.MediaActionBuilder @@ -33,11 +34,11 @@ import org.wordpress.android.ui.posts.editor.media.EditorMedia.AddMediaToPostUiS import org.wordpress.android.ui.posts.editor.media.EditorMedia.AddMediaToPostUiState.AddingSingleMedia import org.wordpress.android.ui.uploads.UploadService import org.wordpress.android.ui.utils.UiString.UiStringRes -import org.wordpress.android.util.MediaUtilsWrapper import org.wordpress.android.util.NetworkUtilsWrapper import org.wordpress.android.util.StringUtils import org.wordpress.android.util.ToastUtils.Duration import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper +import org.wordpress.android.util.analytics.AnalyticsUtils import org.wordpress.android.util.analytics.AnalyticsUtilsWrapper import org.wordpress.android.viewmodel.Event import org.wordpress.android.viewmodel.SingleLiveEvent @@ -50,7 +51,6 @@ class EditorMedia @Inject constructor( private val updateMediaModelUseCase: UpdateMediaModelUseCase, private val getMediaModelUseCase: GetMediaModelUseCase, private val dispatcher: Dispatcher, - private val mediaUtilsWrapper: MediaUtilsWrapper, private val networkUtilsWrapper: NetworkUtilsWrapper, private val addLocalMediaToPostUseCase: AddLocalMediaToPostUseCase, private val addExistingMediaToPostUseCase: AddExistingMediaToPostUseCase, @@ -93,20 +93,6 @@ class EditorMedia @Inject constructor( _uiState.value = AddingMediaIdle } - // region Adding new media to a post - fun advertiseImageOptimisationAndAddMedia(uriList: List) { - if (mediaUtilsWrapper.shouldAdvertiseImageOptimization()) { - editorMediaListener.advertiseImageOptimization { - addNewMediaItemsToEditorAsync( - uriList, - false - ) - } - } else { - addNewMediaItemsToEditorAsync(uriList, false) - } - } - fun addNewMediaToEditorAsync(mediaUri: Uri, freshlyTaken: Boolean) { addNewMediaItemsToEditorAsync(listOf(mediaUri), freshlyTaken) } @@ -143,15 +129,6 @@ class EditorMedia @Inject constructor( ) } } - - fun onPhotoPickerMediaChosen(uriList: List) { - val onlyVideos = uriList.all { mediaUtilsWrapper.isVideo(it.toString()) } - if (onlyVideos) { - addNewMediaItemsToEditorAsync(uriList, false) - } else { - advertiseImageOptimisationAndAddMedia(uriList) - } - } // endregion // region Add existing media to a post @@ -294,10 +271,27 @@ class EditorMedia @Inject constructor( it["error_type"] = error.type.name } } - analyticsTrackerWrapper.track(EDITOR_UPLOAD_MEDIA_FAILED, properties) + AnalyticsUtils.trackWithSiteDetails( + analyticsTrackerWrapper, + EDITOR_UPLOAD_MEDIA_FAILED, + site, + properties) listener.onMediaUploadFailed(media.id.toString()) } + fun onMediaUploadPaused(listener: EditorMediaUploadListener, media: MediaModel, error: MediaError) = launch { + val properties: Map = withContext(bgDispatcher) { + analyticsUtilsWrapper + .getMediaProperties(media.isVideo, null, media.filePath) + .also { + it["error_type"] = error.type.name + } + } + + analyticsTrackerWrapper.track(EDITOR_UPLOAD_MEDIA_PAUSED, site, properties) + listener.onMediaUploadPaused(media.id.toString()) + } + sealed class AddMediaToPostUiState( val editorOverlayVisibility: Boolean, val progressDialogUiState: ProgressDialogUiState diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/EditorMediaListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/EditorMediaListener.kt index 4f40d9affda0..b597d9c3ea18 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/EditorMediaListener.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/EditorMediaListener.kt @@ -9,7 +9,6 @@ import org.wordpress.android.util.helpers.MediaFile interface EditorMediaListener { fun appendMediaFiles(mediaFiles: Map) fun syncPostObjectWithUiAndSaveIt(listener: OnPostUpdatedFromUIListener? = null) - fun advertiseImageOptimization(listener: () -> Unit) fun onMediaModelsCreatedFromOptimizedUris(oldUriToMediaFiles: Map) fun getImmutablePost(): PostImmutableModel fun showVideoDurationLimitWarning(fileName: String) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/RetryFailedMediaUploadUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/RetryFailedMediaUploadUseCase.kt index 1a00989a78ce..8ae1a20c0480 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/RetryFailedMediaUploadUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/media/RetryFailedMediaUploadUseCase.kt @@ -3,7 +3,10 @@ package org.wordpress.android.ui.posts.editor.media import dagger.Reusable import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.fluxc.model.MediaModel.MediaUploadState.QUEUED +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.store.SiteStore import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper +import org.wordpress.android.util.analytics.AnalyticsUtils import javax.inject.Inject @Reusable @@ -11,7 +14,8 @@ class RetryFailedMediaUploadUseCase @Inject constructor( private val getMediaModelUseCase: GetMediaModelUseCase, private val updateMediaModelUseCase: UpdateMediaModelUseCase, private val uploadMediaUseCase: UploadMediaUseCase, - private val tracker: AnalyticsTrackerWrapper + private val tracker: AnalyticsTrackerWrapper, + private val siteStore: SiteStore ) { suspend fun retryFailedMediaAsync( editorMediaListener: EditorMediaListener, @@ -33,7 +37,9 @@ class RetryFailedMediaUploadUseCase @Inject constructor( editorMediaListener, mediaModels ) - tracker.track(Stat.EDITOR_UPLOAD_MEDIA_RETRIED) + val siteId = editorMediaListener.getImmutablePost().localSiteId + val site: SiteModel? = siteStore.getSiteByLocalId(siteId) + AnalyticsUtils.trackWithSiteDetails(tracker, Stat.EDITOR_UPLOAD_MEDIA_RETRIED, site, null); } } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java index bfb0ae37d01a..eb9e37a9e514 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefs.java @@ -218,9 +218,6 @@ public enum UndeletablePrefKey implements PrefKey { BOOKMARKS_SAVED_LOCALLY_DIALOG_SHOWN, - // When we need to show the new image optimize promo dialog - IMAGE_OPTIMIZE_PROMO_REQUIRED, - // When we need to show the snackbar indicating how notifications can be navigated through SWIPE_TO_NAVIGATE_NOTIFICATIONS, @@ -603,14 +600,6 @@ public static void setBookmarksSavedLocallyDialogShown() { setBoolean(UndeletablePrefKey.BOOKMARKS_SAVED_LOCALLY_DIALOG_SHOWN, false); } - public static boolean isImageOptimizePromoRequired() { - return getBoolean(UndeletablePrefKey.IMAGE_OPTIMIZE_PROMO_REQUIRED, true); - } - - public static void setImageOptimizePromoRequired(boolean required) { - setBoolean(UndeletablePrefKey.IMAGE_OPTIMIZE_PROMO_REQUIRED, required); - } - /** * This method should only be used by specific client classes that need access to the persisted selected site * instance due to the fact that the in-memory selected site instance might not be yet available. diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/StatsFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/StatsFragment.kt index a30fba860869..0bd8572cf639 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/StatsFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/StatsFragment.kt @@ -43,13 +43,17 @@ import org.wordpress.android.ui.stats.refresh.utils.StatsSiteProvider.SiteUpdate import org.wordpress.android.ui.utils.UiHelpers import org.wordpress.android.util.JetpackBrandingUtils import org.wordpress.android.models.JetpackPoweredScreen +import org.wordpress.android.ui.stats.refresh.lists.StatsListViewModel.StatsSection.TRAFFIC import org.wordpress.android.util.WPSwipeToRefreshHelper +import org.wordpress.android.util.config.StatsTrafficTabFeatureConfig import org.wordpress.android.util.helpers.SwipeToRefreshHelper import org.wordpress.android.viewmodel.observeEvent import org.wordpress.android.widgets.WPSnackbar import javax.inject.Inject private val statsSections = listOf(INSIGHTS, DAYS, WEEKS, MONTHS, YEARS) +private val statsSectionsWithTrafficTab = listOf(TRAFFIC, INSIGHTS) +private var statsTrafficTabEnabled = false @AndroidEntryPoint class StatsFragment : Fragment(R.layout.stats_fragment), ScrollableViewInitializedListener { @@ -58,6 +62,10 @@ class StatsFragment : Fragment(R.layout.stats_fragment), ScrollableViewInitializ @Inject lateinit var jetpackBrandingUtils: JetpackBrandingUtils + + @Inject + lateinit var statsTrafficTabFeatureConfig: StatsTrafficTabFeatureConfig + private val viewModel: StatsViewModel by activityViewModels() private lateinit var swipeToRefreshHelper: SwipeToRefreshHelper private lateinit var selectedTabListener: SelectedTabListener @@ -91,6 +99,8 @@ class StatsFragment : Fragment(R.layout.stats_fragment), ScrollableViewInitializ } private fun StatsFragmentBinding.initializeViews() { + statsTrafficTabEnabled = statsTrafficTabFeatureConfig.isEnabled() + val adapter = StatsPagerAdapter(this@StatsFragment) statsPager.adapter = adapter statsPager.setPageTransformer( @@ -180,7 +190,11 @@ class StatsFragment : Fragment(R.layout.stats_fragment), ScrollableViewInitializ viewModel.selectedSection.observe(viewLifecycleOwner) { selectedSection -> selectedSection?.let { - handleSelectedSection(selectedSection) + if (statsTrafficTabEnabled) { + handleSelectedSectionWithTrafficTab(selectedSection) + } else { + handleSelectedSection(selectedSection) + } } } @@ -209,6 +223,30 @@ class StatsFragment : Fragment(R.layout.stats_fragment), ScrollableViewInitializ } } + @Suppress("MagicNumber") + private fun StatsFragmentBinding.handleSelectedSectionWithTrafficTab( + selectedSection: StatsSection + ) { + val position = when (selectedSection) { + TRAFFIC -> 0 + INSIGHTS -> 1 + DETAIL, + INSIGHT_DETAIL, + TOTAL_LIKES_DETAIL, + TOTAL_COMMENTS_DETAIL, + TOTAL_FOLLOWERS_DETAIL, + ANNUAL_STATS -> null + else -> null + } + position?.let { + if (statsPager.currentItem != position) { + tabLayout.removeOnTabSelectedListener(selectedTabListener) + statsPager.setCurrentItem(position, false) + tabLayout.addOnTabSelectedListener(selectedTabListener) + } + } + } + @Suppress("MagicNumber") private fun StatsFragmentBinding.handleSelectedSection( selectedSection: StatsSection @@ -225,6 +263,7 @@ class StatsFragment : Fragment(R.layout.stats_fragment), ScrollableViewInitializ TOTAL_COMMENTS_DETAIL, TOTAL_FOLLOWERS_DETAIL, ANNUAL_STATS -> null + else -> null } position?.let { if (statsPager.currentItem != position) { @@ -295,18 +334,20 @@ class StatsFragment : Fragment(R.layout.stats_fragment), ScrollableViewInitializ } class StatsPagerAdapter(private val parent: Fragment) : FragmentStateAdapter(parent) { - override fun getItemCount(): Int = statsSections.size + private val statsTabs = if (statsTrafficTabEnabled) statsSectionsWithTrafficTab else statsSections + override fun getItemCount(): Int = statsTabs.size override fun createFragment(position: Int): Fragment { - return StatsListFragment.newInstance(statsSections[position]) + return StatsListFragment.newInstance(statsTabs[position]) } fun getTabTitle(position: Int): CharSequence { - return parent.context?.getString(statsSections[position].titleRes).orEmpty() + return parent.context?.getString(statsTabs[position].titleRes).orEmpty() } } private class SelectedTabListener(val viewModel: StatsViewModel) : OnTabSelectedListener { + private val statsTabs = if (statsTrafficTabEnabled) statsSectionsWithTrafficTab else statsSections override fun onTabReselected(tab: Tab?) { // Do nothing } @@ -316,6 +357,6 @@ private class SelectedTabListener(val viewModel: StatsViewModel) : OnTabSelected } override fun onTabSelected(tab: Tab) { - viewModel.onSectionSelected(statsSections[tab.position]) + viewModel.onSectionSelected(statsTabs[tab.position]) } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/StatsViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/StatsViewModel.kt index 9217d1c7bb0e..2ede24a24638 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/StatsViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/StatsViewModel.kt @@ -18,6 +18,7 @@ import org.wordpress.android.analytics.AnalyticsTracker.Stat.STATS_PERIOD_DAYS_A import org.wordpress.android.analytics.AnalyticsTracker.Stat.STATS_PERIOD_MONTHS_ACCESSED import org.wordpress.android.analytics.AnalyticsTracker.Stat.STATS_PERIOD_WEEKS_ACCESSED import org.wordpress.android.analytics.AnalyticsTracker.Stat.STATS_PERIOD_YEARS_ACCESSED +import org.wordpress.android.analytics.AnalyticsTracker.Stat.STATS_TRAFFIC_ACCESSED import org.wordpress.android.fluxc.network.utils.StatsGranularity import org.wordpress.android.fluxc.store.DEFAULT_INSIGHTS import org.wordpress.android.fluxc.store.JETPACK_DEFAULT_INSIGHTS @@ -279,6 +280,7 @@ class StatsViewModel private fun trackSectionSelected(statsSection: StatsSection) { when (statsSection) { + StatsSection.TRAFFIC -> analyticsTracker.track(STATS_TRAFFIC_ACCESSED) StatsSection.INSIGHTS -> analyticsTracker.track(STATS_INSIGHTS_ACCESSED) StatsSection.DAYS -> analyticsTracker.trackGranular(STATS_PERIOD_DAYS_ACCESSED, StatsGranularity.DAYS) StatsSection.WEEKS -> analyticsTracker.trackGranular(STATS_PERIOD_WEEKS_ACCESSED, StatsGranularity.WEEKS) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/lists/StatsListFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/lists/StatsListFragment.kt index 4d48b4e45eed..fc1d01cef8d9 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/lists/StatsListFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/lists/StatsListFragment.kt @@ -184,6 +184,7 @@ class StatsListFragment : ViewPagerFragment(R.layout.stats_list_fragment) { StatsSection.TOTAL_COMMENTS_DETAIL -> TotalCommentsDetailListViewModel::class.java StatsSection.TOTAL_FOLLOWERS_DETAIL -> TotalFollowersDetailListViewModel::class.java StatsSection.ANNUAL_STATS, + StatsSection.TRAFFIC -> DaysListViewModel::class.java // Replace with TrafficListViewModel StatsSection.INSIGHTS -> InsightsListViewModel::class.java StatsSection.DAYS -> DaysListViewModel::class.java StatsSection.WEEKS -> WeeksListViewModel::class.java diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/lists/StatsListViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/lists/StatsListViewModel.kt index 2070d4e737b7..d6fd939a605d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/lists/StatsListViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/lists/StatsListViewModel.kt @@ -57,6 +57,7 @@ abstract class StatsListViewModel( private var isInitialized = false enum class StatsSection(@StringRes val titleRes: Int) { + TRAFFIC(R.string.stats_traffic), INSIGHTS(R.string.stats_insights), DAYS(R.string.stats_timeframe_days), WEEKS(R.string.stats_timeframe_weeks), diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/lists/sections/insights/usecases/TotalFollowersUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/lists/sections/insights/usecases/TotalFollowersUseCase.kt index 65cddd723572..28deb93cc99c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/lists/sections/insights/usecases/TotalFollowersUseCase.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/lists/sections/insights/usecases/TotalFollowersUseCase.kt @@ -19,7 +19,9 @@ import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Title import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.ValueWithChartItem import org.wordpress.android.ui.stats.refresh.lists.sections.insights.InsightUseCaseFactory import org.wordpress.android.ui.stats.refresh.utils.ActionCardHandler +import org.wordpress.android.ui.stats.refresh.utils.MILLION import org.wordpress.android.ui.stats.refresh.utils.StatsSiteProvider +import org.wordpress.android.ui.stats.refresh.utils.StatsUtils import org.wordpress.android.ui.stats.refresh.utils.trackWithType import org.wordpress.android.ui.utils.ListItemInteraction import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper @@ -36,7 +38,8 @@ class TotalFollowersUseCase @Inject constructor( private val totalStatsMapper: TotalStatsMapper, private val analyticsTracker: AnalyticsTrackerWrapper, private val actionCardHandler: ActionCardHandler, - private val useCaseMode: UseCaseMode + private val useCaseMode: UseCaseMode, + private val statsUtils: StatsUtils ) : StatelessUseCase(TOTAL_FOLLOWERS, mainDispatcher, bgDispatcher) { override fun buildLoadingItem(): List = listOf(TitleWithMore(R.string.stats_view_total_followers)) @@ -60,7 +63,10 @@ class TotalFollowersUseCase @Inject constructor( addActionCard(domainModel) val items = mutableListOf() items.add(buildTitle()) - items.add(ValueWithChartItem(value = domainModel.toString(), extraBottomMargin = true)) + items.add(ValueWithChartItem( + value = statsUtils.toFormattedString(domainModel, MILLION), + extraBottomMargin = true + )) if (totalStatsMapper.shouldShowFollowersGuideCard(domainModel)) { items.add(ListItemGuideCard(resourceProvider.getString(R.string.stats_insights_followers_guide_card))) } @@ -96,7 +102,8 @@ class TotalFollowersUseCase @Inject constructor( private val resourceProvider: ResourceProvider, private val totalStatsMapper: TotalStatsMapper, private val analyticsTracker: AnalyticsTrackerWrapper, - private val actionCardHandler: ActionCardHandler + private val actionCardHandler: ActionCardHandler, + private val statsUtils: StatsUtils ) : InsightUseCaseFactory { override fun build(useCaseMode: UseCaseMode) = TotalFollowersUseCase( mainDispatcher, @@ -107,7 +114,8 @@ class TotalFollowersUseCase @Inject constructor( totalStatsMapper, analyticsTracker, actionCardHandler, - useCaseMode + useCaseMode, + statsUtils ) } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/utils/SelectedSectionManager.kt b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/utils/SelectedSectionManager.kt index dc8774cd62ef..a3be1b5a3c3d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/utils/SelectedSectionManager.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/utils/SelectedSectionManager.kt @@ -48,6 +48,7 @@ fun StatsSection.toStatsGranularity(): StatsGranularity? { return when (this) { ANNUAL_STATS, DETAIL, TOTAL_LIKES_DETAIL, TOTAL_COMMENTS_DETAIL, TOTAL_FOLLOWERS_DETAIL, INSIGHTS -> null StatsSection.INSIGHT_DETAIL, + StatsSection.TRAFFIC -> DAYS // Replace with TRAFFIC when it's implemented StatsSection.DAYS -> DAYS StatsSection.WEEKS -> WEEKS StatsSection.MONTHS -> MONTHS diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/utils/StatsAnalyticsUtils.kt b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/utils/StatsAnalyticsUtils.kt index f5534852b869..95bb9b5e0f79 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/utils/StatsAnalyticsUtils.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/utils/StatsAnalyticsUtils.kt @@ -6,6 +6,7 @@ import org.wordpress.android.fluxc.network.utils.StatsGranularity import org.wordpress.android.fluxc.store.StatsStore.InsightType import org.wordpress.android.ui.stats.refresh.lists.StatsListViewModel.StatsSection import org.wordpress.android.ui.stats.refresh.lists.StatsListViewModel.StatsSection.INSIGHT_DETAIL +import org.wordpress.android.ui.stats.refresh.lists.StatsListViewModel.StatsSection.TRAFFIC import org.wordpress.android.ui.stats.refresh.lists.widget.configuration.StatsWidgetConfigureFragment.WidgetType import org.wordpress.android.ui.stats.refresh.lists.widget.configuration.StatsWidgetConfigureFragment.WidgetType.ALL_TIME_VIEWS import org.wordpress.android.ui.stats.refresh.lists.widget.configuration.StatsWidgetConfigureFragment.WidgetType.TODAY_VIEWS @@ -55,7 +56,7 @@ fun AnalyticsTrackerWrapper.trackViewsVisitorsChips(position: Int) { fun AnalyticsTrackerWrapper.trackWithSection(stat: Stat, section: StatsSection) { val property = when (section) { - StatsSection.DAYS -> DAYS_PROPERTY + StatsSection.DAYS, TRAFFIC -> DAYS_PROPERTY // Replace with TRAFFIC when it's implemented StatsSection.WEEKS -> WEEKS_PROPERTY StatsSection.MONTHS -> MONTHS_PROPERTY StatsSection.YEARS -> YEARS_PROPERTY diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/utils/StatsDateSelector.kt b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/utils/StatsDateSelector.kt index 1bf6aa2fc4ff..47227c520494 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/utils/StatsDateSelector.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stats/refresh/utils/StatsDateSelector.kt @@ -10,6 +10,7 @@ import org.wordpress.android.fluxc.network.utils.StatsGranularity.YEARS import org.wordpress.android.ui.stats.refresh.StatsViewModel.DateSelectorUiModel import org.wordpress.android.ui.stats.refresh.lists.StatsListViewModel.StatsSection import org.wordpress.android.ui.stats.refresh.lists.StatsListViewModel.StatsSection.INSIGHTS +import org.wordpress.android.ui.stats.refresh.lists.StatsListViewModel.StatsSection.TRAFFIC import org.wordpress.android.ui.stats.refresh.lists.sections.granular.SelectedDateProvider import org.wordpress.android.ui.stats.refresh.lists.sections.granular.SelectedDateProvider.SelectedDate import org.wordpress.android.util.perform @@ -78,7 +79,7 @@ constructor( StatsSection.TOTAL_FOLLOWERS_DETAIL, StatsSection.INSIGHTS, StatsSection.INSIGHT_DETAIL, - StatsSection.DAYS -> DAYS + StatsSection.DAYS, TRAFFIC -> DAYS // Replace with TRAFFIC when it's implemented StatsSection.WEEKS -> WEEKS StatsSection.MONTHS -> MONTHS StatsSection.ANNUAL_STATS, diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt index 4f6c7cdfa70e..50055bc6f87a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/StoryComposerActivity.kt @@ -81,7 +81,6 @@ import org.wordpress.android.util.ListUtils import org.wordpress.android.util.MediaUtils import org.wordpress.android.util.ToastUtils import org.wordpress.android.util.ToastUtils.Duration.LONG -import org.wordpress.android.util.WPMediaUtils import org.wordpress.android.util.WPPermissionUtils import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper import org.wordpress.android.util.analytics.AnalyticsUtilsWrapper @@ -346,7 +345,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), it.hasExtra(MediaPickerConstants.EXTRA_MEDIA_URIS) -> { data.getStringArrayExtra(MediaPickerConstants.EXTRA_MEDIA_URIS)?.let { val uriList: List = convertStringArrayIntoUrisList(it) - storyEditorMedia.onPhotoPickerMediaChosen(uriList) + storyEditorMedia.addNewMediaItemsToEditorAsync(uriList, false) } } it.hasExtra(MediaBrowserActivity.RESULT_IDS) -> { @@ -422,7 +421,7 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), val uriList: List = convertStringArrayIntoUrisList(it) if (uriList.isNotEmpty()) { - storyEditorMedia.onPhotoPickerMediaChosen(uriList) + storyEditorMedia.addNewMediaItemsToEditorAsync(uriList, false) } } } @@ -488,10 +487,6 @@ class StoryComposerActivity : ComposeLoopFrameActivity(), listener?.onPostUpdatedFromUI(null) } - override fun advertiseImageOptimization(listener: () -> Unit) { - WPMediaUtils.advertiseImageOptimization(this) { listener.invoke() } - } - override fun onMediaModelsCreatedFromOptimizedUris(oldUriToMediaFiles: Map) { // no op - we're not doing any special handling while composing, only when saving in the UploadBridge } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryEditorMedia.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryEditorMedia.kt index 4a7dac9dd852..1a91710c5bc6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryEditorMedia.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryEditorMedia.kt @@ -22,14 +22,12 @@ import org.wordpress.android.ui.stories.media.StoryEditorMedia.AddMediaToStoryPo import org.wordpress.android.ui.stories.media.StoryEditorMedia.AddMediaToStoryPostUiState.AddingMultipleMediaToStory import org.wordpress.android.ui.stories.media.StoryEditorMedia.AddMediaToStoryPostUiState.AddingSingleMediaToStory import org.wordpress.android.ui.utils.UiString.UiStringRes -import org.wordpress.android.util.MediaUtilsWrapper import org.wordpress.android.viewmodel.Event import javax.inject.Inject import javax.inject.Named import kotlin.coroutines.CoroutineContext class StoryEditorMedia @Inject constructor( - private val mediaUtilsWrapper: MediaUtilsWrapper, private val addLocalMediaToPostUseCase: AddLocalMediaToPostUseCase, private val addExistingMediaToPostUseCase: AddExistingMediaToPostUseCase, @Named(UI_THREAD) private val mainDispatcher: CoroutineDispatcher @@ -55,20 +53,6 @@ class StoryEditorMedia @Inject constructor( _uiState.value = AddingMediaToStoryIdle } - // region Adding new media to a post - fun advertiseImageOptimisationAndAddMedia(uriList: List) { - if (mediaUtilsWrapper.shouldAdvertiseImageOptimization()) { - editorMediaListener.advertiseImageOptimization { - addNewMediaItemsToEditorAsync( - uriList, - false - ) - } - } else { - addNewMediaItemsToEditorAsync(uriList, false) - } - } - fun addNewMediaItemsToEditorAsync(uriList: List, freshlyTaken: Boolean) { launch { _uiState.value = if (uriList.size > 1) { @@ -90,15 +74,6 @@ class StoryEditorMedia @Inject constructor( _uiState.value = AddingMediaToStoryIdle } } - - fun onPhotoPickerMediaChosen(uriList: List) { - val onlyVideos = uriList.all { mediaUtilsWrapper.isVideo(it.toString()) } - if (onlyVideos) { - addNewMediaItemsToEditorAsync(uriList, false) - } else { - advertiseImageOptimisationAndAddMedia(uriList) - } - } // endregion fun addExistingMediaToEditorAsync(source: AddExistingMediaSource, mediaIdList: List) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt index cc84f6fad6d1..77c7c52fb953 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/stories/media/StoryMediaSaveUploadBridge.kt @@ -133,10 +133,6 @@ class StoryMediaSaveUploadBridge @Inject constructor( listener?.onPostUpdatedFromUI(null) } - override fun advertiseImageOptimization(listener: () -> Unit) { - // no op - } - override fun onMediaModelsCreatedFromOptimizedUris(oldUriToMediaFiles: Map) { // in order to support Story editing capabilities, we save a serialized version of the Story slides // after their composedFrameFiles have been processed. diff --git a/WordPress/src/main/java/org/wordpress/android/ui/uploads/PostUploadNotifier.java b/WordPress/src/main/java/org/wordpress/android/ui/uploads/PostUploadNotifier.java index a205977aa93c..ca6d44d4494a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/uploads/PostUploadNotifier.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/uploads/PostUploadNotifier.java @@ -135,7 +135,11 @@ private synchronized void startOrUpdateForegroundNotification(@Nullable PostImmu updateNotificationBuilder(post); if (sNotificationData.mNotificationId == 0) { sNotificationData.mNotificationId = (new Random()).nextInt(); - mService.startForeground(sNotificationData.mNotificationId, mNotificationBuilder.build()); + try { + mService.startForeground(sNotificationData.mNotificationId, mNotificationBuilder.build()); + } catch (RuntimeException exception) { + AppLog.e(T.POSTS, "startOrUpdateForegroundNotification failed; See issue #18714", exception); + } } else { // service was already started, let's just modify the notification doNotify(sNotificationData.mNotificationId, mNotificationBuilder.build(), null); diff --git a/WordPress/src/main/java/org/wordpress/android/util/MediaUtilsWrapper.kt b/WordPress/src/main/java/org/wordpress/android/util/MediaUtilsWrapper.kt index 3ec86333c275..b906fbd95702 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/MediaUtilsWrapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/MediaUtilsWrapper.kt @@ -41,9 +41,6 @@ class MediaUtilsWrapper @Inject constructor(private val appContext: Context) { fun copyFileToAppStorage(imageUri: Uri, headers: Map? = null): Uri? = MediaUtils.downloadExternalMedia(appContext, imageUri, headers) - fun shouldAdvertiseImageOptimization(): Boolean = - WPMediaUtils.shouldAdvertiseImageOptimization(appContext) - fun getMimeType(uri: Uri): String? = appContext.contentResolver.getType(uri) fun getVideoThumbnail(videoPath: String, headers: Map): String? = diff --git a/WordPress/src/main/java/org/wordpress/android/util/WPMediaUtils.java b/WordPress/src/main/java/org/wordpress/android/util/WPMediaUtils.java index ef971cb663ec..f8eb64f1f5d7 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/WPMediaUtils.java +++ b/WordPress/src/main/java/org/wordpress/android/util/WPMediaUtils.java @@ -6,10 +6,8 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.pm.PackageManager; import android.media.MediaScannerConnection; import android.net.Uri; -import android.os.Build; import android.os.Environment; import android.provider.MediaStore; import android.view.ViewConfiguration; @@ -19,7 +17,6 @@ import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; -import androidx.core.content.ContextCompat; import androidx.core.content.FileProvider; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -46,7 +43,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; public class WPMediaUtils { @@ -104,75 +100,6 @@ public static boolean isVideoOptimizationEnabled() { return AppPrefs.isVideoOptimize(); } - /** - * Check if we should advertise image optimization feature for the current site. - *

- * The following condition need to be all true: - * 1) Image optimization is ON on the site. - * 2) Didn't already ask to keep or disable the feature. - * 3) The user has granted storage access to the app. - * This is because we don't want to ask so much things to users the first time they try to add a picture to the app. - * - * @param context The context - * @return true if we should advertise the feature, false otherwise. - */ - public static boolean shouldAdvertiseImageOptimization(final Context context) { - boolean isPromoRequired = AppPrefs.isImageOptimizePromoRequired(); - if (!isPromoRequired) { - return false; - } - - // Check we can access storage before asking for optimizing image - boolean hasStoreAccess = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R - || ContextCompat.checkSelfPermission(context, - android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; - if (!hasStoreAccess) { - return false; - } - - // Check whether image optimization is enabled for the site - return AppPrefs.isImageOptimize(); - } - - public interface OnAdvertiseImageOptimizationListener { - void done(); - } - - public static void advertiseImageOptimization(final Context context, - final OnAdvertiseImageOptimizationListener listener) { - DialogInterface.OnClickListener onClickListener = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - String propertyValue = (which == DialogInterface.BUTTON_POSITIVE) ? "on" : "off"; - AnalyticsTracker.track(AnalyticsTracker.Stat.APP_SETTINGS_OPTIMIZE_IMAGES_POPUP_TAPPED, - Collections.singletonMap("option", propertyValue)); - - if (which == DialogInterface.BUTTON_NEGATIVE && AppPrefs.isImageOptimize()) { - AppPrefs.setImageOptimize(false); - } - - listener.done(); - } - }; - - DialogInterface.OnCancelListener onCancelListener = new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - listener.done(); - } - }; - - AlertDialog.Builder builder = new MaterialAlertDialogBuilder(context); - builder.setTitle(R.string.image_optimization_popup_title); - builder.setMessage(R.string.image_optimization_popup_desc); - builder.setPositiveButton(R.string.leave_on, onClickListener); - builder.setNegativeButton(R.string.turn_off, onClickListener); - builder.setOnCancelListener(onCancelListener); - builder.show(); - // Do not ask again - AppPrefs.setImageOptimizePromoRequired(false); - } - /** * Given a media error returns the error message to display on the UI. * diff --git a/WordPress/src/main/java/org/wordpress/android/util/config/StatsTrafficTabFeatureConfig.kt b/WordPress/src/main/java/org/wordpress/android/util/config/StatsTrafficTabFeatureConfig.kt new file mode 100644 index 000000000000..7b55f34878ed --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/util/config/StatsTrafficTabFeatureConfig.kt @@ -0,0 +1,16 @@ +package org.wordpress.android.util.config + +import org.wordpress.android.BuildConfig +import org.wordpress.android.annotation.Feature +import javax.inject.Inject + +private const val STATS_TRAFFIC_TAB_REMOTE_FIELD = "stats_traffic_tab" + +@Feature(STATS_TRAFFIC_TAB_REMOTE_FIELD, false) +class StatsTrafficTabFeatureConfig @Inject constructor( + appConfig: AppConfig +) : FeatureConfig( + appConfig, + BuildConfig.STATS_TRAFFIC_TAB, + STATS_TRAFFIC_TAB_REMOTE_FIELD +) diff --git a/WordPress/src/main/res/drawable-ldrtl/ic_arrow_right_white_24dp.xml b/WordPress/src/main/res/drawable-ldrtl/ic_arrow_right_white_24dp.xml new file mode 100644 index 000000000000..393d26d0cfda --- /dev/null +++ b/WordPress/src/main/res/drawable-ldrtl/ic_arrow_right_white_24dp.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/WordPress/src/main/res/drawable/ic_dot_white_8dp.xml b/WordPress/src/main/res/drawable/ic_dot_white_8dp.xml new file mode 100644 index 000000000000..6cbc9dc2c459 --- /dev/null +++ b/WordPress/src/main/res/drawable/ic_dot_white_8dp.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/WordPress/src/main/res/layout/blogging_prompt_card_compact.xml b/WordPress/src/main/res/layout/blogging_prompt_card_compact.xml index b9d9295cb8fd..1ed54a16933f 100644 --- a/WordPress/src/main/res/layout/blogging_prompt_card_compact.xml +++ b/WordPress/src/main/res/layout/blogging_prompt_card_compact.xml @@ -88,7 +88,7 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/margin_small" android:textAppearance="?attr/textAppearanceCaption" - tools:text="@string/my_site_blogging_prompt_card_attribution_dayone" /> + tools:text="@string/my_site_blogging_prompt_card_attribution_day_one" /> diff --git a/WordPress/src/main/res/layout/domain_site_domains_card.xml b/WordPress/src/main/res/layout/domain_site_domains_card.xml index acdfda31caeb..ced294214d56 100644 --- a/WordPress/src/main/res/layout/domain_site_domains_card.xml +++ b/WordPress/src/main/res/layout/domain_site_domains_card.xml @@ -16,23 +16,15 @@ android:layout_height="wrap_content" android:padding="@dimen/margin_extra_large"> - - + + - diff --git a/WordPress/src/main/res/layout/me_fragment.xml b/WordPress/src/main/res/layout/me_fragment.xml index a9d021478e50..0661d0dc88b1 100644 --- a/WordPress/src/main/res/layout/me_fragment.xml +++ b/WordPress/src/main/res/layout/me_fragment.xml @@ -222,6 +222,31 @@ style="@style/MeListSectionDividerView" tools:visibility="visible"/> + + + + + + + + + + diff --git a/WordPress/src/main/res/layout/my_site_blogging_prompt_card.xml b/WordPress/src/main/res/layout/my_site_blogging_prompt_card.xml index aac5f191f805..818e104d0a83 100644 --- a/WordPress/src/main/res/layout/my_site_blogging_prompt_card.xml +++ b/WordPress/src/main/res/layout/my_site_blogging_prompt_card.xml @@ -75,7 +75,7 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/margin_small" android:textAppearance="?attr/textAppearanceCaption" - tools:text="@string/my_site_blogging_prompt_card_attribution_dayone" /> + tools:text="@string/my_site_blogging_prompt_card_attribution_day_one" /> diff --git a/WordPress/src/main/res/layout/my_site_todays_stats_card.xml b/WordPress/src/main/res/layout/my_site_todays_stats_card.xml index 461445bfb52b..e046610c7eae 100644 --- a/WordPress/src/main/res/layout/my_site_todays_stats_card.xml +++ b/WordPress/src/main/res/layout/my_site_todays_stats_card.xml @@ -40,6 +40,16 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/views_title" tools:text="1,743" /> + + + @@ -79,10 +89,9 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/margin_extra_large" android:layout_marginTop="@dimen/margin_large" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.5" - app:layout_constraintStart_toEndOf="@+id/visitors_layout" - app:layout_constraintTop_toBottomOf="@+id/my_site_toolbar"> + app:layout_constraintEnd_toStartOf="@+id/comments_layout" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/views_layout"> + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@+id/likes_layout"/> + diff --git a/WordPress/src/main/res/values-ar/strings.xml b/WordPress/src/main/res/values-ar/strings.xml index 63ed0c049564..58019d934b74 100644 --- a/WordPress/src/main/res/values-ar/strings.xml +++ b/WordPress/src/main/res/values-ar/strings.xml @@ -399,7 +399,6 @@ Language: ar آخر 7 أيام %d من الأسابيع أسبوع واحد - من <b>DayOne</b> ذكرني لاحقًا التبديل إلى تطبيق Jetpack معرفة المزيد على jetpack.com diff --git a/WordPress/src/main/res/values-cs/strings.xml b/WordPress/src/main/res/values-cs/strings.xml index 5f6a969f43fc..1f365fe039fb 100644 --- a/WordPress/src/main/res/values-cs/strings.xml +++ b/WordPress/src/main/res/values-cs/strings.xml @@ -126,7 +126,6 @@ Language: cs_CZ Posledních 7 dní %d týdnů 1 týden - Od <b>prvního dne</b> Připomenout později Přepněte do aplikace Jetpack Více se dozvíte na jetpack.com diff --git a/WordPress/src/main/res/values-de/strings.xml b/WordPress/src/main/res/values-de/strings.xml index 6fcfc1a64ebf..b344a08f4dba 100644 --- a/WordPress/src/main/res/values-de/strings.xml +++ b/WordPress/src/main/res/values-de/strings.xml @@ -411,7 +411,6 @@ Language: de Letzte 7 Tage %d Wochen 1 Woche - Von <b>DayOne</b> Später daran erinnern Die Statistiken, der Reader, die Benachrichtigungen und weitere Funktionen werden bald in die Jetpack-Mobil-App verschoben. Zur Jetpack-App wechseln diff --git a/WordPress/src/main/res/values-en-rCA/strings.xml b/WordPress/src/main/res/values-en-rCA/strings.xml index f7bd101eab93..b4f0dad06a92 100644 --- a/WordPress/src/main/res/values-en-rCA/strings.xml +++ b/WordPress/src/main/res/values-en-rCA/strings.xml @@ -365,7 +365,6 @@ Language: en_CA 1 week %d weeks Get the Jetpack app - From <b>DayOne</b> Remind me later Learn more at jetpack.com Switching is free and only takes a minute. diff --git a/WordPress/src/main/res/values-en-rGB/strings.xml b/WordPress/src/main/res/values-en-rGB/strings.xml index e76a9f4e1c67..bf14691fa081 100644 --- a/WordPress/src/main/res/values-en-rGB/strings.xml +++ b/WordPress/src/main/res/values-en-rGB/strings.xml @@ -1,11 +1,13 @@ + Waiting for connection + Traffic Working offline Network connection re-established Network connection lost, working offline @@ -411,7 +413,7 @@ Language: en_GB Last 7 days %d weeks 1 week - From <b>DayOne</b> + From <b>Day One</b> Remind me later Stats, Reader, Notifications and other features will soon move to the Jetpack mobile app. Switch to the Jetpack app diff --git a/WordPress/src/main/res/values-es/strings.xml b/WordPress/src/main/res/values-es/strings.xml index 11c0929b8d81..a455319940dd 100644 --- a/WordPress/src/main/res/values-es/strings.xml +++ b/WordPress/src/main/res/values-es/strings.xml @@ -1,11 +1,13 @@ + Esperando conexión + Tráfico Trabajo sin conexión Conexión de red restablecida Conexión de red perdida, trabajando sin conexión @@ -411,7 +413,7 @@ Language: es Últimos siete días %d semanas 1 semana - Desde el <b>DayOne</b> + Desde <b>Day One</b> Recuérdamelo más tarde Algunas funciones, como estadísticas, lector o avisos, se trasladarán pronto a la aplicación móvil de Jetpack. Cambiar a la aplicación de Jetpack diff --git a/WordPress/src/main/res/values-fr-rCA/strings.xml b/WordPress/src/main/res/values-fr-rCA/strings.xml index 2c530d75574a..af19b20f9fc9 100644 --- a/WordPress/src/main/res/values-fr-rCA/strings.xml +++ b/WordPress/src/main/res/values-fr-rCA/strings.xml @@ -402,7 +402,6 @@ Language: fr 7 derniers jours %d semaines 1 semaine - Depuis le <b>jour 1</b> Me rappeler ultérieurement Les statistiques, le lecteur, les notifications et d’autres fonctionnalités seront bientôt transférées vers l’application mobile Jetpack. Passer à l’application Jetpack diff --git a/WordPress/src/main/res/values-fr/strings.xml b/WordPress/src/main/res/values-fr/strings.xml index 2c530d75574a..af19b20f9fc9 100644 --- a/WordPress/src/main/res/values-fr/strings.xml +++ b/WordPress/src/main/res/values-fr/strings.xml @@ -402,7 +402,6 @@ Language: fr 7 derniers jours %d semaines 1 semaine - Depuis le <b>jour 1</b> Me rappeler ultérieurement Les statistiques, le lecteur, les notifications et d’autres fonctionnalités seront bientôt transférées vers l’application mobile Jetpack. Passer à l’application Jetpack diff --git a/WordPress/src/main/res/values-gl/strings.xml b/WordPress/src/main/res/values-gl/strings.xml index f64db893933f..acc26a8e298e 100644 --- a/WordPress/src/main/res/values-gl/strings.xml +++ b/WordPress/src/main/res/values-gl/strings.xml @@ -411,7 +411,6 @@ Language: gl_ES As túas visitas nos últimos sete días son %1$s máis que nos sete días anteriores. Os teus visitantes nos últimos sete días son %1$s menos que nos sete días anteriores. Os teus visitantes nos últimos sete días son %1$s máis que nos sete días anteriores. - Desde o <b>DayOne</b> Recórdamo máis tarde Cambiar á aplicación de Jetpack Máis información en jetpack.com diff --git a/WordPress/src/main/res/values-he/strings.xml b/WordPress/src/main/res/values-he/strings.xml index 44c01c1aac8f..d340794b050c 100644 --- a/WordPress/src/main/res/values-he/strings.xml +++ b/WordPress/src/main/res/values-he/strings.xml @@ -405,7 +405,6 @@ Language: he_IL שבעת הימים האחרונים %d שבועות שבוע אחד - מתוך <b>DayOne</b> הזכירו לי מאוחר יותר האפשרויות של נתונים סטטיסטיים, Reader, הודעות ואפשרויות אחרות יועברו בקרוב אל האפליקציה של Jetpack לנייד. החלפה לאפליקציה של Jetpack diff --git a/WordPress/src/main/res/values-id/strings.xml b/WordPress/src/main/res/values-id/strings.xml index cb8caf5795c4..50d352f5b3c9 100644 --- a/WordPress/src/main/res/values-id/strings.xml +++ b/WordPress/src/main/res/values-id/strings.xml @@ -404,7 +404,6 @@ Language: id 7 hari terakhir %d minggu 1 minggu - Dari <b>DayOne</b> Ingatkan saya nanti Statistik, Reader, Pemberitahuan, dan berbagai fitur lain akan segera berpindah ke aplikasi ponsel Jetpack. Ganti ke aplikasi Jetpack diff --git a/WordPress/src/main/res/values-it/strings.xml b/WordPress/src/main/res/values-it/strings.xml index 95f188dc473e..fd8b70cce538 100644 --- a/WordPress/src/main/res/values-it/strings.xml +++ b/WordPress/src/main/res/values-it/strings.xml @@ -1,6 +1,6 @@ + Aștept conexiunea + Trafic Lucrează offline Conexiunea la rețea este restabilită Conexiune la rețea este pierdută, lucrează offline @@ -411,7 +413,7 @@ Language: ro Ultimele 7 zile %d săptămâni O săptămână - De la <b>DayOne</b> + De la <b>Day One</b> Amintește-mi mai târziu În curând, Statistici, Cititor, Notificări și alte funcționalități se vor muta în aplicația Jetpack pentru mobil. Comută la aplicația Jetpack diff --git a/WordPress/src/main/res/values-ru/strings.xml b/WordPress/src/main/res/values-ru/strings.xml index e6e4dbe70ddf..bdf7541bd763 100644 --- a/WordPress/src/main/res/values-ru/strings.xml +++ b/WordPress/src/main/res/values-ru/strings.xml @@ -1,11 +1,13 @@ + Ожидание подключения + Посещаемость Автономная работа Сетевое соединение восстановлено Сетевое соединение потеряно, работа в автономном режиме @@ -411,7 +413,7 @@ Language: ru Последние 7 дней %d нед. 1 неделя - От <b>В Первый День</b> + От <b>В Первый День</b> Напомнить мне позже Статистика, Чтиво, Уведомления и другие функции скоро переедут в мобильное приложение Jetpack. Перейти в новое приложение Jetpack diff --git a/WordPress/src/main/res/values-sq/strings.xml b/WordPress/src/main/res/values-sq/strings.xml index de4fd2a3942f..8a2748e80e81 100644 --- a/WordPress/src/main/res/values-sq/strings.xml +++ b/WordPress/src/main/res/values-sq/strings.xml @@ -410,7 +410,6 @@ Language: sq_AL 7 ditët e fundit %d javë 1 javë - Nga <b>Dita e Parë</b> Kujtoma më vonë Statistikat, Lexuesi, Njoftimet dhe të tjera veçorit të Jetpack-ut së shpejti do të kalohen te aplikacioni Jetpack për celular. Kaloni te aplikacioni Jetpack diff --git a/WordPress/src/main/res/values-sv/strings.xml b/WordPress/src/main/res/values-sv/strings.xml index 02e796d929fe..ebfee79526c4 100644 --- a/WordPress/src/main/res/values-sv/strings.xml +++ b/WordPress/src/main/res/values-sv/strings.xml @@ -1,11 +1,13 @@ + Väntar på anslutning + Trafik Arbetar offline Nätverksanslutningen har återupprättats Nätverksanslutning förlorad, arbetar offline @@ -411,7 +413,7 @@ Language: sv_SE Senaste 7 dagarna %d veckor 1 vecka - Från <b>dag ett</b> + Från <b>Day One</b> Påminn mig senare Statistik, läsare, aviseringar och andra funktioner kommer snart att flyttas till Jetpacks mobilapp. Byt till Jetpack-appen diff --git a/WordPress/src/main/res/values-tr/strings.xml b/WordPress/src/main/res/values-tr/strings.xml index 731d4ede11df..4405348524d1 100644 --- a/WordPress/src/main/res/values-tr/strings.xml +++ b/WordPress/src/main/res/values-tr/strings.xml @@ -408,7 +408,6 @@ Language: tr Son 7 gün %d hafta 1 hafta - <b>DayOne</b> tarafından Daha sonra hatırlat İstatistikler, Okuyucu, Bildirimler ve diğer özellikler yakında Jetpack mobil uygulamasına taşınacak. Jetpack uygulamasına geçiş yapın diff --git a/WordPress/src/main/res/values-zh-rCN/strings.xml b/WordPress/src/main/res/values-zh-rCN/strings.xml index bc5bdbb1afe6..7cef5ff8e92b 100644 --- a/WordPress/src/main/res/values-zh-rCN/strings.xml +++ b/WordPress/src/main/res/values-zh-rCN/strings.xml @@ -401,7 +401,6 @@ Language: zh_CN 过去 7 天 %d 周 1 周 - 从<b>第一天</b>开始 稍后提醒我 统计信息、阅读器、通知和其他功能即将移至 Jetpack 移动应用。 切换到 Jetpack 应用 diff --git a/WordPress/src/main/res/values-zh-rHK/strings.xml b/WordPress/src/main/res/values-zh-rHK/strings.xml index 611c79eab4e0..b15514a48548 100644 --- a/WordPress/src/main/res/values-zh-rHK/strings.xml +++ b/WordPress/src/main/res/values-zh-rHK/strings.xml @@ -1,6 +1,6 @@ Debug cookies @@ -953,6 +954,11 @@ cookie_name cookie_value + + Design System + Foundation + Components + Stats To use Stats on your WordPress site, you\'ll need to install the Jetpack plugin. @@ -1327,6 +1333,7 @@ Unknown Search Terms + Traffic Insights All-time posts, views, and visitors Today\'s Stats @@ -2367,6 +2374,7 @@ @string/stats_likes @string/stats_views @string/stats_visitors + @string/stats_comments Interested in building your audience? Check out our <a href="%1$s">top tips</a>. @string/my_site_dashboard_card_more_menu_hide_card View stats @@ -2398,7 +2406,7 @@ View more prompts Skip for today Turn off prompts - From <b>DayOne</b> + From <b>Day One</b> From <b>Bloganuary</b> Learn more Skipped today\'s blogging prompt @@ -2975,12 +2983,6 @@ Camera Microphone - - Yes, leave on - No, turn off - Keep optimizing images? - Image optimization shrinks images for faster uploading.\n\nThis option is enabled by default, but you can change it in the app settings at any time. - @@ -4840,5 +4842,6 @@ translators: %s: Select control option value e.g: "Auto, 25%". --> Network connection lost, working offline Network connection re-established Working Offline + Waiting for connection diff --git a/WordPress/src/test/java/org/wordpress/android/ui/bloggingprompts/BloggingPromptsPostTagProviderTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/bloggingprompts/BloggingPromptsPostTagProviderTest.kt index 942681c91225..73250ba0c7f5 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/bloggingprompts/BloggingPromptsPostTagProviderTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/bloggingprompts/BloggingPromptsPostTagProviderTest.kt @@ -27,25 +27,21 @@ class BloggingPromptsPostTagProviderTest : BaseUnitTest() { } @Test - fun `Should return the expected tag when promptIdTag is called given valid url`() { - whenever(readerUtilsWrapper.getTagFromTagUrl(any())).thenReturn(BLOGGING_PROMPT_ID_TAG) - - val actual = tagProvider.promptIdTag("valid-url") + fun `Should return the expected tag when promptIdTag is called given valid id`() { + val actual = tagProvider.promptIdTag(1234) assertThat(actual).isEqualTo(BLOGGING_PROMPT_ID_TAG) } @Test - fun `Should return the generic tag when promptIdTag is called given invalid url`() { - whenever(readerUtilsWrapper.getTagFromTagUrl(any())).thenReturn("") - - val actual = tagProvider.promptIdTag("invalid-url") + fun `Should return the generic tag when promptIdTag is called given invalid id`() { + val actual = tagProvider.promptIdTag(0) assertThat(actual).isEqualTo(BloggingPromptsPostTagProvider.BLOGGING_PROMPT_TAG) } @Test - fun `Should return the expected ReaderTag when promptIdSearchReaderTag is called`() { + fun `Should return the expected ReaderTag when promptSearchReaderTag is called`() { whenever(readerUtilsWrapper.getTagFromTagUrl(any())).thenReturn(BLOGGING_PROMPT_ID_TAG) val expected = ReaderTag( BLOGGING_PROMPT_ID_TAG, @@ -54,11 +50,26 @@ class BloggingPromptsPostTagProviderTest : BaseUnitTest() { ReaderPostLogic.formatFullEndpointForTag(BLOGGING_PROMPT_ID_TAG), ReaderTagType.FOLLOWED, ) - val actual = tagProvider.promptIdSearchReaderTag("valid-url") + val actual = tagProvider.promptSearchReaderTag("valid-url") + assertEquals(expected, actual) + } + + @Test + fun `Should return the base Prompt ReaderTag when promptSearchReaderTag is called`() { + whenever(readerUtilsWrapper.getTagFromTagUrl(any())).thenReturn("") + val expected = ReaderTag( + BLOGGING_PROMPT_TAG, + BLOGGING_PROMPT_TAG, + BLOGGING_PROMPT_TAG, + ReaderPostLogic.formatFullEndpointForTag(BLOGGING_PROMPT_TAG), + ReaderTagType.FOLLOWED, + ) + val actual = tagProvider.promptSearchReaderTag("invalid-url") assertEquals(expected, actual) } companion object { private const val BLOGGING_PROMPT_ID_TAG = "dailyprompt-1234" + private const val BLOGGING_PROMPT_TAG = "dailyprompt" } } diff --git a/WordPress/src/test/java/org/wordpress/android/ui/debug/preferences/DebugSharedPreferenceFlagsViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/debug/preferences/DebugSharedPreferenceFlagsViewModelTest.kt new file mode 100644 index 000000000000..7e0d136c874b --- /dev/null +++ b/WordPress/src/test/java/org/wordpress/android/ui/debug/preferences/DebugSharedPreferenceFlagsViewModelTest.kt @@ -0,0 +1,42 @@ +package org.wordpress.android.ui.debug.preferences + +import junit.framework.TestCase.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner +import org.mockito.kotlin.argThat +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.wordpress.android.ui.prefs.AppPrefsWrapper + +@RunWith(MockitoJUnitRunner::class) +class DebugSharedPreferenceFlagsViewModelTest { + @Mock + private lateinit var prefsWrapper: AppPrefsWrapper + private lateinit var viewModel: DebugSharedPreferenceFlagsViewModel + + @Test + fun `WHEN init THEN should load the flags from the prefs`() { + whenever(prefsWrapper.getAllPrefs()).thenReturn(mapOf("key" to true)) + + initViewModel() + + assertTrue(viewModel.uiStateFlow.value["key"]!!) + } + + @Test + fun `WHEN setFlag THEN should update the prefs and the ui state`() { + initViewModel() + + viewModel.setFlag("key", true) + + verify(prefsWrapper).putBoolean(argThat { key -> key.name() == "key" }, eq(true)) + assertTrue(viewModel.uiStateFlow.value["key"]!!) + } + + private fun initViewModel() { + viewModel = DebugSharedPreferenceFlagsViewModel(prefsWrapper) + } +} diff --git a/WordPress/src/test/java/org/wordpress/android/ui/domains/DomainsDashboardViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/domains/DomainsDashboardViewModelTest.kt index 1a8e87291aa3..89c7d4d1edeb 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/domains/DomainsDashboardViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/domains/DomainsDashboardViewModelTest.kt @@ -10,6 +10,7 @@ import org.wordpress.android.BaseUnitTest import org.wordpress.android.R import org.wordpress.android.fluxc.model.PlanModel import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.network.rest.wpcom.site.AllDomainsDomain import org.wordpress.android.fluxc.network.rest.wpcom.site.Domain import org.wordpress.android.fluxc.store.SiteStore import org.wordpress.android.fluxc.store.SiteStore.FetchedDomainsPayload @@ -19,6 +20,8 @@ import org.wordpress.android.ui.domains.DomainsDashboardItem.PurchaseDomain import org.wordpress.android.ui.domains.DomainsDashboardItem.PurchasePlan import org.wordpress.android.ui.domains.DomainsDashboardItem.SiteDomains import org.wordpress.android.ui.domains.DomainsDashboardItem.SiteDomainsHeader +import org.wordpress.android.ui.domains.usecases.AllDomains +import org.wordpress.android.ui.domains.usecases.FetchAllDomainsUseCase import org.wordpress.android.ui.domains.usecases.FetchPlansUseCase import org.wordpress.android.ui.plans.PlansConstants.FREE_PLAN_ID import org.wordpress.android.ui.plans.PlansConstants.PREMIUM_PLAN_ID @@ -32,6 +35,7 @@ class DomainsDashboardViewModelTest : BaseUnitTest() { private val analyticsTracker: AnalyticsTrackerWrapper = mock() private val htmlMessageUtils: HtmlMessageUtils = mock() private val fetchPlansUseCase: FetchPlansUseCase = mock() + private val fetchAllDomainsUseCase: FetchAllDomainsUseCase = mock() private lateinit var viewModel: DomainsDashboardViewModel @@ -44,6 +48,7 @@ class DomainsDashboardViewModelTest : BaseUnitTest() { analyticsTracker, htmlMessageUtils, fetchPlansUseCase, + fetchAllDomainsUseCase, testDispatcher() ) @@ -171,6 +176,8 @@ class DomainsDashboardViewModelTest : BaseUnitTest() { val plan = if (hasDomainCredits) planWithCredits else planWithNoCredits whenever(fetchPlansUseCase.execute(site)).thenReturn(OnPlansFetched(site, listOf(plan))) + val allDomains = if (hasCustomDomains) listOf(allDomainsDomain) else emptyList() + whenever(fetchAllDomainsUseCase.execute()).thenReturn(AllDomains.Success(allDomains)) viewModel.start(site) } @@ -189,6 +196,8 @@ class DomainsDashboardViewModelTest : BaseUnitTest() { wpcomDomain = false ) + private val allDomainsDomain = AllDomainsDomain(domain = "henna.tattoo") + private val siteWithFreePlan = SiteModel().apply { siteId = TEST_SITE_ID url = TEST_DOMAIN_NAME diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptCardViewModelSliceTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptCardViewModelSliceTest.kt index d97bb8a4fec9..60de6f2849e0 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptCardViewModelSliceTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/bloggingprompts/BloggingPromptCardViewModelSliceTest.kt @@ -141,7 +141,7 @@ class BloggingPromptCardViewModelSliceTest : BaseUnitTest() { val tagUrl = "valid-url" val expectedTag = mock() - whenever(bloggingPromptsPostTagProvider.promptIdSearchReaderTag(tagUrl)).thenReturn(expectedTag) + whenever(bloggingPromptsPostTagProvider.promptSearchReaderTag(tagUrl)).thenReturn(expectedTag) val params = viewModelSlice.getBuilderParams(mock()) params.onViewAnswersClick(tagUrl) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsCardBuilderTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsCardBuilderTest.kt index e1d78c241293..faa097e1b860 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsCardBuilderTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/cards/dashboard/todaysstats/TodaysStatsCardBuilderTest.kt @@ -31,6 +31,7 @@ private const val TODAYS_STATS_COMMENTS = 1000 private const val TODAYS_STATS_VIEWS_FORMATTED_STRING = "10,000" private const val TODAYS_STATS_VISITORS_FORMATTED_STRING = "1,000" private const val TODAYS_STATS_LIKES_FORMATTED_STRING = "100" +private const val TODAYS_STATS_COMMENTS_FORMATTED_STRING = "1,000" private const val GET_MORE_VIEWS_MSG_WITH_CLICKABLE_LINK = "If you want to try get more views and traffic check out our " + @@ -194,6 +195,7 @@ class TodaysStatsCardBuilderTest : BaseUnitTest() { views = UiStringText(TODAYS_STATS_VIEWS_FORMATTED_STRING), visitors = UiStringText(TODAYS_STATS_VISITORS_FORMATTED_STRING), likes = UiStringText(TODAYS_STATS_LIKES_FORMATTED_STRING), + comments = UiStringText(TODAYS_STATS_COMMENTS_FORMATTED_STRING), onCardClick = onTodaysStatsCardClick, moreMenuOptions = TodaysStatsCard.MoreMenuOptions( onMoreMenuClick = onMoreMenuClick, diff --git a/WordPress/src/test/java/org/wordpress/android/ui/posts/editor/media/EditorMediaTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/posts/editor/media/EditorMediaTest.kt index c081fceead0b..ac43eaf276c9 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/posts/editor/media/EditorMediaTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/posts/editor/media/EditorMediaTest.kt @@ -1,6 +1,5 @@ package org.wordpress.android.ui.posts.editor.media -import android.net.Uri import androidx.lifecycle.Observer import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -27,7 +26,6 @@ import org.wordpress.android.fluxc.store.MediaStore.FetchMediaListPayload import org.wordpress.android.ui.pages.SnackbarMessageHolder import org.wordpress.android.ui.posts.editor.media.EditorMedia.AddMediaToPostUiState import org.wordpress.android.ui.utils.UiString.UiStringRes -import org.wordpress.android.util.MediaUtilsWrapper import org.wordpress.android.util.NetworkUtilsWrapper import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper import org.wordpress.android.util.analytics.AnalyticsUtilsWrapper @@ -36,38 +34,6 @@ import org.wordpress.android.viewmodel.helpers.ToastMessageHolder @ExperimentalCoroutinesApi class EditorMediaTest : BaseUnitTest() { - @Test - fun `advertiseImageOptimisationAndAddMedia shows dialog when shouldAdvertiseImageOptimization is true`() { - // Arrange - val editorMediaListener = mock() - val mediaUtilsWrapper = createMediaUtilsWrapper(shouldAdvertiseImageOptimization = true) - - // Act - createEditorMedia( - editorMediaListener = editorMediaListener, - mediaUtilsWrapper = mediaUtilsWrapper - ) - .advertiseImageOptimisationAndAddMedia(mock()) - // Assert - verify(editorMediaListener).advertiseImageOptimization(anyOrNull()) - } - - @Test - fun `advertiseImageOptimisationAndAddMedia does NOT show dialog when shouldAdvertiseImageOptimization is false`() { - // Arrange - val editorMediaListener = mock() - val mediaUtilsWrapper = createMediaUtilsWrapper(shouldAdvertiseImageOptimization = false) - - // Act - createEditorMedia( - editorMediaListener = editorMediaListener, - mediaUtilsWrapper = mediaUtilsWrapper - ) - .advertiseImageOptimisationAndAddMedia(mock()) - // Assert - verify(editorMediaListener, never()).advertiseImageOptimization(anyOrNull()) - } - @Test fun `addNewMediaItemsToEditorAsync emits AddingSingleMedia for a single uri`() = test { // Arrange @@ -142,45 +108,6 @@ class EditorMediaTest : BaseUnitTest() { ) } - @Test - fun `onPhotoPickerMediaChosen does NOT invoke shouldAdvertiseImageOptimization when only video files`() = - test { - // Arrange - val uris = listOf(VIDEO_URI, VIDEO_URI, VIDEO_URI, VIDEO_URI) - val editorMediaListener = mock() - - val mediaUtilsWrapper = createMediaUtilsWrapper() - - // Act - createEditorMedia( - mediaUtilsWrapper = mediaUtilsWrapper, - editorMediaListener = editorMediaListener - ) - .onPhotoPickerMediaChosen(uris) - // Assert - verify(editorMediaListener, never()).advertiseImageOptimization(anyOrNull()) - verify(mediaUtilsWrapper, never()).shouldAdvertiseImageOptimization() - } - - @Test - fun `onPhotoPickerMediaChosen invokes shouldAdvertiseImageOptimization when at least 1 image file`() = - test { - // Arrange - val uris = listOf(VIDEO_URI, VIDEO_URI, IMAGE_URI, VIDEO_URI) - val editorMediaListener = mock() - - val mediaUtilsWrapper = createMediaUtilsWrapper() - - // Act - createEditorMedia( - mediaUtilsWrapper = mediaUtilsWrapper, - editorMediaListener = editorMediaListener - ) - .onPhotoPickerMediaChosen(uris) - // Assert - verify(mediaUtilsWrapper).shouldAdvertiseImageOptimization() - } - @Test fun `addExistingMediaToEditorAsync passes mediaId to addExistingMediaToPostUseCase`() = test { @@ -332,8 +259,6 @@ class EditorMediaTest : BaseUnitTest() { } private companion object Fixtures { - private val VIDEO_URI = mock() - private val IMAGE_URI = mock() private const val MEDIA_MODEL_REMOTE_ID = 123L private const val MEDIA_MODEL_LOCAL_ID = 1 @@ -341,7 +266,6 @@ class EditorMediaTest : BaseUnitTest() { updateMediaModelUseCase: UpdateMediaModelUseCase = mock(), getMediaModelUseCase: GetMediaModelUseCase = createGetMediaModelUseCase(), dispatcher: Dispatcher = mock(), - mediaUtilsWrapper: MediaUtilsWrapper = createMediaUtilsWrapper(), networkUtilsWrapper: NetworkUtilsWrapper = mock(), addLocalMediaToPostUseCase: AddLocalMediaToPostUseCase = createAddLocalMediaToPostUseCase(), addExistingMediaToPostUseCase: AddExistingMediaToPostUseCase = mock(), @@ -358,7 +282,6 @@ class EditorMediaTest : BaseUnitTest() { updateMediaModelUseCase, getMediaModelUseCase, dispatcher, - mediaUtilsWrapper, networkUtilsWrapper, addLocalMediaToPostUseCase, addExistingMediaToPostUseCase, @@ -375,16 +298,6 @@ class EditorMediaTest : BaseUnitTest() { return editorMedia } - fun createMediaUtilsWrapper( - shouldAdvertiseImageOptimization: Boolean = false - ) = - mock { - on { shouldAdvertiseImageOptimization() } - .thenReturn(shouldAdvertiseImageOptimization) - on { isVideo(VIDEO_URI.toString()) }.thenReturn(true) - on { isVideo(IMAGE_URI.toString()) }.thenReturn(false) - } - fun createAddLocalMediaToPostUseCase(resultForAddNewMediaToEditorAsync: Boolean = true) = mock { onBlocking { diff --git a/WordPress/src/test/java/org/wordpress/android/ui/posts/editor/media/RetryFailedMediaUploadUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/posts/editor/media/RetryFailedMediaUploadUseCaseTest.kt index 366303e9fe00..c51bbaa40fbb 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/posts/editor/media/RetryFailedMediaUploadUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/posts/editor/media/RetryFailedMediaUploadUseCaseTest.kt @@ -18,6 +18,8 @@ import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.fluxc.model.MediaModel import org.wordpress.android.fluxc.model.MediaModel.MediaUploadState import org.wordpress.android.fluxc.model.MediaModel.MediaUploadState.QUEUED +import org.wordpress.android.fluxc.model.PostImmutableModel +import org.wordpress.android.fluxc.store.SiteStore import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper @ExperimentalCoroutinesApi @@ -29,8 +31,9 @@ class RetryFailedMediaUploadUseCaseTest : BaseUnitTest() { val getMediaModelUseCase = createGetMediaModelUseCase() val useCase = createUseCase(getMediaModelUseCase = getMediaModelUseCase) + // Act - useCase.retryFailedMediaAsync(mock(), FAILED_MEDIA_IDS) + useCase.retryFailedMediaAsync(createEditorMediaListener(), FAILED_MEDIA_IDS) // Assert verify(getMediaModelUseCase).loadMediaByLocalId(FAILED_MEDIA_IDS) } @@ -42,7 +45,7 @@ class RetryFailedMediaUploadUseCaseTest : BaseUnitTest() { val useCase = createUseCase(updateMediaModelUseCase = updateMediaModelUseCase) // Act - useCase.retryFailedMediaAsync(mock(), FAILED_MEDIA_IDS) + useCase.retryFailedMediaAsync(createEditorMediaListener(), FAILED_MEDIA_IDS) // Assert verify(updateMediaModelUseCase, times(FAILED_MEDIA_IDS.size)).updateMediaModel( @@ -59,7 +62,7 @@ class RetryFailedMediaUploadUseCaseTest : BaseUnitTest() { val useCase = createUseCase(uploadMediaUseCase = uploadMediaUseCase) // Act - useCase.retryFailedMediaAsync(mock(), FAILED_MEDIA_IDS) + useCase.retryFailedMediaAsync(createEditorMediaListener(), FAILED_MEDIA_IDS) // Assert verify(uploadMediaUseCase).saveQueuedPostAndStartUpload( @@ -74,7 +77,7 @@ class RetryFailedMediaUploadUseCaseTest : BaseUnitTest() { val trackerWrapper: AnalyticsTrackerWrapper = mock() val useCase = createUseCase(tracker = trackerWrapper) // Act - useCase.retryFailedMediaAsync(mock(), FAILED_MEDIA_IDS) + useCase.retryFailedMediaAsync(createEditorMediaListener(), FAILED_MEDIA_IDS) // Assert verify(trackerWrapper).track(Stat.EDITOR_UPLOAD_MEDIA_RETRIED) } @@ -112,13 +115,15 @@ class RetryFailedMediaUploadUseCaseTest : BaseUnitTest() { getMediaModelUseCase: GetMediaModelUseCase = createGetMediaModelUseCase(), updateMediaModelUseCase: UpdateMediaModelUseCase = mock(), uploadMediaUseCase: UploadMediaUseCase = mock(), - tracker: AnalyticsTrackerWrapper = mock() + tracker: AnalyticsTrackerWrapper = mock(), + siteStore: SiteStore = mock() ): RetryFailedMediaUploadUseCase { return RetryFailedMediaUploadUseCase( getMediaModelUseCase, updateMediaModelUseCase, uploadMediaUseCase, - tracker + tracker, + siteStore ) } @@ -136,5 +141,13 @@ class RetryFailedMediaUploadUseCaseTest : BaseUnitTest() { this.id = mediaModelId this.uploadState = MediaUploadState.FAILED.name } + + fun createEditorMediaListener() = mock { + on { getImmutablePost() } doAnswer { + mock { + on { localSiteId } doAnswer { 0 } + } + } + } } } diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/lists/sections/insights/usecases/TotalFollowersUseCaseTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/lists/sections/insights/usecases/TotalFollowersUseCaseTest.kt index 1c01e9fc18af..93165d577ace 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/lists/sections/insights/usecases/TotalFollowersUseCaseTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stats/refresh/lists/sections/insights/usecases/TotalFollowersUseCaseTest.kt @@ -5,6 +5,7 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test import org.mockito.Mock +import org.mockito.kotlin.any import org.mockito.kotlin.whenever import org.wordpress.android.BaseUnitTest import org.wordpress.android.R @@ -24,6 +25,7 @@ import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.Type. import org.wordpress.android.ui.stats.refresh.lists.sections.BlockListItem.ValueWithChartItem import org.wordpress.android.ui.stats.refresh.utils.ActionCardHandler import org.wordpress.android.ui.stats.refresh.utils.StatsSiteProvider +import org.wordpress.android.ui.stats.refresh.utils.StatsUtils import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper import org.wordpress.android.viewmodel.ResourceProvider @@ -50,6 +52,9 @@ class TotalFollowersUseCaseTest : BaseUnitTest() { @Mock lateinit var useCaseMode: UseCaseMode + @Mock + lateinit var statsUtils: StatsUtils + @Mock lateinit var actionCardHandler: ActionCardHandler private lateinit var useCase: TotalFollowersUseCase @@ -66,9 +71,11 @@ class TotalFollowersUseCaseTest : BaseUnitTest() { totalStatsMapper, analyticsTrackerWrapper, actionCardHandler, - useCaseMode + useCaseMode, + statsUtils ) whenever(statsSiteProvider.siteModel).thenReturn(site) + whenever(statsUtils.toFormattedString(any(), any())).then { (it.arguments[0] as Int).toString() } } @Test diff --git a/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/StoryEditorMediaTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/StoryEditorMediaTest.kt index 2bc45b71e985..90176998e575 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/StoryEditorMediaTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/stories/usecase/StoryEditorMediaTest.kt @@ -1,6 +1,5 @@ package org.wordpress.android.ui.stories.usecase -import android.net.Uri import androidx.lifecycle.Observer import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -23,43 +22,10 @@ import org.wordpress.android.ui.posts.editor.media.EditorMediaListener import org.wordpress.android.ui.stories.media.StoryEditorMedia import org.wordpress.android.ui.stories.media.StoryEditorMedia.AddMediaToStoryPostUiState import org.wordpress.android.ui.utils.UiString.UiStringRes -import org.wordpress.android.util.MediaUtilsWrapper import org.wordpress.android.viewmodel.Event @ExperimentalCoroutinesApi class StoryEditorMediaTest : BaseUnitTest() { - @Test - fun `advertiseImageOptimisationAndAddMedia shows dialog when shouldAdvertiseImageOptimization is true`() { - // Arrange - val editorMediaListener = mock() - val mediaUtilsWrapper = createMediaUtilsWrapper(shouldAdvertiseImageOptimization = true) - - // Act - createStoryEditorMedia( - editorMediaListener = editorMediaListener, - mediaUtilsWrapper = mediaUtilsWrapper - ).advertiseImageOptimisationAndAddMedia(mock()) - - // Assert - verify(editorMediaListener).advertiseImageOptimization(anyOrNull()) - } - - @Test - fun `advertiseImageOptimisationAndAddMedia does NOT show dialog when shouldAdvertiseImageOptimization is false`() { - // Arrange - val editorMediaListener = mock() - val mediaUtilsWrapper = createMediaUtilsWrapper(shouldAdvertiseImageOptimization = false) - - // Act - createStoryEditorMedia( - editorMediaListener = editorMediaListener, - mediaUtilsWrapper = mediaUtilsWrapper - ) - .advertiseImageOptimisationAndAddMedia(mock()) - // Assert - verify(editorMediaListener, never()).advertiseImageOptimization(anyOrNull()) - } - @Test fun `addNewMediaItemsToEditorAsync emits AddingSingleMedia for a single uri`() = test { // Arrange @@ -117,58 +83,14 @@ class StoryEditorMediaTest : BaseUnitTest() { verify(observer, never()).onChanged(captor.capture()) } - @Test - fun `onPhotoPickerMediaChosen does NOT invoke shouldAdvertiseImageOptimization when only video files`() = - test { - // Arrange - val uris = listOf(VIDEO_URI, VIDEO_URI, VIDEO_URI, VIDEO_URI) - val editorMediaListener = mock() - - val mediaUtilsWrapper = createMediaUtilsWrapper() - - // Act - createStoryEditorMedia( - mediaUtilsWrapper = mediaUtilsWrapper, - editorMediaListener = editorMediaListener - ) - .onPhotoPickerMediaChosen(uris) - // Assert - verify(editorMediaListener, never()).advertiseImageOptimization(anyOrNull()) - verify(mediaUtilsWrapper, never()).shouldAdvertiseImageOptimization() - } - - @Test - fun `onPhotoPickerMediaChosen invokes shouldAdvertiseImageOptimization when at least 1 image file`() = - test { - // Arrange - val uris = listOf(VIDEO_URI, VIDEO_URI, IMAGE_URI, VIDEO_URI) - val editorMediaListener = mock() - - val mediaUtilsWrapper = createMediaUtilsWrapper() - - // Act - createStoryEditorMedia( - mediaUtilsWrapper = mediaUtilsWrapper, - editorMediaListener = editorMediaListener - ) - .onPhotoPickerMediaChosen(uris) - // Assert - verify(mediaUtilsWrapper).shouldAdvertiseImageOptimization() - } - private companion object Fixtures { - private val VIDEO_URI = mock() - private val IMAGE_URI = mock() - fun createStoryEditorMedia( - mediaUtilsWrapper: MediaUtilsWrapper = createMediaUtilsWrapper(), addLocalMediaToPostUseCase: AddLocalMediaToPostUseCase = createAddLocalMediaToPostUseCase(), addExistingMediaToPostUseCase: AddExistingMediaToPostUseCase = mock(), siteModel: SiteModel = mock(), editorMediaListener: EditorMediaListener = mock() ): StoryEditorMedia { val editorMedia = StoryEditorMedia( - mediaUtilsWrapper, addLocalMediaToPostUseCase, addExistingMediaToPostUseCase, UnconfinedTestDispatcher() @@ -177,16 +99,6 @@ class StoryEditorMediaTest : BaseUnitTest() { return editorMedia } - fun createMediaUtilsWrapper( - shouldAdvertiseImageOptimization: Boolean = false - ) = - mock { - on { shouldAdvertiseImageOptimization() } - .thenReturn(shouldAdvertiseImageOptimization) - on { isVideo(VIDEO_URI.toString()) }.thenReturn(true) - on { isVideo(IMAGE_URI.toString()) }.thenReturn(false) - } - fun createAddLocalMediaToPostUseCase(resultForAddNewMediaToEditorAsync: Boolean = true) = mock { onBlocking { diff --git a/build.gradle b/build.gradle index a62b995e4d81..e29ad0136166 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id "io.gitlab.arturbosch.detekt" id 'com.automattic.android.measure-builds' + id "org.jetbrains.kotlinx.kover" id "androidx.navigation.safeargs.kotlin" apply false id "com.android.library" apply false id 'com.google.gms.google-services' apply false @@ -22,7 +23,7 @@ ext { automatticRestVersion = '1.0.8' automatticStoriesVersion = '2.4.0' automatticTracksVersion = '3.3.0' - gutenbergMobileVersion = 'v1.110.1' + gutenbergMobileVersion = 'v1.111.2' wordPressAztecVersion = 'v1.9.0' wordPressFluxCVersion = '2.61.0' wordPressLoginVersion = '1.10.0' @@ -248,4 +249,5 @@ dependencies { detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:$gradle.ext.detektVersion" } +apply from: './config/gradle/code_coverage.gradle' apply from: './config/gradle/gradle_build_scan.gradle' diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000000..ec6a47201a74 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,8 @@ +comment: + require_changes: true +coverage: + status: + project: off + patch: off +codecov: + max_report_age: off diff --git a/config/gradle/code_coverage.gradle b/config/gradle/code_coverage.gradle new file mode 100644 index 000000000000..f22a85688ee8 --- /dev/null +++ b/config/gradle/code_coverage.gradle @@ -0,0 +1,72 @@ +static String resolveProjectVariantForCodeCoverage(Project project) { + if (project.name == "WordPress") { + return "wordpressVanillaRelease" + } else { + return "release" + } +} + +allprojects { + pluginManager.withPlugin("org.jetbrains.kotlinx.kover") { + if (project.plugins.hasPlugin("com.android.library") || project.plugins.hasPlugin("com.android.application")) { + koverReport { + defaults { + mergeWith(resolveProjectVariantForCodeCoverage(project)) + } + } + } + + koverReport { + filters { + excludes { + packages( + 'com.bumptech.glide', + 'dagger.*', + '*.compose*', + '*.debug*', + 'hilt_aggregated_deps', + '*.databinding', + 'org.wordpress.android.modules', + 'org.wordpress.android.widgets', + ) + + classes( + '*_Factory*', + '*Activity', + '*Activity$*', + '*Adapter', + '*Adapter$*', + '*BuildConfig', + '*DiffCallback*', + '*Dialog', + '*Dialog$*', + '*Fragment', + '*Fragment$*', + '*FragmentDirections*', + '*FragmentKt*', + '*Module', + '*Module_*', + '*View', + '*View$*', + '*ViewHolder', + '*ViewHolder$*', + '*ViewHolderKt*', + '*.Hilt_*', + '*HiltModules*', + '*_MembersInjector', + ) + } + } + } + } +} + +dependencies { + kover( + project(":WordPress"), + project(":libs:editor"), + project(":libs:image-editor"), + project(":libs:processors"), + ) +} + diff --git a/docs/code-coverage.md b/docs/code-coverage.md new file mode 100644 index 000000000000..3b257dcab4c6 --- /dev/null +++ b/docs/code-coverage.md @@ -0,0 +1,12 @@ +# Code coverage + +This project uses [Kover](https://github.com/Kotlin/kotlinx-kover) tool for generating code coverage metrics. +To run the code coverage report for the whole codebase, run `./gradlew koverHtmlReport` and open the HTML report at `WordPress/build/reports/kover/html/index.html`. + +## Coverage exclusions + +To get more precise results of the metrics, some classes are excluded from code coverage calculations. Those classes are e.g. Dagger/Hilt generated code, Acitivies, Fragments, databinding etc. A complete list of exclusions is available in the `config/gradle/code_coverage.gradle` file. + +## Codecov + +We also have `Codecov` integration for getting reports on each PR and observing trends. See [the dashboard](https://app.codecov.io/github/wordpress-mobile/WordPress-Android/) to get more insights. diff --git a/fastlane/resources/values/strings.xml b/fastlane/resources/values/strings.xml index ad913439331d..f7865454b8b2 100644 --- a/fastlane/resources/values/strings.xml +++ b/fastlane/resources/values/strings.xml @@ -945,6 +945,7 @@ Don\'t see a feature you\'re working on? Check that your feature config file is annotated with the FeatureInDevelopment annotation. Tools Force show Weekly Roundup notification + All shared preference flags Debug cookies @@ -953,6 +954,11 @@ cookie_name cookie_value + + Design System + Foundation + Components + Stats To use Stats on your WordPress site, you\'ll need to install the Jetpack plugin. @@ -1327,6 +1333,7 @@ Unknown Search Terms + Traffic Insights All-time posts, views, and visitors Today\'s Stats @@ -2367,6 +2374,7 @@ @string/stats_likes @string/stats_views @string/stats_visitors + @string/stats_comments Interested in building your audience? Check out our <a href="%1$s">top tips</a>. @string/my_site_dashboard_card_more_menu_hide_card View stats @@ -2398,7 +2406,7 @@ View more prompts Skip for today Turn off prompts - From <b>DayOne</b> + From <b>Day One</b> From <b>Bloganuary</b> Learn more Skipped today\'s blogging prompt @@ -4840,5 +4848,6 @@ translators: %s: Select control option value e.g: "Auto, 25%". --> Network connection lost, working offline Network connection re-established Working Offline + Waiting for connection 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 28c466bd1e75..12ef44396989 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 @@ -82,6 +82,7 @@ public enum Stat { READER_SITE_SHARED, STATS_ACCESSED, STATS_ACCESS_ERROR, + STATS_TRAFFIC_ACCESSED, STATS_INSIGHTS_ACCESSED, STATS_INSIGHTS_MANAGEMENT_HINT_DISMISSED, STATS_INSIGHTS_MANAGEMENT_HINT_CLICKED, @@ -210,6 +211,7 @@ public enum Stat { EDITOR_EDITED_IMAGE, // Visual editor only EDITOR_UPLOAD_MEDIA_FAILED, // Visual editor only EDITOR_UPLOAD_MEDIA_RETRIED, // Visual editor only + EDITOR_UPLOAD_MEDIA_PAUSED, // Visual editor only EDITOR_TAPPED_BLOCKQUOTE, EDITOR_TAPPED_BOLD, EDITOR_TAPPED_ELLIPSIS_COLLAPSE, @@ -894,7 +896,6 @@ public enum Stat { APP_SETTINGS_VIDEO_OPTIMIZATION_CHANGED, APP_SETTINGS_MAX_VIDEO_SIZE_CHANGED, APP_SETTINGS_VIDEO_QUALITY_CHANGED, - APP_SETTINGS_OPTIMIZE_IMAGES_POPUP_TAPPED, PRIVACY_SETTINGS_OPENED, PRIVACY_SETTINGS_REPORT_CRASHES_TOGGLED, SHARING_BUTTONS_EDIT_SHARING_BUTTONS_CHANGED, diff --git a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTrackerNosara.java b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTrackerNosara.java index 8d863c39dfd6..6eeaa539722c 100644 --- a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTrackerNosara.java +++ b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTrackerNosara.java @@ -733,6 +733,8 @@ public static String getEventNameForStat(AnalyticsTracker.Stat stat) { return "editor_upload_media_failed"; case EDITOR_UPLOAD_MEDIA_RETRIED: return "editor_upload_media_retried"; + case EDITOR_UPLOAD_MEDIA_PAUSED: + return "editor_upload_media_paused"; case EDITOR_CLOSED: return "editor_closed"; case EDITOR_SESSION_START: @@ -1071,6 +1073,8 @@ public static String getEventNameForStat(AnalyticsTracker.Stat stat) { return "stats_accessed"; case STATS_ACCESS_ERROR: return "stats_access_error"; + case STATS_TRAFFIC_ACCESSED: + return "stats_traffic_accessed"; case STATS_INSIGHTS_ACCESSED: return "stats_insights_accessed"; case STATS_INSIGHTS_MANAGEMENT_HINT_DISMISSED: @@ -2269,8 +2273,6 @@ public static String getEventNameForStat(AnalyticsTracker.Stat stat) { return "app_settings_privacy_settings_tapped"; case APP_SETTINGS_OPEN_DEVICE_SETTINGS_TAPPED: return "app_settings_open_device_settings_tapped"; - case APP_SETTINGS_OPTIMIZE_IMAGES_POPUP_TAPPED: - return "app_settings_optimize_images_popup_tapped"; case APP_SETTINGS_MAX_IMAGE_SIZE_CHANGED: return "app_settings_max_image_size_changed"; case APP_SETTINGS_IMAGE_QUALITY_CHANGED: diff --git a/libs/editor/build.gradle b/libs/editor/build.gradle index b6323f01d4c0..288035530a8a 100644 --- a/libs/editor/build.gradle +++ b/libs/editor/build.gradle @@ -2,6 +2,7 @@ plugins { id "com.android.library" id "org.jetbrains.kotlin.android" id "org.jetbrains.kotlin.plugin.parcelize" + id "org.jetbrains.kotlinx.kover" } repositories { @@ -10,6 +11,9 @@ repositories { content { includeGroup "org.wordpress" includeGroup "org.wordpress.aztec" + includeGroupByRegex "org.wordpress.react-native-libraries.*" + // 'org.wordpress-mobile' group is deprecated. It's kept for now for smoother transition + // but it should be removed soon (within couple weeks) includeGroup "org.wordpress-mobile" includeGroup "org.wordpress-mobile.gutenberg-mobile" includeGroupByRegex "org.wordpress-mobile.react-native-libraries.*" diff --git a/libs/editor/src/main/java/org/wordpress/android/editor/AztecEditorFragment.java b/libs/editor/src/main/java/org/wordpress/android/editor/AztecEditorFragment.java index 4c945a3feced..836c3e67a011 100644 --- a/libs/editor/src/main/java/org/wordpress/android/editor/AztecEditorFragment.java +++ b/libs/editor/src/main/java/org/wordpress/android/editor/AztecEditorFragment.java @@ -1431,6 +1431,12 @@ public void onMediaUploadFailed(final String localMediaId) { mUploadingMediaProgressMax.remove(localMediaId); } + @Override + public void onMediaUploadPaused(final String localMediaId) { + // Aztec does not leverage the paused media state, only the Gutenberg editor + onMediaUploadFailed(localMediaId); + } + @Override public void onVideoInfoRequested(final AztecAttributes attrs) { // VideoPress special case here diff --git a/libs/editor/src/main/java/org/wordpress/android/editor/EditorMediaUploadListener.java b/libs/editor/src/main/java/org/wordpress/android/editor/EditorMediaUploadListener.java index ce90ac13eaf7..298c9f0d9160 100644 --- a/libs/editor/src/main/java/org/wordpress/android/editor/EditorMediaUploadListener.java +++ b/libs/editor/src/main/java/org/wordpress/android/editor/EditorMediaUploadListener.java @@ -9,4 +9,5 @@ public interface EditorMediaUploadListener { void onMediaUploadProgress(String localId, float progress); void onMediaUploadFailed(String localId); void onGalleryMediaUploadSucceeded(long galleryId, long remoteId, int remaining); + void onMediaUploadPaused(String localId); } diff --git a/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java b/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java index e22a413c465b..ec062f54e37e 100644 --- a/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java +++ b/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergContainerFragment.java @@ -261,6 +261,10 @@ public void mediaFileUploadFailed(final int mediaId) { mWPAndroidGlueCode.mediaFileUploadFailed(mediaId); } + public void mediaFileUploadPaused(final int mediaId) { + mWPAndroidGlueCode.mediaFileUploadPaused(mediaId); + } + public void mediaFileUploadSucceeded(final int mediaId, final String mediaUrl, final int serverMediaId) { mWPAndroidGlueCode.mediaFileUploadSucceeded(mediaId, mediaUrl, serverMediaId, new WritableNativeMap()); } diff --git a/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java b/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java index 438ff12889ee..3aedae6fd8cc 100644 --- a/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java +++ b/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergEditorFragment.java @@ -845,7 +845,11 @@ private void updateFailedMediaState() { for (String mediaId : mFailedMediaIds) { // upload progress should work on numeric mediaIds only if (!TextUtils.isEmpty(mediaId) && TextUtils.isDigitsOnly(mediaId)) { - getGutenbergContainerFragment().mediaFileUploadFailed(Integer.valueOf(mediaId)); + if (NetworkUtils.isNetworkAvailable(getActivity())) { + getGutenbergContainerFragment().mediaFileUploadFailed(Integer.valueOf(mediaId)); + } else { + getGutenbergContainerFragment().mediaFileUploadPaused(Integer.valueOf(mediaId)); + } } else { getGutenbergContainerFragment().mediaFileSaveFailed(mediaId); } @@ -1496,6 +1500,13 @@ public void onMediaUploadFailed(final String localMediaId) { mUploadingMediaProgressMax.remove(localMediaId); } + @Override + public void onMediaUploadPaused(final String localMediaId) { + getGutenbergContainerFragment().mediaFileUploadPaused(Integer.valueOf(localMediaId)); + mFailedMediaIds.add(localMediaId); + mUploadingMediaProgressMax.remove(localMediaId); + } + @Override public void onGalleryMediaUploadSucceeded(final long galleryId, long remoteMediaId, int remaining) { } @@ -1590,7 +1601,7 @@ public void onGutenbergDialogNegativeClicked(@NonNull String instanceTag) { @Override public void onConnectionStatusChange(boolean isConnected) { getGutenbergContainerFragment().onConnectionStatusChange(isConnected); - if (BuildConfig.DEBUG && isConnected && hasFailedMediaUploads()) { + if (isConnected && hasFailedMediaUploads()) { mEditorFragmentListener.onMediaRetryAll(mFailedMediaIds); } } diff --git a/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergPropsBuilder.kt b/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergPropsBuilder.kt index c4117e0251f9..53ad2c0c9aae 100644 --- a/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergPropsBuilder.kt +++ b/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergPropsBuilder.kt @@ -18,7 +18,6 @@ data class GutenbergPropsBuilder( private val enableInstagramEmbed: Boolean, private val enableLoomEmbed: Boolean, private val enableSmartframeEmbed: Boolean, - private val enableMediaFilesCollectionBlocks: Boolean, private val enableMentions: Boolean, private val enableXPosts: Boolean, private val enableUnsupportedBlockEditor: Boolean, @@ -43,7 +42,6 @@ data class GutenbergPropsBuilder( enableInstagramEmbed = enableInstagramEmbed, enableLoomEmbed = enableLoomEmbed, enableSmartframeEmbed = enableSmartframeEmbed, - enableMediaFilesCollectionBlocks = enableMediaFilesCollectionBlocks, enableMentions = enableMentions, enableXPosts = enableXPosts, enableUnsupportedBlockEditor = enableUnsupportedBlockEditor, diff --git a/libs/image-editor/build.gradle b/libs/image-editor/build.gradle index 21fbf110e765..2cdd7f6bf40f 100644 --- a/libs/image-editor/build.gradle +++ b/libs/image-editor/build.gradle @@ -3,6 +3,7 @@ plugins { id "org.jetbrains.kotlin.android" id "org.jetbrains.kotlin.plugin.parcelize" id "androidx.navigation.safeargs.kotlin" + id "org.jetbrains.kotlinx.kover" } android { diff --git a/libs/processors/build.gradle b/libs/processors/build.gradle index 03b3715127f0..8e338bd2ea76 100644 --- a/libs/processors/build.gradle +++ b/libs/processors/build.gradle @@ -1,6 +1,7 @@ plugins { id "org.jetbrains.kotlin.jvm" id "org.jetbrains.kotlin.kapt" + id "org.jetbrains.kotlinx.kover" } sourceCompatibility = JavaVersion.VERSION_1_8 diff --git a/settings.gradle b/settings.gradle index 5ce20fe7d7ed..5e48b42287db 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,6 +8,7 @@ pluginManagement { gradle.ext.detektVersion = '1.23.0' gradle.ext.violationCommentsVersion = '1.67' gradle.ext.measureBuildsVersion = '2.0.3' + gradle.ext.koverVersion = '0.7.5' plugins { id "org.jetbrains.kotlin.android" version gradle.ext.kotlinVersion @@ -24,6 +25,7 @@ pluginManagement { id "io.gitlab.arturbosch.detekt" version gradle.ext.detektVersion id "se.bjurr.violations.violation-comments-to-github-gradle-plugin" version gradle.ext.violationCommentsVersion id 'com.automattic.android.measure-builds' version gradle.ext.measureBuildsVersion + id "org.jetbrains.kotlinx.kover" version gradle.ext.koverVersion } repositories { maven { diff --git a/version.properties b/version.properties index 49023e812e58..677e9c3a4ea9 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ -versionName=24.0.2 -versionCode=1404 \ No newline at end of file +versionName=24.1-rc-2 +versionCode=1404