Commit Graph
287 Commits
Author SHA1 Message Date
Jason Ross 5ee1cbeecf Merge pull request #184 from JMR-dev/feat-71-strikethrough
feat(compose): add strikethrough to the formatting toolbar
2026-07-02 20:32:20 -05:00
Jason Ross ec88548f25 Merge main into feat-71-strikethrough 2026-07-02 20:21:11 -05:00
Jason Ross 1d63d6c7e1 Merge pull request #170 from JMR-dev/fix-148-async-seen-flag
fix(reader): propagate SEEN flag off the message-open critical path
2026-07-02 20:20:36 -05:00
Jason Ross ecbc052389 Merge main into fix-148-async-seen-flag 2026-07-02 20:08:15 -05:00
Jason Ross 0d0bb0d8fe Merge main into feat-71-strikethrough 2026-07-02 20:08:13 -05:00
Jason Ross 421f113349 Merge pull request #178 from JMR-dev/feat-162-advanced-settings-hierarchy
feat(settings): reorder the Advanced settings section
2026-07-02 20:07:42 -05:00
Jason Ross 31771e3a4e Merge main into feat-71-strikethrough 2026-07-02 19:56:24 -05:00
Jason Ross 228ea83288 Merge main into feat-162-advanced-settings-hierarchy 2026-07-02 19:56:22 -05:00
Jason Ross 0ef26da933 Merge pull request #173 from JMR-dev/feat-150-battery-deeplink
feat(onboarding): deep-link battery step nearer the per-app background-activity screen (best-effort)
2026-07-02 19:55:54 -05:00
Jason Ross 73f69c5326 Merge main into feat-150-battery-deeplink 2026-07-02 19:44:32 -05:00
Jason Ross aff7133bbb Merge main into feat-162-advanced-settings-hierarchy 2026-07-02 19:44:31 -05:00
Jason Ross 47b7efade7 Merge main into fix-148-async-seen-flag 2026-07-02 19:44:29 -05:00
Jason Ross 6a3b3d5a92 Merge main into feat-71-strikethrough 2026-07-02 19:44:29 -05:00
Jason Ross e22e6c14dd Merge pull request #175 from JMR-dev/feat-163-default-account
feat(settings): let the user set a default mail account
2026-07-02 19:44:01 -05:00
Jason Ross bd1f09fc06 Merge main into feat-71-strikethrough 2026-07-02 19:33:18 -05:00
Jason Ross 565a4ebfa8 Merge main into fix-148-async-seen-flag 2026-07-02 19:33:17 -05:00
Jason Ross d6100462a7 Merge main into feat-163-default-account 2026-07-02 19:33:16 -05:00
Jason Ross 56a6776f67 Merge main into feat-162-advanced-settings-hierarchy 2026-07-02 19:33:15 -05:00
Jason Ross 96fb91ce8c Merge main into feat-150-battery-deeplink 2026-07-02 19:33:13 -05:00
Jason Ross 7d92aa6feb Merge pull request #181 from JMR-dev/fix-157-notification-open-message
fix(notifications): reliably open the tapped message from a new-mail notification
2026-07-02 19:32:42 -05:00
Jason Ross 52d99d2bf9 Merge main into feat-150-battery-deeplink 2026-07-02 19:21:15 -05:00
Jason Ross 41d06c5c77 Merge main into fix-157-notification-open-message 2026-07-02 19:21:13 -05:00
Jason Ross 5eebe2e2b1 Merge main into feat-162-advanced-settings-hierarchy 2026-07-02 19:21:12 -05:00
Jason Ross d4412ce5f3 Merge main into feat-163-default-account 2026-07-02 19:21:11 -05:00
Jason Ross e2ab1b8953 Merge main into fix-148-async-seen-flag 2026-07-02 19:21:10 -05:00
Jason Ross 64918731eb Merge main into feat-71-strikethrough 2026-07-02 19:21:09 -05:00
Jason Ross 66534d6a0e Merge pull request #183 from JMR-dev/feat-159-report-required-email
feat(reporting): require a reply-to email and a 200-char minimum on problem reports
2026-07-02 19:20:14 -05:00
JMR-devandClaude Opus 4.8 60a8e7df1b feat(compose): add strikethrough to the formatting toolbar
The rich-text engine already fully supported RichStyle.Strikethrough
(HTML serialization, parsing, and rendering); only the toolbar control
was missing. Adds an "S" FormatButton next to Bold/Italic/Underline,
wired the same way (onToggleStyle + isStyled toggle state), plus the
format_strikethrough string resource for its onClickLabel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 19:19:14 -05:00
Jason Ross 7201ff3eaa Merge branch 'main' into fix-148-async-seen-flag 2026-07-02 19:16:12 -05:00
Jason Ross fce2ae981c Merge main into feat-163-default-account 2026-07-02 19:09:04 -05:00
Jason Ross e475c30f0a Merge main into feat-162-advanced-settings-hierarchy 2026-07-02 19:09:03 -05:00
Jason Ross 68e4e2471c Merge main into fix-157-notification-open-message 2026-07-02 19:09:02 -05:00
Jason Ross 2b38d06c6d Merge main into feat-159-report-required-email 2026-07-02 19:09:01 -05:00
Jason Ross 036df6b2fe Merge main into feat-150-battery-deeplink 2026-07-02 19:09:00 -05:00
Jason Ross 9e2b4bf49f Merge pull request #180 from JMR-dev/feat-153-icloud-onboarding-help
feat(accountsetup): add iCloud app-password/2FA help in onboarding
2026-07-02 19:08:34 -05:00
Jason Ross 6cf15c5af5 Merge branch 'main' into feat-153-icloud-onboarding-help 2026-07-02 18:57:57 -05:00
JMR-devandClaude Opus 4.8 2151bc6d7c feat(reporting): require a reply-to email and a 200-char minimum on problem reports
Adds friction to the "Report a Problem" form: a required email field (basic
local-part@domain.tld validation), a required consent notice about being
contacted at that address, and a 200-character minimum on the comment field
with a live "x/200" counter that turns red (with the field outline) until the
threshold is met. Submit stays disabled until both the comment and email are
valid, mirroring and extending the existing SUBMITTING gate. The email rides
along on DebugReport (userEmail) so it round-trips through the storage JSON
and the exact payload that's previewed, copied, saved, and POSTed. The new
ViewModel-level guard on submit() also fully integrates with the #161
success-confirmation dialog: invalid attempts never reach SUBMITTING/SUCCEEDED,
so the dialog flow is unaffected.

