feat: theme chrome by palette, selectable text, wap fallback, and library pages

This commit is contained in:
randogoth 2026-10-11 11:48:24 +03:00
parent c104c2026b
commit 56f3d959fa
12 changed files with 534 additions and 51 deletions

View file

@ -5,26 +5,38 @@ import 'package:signals/signals_flutter.dart' show SignalBuilder;
import '../application/actors/fetch_actor.dart';
import '../application/controllers/browser_controller.dart';
import '../application/controllers/reader_settings_controller.dart';
import '../application/internal_pages.dart';
import '../database/database.dart';
import '../l10n/strings.dart';
import '../mews/document.dart';
import 'reader/palettes.dart';
import 'reader/reader_screen.dart';
/// Wires the app's dependencies together and provides them to the tree.
/// Everything is local-first: the database is created up front and the fetch
/// actor is the only path to the network.
class AppDependencies {
AppDependencies({MewsDatabase? database, Actor<FetchCommand, FetchResult>? fetch})
: db = database ?? MewsDatabase(),
fetch = fetch ?? Actor.create(FetchHandler.new) {
browser = BrowserController(db: db, fetch: this.fetch);
AppDependencies({
MewsDatabase? database,
Actor<FetchCommand, FetchResult>? fetch,
Strings? strings,
}) : db = database ?? MewsDatabase(),
fetch = fetch ?? Actor.create(FetchHandler.new),
strings = strings ?? Strings() {
settings = ReaderSettingsController(db);
library = LibraryController(db);
internalPages = InternalPages(db: db, library: library, strings: this.strings);
browser = BrowserController(db: db, fetch: this.fetch, internalPages: internalPages);
}
/// Localized strings, also used to title the internal pages.
final Strings strings;
final MewsDatabase db;
final Actor<FetchCommand, FetchResult> fetch;
late final BrowserController browser;
late final ReaderSettingsController settings;
late final LibraryController library;
late final InternalPages internalPages;
Future<void> start() async {
await settings.load();
@ -70,6 +82,8 @@ class MewsApp extends StatelessWidget {
}
}
/// The chrome (address bar, FOB, sheet, dialogs) follows the same palette
/// the reader selected for pages, so the whole app shares one color scheme.
class _ThemedApp extends StatelessWidget {
const _ThemedApp();
@ -82,21 +96,55 @@ class _ThemedApp extends StatelessWidget {
'dark' => ThemeMode.dark,
_ => ThemeMode.system,
};
final settings = deps.settings;
final page = deps.browser.currentPage.value;
final variant = settings.variant.value == 'site'
? (page?.variant ?? MewsVariant.plain)
: pageVariantFromName(settings.variant.value);
return MaterialApp(
title: 'Mews',
themeMode: mode,
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xff1a4f9c)),
),
darkTheme: ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xff1a4f9c), brightness: Brightness.dark),
),
theme: _themeFor(paletteFor(pageVariant: variant, readerVariant: 'site', dark: false)),
darkTheme: _themeFor(paletteFor(pageVariant: variant, readerVariant: 'site', dark: true)),
home: const ReaderScreen(),
);
});
}
ThemeData _themeFor(MewsPalette palette) {
final dark = palette.background.computeLuminance() < 0.5;
final scheme = ColorScheme(
brightness: dark ? Brightness.dark : Brightness.light,
primary: palette.link,
onPrimary: palette.background,
primaryContainer: palette.link,
onPrimaryContainer: palette.background,
secondary: palette.border,
onSecondary: palette.foreground,
surface: palette.background,
onSurface: palette.foreground,
onSurfaceVariant: palette.foreground,
surfaceContainerHighest: palette.preBackground,
outline: palette.border,
error: const Color(0xffba1a1a),
onError: Colors.white,
);
return ThemeData(
useMaterial3: true,
colorScheme: scheme,
scaffoldBackgroundColor: palette.background,
// The FOB follows the palette, not the M3 container defaults.
floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: palette.link,
foregroundColor: palette.background,
),
// Selection highlights and the caret follow the palette too.
textSelectionTheme: TextSelectionThemeData(
cursorColor: palette.link,
selectionColor: palette.link.withValues(alpha: 0.3),
selectionHandleColor: palette.link,
),
);
}
}