diff --git a/.buildkite/commands/run-unit-tests.sh b/.buildkite/commands/run-unit-tests.sh index 9b1e5be2c019..4a05b309927b 100755 --- a/.buildkite/commands/run-unit-tests.sh +++ b/.buildkite/commands/run-unit-tests.sh @@ -14,12 +14,10 @@ set +e :libs:processors:test \ :libs:image-editor:testDebugUnitTest \ :libs:fluxc:testDebugUnitTest \ - :libs:login:testDebugUnitTest \ koverXmlReportWordpressWasabiDebug \ :libs:processors:koverXmlReportJvm \ :libs:image-editor:koverXmlReportDebug \ - :libs:fluxc:koverXmlReportDebug \ - :libs:login:koverXmlReportDebug + :libs:fluxc:koverXmlReportDebug TESTS_EXIT_STATUS=$? set -e echo "" @@ -49,7 +47,6 @@ declare -A TEST_RESULT_DIRS=( ["processors"]="libs/processors/build/test-results/test" ["image-editor"]="libs/image-editor/build/test-results/testDebugUnitTest" ["fluxc"]="libs/fluxc/build/test-results/testDebugUnitTest" - ["login"]="libs/login/build/test-results/testDebugUnitTest" ) # Create temporary directory for collecting all test results diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index b180c030ef64..2f8f67fda4f7 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -2,7 +2,7 @@ 26.6 ----- - +* [**] Improved the login experience – it now works edge-to-edge and should be easier to read. 26.5 ----- diff --git a/WordPress/build.gradle b/WordPress/build.gradle index cd4cb1c3e5eb..dfc8f056d832 100644 --- a/WordPress/build.gradle +++ b/WordPress/build.gradle @@ -168,7 +168,6 @@ android { // Override these constants in jetpack product flavor to enable/ disable features buildConfigField "boolean", "ENABLE_SITE_CREATION", "true" buildConfigField "boolean", "ENABLE_ADD_SELF_HOSTED_SITE", "true" - buildConfigField "boolean", "ENABLE_SIGNUP", "true" buildConfigField "boolean", "ENABLE_READER", "true" buildConfigField "boolean", "ENABLE_CREATE_FAB", "true" buildConfigField "boolean", "ENABLE_FOLLOWED_SITES_SETTINGS", "true" @@ -221,7 +220,6 @@ android { buildConfigField "boolean", "IS_JETPACK_APP", "true" buildConfigField "boolean", "ENABLE_SITE_CREATION", "true" buildConfigField "boolean", "ENABLE_ADD_SELF_HOSTED_SITE", "true" - buildConfigField "boolean", "ENABLE_SIGNUP", "true" buildConfigField "boolean", "ENABLE_READER", "true" buildConfigField "boolean", "ENABLE_CREATE_FAB", "true" buildConfigField "boolean", "ENABLE_FOLLOWED_SITES_SETTINGS", "true" @@ -394,7 +392,6 @@ dependencies { strictly libs.wordpress.utils.get().version } } - implementation project(":libs:login") implementation project(":libs:posttypes") implementation("$gradle.ext.aboutAutomatticBinaryPath:${libs.versions.automattic.about.get()}") @@ -436,7 +433,6 @@ dependencies { implementation(libs.androidx.lifecycle.process) implementation(libs.android.volley) implementation(libs.google.play.review) - implementation(libs.google.play.services.auth) implementation(libs.google.mlkit.barcode.scanning.common) implementation(libs.google.mlkit.text.recognition) implementation(libs.google.mlkit.barcode.scanning.main) @@ -608,10 +604,6 @@ if (!file("google-services.json").exists()) { } } -// Print warning message if example Google services file is used. -if ((file('google-services.json').text) == (file('google-services.json-example').text)) { - println("WARNING: You're using the example google-services.json file. Google login will fail.") -} tasks.register("printVersionName") { def versionName = android.defaultConfig.versionName diff --git a/WordPress/src/androidTest/java/org/wordpress/android/e2e/LoginTests.kt b/WordPress/src/androidTest/java/org/wordpress/android/e2e/LoginTests.kt index dd9380378e54..87592bca35c2 100644 --- a/WordPress/src/androidTest/java/org/wordpress/android/e2e/LoginTests.kt +++ b/WordPress/src/androidTest/java/org/wordpress/android/e2e/LoginTests.kt @@ -6,7 +6,6 @@ import org.junit.Test import org.wordpress.android.e2e.flows.LoginFlow import org.wordpress.android.support.BaseTest import org.wordpress.android.support.ComposeEspressoLink -import org.wordpress.android.support.E2ECredentials @HiltAndroidTest class LoginTests : BaseTest() { @@ -16,61 +15,11 @@ class LoginTests : BaseTest() { logoutIfNecessary() } - @Test - fun e2eLoginWithEmailPassword() { - LoginFlow().chooseContinueWithWpCom(super.mComposeTestRule) - .enterEmailAddress(E2ECredentials.WP_COM_USER_EMAIL) - .enterPassword(E2ECredentials.WP_COM_USER_PASSWORD) - .confirmLogin() - - ComposeEspressoLink().unregister() - } - - @Test - fun e2eLoginWithPasswordlessAccount() { - LoginFlow().chooseContinueWithWpCom(super.mComposeTestRule) - .enterEmailAddress(E2ECredentials.WP_COM_PASSWORDLESS_USER_EMAIL) - .openMagicLink() - .confirmLogin() - - ComposeEspressoLink().unregister() - } - @Test fun e2eLoginWithSiteAddress() { + // Self-hosted login now uses application passwords via Custom Tabs, + // so this test only verifies navigation to the site address screen LoginFlow().chooseEnterYourSiteAddress(super.mComposeTestRule) - .enterSiteAddress(E2ECredentials.WP_COM_USER_SITE_ADDRESS) - .enterEmailAddress(E2ECredentials.WP_COM_USER_EMAIL) - .enterPassword(E2ECredentials.WP_COM_USER_PASSWORD) - .confirmLogin() - - ComposeEspressoLink().unregister() - } - - @Test - fun e2eLoginWithMagicLink() { - try { - LoginFlow().chooseContinueWithWpCom(super.mComposeTestRule) - .enterEmailAddress(E2ECredentials.WP_COM_USER_EMAIL) - .chooseMagicLink() - .openMagicLink() - .confirmLogin() - - ComposeEspressoLink().unregister() - } finally { - logoutIfNecessary() - } - } - - @Test - fun e2eLoginWithSelfHostedAccount() { - LoginFlow().chooseEnterYourSiteAddress(super.mComposeTestRule) - .enterSiteAddress(E2ECredentials.SELF_HOSTED_USER_SITE_ADDRESS) - .enterUsernameAndPassword( - E2ECredentials.SELF_HOSTED_USER_USERNAME, - E2ECredentials.SELF_HOSTED_USER_PASSWORD - ) - .confirmLogin() ComposeEspressoLink().unregister() } diff --git a/WordPress/src/androidTest/java/org/wordpress/android/e2e/SignUpTests.kt b/WordPress/src/androidTest/java/org/wordpress/android/e2e/SignUpTests.kt deleted file mode 100644 index b97fca579f46..000000000000 --- a/WordPress/src/androidTest/java/org/wordpress/android/e2e/SignUpTests.kt +++ /dev/null @@ -1,37 +0,0 @@ -package org.wordpress.android.e2e - -import dagger.hilt.android.testing.HiltAndroidTest -import org.junit.Before -import org.junit.Test -import org.wordpress.android.e2e.flows.SignupFlow -import org.wordpress.android.support.BaseTest -import org.wordpress.android.support.ComposeEspressoLink -import org.wordpress.android.support.E2ECredentials - -@HiltAndroidTest -class SignUpTests : BaseTest() { - @Before - fun setUp() { - ComposeEspressoLink().unregister() - logoutIfNecessary() - } - - @Test - fun e2eSignUpWithMagicLink() { - try { - SignupFlow().chooseContinueWithWpCom(super.mComposeTestRule) - .enterEmail(E2ECredentials.SIGNUP_EMAIL) - .openMagicLink() - .checkEpilogue( - E2ECredentials.SIGNUP_DISPLAY_NAME, - E2ECredentials.SIGNUP_USERNAME - ) - .enterPassword(E2ECredentials.SIGNUP_PASSWORD) - .dismissInterstitial() - .dismissJetpackAd() - .confirmSignup() - } finally { - logoutIfNecessary() - } - } -} diff --git a/WordPress/src/androidTest/java/org/wordpress/android/e2e/flows/LoginFlow.kt b/WordPress/src/androidTest/java/org/wordpress/android/e2e/flows/LoginFlow.kt index a8dcf13ecd86..5934d59f8995 100644 --- a/WordPress/src/androidTest/java/org/wordpress/android/e2e/flows/LoginFlow.kt +++ b/WordPress/src/androidTest/java/org/wordpress/android/e2e/flows/LoginFlow.kt @@ -1,113 +1,28 @@ package org.wordpress.android.e2e.flows -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.widget.EditText import androidx.compose.ui.test.junit4.ComposeTestRule -import androidx.test.core.app.ActivityScenario -import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.Espresso import androidx.test.espresso.matcher.ViewMatchers -import org.hamcrest.CoreMatchers -import org.hamcrest.Matchers -import org.wordpress.android.BuildConfig import org.wordpress.android.R import org.wordpress.android.e2e.pages.HelpScreen import org.wordpress.android.e2e.pages.LandingPage.tapContinueWithWpCom import org.wordpress.android.e2e.pages.LandingPage.tapEnterYourSiteAddress import org.wordpress.android.support.WPSupportUtils -import org.wordpress.android.login.R as LoginR class LoginFlow { fun chooseContinueWithWpCom(composeTestRule: ComposeTestRule?): LoginFlow { // Login Prologue – We want to Continue with WordPress.com, not a site address + // Note: WP.com login now uses web-based OAuth flow via Custom Tabs tapContinueWithWpCom(composeTestRule!!) return this } - fun enterEmailAddress(emailAddress: String?): LoginFlow { - // Email Address Screen – Fill it in and click "Continue" - // See LoginEmailFragment - WPSupportUtils.populateTextField(R.id.input, emailAddress) - WPSupportUtils.clickOn(LoginR.id.login_continue_button) - return this - } - - fun enterPassword(password: String?): LoginFlow { - // Password Screen – Fill it in and click "Continue" - // See LoginEmailPasswordFragment - WPSupportUtils.populateTextField(R.id.input, password) - WPSupportUtils.clickOn(R.id.bottom_button) - return this - } - - fun chooseMagicLink(): LoginFlow { - // Password Screen – Choose "Get a login link by email" - // See LoginEmailPasswordFragment - WPSupportUtils.clickOn(LoginR.id.login_get_email_link) - return this - } - - fun openMagicLink(): LoginFlow { - // Magic Link Sent Screen – Should see "Check email" button - // See LoginMagicLinkSentFragment - WPSupportUtils.waitForElementToBeDisplayed(LoginR.id.login_open_email_client) - - // Follow the magic link to continue login - // Intent is invoked directly rather than through a browser as WireMock is unavailable once in the background - val appVariant = BuildConfig.FLAVOR_app - val intent = - Intent(Intent.ACTION_VIEW, Uri.parse("$appVariant://magic-login?token=valid_token")) - .setPackage(ApplicationProvider.getApplicationContext().packageName) - ActivityScenario.launch(intent) - return this - } - - fun enterUsernameAndPassword(username: String, password: String): LoginFlow { - val usernameElement = Espresso.onView( - CoreMatchers.allOf( - ViewMatchers.isDescendantOfA(ViewMatchers.withId(LoginR.id.login_username_row)), - Matchers.instanceOf(EditText::class.java) - ) - ) - val passwordElement = Espresso.onView( - CoreMatchers.allOf( - ViewMatchers.isDescendantOfA(ViewMatchers.withId(LoginR.id.login_password_row)), - Matchers.instanceOf(EditText::class.java) - ) - ) - WPSupportUtils.populateTextField( - usernameElement, """ - $username - - """.trimIndent() - ) - WPSupportUtils.populateTextField( - passwordElement, """ - $password - - """.trimIndent() - ) - WPSupportUtils.clickOn(R.id.bottom_button) - return this - } - fun chooseEnterYourSiteAddress(composeTestRule: ComposeTestRule?): LoginFlow { // Login Prologue – We want to continue with a site address not a WordPress.com account tapEnterYourSiteAddress(composeTestRule!!) return this } - fun enterSiteAddress(siteAddress: String?): LoginFlow { - // Site Address Screen – Fill it in and click "Continue" - // See LoginSiteApplicationPasswordFragment - WPSupportUtils.populateTextField(R.id.input, siteAddress) - WPSupportUtils.clickOn(R.id.bottom_button) - return this - } - fun tapHelp(): HelpScreen { WPSupportUtils.clickOn(Espresso.onView(ViewMatchers.withId(R.id.help))) return HelpScreen() diff --git a/WordPress/src/androidTest/java/org/wordpress/android/e2e/flows/SignupFlow.kt b/WordPress/src/androidTest/java/org/wordpress/android/e2e/flows/SignupFlow.kt deleted file mode 100644 index f98217559cb0..000000000000 --- a/WordPress/src/androidTest/java/org/wordpress/android/e2e/flows/SignupFlow.kt +++ /dev/null @@ -1,102 +0,0 @@ -package org.wordpress.android.e2e.flows - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.net.Uri -import androidx.compose.ui.test.junit4.ComposeTestRule -import androidx.test.core.app.ActivityScenario -import androidx.test.core.app.ApplicationProvider -import androidx.test.espresso.Espresso -import androidx.test.espresso.matcher.ViewMatchers -import org.hamcrest.Matchers -import org.wordpress.android.BuildConfig -import org.wordpress.android.R -import org.wordpress.android.e2e.pages.LandingPage.tapContinueWithWpCom -import org.wordpress.android.support.WPSupportUtils -import org.wordpress.android.login.R as LoginR - -class SignupFlow { - fun chooseContinueWithWpCom(composeTestRule: ComposeTestRule?): SignupFlow { - // Login Prologue – We want to Continue with WordPress.com, not a site address - tapContinueWithWpCom(composeTestRule!!) - return this - } - - fun enterEmail(email: String?): SignupFlow { - // Email file = id/input - WPSupportUtils.populateTextField(Espresso.onView(ViewMatchers.withId(R.id.input)), email) - WPSupportUtils.clickOn(Espresso.onView(ViewMatchers.withId(LoginR.id.login_continue_button))) - return this - } - - fun openMagicLink(): SignupFlow { - // Should see "Check email" button - // See SignupMagicLinkFragment - WPSupportUtils.waitForElementToBeDisplayed(LoginR.id.signup_magic_link_button) - - // Follow the magic link to continue login - // Intent is invoked directly rather than through a browser as WireMock is unavailable once in the background - val appVariant = BuildConfig.FLAVOR_app // Either "wordpress" or "jetpack" - val intent = Intent( - Intent.ACTION_VIEW, - Uri.parse("$appVariant://magic-login?token=valid_token&new_user=1") - ).setPackage(ApplicationProvider.getApplicationContext().packageName) - ActivityScenario.launch(intent) - return this - } - - fun checkEpilogue(displayName: String?, username: String?): SignupFlow { - // Check Epilogue data - val emailHeaderView = - Espresso.onView(ViewMatchers.withId(R.id.login_epilogue_header_subtitle)) - WPSupportUtils.waitForElementToBeDisplayed(emailHeaderView) - val displayNameField = Espresso.onView( - Matchers.allOf( - ViewMatchers.withId(R.id.input), - ViewMatchers.withText(displayName) - ) - ) - val usernameField = Espresso.onView( - Matchers.allOf( - ViewMatchers.withId(R.id.input), - ViewMatchers.withText(username) - ) - ) - WPSupportUtils.waitForElementToBeDisplayed(displayNameField) - WPSupportUtils.waitForElementToBeDisplayed(usernameField) - return this - } - - fun enterPassword(password: String?): SignupFlow { - // Enter Password - val passwordField = Espresso.onView( - Matchers.allOf( - ViewMatchers.withId(R.id.input), - ViewMatchers.withHint("Password (optional)") - ) - ) - WPSupportUtils.waitForElementToBeDisplayed(passwordField) - WPSupportUtils.populateTextField(passwordField, password) - - // Click continue - WPSupportUtils.clickOn(Espresso.onView(ViewMatchers.withId(R.id.bottom_button))) - return this - } - - fun dismissInterstitial(): SignupFlow { - // Dismiss post-signup interstitial - WPSupportUtils.clickOn(Espresso.onView(ViewMatchers.withId(R.id.dismiss_button))) - return this - } - - fun dismissJetpackAd(): SignupFlow { - WPSupportUtils.dismissJetpackAdIfPresent() - return this - } - - fun confirmSignup() { - // Confirm signup - WPSupportUtils.waitForElementToBeDisplayed(R.id.nav_sites) - } -} diff --git a/WordPress/src/androidTest/java/org/wordpress/android/support/BaseTest.java b/WordPress/src/androidTest/java/org/wordpress/android/support/BaseTest.java index b36dfd6fba4d..6c54d5c9c7c4 100644 --- a/WordPress/src/androidTest/java/org/wordpress/android/support/BaseTest.java +++ b/WordPress/src/androidTest/java/org/wordpress/android/support/BaseTest.java @@ -17,7 +17,6 @@ import org.wordpress.android.BuildConfig; import org.wordpress.android.InitializationRule; import org.wordpress.android.R; -import org.wordpress.android.e2e.flows.LoginFlow; import org.wordpress.android.e2e.pages.MePage; import org.wordpress.android.e2e.pages.MySitesPage; import org.wordpress.android.rules.RetryTestRule; @@ -35,8 +34,6 @@ import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.is; import static org.wordpress.android.support.E2ECredentials.SELF_HOSTED_USER_SITE_ADDRESS; -import static org.wordpress.android.support.E2ECredentials.WP_COM_USER_EMAIL; -import static org.wordpress.android.support.E2ECredentials.WP_COM_USER_PASSWORD; import static org.wordpress.android.support.WPSupportUtils.isElementDisplayed; public class BaseTest { @@ -96,11 +93,11 @@ protected void logoutIfNecessary() { } protected void wpLogin() { - logoutIfNecessary(); - new LoginFlow().chooseContinueWithWpCom(mComposeTestRule) - .enterEmailAddress(WP_COM_USER_EMAIL) - .enterPassword(WP_COM_USER_PASSWORD) - .confirmLogin(); + // WP.com login now uses web-based OAuth flow which cannot be automated via Espresso. + // Tests requiring WP.com login need a different approach (e.g., pre-authenticated state). + throw new UnsupportedOperationException( + "WP.com login is now web-based and cannot be automated via Espresso" + ); } private void wpLogout() { diff --git a/WordPress/src/jetpack/java/org/wordpress/android/ui/accounts/login/LoginPrologueRevampedFragment.kt b/WordPress/src/jetpack/java/org/wordpress/android/ui/accounts/login/LoginPrologueRevampedFragment.kt index 520e23b4d4aa..286e7b5495cd 100644 --- a/WordPress/src/jetpack/java/org/wordpress/android/ui/accounts/login/LoginPrologueRevampedFragment.kt +++ b/WordPress/src/jetpack/java/org/wordpress/android/ui/accounts/login/LoginPrologueRevampedFragment.kt @@ -6,7 +6,11 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -28,6 +32,7 @@ import androidx.fragment.app.viewModels import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.isActive import org.wordpress.android.R +import org.wordpress.android.ui.accounts.LoginActivity import org.wordpress.android.ui.accounts.login.components.LoopingTextWithBackground import org.wordpress.android.ui.accounts.login.components.PrimaryButton import org.wordpress.android.ui.accounts.login.components.SecondaryButton @@ -41,7 +46,7 @@ val LocalPosition = compositionLocalOf { 0f } @AndroidEntryPoint class LoginPrologueRevampedFragment : Fragment() { - private lateinit var loginPrologueListener: LoginPrologueListener + private lateinit var loginActivity: LoginActivity private val viewModel by viewModels() override fun onCreateView( @@ -55,11 +60,11 @@ class LoginPrologueRevampedFragment : Fragment() { LoginScreenRevamped( onWpComLoginClicked = { viewModel.onWpComLoginClicked() - loginPrologueListener.showWPcomLoginScreen(this.context) + loginActivity.showWPcomLoginScreen(this.context) }, onSiteAddressLoginClicked = { viewModel.onSiteAddressLoginClicked() - loginPrologueListener.loginViaSiteAddress() + loginActivity.loginViaSiteAddress() }, ) } @@ -69,8 +74,8 @@ class LoginPrologueRevampedFragment : Fragment() { override fun onAttach(context: Context) { super.onAttach(context) - check(context is LoginPrologueListener) { "$context must implement LoginPrologueListener" } - loginPrologueListener = context + check(context is LoginActivity) { "$context must be LoginActivity" } + loginActivity = context } companion object { @@ -112,11 +117,12 @@ private fun LoginScreenRevamped( onWpComLoginClicked: () -> Unit, onSiteAddressLoginClicked: () -> Unit, ) { - Box { + Box(modifier = Modifier.fillMaxSize()) { LoopingTextWithBackground() TopLinearGradient() WordpressJetpackLogo( modifier = Modifier + .statusBarsPadding() .padding(top = dimensionResource(id = R.dimen.login_prologue_logo_top_padding)) .width(132.dp) .align(Alignment.TopCenter) @@ -131,6 +137,7 @@ private fun LoginScreenRevamped( onClick = onWpComLoginClicked, modifier = Modifier.testTag(TestTags.BUTTON_WPCOM_AUTH) ) + Spacer(modifier = Modifier.height(16.dp)) SecondaryButton(onClick = onSiteAddressLoginClicked) } } diff --git a/WordPress/src/jetpack/java/org/wordpress/android/ui/accounts/login/components/SecondaryButton.kt b/WordPress/src/jetpack/java/org/wordpress/android/ui/accounts/login/components/SecondaryButton.kt index 1b04d3ae09ff..b90a713b06f8 100644 --- a/WordPress/src/jetpack/java/org/wordpress/android/ui/accounts/login/components/SecondaryButton.kt +++ b/WordPress/src/jetpack/java/org/wordpress/android/ui/accounts/login/components/SecondaryButton.kt @@ -34,7 +34,7 @@ fun SecondaryButton( ), modifier = modifier .padding(horizontal = dimensionResource(R.dimen.login_prologue_revamped_buttons_padding)) - .padding(bottom = 60.dp) + .padding(bottom = 16.dp) .fillMaxWidth(), ) { Text( diff --git a/WordPress/src/jetpack/res/values/strings.xml b/WordPress/src/jetpack/res/values/strings.xml index f5c6d242def6..38f535f387e7 100644 --- a/WordPress/src/jetpack/res/values/strings.xml +++ b/WordPress/src/jetpack/res/values/strings.xml @@ -36,8 +36,8 @@ Jetpack for Android Support - Log out of Jetpack - Log out of Jetpack? + Log out of WordPress.com + Log out of WordPress.com? About Jetpack diff --git a/WordPress/src/jetpack/res/values/styles_login.xml b/WordPress/src/jetpack/res/values/styles_login.xml index de1807300aa4..045e125f3d8d 100644 --- a/WordPress/src/jetpack/res/values/styles_login.xml +++ b/WordPress/src/jetpack/res/values/styles_login.xml @@ -1,26 +1,3 @@ - - - - - - - - - - - diff --git a/WordPress/src/main/AndroidManifest.xml b/WordPress/src/main/AndroidManifest.xml index 981df4ce2d2b..2a0d592311d9 100644 --- a/WordPress/src/main/AndroidManifest.xml +++ b/WordPress/src/main/AndroidManifest.xml @@ -129,7 +129,8 @@ android:label="@string/me_section_screen_title" /> @@ -145,7 +146,7 @@ @@ -187,17 +188,6 @@ - - - - - - mDispatchingAndroidInjector; @Inject protected LoginAnalyticsListener mLoginAnalyticsListener; - @Inject ZendeskHelper mZendeskHelper; @Inject UnifiedLoginTracker mUnifiedLoginTracker; @Inject protected SiteStore mSiteStore; @Inject protected AccountStore mAccountStore; @@ -162,22 +110,30 @@ private enum SmartLockHelperState { // Flag to track when we're waiting for account/sites to load after OAuth login private boolean mIsWaitingForSitesToLoad = false; private ArrayList mOldSitesIdsForLoginUpdate; - @Inject BuildConfigWrapper mBuildConfigWrapper; - @Inject ContactSupportFeatureConfig mContactSupportFeatureConfig; @Inject ExperimentalFeatures mExperimentalFeatures; - @Inject LoginCompletionUseCase mLoginCompletionUseCase; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Attempt Login if this activity was created in response to a user confirming login, and if - // successful clear the intent so we don't reuse the OAuth code if the activity is recreated - boolean loginProcessed = mLoginHelper.tryLoginWithDataString(getIntent().getDataString()); + // Enable edge-to-edge display + WindowCompat.setDecorFitsSystemWindows(getWindow(), false); - if (loginProcessed) { + // Attempt Login if this activity was created in response to a user confirming login + String dataString = getIntent().getDataString(); + if (mLoginHelper.hasOAuthCallback(dataString)) { getIntent().setData(null); + setContentView(R.layout.login_loading); + mLoginHelper.tryLoginWithDataString( + dataString, + () -> loggedInAndFinish(new ArrayList<>(), true), + error -> showLoginError(error) + ); + return; + } else { + // Not an OAuth callback - clear any pending login flow + AppPrefs.setPendingLoginFlow(null); } // Start preloading the WordPress.com login page if needed – this avoids visual hitches @@ -185,75 +141,43 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { mLoginHelper.bindCustomTabsService(this); // go no further if the user is already logged in and this is the login screen shown at startup - // FULL = WPAndroid - // JETPACK_LOGIN_ONLY = JPAndroid - LoginMode loginMode = getLoginMode(); - if ((mLoginHelper.isLoggedIn()) && (loginMode == LoginMode.FULL || loginMode == LoginMode.JETPACK_LOGIN_ONLY)) { + LoginFlow loginFlow = getLoginFlow(); + if ((mLoginHelper.isLoggedIn()) && (loginFlow == LoginFlow.PROLOGUE)) { + // Show loading UI while we fetch account and sites in the background + setContentView(R.layout.login_loading); this.loggedInAndFinish(new ArrayList(), true); return; } LoginFlowThemeHelper.injectMissingCustomAttributes(getTheme()); - setContentView(R.layout.login_activity); + FrameLayout fragmentContainer = new FrameLayout(this); + mFragmentContainerId = R.id.fragment_container; + fragmentContainer.setId(mFragmentContainerId); + fragmentContainer.setFitsSystemWindows(false); + fragmentContainer.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + )); + setContentView(fragmentContainer); if (savedInstanceState == null) { - if (getIntent() != null) { - mJetpackConnectSource = - (JetpackConnectionSource) getIntent().getSerializableExtra(ARG_JETPACK_CONNECT_SOURCE); - } - mLoginAnalyticsListener.trackLoginAccessed(); - switch (loginMode) { - case FULL: - case JETPACK_LOGIN_ONLY: - mUnifiedLoginTracker.setSource(Source.DEFAULT); - mIsSignupFromLoginEnabled = mBuildConfigWrapper.isSignupEnabled(); - loginFromPrologue(); + mUnifiedLoginTracker.setSource(loginFlow.getAnalyticsSource()); + + switch (loginFlow.getInitialScreen()) { + case PROLOGUE: + showFragment(new LoginPrologueRevampedFragment(), LoginPrologueRevampedFragment.TAG); break; - case WPCOM_LOGIN_ONLY: - case JETPACK_REST_CONNECT: - mUnifiedLoginTracker.setSource(Source.ADD_WORDPRESS_COM_ACCOUNT); - mIsSignupFromLoginEnabled = mBuildConfigWrapper.isSignupEnabled(); - checkSmartLockPasswordAndStartLogin(); + case WPCOM_OAUTH: + showWPcomLoginScreen(this); break; - case JETPACK_SELFHOSTED: - case SELFHOSTED_ONLY: - mUnifiedLoginTracker.setSource(Source.SELF_HOSTED); + case SELF_HOSTED: showFragment(new LoginSiteApplicationPasswordFragment(), LoginSiteApplicationPasswordFragment.TAG); break; - case JETPACK_STATS: - mUnifiedLoginTracker.setSource(Source.JETPACK); - mIsSignupFromLoginEnabled = mBuildConfigWrapper.isSignupEnabled(); - checkSmartLockPasswordAndStartLogin(); - break; - case WPCOM_LOGIN_DEEPLINK: - mUnifiedLoginTracker.setSource(Source.DEEPLINK); - checkSmartLockPasswordAndStartLogin(); - break; - case WPCOM_REAUTHENTICATE: - mUnifiedLoginTracker.setSource(Source.REAUTHENTICATION); - showWPcomLoginScreen(getBaseContext()); - break; - case SHARE_INTENT: - mUnifiedLoginTracker.setSource(Source.SHARE); - checkSmartLockPasswordAndStartLogin(); - break; - case WOO_LOGIN_MODE: - break; } } else { - mSmartLockHelperState = SmartLockHelperState.valueOf( - savedInstanceState.getString(KEY_SMARTLOCK_HELPER_STATE)); - - if (mSmartLockHelperState != SmartLockHelperState.NOT_TRIGGERED) { - // reconnect SmartLockHelper - initSmartLockHelperConnection(); - } - - mIsSignupFromLoginEnabled = savedInstanceState.getBoolean(KEY_SIGNUP_FROM_LOGIN_ENABLED); - mIsSiteLoginAvailableFromPrologue = savedInstanceState.getBoolean(KEY_SITE_LOGIN_AVAILABLE_FROM_PROLOGUE); String source = savedInstanceState.getString(KEY_UNIFIED_TRACKER_SOURCE); if (source != null) { mUnifiedLoginTracker.setSource(source); @@ -278,19 +202,9 @@ private void initViewModel() { }); } - private void loginFromPrologue() { - showFragment(new LoginPrologueRevampedFragment(), LoginPrologueRevampedFragment.TAG); - mIsSmartLockTriggeredFromPrologue = true; - mIsSiteLoginAvailableFromPrologue = true; - initSmartLockIfNotFinished(true); - } - @Override - public void onSaveInstanceState(Bundle outState) { + public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); - outState.putString(KEY_SMARTLOCK_HELPER_STATE, mSmartLockHelperState.name()); - outState.putBoolean(KEY_SIGNUP_FROM_LOGIN_ENABLED, mIsSignupFromLoginEnabled); - outState.putBoolean(KEY_SITE_LOGIN_AVAILABLE_FROM_PROLOGUE, mIsSiteLoginAvailableFromPrologue); outState.putString(KEY_UNIFIED_TRACKER_SOURCE, mUnifiedLoginTracker.getSource().getValue()); Flow flow = mUnifiedLoginTracker.getFlow(); if (flow != null) { @@ -298,6 +212,24 @@ public void onSaveInstanceState(Bundle outState) { } } + @Override + protected void onNewIntent(@NonNull Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + + // Handle OAuth callback when activity is reused (singleTop) + String dataString = intent.getDataString(); + if (mLoginHelper.hasOAuthCallback(dataString)) { + intent.setData(null); + setContentView(R.layout.login_loading); + mLoginHelper.tryLoginWithDataString( + dataString, + () -> loggedInAndFinish(new ArrayList<>(), true), + error -> showLoginError(error) + ); + } + } + @Override protected void onStart() { super.onStart(); @@ -310,6 +242,23 @@ protected void onStop() { mDispatcher.unregister(this); } + @Override + protected void onDestroy() { + super.onDestroy(); + mLoginHelper.dispose(); + } + + @Override + protected void onResume() { + super.onResume(); + // Check if self-hosted login completed while in share flow + // ApplicationPasswordLoginActivity finishes back here after successful login + if (getLoginFlow() == LoginFlow.SHARE_INTENT && mSiteStore.hasSite()) { + setResult(Activity.RESULT_OK); + finish(); + } + } + @SuppressWarnings("unused") @Subscribe(threadMode = ThreadMode.MAIN) public void onAccountChanged(OnAccountChanged event) { @@ -341,88 +290,109 @@ public void onSiteChanged(OnSiteChanged event) { private void finishLoginAfterSitesLoaded() { mIsWaitingForSitesToLoad = false; - navigateToMainActivityOrFinish(); + ArrayList oldSitesIds = mOldSitesIdsForLoginUpdate != null + ? mOldSitesIdsForLoginUpdate + : new ArrayList<>(); + mOldSitesIdsForLoginUpdate = null; + loggedInAndFinish(oldSitesIds, false); } /** - * Navigates to the main activity (or post-signup interstitial) and finishes the login flow. + * Navigates to the main activity and finishes the login flow. * This is the common exit point for successful logins. */ private void navigateToMainActivityOrFinish() { - MainNavigationDestination destination = - mLoginCompletionUseCase.getMainNavigationDestination(getLoginMode(), mSiteStore.hasSite()); - switch (destination) { - case POST_SIGNUP_INTERSTITIAL: - ActivityLauncher.showPostSignupInterstitial(this); - break; - case MAIN_ACTIVITY: - ActivityLauncher.showMainActivity(this); - break; - case FINISH_ONLY: - default: - // For other modes (JETPACK_STATS, JETPACK_REST_CONNECT, WPCOM_LOGIN_DEEPLINK, - // WPCOM_REAUTHENTICATE, etc.), just finish and let the caller handle navigation - break; + startPostLoginServices(); + + LoginFlow.CompletionBehavior behavior = getLoginFlow().getCompletionBehavior(); + if (behavior == LoginFlow.CompletionBehavior.MAIN_ACTIVITY) { + // Select the primary site after WP.com login + ActivityLauncher.showMainActivity(this, false, true); } + // For FINISH and FINISH_WITH_SITE, just finish and let the caller handle navigation setResult(Activity.RESULT_OK); finish(); } - private void showFragment(Fragment fragment, String tag) { + private void showPrologueScreen() { + LoginFlowThemeHelper.injectMissingCustomAttributes(getTheme()); + FrameLayout fragmentContainer = new FrameLayout(this); + mFragmentContainerId = R.id.fragment_container; + fragmentContainer.setId(mFragmentContainerId); + fragmentContainer.setFitsSystemWindows(false); + fragmentContainer.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + )); + setContentView(fragmentContainer); + showFragment( + new LoginPrologueRevampedFragment(), + LoginPrologueRevampedFragment.TAG + ); + } + + private void showLoginError(@NonNull Exception error) { + AppLog.e(T.MAIN, "OAuth login failed", error); + + View progressBar = findViewById(R.id.progress_bar); + View loadingText = findViewById(R.id.loading_text); + TextView errorText = findViewById(R.id.error_text); + View retryButton = findViewById(R.id.retry_button); + + if (progressBar != null) progressBar.setVisibility(View.GONE); + if (loadingText != null) loadingText.setVisibility(View.GONE); + + if (errorText != null) { + errorText.setText(getString(R.string.error_generic_network)); + errorText.setVisibility(View.VISIBLE); + } + + if (retryButton != null) { + retryButton.setVisibility(View.VISIBLE); + retryButton.setOnClickListener(v -> showPrologueScreen()); + } + } + + private void showFragment(@NonNull Fragment fragment, @NonNull String tag) { FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); - fragmentTransaction.replace(R.id.fragment_container, fragment, tag); + fragmentTransaction.replace(mFragmentContainerId, fragment, tag); fragmentTransaction.commit(); } - private void slideInFragment(Fragment fragment, boolean shouldAddToBackStack, String tag) { + private void slideInFragment(@NonNull Fragment fragment, boolean shouldAddToBackStack, @NonNull String tag) { FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); fragmentTransaction.setCustomAnimations(R.anim.activity_slide_in_from_right, R.anim.activity_slide_out_to_left, R.anim.activity_slide_in_from_left, R.anim.activity_slide_out_to_right); - fragmentTransaction.replace(R.id.fragment_container, fragment, tag); + fragmentTransaction.replace(mFragmentContainerId, fragment, tag); if (shouldAddToBackStack) { fragmentTransaction.addToBackStack(null); } fragmentTransaction.commitAllowingStateLoss(); } - private void addGoogleFragment(GoogleFragment googleFragment, String tag) { - FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); - googleFragment.setRetainInstance(true); - fragmentTransaction.add(googleFragment, tag); - fragmentTransaction.commit(); - } - - private LoginPrologueRevampedFragment getLoginPrologueRevampedFragment() { - Fragment fragment = getSupportFragmentManager().findFragmentByTag(LoginPrologueRevampedFragment.TAG); - return fragment == null ? null : (LoginPrologueRevampedFragment) fragment; - } - - private LoginEmailFragment getLoginEmailFragment() { - Fragment fragment = getSupportFragmentManager().findFragmentByTag(LoginEmailFragment.TAG); - return fragment == null ? null : (LoginEmailFragment) fragment; - } - - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - if (item.getItemId() == android.R.id.home) { - getOnBackPressedDispatcher().onBackPressed(); - return true; + public LoginFlow getLoginFlow() { + if (mLoginFlow != null) { + // returned the cached value + return mLoginFlow; } - return false; - } + // compute and cache the Login flow + mLoginFlow = LoginFlow.fromIntent(getIntent()); - @Override - public LoginMode getLoginMode() { - if (mLoginMode != null) { - // returned the cached value - return mLoginMode; + // If the flow is PROLOGUE (default) but we have a pending flow from an OAuth callback, + // use that instead + String pendingFlowName = AppPrefs.getPendingLoginFlow(); + if (mLoginFlow == LoginFlow.PROLOGUE + && pendingFlowName != null) { + try { + mLoginFlow = LoginFlow.valueOf(pendingFlowName); + } catch (IllegalArgumentException ignored) { + // Invalid value, stick with PROLOGUE + } + AppPrefs.setPendingLoginFlow(null); } - // compute and cache the Login mode - mLoginMode = LoginMode.fromIntent(getIntent()); - - return mLoginMode; + return mLoginFlow; } private void loggedInAndFinish(ArrayList oldSitesIds, boolean doLoginUpdate) { @@ -431,7 +401,7 @@ private void loggedInAndFinish(ArrayList oldSitesIds, boolean doLoginUp // If doLoginUpdate is true, we need to fetch account and sites before navigating. // This happens after WordPress.com OAuth login where we only have the token. - if (mLoginCompletionUseCase.shouldWaitForSitesToLoad(doLoginUpdate, mSiteStore.hasSite())) { + if (doLoginUpdate) { AppLog.i(T.MAIN, "Fetching account and sites before proceeding"); mIsWaitingForSitesToLoad = true; mOldSitesIdsForLoginUpdate = oldSitesIds; @@ -439,16 +409,14 @@ private void loggedInAndFinish(ArrayList oldSitesIds, boolean doLoginUp return; // Wait for onAccountChanged -> onSiteChanged before navigating } - LoginCompletionAction action = mLoginCompletionUseCase.getLoginCompletionAction(getLoginMode()); - switch (action) { - case FINISH_WITH_NEW_SITE: + LoginFlow.CompletionBehavior behavior = getLoginFlow().getCompletionBehavior(); + switch (behavior) { + case FINISH_WITH_SITE: // Handle self-hosted site login - find the newly added site and return its ID finishWithNewlyAddedSiteId(oldSitesIds); break; - case FINISH_ONLY: - // WooCommerce handles its own navigation - break; - case NAVIGATE_TO_MAIN: + case MAIN_ACTIVITY: + case FINISH: default: // For all other modes, use the common navigation logic navigateToMainActivityOrFinish(); @@ -479,103 +447,14 @@ private void finishWithNewlyAddedSiteId(ArrayList oldSitesIds) { finish(); } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - AppLog.d(T.MAIN, "LoginActivity: onActivity Result - requestCode" + requestCode); - super.onActivityResult(requestCode, resultCode, data); - - switch (requestCode) { - case RequestCodes.SHOW_SIGNUP_EPILOGUE_AND_RETURN: - // we showed the epilogue screen as informational and sites got loaded so, just - // return to login caller now - setResult(RESULT_OK); - finish(); - break; - case RequestCodes.SMART_LOCK_SAVE: - if (resultCode == RESULT_OK) { - mLoginAnalyticsListener.trackLoginAutofillCredentialsUpdated(); - AppLog.d(AppLog.T.NUX, "Credentials saved"); - } else { - AppLog.d(AppLog.T.NUX, "Credentials save cancelled"); - } - break; - case RequestCodes.SMART_LOCK_READ: - if (resultCode == RESULT_OK) { - AppLog.d(AppLog.T.NUX, "Credentials retrieved"); - Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY); - onCredentialRetrieved(credential); - } else { - AppLog.e(AppLog.T.NUX, "Credential read failed"); - onCredentialsUnavailable(); - } - break; - } - } - - private void jumpToUsernamePassword(String username, String password) { - LoginUsernamePasswordFragment loginUsernamePasswordFragment = - LoginUsernamePasswordFragment.newInstance("wordpress.com", "wordpress.com", username, password, true); - slideInFragment(loginUsernamePasswordFragment, true, LoginUsernamePasswordFragment.TAG); - } - - private boolean initSmartLockHelperConnection() { - mSmartLockHelper = new SmartLockHelper(this); - return mSmartLockHelper.initSmartLockForPasswords(); - } - - private void checkSmartLockPasswordAndStartLogin() { - initSmartLockIfNotFinished(true); - - if (mSmartLockHelperState == SmartLockHelperState.FINISHED) { - startLogin(); - } - } - - /** - * @param triggerFillInOnConnect set to true, if you want to show an account chooser dialog when the user has - * stored their credentials in the past. Set to false, if you just want to - * initialize SmartLock eg. when you want to use it just to save users credentials. - */ - private void initSmartLockIfNotFinished(boolean triggerFillInOnConnect) { - if (mSmartLockHelperState == SmartLockHelperState.NOT_TRIGGERED) { - if (initSmartLockHelperConnection()) { - if (triggerFillInOnConnect) { - mSmartLockHelperState = SmartLockHelperState.TRIGGER_FILL_IN_ON_CONNECT; - } else { - mSmartLockHelperState = SmartLockHelperState.FINISH_ON_CONNECT; - } - } else { - // just shortcircuit the attempt to use SmartLockHelper - mSmartLockHelperState = SmartLockHelperState.FINISHED; - } - } - } - - private void startLogin() { - if (getLoginEmailFragment() != null) { - // email screen is already shown so, login has already started. Just bail. - return; - } - - if (getLoginPrologueRevampedFragment() == null) { - // prologue fragment is not shown so, the email screen will be the initial screen on the fragment container - showFragment(LoginEmailFragment.newInstance(mIsSignupFromLoginEnabled), LoginEmailFragment.TAG); - - if (getLoginMode() == LoginMode.JETPACK_STATS) { - mIsJetpackConnect = true; - } - } else { - // prologue fragment is shown so, slide in the email screen (and add to history) - slideInFragment(LoginEmailFragment.newInstance(mIsSignupFromLoginEnabled), true, LoginEmailFragment.TAG); - } - } - - // LoginPrologueListener implementation methods - public void showWPcomLoginScreen(@NonNull Context context) { AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_WPCOM_WEBVIEW); mUnifiedLoginTracker.setFlowAndStep(Flow.WORDPRESS_COM_WEB, Step.WPCOM_WEB_START); + // Save the current login flow so it survives the OAuth + // callback (which creates a new activity instance) + AppPrefs.setPendingLoginFlow(getLoginFlow().name()); + CustomTabsIntent intent = getCustomTabsIntent(); Uri loginUri = mLoginHelper.getWpcomLoginUri(); @@ -598,302 +477,24 @@ public void showWPcomLoginScreen(@NonNull Context context) { .build(); } - @Override - public void onTermsOfServiceClicked() { - AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_TERMS_OF_SERVICE_TAPPED); - mUnifiedLoginTracker.trackClick(Click.TERMS_OF_SERVICE_CLICKED); - ActivityLauncher.openUrlExternal(this, WPUrlUtils.buildTermsOfServiceUrl(this)); - } - - // LoginListener implementation methods - - @Override - public void gotWpcomEmail(String email, boolean verifyEmail, @Nullable AuthOptions authOptions) { - initSmartLockIfNotFinished(false); - boolean isMagicLinkEnabled = - getLoginMode() != LoginMode.WPCOM_LOGIN_DEEPLINK && getLoginMode() != LoginMode.SHARE_INTENT; - - if (authOptions != null) { - if (authOptions.isPasswordless()) { - showMagicLinkRequestScreen(email, verifyEmail, false, true); - } else { - showEmailPasswordScreen(email, verifyEmail, isMagicLinkEnabled); - } - } else { - if (isMagicLinkEnabled) { - showMagicLinkRequestScreen(email, verifyEmail, true, false); - } else { - showEmailPasswordScreen(email, verifyEmail, false); - } - } - } - - private void showEmailPasswordScreen(String email, boolean verifyEmail, boolean allowMagicLink) { - LoginEmailPasswordFragment loginEmailPasswordFragment = LoginEmailPasswordFragment - .newInstance(email, null, null, null, false, allowMagicLink, verifyEmail); - slideInFragment(loginEmailPasswordFragment, true, LoginEmailPasswordFragment.TAG); - } - - private void showMagicLinkRequestScreen(String email, boolean verifyEmail, boolean allowPassword, - boolean forceRequestAtStart) { - AuthEmailPayloadScheme scheme = mViewModel.getMagicLinkScheme(); - String jetpackConnectionSource = mJetpackConnectSource != null ? mJetpackConnectSource.toString() : null; - LoginMagicLinkRequestFragment loginMagicLinkRequestFragment = LoginMagicLinkRequestFragment - .newInstance(email, scheme, mIsJetpackConnect, jetpackConnectionSource, verifyEmail, allowPassword, - forceRequestAtStart); - slideInFragment(loginMagicLinkRequestFragment, true, LoginMagicLinkRequestFragment.TAG); - } - - @Override - public void gotUnregisteredEmail(String email) { - showSignupMagicLink(email); - } - - @Override - public void gotUnregisteredSocialAccount(String email, String displayName, String idToken, String photoUrl, - String service) { - SignupConfirmationFragment signupConfirmationFragment = - SignupConfirmationFragment.newInstance(email, displayName, idToken, photoUrl, service); - slideInFragment(signupConfirmationFragment, true, SignupConfirmationFragment.TAG); - } - - @Override public void loginViaSiteAddress() { - slideInFragment(new LoginSiteApplicationPasswordFragment(), true, LoginSiteApplicationPasswordFragment.TAG); - } - - @Override - public void loginViaSocialAccount(String email, String idToken, String service, boolean isPasswordRequired) { - LoginEmailPasswordFragment loginEmailPasswordFragment = - LoginEmailPasswordFragment.newInstance(email, null, idToken, service, isPasswordRequired); - slideInFragment(loginEmailPasswordFragment, true, LoginEmailPasswordFragment.TAG); - } - - @Override - public void loggedInViaSocialAccount(ArrayList oldSitesIds, boolean doLoginUpdate) { - mLoginAnalyticsListener.trackLoginSocialSuccess(); - loggedInAndFinish(oldSitesIds, doLoginUpdate); - } - - @Override - public void loginViaWpcomUsernameInstead() { - jumpToUsernamePassword(null, null); - } - - @Override - public void showMagicLinkSentScreen(String email, boolean allowPassword) { - LoginMagicLinkSentFragment loginMagicLinkSentFragment = - LoginMagicLinkSentFragment.newInstance(email, allowPassword); - slideInFragment(loginMagicLinkSentFragment, true, LoginMagicLinkSentFragment.TAG); - } - - @Override - public void showSignupMagicLink(String email) { - boolean isEmailClientAvailable = WPActivityUtils.isEmailClientAvailable(this); - AuthEmailPayloadScheme scheme = mViewModel.getMagicLinkScheme(); - SignupMagicLinkFragment signupMagicLinkFragment = SignupMagicLinkFragment.newInstance(email, mIsJetpackConnect, - mJetpackConnectSource != null ? mJetpackConnectSource.toString() : null, isEmailClientAvailable, - scheme); - slideInFragment(signupMagicLinkFragment, true, SignupMagicLinkFragment.TAG); - } - - @Override - public void showSignupSocial(String email, String displayName, String idToken, String photoUrl, String service) { - if (GoogleFragment.SERVICE_TYPE_GOOGLE.equals(service)) { - addGoogleFragment(SignupGoogleFragment.newInstance(email, displayName, idToken, photoUrl), - SignupGoogleFragment.TAG); - } - } - - @Override - public void openEmailClient(boolean isLogin) { - mUnifiedLoginTracker.trackClick(Click.OPEN_EMAIL_CLIENT); - if (WPActivityUtils.isEmailClientAvailable(this)) { - if (isLogin) { - mLoginAnalyticsListener.trackLoginMagicLinkOpenEmailClientClicked(); - } else { - mLoginAnalyticsListener.trackSignupMagicLinkOpenEmailClientClicked(); - } - - WPActivityUtils.openEmailClientChooser(this, getString(R.string.login_select_email_client)); - } else { - ToastUtils.showToast(this, R.string.login_email_client_not_found); + // Track if we're in a share flow so ApplicationPasswordLoginActivity knows to just finish + if (getLoginFlow() == LoginFlow.SHARE_INTENT) { + AppPrefs.setShareFlowPending(true); } - } - - @Override - public void usePasswordInstead(String email) { - mLoginAnalyticsListener.trackLoginMagicLinkExited(); - LoginEmailPasswordFragment loginEmailPasswordFragment = - LoginEmailPasswordFragment.newInstance(email, null, null, null, false); - slideInFragment(loginEmailPasswordFragment, true, LoginEmailPasswordFragment.TAG); - } - - @Override - public void forgotPassword(String url) { - mLoginAnalyticsListener.trackLoginForgotPasswordClicked(); - ActivityLauncher.openUrlExternal(this, url + FORGOT_PASSWORD_URL_SUFFIX); - } - - @Override - public void useMagicLinkInstead(String email, boolean verifyEmail) { - showMagicLinkRequestScreen(email, verifyEmail, false, true); - } - - @Override - public void needs2fa(String email, String password) { - Login2FaFragment login2FaFragment = Login2FaFragment.newInstance(email, password); - slideInFragment(login2FaFragment, true, Login2FaFragment.TAG); - } - - @Override - public void needs2fa(String email, String password, String userId, String webauthnNonce, String nonceAuthenticator, - String nonceBackup, String noncePush, List supportedAuthTypes) { - mLoginAnalyticsListener.trackLogin2faNeeded(); - Login2FaFragment login2FaFragment = Login2FaFragment.newInstance(email, password, userId, webauthnNonce, - nonceAuthenticator, nonceBackup, noncePush, supportedAuthTypes); - slideInFragment(login2FaFragment, true, Login2FaFragment.TAG); - } - - @Override - public void needs2faSocial(String email, String userId, String nonceAuthenticator, String nonceBackup, - String nonceSms, String nonceWebauthn, List authTypes) { - mLoginAnalyticsListener.trackLoginSocial2faNeeded(); - Login2FaFragment login2FaFragment = Login2FaFragment.newInstanceSocial(email, userId, - nonceAuthenticator, nonceBackup, - nonceSms, nonceWebauthn, authTypes); - slideInFragment(login2FaFragment, true, Login2FaFragment.TAG); - } - - @Override - public void needs2faSocialConnect(String email, String password, String idToken, String service) { - mLoginAnalyticsListener.trackLoginSocial2faNeeded(); - Login2FaFragment login2FaFragment = - Login2FaFragment.newInstanceSocialConnect(email, password, idToken, service); - slideInFragment(login2FaFragment, true, Login2FaFragment.TAG); - } - - @Override - public void loggedInViaPassword(ArrayList oldSitesIds) { - loggedInAndFinish(oldSitesIds, false); - } - - @Override - public void alreadyLoggedInWpcom(ArrayList oldSitesIds) { - ToastUtils.showToast(this, R.string.already_logged_in_wpcom, ToastUtils.Duration.LONG); - loggedInAndFinish(oldSitesIds, false); - } - - @Override - public void gotWpcomSiteInfo(String siteAddress) { - LoginEmailFragment loginEmailFragment = LoginEmailFragment.newInstance(siteAddress); - slideInFragment(loginEmailFragment, true, LoginEmailFragment.TAG); - } - - @Override - public void gotXmlRpcEndpoint(String inputSiteAddress, String endpointAddress) { - LoginUsernamePasswordFragment loginUsernamePasswordFragment = - LoginUsernamePasswordFragment.newInstance(inputSiteAddress, endpointAddress, null, null, false); - slideInFragment(loginUsernamePasswordFragment, true, LoginUsernamePasswordFragment.TAG); - } - - @Override - public void handleSslCertificateError(MemorizingTrustManager memorizingTrustManager, - final SelfSignedSSLCallback callback) { - SelfSignedSSLUtils.showSSLWarningDialog(this, memorizingTrustManager, new SelfSignedSSLUtils.Callback() { - @Override - public void certificateTrusted() { - callback.certificateTrusted(); - } - }); + slideInFragment(new LoginSiteApplicationPasswordFragment(), true, LoginSiteApplicationPasswordFragment.TAG); } private void viewHelp(Origin origin) { - List extraSupportTags = getLoginMode() == LoginMode.JETPACK_STATS ? Collections + List extraSupportTags = getLoginFlow() == LoginFlow.JETPACK_STATS ? Collections .singletonList(ZendeskExtraTags.connectingJetpack) : null; ActivityLauncher.viewHelp(this, origin, null, extraSupportTags, mExperimentalFeatures); } - @Override public void helpSiteAddress(String url) { viewHelp(Origin.LOGIN_SITE_ADDRESS); } - @Override - public void helpFindingSiteAddress(String username, SiteStore siteStore) { - mUnifiedLoginTracker.trackClick(Click.HELP_FINDING_SITE_ADDRESS); - if (!mBuildConfigWrapper.isJetpackApp()) { - viewHelp(Origin.LOGIN_SITE_ADDRESS); - } else { - if (mContactSupportFeatureConfig.isEnabled()) { - Intent intent = SupportWebViewActivity.createIntent( - this, - Origin.LOGIN_SITE_ADDRESS, - null, - null); - startActivity(intent); - } else { - mZendeskHelper.createNewTicket(this, Origin.LOGIN_SITE_ADDRESS, null); - } - } - } - - @Override - public void loggedInViaUsernamePassword(ArrayList oldSitesIds) { - loggedInAndFinish(oldSitesIds, false); - } - - @Override - public void helpEmailScreen(String email) { - viewHelp(Origin.LOGIN_EMAIL); - } - - @Override - public void helpSignupEmailScreen(String email) { - viewHelp(Origin.SIGNUP_EMAIL); - } - - @Override - public void helpSignupMagicLinkScreen(String email) { - viewHelp(Origin.SIGNUP_MAGIC_LINK); - } - - @Override - public void helpSignupConfirmationScreen(String email) { - viewHelp(Origin.SIGNUP_CONFIRMATION); - } - - @Override - public void helpSocialEmailScreen(String email) { - viewHelp(Origin.LOGIN_SOCIAL); - } - - @Override - public void addGoogleLoginFragment(boolean isSignupFromLoginEnabled) { - addGoogleFragment(LoginGoogleFragment.newInstance(isSignupFromLoginEnabled), LoginGoogleFragment.TAG); - } - - @Override - public void helpMagicLinkRequest(String email) { - viewHelp(Origin.LOGIN_MAGIC_LINK); - } - - @Override - public void helpMagicLinkSent(String email) { - viewHelp(Origin.LOGIN_MAGIC_LINK); - } - - @Override - public void helpEmailPasswordScreen(String email) { - viewHelp(Origin.LOGIN_EMAIL_PASSWORD); - } - - @Override - public void help2FaScreen(String email) { - viewHelp(Origin.LOGIN_2FA); - } - - @Override public void startPostLoginServices() { // Get reader tags so they're available as soon as the Reader is accessed - done for // both wp.com and self-hosted (self-hosted = "logged out" reader) - note that this @@ -904,212 +505,15 @@ public void startPostLoginServices() { NotificationsUpdateServiceStarter.startService(getApplicationContext()); } - @Override - public void helpUsernamePassword(String url, String username, boolean isWpcom) { - viewHelp(Origin.LOGIN_USERNAME_PASSWORD); - } - - // SmartLock - - @Override - public void saveCredentialsInSmartLock(@Nullable final String username, @Nullable final String password, - @NonNull final String displayName, @Nullable final Uri profilePicture) { - LoginMode mode = getLoginMode(); - if (mode == LoginMode.SELFHOSTED_ONLY || mode == LoginMode.JETPACK_SELFHOSTED) { - // bail if we are on the selfhosted flow since we haven't initialized SmartLock-for-Passwords for it. - // Otherwise, logging in to WPCOM via the site-picker flow (for example) results in a crash. - // See https://github.com/wordpress-mobile/WordPress-Android/issues/7182#issuecomment-362791364 - // There might be more circumstances that lead to this crash though. Not all crash reports seem to - // originate from the site-picker. - return; - } - - if (mSmartLockHelper == null) { - // log some data to help us debug https://github.com/wordpress-mobile/WordPress-Android/issues/7182 - final String loginModeStr = "LoginMode: " + (getLoginMode() != null ? getLoginMode().name() : "null"); - AppLog.w(AppLog.T.NUX, "Internal inconsistency error! mSmartLockHelper found null!" + loginModeStr); - - // bail - return; - } - - mSmartLockHelper.saveCredentialsInSmartLock(StringUtils.notNullStr(username), StringUtils.notNullStr(password), - displayName, profilePicture); - } - - @Override - public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { - AppLog.d(AppLog.T.NUX, "Connection result: " + connectionResult); - mSmartLockHelperState = SmartLockHelperState.FINISHED; - } - - @Override - public void onConnected(Bundle bundle) { - AppLog.d(AppLog.T.NUX, "Google API client connected"); - - switch (mSmartLockHelperState) { - case NOT_TRIGGERED: - // should not reach this state here! - throw new RuntimeException("Internal inconsistency error!"); - case TRIGGER_FILL_IN_ON_CONNECT: - mSmartLockHelperState = SmartLockHelperState.FINISHED; - - // force account chooser - mSmartLockHelper.disableAutoSignIn(); - - mSmartLockHelper.smartLockAutoFill(this); - break; - case FINISH_ON_CONNECT: - mSmartLockHelperState = SmartLockHelperState.FINISHED; - break; - case FINISHED: - // don't do anything special. We're reconnecting the GoogleApiClient on rotation. - break; - } - } - - @Override - public void onCredentialRetrieved(Credential credential) { - mLoginAnalyticsListener.trackLoginAutofillCredentialsFilled(); - - mSmartLockHelperState = SmartLockHelperState.FINISHED; - - final String username = credential.getId(); - final String password = credential.getPassword(); - jumpToUsernamePassword(username, password); - } - - @Override - public void onCredentialsUnavailable() { - mSmartLockHelperState = SmartLockHelperState.FINISHED; - if (mIsSmartLockTriggeredFromPrologue) { - return; - } - startLogin(); - } - - @Override - public void onConnectionSuspended(int i) { - AppLog.d(AppLog.T.NUX, "Google API client connection suspended"); - } - - @Override - public void showSignupToLoginMessage() { - WPSnackbar.make( - findViewById(R.id.main_view), - R.string.signup_user_exists, - Snackbar.LENGTH_LONG - ).show(); - } - - // GoogleListener - - @Override - public void onGoogleEmailSelected(String email) { - LoginEmailFragment loginEmailFragment = - (LoginEmailFragment) getSupportFragmentManager().findFragmentByTag(LoginEmailFragment.TAG); - if (loginEmailFragment != null) { - loginEmailFragment.setGoogleEmail(email); - } - } - - @Override - public void onGoogleLoginFinished() { - LoginEmailFragment loginEmailFragment = - (LoginEmailFragment) getSupportFragmentManager().findFragmentByTag(LoginEmailFragment.TAG); - if (loginEmailFragment != null) { - loginEmailFragment.finishLogin(); - } - } - - @Override - public void onGoogleSignupFinished(String name, String email, String photoUrl, String username) { - AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_SOCIAL_SUCCESS); - if (mIsJetpackConnect) { - ActivityLauncher.showSignupEpilogueForResult(this, name, email, photoUrl, username, false); - } else { - ActivityLauncher.showMainActivityAndSignupEpilogue(this, name, email, photoUrl, username); - } - - setResult(Activity.RESULT_OK); - finish(); - } - - @Override - public void onGoogleSignupError(String msg) { - mUnifiedLoginTracker.trackFailure(msg); - // Only show the error dialog if the activity is still active - if (!getSupportFragmentManager().isStateSaved()) { - BasicFragmentDialog dialog = new BasicFragmentDialog(); - dialog.initialize(GOOGLE_ERROR_DIALOG_TAG, getString(R.string.error), - msg, - getString(org.wordpress.android.login.R.string.login_error_button), - null, - null); - dialog.show(getSupportFragmentManager(), GOOGLE_ERROR_DIALOG_TAG); - } else { - AppLog.d(T.MAIN, "'Google sign up failed' dialog not shown, because the activity wasn't visible."); - } - } - @Override public void onPositiveClicked(@NonNull String instanceTag) { - switch (instanceTag) { - case GOOGLE_ERROR_DIALOG_TAG: - // just dismiss the dialog - break; - } + // No dialog tags currently handled } @Override public AndroidInjector androidInjector() { return mDispatchingAndroidInjector; } - @Override public void startOver() { - // Not used in WordPress app - } - - @Override - public void showHelpFindingConnectedEmail() { - // Not used in WordPress app - } - - @Override - public void gotConnectedSiteInfo( - @NonNull String siteAddress, - @Nullable String redirectUrl, - boolean hasJetpack) { - // Not used in WordPress app - } - - @Override - public void helpHandleDiscoveryError( - String siteAddress, - String endpointAddress, - String username, - String password, - String userAvatarUrl, - int errorMessage) { - // Not used in WordPress app - } - - @Override - public void helpNoJetpackScreen( - String siteAddress, - String endpointAddress, - String username, - String password, - String userAvatarUrl, - Boolean checkJetpackAvailability) { - // Not used in WordPress app - } - - @Override - public void loginViaSiteCredentials(String inputSiteAddress) { - // Not used in WordPress app - } - - @Override public void handleSiteAddressError(ConnectSiteInfoPayload siteInfo) { mViewModel.onHandleSiteAddressError(siteInfo); } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginFlow.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginFlow.kt new file mode 100644 index 000000000000..b71d435f24da --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginFlow.kt @@ -0,0 +1,107 @@ +package org.wordpress.android.ui.accounts + +import android.content.Intent + +/** + * Represents the different login flows in the app. + * + * @property analyticsSource The analytics source value for tracking + * @property initialScreen The initial screen to show when starting this flow + * @property completionBehavior How the login activity should behave after successful login + */ +enum class LoginFlow( + val analyticsSource: String, + val initialScreen: InitialScreen, + val completionBehavior: CompletionBehavior +) { + /** Default login flow showing the prologue with all login options */ + PROLOGUE( + analyticsSource = "default", + initialScreen = InitialScreen.PROLOGUE, + completionBehavior = CompletionBehavior.MAIN_ACTIVITY + ), + + /** Direct WP.com OAuth login, skipping the prologue */ + WPCOM_LOGIN( + analyticsSource = "add_wordpress_com_account", + initialScreen = InitialScreen.WPCOM_OAUTH, + completionBehavior = CompletionBehavior.MAIN_ACTIVITY + ), + + /** Self-hosted site login only */ + SELFHOSTED_ONLY( + analyticsSource = "self_hosted", + initialScreen = InitialScreen.SELF_HOSTED, + completionBehavior = CompletionBehavior.FINISH_WITH_SITE + ), + + /** Login for viewing Jetpack stats */ + JETPACK_STATS( + analyticsSource = "jetpack", + initialScreen = InitialScreen.WPCOM_OAUTH, + completionBehavior = CompletionBehavior.FINISH + ), + + /** Login for Jetpack REST API connection */ + JETPACK_REST_CONNECT( + analyticsSource = "add_wordpress_com_account", + initialScreen = InitialScreen.WPCOM_OAUTH, + completionBehavior = CompletionBehavior.FINISH + ), + + /** Login triggered by a deep link */ + WPCOM_LOGIN_DEEPLINK( + analyticsSource = "deeplink", + initialScreen = InitialScreen.WPCOM_OAUTH, + completionBehavior = CompletionBehavior.FINISH + ), + + /** Re-authentication after token expiry */ + WPCOM_REAUTHENTICATE( + analyticsSource = "reauthentication", + initialScreen = InitialScreen.WPCOM_OAUTH, + completionBehavior = CompletionBehavior.FINISH + ), + + /** Login triggered by share intent from another app */ + SHARE_INTENT( + analyticsSource = "share", + initialScreen = InitialScreen.PROLOGUE, + completionBehavior = CompletionBehavior.FINISH_WITH_SITE + ); + + /** The initial screen to display when starting a login flow */ + enum class InitialScreen { + /** Show the login prologue with all login options */ + PROLOGUE, + /** Go directly to WP.com OAuth */ + WPCOM_OAUTH, + /** Show the self-hosted site login form */ + SELF_HOSTED + } + + /** How the login activity should behave after successful login */ + enum class CompletionBehavior { + /** Navigate to main activity */ + MAIN_ACTIVITY, + /** Just finish the activity, letting the caller handle navigation */ + FINISH, + /** Finish with the newly added site ID */ + FINISH_WITH_SITE + } + + fun putInto(intent: Intent) { + intent.putExtra(ARG_LOGIN_FLOW, this.name) + } + + companion object { + private const val ARG_LOGIN_FLOW = "ARG_LOGIN_FLOW" + + @JvmStatic + fun fromIntent(intent: Intent): LoginFlow { + return intent.getStringExtra(ARG_LOGIN_FLOW) + ?.let { runCatching { valueOf(it) }.getOrNull() } + ?: PROLOGUE + } + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginMagicLinkInterceptActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginMagicLinkInterceptActivity.java index 2f1c123726a6..a4ed23fb310e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginMagicLinkInterceptActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginMagicLinkInterceptActivity.java @@ -6,7 +6,7 @@ import androidx.annotation.Nullable; -import org.wordpress.android.login.LoginAnalyticsListener; +import org.wordpress.android.ui.accounts.login.LoginAnalyticsListener; import org.wordpress.android.ui.JetpackConnectionSource; import org.wordpress.android.ui.main.BaseAppCompatActivity; import org.wordpress.android.ui.main.WPMainActivity; @@ -17,7 +17,7 @@ /** * Deep link receiver for magic links. Starts {@link WPMainActivity} where flow is routed to login - * or signup based on deep link scheme, host, and parameters. + * based on deep link scheme, host, and parameters. */ @AndroidEntryPoint public class LoginMagicLinkInterceptActivity extends BaseAppCompatActivity { @@ -43,14 +43,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { if (hasMagicLinkLoginIntent()) { intent.putExtra(WPMainActivity.ARG_IS_MAGIC_LINK_LOGIN, true); - - if (hasMagicLinkSignupIntent()) { - mLoginAnalyticsListener.trackSignupMagicLinkOpened(); - intent.putExtra(WPMainActivity.ARG_IS_MAGIC_LINK_SIGNUP, true); - } else { - mLoginAnalyticsListener.trackLoginMagicLinkOpened(); - intent.putExtra(WPMainActivity.ARG_IS_MAGIC_LINK_SIGNUP, false); - } + mLoginAnalyticsListener.trackLoginMagicLinkOpened(); } if (isJetpackConnectFlow()) { @@ -68,18 +61,6 @@ private boolean hasMagicLinkLoginIntent() { return Intent.ACTION_VIEW.equals(mAction) && host.contains(LoginActivity.MAGIC_LOGIN); } - private boolean hasMagicLinkSignupIntent() { - if (mUri != null) { - String parameter = SignupEpilogueActivity.MAGIC_SIGNUP_PARAMETER; - String value = (mUri.getQueryParameterNames() != null && mUri.getQueryParameter(parameter) != null) - ? mUri.getQueryParameter(parameter) : ""; - return Intent.ACTION_VIEW.equals(mAction) - && value.equalsIgnoreCase(SignupEpilogueActivity.MAGIC_SIGNUP_VALUE); - } else { - return false; - } - } - private boolean isJetpackConnectFlow() { if (mUri != null) { String value = (mUri.getQueryParameterNames() != null && mUri.getQueryParameter(PARAMETER_FLOW) != null) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/PostSignupInterstitialActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/PostSignupInterstitialActivity.kt deleted file mode 100644 index 956498c8f0e6..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/PostSignupInterstitialActivity.kt +++ /dev/null @@ -1,91 +0,0 @@ -package org.wordpress.android.ui.accounts - -import android.os.Bundle -import androidx.activity.addCallback -import androidx.lifecycle.ViewModelProvider -import com.google.android.material.button.MaterialButton -import dagger.hilt.android.AndroidEntryPoint -import org.wordpress.android.R -import org.wordpress.android.databinding.PostSignupInterstitialActivityBinding -import org.wordpress.android.ui.ActivityLauncher -import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginFragment -import org.wordpress.android.ui.main.BaseAppCompatActivity -import org.wordpress.android.ui.sitecreation.misc.SiteCreationSource -import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel -import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction -import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.DISMISS -import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.DISMISS_FOR_JETPACK_REMOVAL -import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY -import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.START_SITE_CONNECTION_FLOW -import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.START_SITE_CREATION_FLOW -import javax.inject.Inject - -@AndroidEntryPoint -class PostSignupInterstitialActivity : BaseAppCompatActivity() { - @Inject - lateinit var viewModelFactory: ViewModelProvider.Factory - private lateinit var viewModel: PostSignupInterstitialViewModel - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - LoginFlowThemeHelper.injectMissingCustomAttributes(theme) - - viewModel = ViewModelProvider(this, viewModelFactory) - .get(PostSignupInterstitialViewModel::class.java) - val binding = PostSignupInterstitialActivityBinding.inflate(layoutInflater) - setContentView(binding.root) - - onBackPressedDispatcher.addCallback(this) { viewModel.onBackButtonPressed() } - - with(binding) { - viewModel.onInterstitialShown() - createNewSiteButton().setOnClickListener { viewModel.onCreateNewSiteButtonPressed() } - addSelfHostedSiteButton().setOnClickListener { viewModel.onAddSelfHostedSiteButtonPressed() } - dismissButton().setOnClickListener { viewModel.onDismissButtonPressed() } - } - - viewModel.navigationAction.observe(this) { executeAction(it) } - } - - private fun PostSignupInterstitialActivityBinding.createNewSiteButton() = - root.findViewById(R.id.create_new_site_button) - - private fun PostSignupInterstitialActivityBinding.addSelfHostedSiteButton() = - root.findViewById(R.id.add_self_hosted_site_button) - - private fun PostSignupInterstitialActivityBinding.dismissButton() = - root.findViewById(R.id.dismiss_button) - - private fun executeAction(navigationAction: NavigationAction) = when (navigationAction) { - START_SITE_CREATION_FLOW -> startSiteCreationFlow() - START_SITE_CONNECTION_FLOW -> startSiteConnectionFlow() - SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY -> showJetpackIndividualPluginOverlay() - DISMISS -> dismiss() - DISMISS_FOR_JETPACK_REMOVAL -> dismissForJetpackRemoval() - } - - private fun startSiteCreationFlow() { - ActivityLauncher.showMainActivityAndSiteCreationActivity(this, SiteCreationSource.SIGNUP_EPILOGUE) - finish() - } - - private fun startSiteConnectionFlow() { - ActivityLauncher.addSelfHostedSiteForResult(this) - finish() - } - - private fun dismiss() { - ActivityLauncher.viewReader(this) - finish() - } - - private fun dismissForJetpackRemoval() { - ActivityLauncher.viewMySite(this) - finish() - } - - private fun showJetpackIndividualPluginOverlay() { - WPJetpackIndividualPluginFragment.show(supportFragmentManager) - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/SignupEpilogueActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/accounts/SignupEpilogueActivity.java deleted file mode 100644 index 57d28f0d0f0a..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/SignupEpilogueActivity.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.wordpress.android.ui.accounts; - -import android.os.Bundle; - -import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentTransaction; - -import org.wordpress.android.R; -import org.wordpress.android.fluxc.store.SiteStore; -import org.wordpress.android.ui.ActivityLauncher; -import org.wordpress.android.ui.accounts.signup.SignupEpilogueFragment; -import org.wordpress.android.ui.accounts.signup.SignupEpilogueListener; -import org.wordpress.android.ui.main.BaseAppCompatActivity; - -import javax.inject.Inject; - -import dagger.hilt.android.AndroidEntryPoint; - -@AndroidEntryPoint -public class SignupEpilogueActivity extends BaseAppCompatActivity implements SignupEpilogueListener { - public static final String EXTRA_SIGNUP_DISPLAY_NAME = "EXTRA_SIGNUP_DISPLAY_NAME"; - public static final String EXTRA_SIGNUP_EMAIL_ADDRESS = "EXTRA_SIGNUP_EMAIL_ADDRESS"; - public static final String EXTRA_SIGNUP_IS_EMAIL = "EXTRA_SIGNUP_IS_EMAIL"; - public static final String EXTRA_SIGNUP_PHOTO_URL = "EXTRA_SIGNUP_PHOTO_URL"; - public static final String EXTRA_SIGNUP_USERNAME = "EXTRA_SIGNUP_USERNAME"; - public static final String MAGIC_SIGNUP_PARAMETER = "new_user"; - public static final String MAGIC_SIGNUP_VALUE = "1"; - - @Inject SiteStore mSiteStore; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - LoginFlowThemeHelper.injectMissingCustomAttributes(getTheme()); - - setContentView(R.layout.signup_epilogue_activity); - - if (savedInstanceState == null) { - String name = getIntent().getStringExtra(EXTRA_SIGNUP_DISPLAY_NAME); - String email = getIntent().getStringExtra(EXTRA_SIGNUP_EMAIL_ADDRESS); - String photoUrl = getIntent().getStringExtra(EXTRA_SIGNUP_PHOTO_URL); - String username = getIntent().getStringExtra(EXTRA_SIGNUP_USERNAME); - boolean isEmail = getIntent().getBooleanExtra(EXTRA_SIGNUP_IS_EMAIL, false); - addSignupEpilogueFragment(name, email, photoUrl, username, isEmail); - } - } - - protected void addSignupEpilogueFragment(String name, String email, String photoUrl, String username, - boolean isEmail) { - SignupEpilogueFragment signupEpilogueSocialFragment = SignupEpilogueFragment.Companion.newInstance( - name, email, photoUrl, username, isEmail); - FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); - fragmentTransaction.replace(R.id.fragment_container, signupEpilogueSocialFragment, SignupEpilogueFragment.TAG); - fragmentTransaction.commit(); - } - - @Override - public void onContinue() { - if (!mSiteStore.hasSite()) { - ActivityLauncher.showPostSignupInterstitial(this); - } - - setResult(RESULT_OK); - finish(); - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/SmartLockHelper.java b/WordPress/src/main/java/org/wordpress/android/ui/accounts/SmartLockHelper.java deleted file mode 100644 index e0d0b0fe1278..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/SmartLockHelper.java +++ /dev/null @@ -1,155 +0,0 @@ -package org.wordpress.android.ui.accounts; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.IntentSender; -import android.net.Uri; -import android.text.TextUtils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentActivity; - -import com.google.android.gms.auth.api.Auth; -import com.google.android.gms.auth.api.credentials.Credential; -import com.google.android.gms.auth.api.credentials.CredentialRequest; -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.GoogleApiAvailability; -import com.google.android.gms.common.api.CommonStatusCodes; -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks; -import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.api.Status; - -import org.wordpress.android.ui.RequestCodes; -import org.wordpress.android.util.AppLog; -import org.wordpress.android.util.AppLog.T; - -import java.lang.ref.WeakReference; - -public class SmartLockHelper { - private GoogleApiClient mCredentialsClient; - private WeakReference mActivity; - - public interface Callback { - void onCredentialRetrieved(Credential credential); - - void onCredentialsUnavailable(); - } - - public SmartLockHelper(@NonNull FragmentActivity activity) { - if (activity instanceof OnConnectionFailedListener && activity instanceof ConnectionCallbacks) { - mActivity = new WeakReference<>(activity); - } else { - throw new RuntimeException("SmartLockHelper constructor needs an activity that " - + "implements OnConnectionFailedListener and ConnectionCallbacks"); - } - } - - private FragmentActivity getActivityAndCheckAvailability() { - FragmentActivity activity = mActivity.get(); - if (activity == null) { - return null; - } - int status = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(activity); - if (status == ConnectionResult.SUCCESS) { - return activity; - } - return null; - } - - public boolean initSmartLockForPasswords() { - FragmentActivity activity = getActivityAndCheckAvailability(); - if (activity == null) { - return false; - } - mCredentialsClient = new GoogleApiClient.Builder(activity) - .addConnectionCallbacks((ConnectionCallbacks) activity) - .enableAutoManage(activity, (OnConnectionFailedListener) activity) - .addApi(Auth.CREDENTIALS_API) - .build(); - return true; - } - - public void smartLockAutoFill(@NonNull final Callback callback) { - Activity activity = getActivityAndCheckAvailability(); - if (activity == null || mCredentialsClient == null || !mCredentialsClient.isConnected()) { - return; - } - CredentialRequest credentialRequest = new CredentialRequest.Builder() - .setPasswordLoginSupported(true) - .build(); - Auth.CredentialsApi.request(mCredentialsClient, credentialRequest).setResultCallback( - result -> { - Activity currentActivity = getActivityAndCheckAvailability(); - if (currentActivity == null) { - return; - } - Status status = result.getStatus(); - if (status.isSuccess()) { - Credential credential = result.getCredential(); - callback.onCredentialRetrieved(credential); - } else { - if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) { - try { - // Prompt the user to choose a saved credential - status.startResolutionForResult(currentActivity, RequestCodes.SMART_LOCK_READ); - } catch (IntentSender.SendIntentException e) { - AppLog.d(T.NUX, "SmartLock: Failed to send resolution for credential request"); - - callback.onCredentialsUnavailable(); - } - } else { - // The user must create an account or log in manually. - AppLog.d(T.NUX, "SmartLock: Unsuccessful credential request."); - - callback.onCredentialsUnavailable(); - } - } - }); - } - - - public void saveCredentialsInSmartLock(@NonNull final String username, @NonNull final String password, - @NonNull final String displayName, @Nullable final Uri profilePicture) { - // need username and password fields for Smart Lock - // https://github.com/wordpress-mobile/WordPress-Android/issues/5850 - if (TextUtils.isEmpty(password) || TextUtils.isEmpty(username)) { - AppLog.i(T.MAIN, String.format( - "Cannot save Smart Lock credentials, username (%s) or password (%s) is empty", username, password)); - return; - } - - Activity activity = getActivityAndCheckAvailability(); - if (activity == null || mCredentialsClient == null || !mCredentialsClient.isConnected()) { - return; - } - Credential credential = new Credential.Builder(username).setPassword(password) - .setName(displayName) - .setProfilePictureUri(profilePicture).build(); - Auth.CredentialsApi.save(mCredentialsClient, credential).setResultCallback( - new ResultCallback() { - @Override - @SuppressLint("VisibleForTests") - public void onResult(@NonNull Status status) { - if (!status.isSuccess() && status.hasResolution()) { - try { - Activity activity = getActivityAndCheckAvailability(); - if (activity == null) { - return; - } - // This prompt the user to resolve the save request - status.startResolutionForResult(activity, RequestCodes.SMART_LOCK_SAVE); - } catch (IntentSender.SendIntentException e) { - // Could not resolve the request - } - } - } - }); - } - - public void disableAutoSignIn() { - Auth.CredentialsApi.disableAutoSignIn(mCredentialsClient); - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginActivity.kt index 63bd1383f272..061f1eafc195 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginActivity.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.CircularProgressIndicator import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.core.view.WindowCompat import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint @@ -14,6 +15,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.wordpress.android.R import org.wordpress.android.ui.ActivityLauncher +import org.wordpress.android.ui.accounts.LoginActivity import org.wordpress.android.ui.accounts.UnifiedLoginTracker import org.wordpress.android.ui.compose.theme.AppThemeM3 import org.wordpress.android.ui.main.BaseAppCompatActivity @@ -34,6 +36,7 @@ class ApplicationPasswordLoginActivity: BaseAppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + WindowCompat.setDecorFitsSystemWindows(window, false) initViewModel() setContent { AppThemeM3 { @@ -73,11 +76,16 @@ class ApplicationPasswordLoginActivity: BaseAppCompatActivity() { ) } + // Check if we're in a share flow - if so, just finish and return to LoginActivity + val isShareFlow = LoginActivity.consumeShareFlowPending() + if (isShareFlow && !navigationActionData.isError) { + setResult(RESULT_OK) + finish() + return + } + if (navigationActionData.isError) { ActivityLauncher.showMainActivity(this) - } else if (navigationActionData.showPostSignupInterstitial) { - unifiedLoginTracker.setFlow(UnifiedLoginTracker.Flow.APPLICATION_PASSWORD.value) - ActivityLauncher.showPostSignupInterstitial(this) } else { unifiedLoginTracker.setFlow(UnifiedLoginTracker.Flow.APPLICATION_PASSWORD.value) val mainActivityIntent = Intent(this, WPMainActivity::class.java) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginViewModel.kt index b529272367c4..0686361e1b0f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginViewModel.kt @@ -15,11 +15,10 @@ import org.wordpress.android.fluxc.network.discovery.SelfHostedEndpointFinder import org.wordpress.android.fluxc.store.SiteStore import org.wordpress.android.fluxc.store.SiteStore.OnSiteChanged import org.wordpress.android.fluxc.utils.AppLogWrapper -import org.wordpress.android.login.util.SiteUtils +import org.wordpress.android.util.SiteUtils import org.wordpress.android.modules.IO_THREAD import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.UriLogin -import org.wordpress.android.ui.prefs.AppPrefsWrapper import org.wordpress.android.util.AppLog import org.wordpress.android.util.UrlUtils import javax.inject.Inject @@ -32,7 +31,6 @@ class ApplicationPasswordLoginViewModel @Inject constructor( private val applicationPasswordLoginHelper: ApplicationPasswordLoginHelper, private val selfHostedEndpointFinder: SelfHostedEndpointFinder, private val siteStore: SiteStore, - private val appPrefsWrapper: AppPrefsWrapper, private val appLogWrapper: AppLogWrapper, ) : ViewModel() { private val _onFinishedEvent = MutableSharedFlow() @@ -66,7 +64,6 @@ class ApplicationPasswordLoginViewModel @Inject constructor( _onFinishedEvent.emit( NavigationActionData( showSiteSelector = false, - showPostSignupInterstitial = false, siteUrl = "", oldSitesIDs = oldSitesIDs, isError = true @@ -136,7 +133,6 @@ class ApplicationPasswordLoginViewModel @Inject constructor( private suspend fun emitErrorFetching(siteUrl: String) = _onFinishedEvent.emit( NavigationActionData( showSiteSelector = false, - showPostSignupInterstitial = false, siteUrl = siteUrl, oldSitesIDs = oldSitesIDs, isError = true @@ -154,7 +150,6 @@ class ApplicationPasswordLoginViewModel @Inject constructor( _onFinishedEvent.emit( NavigationActionData( showSiteSelector = false, - showPostSignupInterstitial = false, siteUrl = currentUrlLogin?.siteUrl, oldSitesIDs = oldSitesIDs, isError = true @@ -165,8 +160,6 @@ class ApplicationPasswordLoginViewModel @Inject constructor( NavigationActionData( showSiteSelector = siteStore.hasSite() && oldSitesIDs?.contains(site.id) != true, // null or false - showPostSignupInterstitial = !siteStore.hasSite() - && appPrefsWrapper.shouldShowPostSignupInterstitial, siteUrl = currentUrlLogin?.siteUrl, oldSitesIDs = oldSitesIDs, isError = false, @@ -179,7 +172,6 @@ class ApplicationPasswordLoginViewModel @Inject constructor( data class NavigationActionData( val showSiteSelector: Boolean, - val showPostSignupInterstitial: Boolean, val siteUrl: String?, val oldSitesIDs: ArrayList?, val isError: Boolean, diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordsViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordsViewModel.kt index d93285f31f45..0e1cd0d233f1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordsViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordsViewModel.kt @@ -27,6 +27,7 @@ import java.util.Locale import rs.wordpress.api.kotlin.WpApiClient import rs.wordpress.api.kotlin.WpRequestResult import uniffi.wp_api.ApplicationPasswordWithViewContext +import uniffi.wp_api.RequestExecutionErrorReason import uniffi.wp_api.WpApiParamOrder import javax.inject.Inject import javax.inject.Named @@ -201,8 +202,43 @@ class ApplicationPasswordsViewModel @Inject constructor( userIdResponse.response.data.id } + is WpRequestResult.WpError -> { + val error = "Error getting current user Id: WpError - ${userIdResponse.errorMessage}" + appLogWrapper.e(AppLog.T.API, "$error (response: ${userIdResponse.response})") + onError(error) + null + } + + is WpRequestResult.UnknownError -> { + val error = "Error getting current user Id: UnknownError - " + + "statusCode=${userIdResponse.statusCode}, response=${userIdResponse.response}" + appLogWrapper.e(AppLog.T.API, error) + onError(error) + null + } + + is WpRequestResult.ResponseParsingError<*> -> { + val error = "Error getting current user Id: ResponseParsingError - $userIdResponse" + appLogWrapper.e(AppLog.T.API, error) + onError(error) + null + } + + is WpRequestResult.RequestExecutionFailed -> { + val isTimeout = userIdResponse.reason is RequestExecutionErrorReason.HttpTimeoutError + val error = if (isTimeout) { + "Error getting current user Id: Request timed out" + } else { + "Error getting current user Id: RequestExecutionFailed - " + + "reason=${userIdResponse.reason}, statusCode=${userIdResponse.statusCode}" + } + appLogWrapper.e(AppLog.T.API, error) + onError(error) + null + } + else -> { - val error = "Error getting current user Id" + val error = "Error getting current user Id: ${userIdResponse::class.simpleName} - $userIdResponse" appLogWrapper.e(AppLog.T.API, error) onError(error) null @@ -223,8 +259,53 @@ class ApplicationPasswordsViewModel @Inject constructor( currentApplicationPasswordResponse.response.data } + is WpRequestResult.WpError -> { + val error = "Error getting Application Password list: WpError - " + + currentApplicationPasswordResponse.errorMessage + appLogWrapper.e( + AppLog.T.API, + "$error (response: ${currentApplicationPasswordResponse.response})" + ) + onError(error) + emptyList() + } + + is WpRequestResult.UnknownError -> { + val error = "Error getting Application Password list: UnknownError - " + + "statusCode=${currentApplicationPasswordResponse.statusCode}, " + + "response=${currentApplicationPasswordResponse.response}" + appLogWrapper.e(AppLog.T.API, error) + onError(error) + emptyList() + } + + is WpRequestResult.ResponseParsingError<*> -> { + val error = "Error getting Application Password list: ResponseParsingError - " + + "$currentApplicationPasswordResponse" + appLogWrapper.e(AppLog.T.API, error) + onError(error) + emptyList() + } + + is WpRequestResult.RequestExecutionFailed -> { + val isTimeout = + currentApplicationPasswordResponse.reason is RequestExecutionErrorReason.HttpTimeoutError + val error = if (isTimeout) { + "Error getting Application Password list: Request timed out" + } else { + "Error getting Application Password list: RequestExecutionFailed - " + + "reason=${currentApplicationPasswordResponse.reason}, " + + "statusCode=${currentApplicationPasswordResponse.statusCode}" + } + appLogWrapper.e(AppLog.T.API, error) + onError(error) + emptyList() + } + else -> { - val error = "Error getting Application Password list" + val error = "Error getting Application Password list: " + + "${currentApplicationPasswordResponse::class.simpleName} - " + + "$currentApplicationPasswordResponse" appLogWrapper.e(AppLog.T.API, error) onError(error) emptyList() diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt index 9fdbae901799..bbd4fda8e98c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt @@ -1,6 +1,9 @@ package org.wordpress.android.ui.accounts.login +import android.content.Context import androidx.core.net.toUri +import org.wordpress.android.R +import org.wordpress.android.util.DeviceUtils import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext import org.wordpress.android.analytics.AnalyticsTracker @@ -194,9 +197,10 @@ class ApplicationPasswordLoginHelper @Inject constructor( * This class is created to wrap the Uri calls and let us unit test the login helper */ class UriLoginWrapper @Inject constructor( + private val context: Context, private val apiRootUrlCache: ApiRootUrlCache, private val buildConfigWrapper: BuildConfigWrapper, - ) { + ) { fun parseUriLogin(url: String): UriLogin { val uri = url.toUri() val siteUrl = UrlUtils.normalizeUrl(uri.getQueryParameter("site_url")) @@ -210,15 +214,15 @@ class ApplicationPasswordLoginHelper @Inject constructor( return if (authorizationUrl.isNullOrEmpty()) { authorizationUrl.orEmpty() } else { - val appName: String - val successUrl: String - if (buildConfigWrapper.isJetpackApp) { - appName = ANDROID_JETPACK_CLIENT - successUrl = JETPACK_SUCCESS_URL + val userDeviceName = DeviceUtils.getInstance().getDeviceName(context) + val (appName, successUrl) = if (buildConfigWrapper.isJetpackApp) { + context.getString(R.string.application_password_app_name_jetpack, userDeviceName) to + JETPACK_SUCCESS_URL } else { - appName = ANDROID_WORDPRESS_CLIENT - successUrl = WORDPRESS_SUCCESS_URL + context.getString(R.string.application_password_app_name_wordpress, userDeviceName) to + WORDPRESS_SUCCESS_URL } + authorizationUrl.toUri().buildUpon().apply { appendQueryParameter("app_name", appName) appendQueryParameter("success_url", successUrl) @@ -228,8 +232,6 @@ class ApplicationPasswordLoginHelper @Inject constructor( } companion object { - const val ANDROID_JETPACK_CLIENT = "android-jetpack-client" - const val ANDROID_WORDPRESS_CLIENT = "android-wordpress-client" private const val JETPACK_SUCCESS_URL = "jetpack://app-pass-authorize" private const val WORDPRESS_SUCCESS_URL = "wordpress://app-pass-authorize" } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginAnalyticsListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginAnalyticsListener.kt new file mode 100644 index 000000000000..cc3ab83cc8a4 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginAnalyticsListener.kt @@ -0,0 +1,22 @@ +package org.wordpress.android.ui.accounts.login + +interface LoginAnalyticsListener { + fun trackAnalyticsSignIn(isWpcomLogin: Boolean) + fun trackLoginAccessed() + fun trackLoginAutofillCredentialsUpdated() + fun trackLoginMagicLinkOpened() + fun trackLoginMagicLinkSucceeded() + fun trackUrlFormViewed() + fun trackConnectedSiteInfoRequested(url: String?) + fun trackConnectedSiteInfoFailed( + url: String?, + errorContext: String?, + errorType: String?, + errorDescription: String? + ) + fun trackConnectedSiteInfoSucceeded(properties: Map) + fun trackFailure(message: String?) + fun trackSubmitClicked() + fun trackShowHelpClick() + fun siteAddressFormScreenResumed() +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginAnalyticsTracker.java b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginAnalyticsTracker.java index d85022a7a85a..c95ab8161ab4 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginAnalyticsTracker.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginAnalyticsTracker.java @@ -5,7 +5,6 @@ import org.wordpress.android.analytics.AnalyticsTracker; import org.wordpress.android.fluxc.store.AccountStore; import org.wordpress.android.fluxc.store.SiteStore; -import org.wordpress.android.login.LoginAnalyticsListener; import org.wordpress.android.ui.accounts.UnifiedLoginTracker; import org.wordpress.android.ui.accounts.UnifiedLoginTracker.Click; import org.wordpress.android.ui.accounts.UnifiedLoginTracker.Flow; @@ -35,253 +34,32 @@ public void trackAnalyticsSignIn(boolean isWpcom) { AnalyticsUtils.trackAnalyticsSignIn(mAccountStore, mSiteStore, isWpcom); } - @Override - public void trackCreatedAccount(String username, String email, CreatedAccountSource source) { - AnalyticsUtils.trackAnalyticsAccountCreated(username, email, source.asPropertyMap()); - } - - @Override - public void trackEmailFormViewed() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_EMAIL_FORM_VIEWED); - mUnifiedLoginTracker.track(Flow.WORDPRESS_COM, Step.START); - } - - @Override - public void trackInsertedInvalidUrl() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_INSERTED_INVALID_URL); - } - @Override public void trackLoginAccessed() { AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_ACCESSED); } - @Override - public void trackLoginAutofillCredentialsFilled() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_AUTOFILL_CREDENTIALS_FILLED); - mUnifiedLoginTracker.track(Flow.SMART_LOCK_LOGIN, Step.START); - } - @Override public void trackLoginAutofillCredentialsUpdated() { AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_AUTOFILL_CREDENTIALS_UPDATED); } - @Override - public void trackLoginFailed(String errorContext, String errorType, String errorDescription) { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_FAILED, errorContext, errorType, errorDescription); - } - - @Override - public void trackLoginForgotPasswordClicked() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_FORGOT_PASSWORD_CLICKED); - mUnifiedLoginTracker.trackClick(Click.FORGOTTEN_PASSWORD); - } - - @Override - public void trackLoginMagicLinkExited() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_MAGIC_LINK_EXITED); - } - @Override public void trackLoginMagicLinkOpened() { AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_MAGIC_LINK_OPENED); } - @Override - public void trackLoginMagicLinkOpenEmailClientClicked() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_MAGIC_LINK_OPEN_EMAIL_CLIENT_CLICKED); - mUnifiedLoginTracker.track(Flow.LOGIN_MAGIC_LINK, Step.EMAIL_OPENED); - } - @Override public void trackLoginMagicLinkSucceeded() { AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_MAGIC_LINK_SUCCEEDED); } - @Override - public void trackLoginSocial2faNeeded() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_SOCIAL_2FA_NEEDED); - } - - @Override - public void trackLoginSocialSuccess() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_SOCIAL_SUCCESS); - } - - @Override - public void trackMagicLinkFailed(Map properties) { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_MAGIC_LINK_FAILED, properties); - } - - @Override - public void trackSignupMagicLinkOpenEmailClientViewed() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_MAGIC_LINK_OPEN_EMAIL_CLIENT_VIEWED); - mUnifiedLoginTracker.track(Flow.SIGNUP, Step.MAGIC_LINK_REQUESTED); - } - - @Override - public void trackLoginMagicLinkOpenEmailClientViewed() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_MAGIC_LINK_OPEN_EMAIL_CLIENT_VIEWED); - mUnifiedLoginTracker.track(Flow.LOGIN_MAGIC_LINK, Step.MAGIC_LINK_REQUESTED); - } - - @Override - public void trackMagicLinkRequested() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_MAGIC_LINK_REQUESTED); - } - - @Override - public void trackMagicLinkRequestFormViewed() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_MAGIC_LINK_REQUEST_FORM_VIEWED); - mUnifiedLoginTracker.track(Flow.LOGIN_MAGIC_LINK, Step.START); - } - - @Override - public void trackPasswordFormViewed(boolean isSocialChallenge) { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_PASSWORD_FORM_VIEWED); - if (isSocialChallenge) { - mUnifiedLoginTracker.track(Flow.GOOGLE_LOGIN, Step.PASSWORD_CHALLENGE); - } else { - mUnifiedLoginTracker.track(Flow.LOGIN_PASSWORD, Step.START); - } - } - - @Override - public void trackSignupCanceled() { - AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_CANCELED); - } - - @Override - public void trackSignupEmailButtonTapped() { - AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_EMAIL_BUTTON_TAPPED); - } - - @Override - public void trackSignupEmailToLogin() { - AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_EMAIL_TO_LOGIN); - } - - @Override - public void trackSignupGoogleButtonTapped() { - AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_SOCIAL_BUTTON_TAPPED); - } - - @Override - public void trackSignupMagicLinkFailed() { - AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_MAGIC_LINK_FAILED); - } - - @Override - public void trackSignupMagicLinkOpened() { - AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_MAGIC_LINK_OPENED); - } - - @Override - public void trackSignupMagicLinkOpenEmailClientClicked() { - AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_MAGIC_LINK_OPEN_EMAIL_CLIENT_CLICKED); - mUnifiedLoginTracker.track(Flow.SIGNUP, Step.EMAIL_OPENED); - } - - @Override - public void trackSignupMagicLinkSent() { - AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_MAGIC_LINK_SENT); - } - - @Override - public void trackSignupMagicLinkSucceeded() { - AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_MAGIC_LINK_SUCCEEDED); - } - - @Override - public void trackSignupSocialAccountsNeedConnecting() { - AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_SOCIAL_ACCOUNTS_NEED_CONNECTING); - } - - @Override - public void trackSignupSocialButtonFailure() { - AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_SOCIAL_BUTTON_FAILURE); - } - - @Override - public void trackSignupSocialToLogin() { - AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_SOCIAL_TO_LOGIN); - } - - @Override - public void trackSignupTermsOfServiceTapped() { - AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_TERMS_OF_SERVICE_TAPPED); - } - - @Override - public void trackSocialButtonStart() { - mUnifiedLoginTracker.track(Flow.GOOGLE_LOGIN, Step.START); - } - - @Override - public void trackSocialAccountsNeedConnecting() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_SOCIAL_ACCOUNTS_NEED_CONNECTING); - } - - @Override - public void trackSocialButtonClick() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_SOCIAL_BUTTON_CLICK); - mUnifiedLoginTracker.trackClick(Click.LOGIN_WITH_GOOGLE); - } - - @Override - public void trackSocialButtonFailure() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_SOCIAL_BUTTON_FAILURE); - } - - @Override - public void trackSocialConnectFailure() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_SOCIAL_CONNECT_FAILURE); - } - - @Override - public void trackSocialConnectSuccess() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_SOCIAL_CONNECT_SUCCESS); - } - - @Override - public void trackSocialErrorUnknownUser() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_SOCIAL_ERROR_UNKNOWN_USER); - } - - @Override - public void trackSocialFailure(String errorContext, String errorType, String errorDescription) { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_SOCIAL_FAILURE, errorContext, errorType, errorDescription); - } - - @Override - public void trackTwoFactorFormViewed() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_TWO_FACTOR_FORM_VIEWED); - mUnifiedLoginTracker.track(Step.TWO_FACTOR_AUTHENTICATION); - } - @Override public void trackUrlFormViewed() { AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_URL_FORM_VIEWED); mUnifiedLoginTracker.track(Flow.LOGIN_SITE_ADDRESS, Step.START); } - @Override - public void trackUrlHelpScreenViewed() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_URL_HELP_SCREEN_VIEWED); - } - - @Override - public void trackUsernamePasswordFormViewed() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_USERNAME_PASSWORD_FORM_VIEWED); - mUnifiedLoginTracker.track(Step.USERNAME_PASSWORD); - } - - @Override - public void trackWpComBackgroundServiceUpdate(Map properties) { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_WPCOM_BACKGROUND_SERVICE_UPDATE, properties); - } - @Override public void trackConnectedSiteInfoRequested(String url) { Map properties = new HashMap<>(); @@ -306,31 +84,11 @@ public void trackFailure(String message) { mUnifiedLoginTracker.trackFailure(message); } - @Override - public void trackSendCodeWithTextClicked() { - mUnifiedLoginTracker.trackClick(Click.SEND_CODE_WITH_TEXT); - } - - @Override - public void trackSubmit2faCodeClicked() { - mUnifiedLoginTracker.trackClick(Click.SUBMIT_2FA_CODE); - } - @Override public void trackSubmitClicked() { mUnifiedLoginTracker.trackClick(Click.SUBMIT); } - @Override - public void trackRequestMagicLinkClick() { - mUnifiedLoginTracker.trackClick(Click.REQUEST_MAGIC_LINK); - } - - @Override - public void trackLoginWithPasswordClick() { - mUnifiedLoginTracker.trackClick(Click.LOGIN_WITH_PASSWORD); - } - @Override public void trackShowHelpClick() { mUnifiedLoginTracker.trackClick(Click.SHOW_HELP); @@ -338,73 +96,7 @@ public void trackShowHelpClick() { } @Override - public void trackDismissDialog() { - mUnifiedLoginTracker.trackClick(Click.DISMISS); - } - - @Override - public void trackSelectEmailField() { - mUnifiedLoginTracker.trackClick(Click.SELECT_EMAIL_FIELD); - } - - @Override - public void trackPickEmailFromHint() { - mUnifiedLoginTracker.trackClick(Click.PICK_EMAIL_FROM_HINT); - } - - @Override - public void trackShowEmailHints() { - mUnifiedLoginTracker.track(Step.SHOW_EMAIL_HINTS); - } - - @Override - public void emailFormScreenResumed() { - mUnifiedLoginTracker.setFlowAndStep(Flow.WORDPRESS_COM, Step.START); - } - - @Override - public void trackSocialSignupConfirmationViewed() { - mUnifiedLoginTracker.track(Flow.GOOGLE_SIGNUP, Step.START); - } - - @Override - public void trackCreateAccountClick() { - mUnifiedLoginTracker.trackClick(Click.CREATE_ACCOUNT); - } - - @Override public void emailPasswordFormScreenResumed() { - mUnifiedLoginTracker.setStep(Step.START); - } - - @Override public void siteAddressFormScreenResumed() { + public void siteAddressFormScreenResumed() { mUnifiedLoginTracker.setStep(Step.START); } - - @Override public void magicLinkRequestScreenResumed() { - mUnifiedLoginTracker.setStep(Step.START); - } - - @Override public void magicLinkSentScreenResumed() { - mUnifiedLoginTracker.setStep(Step.MAGIC_LINK_REQUESTED); - } - - @Override public void usernamePasswordScreenResumed() { - mUnifiedLoginTracker.setStep(Step.USERNAME_PASSWORD); - } - - @Override public void trackLoginSecurityKeyFailure() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_SECURITY_KEY_FAILURE); - } - - @Override public void trackLoginSecurityKeySuccess() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_SECURITY_KEY_SUCCESS); - } - - @Override public void trackUseSecurityKeyClicked() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_SECURITY_KEY_CLICKED); - } - - @Override public void trackLogin2faNeeded() { - AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_2FA_NEEDED); - } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginCompletionUseCase.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginCompletionUseCase.kt deleted file mode 100644 index c34267789dc2..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginCompletionUseCase.kt +++ /dev/null @@ -1,98 +0,0 @@ -package org.wordpress.android.ui.accounts.login - -import org.wordpress.android.login.LoginMode -import org.wordpress.android.ui.prefs.AppPrefs -import javax.inject.Inject - -/** - * Use case for determining the appropriate action after login completion. - * This extracts testable logic from LoginActivity. - */ -class LoginCompletionUseCase @Inject constructor( - private val appPrefsWrapper: AppPrefsWrapper -) { - /** - * Determines if we should wait for sites to load after login. - * - * @param doLoginUpdate Whether the login requires fetching account/sites - * @param hasSites Whether the user already has sites loaded - * @return true if we should wait for sites to load before proceeding - */ - fun shouldWaitForSitesToLoad(doLoginUpdate: Boolean, hasSites: Boolean): Boolean { - return doLoginUpdate && !hasSites - } - - /** - * Determines the navigation action to take based on the login mode. - * - * @param loginMode The current login mode - * @return The appropriate navigation action - */ - fun getLoginCompletionAction(loginMode: LoginMode): LoginCompletionAction { - return when (loginMode) { - LoginMode.SHARE_INTENT, - LoginMode.JETPACK_SELFHOSTED, - LoginMode.SELFHOSTED_ONLY -> LoginCompletionAction.FINISH_WITH_NEW_SITE - - LoginMode.WOO_LOGIN_MODE -> LoginCompletionAction.FINISH_ONLY - - else -> LoginCompletionAction.NAVIGATE_TO_MAIN - } - } - - /** - * Determines the navigation destination for the main activity flow. - * - * @param loginMode The current login mode - * @param hasSites Whether the user has any sites - * @return The navigation destination - */ - fun getMainNavigationDestination( - loginMode: LoginMode, - hasSites: Boolean - ): MainNavigationDestination { - return when (loginMode) { - LoginMode.FULL, - LoginMode.JETPACK_LOGIN_ONLY, - LoginMode.WPCOM_LOGIN_ONLY -> { - if (!hasSites && appPrefsWrapper.shouldShowPostSignupInterstitial()) { - MainNavigationDestination.POST_SIGNUP_INTERSTITIAL - } else { - MainNavigationDestination.MAIN_ACTIVITY - } - } - else -> MainNavigationDestination.FINISH_ONLY - } - } - - /** - * Enum representing the high-level login completion action. - */ - enum class LoginCompletionAction { - /** Navigate to main activity (or post-signup interstitial) */ - NAVIGATE_TO_MAIN, - /** Finish with the newly added site ID (for self-hosted) */ - FINISH_WITH_NEW_SITE, - /** Just finish the activity (WooCommerce handles its own navigation) */ - FINISH_ONLY - } - - /** - * Enum representing the specific navigation destination. - */ - enum class MainNavigationDestination { - /** Show the post-signup interstitial screen */ - POST_SIGNUP_INTERSTITIAL, - /** Show the main activity */ - MAIN_ACTIVITY, - /** Just finish the activity */ - FINISH_ONLY - } -} - -/** - * Wrapper around AppPrefs to allow for testing. - */ -class AppPrefsWrapper @Inject constructor() { - fun shouldShowPostSignupInterstitial(): Boolean = AppPrefs.shouldShowPostSignupInterstitial() -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginPrologueListener.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginPrologueListener.kt deleted file mode 100644 index 98b975a38024..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginPrologueListener.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.wordpress.android.ui.accounts.login - -import android.content.Context - -interface LoginPrologueListener { - // Login Prologue callbacks - fun showWPcomLoginScreen(context: Context) - fun loginViaSiteAddress() -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginProloguePageFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginProloguePageFragment.kt deleted file mode 100644 index b2952ca5fc12..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginProloguePageFragment.kt +++ /dev/null @@ -1,115 +0,0 @@ -package org.wordpress.android.ui.accounts.login - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.widget.EditText -import android.widget.TextView -import androidx.annotation.LayoutRes -import androidx.annotation.StringRes -import androidx.core.text.HtmlCompat -import androidx.fragment.app.Fragment -import org.wordpress.android.R -import org.wordpress.android.databinding.LoginIntroTemplateViewBinding -import org.wordpress.android.util.ActivityUtils -import kotlin.math.min - -class LoginProloguePageFragment : Fragment(R.layout.login_intro_template_view) { - @StringRes - private var promoTitle: Int? = null - - @LayoutRes - private var promoLayoutId: Int? = null - - @LayoutRes - private var promoBackgroundId: Int? = null - - companion object { - private const val KEY_PROMO_TITLE = "KEY_PROMO_TITLE" - private const val KEY_PROMO_LAYOUT = "KEY_PROMO_LAYOUT" - private const val KEY_PROMO_BACKGROUND = "KEY_PROMO_BACKGROUND" - - @JvmStatic - fun newInstance( - @StringRes promoTitle: Int, - @LayoutRes promoLayoutId: Int, - @LayoutRes promoBackgroundId: Int - ) = LoginProloguePageFragment().apply { - arguments = Bundle().apply { - putInt(KEY_PROMO_TITLE, promoTitle) - putInt(KEY_PROMO_LAYOUT, promoLayoutId) - putInt(KEY_PROMO_BACKGROUND, promoBackgroundId) - } - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - arguments?.let { - promoTitle = it.getInt(KEY_PROMO_TITLE) - promoLayoutId = it.getInt(KEY_PROMO_LAYOUT) - promoBackgroundId = it.getInt(KEY_PROMO_BACKGROUND) - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - val inflater = LayoutInflater.from(view.context) - val binding = LoginIntroTemplateViewBinding.bind(view) - - promoTitle?.let { binding.promoTitle.setText(it) } - - val container = binding.promoLayoutContainer - container.post { - promoLayoutId?.let { - val content = inflater.inflate(promoLayoutId!!, container, false) - - val widthOfContainer = container.width - val heightOfContainer = container.height - - val smallestDimension = min(widthOfContainer, heightOfContainer).toFloat() - val sizeOfContent = resources.getDimensionPixelOffset(R.dimen.login_prologue_content_area).toFloat() - - val scaleFactor = smallestDimension / sizeOfContent - - content.scaleX = scaleFactor - content.scaleY = scaleFactor - - container.addView(content) - - // add a cursor to the end of the EditText at second prologue screen - if (promoLayoutId == R.layout.login_prologue_second) { - val editText = view.findViewById(R.id.edit_text) - editText.post { - editText.isPressed = true - editText.setSelection(editText.length()) - } - } - - // format text from HTML to show bold on third prologue screen - if (promoLayoutId == R.layout.login_prologue_third) { - view.findViewById(R.id.text_one).text = HtmlCompat.fromHtml( - getString(R.string.login_prologue_third_subtitle_one), - HtmlCompat.FROM_HTML_MODE_LEGACY - ) - view.findViewById(R.id.text_two).text = HtmlCompat.fromHtml( - getString(R.string.login_prologue_third_subtitle_two), - HtmlCompat.FROM_HTML_MODE_LEGACY - ) - view.findViewById(R.id.text_three).text = HtmlCompat.fromHtml( - getString(R.string.login_prologue_third_subtitle_three), - HtmlCompat.FROM_HTML_MODE_LEGACY - ) - } - } - } - promoBackgroundId?.let { - inflater.inflate(it, binding.promoBackgroundContainer, true) - } - } - - override fun onResume() { - super.onResume() - activity?.let { ActivityUtils.hideKeyboard(it) } - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginProloguePagerAdapter.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginProloguePagerAdapter.kt deleted file mode 100644 index aad482c17cd9..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/LoginProloguePagerAdapter.kt +++ /dev/null @@ -1,52 +0,0 @@ -package org.wordpress.android.ui.accounts.login - -import androidx.annotation.LayoutRes -import androidx.annotation.StringRes -import androidx.fragment.app.Fragment -import androidx.viewpager2.adapter.FragmentStateAdapter -import org.wordpress.android.R - -class LoginProloguePagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) { - private val pages = listOf( - Page( - R.string.login_prologue_title_first, - R.layout.login_prologue_first, - R.layout.login_prologue_background_first - ), - Page( - R.string.login_prologue_title_second, - R.layout.login_prologue_second, - R.layout.login_prologue_background_second - ), - Page( - R.string.login_prologue_title_third, - R.layout.login_prologue_third, - R.layout.login_prologue_background_third - ), - Page( - R.string.login_prologue_title_fourth, - R.layout.login_prologue_fourth, - R.layout.login_prologue_background_fourth - ), - Page( - R.string.login_prologue_title_fifth, - R.layout.login_prologue_fifth, - R.layout.login_prologue_background_fifth - ) - ) - - private data class Page( - @StringRes val promoTitle: Int = 0, - @LayoutRes val promoLayoutId: Int = 0, - @LayoutRes val promoBackgroundId: Int = 0 - ) - - override fun getItemCount(): Int { - return pages.size - } - - override fun createFragment(position: Int): Fragment { - val page = pages[position] - return LoginProloguePageFragment.newInstance(page.promoTitle, page.promoLayoutId, page.promoBackgroundId) - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/WPcomLoginHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/WPcomLoginHelper.kt index cfae5c7db717..bceaeeef73af 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/WPcomLoginHelper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/WPcomLoginHelper.kt @@ -3,48 +3,73 @@ package org.wordpress.android.ui.accounts.login import android.content.ComponentName import android.content.Context import android.net.Uri -import android.util.Log import androidx.browser.customtabs.CustomTabsCallback import androidx.browser.customtabs.CustomTabsClient import androidx.browser.customtabs.CustomTabsServiceConnection import androidx.browser.customtabs.CustomTabsSession import androidx.core.net.toUri +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.wordpress.android.fluxc.network.rest.wpapi.WPcomLoginClient import org.wordpress.android.fluxc.network.rest.wpcom.auth.AppSecrets import org.wordpress.android.fluxc.store.AccountStore +import org.wordpress.android.util.AppLog +import org.wordpress.android.util.AppLog.T import javax.inject.Inject -import kotlin.coroutines.CoroutineContext class WPcomLoginHelper @Inject constructor( private val loginClient: WPcomLoginClient, private val accountStore: AccountStore, appSecrets: AppSecrets, ) { - private val context: CoroutineContext = Dispatchers.IO + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) val wpcomLoginUri = loginClient.loginUri(appSecrets.redirectUri) private val customTabsServiceConnection = ServiceConnection(wpcomLoginUri) - private var processedAuthData: String? = null - @Suppress("ReturnCount") - fun tryLoginWithDataString(data: String?): Boolean { - if (data == null || data == processedAuthData) { - return false - } - - val code = data.toUri().getQueryParameter("code") ?: return false + /** + * Returns true if the data string contains an OAuth callback code. + */ + fun hasOAuthCallback(data: String?): Boolean { + if (data == null) return false + return data.toUri().getQueryParameter("code") != null + } - runBlocking { - val tokenResult = loginClient.exchangeAuthCodeForToken(code) - accountStore.updateAccessToken(tokenResult.getOrThrow()) - Log.i("WPCOM_LOGIN", "Login Successful") + /** + * Asynchronously exchanges the OAuth code in the data string for + * an access token. Calls [onSuccess] on the main thread if the + * exchange succeeds, or [onFailure] with the exception if it + * fails. + */ + fun tryLoginWithDataString( + data: String, + onSuccess: Runnable, + onFailure: java.util.function.Consumer + ) { + val code = data.toUri().getQueryParameter("code") + if (code == null) { + onFailure.accept( + IllegalArgumentException("Missing OAuth code in callback") + ) + return } - processedAuthData = data - return true + scope.launch { + try { + val tokenResult = loginClient + .exchangeAuthCodeForToken(code) + accountStore.updateAccessToken( + tokenResult.getOrThrow() + ) + withContext(Dispatchers.Main) { onSuccess.run() } + } catch (e: Exception) { + withContext(Dispatchers.Main) { onFailure.accept(e) } + } + } } fun isLoggedIn(): Boolean { @@ -52,7 +77,8 @@ class WPcomLoginHelper @Inject constructor( } fun dispose() { - context.cancel() + scope.cancel() + customTabsServiceConnection.unbind() } fun bindCustomTabsService(context: Context) { @@ -62,17 +88,25 @@ class WPcomLoginHelper @Inject constructor( class ServiceConnection( var uri: Uri -): CustomTabsServiceConnection() { +) : CustomTabsServiceConnection() { + private var boundContext: Context? = null private var client: CustomTabsClient? = null private var session: CustomTabsSession? = null - override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) { + override fun onCustomTabsServiceConnected( + name: ComponentName, + client: CustomTabsClient + ) { client.warmup(0) this.client = client val session = client.newSession(CustomTabsCallback()) session?.mayLaunchUrl(uri, null, null) - session?.mayLaunchUrl("https://wordpress.com/log-in/".toUri(), null, null) + session?.mayLaunchUrl( + "https://wordpress.com/log-in/".toUri(), + null, + null + ) this.session = session } @@ -90,8 +124,21 @@ class ServiceConnection( // Get the default browser package name, this will be null if // the default browser does not provide a CustomTabsService - val packageName = CustomTabsClient.getPackageName(context, null) ?: return + val packageName = + CustomTabsClient.getPackageName(context, null) ?: return + + CustomTabsClient.bindCustomTabsService( + context, + packageName, + this + ) + boundContext = context + } - CustomTabsClient.bindCustomTabsService(context, packageName, this) + fun unbind() { + boundContext?.unbindService(this) + boundContext = null + client = null + session = null } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt index 39689966d8f9..010c72b94f1b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt @@ -1,8 +1,10 @@ package org.wordpress.android.ui.accounts.login.applicationpassword +import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow @@ -10,19 +12,20 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import org.wordpress.android.R import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider import org.wordpress.android.fluxc.utils.AppLogWrapper import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper -import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.ANDROID_JETPACK_CLIENT -import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.ANDROID_WORDPRESS_CLIENT import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.UriLogin import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures.Feature import org.wordpress.android.util.AppLog import org.wordpress.android.util.BuildConfigWrapper +import org.wordpress.android.util.DeviceUtils import rs.wordpress.api.kotlin.WpRequestResult import uniffi.wp_api.ApplicationPasswordCreateParams +import uniffi.wp_api.RequestExecutionErrorReason import uniffi.wp_api.WpUuid import java.text.SimpleDateFormat import java.util.Date @@ -31,6 +34,7 @@ import javax.inject.Inject @HiltViewModel class ApplicationPasswordAutoAuthDialogViewModel @Inject constructor( + @ApplicationContext private val context: Context, private val wpApiClientProvider: WpApiClientProvider, private val applicationPasswordLoginHelper: ApplicationPasswordLoginHelper, private val buildConfigWrapper: BuildConfigWrapper, @@ -43,7 +47,7 @@ class ApplicationPasswordAutoAuthDialogViewModel @Inject constructor( private val _isLoading = MutableStateFlow(false) val isLoading: StateFlow = _isLoading.asStateFlow() - @Suppress("TooGenericExceptionCaught") + @Suppress("TooGenericExceptionCaught", "LongMethod") fun createApplicationPassword(site: SiteModel) { viewModelScope.launch { try { @@ -57,10 +61,11 @@ class ApplicationPasswordAutoAuthDialogViewModel @Inject constructor( val client = wpApiClientProvider.getWpApiClientCookiesNonceAuthentication( site = site, ) + val deviceName = DeviceUtils.getInstance().getDeviceName(context) val appName = if (buildConfigWrapper.isJetpackApp) { - ANDROID_JETPACK_CLIENT + context.getString(R.string.application_password_app_name_jetpack, deviceName) } else { - ANDROID_WORDPRESS_CLIENT + context.getString(R.string.application_password_app_name_wordpress, deviceName) } val appId = WpUuid() val response = client.request { requestBuilder -> @@ -88,8 +93,33 @@ class ApplicationPasswordAutoAuthDialogViewModel @Inject constructor( _navigationEvent.emit(NavigationEvent.Success) } + is WpRequestResult.WpError -> { + appLogWrapper.e( + AppLog.T.API, + "Error creating application password: WpError - ${response.errorMessage}" + ) + fallbackToManualLogin(site.url) + } + + is WpRequestResult.RequestExecutionFailed -> { + val isTimeout = response.reason is RequestExecutionErrorReason.HttpTimeoutError + if (isTimeout) { + appLogWrapper.e(AppLog.T.API, "Error creating application password: Request timed out") + } else { + appLogWrapper.e( + AppLog.T.API, + "Error creating application password: RequestExecutionFailed - " + + "reason=${response.reason}, statusCode=${response.statusCode}" + ) + } + fallbackToManualLogin(site.url) + } + else -> { - appLogWrapper.e(AppLog.T.API, "Error creating application password") + appLogWrapper.e( + AppLog.T.API, + "Error creating application password: ${response::class.simpleName} - $response" + ) fallbackToManualLogin(site.url) } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/LoginSiteApplicationPasswordFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/LoginSiteApplicationPasswordFragment.kt index 813b08cb1935..165c8d2c6bfa 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/LoginSiteApplicationPasswordFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/LoginSiteApplicationPasswordFragment.kt @@ -1,123 +1,99 @@ package org.wordpress.android.ui.accounts.login.applicationpassword +import android.content.Context import android.os.Bundle -import android.text.Editable -import android.text.TextWatcher +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Button -import android.widget.EditText -import android.widget.TextView -import androidx.annotation.LayoutRes -import androidx.appcompat.app.ActionBar -import androidx.appcompat.widget.Toolbar +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch -import org.wordpress.android.WordPress -import org.wordpress.android.fluxc.store.AccountStore -import org.wordpress.android.login.LoginBaseFormFragment -import org.wordpress.android.login.LoginListener -import org.wordpress.android.login.LoginSiteAddressHelpDialogFragment -import org.wordpress.android.login.LoginSiteAddressValidator -import org.wordpress.android.login.R -import org.wordpress.android.login.widgets.WPLoginInputRow -import org.wordpress.android.login.widgets.WPLoginInputRow.OnEditorCommitListener +import org.wordpress.android.R import org.wordpress.android.ui.ActivityNavigator -import org.wordpress.android.util.EditTextUtils +import org.wordpress.android.ui.accounts.LoginActivity +import org.wordpress.android.ui.accounts.login.LoginAnalyticsListener +import org.wordpress.android.ui.compose.theme.AppThemeM3 import org.wordpress.android.util.NetworkUtils +import org.wordpress.android.util.UrlUtils +import org.wordpress.android.util.WPUrlUtils import javax.inject.Inject -class LoginSiteApplicationPasswordFragment : LoginBaseFormFragment(), TextWatcher, - OnEditorCommitListener { - private var siteAddressInput: WPLoginInputRow? = null +@AndroidEntryPoint +class LoginSiteApplicationPasswordFragment : Fragment() { + private var loginActivity: LoginActivity? = null - private var loginSiteAddressValidator = LoginSiteAddressValidator() - - @Inject - lateinit var viewModelFactory: ViewModelProvider.Factory - private lateinit var viewModel: LoginSiteApplicationPasswordViewModel - - @Inject - lateinit var accountStore: AccountStore + private val viewModel: LoginSiteApplicationPasswordViewModel by viewModels() @Inject lateinit var activityNavigator: ActivityNavigator - @LayoutRes - override fun getContentLayout(): Int = R.layout.login_site_address_screen - - @LayoutRes - override fun getProgressBarText(): Int = R.string.login_checking_site_address - - override fun setupLabel(label: TextView) { - label.setText(R.string.enter_site_address) - } - - override fun setupContent(rootView: ViewGroup) { - // Stub - } - - override fun setupBottomButton(button: Button) { - button.setOnClickListener { discover() } - } - - override fun buildToolbar(toolbar: Toolbar, actionBar: ActionBar) { - actionBar.setTitle(R.string.log_in) - } - - override fun getEditTextToFocusOnStart(): EditText? = siteAddressInput?.editText - - override fun onHelp() { - if (mLoginListener != null) { - mLoginListener.helpSiteAddress(loginSiteAddressValidator.cleanedSiteAddress) + @Inject + lateinit var analyticsListener: LoginAnalyticsListener + + override fun onAttach(context: Context) { + super.onAttach(context) + check(context is LoginActivity) { "$context must be LoginActivity" } + loginActivity = context + } + + override fun onDetach() { + super.onDetach() + loginActivity = null + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + val isLoading by viewModel.loadingStateFlow.collectAsState() + val errorMessage by viewModel.errorMessage.collectAsState() + + AppThemeM3 { + LoginSiteApplicationPasswordScreen( + isLoading = isLoading, + errorMessage = errorMessage, + onBackClick = { + requireActivity().onBackPressedDispatcher.onBackPressed() + }, + onHelpClick = { cleanedAddress -> + analyticsListener.trackShowHelpClick() + loginActivity?.helpSiteAddress(cleanedAddress) + }, + onContinueClick = { cleanedAddress -> + discover(cleanedAddress) + }, + onErrorDismissed = { + viewModel.clearError() + } + ) + } + } } } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - (requireActivity().application as WordPress).component().inject(this) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - mAnalyticsListener.trackUrlFormViewed() - - requireActivity().setTitle(R.string.site_address_login_title) - this.siteAddressInput = view.findViewById(R.id.login_site_address_row) - siteAddressInput?.addTextChangedListener(this) - siteAddressInput?.setOnEditorCommitListener(this) - - view.findViewById(R.id.login_site_address_help_button).setOnClickListener { - mAnalyticsListener.trackShowHelpClick() - showSiteAddressHelp() - } - - loginSiteAddressValidator.isValid.observe(viewLifecycleOwner) { enabled -> - bottomButton.isEnabled = enabled - } - loginSiteAddressValidator.errorMessageResId.observe(viewLifecycleOwner) { resId -> - if (resId != null) { - showError(resId) - } else { - siteAddressInput?.setError(null) - } - } - - viewModel = ViewModelProvider(this, viewModelFactory)[LoginSiteApplicationPasswordViewModel::class.java] + activity?.setTitle(R.string.site_address_login_title) + analyticsListener.trackUrlFormViewed() viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.discoveryURL.collect { url -> if (url.isEmpty()) { - showError(R.string.application_password_not_supported_error) - return@collect + viewModel.setError(getString(R.string.application_password_not_supported_error)) } else { activityNavigator.openApplicationPasswordLogin(requireActivity(), url) } @@ -125,70 +101,34 @@ class LoginSiteApplicationPasswordFragment : LoginBaseFormFragment - if (loading) { - startProgress() - } else { - endProgressIfNeeded() + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.errorMessage.collect { error -> + error?.let { + analyticsListener.trackFailure(it) + } } } - .launchIn(viewLifecycleOwner.lifecycleScope) + } } override fun onResume() { super.onResume() - mAnalyticsListener.siteAddressFormScreenResumed() - } - - override fun onDestroyView() { - super.onDestroyView() - loginSiteAddressValidator.dispose() - siteAddressInput = null - } - - override fun onEditorCommit() { - if (bottomButton.isEnabled) { - discover() - } - } - - override fun afterTextChanged(s: Editable) { - siteAddressInput?.let { siteAddressInput -> - loginSiteAddressValidator.setAddress(EditTextUtils.getText(siteAddressInput.editText)) - } - } - - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { - // Stub - } - - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - siteAddressInput?.setError(null) - } - - private fun showError(messageId: Int) { - val message = getString(messageId) - mAnalyticsListener.trackFailure(message) - siteAddressInput?.setError(message) + analyticsListener.siteAddressFormScreenResumed() } - private fun showSiteAddressHelp() { - LoginSiteAddressHelpDialogFragment().show( - parentFragmentManager, - LoginSiteAddressHelpDialogFragment.TAG - ) - } - - private fun discover() { + private fun discover(cleanedUrl: String) { if (!NetworkUtils.checkConnection(activity)) { return } - mAnalyticsListener.trackSubmitClicked() - - val cleanedUrl = loginSiteAddressValidator.cleanedSiteAddress - mAnalyticsListener.trackConnectedSiteInfoRequested(cleanedUrl) + // WP.com sites should use the OAuth flow, not application passwords + val urlWithScheme = UrlUtils.addUrlSchemeIfNeeded(cleanedUrl, true) + if (WPUrlUtils.isWordPressCom(urlWithScheme)) { + loginActivity?.showWPcomLoginScreen(requireContext()) + return + } + analyticsListener.trackSubmitClicked() + analyticsListener.trackConnectedSiteInfoRequested(cleanedUrl) viewModel.runApiDiscovery(cleanedUrl) } @@ -196,4 +136,3 @@ class LoginSiteApplicationPasswordFragment : LoginBaseFormFragment Unit, + onHelpClick: (cleanedAddress: String) -> Unit, + onContinueClick: (cleanedAddress: String) -> Unit, + onErrorDismissed: () -> Unit, + modifier: Modifier = Modifier +) { + var siteAddress by rememberSaveable { mutableStateOf("") } + val focusRequester = remember { FocusRequester() } + + val cleanedAddress = siteAddress.trim().replace(Regex("[\r\n]"), "") + val isValid = cleanedAddress.isNotEmpty() && Patterns.WEB_URL.matcher(cleanedAddress).matches() + + if (isLoading) { + LoadingDialog() + } + + Scaffold( + modifier = modifier, + contentWindowInsets = WindowInsets(0, 0, 0, 0), + topBar = { + TopAppBar( + title = { Text(text = stringResource(R.string.log_in)) }, + navigationIcon = { + IconButton(onClick = onBackClick) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(R.string.back) + ) + } + }, + actions = { + IconButton(onClick = { onHelpClick(cleanedAddress) }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.HelpOutline, + contentDescription = stringResource(R.string.help) + ) + } + } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .imePadding() + .verticalScroll(rememberScrollState()) + .padding(horizontal = 16.dp) + ) { + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = stringResource(R.string.enter_site_address), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface + ) + + Spacer(modifier = Modifier.height(24.dp)) + + OutlinedTextField( + value = siteAddress, + onValueChange = { newValue -> + siteAddress = newValue + onErrorDismissed() + }, + modifier = Modifier + .fillMaxWidth() + .focusRequester(focusRequester), + label = { Text(text = stringResource(R.string.login_site_address)) }, + isError = errorMessage != null, + supportingText = errorMessage?.let { { Text(text = it) } }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Uri, + imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions( + onDone = { + if (isValid && !isLoading) { + onContinueClick(cleanedAddress) + } + } + ), + singleLine = true + ) + + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } + + Spacer(modifier = Modifier.weight(1f)) + + Button( + onClick = { onContinueClick(cleanedAddress) }, + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding() + .padding(vertical = 16.dp), + enabled = isValid && !isLoading + ) { + Text(text = stringResource(R.string.login_continue)) + } + } + } +} + +@Composable +private fun LoadingDialog() { + Dialog(onDismissRequest = { }) { + Surface( + shape = MaterialTheme.shapes.medium, + tonalElevation = 8.dp + ) { + Column( + modifier = Modifier.padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + CircularProgressIndicator() + Spacer(modifier = Modifier.height(16.dp)) + Text(text = stringResource(R.string.login_checking_site_address)) + } + } + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/LoginSiteApplicationPasswordViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/LoginSiteApplicationPasswordViewModel.kt index 29a8b7f43095..db3d4cad554b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/LoginSiteApplicationPasswordViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/LoginSiteApplicationPasswordViewModel.kt @@ -2,30 +2,43 @@ package org.wordpress.android.ui.accounts.login.applicationpassword import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.flow.MutableSharedFlow +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper import javax.inject.Inject +@HiltViewModel class LoginSiteApplicationPasswordViewModel @Inject constructor( private val applicationPasswordLoginHelper: ApplicationPasswordLoginHelper, ) : ViewModel() { - // replay = 0: New subscribers will not receive previously emitted values - private val _discoveryURL = MutableSharedFlow(replay = 0) - val discoveryURL = _discoveryURL.asSharedFlow() + private val _discoveryURL = Channel(Channel.BUFFERED) + val discoveryURL = _discoveryURL.receiveAsFlow() private val _loadingStateFlow = MutableStateFlow(false) val loadingStateFlow get() = _loadingStateFlow.asStateFlow() + private val _errorMessage = MutableStateFlow(null) + val errorMessage = _errorMessage.asStateFlow() + fun runApiDiscovery(siteUrl: String) { + _errorMessage.value = null _loadingStateFlow.value = true viewModelScope.launch { val discoveryUrl = applicationPasswordLoginHelper.getAuthorizationUrlComplete(siteUrl) - _discoveryURL.emit(discoveryUrl) + _discoveryURL.send(discoveryUrl) _loadingStateFlow.value = false } } + + fun setError(message: String) { + _errorMessage.value = message + } + + fun clearError() { + _errorMessage.value = null + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/jetpack/LoginNoSitesFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/jetpack/LoginNoSitesFragment.kt index a192680a8ef4..33211ec8c4d2 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/jetpack/LoginNoSitesFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/jetpack/LoginNoSitesFragment.kt @@ -1,6 +1,5 @@ package org.wordpress.android.ui.accounts.login.jetpack -import android.content.Context import android.os.Bundle import android.view.View import androidx.activity.addCallback @@ -10,9 +9,6 @@ import dagger.hilt.android.AndroidEntryPoint import org.wordpress.android.R import org.wordpress.android.WordPress import org.wordpress.android.databinding.JetpackLoginEmptyViewBinding -import org.wordpress.android.login.LoginListener -import org.wordpress.android.login.util.AvatarHelper -import org.wordpress.android.login.util.AvatarHelper.AvatarRequestListener import org.wordpress.android.ui.ActivityLauncher import org.wordpress.android.ui.accounts.LoginNavigationEvents.ShowInstructions import org.wordpress.android.ui.accounts.LoginNavigationEvents.ShowSignInForResultJetpackOnly @@ -20,6 +16,8 @@ import org.wordpress.android.ui.accounts.login.jetpack.LoginNoSitesViewModel.Sta import org.wordpress.android.ui.accounts.login.jetpack.LoginNoSitesViewModel.State.ShowUser import org.wordpress.android.ui.main.utils.MeGravatarLoader import org.wordpress.android.ui.utils.UiHelpers +import org.wordpress.android.util.image.ImageManager +import org.wordpress.android.util.image.ImageType import javax.inject.Inject @AndroidEntryPoint @@ -35,9 +33,11 @@ class LoginNoSitesFragment : Fragment(R.layout.jetpack_login_empty_view) { @Inject lateinit var meGravatarLoader: MeGravatarLoader + @Inject + lateinit var imageManager: ImageManager + @Inject lateinit var uiHelpers: UiHelpers - private var loginListener: LoginListener? = null private val viewModel: LoginNoSitesViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -97,15 +97,11 @@ class LoginNoSitesFragment : Fragment(R.layout.jetpack_login_empty_view) { } private fun JetpackLoginEmptyViewBinding.loadGravatar(avatarUrl: String) { - AvatarHelper.loadAvatarFromUrl( - this@LoginNoSitesFragment, - meGravatarLoader.constructGravatarUrl(avatarUrl), + imageManager.loadIntoCircle( userContainer.imageAvatar, - object : AvatarRequestListener { - override fun onRequestFinished() { - // no op - } - }) + ImageType.AVATAR_WITHOUT_BACKGROUND, + meGravatarLoader.constructGravatarUrl(avatarUrl) + ) } private fun JetpackLoginEmptyViewBinding.setUserName(value: String) = @@ -127,18 +123,6 @@ class LoginNoSitesFragment : Fragment(R.layout.jetpack_login_empty_view) { super.onSaveInstanceState(outState) } - override fun onAttach(context: Context) { - super.onAttach(context) - - // this will throw if parent activity doesn't implement the login listener interface - loginListener = context as? LoginListener - } - - override fun onDetach() { - loginListener = null - super.onDetach() - } - override fun onResume() { super.onResume() viewModel.onFragmentResume() diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/jetpack/LoginSiteCheckErrorFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/jetpack/LoginSiteCheckErrorFragment.kt index ec4a267d3199..014cbb31d776 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/jetpack/LoginSiteCheckErrorFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/jetpack/LoginSiteCheckErrorFragment.kt @@ -1,6 +1,5 @@ package org.wordpress.android.ui.accounts.login.jetpack -import android.content.Context import android.os.Bundle import android.view.View import androidx.activity.addCallback @@ -9,7 +8,6 @@ import androidx.fragment.app.viewModels import dagger.hilt.android.AndroidEntryPoint import org.wordpress.android.R import org.wordpress.android.databinding.JetpackLoginEmptyViewBinding -import org.wordpress.android.login.LoginListener import org.wordpress.android.ui.ActivityLauncher import org.wordpress.android.ui.accounts.LoginNavigationEvents.ShowInstructions import org.wordpress.android.ui.accounts.LoginNavigationEvents.ShowSignInForResultJetpackOnly @@ -39,7 +37,6 @@ class LoginSiteCheckErrorFragment : Fragment(R.layout.jetpack_login_empty_view) @Inject lateinit var htmlMessageUtils: HtmlMessageUtils - private var loginListener: LoginListener? = null private var siteAddress: String? = null private val viewModel: LoginSiteCheckErrorViewModel by viewModels() @@ -98,18 +95,6 @@ class LoginSiteCheckErrorFragment : Fragment(R.layout.jetpack_login_empty_view) ActivityLauncher.openUrlExternal(requireContext(), url) } - override fun onAttach(context: Context) { - super.onAttach(context) - - // this will throw if parent activity doesn't implement the login listener interface - loginListener = context as? LoginListener - } - - override fun onDetach() { - loginListener = null - super.onDetach() - } - override fun onResume() { super.onResume() diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/signup/SignupEpilogueFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/signup/SignupEpilogueFragment.kt deleted file mode 100644 index a3fc10a5fe73..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/signup/SignupEpilogueFragment.kt +++ /dev/null @@ -1,727 +0,0 @@ -@file:Suppress("DEPRECATION") - -package org.wordpress.android.ui.accounts.signup - -import android.annotation.SuppressLint -import android.content.Context -import android.content.DialogInterface -import android.os.Bundle -import androidx.core.net.toUri -import android.text.Editable -import android.text.TextUtils -import android.text.TextWatcher -import android.view.KeyEvent -import android.view.LayoutInflater -import android.view.View -import android.view.View.OnFocusChangeListener -import android.view.ViewGroup -import android.view.ViewTreeObserver -import android.widget.Button -import android.widget.EditText -import android.widget.FrameLayout -import android.widget.ImageView -import android.widget.TextView -import androidx.annotation.LayoutRes -import androidx.core.widget.NestedScrollView -import androidx.lifecycle.lifecycleScope -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.gravatar.AvatarQueryOptions -import com.gravatar.AvatarUrl -import com.gravatar.quickeditor.GravatarQuickEditor -import com.gravatar.quickeditor.ui.editor.AuthenticationMethod -import com.gravatar.quickeditor.ui.editor.AvatarPickerContentLayout -import com.gravatar.quickeditor.ui.editor.AvatarPickerResult -import com.gravatar.quickeditor.ui.editor.GravatarQuickEditorParams -import com.gravatar.services.AvatarService -import com.gravatar.services.GravatarResult -import com.gravatar.types.Email -import kotlinx.coroutines.launch -import org.greenrobot.eventbus.Subscribe -import org.greenrobot.eventbus.ThreadMode -import org.wordpress.android.R -import org.wordpress.android.WordPress -import org.wordpress.android.analytics.AnalyticsTracker -import org.wordpress.android.analytics.AnalyticsTracker.Stat -import org.wordpress.android.fluxc.Dispatcher -import org.wordpress.android.fluxc.action.AccountAction -import org.wordpress.android.fluxc.generated.AccountActionBuilder -import org.wordpress.android.fluxc.store.AccountStore -import org.wordpress.android.fluxc.store.AccountStore.AccountUsernameActionType -import org.wordpress.android.fluxc.store.AccountStore.OnAccountChanged -import org.wordpress.android.fluxc.store.AccountStore.OnUsernameChanged -import org.wordpress.android.fluxc.store.AccountStore.PushAccountSettingsPayload -import org.wordpress.android.fluxc.store.AccountStore.PushUsernamePayload -import org.wordpress.android.login.LoginBaseFormFragment -import org.wordpress.android.login.widgets.WPLoginInputRow -import org.wordpress.android.ui.FullScreenDialogFragment -import org.wordpress.android.ui.FullScreenDialogFragment.OnShownListener -import org.wordpress.android.ui.accounts.UnifiedLoginTracker -import org.wordpress.android.ui.prefs.AppPrefsWrapper -import org.wordpress.android.ui.reader.services.update.ReaderUpdateLogic.UpdateTask -import org.wordpress.android.ui.reader.services.update.ReaderUpdateServiceStarter -import org.wordpress.android.util.AppLog -import org.wordpress.android.util.MediaUtils -import org.wordpress.android.util.StringUtils -import org.wordpress.android.util.ToastUtils -import org.wordpress.android.util.extensions.redirectContextClickToLongPressListener -import org.wordpress.android.util.image.ImageManager -import org.wordpress.android.util.image.ImageType -import org.wordpress.android.widgets.WPTextView -import java.io.File -import java.net.URI -import java.net.URISyntaxException -import java.util.EnumSet -import java.util.Locale -import javax.inject.Inject - -@Suppress("LargeClass") -class SignupEpilogueFragment : LoginBaseFormFragment(), - FullScreenDialogFragment.OnConfirmListener, FullScreenDialogFragment.OnDismissListener, - OnShownListener { - private lateinit var mEditTextDisplayName: EditText - private lateinit var mEditTextUsername: EditText - private var mDialog: FullScreenDialogFragment? = null - private var mSignupEpilogueListener: SignupEpilogueListener? = null - - private lateinit var mHeaderAvatarAdd: ImageView - private var mDisplayName: String? = null - private lateinit var mEmailAddress: String - private lateinit var mPhotoUrl: String - private var mUsername: String? = null - private lateinit var mInputPassword: WPLoginInputRow - private lateinit var mHeaderAvatar: ImageView - private lateinit var mHeaderDisplayName: WPTextView - private lateinit var mHeaderEmailAddress: WPTextView - private var mBottomShadow: View? = null - private lateinit var mScrollView: NestedScrollView - private var mIsAvatarAdded: Boolean = false - private var mIsEmailSignup: Boolean = false - - private var mIsUpdatingDisplayName = false - private var mIsUpdatingPassword = false - private var mHasUpdatedPassword = false - private var mHasMadeUpdates = false - - @Inject - lateinit var mAccount: AccountStore - - @Inject - lateinit var dispatcher: Dispatcher - - @Inject - lateinit var mImageManager: ImageManager - - @Inject - lateinit var mAppPrefsWrapper: AppPrefsWrapper - - @Inject - lateinit var mUnifiedLoginTracker: UnifiedLoginTracker - - @Inject - lateinit var mSignupUtils: SignupUtils - - @Inject - lateinit var mAvatarService: AvatarService - - @LayoutRes - override fun getContentLayout(): Int { - return 0 // no content layout; entire view is inflated in createMainView - } - - @LayoutRes - override fun getProgressBarText(): Int { - return R.string.signup_updating_account - } - - override fun setupLabel(label: TextView) { - // no label in this screen - } - - override fun createMainView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): ViewGroup { - return inflater.inflate(R.layout.signup_epilogue, container, false) as ViewGroup - } - - @Suppress("LongMethod") - override fun setupContent(rootView: ViewGroup) { - val headerAvatarLayout = - rootView.findViewById(R.id.login_epilogue_header_avatar_layout) - headerAvatarLayout.isEnabled = mIsEmailSignup - headerAvatarLayout.setOnClickListener { - mUnifiedLoginTracker.trackClick(UnifiedLoginTracker.Click.SELECT_AVATAR) - GravatarQuickEditor.show( - fragment = this, - gravatarQuickEditorParams = GravatarQuickEditorParams { - email = Email(mEmailAddress) - avatarPickerContentLayout = AvatarPickerContentLayout.Horizontal - }, - authenticationMethod = AuthenticationMethod.Bearer(mAccount.accessToken.orEmpty()), - updateHandler = { event -> - when (event) { - AvatarPickerResult -> { - mPhotoUrl = AvatarUrl( - email = Email(mEmailAddress), - avatarQueryOptions = AvatarQueryOptions { - preferredSize = resources.getDimensionPixelSize(R.dimen.avatar_sz_large) - } - ).url(cacheBuster = System.currentTimeMillis().toString()).toString() - mImageManager.loadIntoCircle( - mHeaderAvatar, - ImageType.AVATAR_WITHOUT_BACKGROUND, - mPhotoUrl - ) - mHeaderAvatarAdd.visibility = View.GONE - mIsAvatarAdded = true - } - - else -> Unit - } - }, - ) - } - headerAvatarLayout.setOnLongClickListener { - ToastUtils.showToast( - activity, getString(R.string.content_description_add_avatar), - ToastUtils.Duration.SHORT - ) - true - } - headerAvatarLayout.redirectContextClickToLongPressListener() - mHeaderAvatarAdd = rootView.findViewById(R.id.login_epilogue_header_avatar_add) - mHeaderAvatarAdd.setVisibility(if (mIsEmailSignup) View.VISIBLE else View.GONE) - mHeaderAvatar = rootView.findViewById(R.id.login_epilogue_header_avatar) - mHeaderDisplayName = rootView.findViewById(R.id.login_epilogue_header_title) - mHeaderDisplayName.text = mDisplayName - mHeaderEmailAddress = rootView.findViewById(R.id.login_epilogue_header_subtitle) - mHeaderEmailAddress.text = mEmailAddress - val inputDisplayName = - rootView.findViewById(R.id.signup_epilogue_input_display) - mEditTextDisplayName = inputDisplayName.editText - mEditTextDisplayName.setText(mDisplayName) - mEditTextDisplayName.addTextChangedListener(object : TextWatcher { - @Suppress("EmptyFunctionBlock") - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { - } - - @Suppress("EmptyFunctionBlock") - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - } - - override fun afterTextChanged(s: Editable) { - mDisplayName = s.toString() - mHeaderDisplayName.text = mDisplayName - } - }) - val inputUsername = - rootView.findViewById(R.id.signup_epilogue_input_username) - mEditTextUsername = inputUsername.editText - mEditTextUsername.setText(mUsername) - mEditTextUsername.setOnClickListener { - mUnifiedLoginTracker.trackClick(UnifiedLoginTracker.Click.EDIT_USERNAME) - launchDialog() - } - mEditTextUsername.onFocusChangeListener = OnFocusChangeListener { _, hasFocus -> - if (hasFocus) { - launchDialog() - } - } - mEditTextUsername.setOnKeyListener { _, keyCode, _ -> - // Consume keyboard events except for Enter (i.e. click/tap) and Tab (i.e. focus/navigation). - // The onKey method returns true if the listener has consumed the event and false otherwise - // allowing hardware keyboard users to tap and navigate, but not input text as expected. - // This allows the username changer to launch using the keyboard. - !(keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_TAB) - } - mInputPassword = rootView.findViewById(R.id.signup_epilogue_input_password) - mInputPassword.visibility = if (mIsEmailSignup) View.VISIBLE else View.GONE - val passwordDetail = - rootView.findViewById(R.id.signup_epilogue_input_password_detail) - passwordDetail.visibility = if (mIsEmailSignup) View.VISIBLE else View.GONE - - // Set focus on static text field to avoid showing keyboard on start. - mHeaderEmailAddress.requestFocus() - - mBottomShadow = rootView.findViewById(R.id.bottom_shadow) - mScrollView = rootView.findViewById(R.id.scroll_view) - mScrollView.setOnScrollChangeListener { _: NestedScrollView?, _: Int, _: Int, _: Int, _: Int -> - showBottomShadowIfNeeded() - } - // We must use onGlobalLayout here otherwise canScrollVertically will always return false - mScrollView.getViewTreeObserver() - .addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - mScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(this) - showBottomShadowIfNeeded() - } - }) - } - - private fun showBottomShadowIfNeeded() { - val canScrollDown = mScrollView.canScrollVertically(1) - if (mBottomShadow != null) { - mBottomShadow!!.visibility = - if (canScrollDown) View.VISIBLE else View.GONE - } - } - - override fun setupBottomButton(button: Button) { - button.setOnClickListener { - mUnifiedLoginTracker.trackClick(UnifiedLoginTracker.Click.CONTINUE) - updateAccountOrContinue() - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - (requireActivity().application as WordPress).component().inject(this) - dispatcher.dispatch(AccountActionBuilder.newFetchAccountAction()) - - mDisplayName = requireArguments().getString(ARG_DISPLAY_NAME) - mEmailAddress = StringUtils.notNullStr(requireArguments().getString(ARG_EMAIL_ADDRESS)) - mPhotoUrl = StringUtils.notNullStr(requireArguments().getString(ARG_PHOTO_URL)) - mUsername = requireArguments().getString(ARG_USERNAME) - mIsEmailSignup = requireArguments().getBoolean(ARG_IS_EMAIL_SIGNUP) - } - - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) - - if (savedInstanceState == null) { - // Start loading reader tags so they will be available asap - ReaderUpdateServiceStarter.startService( - WordPress.getContext(), - EnumSet.of(UpdateTask.TAGS) - ) - - mUnifiedLoginTracker.track(step = UnifiedLoginTracker.Step.SUCCESS) - if (mIsEmailSignup) { - AnalyticsTracker.track(Stat.SIGNUP_EMAIL_EPILOGUE_VIEWED) - - // Start progress and wait for account to be fetched before populating views when - // email does not exist in account store. - if (TextUtils.isEmpty(mAccountStore.account.email)) { - startProgress(false) - } else { - // Skip progress and populate views when email does exist in account store. - populateViews() - } - } else { - AnalyticsTracker.track(Stat.SIGNUP_SOCIAL_EPILOGUE_VIEWED) - mAccount.accessToken?.let { accessToken -> - DownloadAvatarAndUploadGravatarThread( - mPhotoUrl, - mEmailAddress, - accessToken - ).start() - mImageManager.loadIntoCircle( - mHeaderAvatar, - ImageType.AVATAR_WITHOUT_BACKGROUND, - (mPhotoUrl) - ) - } - } - } else { - mDialog = parentFragmentManager - .findFragmentByTag(FullScreenDialogFragment.TAG) as FullScreenDialogFragment? - - if (mDialog != null) { - mDialog!!.setOnConfirmListener(this) - mDialog!!.setOnDismissListener(this) - } - - mDisplayName = savedInstanceState.getString(KEY_DISPLAY_NAME) - mUsername = savedInstanceState.getString(KEY_USERNAME) - mIsAvatarAdded = savedInstanceState.getBoolean(KEY_IS_AVATAR_ADDED) - - if (mIsEmailSignup) { - mPhotoUrl = StringUtils.notNullStr(savedInstanceState.getString(KEY_PHOTO_URL)) - mEmailAddress = StringUtils.notNullStr(savedInstanceState.getString(KEY_EMAIL_ADDRESS)) - mHeaderEmailAddress.text = mEmailAddress - mHeaderAvatarAdd.visibility = if (mIsAvatarAdded) View.GONE else View.VISIBLE - } - mImageManager.loadIntoCircle( - mHeaderAvatar, - ImageType.AVATAR_WITHOUT_BACKGROUND, - mPhotoUrl - ) - - mIsUpdatingDisplayName = savedInstanceState.getBoolean(KEY_IS_UPDATING_DISPLAY_NAME) - mIsUpdatingPassword = savedInstanceState.getBoolean(KEY_IS_UPDATING_PASSWORD) - mHasUpdatedPassword = savedInstanceState.getBoolean(KEY_HAS_UPDATED_PASSWORD) - mHasMadeUpdates = savedInstanceState.getBoolean(KEY_HAS_MADE_UPDATES) - } - } - - @Suppress("TooGenericExceptionThrown") - override fun onAttach(context: Context) { - super.onAttach(context) - - if (context is SignupEpilogueListener) { - mSignupEpilogueListener = context - } else { - throw RuntimeException("$context must implement SignupEpilogueListener") - } - } - - override fun onConfirm(result: Bundle?) { - if (result != null) { - mUsername = result.getString(BaseUsernameChangerFullScreenDialogFragment.RESULT_USERNAME) - mEditTextUsername.setText(mUsername) - } - } - - override fun onDismiss() { - val props: MutableMap = HashMap() - props[SOURCE] = SOURCE_SIGNUP_EPILOGUE - AnalyticsTracker.track(Stat.CHANGE_USERNAME_DISMISSED, props) - mDialog = null - } - - override fun onShown() { - val props: MutableMap = HashMap() - props[SOURCE] = SOURCE_SIGNUP_EPILOGUE - AnalyticsTracker.track(Stat.CHANGE_USERNAME_DISPLAYED, props) - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putString(KEY_PHOTO_URL, mPhotoUrl) - outState.putString(KEY_DISPLAY_NAME, mDisplayName) - outState.putString(KEY_EMAIL_ADDRESS, mEmailAddress) - outState.putString(KEY_USERNAME, mUsername) - outState.putBoolean(KEY_IS_AVATAR_ADDED, mIsAvatarAdded) - outState.putBoolean(KEY_IS_UPDATING_DISPLAY_NAME, mIsUpdatingDisplayName) - outState.putBoolean(KEY_IS_UPDATING_PASSWORD, mIsUpdatingPassword) - outState.putBoolean(KEY_HAS_UPDATED_PASSWORD, mHasUpdatedPassword) - outState.putBoolean(KEY_HAS_MADE_UPDATES, mHasMadeUpdates) - } - - @Suppress("EmptyFunctionBlock") - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { - } - - @Suppress("EmptyFunctionBlock") - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - } - - @Suppress("EmptyFunctionBlock") - override fun afterTextChanged(s: Editable) { - } - - @Suppress("EmptyFunctionBlock") - override fun onHelp() { - } - - override fun onLoginFinished() { - endProgress() - } - - @Suppress("unused") - @Subscribe(threadMode = ThreadMode.MAIN) - override fun onAccountChanged(event: OnAccountChanged) { - if (event.isError) { - if (mIsUpdatingDisplayName) { - mIsUpdatingDisplayName = false - AnalyticsTracker.track( - if (mIsEmailSignup - ) Stat.SIGNUP_EMAIL_EPILOGUE_UPDATE_DISPLAY_NAME_FAILED - else Stat.SIGNUP_SOCIAL_EPILOGUE_UPDATE_DISPLAY_NAME_FAILED - ) - } else if (mIsUpdatingPassword) { - mIsUpdatingPassword = false - } - - AppLog.e( - AppLog.T.API, ("SignupEpilogueFragment.onAccountChanged: " - + event.error.type + " - " + event.error.message) - ) - endProgress() - - if (isPasswordInErrorMessage(event.error.message)) { - showErrorDialogWithCloseButton(event.error.message) - } else { - showErrorDialog(getString(R.string.signup_epilogue_error_generic)) - } - // Wait to populate epilogue for email interface until account is fetched and email address - // is available since flow is coming from magic link with no instance argument values. - } else if (mIsEmailSignup && (event.causeOfChange == AccountAction.FETCH_ACCOUNT - ) && !TextUtils.isEmpty(mAccountStore.account.email) - ) { - endProgress() - populateViews() - } else if (event.causeOfChange == AccountAction.PUSH_SETTINGS) { - mHasMadeUpdates = true - - if (mIsUpdatingDisplayName) { - mIsUpdatingDisplayName = false - AnalyticsTracker.track( - if (mIsEmailSignup - ) Stat.SIGNUP_EMAIL_EPILOGUE_UPDATE_DISPLAY_NAME_SUCCEEDED - else Stat.SIGNUP_SOCIAL_EPILOGUE_UPDATE_DISPLAY_NAME_SUCCEEDED - ) - } else if (mIsUpdatingPassword) { - mIsUpdatingPassword = false - mHasUpdatedPassword = true - } - - updateAccountOrContinue() - } - } - - @Suppress("unused") - @Subscribe(threadMode = ThreadMode.MAIN) - fun onUsernameChanged(event: OnUsernameChanged) { - if (event.isError) { - AnalyticsTracker.track( - if (mIsEmailSignup - ) Stat.SIGNUP_EMAIL_EPILOGUE_UPDATE_USERNAME_FAILED - else Stat.SIGNUP_SOCIAL_EPILOGUE_UPDATE_USERNAME_FAILED - ) - AppLog.e( - AppLog.T.API, ("SignupEpilogueFragment.onUsernameChanged: " - + event.error.type + " - " + event.error.message) - ) - endProgress() - showErrorDialog(getString(R.string.signup_epilogue_error_generic)) - } else { - mHasMadeUpdates = true - AnalyticsTracker.track( - if (mIsEmailSignup - ) Stat.SIGNUP_EMAIL_EPILOGUE_UPDATE_USERNAME_SUCCEEDED - else Stat.SIGNUP_SOCIAL_EPILOGUE_UPDATE_USERNAME_SUCCEEDED - ) - updateAccountOrContinue() - } - } - - private fun changedDisplayName(): Boolean { - return !TextUtils.equals(mAccount.account.displayName, mDisplayName) - } - - private fun changedPassword(): Boolean { - return !TextUtils.isEmpty(mInputPassword.editText.text.toString()) - } - - private fun changedUsername(): Boolean { - return !TextUtils.equals(mAccount.account.userName, mUsername) - } - - private fun isPasswordInErrorMessage(message: String): Boolean { - val lowercaseMessage = message.lowercase(Locale.getDefault()) - val lowercasePassword = getString(R.string.password).lowercase(Locale.getDefault()) - return lowercaseMessage.contains(lowercasePassword) - } - - private fun launchDialog() { - AnalyticsTracker.track( - if (mIsEmailSignup - ) Stat.SIGNUP_EMAIL_EPILOGUE_USERNAME_TAPPED - else Stat.SIGNUP_SOCIAL_EPILOGUE_USERNAME_TAPPED - ) - - val bundle: Bundle = BaseUsernameChangerFullScreenDialogFragment.newBundle( - mEditTextDisplayName.text.toString(), mEditTextUsername.text.toString() - ) - - mDialog = FullScreenDialogFragment.Builder(requireContext()) - .setTitle(R.string.username_changer_title) - .setAction(R.string.username_changer_action) - .setToolbarTheme(org.wordpress.android.login.R.style.ThemeOverlay_LoginFlow_Toolbar) - .setOnConfirmListener(this) - .setOnDismissListener(this) - .setOnShownListener(this) - .setContent(UsernameChangerFullScreenDialogFragment::class.java, bundle) - .build() - - mDialog?.show(requireActivity().supportFragmentManager, FullScreenDialogFragment.TAG) - } - - private fun populateViews() { - mEmailAddress = mAccountStore.account.email - mDisplayName = mSignupUtils.createDisplayNameFromEmail(mEmailAddress) - mUsername = if (!TextUtils.isEmpty(mAccountStore.account.userName) - ) mAccountStore.account.userName else mSignupUtils.createUsernameFromEmail(mEmailAddress) - mHeaderDisplayName.text = mDisplayName - mHeaderEmailAddress.text = mEmailAddress - mEditTextDisplayName.setText(mDisplayName) - mEditTextUsername.setText(mUsername) - // Set fragment arguments to know if account should be updated when values change. - val args = Bundle() - args.putString(ARG_DISPLAY_NAME, mDisplayName) - args.putString(ARG_EMAIL_ADDRESS, mEmailAddress) - args.putString(ARG_PHOTO_URL, mPhotoUrl) - args.putString(ARG_USERNAME, mUsername) - args.putBoolean(ARG_IS_EMAIL_SIGNUP, mIsEmailSignup) - arguments = args - } - - private fun showErrorDialog(message: String?) { - val dialogListener: DialogInterface.OnClickListener = - DialogInterface.OnClickListener { _, which -> - when (which) { - DialogInterface.BUTTON_NEGATIVE -> undoChanges() - DialogInterface.BUTTON_POSITIVE -> updateAccountOrContinue() - } - } - - val dialog = MaterialAlertDialogBuilder(requireActivity()) - .setMessage(message) - .setNeutralButton(R.string.login_error_button, dialogListener) - .setNegativeButton(R.string.signup_epilogue_error_button_negative, dialogListener) - .setPositiveButton(R.string.signup_epilogue_error_button_positive, dialogListener) - .create() - dialog.show() - } - - private fun showErrorDialogWithCloseButton(message: String?) { - val dialog = MaterialAlertDialogBuilder(requireActivity()) - .setMessage(message) - .setPositiveButton(R.string.login_error_button, null) - .create() - dialog.show() - } - - @SuppressLint("SetTextI18n") - private fun undoChanges() { - mDisplayName = if (!TextUtils.isEmpty(mAccountStore.account.displayName) - ) mAccountStore.account.displayName else requireArguments().getString(ARG_DISPLAY_NAME) - mEditTextDisplayName.setText(mDisplayName) - mUsername = if (!TextUtils.isEmpty(mAccountStore.account.userName) - ) mAccountStore.account.userName else requireArguments().getString(ARG_USERNAME) - mEditTextUsername.setText(mUsername) - mInputPassword.editText.setText("") - updateAccountOrContinue() - } - - private fun updateAccountOrContinue() { - if (changedUsername()) { - startProgressIfNeeded() - updateUsername() - } else if (changedDisplayName()) { - startProgressIfNeeded() - mIsUpdatingDisplayName = true - updateDisplayName() - } else if (changedPassword() && !mHasUpdatedPassword) { - startProgressIfNeeded() - mIsUpdatingPassword = true - updatePassword() - } else if (mSignupEpilogueListener != null) { - if (!mHasMadeUpdates) { - AnalyticsTracker.track( - if (mIsEmailSignup - ) Stat.SIGNUP_EMAIL_EPILOGUE_UNCHANGED - else Stat.SIGNUP_SOCIAL_EPILOGUE_UNCHANGED - ) - } - endProgressIfNeeded() - mSignupEpilogueListener!!.onContinue() - } - } - - private fun updateDisplayName() { - val payload = PushAccountSettingsPayload() - payload.params = HashMap() - payload.params["display_name"] = mDisplayName - dispatcher.dispatch(AccountActionBuilder.newPushSettingsAction(payload)) - } - - private fun updatePassword() { - val payload = PushAccountSettingsPayload() - payload.params = HashMap() - payload.params["password"] = mInputPassword.editText.text.toString() - dispatcher.dispatch(AccountActionBuilder.newPushSettingsAction(payload)) - } - - private fun updateUsername() { - mUsername?.let { - val payload = PushUsernamePayload(it, AccountUsernameActionType.KEEP_OLD_SITE_AND_ADDRESS) - dispatcher.dispatch(AccountActionBuilder.newPushUsernameAction(payload)) - } - } - - private inner class DownloadAvatarAndUploadGravatarThread( - private val mUrl: String, - private val mEmail: String, - private val mToken: String - ) : Thread() { - override fun run() { - @Suppress("TooGenericExceptionCaught") - try { - val uri = MediaUtils.downloadExternalMedia(context, mUrl.toUri()) - val file = File(URI(uri.toString())) - lifecycleScope.launch { - when (val result = mAvatarService.uploadCatching(file, mToken, Email(mEmail).hash(), true)) { - is GravatarResult.Success -> { - AppLog.i( - AppLog.T.NUX, - "Google avatar download and Gravatar upload succeeded." - ) - AnalyticsTracker.track(Stat.ME_GRAVATAR_UPLOADED) - } - - is GravatarResult.Failure -> { - AppLog.i( - AppLog.T.NUX, - "Google avatar download and Gravatar upload failed." - ) - val properties: MutableMap = HashMap() - properties["error_type"] = result.error - AnalyticsTracker.track(Stat.ME_GRAVATAR_UPLOAD_EXCEPTION, properties) - } - } - } - } catch (exception: NullPointerException) { - AppLog.e( - AppLog.T.NUX, ("Google avatar download and Gravatar upload failed - " - + exception.toString() + " - " + exception.message) - ) - } catch (exception: URISyntaxException) { - AppLog.e( - AppLog.T.NUX, ("Google avatar download and Gravatar upload failed - " - + exception.toString() + " - " + exception.message) - ) - } - } - } - - companion object { - private const val ARG_DISPLAY_NAME = "ARG_DISPLAY_NAME" - private const val ARG_EMAIL_ADDRESS = "ARG_EMAIL_ADDRESS" - private const val ARG_IS_EMAIL_SIGNUP = "ARG_IS_EMAIL_SIGNUP" - private const val ARG_PHOTO_URL = "ARG_PHOTO_URL" - private const val ARG_USERNAME = "ARG_USERNAME" - private const val KEY_DISPLAY_NAME = "KEY_DISPLAY_NAME" - private const val KEY_EMAIL_ADDRESS = "KEY_EMAIL_ADDRESS" - private const val KEY_IS_AVATAR_ADDED = "KEY_IS_AVATAR_ADDED" - private const val KEY_PHOTO_URL = "KEY_PHOTO_URL" - private const val KEY_USERNAME = "KEY_USERNAME" - private const val KEY_IS_UPDATING_DISPLAY_NAME = "KEY_IS_UPDATING_DISPLAY_NAME" - private const val KEY_IS_UPDATING_PASSWORD = "KEY_IS_UPDATING_PASSWORD" - private const val KEY_HAS_UPDATED_PASSWORD = "KEY_HAS_UPDATED_PASSWORD" - private const val KEY_HAS_MADE_UPDATES = "KEY_HAS_MADE_UPDATES" - - private const val SOURCE = "source" - private const val SOURCE_SIGNUP_EPILOGUE = "signup_epilogue" - - const val TAG: String = "signup_epilogue_fragment_tag" - - fun newInstance( - displayName: String?, emailAddress: String?, - photoUrl: String?, username: String?, - isEmailSignup: Boolean - ): SignupEpilogueFragment { - val signupEpilogueFragment = SignupEpilogueFragment() - val args = Bundle() - args.putString(ARG_DISPLAY_NAME, displayName) - args.putString(ARG_EMAIL_ADDRESS, emailAddress) - args.putString(ARG_PHOTO_URL, photoUrl) - args.putString(ARG_USERNAME, username) - args.putBoolean(ARG_IS_EMAIL_SIGNUP, isEmailSignup) - signupEpilogueFragment.arguments = args - return signupEpilogueFragment - } - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/signup/SignupEpilogueListener.java b/WordPress/src/main/java/org/wordpress/android/ui/accounts/signup/SignupEpilogueListener.java deleted file mode 100644 index aa83251c8a0f..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/signup/SignupEpilogueListener.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.wordpress.android.ui.accounts.signup; - -public interface SignupEpilogueListener { - void onContinue(); -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/signup/SignupUtils.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/signup/SignupUtils.kt deleted file mode 100644 index afe9eb6ecb8a..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/signup/SignupUtils.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.wordpress.android.ui.accounts.signup - -import java.util.Locale -import javax.inject.Inject - -class SignupUtils @Inject constructor() { - /** - * Create a display name from the email address by taking everything before the "@" symbol, - * removing all non-letters and non-periods, replacing periods with spaces, and capitalizing - * the first letter of each word. - * - * @return [String] to be the display name - */ - fun createDisplayNameFromEmail(emailAddress: String): String? { - val username = emailAddress.split("@").firstOrNull() ?: return null - val usernameArray = username.replace("[^A-Za-z/.]".toRegex(), "").split(".") - val builder = StringBuilder() - for (item in usernameArray) { - if (item.isNotEmpty()) { - builder.append(item.substring(0, 1).uppercase(Locale.ROOT)) - if (item.length > 1) { - builder.append(item.substring(1)) - } - builder.append(" ") - } - } - val displayName = builder.toString().trim() - return if (displayName.isNotEmpty()) displayName else null - } - - /** - * Create a username from the email address by taking everything before the "@" symbol and - * removing all non-alphanumeric characters. - * - * @return [String] to be the username - */ - fun createUsernameFromEmail(emailAddress: String): String? { - return emailAddress.split("@".toRegex())[0] - .replace("[^A-Za-z0-9]".toRegex(), "") - .lowercase(Locale.ROOT) - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/signup/UsernameChangerFullScreenDialogFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/signup/UsernameChangerFullScreenDialogFragment.kt deleted file mode 100644 index c0f5da2259e4..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/signup/UsernameChangerFullScreenDialogFragment.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.wordpress.android.ui.accounts.signup - -import android.os.Bundle -import android.text.Spanned -import androidx.core.text.HtmlCompat -import dagger.hilt.android.AndroidEntryPoint -import org.wordpress.android.R -import org.wordpress.android.analytics.AnalyticsTracker.Stat.SIGNUP_SOCIAL_EPILOGUE_USERNAME_SUGGESTIONS_FAILED -import org.wordpress.android.ui.FullScreenDialogFragment.FullScreenDialogController - -/** - * Implements functionality specific to the Username Changer functionality in the sign-up flow. - */ -@AndroidEntryPoint -class UsernameChangerFullScreenDialogFragment : BaseUsernameChangerFullScreenDialogFragment() { - override fun getSuggestionsFailedStat() = SIGNUP_SOCIAL_EPILOGUE_USERNAME_SUGGESTIONS_FAILED - override fun canHeaderTextLiveUpdate() = true - override fun getHeaderText(username: String?, display: String?): Spanned = HtmlCompat.fromHtml( - String.format( - getString(R.string.username_changer_header), - "", - username, - "", - "", - display, - "" - ), - HtmlCompat.FROM_HTML_MODE_LEGACY - ) - - override fun getTrackEventSource() = SOURCE - - override fun onUsernameConfirmed(controller: FullScreenDialogController, usernameSelected: String) { - val result = Bundle().apply { putString(RESULT_USERNAME, usernameSelected) } - controller.confirm(result) - } - - companion object { - const val SOURCE = "signup_epilogue" - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/compose/components/ColumnWithFrostedGlassBackground.kt b/WordPress/src/main/java/org/wordpress/android/ui/compose/components/ColumnWithFrostedGlassBackground.kt index 2aac348b33f3..2f4f0ed73cbb 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/compose/components/ColumnWithFrostedGlassBackground.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/compose/components/ColumnWithFrostedGlassBackground.kt @@ -5,6 +5,7 @@ import android.os.Build.VERSION_CODES import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -41,7 +42,10 @@ fun ColumnWithTopGlassBorder( content: @Composable ColumnScope.() -> Unit ) { Column( - modifier = Modifier.background(backgroundColor), horizontalAlignment = Alignment.CenterHorizontally + modifier = Modifier + .background(backgroundColor) + .navigationBarsPadding(), + horizontalAlignment = Alignment.CenterHorizontally ) { HorizontalDivider( thickness = borderThickness, diff --git a/WordPress/src/main/java/org/wordpress/android/ui/compose/components/TrainOfIcons.kt b/WordPress/src/main/java/org/wordpress/android/ui/compose/components/TrainOfIcons.kt index 70b1fdf46de1..394de4f1defe 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/compose/components/TrainOfIcons.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/compose/components/TrainOfIcons.kt @@ -117,9 +117,9 @@ fun TrainOfIconsPreview() { AppThemeM3 { TrainOfIcons( iconModels = listOf( - R.drawable.login_prologue_second_asset_three, - R.drawable.login_prologue_second_asset_two, - R.drawable.login_prologue_third_asset_one, + R.mipmap.app_icon, + R.mipmap.app_icon, + R.mipmap.app_icon, R.mipmap.app_icon ).map { TrainOfIconsModel(it) }, contentDescription = "Train of icons", diff --git a/WordPress/src/main/java/org/wordpress/android/ui/jetpackplugininstall/remoteplugin/JetpackRemoteInstallActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/jetpackplugininstall/remoteplugin/JetpackRemoteInstallActivity.kt index 00216ae422ba..180fd03812f9 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/jetpackplugininstall/remoteplugin/JetpackRemoteInstallActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/jetpackplugininstall/remoteplugin/JetpackRemoteInstallActivity.kt @@ -11,7 +11,7 @@ import dagger.hilt.android.AndroidEntryPoint import org.wordpress.android.R import org.wordpress.android.WordPress import org.wordpress.android.fluxc.model.SiteModel -import org.wordpress.android.login.LoginMode +import org.wordpress.android.ui.accounts.LoginFlow import org.wordpress.android.ui.ActivityLauncher import org.wordpress.android.ui.JetpackConnectionSource import org.wordpress.android.ui.JetpackConnectionWebViewActivity @@ -101,7 +101,7 @@ class JetpackRemoteInstallActivity : BaseAppCompatActivity() { source: JetpackConnectionSource ) { val loginIntent = Intent(this, LoginActivity::class.java) - LoginMode.JETPACK_STATS.putInto(loginIntent) + LoginFlow.JETPACK_STATS.putInto(loginIntent) loginIntent.putExtra(LoginActivity.ARG_JETPACK_CONNECT_SOURCE, source) startActivityForResult(loginIntent, RequestCodes.JETPACK_LOGIN) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/layoutpicker/CategoryViewHolder.kt b/WordPress/src/main/java/org/wordpress/android/ui/layoutpicker/CategoryViewHolder.kt index a0da31144b20..25a48f0fc8eb 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/layoutpicker/CategoryViewHolder.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/layoutpicker/CategoryViewHolder.kt @@ -4,7 +4,7 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import org.wordpress.android.databinding.ModalLayoutPickerCategoryBinding -import org.wordpress.android.login.util.getColorStateListFromAttribute +import org.wordpress.android.util.extensions.getColorStateListFromAttribute import org.wordpress.android.util.extensions.getColorFromAttribute import org.wordpress.android.util.extensions.setVisible diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/BaseAppCompatActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/main/BaseAppCompatActivity.kt index dc4ae3530506..cb85ba25354a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/BaseAppCompatActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/BaseAppCompatActivity.kt @@ -38,6 +38,8 @@ import org.wordpress.android.ui.reader.ReaderSubsActivity import org.wordpress.android.ui.selfhostedusers.SelfHostedUsersActivity import org.wordpress.android.ui.sitemonitor.SiteMonitorParentActivity import org.wordpress.android.ui.subscribers.SubscribersActivity +import org.wordpress.android.ui.accounts.LoginActivity +import org.wordpress.android.ui.accounts.applicationpassword.ApplicationPasswordLoginActivity import org.wordpress.android.ui.taxonomies.TermsDataViewActivity /** @@ -107,6 +109,8 @@ private val excludedActivities = listOf( SupportWebViewActivity::class.java.name, // these are excluded because they explicitly enable edge-to-edge + ApplicationPasswordLoginActivity::class.java.name, + LoginActivity::class.java.name, MediaSettingsActivity::class.java.name, ReaderPostPagerActivity::class.java.name, 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 f46f1bf92ba4..95f17d0d9449 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 @@ -46,6 +46,8 @@ import org.wordpress.android.fluxc.store.SiteStore import org.wordpress.android.fluxc.utils.AppLogWrapper import org.wordpress.android.models.JetpackPoweredScreen import org.wordpress.android.ui.ActivityLauncher +import org.wordpress.android.ui.ActivityNavigator +import org.wordpress.android.ui.accounts.login.WPcomLoginHelper import org.wordpress.android.ui.about.UnifiedAboutActivity import org.wordpress.android.ui.accounts.HelpActivity.Origin.ME_SCREEN_HELP import org.wordpress.android.ui.debug.DebugSettingsActivity @@ -139,6 +141,12 @@ class MeFragment : Fragment(R.layout.me_fragment), OnScrollToTopListener { @Inject lateinit var appLogWrapper: AppLogWrapper + @Inject + lateinit var activityNavigator: ActivityNavigator + + @Inject + lateinit var wpcomLoginHelper: WPcomLoginHelper + private val viewModel: MeViewModel by viewModels() private val emailVerificationViewModel: EmailVerificationViewModel by viewModels() @@ -222,10 +230,8 @@ class MeFragment : Fragment(R.layout.me_fragment), OnScrollToTopListener { if (accountStore.hasAccessToken()) { signOutWordPressComWithConfirmation() } else { - if (BuildConfig.IS_JETPACK_APP) { - ActivityLauncher.showSignInForResultJetpackOnly(activity) - } else { - ActivityLauncher.showSignInForResultWpComOnly(activity) + activity?.let { + activityNavigator.openInCustomTab(it, wpcomLoginHelper.wpcomLoginUri.toString()) } } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java index 0e040cde4f00..12ffcf18cbbd 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java @@ -10,7 +10,6 @@ import android.os.Handler; import android.os.Looper; import android.text.TextUtils; -import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.View; import android.widget.TextView; @@ -42,7 +41,6 @@ import org.wordpress.android.fluxc.Dispatcher; import org.wordpress.android.fluxc.generated.AccountActionBuilder; import org.wordpress.android.fluxc.generated.SiteActionBuilder; -import org.wordpress.android.fluxc.model.AccountModel; import org.wordpress.android.fluxc.model.PostModel; import org.wordpress.android.fluxc.model.SiteModel; import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.WpAppNotifierHandler; @@ -62,7 +60,7 @@ import org.wordpress.android.fluxc.store.SiteStore.OnSiteRemoved; import org.wordpress.android.inappupdate.IInAppUpdateManager; import org.wordpress.android.inappupdate.InAppUpdateListener; -import org.wordpress.android.login.LoginAnalyticsListener; +import org.wordpress.android.ui.accounts.login.LoginAnalyticsListener; import org.wordpress.android.networking.ConnectionChangeReceiver; import org.wordpress.android.push.GCMMessageHandler; import org.wordpress.android.push.GCMMessageService; @@ -80,7 +78,6 @@ import org.wordpress.android.ui.Shortcut; import org.wordpress.android.ui.ShortcutsNavigator; import org.wordpress.android.ui.accounts.LoginActivity; -import org.wordpress.android.ui.accounts.SignupEpilogueActivity; import org.wordpress.android.ui.bloggingprompts.onboarding.BloggingPromptsOnboardingDialogFragment; import org.wordpress.android.ui.bloggingprompts.onboarding.BloggingPromptsOnboardingDialogFragment.DialogType; import org.wordpress.android.ui.bloggingprompts.onboarding.BloggingPromptsReminderSchedulerListener; @@ -177,7 +174,6 @@ import static androidx.lifecycle.Lifecycle.State.STARTED; import static org.wordpress.android.WordPress.SITE; -import static org.wordpress.android.login.LoginAnalyticsListener.CreatedAccountSource.EMAIL; import static org.wordpress.android.push.NotificationsProcessingService.ARG_NOTIFICATION_TYPE; import static org.wordpress.android.ui.JetpackConnectionSource.NOTIFICATIONS; @@ -195,10 +191,8 @@ public class WPMainActivity extends BaseAppCompatActivity implements public static final String ARG_CONTINUE_JETPACK_CONNECT = "ARG_CONTINUE_JETPACK_CONNECT"; public static final String ARG_CREATE_SITE = "ARG_CREATE_SITE"; public static final String ARG_IS_MAGIC_LINK_LOGIN = "ARG_IS_MAGIC_LINK_LOGIN"; - public static final String ARG_IS_MAGIC_LINK_SIGNUP = "ARG_IS_MAGIC_LINK_SIGNUP"; public static final String ARG_JETPACK_CONNECT_SOURCE = "ARG_JETPACK_CONNECT_SOURCE"; public static final String ARG_OPENED_FROM_PUSH = "opened_from_push"; - public static final String ARG_SHOW_SIGNUP_EPILOGUE = "show_signup_epilogue"; public static final String ARG_SHOW_SITE_CREATION = "show_site_creation"; public static final String ARG_SITE_CREATION_SOURCE = "ARG_SITE_CREATION_SOURCE"; public static final String ARG_WP_COM_SIGN_UP = "sign_up"; @@ -218,6 +212,7 @@ public class WPMainActivity extends BaseAppCompatActivity implements public static final String ARG_DISMISS_NOTIFICATION = "dismiss_notification"; public static final String ARG_OPEN_BLOGGING_REMINDERS = "show_blogging_reminders_flow"; public static final String ARG_SELECTED_SITE = "SELECTED_SITE_ID"; + public static final String ARG_SELECT_PRIMARY_SITE = "SELECT_PRIMARY_SITE"; public static final String ARG_STAT_TO_TRACK = "stat_to_track"; public static final String ARG_EDITOR_ORIGIN = "editor_origin"; public static final String ARG_IS_CHANGING_CONFIGURATION = "IS_CHANGING_CONFIGURATION"; @@ -231,7 +226,6 @@ public class WPMainActivity extends BaseAppCompatActivity implements private TextView mConnectionBar; private JetpackConnectionSource mJetpackConnectSource; private boolean mIsMagicLinkLogin; - private boolean mIsMagicLinkSignup; private WPMainActivityViewModel mViewModel; private ModalLayoutPickerViewModel mMLPViewModel; @@ -323,7 +317,6 @@ public void onCreate(@Nullable Bundle savedInstanceState) { }); mIsMagicLinkLogin = getIntent().getBooleanExtra(ARG_IS_MAGIC_LINK_LOGIN, false); - mIsMagicLinkSignup = getIntent().getBooleanExtra(ARG_IS_MAGIC_LINK_SIGNUP, false); mJetpackConnectSource = (JetpackConnectionSource) getIntent().getSerializableExtra(ARG_JETPACK_CONNECT_SOURCE); String authTokenToSet = null; @@ -424,12 +417,6 @@ && getIntent().getExtras().getBoolean(ARG_CONTINUE_JETPACK_CONNECT, false)) { // Save Token to the AccountStore. This will trigger a onAuthenticationChanged. UpdateTokenPayload payload = new UpdateTokenPayload(authTokenToSet); mDispatcher.dispatch(AccountActionBuilder.newUpdateAccessTokenAction(payload)); - } else if (getIntent().getBooleanExtra(ARG_SHOW_SIGNUP_EPILOGUE, false) && savedInstanceState == null) { - ActivityLauncher.showSignupEpilogue(this, - getIntent().getStringExtra(SignupEpilogueActivity.EXTRA_SIGNUP_DISPLAY_NAME), - getIntent().getStringExtra(SignupEpilogueActivity.EXTRA_SIGNUP_EMAIL_ADDRESS), - getIntent().getStringExtra(SignupEpilogueActivity.EXTRA_SIGNUP_PHOTO_URL), - getIntent().getStringExtra(SignupEpilogueActivity.EXTRA_SIGNUP_USERNAME), false); } else if (getIntent().getBooleanExtra(ARG_SHOW_SITE_CREATION, false) && savedInstanceState == null) { ActivityLauncher.newBlogForResult(this, SiteCreationSource.fromString(getIntent().getStringExtra(ARG_SITE_CREATION_SOURCE))); @@ -452,6 +439,12 @@ && getIntent().getExtras().getBoolean(ARG_CONTINUE_JETPACK_CONNECT, false)) { onSetPromptReminderClick(getIntent().getIntExtra(ARG_OPEN_BLOGGING_REMINDERS, 0)); } + // Check if we should select the primary site (e.g., after WP.com login) + if (getIntent().getBooleanExtra(ARG_SELECT_PRIMARY_SITE, false)) { + getIntent().removeExtra(ARG_SELECT_PRIMARY_SITE); + mSelectedSiteRepository.removeSite(); + initSelectedSite(); + } // Check if a specific site should be selected (e.g., after adding a self-hosted site) int selectedSiteId = getIntent().getIntExtra(ARG_SELECTED_SITE, SelectedSiteRepository.UNAVAILABLE); if (selectedSiteId != SelectedSiteRepository.UNAVAILABLE) { @@ -1267,7 +1260,6 @@ private void setSite(Intent data) { @Override @SuppressWarnings("deprecation") public void onActivityResult(int requestCode, int resultCode, Intent data) { - Log.e("WPMainActivity", "onActivityResult: " + requestCode + " " + resultCode); super.onActivityResult(requestCode, resultCode, data); if (!mSelectedSiteRepository.hasSelectedSite()) { initSelectedSite(); @@ -1450,25 +1442,12 @@ public void onAuthenticationChanged(OnAuthenticationChanged event) { if (mAccountStore.hasAccessToken()) { if (mIsMagicLinkLogin) { - if (mIsMagicLinkSignup) { - // Sets a flag that we need to track a magic link sign up. - // We'll handle it in onAccountChanged so we know we have - // updated account info. - AppPrefs.setShouldTrackMagicLinkSignup(true); - mDispatcher.dispatch(AccountActionBuilder.newFetchAccountAction()); - if (mJetpackConnectSource != null) { - ActivityLauncher.continueJetpackConnect(this, mJetpackConnectSource, getSelectedSite()); - } else { - ActivityLauncher.showSignupEpilogue(this, null, null, null, null, true); - } - } else { - mLoginAnalyticsListener.trackLoginMagicLinkSucceeded(); + mLoginAnalyticsListener.trackLoginMagicLinkSucceeded(); - if (mJetpackConnectSource != null) { - ActivityLauncher.continueJetpackConnect(this, mJetpackConnectSource, getSelectedSite()); - } else { - initSelectedSite(); - } + if (mJetpackConnectSource != null) { + ActivityLauncher.continueJetpackConnect(this, mJetpackConnectSource, getSelectedSite()); + } else { + initSelectedSite(); } } } @@ -1480,27 +1459,9 @@ public void onAccountChanged(OnAccountChanged event) { // Sign-out is handled in `handleSiteRemoved`, no need to show the signup flow here if (mAccountStore.hasAccessToken()) { if (mBottomNav != null) mBottomNav.showNoteBadge(mAccountStore.getAccount().getHasUnseenNotes()); - if (AppPrefs.getShouldTrackMagicLinkSignup()) { - trackMagicLinkSignupIfNeeded(); - } - } - } - - /** - * Bumps stats related to a magic link sign up provided the account has been updated with - * the username and email address needed to refresh analytics meta data. - */ - private void trackMagicLinkSignupIfNeeded() { - AccountModel account = mAccountStore.getAccount(); - if (!TextUtils.isEmpty(account.getUserName()) && !TextUtils.isEmpty(account.getEmail())) { - mLoginAnalyticsListener.trackCreatedAccount(account.getUserName(), account.getEmail(), EMAIL); - mLoginAnalyticsListener.trackSignupMagicLinkSucceeded(); - mLoginAnalyticsListener.trackAnalyticsSignIn(true); - AppPrefs.removeShouldTrackMagicLinkSignup(); } } - @SuppressWarnings("unused") @Subscribe(threadMode = ThreadMode.MAIN) public void onEventMainThread(NotificationEvents.NotificationsChanged event) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt index 4769b3ccda67..a72bdec7ebad 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.wordpress.android.R import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider import org.wordpress.android.fluxc.store.SiteStore import org.wordpress.android.fluxc.utils.AppLogWrapper import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper @@ -19,6 +20,8 @@ import org.wordpress.android.ui.utils.ListItemInteraction import org.wordpress.android.ui.utils.UiString.UiStringRes import org.wordpress.android.util.AppLog import org.wordpress.android.viewmodel.Event +import rs.wordpress.api.kotlin.WpRequestResult +import uniffi.wp_api.RequestExecutionErrorReason import javax.inject.Inject class ApplicationPasswordViewModelSlice @Inject constructor( @@ -26,6 +29,7 @@ class ApplicationPasswordViewModelSlice @Inject constructor( private val siteStore: SiteStore, private val experimentalFeatures: ExperimentalFeatures, private val appLogWrapper: AppLogWrapper, + private val wpApiClientProvider: WpApiClientProvider, ) { lateinit var scope: CoroutineScope @@ -39,10 +43,18 @@ class ApplicationPasswordViewModelSlice @Inject constructor( private val _onSnackbarMessage = MutableLiveData>() val onSnackbarMessage = _onSnackbarMessage - val uiModelMutable = MutableLiveData() - val uiModel: LiveData = uiModelMutable + val uiModelMutable = MutableLiveData() + val uiModel: LiveData = uiModelMutable fun buildCard(siteModel: SiteModel) { + val storedSite = siteStore.sites.firstOrNull { it.id == siteModel.id } + + // For sites using application passwords, validate credentials + if (storedSite != null && storedSite.isUsingSelfHostedRestApi) { + validateCredentialsAndBuildCard(storedSite) + return + } + if (shouldBuildCard()) { buildApplicationPasswordDiscovery(siteModel) } else { @@ -51,9 +63,109 @@ class ApplicationPasswordViewModelSlice @Inject constructor( } } + @Suppress("TooGenericExceptionCaught", "LongMethod") + private fun validateCredentialsAndBuildCard(site: SiteModel) { + appLogWrapper.d(AppLog.T.MAIN, "A_P: Validating credentials for ${site.url}") + appLogWrapper.d(AppLog.T.MAIN, "A_P: isUsingSelfHostedRestApi: ${site.isUsingSelfHostedRestApi}") + appLogWrapper.d(AppLog.T.MAIN, "A_P: hasApiRestUsername: ${!site.apiRestUsernamePlain.isNullOrEmpty()}") + appLogWrapper.d(AppLog.T.MAIN, "A_P: hasApiRestPassword: ${!site.apiRestPasswordPlain.isNullOrEmpty()}") + appLogWrapper.d(AppLog.T.MAIN, "A_P: wpApiRestUrl: ${site.wpApiRestUrl}") + appLogWrapper.d(AppLog.T.MAIN, "A_P: origin: ${site.origin}") + + // If credentials are missing, show reauthentication banner immediately + if (applicationPasswordLoginHelper.siteHasBadCredentials(site)) { + appLogWrapper.d(AppLog.T.MAIN, "A_P: Credentials missing for ${site.url}, showing banner") + buildReauthenticationBanner(site) + return + } + + // Validate credentials by making a simple API call + scope.launch { + try { + appLogWrapper.d(AppLog.T.MAIN, "A_P: Making API call to validate credentials") + val client = wpApiClientProvider.getWpApiClient(site) + val response = client.request { requestBuilder -> + requestBuilder.users().retrieveMeWithViewContext() + } + appLogWrapper.d(AppLog.T.MAIN, "A_P: API response type: ${response::class.simpleName}") + when (response) { + is WpRequestResult.Success -> { + // Credentials are valid, hide the card + uiModelMutable.postValue(null) + appLogWrapper.d(AppLog.T.MAIN, "A_P: Credentials valid for ${site.url}") + } + is WpRequestResult.WpError -> { + appLogWrapper.d(AppLog.T.MAIN, "A_P: WpError for ${site.url}: ${response.response}") + buildReauthenticationBanner(site) + } + is WpRequestResult.UnknownError -> { + appLogWrapper.d( + AppLog.T.MAIN, + "A_P: UnknownError for ${site.url}: code=${response.statusCode}, msg=${response.response}" + ) + buildReauthenticationBanner(site) + } + is WpRequestResult.RequestExecutionFailed -> { + val isTimeout = response.reason is RequestExecutionErrorReason.HttpTimeoutError + if (isTimeout) { + appLogWrapper.d(AppLog.T.MAIN, "A_P: Request timed out for ${site.url}") + } else { + appLogWrapper.d( + AppLog.T.MAIN, + "A_P: RequestExecutionFailed for ${site.url}: " + + "reason=${response.reason}, statusCode=${response.statusCode}" + ) + } + // Don't show reauthentication banner for timeouts - it's likely a network issue + if (!isTimeout) { + buildReauthenticationBanner(site) + } else { + uiModelMutable.postValue(null) + } + } + else -> { + // Credentials are invalid, show reauthentication banner + appLogWrapper.d(AppLog.T.MAIN, "A_P: Other error for ${site.url}: $response") + buildReauthenticationBanner(site) + } + } + } catch (e: Exception) { + appLogWrapper.e( + AppLog.T.MAIN, + "A_P: Exception validating credentials for ${site.url}: ${e::class.simpleName}: ${e.message}" + ) + e.printStackTrace() + uiModelMutable.postValue(null) + } + } + } + private fun shouldBuildCard(): Boolean = experimentalFeatures.isEnabled(Feature.EXPERIMENTAL_APPLICATION_PASSWORD_FEATURE) + private fun buildReauthenticationBanner(site: SiteModel) { + scope.launch { + val authorizationUrlComplete = applicationPasswordLoginHelper.getAuthorizationUrlComplete(site.url) + if (authorizationUrlComplete.isEmpty()) { + uiModelMutable.postValue(null) + appLogWrapper.d(AppLog.T.MAIN, "A_P: Hiding reauthentication card for ${site.url} - bad discovery") + } else { + showReauthenticationCard(site, authorizationUrlComplete) + } + } + } + + private fun showReauthenticationCard(site: SiteModel, alternativeUrl: String) { + uiModelMutable.postValue( + MySiteCardAndItem.Item.SingleActionCard( + textResource = R.string.application_password_reauthentication_banner, + imageResource = R.drawable.ic_notice_white_24dp, + onActionClick = { onClick(site, alternativeUrl) } + ) + ) + appLogWrapper.d(AppLog.T.MAIN, "A_P: Showing reauthentication card for ${site.url}") + } + private fun buildApplicationPasswordDiscovery(site: SiteModel) { scope.launch { // If the site is already authorized, no need to run the discovery diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragmentPage.kt b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragmentPage.kt index 4289888e9228..f4011cf2f422 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragmentPage.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragmentPage.kt @@ -80,6 +80,7 @@ class NotificationsListFragmentPage : ViewPagerFragment(R.layout.notifications_l private lateinit var notesAdapter: NotesAdapter private var swipeToRefreshHelper: SwipeToRefreshHelper? = null private var isAnimatingOutNewNotificationsBar = false + private var isFetchingRemoteNotes = false private var tabPosition = 0 private val viewModel: NotificationsListViewModel by viewModels() @@ -171,7 +172,14 @@ class NotificationsListFragmentPage : ViewPagerFragment(R.layout.notifications_l binding?.apply { if (itemsCount > 0) { hideEmptyView() + progressLoading.visibility = View.GONE + } else if (isFetchingRemoteNotes) { + // Show loading spinner while fetching and list is empty + progressLoading.visibility = View.VISIBLE + actionableEmptyView.visibility = View.GONE + notificationsList.visibility = View.GONE } else { + progressLoading.visibility = View.GONE showEmptyViewForCurrentFilter() } } @@ -185,6 +193,8 @@ class NotificationsListFragmentPage : ViewPagerFragment(R.layout.notifications_l super.onResume() binding?.hideNewNotificationsBar() EventBus.getDefault().post(NotificationsUnseenStatus(false)) + // Fetch remote notes when resumed (e.g., after login or returning to this screen) + fetchRemoteNotes() } override fun onSaveInstanceState(outState: Bundle) { @@ -268,8 +278,11 @@ class NotificationsListFragmentPage : ViewPagerFragment(R.layout.notifications_l } if (!NetworkUtils.isNetworkAvailable(activity)) { swipeToRefreshHelper?.isRefreshing = false + isFetchingRemoteNotes = false + updateEmptyLayouts(notesAdapter.itemCount) return } + isFetchingRemoteNotes = true swipeToRefreshHelper?.isRefreshing = true NotificationsUpdateServiceStarter.startService(activity) } @@ -488,6 +501,7 @@ class NotificationsListFragmentPage : ViewPagerFragment(R.layout.notifications_l if (!isAdded) { return } + isFetchingRemoteNotes = false swipeToRefreshHelper?.isRefreshing = false notesAdapter.addAll(event.notes) } @@ -496,7 +510,9 @@ class NotificationsListFragmentPage : ViewPagerFragment(R.layout.notifications_l @Subscribe(threadMode = MAIN) fun onEventMainThread(error: NotificationsRefreshError?) { if (isAdded) { + isFetchingRemoteNotes = false swipeToRefreshHelper?.isRefreshing = false + updateEmptyLayouts(notesAdapter.itemCount) } } 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 967107aec596..e22ee7a1ad46 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 @@ -109,9 +109,6 @@ public enum DeletablePrefKey implements PrefKey { // Used to flag whether the app should strip geolocation from images STRIP_IMAGE_LOCATION, - // Used to flag the account created stat needs to be bumped after account information is synced. - SHOULD_TRACK_MAGIC_LINK_SIGNUP, - // Support email address and name that's independent of any account or site SUPPORT_EMAIL, SUPPORT_NAME, @@ -196,6 +193,11 @@ public enum DeletablePrefKey implements PrefKey { READER_READING_PREFERENCES_JSON, SHOULD_SHOW_READER_ANNOUNCEMENT_CARD, STATS_CARDS_CONFIGURATION_JSON, + + // Login flow preserved across OAuth Custom Tabs redirect + PENDING_LOGIN_FLOW, + // Whether a share flow is pending (for self-hosted login) + IS_SHARE_FLOW_PENDING, } /** @@ -255,9 +257,6 @@ public enum UndeletablePrefKey implements PrefKey { // indicates whether the system notifications are enabled for the app SYSTEM_NOTIFICATIONS_ENABLED, - // Used to indicate whether or not the the post-signup interstitial must be shown - SHOULD_SHOW_POST_SIGNUP_INTERSTITIAL, - // used to indicate that we do not need to show the main FAB tooltip IS_MAIN_FAB_TOOLTIP_DISABLED, @@ -987,18 +986,6 @@ public static void setLastWpComThemeSync(long time) { setLong(UndeletablePrefKey.LAST_WP_COM_THEMES_SYNC, time); } - public static void setShouldTrackMagicLinkSignup(Boolean shouldTrack) { - setBoolean(DeletablePrefKey.SHOULD_TRACK_MAGIC_LINK_SIGNUP, shouldTrack); - } - - public static boolean getShouldTrackMagicLinkSignup() { - return getBoolean(DeletablePrefKey.SHOULD_TRACK_MAGIC_LINK_SIGNUP, false); - } - - public static void removeShouldTrackMagicLinkSignup() { - remove(DeletablePrefKey.SHOULD_TRACK_MAGIC_LINK_SIGNUP); - } - public static void setMainFabTooltipDisabled(Boolean disable) { setBoolean(UndeletablePrefKey.IS_MAIN_FAB_TOOLTIP_DISABLED, disable); } @@ -1113,14 +1100,6 @@ public static boolean getSystemNotificationsEnabled() { return getBoolean(UndeletablePrefKey.SYSTEM_NOTIFICATIONS_ENABLED, true); } - public static void setShouldShowPostSignupInterstitial(boolean shouldShow) { - setBoolean(UndeletablePrefKey.SHOULD_SHOW_POST_SIGNUP_INTERSTITIAL, shouldShow); - } - - public static boolean shouldShowPostSignupInterstitial() { - return getBoolean(UndeletablePrefKey.SHOULD_SHOW_POST_SIGNUP_INTERSTITIAL, true); - } - private static List getPostWithHWAccelerationOff() { String idsAsString = getString(DeletablePrefKey.AZTEC_EDITOR_DISABLE_HW_ACC_KEYS, ""); return Arrays.asList(idsAsString.split(",")); @@ -1817,4 +1796,26 @@ public static int getTrackNetworkRequestsRetentionPeriod() { public static void setTrackNetworkRequestsRetentionPeriod(int period) { setInt(UndeletablePrefKey.TRACK_NETWORK_REQUESTS_RETENTION_PERIOD, period); } + + @Nullable + public static String getPendingLoginFlow() { + String value = getString(DeletablePrefKey.PENDING_LOGIN_FLOW); + return value.isEmpty() ? null : value; + } + + public static void setPendingLoginFlow(@Nullable String flowName) { + setString(DeletablePrefKey.PENDING_LOGIN_FLOW, flowName); + } + + public static boolean consumeShareFlowPending() { + boolean result = getBoolean( + DeletablePrefKey.IS_SHARE_FLOW_PENDING, false + ); + setBoolean(DeletablePrefKey.IS_SHARE_FLOW_PENDING, false); + return result; + } + + public static void setShareFlowPending(boolean pending) { + setBoolean(DeletablePrefKey.IS_SHARE_FLOW_PENDING, pending); + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefsWrapper.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefsWrapper.kt index 2706800222e8..9d1e5d6d7dbc 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefsWrapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppPrefsWrapper.kt @@ -56,10 +56,6 @@ class AppPrefsWrapper @Inject constructor(val buildConfigWrapper: BuildConfigWra get() = AppPrefs.getSystemNotificationsEnabled() set(value) = AppPrefs.setSystemNotificationsEnabled(value) - var shouldShowPostSignupInterstitial: Boolean - get() = AppPrefs.shouldShowPostSignupInterstitial() - set(shouldShow) = AppPrefs.setShouldShowPostSignupInterstitial(shouldShow) - var readerTagsUpdatedTimestamp: Long get() = AppPrefs.getReaderTagsUpdatedTimestamp() set(timestamp) = AppPrefs.setReaderTagsUpdatedTimestamp(timestamp) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java index 454c8e008f2c..5657f3a850b6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java @@ -2099,8 +2099,13 @@ private void removeEmptyCategories() { } private void removeNonSelfHostedPreferences() { - mUsernamePref.setEnabled(true); - mPasswordPref.setEnabled(true); + // Hide username/password preferences for sites using application passwords + if (mSite.isUsingSelfHostedRestApi()) { + WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_site_account); + } else { + mUsernamePref.setEnabled(true); + mPasswordPref.setEnabled(true); + } PreferenceGroup group = (PreferenceGroup) findPreference(getString(R.string.pref_key_site_general)); if (group != null) { group.removeAll(); diff --git a/WordPress/src/main/java/org/wordpress/android/util/AuthenticationDialogUtils.java b/WordPress/src/main/java/org/wordpress/android/util/AuthenticationDialogUtils.java index e2af45953d26..36e6db2137b7 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/AuthenticationDialogUtils.java +++ b/WordPress/src/main/java/org/wordpress/android/util/AuthenticationDialogUtils.java @@ -7,7 +7,7 @@ import org.wordpress.android.fluxc.model.SiteModel; import org.wordpress.android.fluxc.store.SiteStore; -import org.wordpress.android.login.LoginMode; +import org.wordpress.android.ui.accounts.LoginFlow; import org.wordpress.android.ui.ActivityLauncher; import org.wordpress.android.ui.RequestCodes; import org.wordpress.android.ui.accounts.LoginActivity; @@ -38,7 +38,7 @@ public static void showAuthErrorView(AppCompatActivity activity, SiteStore siteS } else { // only WPCOM sites are available so, need to ask the user to log in again Intent intent = new Intent(activity, LoginActivity.class); - LoginMode.WPCOM_REAUTHENTICATE.putInto(intent); + LoginFlow.WPCOM_REAUTHENTICATE.putInto(intent); activity.startActivityForResult(intent, RequestCodes.REAUTHENTICATE); } diff --git a/WordPress/src/main/java/org/wordpress/android/util/BuildConfigWrapper.kt b/WordPress/src/main/java/org/wordpress/android/util/BuildConfigWrapper.kt index 5911cf39ca0b..a4d455b8b890 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/BuildConfigWrapper.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/BuildConfigWrapper.kt @@ -27,8 +27,6 @@ class BuildConfigWrapper @Inject constructor() { val isSiteCreationEnabled = BuildConfig.ENABLE_SITE_CREATION - val isSignupEnabled = BuildConfig.ENABLE_SIGNUP - val isCreateFabEnabled = BuildConfig.ENABLE_CREATE_FAB val isFollowedSitesSettingsEnabled = BuildConfig.ENABLE_FOLLOWED_SITES_SETTINGS diff --git a/WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java b/WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java index c94fe407feab..a16b615d0b94 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java +++ b/WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java @@ -26,8 +26,18 @@ import org.wordpress.android.util.image.ImageType; import java.util.ArrayList; +import java.util.List; public class SiteUtils { + public static ArrayList getCurrentSiteIds(SiteStore siteStore, boolean selfhostedOnly) { + ArrayList siteIDs = new ArrayList<>(); + List sites = selfhostedOnly ? siteStore.getSitesAccessedViaXMLRPC() : siteStore.getSites(); + for (SiteModel site : sites) { + siteIDs.add(site.getId()); + } + return siteIDs; + } + public static final String GB_EDITOR_NAME = "gutenberg"; public static final String AZTEC_EDITOR_NAME = "aztec"; public static final String WP_VIDEOPRESS_V5_JETPACK_VERSION = "8.5"; diff --git a/WordPress/src/main/java/org/wordpress/android/util/image/ImagePlaceholderManager.kt b/WordPress/src/main/java/org/wordpress/android/util/image/ImagePlaceholderManager.kt index 7e20ac25412c..f85b52586049 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/image/ImagePlaceholderManager.kt +++ b/WordPress/src/main/java/org/wordpress/android/util/image/ImagePlaceholderManager.kt @@ -4,7 +4,6 @@ import org.wordpress.android.R import javax.inject.Inject import javax.inject.Singleton import org.wordpress.android.editor.R as EditorR -import org.wordpress.android.login.R as LoginR @Singleton class ImagePlaceholderManager @Inject constructor() { @@ -13,7 +12,7 @@ class ImagePlaceholderManager @Inject constructor() { return when (imgType) { ImageType.AVATAR -> R.drawable.bg_rectangle_placeholder_user_32dp ImageType.AVATAR_WITH_BACKGROUND -> R.drawable.bg_oval_placeholder_user_32dp - ImageType.AVATAR_WITHOUT_BACKGROUND -> LoginR.drawable.ic_user_circle_no_padding_grey_24dp + ImageType.AVATAR_WITHOUT_BACKGROUND -> R.drawable.ic_user_placeholder_primary_24 ImageType.BLAVATAR -> R.drawable.bg_rectangle_placeholder_globe_32dp ImageType.P2_BLAVATAR -> R.drawable.bg_rectangle_placeholder_p2_32dp ImageType.BLAVATAR_ROUNDED_CORNERS -> R.drawable.bg_rectangle_placeholder_radius_4dp_globe_32dp @@ -39,7 +38,7 @@ class ImagePlaceholderManager @Inject constructor() { return when (imgType) { ImageType.AVATAR -> R.drawable.bg_oval_placeholder ImageType.AVATAR_WITH_BACKGROUND -> R.drawable.bg_oval_placeholder_user_32dp - ImageType.AVATAR_WITHOUT_BACKGROUND -> LoginR.drawable.ic_user_circle_no_padding_grey_24dp + ImageType.AVATAR_WITHOUT_BACKGROUND -> R.drawable.ic_user_placeholder_primary_24 ImageType.BLAVATAR -> R.color.placeholder ImageType.P2_BLAVATAR -> R.color.placeholder ImageType.BLAVATAR_ROUNDED_CORNERS -> R.drawable.bg_rectangle_placeholder_radius_4dp diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/accounts/PostSignupInterstitialViewModel.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/accounts/PostSignupInterstitialViewModel.kt deleted file mode 100644 index 8f02a73cbf49..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/accounts/PostSignupInterstitialViewModel.kt +++ /dev/null @@ -1,93 +0,0 @@ -package org.wordpress.android.viewmodel.accounts - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import org.wordpress.android.analytics.AnalyticsTracker.Stat.WELCOME_NO_SITES_INTERSTITIAL_ADD_SELF_HOSTED_SITE_TAPPED -import org.wordpress.android.analytics.AnalyticsTracker.Stat.WELCOME_NO_SITES_INTERSTITIAL_CREATE_NEW_SITE_TAPPED -import org.wordpress.android.analytics.AnalyticsTracker.Stat.WELCOME_NO_SITES_INTERSTITIAL_DISMISSED -import org.wordpress.android.analytics.AnalyticsTracker.Stat.WELCOME_NO_SITES_INTERSTITIAL_SHOWN -import org.wordpress.android.ui.accounts.UnifiedLoginTracker -import org.wordpress.android.ui.accounts.UnifiedLoginTracker.Click -import org.wordpress.android.ui.accounts.UnifiedLoginTracker.Step.SUCCESS -import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalPhaseHelper -import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginHelper -import org.wordpress.android.ui.prefs.AppPrefsWrapper -import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper -import org.wordpress.android.viewmodel.SingleLiveEvent -import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.DISMISS -import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.DISMISS_FOR_JETPACK_REMOVAL -import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY -import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.START_SITE_CONNECTION_FLOW -import org.wordpress.android.viewmodel.accounts.PostSignupInterstitialViewModel.NavigationAction.START_SITE_CREATION_FLOW -import javax.inject.Inject - -class PostSignupInterstitialViewModel -@Inject constructor( - private val appPrefs: AppPrefsWrapper, - private val unifiedLoginTracker: UnifiedLoginTracker, - private val analyticsTracker: AnalyticsTrackerWrapper, - private val wpJetpackIndividualPluginHelper: WPJetpackIndividualPluginHelper, - private val jetpackFeatureRemovalPhaseHelper: JetpackFeatureRemovalPhaseHelper -) : ViewModel() { - val navigationAction: SingleLiveEvent = SingleLiveEvent() - - fun onInterstitialShown() { - analyticsTracker.track(WELCOME_NO_SITES_INTERSTITIAL_SHOWN) - unifiedLoginTracker.track(step = SUCCESS) - appPrefs.shouldShowPostSignupInterstitial = false - checkJetpackIndividualPluginOverlayShouldShow() - } - - fun onCreateNewSiteButtonPressed() { - analyticsTracker.track(WELCOME_NO_SITES_INTERSTITIAL_CREATE_NEW_SITE_TAPPED) - unifiedLoginTracker.trackClick(Click.CREATE_NEW_SITE) - navigationAction.value = START_SITE_CREATION_FLOW - } - - fun onAddSelfHostedSiteButtonPressed() { - analyticsTracker.track(WELCOME_NO_SITES_INTERSTITIAL_ADD_SELF_HOSTED_SITE_TAPPED) - unifiedLoginTracker.trackClick(Click.ADD_SELF_HOSTED_SITE) - navigationAction.value = START_SITE_CONNECTION_FLOW - } - - fun onDismissButtonPressed() = onDismiss() - - fun onBackButtonPressed() = onDismiss() - - private fun onDismiss() { - unifiedLoginTracker.trackClick(Click.DISMISS) - analyticsTracker.track(WELCOME_NO_SITES_INTERSTITIAL_DISMISSED) - if (jetpackFeatureRemovalPhaseHelper.shouldRemoveJetpackFeatures()) { - navigationAction.value = DISMISS_FOR_JETPACK_REMOVAL - } else { - navigationAction.value = DISMISS - } - } - - private fun checkJetpackIndividualPluginOverlayShouldShow() { - // don't check if already shown - if (navigationAction.value == SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY) return - - viewModelScope.launch { - val showOverlay = wpJetpackIndividualPluginHelper.shouldShowJetpackIndividualPluginOverlay() - if (showOverlay) { - delay(DELAY_BEFORE_SHOWING_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY) - navigationAction.postValue(SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY) - } - } - } - - enum class NavigationAction { - START_SITE_CREATION_FLOW, - START_SITE_CONNECTION_FLOW, - DISMISS, - SHOW_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY, - DISMISS_FOR_JETPACK_REMOVAL - } - - companion object { - private const val DELAY_BEFORE_SHOWING_JETPACK_INDIVIDUAL_PLUGIN_OVERLAY = 500L - } -} diff --git a/libs/login/src/main/res/color/login_on_surface_high_selector.xml b/WordPress/src/main/res/color/login_on_surface_high_selector.xml similarity index 100% rename from libs/login/src/main/res/color/login_on_surface_high_selector.xml rename to WordPress/src/main/res/color/login_on_surface_high_selector.xml diff --git a/libs/login/src/main/res/color/login_on_surface_medium_selector.xml b/WordPress/src/main/res/color/login_on_surface_medium_selector.xml similarity index 100% rename from libs/login/src/main/res/color/login_on_surface_medium_selector.xml rename to WordPress/src/main/res/color/login_on_surface_medium_selector.xml diff --git a/WordPress/src/main/res/drawable-hdpi/img_illustration_promo.png b/WordPress/src/main/res/drawable-hdpi/img_illustration_promo.png deleted file mode 100644 index e5e62aba855c..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/img_illustration_promo.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_five.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_five.png deleted file mode 100644 index fe6fcbacc93f..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_five.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_four.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_four.png deleted file mode 100644 index 7d3a131285a3..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_four.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_one.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_one.png deleted file mode 100644 index 378f027f16f8..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_six.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_six.png deleted file mode 100644 index e722b9372bdc..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_six.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_three.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_three.png deleted file mode 100644 index f0730fb0762d..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_two.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_two.png deleted file mode 100644 index 221bac71732f..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_fifth_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_five.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_five.png deleted file mode 100644 index 656bb642da5d..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_five.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_four.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_four.png deleted file mode 100644 index 658b8fe6f472..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_four.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_one.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_one.png deleted file mode 100644 index fe6ee2a1c5c9..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_seven.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_seven.png deleted file mode 100644 index fcc82a240ecf..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_seven.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_six.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_six.png deleted file mode 100644 index 87b2c3f09439..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_six.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_three.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_three.png deleted file mode 100644 index ae271a0fe876..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_two.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_two.png deleted file mode 100644 index bc0272a5038d..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_first_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_second_asset_one.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_second_asset_one.png deleted file mode 100644 index 3c81de6a78cd..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_second_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_second_asset_three.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_second_asset_three.png deleted file mode 100644 index 77727f7e9c98..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_second_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_second_asset_two.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_second_asset_two.png deleted file mode 100644 index 444b756bee32..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_second_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_third_asset_one.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_third_asset_one.png deleted file mode 100644 index fd3144183779..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_third_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_third_asset_three.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_third_asset_three.png deleted file mode 100644 index ae86bb2040b9..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_third_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-hdpi/login_prologue_third_asset_two.png b/WordPress/src/main/res/drawable-hdpi/login_prologue_third_asset_two.png deleted file mode 100644 index 727e679981b5..000000000000 Binary files a/WordPress/src/main/res/drawable-hdpi/login_prologue_third_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/img_illustration_promo.png b/WordPress/src/main/res/drawable-mdpi/img_illustration_promo.png deleted file mode 100644 index 1f86c080625a..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/img_illustration_promo.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_five.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_five.png deleted file mode 100644 index db5a9b63d11f..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_five.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_four.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_four.png deleted file mode 100644 index fd43f72603ed..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_four.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_one.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_one.png deleted file mode 100644 index c2567f3dd056..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_six.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_six.png deleted file mode 100644 index bea1b1c5a8f3..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_six.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_three.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_three.png deleted file mode 100644 index 2d8119ceed01..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_two.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_two.png deleted file mode 100644 index dd37a291f01c..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_fifth_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_five.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_five.png deleted file mode 100644 index 47440243420f..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_five.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_four.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_four.png deleted file mode 100644 index 87116f57752b..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_four.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_one.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_one.png deleted file mode 100644 index 799f17e92f03..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_seven.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_seven.png deleted file mode 100644 index edbd6fb9903a..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_seven.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_six.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_six.png deleted file mode 100644 index 2e3a963f67fc..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_six.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_three.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_three.png deleted file mode 100644 index 9578f306a4cb..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_two.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_two.png deleted file mode 100644 index 7bb1d3539c9d..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_first_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_second_asset_one.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_second_asset_one.png deleted file mode 100644 index d1effea4be85..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_second_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_second_asset_three.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_second_asset_three.png deleted file mode 100644 index 3942ff44ccfc..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_second_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_second_asset_two.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_second_asset_two.png deleted file mode 100644 index efe735909b46..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_second_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_third_asset_one.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_third_asset_one.png deleted file mode 100644 index f490890475da..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_third_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_third_asset_three.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_third_asset_three.png deleted file mode 100644 index 91c7ba4899e2..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_third_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-mdpi/login_prologue_third_asset_two.png b/WordPress/src/main/res/drawable-mdpi/login_prologue_third_asset_two.png deleted file mode 100644 index e01a91acba64..000000000000 Binary files a/WordPress/src/main/res/drawable-mdpi/login_prologue_third_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-night/login_prologue_fourth_asset_one.xml b/WordPress/src/main/res/drawable-night/login_prologue_fourth_asset_one.xml deleted file mode 100644 index ab7a44340156..000000000000 --- a/WordPress/src/main/res/drawable-night/login_prologue_fourth_asset_one.xml +++ /dev/null @@ -1,484 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/src/main/res/drawable-night/login_prologue_fourth_asset_two.xml b/WordPress/src/main/res/drawable-night/login_prologue_fourth_asset_two.xml deleted file mode 100644 index d355d02ca79b..000000000000 --- a/WordPress/src/main/res/drawable-night/login_prologue_fourth_asset_two.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - diff --git a/WordPress/src/main/res/drawable-xhdpi/img_illustration_promo.png b/WordPress/src/main/res/drawable-xhdpi/img_illustration_promo.png deleted file mode 100644 index d867c8448493..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/img_illustration_promo.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_five.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_five.png deleted file mode 100644 index 3975229b9a10..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_five.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_four.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_four.png deleted file mode 100644 index dcb256b7ea9e..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_four.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_one.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_one.png deleted file mode 100644 index 5984dc439d6d..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_six.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_six.png deleted file mode 100644 index 470a52f63e2b..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_six.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_three.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_three.png deleted file mode 100644 index 1c523db52028..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_two.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_two.png deleted file mode 100644 index d0284d1fcad6..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_fifth_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_five.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_five.png deleted file mode 100644 index 0754c9a5321b..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_five.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_four.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_four.png deleted file mode 100644 index 8840ca9ded06..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_four.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_one.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_one.png deleted file mode 100644 index c5b37590dcb6..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_seven.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_seven.png deleted file mode 100644 index 8bb370b31f91..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_seven.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_six.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_six.png deleted file mode 100644 index 9c60eed24224..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_six.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_three.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_three.png deleted file mode 100644 index d7270f119746..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_two.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_two.png deleted file mode 100644 index c4f3982bca1c..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_first_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_second_asset_one.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_second_asset_one.png deleted file mode 100644 index 96441b8e1c0e..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_second_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_second_asset_three.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_second_asset_three.png deleted file mode 100644 index a5790afe35a1..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_second_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_second_asset_two.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_second_asset_two.png deleted file mode 100644 index 76591bf118bc..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_second_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_third_asset_one.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_third_asset_one.png deleted file mode 100644 index e006ea2e9576..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_third_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_third_asset_three.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_third_asset_three.png deleted file mode 100644 index 4668ffcb8635..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_third_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xhdpi/login_prologue_third_asset_two.png b/WordPress/src/main/res/drawable-xhdpi/login_prologue_third_asset_two.png deleted file mode 100644 index e4930ed1c1ed..000000000000 Binary files a/WordPress/src/main/res/drawable-xhdpi/login_prologue_third_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/img_illustration_promo.png b/WordPress/src/main/res/drawable-xxhdpi/img_illustration_promo.png deleted file mode 100644 index 325f1c5ee21e..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/img_illustration_promo.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_five.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_five.png deleted file mode 100644 index b6dfad71523c..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_five.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_four.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_four.png deleted file mode 100644 index fd33c63f75da..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_four.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_one.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_one.png deleted file mode 100644 index 62bdfba468ba..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_six.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_six.png deleted file mode 100644 index fe27cbb43aa1..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_six.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_three.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_three.png deleted file mode 100644 index 02d5303f0e03..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_two.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_two.png deleted file mode 100644 index 3e2d162453e1..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_fifth_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_five.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_five.png deleted file mode 100644 index 359d7519474e..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_five.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_four.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_four.png deleted file mode 100644 index cbeeb1f7d339..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_four.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_one.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_one.png deleted file mode 100644 index b0bee1521bf2..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_seven.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_seven.png deleted file mode 100644 index 141a16e3f420..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_seven.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_six.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_six.png deleted file mode 100644 index 0430212a0d91..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_six.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_three.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_three.png deleted file mode 100644 index 38378e09ed79..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_two.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_two.png deleted file mode 100644 index 939feb92fab2..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_first_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_second_asset_one.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_second_asset_one.png deleted file mode 100644 index 07f152aa91e3..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_second_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_second_asset_three.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_second_asset_three.png deleted file mode 100644 index 2cb233583add..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_second_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_second_asset_two.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_second_asset_two.png deleted file mode 100644 index 76182003cacb..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_second_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_third_asset_one.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_third_asset_one.png deleted file mode 100644 index 6f8734bb3b65..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_third_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_third_asset_three.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_third_asset_three.png deleted file mode 100644 index 3c69158b5bdb..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_third_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_third_asset_two.png b/WordPress/src/main/res/drawable-xxhdpi/login_prologue_third_asset_two.png deleted file mode 100644 index 1ad613c1dfcb..000000000000 Binary files a/WordPress/src/main/res/drawable-xxhdpi/login_prologue_third_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/img_illustration_promo.png b/WordPress/src/main/res/drawable-xxxhdpi/img_illustration_promo.png deleted file mode 100644 index 402376b2a82a..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/img_illustration_promo.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_five.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_five.png deleted file mode 100644 index 2a87b73f6e2f..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_five.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_four.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_four.png deleted file mode 100644 index 6b4d48270cc6..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_four.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_one.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_one.png deleted file mode 100644 index d2aaa78944c8..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_six.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_six.png deleted file mode 100644 index 415adc6227dd..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_six.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_three.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_three.png deleted file mode 100644 index ff636b1266a6..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_two.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_two.png deleted file mode 100644 index c36a6e71470f..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_fifth_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_five.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_five.png deleted file mode 100644 index c84cb44de9b8..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_five.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_four.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_four.png deleted file mode 100644 index e57841192272..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_four.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_one.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_one.png deleted file mode 100644 index f7c97a8d177d..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_seven.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_seven.png deleted file mode 100644 index 5f3e13dc7d61..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_seven.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_six.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_six.png deleted file mode 100644 index aebe834fc8c1..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_six.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_three.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_three.png deleted file mode 100644 index 6336fb3578f4..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_two.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_two.png deleted file mode 100644 index a7e290ef04e9..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_first_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_second_asset_one.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_second_asset_one.png deleted file mode 100644 index 7812b049a7c3..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_second_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_second_asset_three.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_second_asset_three.png deleted file mode 100644 index 3da14af82bee..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_second_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_second_asset_two.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_second_asset_two.png deleted file mode 100644 index 2870492aa57c..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_second_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_third_asset_one.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_third_asset_one.png deleted file mode 100644 index e9537bd1d712..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_third_asset_one.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_third_asset_three.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_third_asset_three.png deleted file mode 100644 index 15c362e495e3..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_third_asset_three.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_third_asset_two.png b/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_third_asset_two.png deleted file mode 100644 index 366194281c65..000000000000 Binary files a/WordPress/src/main/res/drawable-xxxhdpi/login_prologue_third_asset_two.png and /dev/null differ diff --git a/WordPress/src/main/res/drawable/bg_oval_translucent_stroke_3dp.xml b/WordPress/src/main/res/drawable/bg_oval_translucent_stroke_3dp.xml deleted file mode 100644 index 8c1fa24d6515..000000000000 --- a/WordPress/src/main/res/drawable/bg_oval_translucent_stroke_3dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/WordPress/src/main/res/drawable/ic_align_image_left_white_24dp.xml b/WordPress/src/main/res/drawable/ic_align_image_left_white_24dp.xml deleted file mode 100644 index af90770c31f8..000000000000 --- a/WordPress/src/main/res/drawable/ic_align_image_left_white_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/WordPress/src/main/res/drawable/ic_status_white_24dp.xml b/WordPress/src/main/res/drawable/ic_status_white_24dp.xml deleted file mode 100644 index b9d875bc6575..000000000000 --- a/WordPress/src/main/res/drawable/ic_status_white_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/WordPress/src/main/res/drawable/login_prologue_fourth_asset_one.xml b/WordPress/src/main/res/drawable/login_prologue_fourth_asset_one.xml deleted file mode 100644 index bfe7c10fd96a..000000000000 --- a/WordPress/src/main/res/drawable/login_prologue_fourth_asset_one.xml +++ /dev/null @@ -1,484 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/src/main/res/drawable/login_prologue_fourth_asset_two.xml b/WordPress/src/main/res/drawable/login_prologue_fourth_asset_two.xml deleted file mode 100644 index aef62ba2b97b..000000000000 --- a/WordPress/src/main/res/drawable/login_prologue_fourth_asset_two.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - diff --git a/WordPress/src/main/res/layout-land/login_intro_template_view.xml b/WordPress/src/main/res/layout-land/login_intro_template_view.xml deleted file mode 100644 index a5d39702089e..000000000000 --- a/WordPress/src/main/res/layout-land/login_intro_template_view.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - diff --git a/WordPress/src/main/res/layout-land/login_prologue_bottom_buttons_container.xml b/WordPress/src/main/res/layout-land/login_prologue_bottom_buttons_container.xml deleted file mode 100644 index 343c65560c3d..000000000000 --- a/WordPress/src/main/res/layout-land/login_prologue_bottom_buttons_container.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - diff --git a/WordPress/src/main/res/layout-land/post_signup_interstitial_activity.xml b/WordPress/src/main/res/layout-land/post_signup_interstitial_activity.xml deleted file mode 100644 index f0e486ca355c..000000000000 --- a/WordPress/src/main/res/layout-land/post_signup_interstitial_activity.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/WordPress/src/main/res/layout-sw600dp/login_prologue_bottom_buttons_container.xml b/WordPress/src/main/res/layout-sw600dp/login_prologue_bottom_buttons_container.xml deleted file mode 100644 index 435c29e2ab88..000000000000 --- a/WordPress/src/main/res/layout-sw600dp/login_prologue_bottom_buttons_container.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - diff --git a/WordPress/src/main/res/layout-sw600dp/post_signup_interstitial_activity.xml b/WordPress/src/main/res/layout-sw600dp/post_signup_interstitial_activity.xml deleted file mode 100644 index 5f6f9d759687..000000000000 --- a/WordPress/src/main/res/layout-sw600dp/post_signup_interstitial_activity.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/WordPress/src/main/res/layout/help_activity.xml b/WordPress/src/main/res/layout/help_activity.xml index 7469bef0e6ff..3e5376a60bb8 100644 --- a/WordPress/src/main/res/layout/help_activity.xml +++ b/WordPress/src/main/res/layout/help_activity.xml @@ -53,7 +53,7 @@ android:layout_height="@dimen/avatar_sz_large" android:layout_centerHorizontal="true" android:contentDescription="@string/reader_avatar_desc" - android:src="@drawable/ic_user_circle_no_padding_grey_24dp" /> + android:src="@drawable/ic_user_placeholder_primary_24" /> + tools:srcCompat="@drawable/ic_user_placeholder_primary_24" /> - - - - diff --git a/WordPress/src/main/res/layout/login_include_epilogue_header.xml b/WordPress/src/main/res/layout/login_include_epilogue_header.xml index 7b1ef6039d27..d8c1224c75b3 100644 --- a/WordPress/src/main/res/layout/login_include_epilogue_header.xml +++ b/WordPress/src/main/res/layout/login_include_epilogue_header.xml @@ -21,7 +21,7 @@ android:layout_width="@dimen/avatar_sz_login_epilogue" android:layout_height="@dimen/avatar_sz_login_epilogue" android:importantForAccessibility="no" - android:src="@drawable/ic_user_circle_no_padding_grey_24dp" /> + android:src="@drawable/ic_user_placeholder_primary_24" /> - - - - - - - - diff --git a/WordPress/src/main/res/layout/login_loading.xml b/WordPress/src/main/res/layout/login_loading.xml new file mode 100644 index 000000000000..b0d703224a53 --- /dev/null +++ b/WordPress/src/main/res/layout/login_loading.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + diff --git a/WordPress/src/main/res/layout/login_prologue_background_fifth.xml b/WordPress/src/main/res/layout/login_prologue_background_fifth.xml deleted file mode 100644 index 02e303999d85..000000000000 --- a/WordPress/src/main/res/layout/login_prologue_background_fifth.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/src/main/res/layout/login_prologue_background_first.xml b/WordPress/src/main/res/layout/login_prologue_background_first.xml deleted file mode 100644 index de23ee57d66a..000000000000 --- a/WordPress/src/main/res/layout/login_prologue_background_first.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/src/main/res/layout/login_prologue_background_fourth.xml b/WordPress/src/main/res/layout/login_prologue_background_fourth.xml deleted file mode 100644 index 75542a525301..000000000000 --- a/WordPress/src/main/res/layout/login_prologue_background_fourth.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/src/main/res/layout/login_prologue_background_second.xml b/WordPress/src/main/res/layout/login_prologue_background_second.xml deleted file mode 100644 index 62c0b795e3f2..000000000000 --- a/WordPress/src/main/res/layout/login_prologue_background_second.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/src/main/res/layout/login_prologue_background_third.xml b/WordPress/src/main/res/layout/login_prologue_background_third.xml deleted file mode 100644 index 2c50bc599958..000000000000 --- a/WordPress/src/main/res/layout/login_prologue_background_third.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/src/main/res/layout/login_prologue_bottom_buttons_container.xml b/WordPress/src/main/res/layout/login_prologue_bottom_buttons_container.xml deleted file mode 100644 index 9c5efb86cae4..000000000000 --- a/WordPress/src/main/res/layout/login_prologue_bottom_buttons_container.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - diff --git a/WordPress/src/main/res/layout/login_prologue_fifth.xml b/WordPress/src/main/res/layout/login_prologue_fifth.xml deleted file mode 100644 index e6c9e4d5ef57..000000000000 --- a/WordPress/src/main/res/layout/login_prologue_fifth.xml +++ /dev/null @@ -1,268 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/src/main/res/layout/login_prologue_first.xml b/WordPress/src/main/res/layout/login_prologue_first.xml deleted file mode 100644 index 345d412cfbac..000000000000 --- a/WordPress/src/main/res/layout/login_prologue_first.xml +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/src/main/res/layout/login_prologue_fourth.xml b/WordPress/src/main/res/layout/login_prologue_fourth.xml deleted file mode 100644 index 1c5e5da642cd..000000000000 --- a/WordPress/src/main/res/layout/login_prologue_fourth.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/src/main/res/layout/login_prologue_second.xml b/WordPress/src/main/res/layout/login_prologue_second.xml deleted file mode 100644 index ebc7dfad5920..000000000000 --- a/WordPress/src/main/res/layout/login_prologue_second.xml +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/src/main/res/layout/login_prologue_third.xml b/WordPress/src/main/res/layout/login_prologue_third.xml deleted file mode 100644 index ee5e9154be32..000000000000 --- a/WordPress/src/main/res/layout/login_prologue_third.xml +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/src/main/res/layout/notifications_list_fragment_page.xml b/WordPress/src/main/res/layout/notifications_list_fragment_page.xml index 5eb55c9a2669..de0b30185353 100644 --- a/WordPress/src/main/res/layout/notifications_list_fragment_page.xml +++ b/WordPress/src/main/res/layout/notifications_list_fragment_page.xml @@ -27,6 +27,14 @@ + + + app:aevTitle="@string/notifications_empty_unread"> - - - - diff --git a/WordPress/src/main/res/layout/reader_listitem_comment.xml b/WordPress/src/main/res/layout/reader_listitem_comment.xml index aaf31b73be81..6e069f0fbe01 100644 --- a/WordPress/src/main/res/layout/reader_listitem_comment.xml +++ b/WordPress/src/main/res/layout/reader_listitem_comment.xml @@ -77,7 +77,7 @@ android:contentDescription="@string/description_user" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - tools:src="@drawable/ic_user_circle_no_padding_grey_24dp" /> + tools:src="@drawable/ic_user_placeholder_primary_24" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/src/main/res/layout/signup_epilogue_activity.xml b/WordPress/src/main/res/layout/signup_epilogue_activity.xml deleted file mode 100644 index c1ff2bc0b3cd..000000000000 --- a/WordPress/src/main/res/layout/signup_epilogue_activity.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - diff --git a/WordPress/src/main/res/values-ar/strings.xml b/WordPress/src/main/res/values-ar/strings.xml index 8407b2e0d090..a6edf2c9c303 100644 --- a/WordPress/src/main/res/values-ar/strings.xml +++ b/WordPress/src/main/res/values-ar/strings.xml @@ -1480,9 +1480,6 @@ Language: ar لا توجد مقالات حديثة مرحبًا! فحص - قام <b>Johan Brandt</b> بالردّ على مقالتك - لقد تلقيت <b>50 إعجاب</b> على موقعك اليوم - أُعجب <b>Madison Ruiz</b> بمقالتك اختيار انقر على إعادة المحاولة عند الاتصال بالإنترنت مرة أخرى أو إنشاء صفحة فارغة باستخدام الزر أدناه. التخطيطات غير متوفرة دون اتصال انترنت @@ -1493,25 +1490,6 @@ Language: ar التصنيفات لم يتم التعيين التصنيفات - المتاحف في لندن - أفضل المعجبين بالعالم - أفضل عشرة مقاهي - السياسة - الموسيقى - زراعة الحدائق - كرة القدم - الطهي - الفن - موسيقى Rock n\' Roll الأسبوعية - أخبار الويب - باميلا نجوين - لقد ألهمتني أعمال المصور كاميرون كارستن. سأجرِّب هذه التقنيات في المحاولة التالية - الحصول على الإلهام - تابع مواقعك المفضَّلة واكتشف مدوّنات جديدة. - شاهد جمهورك يتزايد من خلال التحليلات المتعمقة. - شاهد التعليقات والإشعارات في الوقت الفعلي. - باستخدام المحرر الضخم والقوي، يمكنك النشر أثناء التنقّل. - مرحبًا بك في أداة إنشاء مواقع الويب الأكثر رواجًا في العالم. فشل تحميل الوسائط تم إنشاء صفحة فارغة تم إنشاء صفحة diff --git a/WordPress/src/main/res/values-cs/strings.xml b/WordPress/src/main/res/values-cs/strings.xml index 84d6dc2a5961..ee35dae6fb70 100644 --- a/WordPress/src/main/res/values-cs/strings.xml +++ b/WordPress/src/main/res/values-cs/strings.xml @@ -836,9 +836,6 @@ Language: cs_CZ Žádné nedávné příspěvky Najděte svůj připojený e-mail Pokračujte s přihlašovacími údaji úložiště - <b>Madison Ruiz</b> lajkoval váš příspěvek - Dnes jste na svém webu obdrželi <b>50 lajků</b> - <b>Johan Brandt</b> odpověděl na váš příspěvek Vybrat Kategorie Rozložení nejsou k dispozici kvůli chybě @@ -849,25 +846,6 @@ Language: cs_CZ Když jste zpět online, klepněte na opakovat nebo pomocí tlačítka níže vytvořte prázdnou stránku. Přidat rubriku Vytvořit novou rubriku - Hudba - Politika - Zahradničení - Fotbal - Vaření - Umění - Práce fotografa Camerona Karstena mě tak inspirovala. Tyto techniky budu zkoušet při příštím - Pamela Nguyen - Novinky na webu - Rock n \'Roll každý týden - Moje top desítka kaváren - Nejlepší fanoušci světa - Muzea v Londýně - Vítejte v nejpopulárnějším nástroji na tvorbu webových stránek na světě. - Díky výkonnému editoru můžete psát příspěvky i na cestách. - Podívejte se na komentáře a oznámení v reálném čase. - Sledujte, jak vaše publikum roste pomocí hloubkové analýzy. - Inspirovat se - Sledujte své oblíbené weby a objevujte nové blogy. Načítání média se nezdařilo Stránka byla vytvořena Prázdná stránka vytvořena diff --git a/WordPress/src/main/res/values-de/strings.xml b/WordPress/src/main/res/values-de/strings.xml index 1c9658f5f703..69cc389c90bc 100644 --- a/WordPress/src/main/res/values-de/strings.xml +++ b/WordPress/src/main/res/values-de/strings.xml @@ -1504,9 +1504,6 @@ Language: de Keine aktuellen Beiträge Willkommen! Scannen - <b>Johan Brandt</b> hat auf deinen Beitrag geantwortet - Heute hast du auf deiner Website <b>50 \"Gefällt mir\"-Angaben</b> erhalten: - <b>Madison Ruiz</b> gefällt dein Beitrag Auswählen Tippe auf „erneut versuchen“, wenn du wieder online bist oder erstelle über den Button unten eine leere Seite. Layouts sind offline nicht verfügbar @@ -1517,25 +1514,6 @@ Language: de Kategorien Nicht festgelegt Kategorien - Museen in London - Die weltbesten Fans - Meine Top 10 Cafés - Politik - Musik - Gärtnern - American Football - Kochen - Kunst - Rock\'n\'Roll wöchentlich - Web-Nachrichten - Pamela Nguyen - Ich finde die Arbeit von Fotograf Cameron Karsten sehr inspirierend. Das nächste Mal probiere ich diese Techniken auch aus - Inspiration - Folge deinen Lieblingswebsites und entdecke neue Blogs. - Sieh mithilfe von umfassenden Analysen dabei zu, wie deine Zielgruppe wächst. - Kommentare und Benachrichtigungen in Echtzeit anzeigen. - Mit dem leistungsstarken Editor kannst du auch von unterwegs aus Beiträge veröffentlichen. - Willkommen beim beliebtesten Website-Baukasten der Welt. Laden der Medien fehlgeschlagen Leere Seite erstellt Seite erstellt diff --git a/WordPress/src/main/res/values-el/strings.xml b/WordPress/src/main/res/values-el/strings.xml index 441091d5f8ce..8e1d6d77228c 100644 --- a/WordPress/src/main/res/values-el/strings.xml +++ b/WordPress/src/main/res/values-el/strings.xml @@ -39,7 +39,6 @@ Language: el_GR Κατηγορίες Δεν έχει οριστεί Κατηγορίες - Μουσεία στο Λονδίνο Όχι τώρα Καλώς ήρθατε στο WordPress! , Επιλεγμένο diff --git a/WordPress/src/main/res/values-en-rCA/strings.xml b/WordPress/src/main/res/values-en-rCA/strings.xml index eab384c385fe..8b18c97f2eb0 100644 --- a/WordPress/src/main/res/values-en-rCA/strings.xml +++ b/WordPress/src/main/res/values-en-rCA/strings.xml @@ -1398,9 +1398,6 @@ Language: en_CA No recent posts Welcome! Scan - <b>Johan Brandt</b> responded to your post - You received <b>50 likes</b> on your site today - <b>Madison Ruiz</b> liked your post Choose Tap retry when you\'re back online or create a blank page using the button below. Layouts not available while offline @@ -1411,25 +1408,6 @@ Language: en_CA Categories Not set Categories - Museums in London - The World\'s Best Fans - My Top Ten Cafes - Politics - Music - Gardening - Football - Cooking - Art - Rock n\' Roll Weekly - Web News - Pamela Nguyen - I am so inspired by photographer Cameron Karsten’s work. I will be trying these techniques on my next - Getting Inspired - Follow your favourite sites and discover new blogs. - Watch your audience grow with in-depth analytics. - See comments and notifications in real time. - With the powerful editor you can post on the go. - Welcome to the world’s most popular website builder. Media loading failed Blank page created Page created diff --git a/WordPress/src/main/res/values-en-rGB/strings.xml b/WordPress/src/main/res/values-en-rGB/strings.xml index 07d0008a2e56..804929584b80 100644 --- a/WordPress/src/main/res/values-en-rGB/strings.xml +++ b/WordPress/src/main/res/values-en-rGB/strings.xml @@ -1421,9 +1421,6 @@ Language: en_GB No recent posts Welcome! Scan - <b>Johan Brandt</b> responded to your post - You received <b>50 likes</b> on your site today - <b>Madison Ruiz</b> liked your post Choose Tap retry when you\'re back online, or create a blank page using the button below. Layouts not available while offline @@ -1434,25 +1431,6 @@ Language: en_GB Categories Not set Categories - Museums in London - The World\'s Best Fans - My Top Ten Cafés - Politics - Music - Gardening - Football - Cooking - Art - Rock ‘n’ Roll Weekly - Web News - Pamela Nguyen - I am so inspired by photographer Cameron Karsten’s work. I will be trying these techniques on my next - Getting Inspired - Follow your favourite sites and discover new blogs. - Watch your audience grow with in-depth analytics. - See comments and notifications in real time. - With the powerful editor, you can post on the go. - Welcome to the world’s most popular website builder. Media loading failed Blank page created Page created diff --git a/WordPress/src/main/res/values-es-rCL/strings.xml b/WordPress/src/main/res/values-es-rCL/strings.xml index b93301ab2f48..87eafa9d4535 100644 --- a/WordPress/src/main/res/values-es-rCL/strings.xml +++ b/WordPress/src/main/res/values-es-rCL/strings.xml @@ -1288,9 +1288,6 @@ Language: es_CL No hay entradas recientes ¡Bienvenido! Explorar - <b>Juan Gómez</b> ha respondido en tu entrada - Hoy has recibido <b>50 me gusta</b> en tu sitio - A <b>Madison Ruíz</b> le ha gustado tu entrada Elegir Toca \"Reintentar\" cuando vuelvas a estar en línea o crea una página en blanco usando el botón a continuación. Los diseños no están disponibles sin conexión @@ -1301,25 +1298,6 @@ Language: es_CL Categorías No establecido Categorías - Museos en Londres - Los mejores fanáticos del mundo - Mis diez mejores cafés - Política - Música - Jardinería - Fútbol - Cocina - Arte - Rock n\' roll semanal - Noticias web - Pamela Nguyen - Estoy muy inspirado por el trabajo del fotógrafo Cameron Karsten. Probaré estas técnicas en mi próximo - Inspírate - Sigue tus sitios favoritos y descubre nuevos blogs. - Observa cómo crece tu audiencia con analíticas avanzadas. - Mira los comentarios y avisos en tiempo real. - Con el potente editor puedes publicar sobre la marcha. - Bienvenido al maquetador web más popular del mundo. La carga del medio ha fallado Página en blanco creada Página creada diff --git a/WordPress/src/main/res/values-es-rCO/strings.xml b/WordPress/src/main/res/values-es-rCO/strings.xml index f300af597321..24ccfc29c858 100644 --- a/WordPress/src/main/res/values-es-rCO/strings.xml +++ b/WordPress/src/main/res/values-es-rCO/strings.xml @@ -1421,9 +1421,6 @@ Language: es_CO Encuentra tu correo electrónico conectado Continuar con las credenciales de la tienda Prueba a seguir más etiquetas para ampliar la búsqueda - A <b>Madison Ruíz</b> le ha gustado tu entrada - Hoy has recibido <b>50 me gusta</b> en tu sitio - <b>Juan Gómez</b> ha respondido en tu entrada Elegir Toca «Reintentar» cuando vuelvas a estar en línea o crea una página en blanco usando el botón a continuación. Los diseños no están disponibles sin conexión @@ -1434,25 +1431,6 @@ Language: es_CO No establecido Categorías Añadir una categoría - Museos en Londres - Los mejores fanáticos del mundo - Mis diez mejores cafés - Política - Música - Jardinería - Fútbol - Cocina - Arte - Rock n\' roll semanal - Noticias web - Pamela Nguyen - Estoy muy inspirado por el trabajo del fotógrafo Cameron Karsten. Probaré estas técnicas en mi próximo - Inspírate - Observa cómo crece tu audiencia con analíticas avanzadas. - Con el potente editor puedes publicar sobre la marcha. - Bienvenido al maquetador web más popular del mundo. - Mira los comentarios y avisos en tiempo real. - Sigue tus sitios favoritos y descubre nuevos blogs. La carga del medio ha fallado Página en blanco creada Página creada diff --git a/WordPress/src/main/res/values-es-rMX/strings.xml b/WordPress/src/main/res/values-es-rMX/strings.xml index 8ce268906c1a..38ee5232b57b 100644 --- a/WordPress/src/main/res/values-es-rMX/strings.xml +++ b/WordPress/src/main/res/values-es-rMX/strings.xml @@ -110,9 +110,6 @@ Language: es_MX No hay entradas recientes Encuentra tu correo electrónico conectado Continuar con las credenciales de la tienda - A <b>Madison Ruíz</b> le ha gustado tu entrada - Hoy has recibido <b>50 me gusta</b> en tu sitio - <b>Juan Gómez</b> ha respondido en tu entrada Elegir Los layouts no están disponibles debido a un error Añadir categoría @@ -123,24 +120,6 @@ Language: es_MX Toca \"Reintentar\" cuando vuelvas a estar en línea o crea una página en blanco usando el botón a continuación. Toca \"Reintentar\" o crea una página en blanco usando el botón a continuación. Los layouts no están disponibles offline - Museos en Londres - Los mejores fanáticos del mundo - Mis diez mejores cafés - Política - Música - Jardinería - Fútbol - Cocina - Arte - Rock n\' roll semanal - Noticias web - Pamela Nguyen - Estoy muy inspirado por el trabajo del fotógrafo Cameron Karsten. Probaré estas técnicas en mi próximo - Inspírate - Observa cómo crece tu audiencia con analíticas avanzadas. - Con el potente editor puedes publicar sobre la marcha. - Bienvenido al maquetador web más popular del mundo. - Mira los comentarios y avisos en tiempo real. La carga del medio ha fallado Página creada Página en blanco creada diff --git a/WordPress/src/main/res/values-es-rVE/strings.xml b/WordPress/src/main/res/values-es-rVE/strings.xml index 847d8b736a1c..8e40fb5ae4ca 100644 --- a/WordPress/src/main/res/values-es-rVE/strings.xml +++ b/WordPress/src/main/res/values-es-rVE/strings.xml @@ -625,9 +625,6 @@ Language: es_VE No hay entradas recientes ¡Bienvenido! Explorar - <b>Juan Gómez</b> ha respondido en tu entrada - Hoy has recibido <b>50 me gusta</b> en tu sitio - A <b>Madison Ruíz</b> le ha gustado tu entrada Elegir Toca «Reintentar» cuando vuelvas a estar en línea o crea una página en blanco usando el botón a continuación. Los diseños no están disponibles sin conexión @@ -638,25 +635,6 @@ Language: es_VE Categorías No establecido Categorías - Museos en Londres - Los mejores fanáticos del mundo - Mis diez mejores cafés - Política - Música - Jardinería - Fútbol - Cocina - Arte - Rock n\' roll semanal - Noticias web - Pamela Nguyen - Estoy muy inspirado por el trabajo del fotógrafo Cameron Karsten. Probaré estas técnicas en mi próximo - Inspírate - Sigue tus sitios favoritos y descubre nuevos blogs. - Observa cómo crece tu audiencia con analíticas avanzadas. - Mira los comentarios y avisos en tiempo real. - Con el potente editor puedes publicar sobre la marcha. - Bienvenido al maquetador web más popular del mundo. La carga del medio ha fallado Página en blanco creada Página creada diff --git a/WordPress/src/main/res/values-es/strings.xml b/WordPress/src/main/res/values-es/strings.xml index d37c19fe442d..cd279dba91a2 100644 --- a/WordPress/src/main/res/values-es/strings.xml +++ b/WordPress/src/main/res/values-es/strings.xml @@ -1504,9 +1504,6 @@ Language: es No hay entradas recientes ¡Bienvenido! Explorar - <b>Juan Gómez</b> ha respondido en tu entrada - Hoy has recibido <b>50 me gusta</b> en tu sitio - A <b>Madison Ruíz</b> le ha gustado tu entrada Elegir Toca «Reintentar» cuando vuelvas a estar en línea o crea una página en blanco usando el botón a continuación. Los diseños no están disponibles sin conexión @@ -1517,25 +1514,6 @@ Language: es Categorías No establecido Categorías - Museos en Londres - Los mejores fanáticos del mundo - Mis diez mejores cafés - Política - Música - Jardinería - Fútbol - Cocina - Arte - Rock n\' roll semanal - Noticias web - Pamela Nguyen - Estoy muy inspirado por el trabajo del fotógrafo Cameron Karsten. Probaré estas técnicas en mi próximo - Inspírate - Sigue tus sitios favoritos y descubre nuevos blogs. - Observa cómo crece tu audiencia con analíticas avanzadas. - Mira los comentarios y avisos en tiempo real. - Con el potente editor puedes publicar sobre la marcha. - Bienvenido al maquetador web más popular del mundo. La carga del medio ha fallado Página en blanco creada Página creada diff --git a/WordPress/src/main/res/values-fr-rCA/strings.xml b/WordPress/src/main/res/values-fr-rCA/strings.xml index 7bca9c73f064..13da9249852a 100644 --- a/WordPress/src/main/res/values-fr-rCA/strings.xml +++ b/WordPress/src/main/res/values-fr-rCA/strings.xml @@ -1478,9 +1478,6 @@ Language: fr Pas d’articles récents. Bienvenue ! Analyser - <b>Johan Brandt</b> a répondu à votre article - Vous avez reçu <b>50 « J’aime »</b> sur votre site aujourd\'hui - <b>Madison Ruiz</b> a aimé votre article Choisir Touchez réessayer quand vous serez de nouveau en ligne ou créer un page blanche en utilisant le bouton ci-dessous. Les mises en page ne sont pas disponibles hors connexion @@ -1491,25 +1488,6 @@ Language: fr Catégories Non définie Catégories - Musées à Londres - Les meilleurs fans du monde - Mon top 10 des cafés - Politique - Musique - Jardinage - Football - Cuisine - Art - Semaine du Rock n’ Roll - Infos du web - Pamela Nguyen - J’apprécie le travail du photographe Cameron Karsten. J’essaierai ses techniques sur mes prochaines photos. - Inspiration - Suivez vos sites préférés et découvrez de nouveaux blogs. - Regardez votre audience grossir avec les statistiques approfondies. - Voir les commentaires et notifications en temps réel. - Avec cet éditeur puissant, vous pouvez publier de n’importe où. - Bienvenue sur le constructeur de site le plus populaire au monde. Échec du chargement du média Page vide créée Page créée diff --git a/WordPress/src/main/res/values-fr/strings.xml b/WordPress/src/main/res/values-fr/strings.xml index 7bca9c73f064..13da9249852a 100644 --- a/WordPress/src/main/res/values-fr/strings.xml +++ b/WordPress/src/main/res/values-fr/strings.xml @@ -1478,9 +1478,6 @@ Language: fr Pas d’articles récents. Bienvenue ! Analyser - <b>Johan Brandt</b> a répondu à votre article - Vous avez reçu <b>50 « J’aime »</b> sur votre site aujourd\'hui - <b>Madison Ruiz</b> a aimé votre article Choisir Touchez réessayer quand vous serez de nouveau en ligne ou créer un page blanche en utilisant le bouton ci-dessous. Les mises en page ne sont pas disponibles hors connexion @@ -1491,25 +1488,6 @@ Language: fr Catégories Non définie Catégories - Musées à Londres - Les meilleurs fans du monde - Mon top 10 des cafés - Politique - Musique - Jardinage - Football - Cuisine - Art - Semaine du Rock n’ Roll - Infos du web - Pamela Nguyen - J’apprécie le travail du photographe Cameron Karsten. J’essaierai ses techniques sur mes prochaines photos. - Inspiration - Suivez vos sites préférés et découvrez de nouveaux blogs. - Regardez votre audience grossir avec les statistiques approfondies. - Voir les commentaires et notifications en temps réel. - Avec cet éditeur puissant, vous pouvez publier de n’importe où. - Bienvenue sur le constructeur de site le plus populaire au monde. Échec du chargement du média Page vide créée Page créée diff --git a/WordPress/src/main/res/values-gl/strings.xml b/WordPress/src/main/res/values-gl/strings.xml index ff924ea351d4..18a80eadb2af 100644 --- a/WordPress/src/main/res/values-gl/strings.xml +++ b/WordPress/src/main/res/values-gl/strings.xml @@ -1429,9 +1429,6 @@ Language: gl_ES Non hai entradas recentes Benvido! Explorar - <b>Xan Vilaboi</b> respondeu na túa entrada - Hoxe recibiches <b>50 gústame</b> no teu sitio - A <b>Madison Ruíz</b> gustoullea túa entrada Elixir Toca «Reintentar» cando volvas a estar en liña ou crea unha páxina en branco usando o seguinte botón. Os deseños non están dispoñibles sen conexión @@ -1442,25 +1439,6 @@ Language: gl_ES Categorías Non establecido Categorías - Museos en Londres - Os mellores fanáticos do mundo - Os meus dez mellores cafés - Política - Música - Xardinería - Fútbol - Cociña - Arte - Rock n\' roll semanal - Noticias web - Pamela Nguyen - Estou moi inspirado polo traballo do fotógrafo Cameron Karsten. Probarei estas técnicas no meu próximo - Inspírate - Segue o teus sitios favoritos e descobre novos blogs. - Observa como crece a túa audiencia con analíticas avanzadas. - Mira os comentarios e avisos en tempo real. - Co potente editor podes publicar sobre a marcha. - Benvido ao maquetador web máis popular do mundo. A carga do medio fallou Páxina en branco creada Páxina creada diff --git a/WordPress/src/main/res/values-he/strings.xml b/WordPress/src/main/res/values-he/strings.xml index de3a8e68a12e..027778931d90 100644 --- a/WordPress/src/main/res/values-he/strings.xml +++ b/WordPress/src/main/res/values-he/strings.xml @@ -1496,9 +1496,6 @@ Language: he_IL אין פוסטים אחרונים ברוך בואך! לסרוק - <b>ישראל ישראלי</b> הגיב לפוסט שלך - קיבלת <b>‏50 ליקיים</b> באתר שלך היום - <b>ישראלה ישראלה</b> סימנה לייק בפוסט שלך לבחור יש להקיש כדי לנסות שוב במצב מקוון או ליצור עמוד ריק בעזרת הכפתור למטה. הפריסות אינן זמינות במצב לא מקוון @@ -1509,25 +1506,6 @@ Language: he_IL קטגוריות לא הוגדר קטגוריות - מוזיאונים בלונדון - המעריצים הכי נאמנים בעולם - העשירייה הפותחת של בתי הקפה שלי - פוליטיקה - מוזיקה - גינון - פוטבול - בישול - אומנות - רוקנרול שבועי - חדשות באינטרנט - פמלה גואן - העבודות של הצלם קמרון קרסטן הן השראה עצומה עבורי. את הטכניקות האלה אביא לידי ביטוי בפעם הבאה שאצור - לקבל השראה - כדאי לעקוב אחר האתרים המועדפים שלך ולגלות בלוגים חדשים לקריאה. - באמצעות ניתוחי עומק ניתן לעקוב אחר הצמיחה של קהל המבקרים. - להציג תגובות והודעות בזמן אמת. - העורך העוצמתי הזה מאפשר לפרסם מכל מקום. - ברוך בואך לכלי הפופולרי ביותר בעולם לבניית אתרי אינטרנט. העלאת מדיה נכשלה נוצר עמוד ריק העמוד נוצר diff --git a/WordPress/src/main/res/values-hr/strings.xml b/WordPress/src/main/res/values-hr/strings.xml index 3160f7a87787..1ad5563678b6 100644 --- a/WordPress/src/main/res/values-hr/strings.xml +++ b/WordPress/src/main/res/values-hr/strings.xml @@ -56,14 +56,6 @@ Language: hr Kategorije Nije postavljeno Kategorije - Muzeji u Londonu - Politika - Glazba - Vrtlarstvo - Nogomet - Kuhanje - Umjetnost - Pamela Nguyen Učitavanje sadržaja nije uspjelo Izrađena je prazna stranica Stranica kreirana diff --git a/WordPress/src/main/res/values-id/strings.xml b/WordPress/src/main/res/values-id/strings.xml index c3a32afbd4e2..b22a28264904 100644 --- a/WordPress/src/main/res/values-id/strings.xml +++ b/WordPress/src/main/res/values-id/strings.xml @@ -1500,9 +1500,6 @@ Language: id Tak ada pos terbaru Selamat datang! Pindai - <b>Johan Brandt</b> menanggapi pos Anda - Anda menerima <b>50 suka</b> di situs Anda hari ini - <b>Madison Ruiz</b> menyukai pos Anda Pilih Ketuk coba lagi setelah Anda kembali online atau buat halaman kosong dengan tombol di bawah ini. Tata letak tidak tersedia saat offline @@ -1513,25 +1510,6 @@ Language: id Kategori Tidak ditentukan Kategori - Museum di London - Penggemar Terbaik di Dunia - Sepuluh Kafe Favorit Saya - Politik - Musik - Berkebun - Sepakbola - Memasak - Seni - Rock n\' Roll Mingguan - Berita Web - Pamela Nguyen - Saya sangat terinspirasi oleh karya fotografer Cameron Karsten. Saya akan mencoba teknik ini selanjutnya - Dapatkan Inspirasi - Ikuti situs favorit Anda dan temukan blog-blog baru. - Perhatikan audiens Anda tumbuh dengan analisis mendalam. - Lihat komentar dan pemberitahuan secara real-time. - Dengan editor yang hebat, Anda dapat langsung menerbitkan pos. - Selamat datang di pembuat situs web terpopuler di dunia. Gagal memuat media Halaman kosong dibuat Halaman dibuat diff --git a/WordPress/src/main/res/values-it/strings.xml b/WordPress/src/main/res/values-it/strings.xml index 1c748b6bc239..73a85ff87556 100644 --- a/WordPress/src/main/res/values-it/strings.xml +++ b/WordPress/src/main/res/values-it/strings.xml @@ -1496,9 +1496,6 @@ Language: it Nessun articolo recente Benvenuto! Scansiona - <b>Johan Brandt</b> ha risposto al tuo articolo - Oggi hai ricevuto <b>50 Mi piace</b> al tuo sito - A <b>Madison Ruiz</b> è piaciuto il tuo articolo Scegli Tocca Riprova quando sei di nuovo online o crea una pagina vuota utilizzando il pulsante sottostante. Layout non disponibili in modalità offline @@ -1509,25 +1506,6 @@ Language: it Categorie Non impostato Categorie - Musei di Londra - World\'s Best Fans - La Top Ten dei bar - Politica - Musica - Giardinaggio - Calcio - Cucina - Arte - Rock \'n\' Roll di questa settimana - Web News - Pamela Nguyen - Sono ispirato dal lavoro del fotografo Cameron Karsten. Proverò queste tecniche durante il mio prossimo - Lasciati ispirare - Segui i tuoi siti preferiti e scopri nuovi blog. - Guarda il tuo pubblico crescere con analisi approfondite. - Visualizza i commenti e le notifiche in tempo reale. - Con l\'editor potente puoi pubblicare anche durante i tuoi spostamenti. - Benvenuto nel costruttore di siti web più popolare del mondo. Caricamento dei file multimediali non riuscito Pagina bianca creata Pagina creata diff --git a/WordPress/src/main/res/values-ja/strings.xml b/WordPress/src/main/res/values-ja/strings.xml index eea95fc2a312..f7df837af9f2 100644 --- a/WordPress/src/main/res/values-ja/strings.xml +++ b/WordPress/src/main/res/values-ja/strings.xml @@ -1466,9 +1466,6 @@ Language: ja_JP 最近の投稿はありません ようこそ ! スキャン - <b>Johan Brandt</b> さんが投稿に返信しました - 今日サイトで<b>50回「いいね」</b>されました - <b>Madison Ruiz</b> さんが投稿に「いいね」しました 選択 オンラインに戻ったら「再試行」をタップするか、以下のボタンを使用して白紙のページを作成します。 レイアウトはオフラインでは利用できません @@ -1479,25 +1476,6 @@ Language: ja_JP カテゴリー 未設定 カテゴリー - ロンドンの博物館 - 世界最高のファン - 私のお気に入りカフェトップ10 - 政治 - ミュージック - 園芸 - フットボール - クッキング - アート - 今週のロックンロール - ウェブニュース - Pamela Nguyen - 写真家の Cameron Karsten 氏の作品にはとても感銘を受けました。 この技術を今度試すつもりです - インスピレーションを受ける - お気に入りのサイトをフォローして新しい読み物を発見しましょう。 - 詳細な分析により、オーディエンスの成長を見守れます。 - コメントと通知をリアルタイムで表示します。 - 強力なエディターで、外出中も投稿できます。 - 世界で最も人気のサイト構築サービスへようこそ。 メディア読み込み失敗 空白ページが作成されました ページが作成されました diff --git a/WordPress/src/main/res/values-kmr/strings.xml b/WordPress/src/main/res/values-kmr/strings.xml index 28046e962e90..c2a7d8237fa2 100644 --- a/WordPress/src/main/res/values-kmr/strings.xml +++ b/WordPress/src/main/res/values-kmr/strings.xml @@ -61,9 +61,6 @@ Language: ku_TR Şandiyên nû tune ye Bi xêr hatî! venerîn - <b>Johan Brandt</b> bersiv da şandiya te - Te îro li ser malpera xwe <b>50 likes</b> ecibandin sitend - <b>Madison Ruiz</b> şandiya te eciband Hilbijêre Dema ku tu ji nû ve serhêl bû li dîsa biceribîneyê bitikîne an jî bi rêya bikaranîna bişkoka jêrîn rûpeleke vala biafirîne. Di derhêlbûnê(offline) de raxistin ne berdest in @@ -74,24 +71,6 @@ Language: ku_TR Kategorî Nesazkirî Kategorî - Muzeyên li Londonê - Piştevanên Herî Baş ên Cîhanê - Deh kafeyên min ên sereke - Siyaset - Muzîk - Nûçeyên Webê - Huner - Baxçevanî - Futbol - Pamela Nguyen - Heftane Rock n\' Roll - Xwarin - Karê wênekêş Cameron Karsten\'ê min zêde han dide. Ez ê van teknîkan piştre biceribînim - Tu bi xêr hatî avakera malperan a herî populer. - Di dema rasteqîne de şîrove û danezanan bibîne. - Bi edîtora bihêz re tu dikarî di meşê de jî şandiyan biweşîne. - Îlhamê Bistîne - Bi analîzên berfireh binêrin gelo girseya we çawa mezin dibe. Medya nehat barkirin Rûpel hat afirandin Rûpela vala hat afirandin diff --git a/WordPress/src/main/res/values-ko/strings.xml b/WordPress/src/main/res/values-ko/strings.xml index 72ceb444f4c3..8f9b964c9e9e 100644 --- a/WordPress/src/main/res/values-ko/strings.xml +++ b/WordPress/src/main/res/values-ko/strings.xml @@ -1504,9 +1504,6 @@ Language: ko_KR 최근 글이 없습니다 환영합니다! 검색하기 - <b>요한 브란트</b>가 글에 답했습니다 - 오늘 사이트에서 <b>50개의 좋아요</b>를 받았습니다 - <b>메디슨 루이즈</b>가 글을 좋아합니다 선택하기 온라인으로 돌아가면 재시도를 누르거나 아래의 단추를 이용하여 빈 페이지를 만드세요. 레이아웃은 오프라인 중에 사용할 수 없습니다 @@ -1517,25 +1514,6 @@ Language: ko_KR 카테고리 설정하지 않았습니다 카테고리 - 런던의 박물관 - 세계 최고의 팬 - 상위 10개 카페 - 정치 - 음악 - 원예 - 축구 - 요리 - 예술 - 주간 락앤롤 - 웹 뉴스 - 파멜라 응우옌 - 사진가 카메론 카르스텐의 작업에서 깊은 영감을 받았습니다. 다음 번에 이 기법을 시도할 것입니다 - 영감받기 - 좋아하는 사이트를 팔로우하고 새 블로그를 검색하세요. - 깊이 있는 분석으로 독자가 늘어나는 것을 확인하세요. - 댓글과 알림을 실시간으로 보세요. - 강력한 편집기로 이동 중에도 글을 쓸 수 있습니다. - 세계적으로 가장 잘 알려진 웹사이트 제작기에 오신 것을 환영합니다. 미디어 로딩을 실패했습니다 빈 페이지를 만들었습니다 페이지를 만들었습니다 diff --git a/WordPress/src/main/res/values-land/dimens.xml b/WordPress/src/main/res/values-land/dimens.xml index 0a2ec43b918c..fc8418cfdd84 100644 --- a/WordPress/src/main/res/values-land/dimens.xml +++ b/WordPress/src/main/res/values-land/dimens.xml @@ -1,10 +1,6 @@ - - 24sp - 48dp - 0dp 0dp diff --git a/WordPress/src/main/res/values-lv/strings.xml b/WordPress/src/main/res/values-lv/strings.xml index e9628ab19c27..96dc7a1d1519 100644 --- a/WordPress/src/main/res/values-lv/strings.xml +++ b/WordPress/src/main/res/values-lv/strings.xml @@ -1310,9 +1310,6 @@ Language: lv Nav pēdējo ziņu Laipni lūdzam! Skenēt - <b>Johans Brandts</b> atbildēja uz jūsu ziņu - Šodien savā vietnē esat saņēmis <b>50 atzīmes Patīk</b> - <b>Madison Ruiz</b> patika jūsu ziņa Izvēlieties Pieskarieties vēlreiz, kad esat tiešsaistē, vai izveidojiet tukšu lapu, izmantojot zemāk esošo pogu. Izkārtojumi nav pieejami bezsaistē @@ -1323,25 +1320,6 @@ Language: lv Kategorijas Nav uzstādīts Kategorijas - Muzeji Londonā - Labākie pasaules līdzjutēji - Manas desmit kafejnīcas - Politika - Mūzika - Dārzkopība - Futbols - Ēdienu gatavošana - Māksla - Rokenrola nedēļas žurnāls - Tīmekļa ziņas - Pamela Nguyen - Mani tik ļoti iedvesmo fotogrāfa Kamerona Karstena darbi. Es izmēģināšu šīs metodes savā nākamajā - Iedvesmojoties - Sekojiet savām iecienītākajām vietnēm un atklājiet jaunus emuārus. - Izmantojot padziļinātu analīzi, vērojiet, kā pieaug auditorija. - Skatiet komentārus un paziņojumus reāllaikā. - Izmantojot spēcīgo redaktoru, jūs varat ievietot ziņas, esot ceļā. - Laipni lūdzam pasaules populārākajā vietņu veidotājā. Multivides ielāde neizdevās Izveidota tukša lapa Lapa ir izveidota diff --git a/WordPress/src/main/res/values-nb/strings.xml b/WordPress/src/main/res/values-nb/strings.xml index a76bef229138..a452eabf70a5 100644 --- a/WordPress/src/main/res/values-nb/strings.xml +++ b/WordPress/src/main/res/values-nb/strings.xml @@ -243,16 +243,6 @@ Language: nb_NO Kategorier Ikke angitt Kategorier - Museer i London - Politikk - Musikk - Hagestell - Fotball - Matlaging - Kunst - Pamela Nguyen - Å bli inspirert - Følg dine favorittnettsteder og oppdag nye blogger. Side opprettet Tilbake Kom i gang diff --git a/WordPress/src/main/res/values-night/colors.xml b/WordPress/src/main/res/values-night/colors.xml index 707ba501a342..9ed50581ce5f 100644 --- a/WordPress/src/main/res/values-night/colors.xml +++ b/WordPress/src/main/res/values-night/colors.xml @@ -7,21 +7,6 @@ #1E1E1E @color/background_dark_elevated - - @color/white_translucent_20 - @color/green_60 - @color/purple_60 - @color/blue_60 - @color/blue_60 - @color/green_60 - @color/yellow_60 - @color/red_60 - @color/purple_60 - - - @color/gray_80 - @color/white - @color/gray_100 diff --git a/WordPress/src/main/res/values-night/styles_login.xml b/WordPress/src/main/res/values-night/styles_login.xml new file mode 100644 index 000000000000..2ea561f40b87 --- /dev/null +++ b/WordPress/src/main/res/values-night/styles_login.xml @@ -0,0 +1,60 @@ + + + + + diff --git a/WordPress/src/main/res/values-nl/strings.xml b/WordPress/src/main/res/values-nl/strings.xml index a78b4dc06f51..4e475639ca70 100644 --- a/WordPress/src/main/res/values-nl/strings.xml +++ b/WordPress/src/main/res/values-nl/strings.xml @@ -1504,9 +1504,6 @@ Language: nl Geen recente berichten Welkom! Scan - <b>Johan Brandt</b> antwoordde op je bericht - Je ontving <b>50 vind-ik-leuks</b> op je site vandaag - <b>Madison Ruiz</b> vond je bericht leuk Kies Tikken op opnieuw proberen wanneer je opnieuw online bent of maak een lege pagina met de knop hieronder. Lay-outs niet beschikbaar indien offline @@ -1517,25 +1514,6 @@ Language: nl Categorieën Niet ingesteld Categorieën - Museums in Londen - The World\'s Best Fans - Mijn top tien cafés - Politiek - Muziek - Tuinieren - Voetbal - Koken - Kunst - Rock n\' Roll Weekly - Webnieuws - Pamela Nguyen - Ik ben zo geïnspireerd door fotograaf Cameron Karsten. Ik ga deze technieken zeker proberen bij mijn volgende - Inspiratie opdoen - Volg je favoriete sites en ontdek nieuwe blogs. - Bekijk hoe je publiek groeit met in-dept analytics. - Bekijk reacties en meldingen in real time. - Met deze krachtige editor kun je onderweg publiceren. - Welkom bij de wereld\'s meest populaire sitebouwer. Media laden is mislukt Blanco pagina aangemaakt Pagina aangemaakt diff --git a/WordPress/src/main/res/values-pl/strings.xml b/WordPress/src/main/res/values-pl/strings.xml index c017bddd6fe5..3e6418bdcccd 100644 --- a/WordPress/src/main/res/values-pl/strings.xml +++ b/WordPress/src/main/res/values-pl/strings.xml @@ -1421,9 +1421,6 @@ Language: pl Brak ostatnich wpisów Witaj! Skanuj - <b>Johan Brandt</b> odpowiedział na twój wpis - Twoja witryna otrzymała dziś <b>50 polubień</b> - <b>Madison Ruiz</b> polubił(a) twój wpis Wybierz Stuknij ponów kiedy będziesz spowrotem online lub stwórz pustą stronę używając poniższego przycisku. Szablony nie są dostępne w trybie offline @@ -1434,25 +1431,6 @@ Language: pl Kategorie Nie ustawiono Kategorie - Muzea w Londynie - Najlepsi na świecie fani - Moje dziesięć ulubionych kawiarni - Polityka - Muzyka - Ogrodnictwo - Piłka nożna - Gotowanie - Sztuka - Tygodniowe Rock n\' Roll - Wiadomości Web - Pamela Nguyen - Jestem tak zainspirowany pracą fotografa Camerona Karstena. Będę próbował tych technik przy mojej następnej - Czerpanie inspiracji - Obserwuj swoje ulubione witryny i odkrywaj nowe blogi. - Obserwuj jak rośnie twoja publiczność dzięki zaawansowanej analityce. - Sprawdzaj komentarze i otrzymuj powiadomienia w czasie rzeczywistym. - Dzięki potężnemu edytorowi możesz w locie dodawać nowe wpisy. - Witaj na najbardziej popularnej platformie do tworzenia witryny internetowej. Nie udało się wczytać plików mediów Utworzono pustą stronę Utworzono stronę diff --git a/WordPress/src/main/res/values-pt-rBR/strings.xml b/WordPress/src/main/res/values-pt-rBR/strings.xml index e35b3e0f337c..69142d76f958 100644 --- a/WordPress/src/main/res/values-pt-rBR/strings.xml +++ b/WordPress/src/main/res/values-pt-rBR/strings.xml @@ -1504,9 +1504,6 @@ Language: pt_BR Nenhum post recente Desejamos boas-vindas! Examinar - <b>Johan Brandt</b> respondeu seu post - Você recebeu <b>50 curtidas</b> em seu site hoje - <b>Madison Ruiz</b> curtiu seu post Escolher Toque em tentar novamente quando estiver online ou crie uma página vazia usando o botão abaixo. Modelos não estão disponíveis off-line @@ -1517,25 +1514,6 @@ Language: pt_BR Categorias Não definido Categorias - Museus em Londres - Os melhores fãs do mundo - Minhas 10 cafeterias preferidas - Política - Música - Jardinagem - Futebol - Culinária - Arte - Novidades sobre Rock n\' Roll - Novidades da internet - Pamela Nguyen - Me inspiro muito no trabalho do fotógrafo. Vou tentar uma dessas técnicas em meu próximo - Inspirando-se - Siga seus sites favoritos e descubra novos blogs. - Veja seu público-alvo aumentar usando análises detalhadas. - Veja comentários e notificações em tempo real. - Com o editor avançado, você publica seus posts de onde estiver. - Desejamos boas-vindas ao criador de sites mais popular do mundo. O envio da mídia falhou Página vazia criada Página criada diff --git a/WordPress/src/main/res/values-ro/strings.xml b/WordPress/src/main/res/values-ro/strings.xml index 56e900e52528..d1fcf0bc4056 100644 --- a/WordPress/src/main/res/values-ro/strings.xml +++ b/WordPress/src/main/res/values-ro/strings.xml @@ -1504,9 +1504,6 @@ Language: ro Niciun articol recent Bine ai venit! Scanează - <b>Johan Brandt</b> a răspuns la articolul tău - Azi ai primit <b>50 de aprecieri</b> pe site - <b>Madison Ruiz</b> ți-a apreciat articolul Alege Atinge reîncearcă când ești din nou online sau creează o pagină goală folosind butonul de mai jos. Aranjamentele nu sunt disponibile când ești offline @@ -1517,25 +1514,6 @@ Language: ro Categorii Nesetată Categorii - Muzee din Londra - Cei mai buni fani din lume - Primele zece cafenele, pe gustul meu - Politică - Muzică - Grădinărit - Fotbal - Bucătărie - Artă - Rock and Roll în fiecare săptămână - Știri pe web - Pamela Nguyen - M-au inspirat foarte mult lucrările fotografului Cameron Karsten. Voi încerca aceste tehnici pe viitor - Inspiră-te - Urmărește-ți site-urile preferate și descoperi bloguri noi. - Urmărești cum îți crește audiența cu analitice detaliate. - Vezi comentariile și notificările în timp real. - Cu acest editor puternic poți publica din mers. - Bine ai venit la cel mai popular constructor de site-uri web din lume. Încărcarea media a eșuat A fost creată o pagină goală Am creat pagina diff --git a/WordPress/src/main/res/values-ru/strings.xml b/WordPress/src/main/res/values-ru/strings.xml index 850132104f77..72934db70355 100644 --- a/WordPress/src/main/res/values-ru/strings.xml +++ b/WordPress/src/main/res/values-ru/strings.xml @@ -1504,9 +1504,6 @@ Language: ru Нет свежих записей Добро пожаловать! Сканировать - <b>Вася Пупкин</b> ответил на вашу запись - Вы сегодня получили <b>50 отметок \"нравится\"</b> на сайте - <b>Юля Петрова</b> отмечает вашу запись как понравившуюся Выбрать Нажмите повторить при восстановлении подключения к сети или создайте пустую страницу нажав на кнопку ниже. Макеты недоступны при отсутствии подключения к сети @@ -1517,25 +1514,6 @@ Language: ru Рубрики Не указано Рубрики - Музеи Лондона - Лучшие мировые фанаты - Мой выбор лучших 10 кафе - Политика - Музыка - Садоводство - Футбол - Кулинария - Искусство - Еженедельный рок-н-ролл - Новости веб - Памела Нгуен - Меня восхищают работы фотографа Кэмерона Карстена. Я попробую его техники в будущем - Вдохновляйтесь - Подписывайтесь на избранные сайты и находите новое чтиво. - Следите за ростом вашей аудитории с глубокой аналитикой. - Следите в реальном времени за комментариями и уведомлениями. - С настолько мощным редактором вы можете создавать публикации на ходу. - Добро пожаловать в самый популярный в мире конструктор сайтов. Загрузка медиафайла неудалась Создана пустая страница Страница создана diff --git a/WordPress/src/main/res/values-sq/strings.xml b/WordPress/src/main/res/values-sq/strings.xml index 7af72d16d125..3dc20ac31ba1 100644 --- a/WordPress/src/main/res/values-sq/strings.xml +++ b/WordPress/src/main/res/values-sq/strings.xml @@ -1499,9 +1499,6 @@ Language: sq_AL Pa postime së fundi Mirë se vini! Skanoje - <b>Durim Cerova</b> iu përgjigj postimit tuaj - Sot në sajtin tuaj patët <b>50 pëlqeim</b> - <b>Maqo Senko</b> pëlqeu postimin tuaj Zgjidhni Prekni <code>Retry</code>, kur të jeni prapë të lidhur, ose krijoni një faqe të zbrazët duke përdorur butonin më poshtë. S’mund të kihen skema, teksa jeni jashtë linje @@ -1512,25 +1509,6 @@ Language: sq_AL Kategori S’është caktuar Kategori - Muzeume në Londër - Tifozët Më të Zjarrtë të Botës - Dhjetë Kafehanet e Mia Më të Mira - Politikë - Muzikë - Lulishtari - Futboll - Gatim - Art - Fyejt Gjatë Javës - Lajme Në Internet - Xhike Baulja - Jam kaq i frymëzuar nga vepra e gdhendësit Çome Çomanga. Do t’i provoj këto teknika në veprën time të ardhshme - Frymëzohuni - Ndiqni sajtet tuaj të parapëlqyer dhe zbuloni blogje të rinj. - Vëzhgoni shtimin e publikut tuaj, me mjete analizimi në thellësi. - Shihni aty për aty komente dhe njoftime. - Me fuqinë e përpunuesit mund të postoni kudo që ndodheni. - Mirë se vini te krijuesi më popullor në botë i sajteve. Dështoi ngarkimi i medias U krijua faqe e zbrazët Faqja u krijua diff --git a/WordPress/src/main/res/values-sr/strings.xml b/WordPress/src/main/res/values-sr/strings.xml index 28c9461cebdd..b495d7f2cfd3 100644 --- a/WordPress/src/main/res/values-sr/strings.xml +++ b/WordPress/src/main/res/values-sr/strings.xml @@ -137,30 +137,12 @@ Language: sr_RS Добродошли! Нема недавних чланака Скенирај - <b>Јохан Брант</b> је одговорио на ваш чланак Одабери Додај категорију Додај нову категорију Категорије Није постављено Категорије - Музеји у Лондону - Политика - Музика - Баштованство - Фудбал - Кување - Уметност - Pamela Nguyen - Мојих десет најбољих кафића - Врло сам инспирисан радом фотографа Камерона Карстена. Испробаваћу ове технике на свом следећем - Веб новости - Инспиришите се - Помоћу моћног уређивача можете да објављујете у покрету. - Видите коментаре и обавештења у реалном времену. - Добродошли у најпопуларнији градитељ веб страница на свету. - Гледајте раст ваше публике помоћу дубинске аналитике. - Пратите своје омиљене локације и откривајте нове блогове. Учитавање садржаја није успело Празна страница је направљена Страница направљена diff --git a/WordPress/src/main/res/values-sv/strings.xml b/WordPress/src/main/res/values-sv/strings.xml index 76c2a943b916..72ddd7952e51 100644 --- a/WordPress/src/main/res/values-sv/strings.xml +++ b/WordPress/src/main/res/values-sv/strings.xml @@ -1503,9 +1503,6 @@ Language: sv_SE Inga nya inlägg Välkommen! Skanna - <b>Erik S</b> har svarat på ditt inlägg - Du har fått <b>50 gilla-markeringar</b> på webbplatsen i dag - <b>Anna S</b> gillade ditt inlägg Välj Tryck på ”Försök igen” när du har internetuppkoppling igen eller skapa en tom sida med knappen nedan. Layouter är inte tillgängliga när du saknar internetförbindelse @@ -1516,25 +1513,6 @@ Language: sv_SE Kategorier Inte inställd Kategorier - Muséer i London - Världens bästa fans - Mina tio favoritcaféer - Politik - Musik - Trädgårdsskötsel - Fotboll - Matlagning - Konst - Veckans nytt om rock’n roll - Webbnyheter - Pamela Nguyen - Jag inspireras verkligen av fotografen Cameron Karstens verk. Jag tänker prova mig på dessa tekniker snart - Låt dig inspireras - Följ dina favoritwebbplatser och hitta nya bloggar att läsa. - Se hur din publik växer med hjälp av detaljerad statistik. - Se kommentarer och aviseringar i realtid. - Med den kraftfulla redigeringsmiljön kan du skapa inlägg på rörlig fot. - Välkommen till världens populäraste webbplatsbyggare. Hämtningen av mediafiler misslyckades En blank sida har skapats Sidan har skapats diff --git a/WordPress/src/main/res/values-sw600dp/dimens.xml b/WordPress/src/main/res/values-sw600dp/dimens.xml index 0b08207845ae..0b74d89b33f5 100644 --- a/WordPress/src/main/res/values-sw600dp/dimens.xml +++ b/WordPress/src/main/res/values-sw600dp/dimens.xml @@ -7,11 +7,6 @@ 300dp - - 600dp - - 38sp - 325dp 250dp diff --git a/WordPress/src/main/res/values-tr/strings.xml b/WordPress/src/main/res/values-tr/strings.xml index 7d7692d49405..0103a154d060 100644 --- a/WordPress/src/main/res/values-tr/strings.xml +++ b/WordPress/src/main/res/values-tr/strings.xml @@ -1504,9 +1504,6 @@ Language: tr Yeni yazı yok Hoş geldiniz! Tarama - <b>Johan Brandt</b> yazınızı yanıtladı - Bugün sitenizde <b>50 beğeni</b> aldınız - <b>Madison Ruiz</b> yazınızı beğendi Seç Çevrim içi olduğunuzda yeniden dene üzerine dokunun ya da aşağıdaki düğmeyi kullanarak boş bir sayfa oluşturun. Çevrim dışıyken yerleşimler kullanılamaz @@ -1517,25 +1514,6 @@ Language: tr Kategoriler Ayarlanmadı Kategoriler - Londra\'daki müzeler - Dünyanın en iyi hayranları - En sevdiğim on kafe - Politika - Müzik - Bahçecilik - Futbol - Aşçılık - Sanat - Haftalık Rock n\' Roll - İnternet haberleri - Pamela Nguyen - Fotoğrafçı Cameron Karsten\'in çalışmalarından çok ilham alıyorum. Bu teknikleri bir sonraki adımda deneyeceğim - İlham alma - Favori sitelerinizi takip edin ve yeni bloglar keşfedin. - Kapsamlı istatistiklerle kitlenizin nasıl büyüdüğünü izleyin. - Yorumları ve bildirimleri gerçek zamanlı olarak görün. - Güçlü düzenleyici ile hareket halindeyken yazı gönderebilirsiniz. - Dünyanın en yaygın site oluşturucusuna hoş geldiniz. Ortam yüklenemedi Boş sayfa oluşturuldu Sayfa oluşturuldu diff --git a/WordPress/src/main/res/values-v27/styles_login.xml b/WordPress/src/main/res/values-v27/styles_login.xml new file mode 100644 index 000000000000..e67dafae5120 --- /dev/null +++ b/WordPress/src/main/res/values-v27/styles_login.xml @@ -0,0 +1,61 @@ + + + + + diff --git a/WordPress/src/main/res/values-v29/styles_login.xml b/WordPress/src/main/res/values-v29/styles_login.xml index 49f7fc02559d..89ddc106c404 100644 --- a/WordPress/src/main/res/values-v29/styles_login.xml +++ b/WordPress/src/main/res/values-v29/styles_login.xml @@ -1,8 +1,62 @@ + + - diff --git a/WordPress/src/main/res/values-vi/strings.xml b/WordPress/src/main/res/values-vi/strings.xml index 9375f620dd40..8765bb3c2d04 100644 --- a/WordPress/src/main/res/values-vi/strings.xml +++ b/WordPress/src/main/res/values-vi/strings.xml @@ -596,9 +596,6 @@ Language: vi_VN Không có bài viết mới Chào mừng! Quét - Hôm nay bạn nhận được <b>50 lượt thích</b> - <b>Madison Ruiz</b> thích bài viết của bạn - <b>Johan Brandt</b> bình luận bài viết của bạn Chọn Nhấn thử lại khi có mạng trở lại hoặc tạo một trang trống bằng nút phía dưới. Bố cục không khả dụng khi ngoại tuyến @@ -609,25 +606,6 @@ Language: vi_VN Chuyên mục Không dùng Chuyên mục - Hồ Nhất Duy - Bảo tàng London - Người hâm mộ tuyệt nhất thế giới - Top mười quán cà phê của tôi - Chính trị - Âm nhạc - Làm vườn - Bóng đá - Nấu ăn - Nghệ thuật - Bolero Việt Nam - Tin Tức - Tôi rất ấn tượng phong cách của nhiếp ảnh gia Cameron Karsten. Tôi sẽ cố áp dụng kỹ thuật của ông ấy vào lần tới - Truyền cảm hứng - Theo dõi sự tăng trưởng với công cụ phân tích. - Đọc bình luận và thông báo thời gian thực. - Đăng mọi nơi với công cụ chỉnh sửa mạnh mẽ. - Theo dõi blog yêu thích và khám phá blog mới. - Chào mừng đến với nền tảng blog nổi tiếng nhất thế giới. Tải media thất bại Đã tạo trang trống Đã tạo trang diff --git a/WordPress/src/main/res/values-zh-rCN/strings.xml b/WordPress/src/main/res/values-zh-rCN/strings.xml index 882affc7edcc..bfc0c693381a 100644 --- a/WordPress/src/main/res/values-zh-rCN/strings.xml +++ b/WordPress/src/main/res/values-zh-rCN/strings.xml @@ -1503,9 +1503,6 @@ Language: zh_CN 无近期文章 欢迎! 扫描 - <b>Johan Brandt</b> 回复了您的文章 - 您的站点今天收到了<b>50个喜欢</b> - <b>Madison Ruiz</b> 喜欢了您的文章 选择 重新上线后,点击“重试”,或使用下面的按钮创建空白页。 离线时布局不可用 @@ -1516,25 +1513,6 @@ Language: zh_CN 类别 未设置 类别 - 伦敦的博物馆 - 世界最佳粉丝 - 我的十大咖啡馆 - 政治 - 音乐 - 园艺 - 足球 - 烹饪 - 艺术 - 摇滚周刊 - 网络新闻 - Pamela Nguyen - 我受到摄影师 Cameron Karsten 的作品启发很大。 我会在下次尝试这些技术 - 受到启发 - 关注您喜欢的站点,发现新的阅读内容。 - 通过深入分析观察您的受众增长。 - 实时查看评论和通知。 - 有了这个功能强大的编辑器,您可以随时随地发表内容。 - 欢迎使用全球最热门的网站构建器。 媒体加载失败 创建了空白页面 页面已创建 diff --git a/WordPress/src/main/res/values-zh-rHK/strings.xml b/WordPress/src/main/res/values-zh-rHK/strings.xml index 06b6aff91cb4..5b2ec74ebca3 100644 --- a/WordPress/src/main/res/values-zh-rHK/strings.xml +++ b/WordPress/src/main/res/values-zh-rHK/strings.xml @@ -1414,9 +1414,6 @@ Language: zh_TW 沒有近期文章 歡迎! 掃瞄 - <b>Johan Brandt</b> 已回應你的文章 - 你的網站本日獲得 <b>50 個讚</b> - <b>Madison Ruiz</b> 已對你的文章按讚 選擇 請在重新上線時點選重試,或是使用下方按鈕建立空白頁面。 版面配置在離線時無法使用 @@ -1427,25 +1424,6 @@ Language: zh_TW 類別 未設定 類別 - 倫敦的博物館 - 全球最棒的粉絲 - 我最愛的十家咖啡館 - 政治 - 音樂 - 園藝 - 橄欖球 - 烹飪 - 藝術 - 每週搖滾 - 網路新聞 - Pamela Nguyen - 攝影師 Cameron Karsten 的作品讓我深受啟發。 我下次會嘗試這些技巧 - 激發靈感 - 追蹤你喜歡的網站並發掘新網誌。 - 透過深入分析資料拓展讀者群。 - 即時查看留言和通知。 - 這個功能強大的編輯器讓你隨時隨地都能發佈文章。 - 歡迎使用全球最受歡迎的網站建置工具。 媒體載入失敗 已建立空白頁面 已建立頁面 diff --git a/WordPress/src/main/res/values-zh-rTW/strings.xml b/WordPress/src/main/res/values-zh-rTW/strings.xml index 06b6aff91cb4..5b2ec74ebca3 100644 --- a/WordPress/src/main/res/values-zh-rTW/strings.xml +++ b/WordPress/src/main/res/values-zh-rTW/strings.xml @@ -1414,9 +1414,6 @@ Language: zh_TW 沒有近期文章 歡迎! 掃瞄 - <b>Johan Brandt</b> 已回應你的文章 - 你的網站本日獲得 <b>50 個讚</b> - <b>Madison Ruiz</b> 已對你的文章按讚 選擇 請在重新上線時點選重試,或是使用下方按鈕建立空白頁面。 版面配置在離線時無法使用 @@ -1427,25 +1424,6 @@ Language: zh_TW 類別 未設定 類別 - 倫敦的博物館 - 全球最棒的粉絲 - 我最愛的十家咖啡館 - 政治 - 音樂 - 園藝 - 橄欖球 - 烹飪 - 藝術 - 每週搖滾 - 網路新聞 - Pamela Nguyen - 攝影師 Cameron Karsten 的作品讓我深受啟發。 我下次會嘗試這些技巧 - 激發靈感 - 追蹤你喜歡的網站並發掘新網誌。 - 透過深入分析資料拓展讀者群。 - 即時查看留言和通知。 - 這個功能強大的編輯器讓你隨時隨地都能發佈文章。 - 歡迎使用全球最受歡迎的網站建置工具。 媒體載入失敗 已建立空白頁面 已建立頁面 diff --git a/WordPress/src/main/res/values/colors.xml b/WordPress/src/main/res/values/colors.xml index 69b55d2d0922..e1b0f698ae14 100644 --- a/WordPress/src/main/res/values/colors.xml +++ b/WordPress/src/main/res/values/colors.xml @@ -18,21 +18,6 @@ @color/neutral_0 - - @color/black_translucent_20 - @color/purple_20 - @color/purple_10 - @color/blue_20 - @color/blue_5 - @color/green_5 - @color/yellow_10 - @color/orange_10 - @color/pink_10 - - - @color/gray_0 - @color/gray_80 - @color/gray_50 @color/black diff --git a/WordPress/src/main/res/values/colors_studio_palette.xml b/WordPress/src/main/res/values/colors_studio_palette.xml index 29d6d304686d..fe82eb0cfa45 100644 --- a/WordPress/src/main/res/values/colors_studio_palette.xml +++ b/WordPress/src/main/res/values/colors_studio_palette.xml @@ -37,15 +37,11 @@ #984a9c #f2e9ed #ebcee0 - #e3afd5 - #d48fc8 #b35eb1 #984a9c - #7c3982 #35163b #1e0c21 - #f7a8c3 #eb6594 #e34c84 #c9356e @@ -62,7 +58,6 @@ #f5ece6 #f7dcc6 - #ffbf86 #e68b28 #d67709 #b26200 @@ -85,11 +80,9 @@ #00ba37 #00a32a #008a20 - #007017 #003008 #001c05 - #09b585 #001d2d #00101c diff --git a/WordPress/src/main/res/values/dimens.xml b/WordPress/src/main/res/values/dimens.xml index 008e86b4f402..70aee57e3744 100644 --- a/WordPress/src/main/res/values/dimens.xml +++ b/WordPress/src/main/res/values/dimens.xml @@ -102,6 +102,7 @@ 24dp 32dp 48dp + 92dp 64dp 68dp 114dp @@ -402,17 +403,8 @@ 40dp 60dp - - 4dp - 5dp - 0dp - - 24sp - - 88dp - 24dp @@ -639,9 +631,6 @@ -96dp - 320dp - 140dp - 160dp 20dp diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index fb0fdc2e0cbd..92e964dc88e0 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -8,7 +8,6 @@ Allows the app to disable WordPress notifications. - @string/app_name Write, edit, and publish from anywhere. WordPress icon @@ -3205,29 +3204,6 @@ Log In - Welcome to the world\’s most popular website builder. - With the powerful editor you can post on the go. - See comments and notifications in real time. - Watch your audience grow with in-depth analytics. - Follow your favorite sites and discover new blogs. - Getting Inspired - I am so inspired by photographer Cameron Karsten\’s work. I will be trying these techniques on my next - <b>Madison Ruiz</b> liked your post - You received <b>50 likes</b> on your site today - <b>Johan Brandt</b> responded to your post - Pamela Nguyen - Web News - Rock n\' Roll Weekly - Art - Cooking - Football - Gardening - Music - Politics - My Top Ten Cafes - The World\'s Best Fans - Museums in London - Invalid verification code Send link by email Create account @@ -4520,6 +4496,9 @@ translators: %s: Select control option value e.g: "Auto, 25%". --> Name Created Last used + Jetpack Android App on %s + WordPress Android App on %s + Your site credentials are invalid. Tap to reauthenticate. There\'s nothing here diff --git a/WordPress/src/main/res/values/styles.xml b/WordPress/src/main/res/values/styles.xml index 64b9c1cdcabe..da7f967d77db 100644 --- a/WordPress/src/main/res/values/styles.xml +++ b/WordPress/src/main/res/values/styles.xml @@ -1040,75 +1040,6 @@ 20 - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @color/white + @color/white + @color/red_50 + @color/white + @color/white + @color/black + @color/black + @color/white - - - - - - - diff --git a/libs/login/src/main/res/values-v27/themes.xml b/libs/login/src/main/res/values-v27/themes.xml deleted file mode 100644 index fc3d18102ebf..000000000000 --- a/libs/login/src/main/res/values-v27/themes.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/libs/login/src/main/res/values/attrs.xml b/libs/login/src/main/res/values/attrs.xml deleted file mode 100644 index 3f5b74b856cd..000000000000 --- a/libs/login/src/main/res/values/attrs.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - diff --git a/libs/login/src/main/res/values/colors.xml b/libs/login/src/main/res/values/colors.xml deleted file mode 100644 index b083926356f7..000000000000 --- a/libs/login/src/main/res/values/colors.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - #399ce3 - #0675c4 - #044b7a - - #c9356e - #8c1749 - - #d63638 - - #ffffff - #121212 - #000000 - - - #61121212 - - - #87a6bc - - - #c8d7e1 - #0087be - diff --git a/libs/login/src/main/res/values/dimens.xml b/libs/login/src/main/res/values/dimens.xml deleted file mode 100644 index 1dd0f0273f50..000000000000 --- a/libs/login/src/main/res/values/dimens.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - 0.12 - - 0dp - 24dp - 1dp - 2dp - 4dp - 6dp - 8dp - 10dp - 12dp - 16dp - 24dp - 32dp - 48dp - - 18dp - 24dp - 32dp - 38dp - 64dp - 40dp - 32dp - 40dp - - - 92dp - 4dp - - diff --git a/libs/login/src/main/res/values/shapes.xml b/libs/login/src/main/res/values/shapes.xml deleted file mode 100644 index db11611bf767..000000000000 --- a/libs/login/src/main/res/values/shapes.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/libs/login/src/main/res/values/themes.xml b/libs/login/src/main/res/values/themes.xml deleted file mode 100644 index b1e85c40f188..000000000000 --- a/libs/login/src/main/res/values/themes.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/libs/login/src/main/res/values/types.xml b/libs/login/src/main/res/values/types.xml deleted file mode 100644 index cff70fb995c7..000000000000 --- a/libs/login/src/main/res/values/types.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/libs/login/src/test/java/org/wordpress/android/login/LoginSiteAddressValidatorTest.java b/libs/login/src/test/java/org/wordpress/android/login/LoginSiteAddressValidatorTest.java deleted file mode 100644 index c31183a770e2..000000000000 --- a/libs/login/src/test/java/org/wordpress/android/login/LoginSiteAddressValidatorTest.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.wordpress.android.login; - -import androidx.arch.core.executor.testing.InstantTaskExecutorRule; -import androidx.lifecycle.Observer; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.robolectric.RobolectricTestRunner; -import org.wordpress.android.util.helpers.Debouncer; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; - -@RunWith(RobolectricTestRunner.class) -public class LoginSiteAddressValidatorTest { - @Rule - public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule(); - - private Debouncer mDebouncer; - private LoginSiteAddressValidator mValidator; - - @Before - public void setUp() { - mDebouncer = mock(Debouncer.class); - doAnswer(new Answer() { - @Override public Void answer(InvocationOnMock invocation) { - final Runnable runnable = invocation.getArgument(1); - runnable.run(); - return null; - } - }).when(mDebouncer).debounce(any(), any(Runnable.class), anyLong(), any(TimeUnit.class)); - - mValidator = new LoginSiteAddressValidator(mDebouncer); - } - - @After - public void tearDown() { - mValidator = null; - mDebouncer = null; - } - - @Test - public void testAnErrorIsReturnedWhenGivenAnInvalidAddress() { - // Arrange - assertThat(mValidator.getErrorMessageResId().getValue()).isNull(); - - // Act - mValidator.setAddress("invalid"); - - // Assert - assertThat(mValidator.getErrorMessageResId().getValue()).isNotNull(); - assertThat(mValidator.getCleanedSiteAddress()).isEqualTo("invalid"); - assertThat(mValidator.getIsValid().getValue()).isFalse(); - } - - @Test - public void testNoErrorIsReturnedButIsInvalidWhenGivenAnEmptyAddress() { - // Act - mValidator.setAddress(""); - - // Assert - assertThat(mValidator.getErrorMessageResId().getValue()).isNull(); - assertThat(mValidator.getIsValid().getValue()).isFalse(); - assertThat(mValidator.getCleanedSiteAddress()).isEqualTo(""); - } - - @Test - public void testTheErrorIsImmediatelyClearedWhenANewAddressIsGiven() { - // Arrange - final ArrayList> resIdValues = new ArrayList<>(); - mValidator.getErrorMessageResId().observeForever(new Observer() { - @Override public void onChanged(Integer resId) { - resIdValues.add(Optional.ofNullable(resId)); - } - }); - - // Act - mValidator.setAddress("invalid"); - mValidator.setAddress("another-invalid"); - - // Assert - assertThat(resIdValues).hasSize(4); - assertThat(resIdValues.get(0)).isEmpty(); - assertThat(resIdValues.get(1)).isNotEmpty(); - assertThat(resIdValues.get(2)).isEmpty(); - assertThat(resIdValues.get(3)).isNotEmpty(); - } - - @Test - public void testItReturnsValidWhenGivenValidURLs() { - // Arrange - final List validUrls = Arrays.asList( - "http://subdomain.example.com", - "http://example.ca", - "example.ca", - "subdomain.example.com", - " space-with-subdomain.example.net", - "https://subdomain.example.com/folder", - "http://subdomain.example.com/folder/over/there ", - "7.7.7.7", - "http://7.7.13.45", - "http://47.147.43.45/folder "); - - // Act and Assert - assertThat(validUrls).allSatisfy(new Consumer() { - @Override public void accept(String url) { - mValidator.setAddress(url); - - assertThat(mValidator.getErrorMessageResId().getValue()).isNull(); - assertThat(mValidator.getIsValid().getValue()).isTrue(); - } - }); - } -} diff --git a/settings.gradle b/settings.gradle index 968f72b13ac9..1f9f9364dd8e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -104,7 +104,6 @@ include ':libs:annotations' include ':libs:mocks' include ':libs:fluxc', ':libs:fluxc-annotations', ':libs:fluxc-processor' -include ':libs:login' include ':libs:posttypes' apply from: './config/gradle/included_builds.gradle' @@ -141,7 +140,6 @@ buildCache { def checkForRemoteBuildCacheOptimizedExperience() { assertSecretsApplied() - assertDeveloperPropertiesMatch("${rootDir}/libs/login/developer.properties") assertDeveloperPropertiesMatch("${rootDir}/libs/fluxc/developer.properties") assertJava21Amazon() }