Closes #159

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 18:52:21 -05:00
Jason Ross 4b61c5f875 Merge branch 'main' into feat-150-battery-deeplink 2026-07-02 18:48:12 -05:00
Jason RossandClaude Opus 4.8 3c2bd0b138 ci: run autoupdate in the CI_CD environment so it can read AUTOUPDATE_TOKEN (#182)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 18:45:39 -05:00
JMR-devandClaude Opus 4.8 69be8a5c69 fix(notifications): reliably open the tapped message from a new-mail notification
MainActivity.onCreate only parsed the incoming intent (pendingCompose /
pendingOpenMessageId) when savedInstanceState == null, on the assumption
that a non-null value always means a config-change recreation (e.g.
rotation), where Android redelivers the same, already-handled intent and
re-parsing would just navigate to a duplicate destination.

But Android also passes a restored, non-null savedInstanceState when it
recreates the activity after the process was killed in the background and
is then relaunched by tapping a notification. There, intent is the new
tap, not a replay, but the guard swallowed it exactly like a rotation, so
pendingOpenMessageId was never set and the tap silently landed wherever
the restored back stack was (typically the mailbox) instead of the
message. That's the #157 regression from the original fix in a6ec00d
(#56). pendingCompose (mailto:/share intents) went through the identical
guard and had the same latent bug.

Replace the savedInstanceState check with IntentHandledMarker, which
marks the Intent instance itself once parsed. A config-change recreation
redelivers that same marked instance, so it's correctly skipped; a
genuinely new intent -- warm via onNewIntent or cold via onCreate after a
process-death relaunch -- is never marked yet, so it's always parsed.
This dedupes on the intent's own identity instead of an unreliable proxy
for it, so it can't confuse the two recreation paths.

Adds IntentHandledMarkerTest covering the marking contract: unhandled on
first look, recognized as handled on a redelivered instance, and still
unhandled on a freshly constructed (but content-equal) instance -- the
process-death case.

Closes #157

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 18:45:18 -05:00
Jason Ross 6490e5e461 Merge branch 'main' into feat-150-battery-deeplink 2026-07-02 18:37:23 -05:00
Jason RossClaude Opus 4.8github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
5f1e611886 feat(reporting): show a clear confirmation dialog after submitting a problem report (#171)
Replace the small inline "Report sent. Thank you!" text with an AlertDialog
carrying the fuller thank-you/no-guarantee message, and gate the screen's
auto-navigate-on-delete LaunchedEffect so it no longer fires while a submit
is in flight or has just succeeded — the dialog's acknowledgement is what
calls onDone() for that path instead. This closes the race where
ReportUploadWorker deletes the report row (and thus flips state.exists to
false) moments after SubmitUiState.SUCCEEDED, which could previously
navigate the user away before the confirmation was ever visible. Discard
and the other non-success paths (FAILED/UNAVAILABLE) are unaffected and
still auto-navigate immediately.

Closes #161

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-02 23:36:36 +00:00
JMR-devandClaude Opus 4.8 72ee1d3774 feat(accountsetup): add iCloud app-password/2FA help in onboarding
iCloud's guided setup screen previously linked only a generic Apple ID
sign-in page and had no two-factor help link, unlike Gmail. Apple also
requires two-factor authentication before it will issue an
app-specific password, so:

- MailProvider.ICLOUD.appPasswordHelpUrl now points at Apple's actual
  app-specific-password instructions (support.apple.com/en-us/102654)
  instead of the generic appleid.apple.com landing page.
- MailProvider.ICLOUD.twoFactorHelpUrl now points at Apple's dedicated
  two-factor-authentication article (support.apple.com/en-us/102660),
  so the existing generic 2FA-help button in AppPasswordSetupScreen
  picks it up automatically, positioned the same as Gmail's (#152).
- The 2FA button now reads "How to turn on Two-Factor Authentication"
  for iCloud instead of Google's "2-Step Verification" wording, via a
  new app_password_2fa_help_icloud string.

Closes #153

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 18:34:14 -05:00
Jason RossClaude Opus 4.8github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
99f6ef19e4 fix(onboarding): request notification permission after welcome screen renders (#168)
* fix(onboarding): request notification permission after welcome screen renders

The POST_NOTIFICATIONS request fired from a MainActivity-root
NotificationPermissionEffect whose LaunchedEffect(Unit) ran on the very
first composition, so the system dialog could pop the instant the icon
was tapped — overlapping cold start/splash before any onboarding context
was on screen.

Move the effect into OnboardingWelcomeScreen so it fires once that screen
(the onboarding start destination) is composed and visible, with the
welcome content behind the dialog. Already-onboarded users launch
straight into the mailbox and never compose the welcome screen, so they
are unaffected; the API 33+ gate and the already-granted no-op are
preserved unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(onboarding): grant POST_NOTIFICATIONS in onboarding E2E to fix API 33+ flow

Moving the notification-permission request into OnboardingWelcomeScreen
(#151) means the system POST_NOTIFICATIONS dialog now pops when that
screen composes. On API 33+ (where it became a runtime permission) the
dialog backgrounded the activity mid-flow, so OnboardingFlowTest failed
with "No compose hierarchies found" on API 33/34/35/36/37 while API
29–32 stayed green.

Pre-grant the permission via a GrantPermissionRule so the dialog never
appears during the flow, guarded for API 33+ (the permission does not
exist below TIRAMISU, so grant nothing there to avoid erroring on older
devices). Adds the androidx.test:rules dependency that provides the rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-02 23:23:30 +00:00
Jason RossandClaude Opus 4.8 f353bdc5ec test(compose): add RichTextEditor unit + ComposeScreen UI coverage (#176)
Closes the #36 test-coverage gap left after the rich-text editor shipped:

- RichTextEditorTest.kt (new JVM unit test, 20 cases): exercises the
  Compose-editor glue in RichTextEditor.kt that had no direct coverage -
  applyStyle/applyBlock/applyLink, the AnnotatedString.toRichContent() <->
  RichTextContent.toAnnotatedString() round trip across every span/link/
  alignment/image/baseStyle channel, and the isStyled/hasBlock predicate
  FormattingToolbar uses to light up its buttons. All plain TextFieldValue/
  AnnotatedString/Color types, so it runs on the JVM with no emulator.
  applyBlock/applyLink go from private to internal so the test can reach
  them directly, mirroring applyStyle's existing internal visibility.

- ComposeScreenTest.kt (androidTest, following this file's existing
  createAndroidComposeRule + fake-repository harness): one case taps the
  bullet-list toolbar button and asserts the sent message carries the
  <ul><li> HTML (block markers apply to the caret's line, so no fragile
  on-device range selection is needed); another asserts every toolbar
  button's click-action label matches its string resource, verifying the
  accessibility claim (the labels are onClickLabel, not contentDescription).

Headings remain deliberately out of scope per the ticket - no model/
toolbar changes here.

Closes #36

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:09:40 +00:00
Jason RossandClaude Opus 4.8 e72a9b15c6 docs(compose): correct RichTextEditor toolbar accessibility KDoc (onClickLabel, not contentDescription) (#179)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 17:52:48 -05:00
JMR-devandClaude Opus 4.8 30c5251a68 feat(settings): reorder the Advanced settings section
Reorder the Advanced block's SwitchRows to Push Mail, Load Remote
Images, Encrypt Local Cache, Require Screen Lock, then Allow insecure
STARTTLS fallback (moved last). Relocate the Background battery usage
row out of Advanced entirely and into the main settings list, directly
above local retention ("Storage on this device"), since it's common
enough (OEM battery optimization delaying push mail) that it shouldn't
be hidden behind "Advanced". Pure composable placement — no string
changes, no behavior change to any toggle.

Closes #162

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 17:46:59 -05:00
Jason RossClaude Opus 4.8github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
97d6f9303b fix(accountsetup): show 2-Step Verification link before app-password link for Gmail (#166)
2-Step Verification is a prerequisite for Gmail's app-passwords page, so
render the twoFactorHelpUrl button first when present, then the
appPasswordHelpUrl button. Previously the prerequisite link rendered
second, so a user without 2FA enabled would hit a dead end on the first
button before noticing the second. No visible change for Yahoo/iCloud,
which have no twoFactorHelpUrl.

Closes #152

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-02 22:42:37 +00:00
Jason Ross 788c450cad Merge branch 'main' into feat-163-default-account 2026-07-02 17:34:20 -05:00
JMR-devandClaude Opus 4.8 e06054afb6 feat(settings): let the user set a default mail account
Persist a defaultAccountId preference (SettingsRepository/AppSettings,
following the existing key/field/setter pattern), add a "Default account"
switch to AccountSettingsScreen, and prefer it in ComposeViewModel's
from-account fallback (fromAccountId -> valid default -> first account).
Deleting the default account clears the preference (SettingsRepository
.clearDefaultAccountId), and a stale/foreign id is validated against the
current account list before use so it can never crash or point at a
missing account.

Closes #163

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 17:28:22 -05:00