Onboarding: require GPL-3.0 license agreement as the first screen #172

Closed
opened 2026-07-02 22:16:58 +00:00 by JMR-dev · 0 comments
JMR-dev commented 2026-07-02 22:16:58 +00:00 (Migrated from github.com)

Context

The onboarding graph (ui/LibreMailApp.kt, onboardingGraph()) currently starts at
Routes.ONBOARDING_WELCOME (navigation(startDestination = Routes.ONBOARDING_WELCOME, route = Routes.ONBOARDING)), shown whenever AppViewModel.startDestination sees no accounts. This
ticket inserts a new screen ahead of it: the user must see the GPL-3.0 license and tap
Agree before reaching the welcome screen (or anything else in the app), or Decline to
exit without using the app.

The repo already has the full license text at LICENSE (674 lines, standard GPLv3) — nothing is
currently bundled for in-app display, so that text needs a runtime-accessible copy.

This directly changes the sequencing #151 depends on: #151 moves the notification-permission
request to fire once "the onboarding flow's first screen (OnboardingWelcomeScreen)" has
appeared. Once this ticket lands, the welcome screen is the second screen, not the first —
the notification prompt should fire when that (now second) screen loads, per this ticket's own
spec ("Notifications ask comes once the next screen loads"). #151's wording will need a one-line
correction to stop calling the welcome screen "the first screen" once this merges — flagging here
so it isn't missed; not fixing it in this ticket since #151 is a separate, already-scoped unit of
work.

Scope

  • Add Routes.ONBOARDING_LICENSE and make it the onboardingGraph's startDestination
    (ahead of Routes.ONBOARDING_WELCOME).
  • New ui/onboarding/LicenseScreen.kt: shows the full GPL-3.0 text (scrollable) with
    Agree and Decline buttons. Bundle the text for runtime display — e.g.
    res/raw/license.txt or an asset — sourced from the root LICENSE file. Risk to flag in
    the PR
    : this creates two copies of the license text that can drift out of sync; either
    wire a build step that copies LICENSE into the bundled resource at build time, or leave a
    clear comment on both files pointing at each other so a manual license update isn't missed.
  • Agree is disabled until the user has scrolled to the end of the license text. Use the
    same rememberScrollState() + Modifier.verticalScroll pattern already used elsewhere in
    this codebase (e.g. AppPasswordSetupScreen.kt, ManualSetupScreen.kt,
    ReportReviewScreen.kt) rather than introducing a new list-based pattern just for this
    screen. Gate on scrollState.value >= scrollState.maxValue: this also correctly
    self-resolves the "whole license fits on one large screen, no scrolling needed" case, since
    maxValue is 0 when nothing needs scrolling and 0 >= 0 is trivially true — no special
    casing required.
  • Decline exits the app. MainActivity is the app's only activity (a single-activity
    Compose app), so finish() should suffice; use finishAffinity() instead if that ever
    proves insufficient (e.g. if a launched sub-activity, like the AppAuth/OAuth redirect
    activity, is still on the back stack). No confirmation dialog specified — a direct exit on
    tap is the simplest reading of the request; add one only if it turns out to be needed.
  • Persist acceptance (e.g. SettingsRepository.licenseAccepted: Boolean, following the
    existing key/AppSettings-field/setter pattern). This is necessary, not optional: since
    AppViewModel.startDestination sends any account-less launch back into Routes.ONBOARDING,
    without a persisted flag a user who agrees but then exits before adding an account (or the
    process dies mid-onboarding) would be forced to agree again on relaunch. Gate
    onboardingGraph's start destination on this flag (already-accepted skips straight to
    ONBOARDING_WELCOME).
  • Hard-gate navigation: no way to reach ONBOARDING_WELCOME without tapping Agree — no back
    gesture/button skip (there's nothing to go back to, since this is now first), and no way
    around the scroll-to-end requirement.
  • Remove/relocate the current NotificationPermissionEffect() trigger point to align with
    #151's fix once both land: the ask fires when OnboardingWelcomeScreen (now the second
    screen) loads, never on this new license screen.

