diff --git a/app/src/androidTest/kotlin/org/libremail/ui/onboarding/OnboardingFlowTest.kt b/app/src/androidTest/kotlin/org/libremail/ui/onboarding/OnboardingFlowTest.kt
index 0b32584..121a218 100644
--- a/app/src/androidTest/kotlin/org/libremail/ui/onboarding/OnboardingFlowTest.kt
+++ b/app/src/androidTest/kotlin/org/libremail/ui/onboarding/OnboardingFlowTest.kt
@@ -224,8 +224,10 @@ class OnboardingFlowTest {
// add" button sit below the fold of this scrolling screen, and a positional click on an
// off-screen button is a silent no-op (which is why this passed only on API 37's taller AVD).
waitForText(string(R.string.app_password_email))
- // Gmail requires 2-Step Verification before app passwords, so its screen (and only its
- // screen — see yahooSetup_hasNoTwoFactorHelpLink) links Google's setup article (issue #98).
+ // Gmail requires 2-Step Verification before app passwords, so its screen links Google's
+ // setup article (issue #98). iCloud gets the same kind of link, in Apple's own terminology
+ // (see icloudSetup_hasTwoFactorHelpLink); Yahoo does not (see
+ // yahooSetup_hasNoTwoFactorHelpLink).
composeTestRule.onNodeWithText(string(R.string.app_password_2fa_help))
.performScrollTo().assertIsDisplayed()
composeTestRule.onNodeWithText(string(R.string.app_password_email))
@@ -254,7 +256,27 @@ class OnboardingFlowTest {
// Yahoo's setup screen keeps its app-password link…
waitForText(string(R.string.app_password_open_page, "Yahoo Mail"))
- // …but gains no 2-Step Verification link: that prerequisite is Gmail-specific (issue #98).
+ // …but gains no two-factor help link: unlike Gmail and iCloud, Yahoo gates nothing on it
+ // (issue #98, #153).
composeTestRule.onNodeWithText(string(R.string.app_password_2fa_help)).assertDoesNotExist()
+ composeTestRule.onNodeWithText(string(R.string.app_password_2fa_help_icloud)).assertDoesNotExist()
+ }
+
+ @Test
+ fun icloudSetup_hasTwoFactorHelpLink() {
+ setOnboardingContent(FakeAccountRepository(), FakeMailRepository())
+
+ composeTestRule.onNodeWithText(string(R.string.onboarding_add_account)).performClick()
+ waitForText("iCloud Mail")
+ composeTestRule.onNodeWithText("iCloud Mail").performClick()
+
+ // Apple also won't issue an app-specific password until two-factor authentication is on,
+ // so iCloud's screen links Apple's own setup article too — using Apple's terminology for
+ // the button ("Two-Factor Authentication"), not Google's "2-Step Verification" (issue #153).
+ waitForText(string(R.string.app_password_2fa_help_icloud))
+ composeTestRule.onNodeWithText(string(R.string.app_password_2fa_help_icloud))
+ .performScrollTo().assertIsDisplayed()
+ composeTestRule.onNodeWithText(string(R.string.app_password_open_page, "iCloud Mail"))
+ .assertIsDisplayed()
}
}
diff --git a/app/src/main/kotlin/org/libremail/domain/model/MailProvider.kt b/app/src/main/kotlin/org/libremail/domain/model/MailProvider.kt
index 9d9ff26..5f93cc2 100644
--- a/app/src/main/kotlin/org/libremail/domain/model/MailProvider.kt
+++ b/app/src/main/kotlin/org/libremail/domain/model/MailProvider.kt
@@ -31,8 +31,8 @@ enum class MailProvider(
val appPasswordHelpUrl: String,
/**
* Setup instructions for the provider's two-factor prerequisite, or null when there isn't one.
- * Only Gmail refuses to create app passwords until 2-Step Verification is on, so only Gmail
- * links its setup article; Yahoo and iCloud gate nothing on it.
+ * Gmail and iCloud both refuse to issue app passwords until two-factor auth is on, so both
+ * link their own setup article; Yahoo gates nothing on it.
*/
val twoFactorHelpUrl: String? = null,
private val imapHost: String,
@@ -74,7 +74,12 @@ enum class MailProvider(
ICLOUD(
key = "icloud",
displayName = "iCloud Mail",
- appPasswordHelpUrl = "https://appleid.apple.com",
+ // Apple's own app-specific-password instructions, not just the generic Apple ID sign-in
+ // page — this article also states the two-factor prerequisite below.
+ appPasswordHelpUrl = "https://support.apple.com/en-us/102654",
+ // Like Gmail, Apple won't issue an app-specific password until two-factor authentication
+ // is on, so link Apple's dedicated setup article too (issue #153).
+ twoFactorHelpUrl = "https://support.apple.com/en-us/102660",
imapHost = "imap.mail.me.com",
smtpHost = "smtp.mail.me.com",
// Apple documents smtp.mail.me.com:587 with STARTTLS for iCloud Mail.
diff --git a/app/src/main/kotlin/org/libremail/ui/accountsetup/AppPasswordSetupScreen.kt b/app/src/main/kotlin/org/libremail/ui/accountsetup/AppPasswordSetupScreen.kt
index 357d4fe..2482252 100644
--- a/app/src/main/kotlin/org/libremail/ui/accountsetup/AppPasswordSetupScreen.kt
+++ b/app/src/main/kotlin/org/libremail/ui/accountsetup/AppPasswordSetupScreen.kt
@@ -151,15 +151,16 @@ fun AppPasswordSetupScreen(
)
Spacer(Modifier.height(12.dp))
- // Only Gmail has a two-factor prerequisite (see MailProvider.twoFactorHelpUrl): its
- // app-passwords page rejects accounts without 2-Step Verification, so point users
- // there first instead of sending them to the app-passwords page's dead end.
+ // Gmail and iCloud both have a two-factor prerequisite (see
+ // MailProvider.twoFactorHelpUrl): neither will issue an app password until it's on, so
+ // point users there first instead of sending them to the app-passwords page's dead
+ // end. Yahoo has no twoFactorHelpUrl, so this renders nothing for it.
provider.twoFactorHelpUrl?.let { twoFactorHelpUrl ->
OutlinedButton(
onClick = { openUrl(twoFactorHelpUrl) },
modifier = Modifier.fillMaxWidth(),
) {
- Text(stringResource(R.string.app_password_2fa_help))
+ Text(stringResource(twoFactorHelpLabel(provider)))
}
Spacer(Modifier.height(8.dp))
}
@@ -269,6 +270,16 @@ private fun providerIntro(provider: MailProvider): Int = when (provider) {
MailProvider.ICLOUD -> R.string.app_password_intro_icloud
}
+/**
+ * Button copy for the two-factor prerequisite link, in each provider's own terminology — Google
+ * calls it "2-Step Verification", Apple "Two-Factor Authentication". Only reached for providers
+ * that expose [MailProvider.twoFactorHelpUrl]; Yahoo has none, so its branch here is unused.
+ */
+private fun twoFactorHelpLabel(provider: MailProvider): Int = when (provider) {
+ MailProvider.ICLOUD -> R.string.app_password_2fa_help_icloud
+ MailProvider.GMAIL, MailProvider.YAHOO -> R.string.app_password_2fa_help
+}
+
private fun MailSecurity.label(): String = when (this) {
MailSecurity.SSL_TLS -> "SSL/TLS"
MailSecurity.STARTTLS -> "STARTTLS"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 976335b..529f981 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -184,6 +184,7 @@
Store this app password carefully — it grants full access to your email. LibreMail keeps it only on this device.
Create an app password for %1$s
How to turn on 2-Step Verification
+ How to turn on Two-Factor Authentication
Couldn\'t open your browser
Email address
App password
diff --git a/app/src/test/kotlin/org/libremail/domain/model/MailProviderTest.kt b/app/src/test/kotlin/org/libremail/domain/model/MailProviderTest.kt
index 5ddfa07..69d16de 100644
--- a/app/src/test/kotlin/org/libremail/domain/model/MailProviderTest.kt
+++ b/app/src/test/kotlin/org/libremail/domain/model/MailProviderTest.kt
@@ -82,7 +82,7 @@ class MailProviderTest {
}
@Test
- fun `only gmail links two-factor setup help, over https`() {
+ fun `gmail and icloud link two-factor setup help over https, yahoo does not`() {
// Gmail's app-passwords page rejects accounts without 2-Step Verification, so its setup
// screen must offer the setup article as a way out (issue #98).
val gmailUrl = assertNotNull(
@@ -91,9 +91,18 @@ class MailProviderTest {
)
assertTrue(gmailUrl.startsWith("https://"), "gmail 2FA help URL must be https")
- // Yahoo and iCloud gate nothing on two-factor, so they must not grow the extra link.
+ // Apple also won't issue an app-specific password until two-factor authentication is on,
+ // so iCloud needs the same escape hatch — pointed at Apple's dedicated article, not a
+ // generic Apple ID sign-in page (issue #153).
+ assertEquals("https://support.apple.com/en-us/102660", MailProvider.ICLOUD.twoFactorHelpUrl)
+
+ // Yahoo gates nothing on two-factor, so it must not grow the extra link.
assertNull(MailProvider.YAHOO.twoFactorHelpUrl)
- assertNull(MailProvider.ICLOUD.twoFactorHelpUrl)
+ }
+
+ @Test
+ fun `icloud app-password help points at Apple's specific instructions, not the generic sign-in page`() {
+ assertEquals("https://support.apple.com/en-us/102654", MailProvider.ICLOUD.appPasswordHelpUrl)
}
@Test