LocalSend Onboarding LabChapter II — Flutter App Shell: Entry Point, Theming & Routing0/6Contents
Chapter II

Flutter App Shell: Entry Point, Theming & Routing

This module covers how the LocalSend Flutter app boots: main.dart's preInit() builds a RefenaContainer before any UI exists, runApp() mounts that container via RefenaScope and wraps the MaterialApp, and theme.dart resolves the light/dark ColorScheme from the user's settings. The MaterialApp's home is HomePage, a stateful tabbed shell (Receive/Send/Settings) that runs postInit() on first frame — the handoff point into discovery, sending, receiving, and signaling subsystems.


From main() to the tabbed hub
main() — app entryapp/lib/main.dart
preInit() — boot sequenceapp/lib/config/init.dart
RefenaScope.withContainerapp/lib/main.dart
LocalSendApp / MaterialAppapp/lib/main.dart
HomePageapp/lib/pages/home_page.dart
Receive / Send / Settings tabsapp/lib/pages/home_page.dart
Figure I · Click a node to see what it does, where it lives in the code, and its labeled connections — the annotation opens beside it.
walkthrough

main(): boot or bail out

app/lib/main.dart · lines 2040
lines 21–30

main() awaits preInit() inside a try/catch. preInit does all the heavy async setup (Rust FFI, persistence, isolates, single-instance check); if it throws, the app shows a standalone error UI instead and returns without ever calling runApp for the real app.

lines 32–39

On success, runApp mounts RefenaScope.withContainer using the container preInit built, making every Refena provider available app-wide, wrapped in TranslationProvider for slang i18n, wrapping the root LocalSendApp widget.

Future<void> main(List<String> args) async {  final RefenaContainer container;  try {    container = await preInit(args);  } catch (e, stackTrace) {    showInitErrorApp(      error: e,      stackTrace: stackTrace,    );    return;  }   runApp(    RefenaScope.withContainer(      container: container,      child: TranslationProvider(        child: const LocalSendApp(),      ),    ),  );}
step 1 of 2
walkthrough

LocalSendApp wires theme, locale, and the home route

app/lib/main.dart · lines 6884
lines 68–72

Title and locale come from the generated slang translations (t.appName) and TranslationProvider; supportedLocales/localizationsDelegates plug slang's locale list into Flutter's Material localization pipeline.

lines 74–76

Light/dark ThemeData both come from getTheme() in theme.dart, keyed by the user's colorMode and Brightness; when colorMode is oled, themeMode is forced to dark regardless of the user's chosen theme setting.

lines 78–83

home renders RouterinoHome around HomePage itself, starting on HomeTab.receive with appStart: true — this is the launching point where the tabbed hub takes over.

            child: MaterialApp(              title: t.appName,              locale: TranslationProvider.of(context).flutterLocale,              supportedLocales: AppLocaleUtils.supportedLocales,              localizationsDelegates: GlobalMaterialLocalizations.delegates,              debugShowCheckedModeBanner: false,              theme: getTheme(colorMode, Brightness.light, dynamicColors),              darkTheme: getTheme(colorMode, Brightness.dark, dynamicColors),              themeMode: colorMode == ColorMode.oled ? ThemeMode.dark : themeMode,              navigatorKey: Routerino.navigatorKey,              home: RouterinoHome(                builder: () => const HomePage(                  initialTab: HomeTab.receive,                  appStart: true,                ),              ),            ),
step 1 of 3
ColorScheme _determineColorScheme(ColorMode mode, Brightness brightness, DynamicColors? dynamicColors) {  final defaultColorScheme = ColorScheme.fromSeed(    seedColor: Colors.teal,    brightness: brightness,  );   final colorScheme = switch (mode) {    ColorMode.system => brightness == Brightness.light ? dynamicColors?.light : dynamicColors?.dark,    ColorMode.localsend => null,    ColorMode.oled => (dynamicColors?.dark ?? defaultColorScheme).copyWith(      surface: Colors.black,    ),    ColorMode.yaru => throw 'Should reach here',  };   return colorScheme ?? defaultColorScheme;}
app/lib/config/theme.dart · lines 144160
Figure II · ColorMode.system uses the OS's dynamic Material You palette (falling back to a teal ColorScheme.fromSeed if unavailable); ColorMode.oled reuses the dynamic dark palette but forces a pure black surface for true-black OLED displays; ColorMode.yaru is unreachable here because getTheme() already branches to _getYaruTheme before calling this function.
Figure III · state machineSwitching between the three home tabs
user drags files/folders onto the window (DropTarget.onDragDone)user taps the Receive destination in the NavigationRail (desktop/tablet)user taps the Settings destination in the bottom NavigationBar (mobile)ReceiveSendSettings
Click a state above.
checkpoint

Check your understanding

Answered in place — nothing is graded, everything is explained. 0 / 3 passed

What happens in main() if preInit(args) throws an exception?

Which widget makes the RefenaContainer's providers (persistence, settings, isolate, etc.) available to the whole widget tree?

In theme.dart's getTheme(), what determines whether the Yaru (Ubuntu) theme is used instead of a seeded Material theme?