From 16e2d96b66cc2b9a6be9f8e01f35d3f8fa4ae7d8 Mon Sep 17 00:00:00 2001 From: ssbg04 Date: Fri, 23 Oct 2026 18:28:39 +0800 Subject: [PATCH] Add Flutter cross-platform UI architecture --- DESIGN.md | 561 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 561 insertions(+) create mode 100644 DESIGN.md diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..d09ee04 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,561 @@ +# Production-Ready Flutter Cross-Platform UI Architecture + +## Overview + +This document defines the architecture for a maintainable Flutter application targeting Windows, Linux, macOS, Android, iOS, and Web. + +**Selected stack:** Flutter + Forui + Riverpod + +The architecture combines a shared design system, feature-first modules, adaptive application shells, and Riverpod-based application state. Desktop, mobile, and browser interfaces share product logic and visual standards without being forced to look or behave identically. + +## 1. Architecture Decisions + +| Area | Decision | +| --- | --- | +| UI foundation | Forui | +| Application framework | Flutter | +| State management | Riverpod, with code generation where beneficial | +| Routing | `go_router` | +| Architecture | Feature-first, with presentation, domain, and data layers where justified | +| Responsive design | Constraint-based layouts and centralized breakpoints | +| Theming | Semantic design tokens and Forui themes | +| Forms | Shared form components and validation rules | +| Platform differences | Separate shell/navigation implementations where needed | +| Testing | Unit, widget, integration, and platform-specific smoke tests | + +### Key architectural rule + +Forui supplies the visual component foundation; the product design system supplies application standards. Riverpod manages state and dependency injection; it must not contain widget styling or layout decisions. + +## 2. Version and Dependency Policy + +Forui is actively maintained and provides more than 50 components, theme generation, localization support, and widgets for desktop and touch interfaces. Its documentation currently lists version `0.27.6`; that release requires a sufficiently recent Flutter and Dart SDK. + +Before starting, verify the installed SDK: + +```bash +flutter --version +flutter doctor -v +``` + +Plan around Flutter 3.47 or newer for the current Forui release, and confirm the SDK constraints in package metadata before locking dependencies. Do not upgrade Flutter blindly if other applications depend on an older version. + +### Recommended dependencies + +Use `flutter pub add` to resolve versions compatible with the installed SDK instead of manually guessing versions for transitive packages. + +```bash +flutter pub add forui +flutter pub add flutter_riverpod +flutter pub add riverpod_annotation +flutter pub add go_router +flutter pub add shared_preferences +flutter pub add path_provider +flutter pub add file_selector + +flutter pub add --dev riverpod_generator +flutter pub add --dev build_runner +flutter pub add --dev riverpod_lint +flutter pub add --dev custom_lint +``` + +| Dependency | Purpose | +| --- | --- | +| `forui` | UI components, themes, overlays, and navigation components | +| `flutter_riverpod` | State management and dependency injection | +| `riverpod_annotation` | Optional provider code generation | +| `go_router` | Named routes, nested navigation, browser URLs, and deep links | +| `shared_preferences` | Lightweight preferences such as theme and density | +| `path_provider` | Platform-specific application directories | +| `file_selector` | Cross-platform file selection | +| `riverpod_generator` | Generates Riverpod provider code | +| `build_runner` | Runs Dart code generators | +| `riverpod_lint`, `custom_lint` | Enforces Riverpod coding practices | + +Riverpod separates application logic from UI and exposes loading, error, and data states explicitly. `go_router` supports all six target platforms and URL-based navigation. + +Do not install a package merely because it is popular. Add database, networking, Markdown parsing, syntax highlighting, and persistence packages only when the application requires them. Keep the dependency count small, review licenses, and commit `pubspec.lock` for the application. + +## 3. Folder Architecture + +Use `lib/` as the production source root. Keep feature-specific UI near its feature, shared design components under `design_system/`, and platform shells under `app/shells/`. + +```text +lib/ +├── main.dart +│ +├── app/ +│ ├── app.dart +│ ├── app_router.dart +│ ├── app_providers.dart +│ ├── app_lifecycle.dart +│ ├── bootstrap.dart +│ └── shells/ +│ ├── app_shell.dart +│ ├── desktop_shell.dart +│ ├── mobile_shell.dart +│ ├── tablet_shell.dart +│ └── web_shell.dart +│ +├── core/ +│ ├── config/ +│ │ ├── app_config.dart +│ │ └── environment.dart +│ ├── errors/ +│ ├── logging/ +│ ├── platform/ +│ │ ├── platform_capabilities.dart +│ │ └── platform_policy.dart +│ ├── responsive/ +│ │ ├── breakpoints.dart +│ │ ├── layout_class.dart +│ │ ├── responsive_builder.dart +│ │ └── window_constraints.dart +│ ├── accessibility/ +│ └── utils/ +│ +├── design_system/ +│ ├── tokens/ +│ │ ├── colors.dart +│ │ ├── spacing.dart +│ │ ├── typography.dart +│ │ ├── radii.dart +│ │ ├── dimensions.dart +│ │ ├── elevation.dart +│ │ └── motion.dart +│ ├── theme/ +│ │ ├── app_theme.dart +│ │ ├── theme_mode_provider.dart +│ │ ├── theme_extensions.dart +│ │ └── generated/ +│ ├── components/ +│ │ ├── buttons/ +│ │ ├── fields/ +│ │ ├── navigation/ +│ │ ├── dialogs/ +│ │ ├── menus/ +│ │ ├── feedback/ +│ │ ├── surfaces/ +│ │ ├── lists/ +│ │ ├── tables/ +│ │ └── loading/ +│ ├── layouts/ +│ │ ├── page_container.dart +│ │ ├── content_constraint.dart +│ │ ├── split_view.dart +│ │ └── section_layout.dart +│ └── icons/ +│ +├── shared/ +│ ├── models/ +│ ├── services/ +│ ├── repositories/ +│ ├── extensions/ +│ └── widgets/ +│ +├── features/ +│ ├── home/ +│ │ ├── domain/ +│ │ ├── data/ +│ │ └── presentation/ +│ │ ├── screens/ +│ │ ├── widgets/ +│ │ └── providers/ +│ ├── editor/ +│ │ ├── domain/ +│ │ ├── data/ +│ │ └── presentation/ +│ │ ├── screens/ +│ │ ├── widgets/ +│ │ └── providers/ +│ ├── preview/ +│ ├── documents/ +│ ├── search/ +│ ├── settings/ +│ └── about/ +│ +├── l10n/ +└── generated/ +``` + +### Feature module convention + +When a feature grows, use this internal structure: + +```text +features/editor/ +├── domain/ +│ ├── entities/ +│ ├── repositories/ +│ └── use_cases/ +├── data/ +│ ├── datasources/ +│ ├── models/ +│ └── repositories/ +└── presentation/ + ├── screens/ + ├── widgets/ + └── providers/ + ├── editor_state.dart + ├── editor_state.g.dart + └── editor_controller.dart +``` + +Do not create empty `domain/`, `data/`, or `use_cases/` folders for every small feature. Introduce those layers when there is actual business logic, data persistence, or an external source to abstract. + +### Architecture boundaries + +- `design_system/` may depend on Flutter and Forui, but must not depend on product features. +- `features/` may use design-system components and Riverpod providers. +- `app/` composes features, routing, global providers, and shells. +- `core/` contains infrastructure and platform policies, not feature-specific UI. +- Domain logic must not import Flutter widgets or Forui. +- Generated files are outputs; never edit `.g.dart` files manually. + +## 4. Design Tokens and Visual Standards + +Use semantic tokens instead of raw hex values scattered throughout widgets. Start with a neutral, restrained interface, then customize the Forui theme using its theme tooling. Forui provides a built-in neutral theme and a visual theme generator. + +### Recommended starting values + +These defaults are design-system values, not mandatory platform specifications. + +| Token | Value | +| --- | ---: | +| Base spacing | 4 logical px | +| `space1` | 4 | +| `space2` | 8 | +| `space3` | 12 | +| `space4` | 16 | +| `space6` | 24 | +| `space8` | 32 | +| `space12` | 48 | +| Small radius | 6 | +| Standard radius | 10 | +| Large radius | 14 | +| Standard border | 1 | +| Standard icon | 20 | +| Compact control height | 36 | +| Standard control height | 40 | +| Comfortable control height | 44–48 | + +### Color palette + +| Role | Starting value | Use | +| --- | --- | --- | +| Primary | `#2563EB` | Actions, links, and selection | +| Light background | `#F8FAFC` | Light-mode page canvas | +| Dark surface | `#111827` | Dark-mode panels | +| Border | `#E2E8F0` | Light-mode separators | + +Define semantic roles for `background`, `surface`, `surfaceElevated`, `textPrimary`, `textSecondary`, `border`, `primary`, `primaryForeground`, `success`, `warning`, `danger`, and `focus`. Define corresponding dark-mode roles rather than simply inverting every color. + +### Token implementation + +```dart +abstract final class AppSpacing { + static const double x1 = 4; + static const double x2 = 8; + static const double x3 = 12; + static const double x4 = 16; + static const double x6 = 24; + static const double x8 = 32; + static const double x12 = 48; +} + +abstract final class AppDimensions { + static const double sidebarExpanded = 248; + static const double sidebarCollapsed = 64; + static const double desktopToolbar = 44; + static const double mobileToolbar = 56; + static const double minimumTouchTarget = 48; + static const double maxReadingWidth = 800; +} +``` + +Forui theme colors and component styles are the source of truth for Forui widgets. Use custom tokens for application-level spacing, content constraints, editor typography, and components Forui does not cover. + +### Typography rules + +- Use a consistent system sans-serif font for UI text. +- Start body text at 14–16 logical px. +- Do not reduce secondary text below comfortable readability merely to fit more information. +- Give editor text a separate configurable reading scale, with optional monospace styling for code. +- Preserve user text scaling; avoid fixed-height text containers that clip enlarged text. +- Use weight and spacing for hierarchy before adding more colors. + +## 5. Responsive Shell Architecture + +Flutter distinguishes responsive design, which fits available space, from adaptive design, which chooses appropriate interactions and layouts. Use both. + +The shell makes three decisions: + +1. Available width: compact, medium, expanded, or wide layout. +2. Platform capabilities: desktop keyboard/mouse, mobile touch, browser URLs, and file access. +3. Feature needs: whether the current screen benefits from a split view, sidebar, bottom navigation, or full-screen content. + +### Breakpoint policy + +| Layout class | Available width | Default layout | +| --- | --- | --- | +| Compact | `< 600` | One main pane; bottom navigation or compact app bar | +| Medium | `600–839` | Navigation rail or drawer; optional secondary pane | +| Expanded | `840–1199` | Persistent sidebar; main content | +| Wide | `1200+` | Sidebar plus optional secondary panel | + +These thresholds are starting points. Base decisions on `LayoutBuilder` constraints rather than physical device model or orientation alone. + +### Shell selection + +| Shell | Responsibilities | +| --- | --- | +| `AppShell` | Routing, theme, global shortcuts, and app state | +| `DesktopShell` | Window-aware sidebar, toolbar, and split panes | +| `MobileShell` | Touch-first navigation and compact actions | +| `TabletShell` | Rail navigation and adaptive panels | +| `WebShell` | URL-aware navigation and browser-friendly behavior | + +The web shell does not need to duplicate the desktop shell. At a wide browser viewport it can reuse desktop workspace components while retaining web routing, browser history, and browser-specific behavior. + +### Breakpoint implementation + +```dart +enum LayoutClass { + compact, + medium, + expanded, + wide, +} + +LayoutClass layoutClassFor(double width) { + if (width < 600) return LayoutClass.compact; + if (width < 840) return LayoutClass.medium; + if (width < 1200) return LayoutClass.expanded; + return LayoutClass.wide; +} +``` + +```dart +class ResponsiveShell extends StatelessWidget { + const ResponsiveShell({ + super.key, + required this.child, + }); + + final Widget child; + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final layout = layoutClassFor(constraints.maxWidth); + + return switch (layout) { + LayoutClass.compact => MobileShell(child: child), + LayoutClass.medium => TabletShell(child: child), + LayoutClass.expanded => DesktopShell(child: child), + LayoutClass.wide => DesktopShell( + child: child, + showSecondaryPanel: true, + ), + }; + }, + ); + } +} +``` + +This is a layout-policy example. Implement shell widgets separately and add platform policy for behavior that differs between a native desktop application and a browser. For example, do not display a desktop window title bar in the browser merely because the viewport is wide. + +## 6. Platform-Specific Standards + +The six targets share design tokens, feature logic, and most components. They do not necessarily share exact navigation or input behavior. + +| Platform | Required considerations | +| --- | --- | +| Windows | Window resizing, keyboard shortcuts, mouse hover, context menus, and native file dialogs | +| Linux | Window resizing, keyboard/mouse interaction, desktop packaging, and file selection | +| macOS | Command-key shortcuts, trackpad gestures where supported, desktop window conventions, and file access | +| Android | Safe areas, back navigation, touch targets, virtual keyboard, and activity lifecycle | +| iOS | Safe areas, system gestures, touch targets, keyboard behavior, and app lifecycle | +| Web | Direct URLs, browser back/forward, refresh, browser keyboard shortcuts, downloads, and clipboard behavior | + +### Desktop rules + +- Use a persistent sidebar at expanded widths. +- Support keyboard navigation and visible focus indicators. +- Allow panels to collapse and, where useful, resize. +- Provide tooltips for unfamiliar icon-only actions. +- Keep shortcuts discoverable and avoid intercepting browser-reserved shortcuts on Web. +- Handle window resizing without losing unsaved state. + +### Mobile rules + +- Use bottom navigation for a small number of primary destinations; otherwise use a drawer or another compact navigation pattern. +- Aim for touch targets of at least 48 logical px where practical. +- Account for keyboard insets and safe areas. +- Prefer a single-pane layout on narrow screens. +- Preserve editor state and scroll position during navigation. +- Respect system text scaling and back navigation. + +### Web rules + +- Keep URLs meaningful and navigable. +- Test direct loading of nested routes, refresh, and browser history. +- Ensure hover interactions have keyboard and touch alternatives. +- Constrain content width on large monitors. +- Test browser zoom and narrow viewport sizes. +- Handle downloads and file selection according to browser permissions and supported APIs. + +## 7. Reusable Component Catalogue + +Create a small, consistent set of product components on top of Forui. Feature screens should use these components instead of independently choosing styles for the same task. + +| Component | Responsibilities | +| --- | --- | +| `AppButton` | Primary, secondary, outline, ghost, and destructive variants | +| `AppIconButton` | Consistent sizing, tooltip, focus, and accessibility label | +| `AppTextField` | Label, helper text, validation, and disabled state | +| `AppSearchField` | Search input, clear action, and loading state | +| `AppDialog` | Confirmation and form dialogs | +| `AppSheet` | Mobile-friendly secondary actions and forms | +| `AppToast` | Brief success and non-blocking feedback | +| `AppEmptyState` | Message, optional illustration/icon, and next action | +| `AppErrorState` | Human-readable error and recovery action | +| `AppLoadingState` | Loading indicator and optional supporting text | +| `AppPageHeader` | Title, description, primary actions, and relevant breadcrumbs | +| `AppSidebar` | Navigation destinations, selected state, and collapse behavior | +| `AppResponsiveContainer` | Content width and horizontal padding | +| `AppSplitView` | Main content and secondary panel on larger windows | +| `AppSection` | Consistent heading and content spacing | +| `AppStatusBadge` | Semantic status labels, not decorative status colors | + +### Component rules + +- Use Forui components where they already meet application standards. +- Wrap a component only when a stable application-wide API or consistent behavior is needed. +- Do not build a custom replacement for every Forui component. +- Keep button variants and status colors limited. +- Every interactive component needs disabled, focus, hover where relevant, and loading behavior. +- Every asynchronous feature needs clear loading, success, empty, and error states. +- Do not introduce a custom component merely to rename a single Forui widget. + +Forui already provides controls, forms, navigation, cards, overlays, dialogs, sheets, tooltips, and feedback widgets. Use its documented catalogue before adding another UI library. + +## 8. Riverpod State-Management Standards + +Organize providers by feature and responsibility. Avoid one giant `app_state.dart` file that owns unrelated screens. + +### State ownership + +| State | Recommended owner | +| --- | --- | +| Theme preference | App-level Riverpod provider plus preference storage | +| Navigation state and current route | `go_router` | +| Sidebar expanded/collapsed | Shell-level state | +| Editor content | Editor feature controller | +| Search query and filters | Feature-level provider or local widget state | +| Loaded documents | Repository plus Riverpod provider | +| Loading/error/data states | Riverpod asynchronous providers | +| Temporary input text | Text controller or local widget state | +| Window size and layout class | Flutter layout constraints, not persisted global state | + +### Provider conventions + +- Use `ConsumerWidget` or `ConsumerStatefulWidget` when a widget needs Riverpod. +- Use `ref.watch` to render state and `ref.read` for event-driven commands. +- Use `select` when only one part of a larger state is needed. +- Keep widget build methods focused on presentation. +- Keep repository implementations outside UI widgets. +- Use `AsyncValue` to model asynchronous loading, success, and failure. +- Use code generation where it reduces boilerplate; do not require it for every trivial provider. +- Avoid global mutable singletons that bypass Riverpod. + +Example provider organization: + +```text +features/editor/presentation/providers/ +├── editor_state.dart +├── editor_state.g.dart +├── editor_controller.dart +├── editor_document_provider.dart +└── editor_shortcuts.dart +``` + +Use the [Riverpod documentation](https://riverpod.dev/) as the reference for provider lifecycles, asynchronous state, testing, and code generation. + +## 9. App Startup, Routing, and Theming + +Use `ProviderScope` at the root, `MaterialApp.router` with `go_router`, and Forui's `FTheme` in the app builder. Follow the installed Forui version's integration pattern because Forui is pre-1.0 and may introduce API changes between minor releases. + +The application should have one theme-selection policy: + +1. Read the saved theme preference: system, light, or dark. +2. Resolve the current brightness. +3. Select the corresponding Forui theme. +4. Apply that theme consistently across all routes and overlays. +5. Update the preference when the user changes it. + +Use the Forui theme generator to create actual theme definitions rather than inventing a theme constructor from memory. + +Use `go_router` as the single source of truth for navigation state and current route state. Organize routes centrally and use nested routes for features that share a shell. Keep browser URLs meaningful on Web, and ensure back-navigation behavior is correct on Android and iOS. Do not duplicate navigation state in Riverpod providers or global mutable state. + +## 10. Testing and Quality Gates + +A cross-platform project is not production-ready merely because it compiles on all six targets. Test each shell and every capability that differs by platform. + +| Test level | What to verify | +| --- | --- | +| Unit tests | Breakpoints, validation, repositories, and domain logic | +| Widget tests | Shared components, themes, and loading/error/empty states | +| Responsive tests | Compact, medium, expanded, and wide layouts | +| Accessibility tests | Labels, focus, contrast, and text scaling | +| Integration tests | Routing, document workflows, and persistence | +| Desktop smoke tests | Resize, shortcuts, context menus, and file selection | +| Mobile smoke tests | Safe areas, keyboard, back navigation, and lifecycle | +| Web smoke tests | Refresh, direct URLs, browser history, downloads, and file selection | + +### Minimum release checks + +```bash +dart format --output=none --set-exit-if-changed . +flutter analyze +flutter test +``` + +Also: + +- Build and launch each supported target. +- Test light and dark themes. +- Test narrow and wide layouts. +- Verify failed asynchronous operations display recoverable errors. +- Check that important text is not clipped at larger system text scales. +- Test file access separately on every platform where it is supported. + +Use CI for formatting, static analysis, unit/widget tests, and platform builds supported by runners. Keep manual or automated smoke tests for platform-specific behavior. + +## 11. Implementation Sequence + +1. Create the project and validate all targets. Check the Flutter SDK, enable required desktop targets, and confirm baseline builds before adding dependencies. +2. Install and lock dependencies. Add Forui, Riverpod, routing, and only required storage/platform packages. +3. Build the design system. Define tokens, typography, light/dark themes, and component conventions. +4. Build shared components. Implement standard buttons, fields, feedback states, dialogs, and page containers. +5. Implement routing and global state. Add `ProviderScope`, theme preference, route configuration, and application-level error handling. +6. Implement responsive shells. Start with compact, medium, expanded, and wide layouts. Add platform-specific behavior only where it provides a real benefit. +7. Build one representative feature. Use a real screen with forms, navigation, asynchronous data, and error states to validate the architecture before scaling it. +8. Expand feature by feature. Keep business logic separate from UI and introduce data/domain layers when justified. +9. Validate all six platforms. Test keyboard and touch interactions, browser behavior, file access, text scaling, and window resizing. +10. Release with quality gates. Automate analysis and tests, document dependency upgrades, and test Forui upgrades before adopting them. + +## Final Recommendation + +Use Forui for components, Riverpod for state and dependency injection, `go_router` for navigation, and semantic design tokens for visual consistency. Build one shared feature layer with adaptive shells instead of six separate UIs. + +The most important decision is to stabilize the design system before building many screens. If every feature independently decides its colors, spacing, dialogs, and navigation behavior, the application will become inconsistent even when individual screens look acceptable. + +## References + +- [Forui documentation and installation](https://forui.dev/docs/getting-started) — theming and component usage. +- [Flutter adaptive and responsive design](https://docs.flutter.dev/ui/adaptive-responsive) — layout and interaction adaptation. +- [Riverpod documentation](https://riverpod.dev/) — state management and testing. +- [go_router package](https://pub.dev/packages/go_router) — routing and deep links.