Ctrl K
ComunidadGuías…

docs/architecture/app-shell-navigation.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# App Shell & Navigation

## Purpose

App bootstrap, the `go_router` typed route tree, and the stateful navigation shell (NavigationRail on tablet/desktop, bottom NavigationBar on phone).

## Key files

| Path | Responsibility |
|---|---|
| `lib/main.dart` | Bootstrap: Hive init, auth migration, auth preload, `ProviderContainer` setup, `runApp` |
| `lib/src/tsumiru_app.dart` | Root widget `TsumiruApp`; wires `GraphQLProvider`, `MaterialApp.router`, theming, locale, `ReauthBannerHost` |
| `lib/src/routes/router_config.dart` | `@riverpod routerConfig` `GoRouter`; typed `ShellRoute`/`GoRoute` defs; `Routes` constants; navigator keys |
| `lib/src/routes/router_config.g.dart` | Generated `$appRoutes` (build_runner) |
| `lib/src/routes/sub_routes/*.dart` | `part` files, one per nav section |
| `lib/src/widgets/shell/navigation_shell_screen.dart` | `NavigationShellScreen` — dispatches to big/small nav bar; runs update check on first mount |
| `lib/src/widgets/shell/big_screen_navigation_bar.dart` | `BigScreenNavigationBar` — `NavigationRail` (extended on desktop, logo in leading) |
| `lib/src/widgets/shell/small_screen_navigation_bar.dart` | `SmallScreenNavigationBar` — M3 bottom `NavigationBar` |
| `lib/src/constants/navigation_bar_data.dart` | `NavigationBarData` — `phoneNavList`/`tabletNavList`, `getNavList(context)` |

## Bootstrap sequence (`main()`)

1. `WidgetsFlutterBinding.ensureInitialized()`, `PackageInfo.fromPlatform()`, `SharedPreferences.getInstance()`, `initHiveForFlutter()`.
2. `GoRouter.optionURLReflectsImperativeAPIs = true`.
3. `ProviderContainer` with three pre-seeded overrides: `packageInfoProvider`, `sharedPreferencesProvider`, `hiveStoreProvider`.
4. `migrateBasicAuthCredentials` — one-shot legacy basic-auth → secure storage migration (non-fatal).
5. `Future.wait([authCredentialsStoreProvider.future, credentialsProvider.future])` — both auth providers loaded **before first frame** (prevents tokenless image requests caching as 401s).
6. `container.read(authCoordinatorProvider.notifier)` — eagerly construct so the proactive-refresh listener is live before any image request.
7. `runApp(UncontrolledProviderScope(container, child: TsumiruApp()))`.

Initial location: `/library/0`.

`TsumiruApp.build()` wraps: `GraphQLProvider` → `MaterialApp.router` → `FToastBuilder` → `ReauthBannerHost`.

## Routing

`go_router` with typed routes (`@TypedShellRoute` / `@TypedGoRoute` / `@TypedStatefulShellRoute`) generated into `router_config.g.dart`. The `GoRouter` itself is a `@riverpod` provider (`routerConfigProvider`).

Route tree (outer → inner):

```
QuickSearchRoute (ShellRouteData — wraps everything in SearchStackScreen)
  NavigationShellRoute (StatefulShellRouteData — NavigationShellScreen)
    LibraryBranch    → /library/:categoryId
    UpdatesBranch    → /updates
    HistoryBranch    → /history
    BrowserBranch    → BrowseShellRoute (nested StatefulShellRoute: Sources / Extensions)
    DownloadsBranch  → /downloads
    MoreBranch       → /more → settings, about, ...
  MangaRoute         → /manga/:mangaId            (outside the shell)
    ReaderRoute      → /manga/:mangaId/chapter/:chapterId
  GlobalSearchRoute, UpdateStatusRoute, Migration*Route
```

Navigator keys: `rootNavigatorKey`, `_quickOpenNavigatorKey` (also `$parentNavigatorKey` for `ReaderRoute`/`GlobalSearchRoute` → they present full-screen above the shell), `_shellNavigatorKey`, `_browseNavigatorKey`.

Navigation is always typed: `const FooRoute(...).go(context)` / `.push(context)` — no string-based navigation.

In wide layouts the navigation and detail shells wrap only the content column in
`Scaffold`. Banners and snackbars render there, right of the rail and below the top bar.

## Screen-size split

- **Phone** (< 600): `SmallScreenNavigationBar` (M3 bottom bar), 5 items (History omitted — lives under More).
- **Tablet** (≥ 600, `context.isTablet`): `BigScreenNavigationBar` (`NavigationRail`), 6 items incl. History.
- **Desktop** (≥ 1200, `context.isDesktop`): rail is **extended** (256px) with logo + title in `leading`.

## Desktop page frame

`lib/src/widgets/desktop/desktop_page_frame.dart` supplies the shared page frame.
`DesktopPageLayout.standard` adds a 16 px start (left in LTR) and top inset;
`header` keeps the start inset and lets `DesktopPageHeader` add the 16 px gap
below the header; `fullBleed` adds neither inset. Page-line helpers align content
within these variants.

Page scroll views reserve a global 14 px scrollbar gutter (10 px thumb plus
4 px clearance). Full-bleed scroll views keep edge-to-edge backgrounds and apply
the clearance to their content instead.

`desktop_top_bar.dart` keeps search and actions on one row. Search has a shared
200 px minimum and 320 px preferred width; icon actions form a group of 40 px
buttons, with 10 px gaps between the search and action groups.

## Gotchas

- **History index offset on phone.** The stateful shell always has 6 branches (0–5); phone shows 5 (History hidden). `NavigationShellScreen` has `getAdjustedIndex`/`getReverseAdjustedIndex` that ±1 for indices ≥ 2 on phone. Changing the nav item count must update these or the wrong branch is selected.
- **Reader presents above the shell** via `_quickOpenNavigatorKey` as `$parentNavigatorKey` — nav bars are fully hidden while reading.
- **`MangaRoute` is outside the stateful shell** (inside `QuickSearchRoute`) — manga detail renders without a nav bar.
- **`NavigationBarData.navList`** (static field) is a stale legacy alias for `phoneNavList`; always use `getNavList(context)`.
- **Browse is a tab-within-a-tab** — a nested `StatefulShellRoute` (Sources / Extensions) with its own navigator.
- **`isTablet` uses `width`, not shortestSide** despite a misleading comment — a landscape phone ≥ 600px wide gets the rail layout.