diff --git a/apps/ubuntu_bootstrap/lib/app.dart b/apps/ubuntu_bootstrap/lib/app.dart index 5079938f7..5330966d6 100644 --- a/apps/ubuntu_bootstrap/lib/app.dart +++ b/apps/ubuntu_bootstrap/lib/app.dart @@ -26,6 +26,18 @@ import 'package:yaru/yaru.dart'; export 'app/installer_wizard.dart'; +/// Runs the installer. +/// +/// This function is the main entrypoint to the installer. It parses command-line arguments, +/// forwards arguments to subiquity, initializes the backend services used by the different +/// installation steps and starts the UI. +/// +/// Command-Line Options: +/// - `--dry-run`: Runs the Subiquity server in dry-run mode. +/// - `--dry-run-config`: Path to the configuration file for dry-run mode. +/// - `--machine-config`: Path to the machine configuration file for dry-run mode. +/// - `--source-catalog`: Path to the source catalog file for dry-run mode. +/// - `--try-or-install`: Displays the "Try or Install" page on startup. Future runInstallerApp( List args, { ThemeData? theme, @@ -79,7 +91,8 @@ Future runInstallerApp( .then((dir) => Directory(dir).existsSync() ? dir : null), ); - // conditional registration if not already registered by flavors or tests + // Conditional registration if not already registered by flavors or tests. All services must be + // registered here or their respective providers will fail to find them when building models. tryRegisterService(GnomeAccessibilityService.new); tryRegisterService( () => SubiquityActiveDirectoryService(getService()), diff --git a/apps/ubuntu_bootstrap/lib/app/installation_step.dart b/apps/ubuntu_bootstrap/lib/app/installation_step.dart index 971554877..370338853 100644 --- a/apps/ubuntu_bootstrap/lib/app/installation_step.dart +++ b/apps/ubuntu_bootstrap/lib/app/installation_step.dart @@ -6,6 +6,26 @@ import 'package:ubuntu_provision/ubuntu_provision.dart'; import 'package:ubuntu_utils/ubuntu_utils.dart'; import 'package:ubuntu_wizard/ubuntu_wizard.dart'; +/// Represents each step or page in the installer workflow. +/// +/// Each step corresponds to an enum value, which defines the page's behavior +/// and its role in the installer. The order of the enums determines the sequence +/// in which the steps appear in the installer. +/// +/// New pages must be added as additional enums to integrate into the workflow. +/// +/// Properties: +/// - `discreteStep`: Indicates whether the step is displayed as a discrete wizard step. Discrete +/// steps get their own circle progress indicator at the bottom of the window. +/// - `wizardStep`: Specifies if the step is part of the wizard flow. Wizard steps appear with +/// circle progress indicators and the default step navigation at the bottom of the window. +/// - `required`: Marks the step as mandatory in the workflow. +/// - `allowedToHide`: Determines if the step can be conditionally hidden. +/// +/// References: +/// - [A discrete, wizard step](https://github.com/canonical/ubuntu-desktop-provision-screenshots/blob/main/bootstrap/light/accessibility.png) +/// - [A non-discrete, wizard step](https://github.com/canonical/ubuntu-desktop-provision-screenshots/blob/main/bootstrap/light/rst.png) +/// - [A non-discrete, non-wizard step](https://github.com/canonical/ubuntu-desktop-provision-screenshots/blob/main/bootstrap/light/install-0.png) enum InstallationStep with RouteName { loading( LoadingPage.new, diff --git a/dev-docs/adding-a-new-page-to-the-installer.md b/dev-docs/adding-a-new-page-to-the-installer.md new file mode 100644 index 000000000..2697652ff --- /dev/null +++ b/dev-docs/adding-a-new-page-to-the-installer.md @@ -0,0 +1,67 @@ +# Adding a new page to the installer + +This project makes use of the [Model-view-viewmodel](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel) +architectural pattern. + +## Add your page +New pages can either be added to `apps/ubuntu_bootstrap/` for the main installer, `apps/ubuntu_init/` +for the first boot experience, or `packages/ubuntu_provision/` if they need to be shared between +the two flows. This document will assume you are adding a page to the main installer. + +- Under `apps/ubuntu_bootstrap/lib/pages`, create a new directory for the page you would like to add +ie: `apps/ubuntu_bootstrap/lib/pages/`. +- Create at minimum the following files (see existing pages for what these files should include): + - `_page.dart`: Defines the UI for the page you would like to add to the installer. + This forms the `view` within the MVVM pattern. + - `_model.dart`: Implements the provider and integrates with backend services to + support your page's functionality. This forms the `view-model` within the MVVM pattern. +- Export your page for use in the installer by adding it to `apps/ubuntu_bootstrap/lib/pages.dart` +- Add your page to the installation steps by adding it as an enum to `InstallationStep` in +`apps/ubuntu_bootstrap/lib/app/installation_step.dart`. The order of the `InstallationStep` enums +determines the order they appear in the installation process. + +Not all wizard pages are standalone. For more complicated flows, instead of adding a page, an +embedded wizard can be added to `InstallationStep`. These embedded wizards house their own wizard +steps, and may share state between multiple pages. See `StorageWizard` in +`apps/ubuntu_bootstrap/lib/pages/storage/storage_wizard.dart` + +## Add your service +- Under `apps/ubuntu_bootstrap/lib/services`, create a new file `_service.dart` +- Within this file, define the backend service that supports your page, and will be called to by +your model. This forms the `model` within the MVVM pattern. +- Export your service for use by your model by adding it to +`apps/ubuntu_bootstrap/lib/services.dart` +- Register your service for use in the installer by adding a statement +`tryRegisterService()` to `apps/ubuntu_bootstrap/lib/app.dart`. + +## Add UI text to the l10n file for translation +- Add any text that appear to the user on your page to the +`apps/ubuntu_bootstrap/lib/l10n/ubuntu_bootstrap_en.arb` file. +- In the `ubuntu_bootstrap` project root, run `flutter gen-l10n` to generate getters for use in your +page. +- Import `apps/ubuntu_bootstrap/lib/l10n/ubuntu_bootstrap_localizations.dart` and make use of the +variables you defined in `ubuntu_bootstrap_en.arb`. This allows for appropriate translated strings +to be chosen based on the end users selected locale. + +## Update tests +### `installer_wizard_test.dart` +- Under `test/`, add a directory for your new page and create at minimum a `buildModel` +helper function that returns a mocked model, making use of the `mokito` annotations for mock generation. +- Run `melos generate` to generate the mocks. +- For each failing test in `test/installer_wizard_test.dart` + - Initialize your mocked model + - Add the initialized model to the to the `ProviderScope` overrides. + - If needed, add statements to correctly navigate and identify your new page in the test flow, + using `tapNext()`, `pumpAndSettle()` and `expect()`. + +From here, appropriate unit tests for the new page should be added under the newly created +`test/` directory. + +### Integration tests +The integration tests are based on the absolute order in which pages appear in `InstallationStep` +in the `apps/ubuntu_bootstrap/lib/app/installation_step.dart` file. Adding a new page will thus +break all integration tests. + +To fix this, add a new `Future testPage()` function to +`UbuntuBootstrapPageTester` in the `/packages/ubuntu_provision_test/lib/src/bootstrap_tester.dart` +file. Adding this function in order to each integration test should fix the failing tests.