Acceptance criteria

  • On a fresh install, the GPL-3.0 license screen is the very first thing shown — before the
    welcome screen, before any permission prompt.
  • Agree stays disabled until the user has scrolled to the end of the license text.
  • Tapping Decline exits the app without navigating anywhere else.
  • The app cannot be navigated past this screen any other way.
  • The notification-permission prompt is not shown on the license screen; it appears once the next
    screen (welcome) has loaded (coordinates with #151).
  • Relaunching after agreeing, but before an account has been added, does not show the license
    screen again.

Relevant files

  • ui/LibreMailApp.kt (onboardingGraph, ~lines 290-294), ui/navigation/Routes.kt,
    ui/AppViewModel.kt, data/settings/SettingsRepository.kt, root LICENSE,
    new ui/onboarding/LicenseScreen.kt.

Dependencies

Sequencing note for #151 (notification prompt timing) — see Context above.

## Context The onboarding graph (`ui/LibreMailApp.kt`, `onboardingGraph()`) currently starts at `Routes.ONBOARDING_WELCOME` (`navigation(startDestination = Routes.ONBOARDING_WELCOME, route = Routes.ONBOARDING)`), shown whenever `AppViewModel.startDestination` sees no accounts. This ticket inserts a new screen **ahead of it**: the user must see the GPL-3.0 license and tap **Agree** before reaching the welcome screen (or anything else in the app), or **Decline** to exit without using the app. The repo already has the full license text at `LICENSE` (674 lines, standard GPLv3) — nothing is currently bundled for in-app display, so that text needs a runtime-accessible copy. This directly changes the sequencing #151 depends on: #151 moves the notification-permission request to fire once "the onboarding flow's first screen (`OnboardingWelcomeScreen`)" has appeared. Once this ticket lands, the welcome screen is the **second** screen, not the first — the notification prompt should fire when *that* (now second) screen loads, per this ticket's own spec ("Notifications ask comes once the next screen loads"). #151's wording will need a one-line correction to stop calling the welcome screen "the first screen" once this merges — flagging here so it isn't missed; not fixing it in this ticket since #151 is a separate, already-scoped unit of work. ## Scope - [ ] Add `Routes.ONBOARDING_LICENSE` and make it the `onboardingGraph`'s `startDestination` (ahead of `Routes.ONBOARDING_WELCOME`). - [ ] New `ui/onboarding/LicenseScreen.kt`: shows the full GPL-3.0 text (scrollable) with **Agree** and **Decline** buttons. Bundle the text for runtime display — e.g. `res/raw/license.txt` or an asset — sourced from the root `LICENSE` file. **Risk to flag in the PR**: this creates two copies of the license text that can drift out of sync; either wire a build step that copies `LICENSE` into the bundled resource at build time, or leave a clear comment on both files pointing at each other so a manual license update isn't missed. - [ ] **Agree is disabled until the user has scrolled to the end of the license text.** Use the same `rememberScrollState()` + `Modifier.verticalScroll` pattern already used elsewhere in this codebase (e.g. `AppPasswordSetupScreen.kt`, `ManualSetupScreen.kt`, `ReportReviewScreen.kt`) rather than introducing a new list-based pattern just for this screen. Gate on `scrollState.value >= scrollState.maxValue`: this also correctly self-resolves the "whole license fits on one large screen, no scrolling needed" case, since `maxValue` is `0` when nothing needs scrolling and `0 >= 0` is trivially true — no special casing required. - [ ] **Decline exits the app.** `MainActivity` is the app's only activity (a single-activity Compose app), so `finish()` should suffice; use `finishAffinity()` instead if that ever proves insufficient (e.g. if a launched sub-activity, like the AppAuth/OAuth redirect activity, is still on the back stack). No confirmation dialog specified — a direct exit on tap is the simplest reading of the request; add one only if it turns out to be needed. - [ ] Persist acceptance (e.g. `SettingsRepository.licenseAccepted: Boolean`, following the existing key/`AppSettings`-field/setter pattern). This is necessary, not optional: since `AppViewModel.startDestination` sends *any* account-less launch back into `Routes.ONBOARDING`, without a persisted flag a user who agrees but then exits before adding an account (or the process dies mid-onboarding) would be forced to agree again on relaunch. Gate `onboardingGraph`'s start destination on this flag (already-accepted skips straight to `ONBOARDING_WELCOME`). - [ ] Hard-gate navigation: no way to reach `ONBOARDING_WELCOME` without tapping Agree — no back gesture/button skip (there's nothing to go back to, since this is now first), and no way around the scroll-to-end requirement. - [ ] Remove/relocate the current `NotificationPermissionEffect()` trigger point to align with #151's fix once both land: the ask fires when `OnboardingWelcomeScreen` (now the second screen) loads, never on this new license screen. ## Acceptance criteria - On a fresh install, the GPL-3.0 license screen is the very first thing shown — before the welcome screen, before any permission prompt. - Agree stays disabled until the user has scrolled to the end of the license text. - Tapping Decline exits the app without navigating anywhere else. - The app cannot be navigated past this screen any other way. - The notification-permission prompt is not shown on the license screen; it appears once the next screen (welcome) has loaded (coordinates with #151). - Relaunching after agreeing, but before an account has been added, does not show the license screen again. ## Relevant files - `ui/LibreMailApp.kt` (`onboardingGraph`, ~lines 290-294), `ui/navigation/Routes.kt`, `ui/AppViewModel.kt`, `data/settings/SettingsRepository.kt`, root `LICENSE`, new `ui/onboarding/LicenseScreen.kt`. ## Dependencies Sequencing note for #151 (notification prompt timing) — see Context above.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: JMR-dev/LibreMail#172