mirror of
https://github.com/rzuasti/oott.git
synced 2026-07-08 19:21:54 +02:00
Polish frontend: design tokens, theming, AppBar titles, empty/loading states
Frontend usability and aesthetics pass across web and mobile: - Add shared layout tokens (theme/dimens.dart: Insets + Breakpoints) and replace magic-number spacing and per-file breakpoint consts. - Route both themes through buildAppTheme (theme/theme_builder.dart): explicit useMaterial3 and a shared branded textTheme (Barlow Condensed for display/headline/title styles); logo now reads its style from the theme. - Move screen titles into the shared AppBar (route-derived) and drop the redundant in-body headers; upgrade Settings buttons to M3 FilledButton. - Add reusable EmptyState and skeleton loaders; Devices empty state links to scanner status, notifications get filter-aware messages. - Add a first-run welcome intro and Save-disabled helper text in Settings. - Make device Filter/Sort adaptive: bottom sheet on phones, dialog on wide. - Collapse the device-list filter chips and Sort/Filter buttons into one row; rename the home Scanners card title and align its style. Tests: add EmptyState/skeleton widget tests and Settings first-run cases; update tests for the FilledButton swap and relocated titles. 89 passing, dart analyze clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5f6f5ad679
commit
9367c359d7
@@ -1,93 +1,92 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'app_colors.dart';
|
||||
import 'theme_builder.dart';
|
||||
|
||||
abstract final class CatppuccinMochaColors {
|
||||
static const Color crust = Color(0xFF11111b);
|
||||
static const Color mantle = Color(0xFF181825);
|
||||
static const Color base = Color(0xFF1e1e2e);
|
||||
static const Color surface0 = Color(0xFF313244);
|
||||
static const Color surface1 = Color(0xFF45475a);
|
||||
static const Color surface2 = Color(0xFF585b70);
|
||||
static const Color overlay0 = Color(0xFF6c7086);
|
||||
static const Color overlay1 = Color(0xFF7f849c);
|
||||
static const Color overlay2 = Color(0xFF9399b2);
|
||||
static const Color subtext0 = Color(0xFFa6adc8);
|
||||
static const Color subtext1 = Color(0xFFbac2de);
|
||||
static const Color text = Color(0xFFcdd6f4);
|
||||
static const Color crust = Color(0xFF11111b);
|
||||
static const Color mantle = Color(0xFF181825);
|
||||
static const Color base = Color(0xFF1e1e2e);
|
||||
static const Color surface0 = Color(0xFF313244);
|
||||
static const Color surface1 = Color(0xFF45475a);
|
||||
static const Color surface2 = Color(0xFF585b70);
|
||||
static const Color overlay0 = Color(0xFF6c7086);
|
||||
static const Color overlay1 = Color(0xFF7f849c);
|
||||
static const Color overlay2 = Color(0xFF9399b2);
|
||||
static const Color subtext0 = Color(0xFFa6adc8);
|
||||
static const Color subtext1 = Color(0xFFbac2de);
|
||||
static const Color text = Color(0xFFcdd6f4);
|
||||
|
||||
static const Color rosewater = Color(0xFFf5e0dc);
|
||||
static const Color flamingo = Color(0xFFf2cdcd);
|
||||
static const Color pink = Color(0xFFf5c2e7);
|
||||
static const Color mauve = Color(0xFFcba6f7);
|
||||
static const Color red = Color(0xFFf38ba8);
|
||||
static const Color maroon = Color(0xFFeba0ac);
|
||||
static const Color peach = Color(0xFFfab387);
|
||||
static const Color yellow = Color(0xFFf9e2af);
|
||||
static const Color green = Color(0xFFa6e3a1);
|
||||
static const Color teal = Color(0xFF94e2d5);
|
||||
static const Color sky = Color(0xFF89dceb);
|
||||
static const Color sapphire = Color(0xFF74c7ec);
|
||||
static const Color blue = Color(0xFF89b4fa);
|
||||
static const Color lavender = Color(0xFFb4befe);
|
||||
static const Color flamingo = Color(0xFFf2cdcd);
|
||||
static const Color pink = Color(0xFFf5c2e7);
|
||||
static const Color mauve = Color(0xFFcba6f7);
|
||||
static const Color red = Color(0xFFf38ba8);
|
||||
static const Color maroon = Color(0xFFeba0ac);
|
||||
static const Color peach = Color(0xFFfab387);
|
||||
static const Color yellow = Color(0xFFf9e2af);
|
||||
static const Color green = Color(0xFFa6e3a1);
|
||||
static const Color teal = Color(0xFF94e2d5);
|
||||
static const Color sky = Color(0xFF89dceb);
|
||||
static const Color sapphire = Color(0xFF74c7ec);
|
||||
static const Color blue = Color(0xFF89b4fa);
|
||||
static const Color lavender = Color(0xFFb4befe);
|
||||
}
|
||||
|
||||
const ColorScheme _catppuccinMochaColorScheme = ColorScheme(
|
||||
brightness: Brightness.dark,
|
||||
|
||||
// Surfaces — AppBar (darkest) → NavRail → body (lightest)
|
||||
surface: CatppuccinMochaColors.base,
|
||||
surfaceContainerLowest: CatppuccinMochaColors.crust,
|
||||
surfaceContainerLow: CatppuccinMochaColors.mantle,
|
||||
surfaceContainer: CatppuccinMochaColors.surface0,
|
||||
surfaceContainerHigh: CatppuccinMochaColors.surface1,
|
||||
surface: CatppuccinMochaColors.base,
|
||||
surfaceContainerLowest: CatppuccinMochaColors.crust,
|
||||
surfaceContainerLow: CatppuccinMochaColors.mantle,
|
||||
surfaceContainer: CatppuccinMochaColors.surface0,
|
||||
surfaceContainerHigh: CatppuccinMochaColors.surface1,
|
||||
surfaceContainerHighest: CatppuccinMochaColors.surface2,
|
||||
onSurface: CatppuccinMochaColors.text,
|
||||
onSurfaceVariant: CatppuccinMochaColors.subtext1,
|
||||
outline: CatppuccinMochaColors.overlay1,
|
||||
outlineVariant: CatppuccinMochaColors.overlay0,
|
||||
onSurface: CatppuccinMochaColors.text,
|
||||
onSurfaceVariant: CatppuccinMochaColors.subtext1,
|
||||
outline: CatppuccinMochaColors.overlay1,
|
||||
outlineVariant: CatppuccinMochaColors.overlay0,
|
||||
|
||||
// Primary — Save button, selected nav indicators
|
||||
primary: CatppuccinMochaColors.mauve,
|
||||
onPrimary: CatppuccinMochaColors.crust,
|
||||
primaryContainer: CatppuccinMochaColors.surface0,
|
||||
primary: CatppuccinMochaColors.mauve,
|
||||
onPrimary: CatppuccinMochaColors.crust,
|
||||
primaryContainer: CatppuccinMochaColors.surface0,
|
||||
onPrimaryContainer: CatppuccinMochaColors.text,
|
||||
|
||||
// Secondary — Test button (not-passing state)
|
||||
secondary: CatppuccinMochaColors.blue,
|
||||
onSecondary: CatppuccinMochaColors.crust,
|
||||
secondaryContainer: CatppuccinMochaColors.surface1,
|
||||
secondary: CatppuccinMochaColors.blue,
|
||||
onSecondary: CatppuccinMochaColors.crust,
|
||||
secondaryContainer: CatppuccinMochaColors.surface1,
|
||||
onSecondaryContainer: CatppuccinMochaColors.text,
|
||||
|
||||
// Tertiary — swipe-to-unread background
|
||||
tertiary: CatppuccinMochaColors.teal,
|
||||
onTertiary: CatppuccinMochaColors.crust,
|
||||
tertiaryContainer: CatppuccinMochaColors.surface0,
|
||||
tertiary: CatppuccinMochaColors.teal,
|
||||
onTertiary: CatppuccinMochaColors.crust,
|
||||
tertiaryContainer: CatppuccinMochaColors.surface0,
|
||||
onTertiaryContainer: CatppuccinMochaColors.text,
|
||||
|
||||
// Error
|
||||
error: CatppuccinMochaColors.red,
|
||||
onError: CatppuccinMochaColors.crust,
|
||||
errorContainer: CatppuccinMochaColors.maroon,
|
||||
error: CatppuccinMochaColors.red,
|
||||
onError: CatppuccinMochaColors.crust,
|
||||
errorContainer: CatppuccinMochaColors.maroon,
|
||||
onErrorContainer: CatppuccinMochaColors.crust,
|
||||
|
||||
// Inverse / scrim
|
||||
inverseSurface: CatppuccinMochaColors.text,
|
||||
inverseSurface: CatppuccinMochaColors.text,
|
||||
onInverseSurface: CatppuccinMochaColors.crust,
|
||||
inversePrimary: CatppuccinMochaColors.mauve,
|
||||
scrim: CatppuccinMochaColors.crust,
|
||||
shadow: CatppuccinMochaColors.crust,
|
||||
inversePrimary: CatppuccinMochaColors.mauve,
|
||||
scrim: CatppuccinMochaColors.crust,
|
||||
shadow: CatppuccinMochaColors.crust,
|
||||
);
|
||||
|
||||
final ThemeData catppuccinMochaDarkTheme = ThemeData(
|
||||
final ThemeData catppuccinMochaDarkTheme = buildAppTheme(
|
||||
colorScheme: _catppuccinMochaColorScheme,
|
||||
extensions: [
|
||||
const AppColorExtension(
|
||||
success: CatppuccinMochaColors.green,
|
||||
onSuccess: CatppuccinMochaColors.crust,
|
||||
warning: CatppuccinMochaColors.peach,
|
||||
onWarning: CatppuccinMochaColors.crust,
|
||||
info: CatppuccinMochaColors.blue,
|
||||
onInfo: CatppuccinMochaColors.crust,
|
||||
),
|
||||
],
|
||||
appColors: const AppColorExtension(
|
||||
success: CatppuccinMochaColors.green,
|
||||
onSuccess: CatppuccinMochaColors.crust,
|
||||
warning: CatppuccinMochaColors.peach,
|
||||
onWarning: CatppuccinMochaColors.crust,
|
||||
info: CatppuccinMochaColors.blue,
|
||||
onInfo: CatppuccinMochaColors.crust,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/// Shared layout tokens for consistent spacing and responsive breakpoints.
|
||||
///
|
||||
/// Use these instead of hand-written magic numbers so spacing stays uniform
|
||||
/// across screens and breakpoints are defined in a single place.
|
||||
library;
|
||||
|
||||
/// Spacing scale (logical pixels). Used for padding, margins and gaps.
|
||||
abstract final class Insets {
|
||||
static const double xs = 4;
|
||||
static const double sm = 8;
|
||||
static const double md = 12;
|
||||
static const double lg = 16;
|
||||
static const double xl = 20;
|
||||
static const double xxl = 24;
|
||||
static const double xxxl = 32;
|
||||
}
|
||||
|
||||
/// Responsive layout breakpoints, aligned with Material 3 window size classes.
|
||||
abstract final class Breakpoints {
|
||||
/// Compact → medium: switch from bottom NavigationBar to NavigationRail.
|
||||
static const double medium = 600;
|
||||
|
||||
/// Medium → expanded: extend the NavigationRail with labels.
|
||||
static const double expanded = 840;
|
||||
|
||||
/// Home screen switches to its two-column layout at this width.
|
||||
static const double twoColumn = 700;
|
||||
}
|
||||
@@ -1,30 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'app_colors.dart';
|
||||
import 'theme_builder.dart';
|
||||
|
||||
abstract final class GruvboxColors {
|
||||
static const Color bgHard = Color(0xFF1d2021);
|
||||
static const Color bg = Color(0xFF282828);
|
||||
static const Color bgSoft = Color(0xFF32302f);
|
||||
static const Color bg1 = Color(0xFF3c3836);
|
||||
static const Color bg2 = Color(0xFF504945);
|
||||
static const Color bg3 = Color(0xFF665c54);
|
||||
static const Color bg4 = Color(0xFF7c6f64);
|
||||
static const Color fg = Color(0xFFebdbb2);
|
||||
static const Color fg2 = Color(0xFFd5c4a1);
|
||||
static const Color gray = Color(0xFF928374);
|
||||
static const Color red = Color(0xFFcc241d);
|
||||
static const Color brightRed = Color(0xFFfb4934);
|
||||
static const Color green = Color(0xFF98971a);
|
||||
static const Color brightGreen = Color(0xFFb8bb26);
|
||||
static const Color yellow = Color(0xFFd79921);
|
||||
static const Color bgHard = Color(0xFF1d2021);
|
||||
static const Color bg = Color(0xFF282828);
|
||||
static const Color bgSoft = Color(0xFF32302f);
|
||||
static const Color bg1 = Color(0xFF3c3836);
|
||||
static const Color bg2 = Color(0xFF504945);
|
||||
static const Color bg3 = Color(0xFF665c54);
|
||||
static const Color bg4 = Color(0xFF7c6f64);
|
||||
static const Color fg = Color(0xFFebdbb2);
|
||||
static const Color fg2 = Color(0xFFd5c4a1);
|
||||
static const Color gray = Color(0xFF928374);
|
||||
static const Color red = Color(0xFFcc241d);
|
||||
static const Color brightRed = Color(0xFFfb4934);
|
||||
static const Color green = Color(0xFF98971a);
|
||||
static const Color brightGreen = Color(0xFFb8bb26);
|
||||
static const Color yellow = Color(0xFFd79921);
|
||||
static const Color brightYellow = Color(0xFFfabd2f);
|
||||
static const Color blue = Color(0xFF458588);
|
||||
static const Color brightBlue = Color(0xFF83a598);
|
||||
static const Color purple = Color(0xFFb16286);
|
||||
static const Color blue = Color(0xFF458588);
|
||||
static const Color brightBlue = Color(0xFF83a598);
|
||||
static const Color purple = Color(0xFFb16286);
|
||||
static const Color brightPurple = Color(0xFFd3869b);
|
||||
static const Color aqua = Color(0xFF689d6a);
|
||||
static const Color brightAqua = Color(0xFF8ec07c);
|
||||
static const Color orange = Color(0xFFd65d0e);
|
||||
static const Color aqua = Color(0xFF689d6a);
|
||||
static const Color brightAqua = Color(0xFF8ec07c);
|
||||
static const Color orange = Color(0xFFd65d0e);
|
||||
static const Color brightOrange = Color(0xFFfe8019);
|
||||
}
|
||||
|
||||
@@ -32,59 +33,57 @@ const ColorScheme _gruvboxColorScheme = ColorScheme(
|
||||
brightness: Brightness.dark,
|
||||
|
||||
// Surfaces — AppBar (darkest) → NavRail → body (lightest)
|
||||
surface: GruvboxColors.bgSoft,
|
||||
surfaceContainerLowest: GruvboxColors.bgHard,
|
||||
surfaceContainerLow: GruvboxColors.bg,
|
||||
surfaceContainer: GruvboxColors.bg1,
|
||||
surfaceContainerHigh: GruvboxColors.bg1,
|
||||
surface: GruvboxColors.bgSoft,
|
||||
surfaceContainerLowest: GruvboxColors.bgHard,
|
||||
surfaceContainerLow: GruvboxColors.bg,
|
||||
surfaceContainer: GruvboxColors.bg1,
|
||||
surfaceContainerHigh: GruvboxColors.bg1,
|
||||
surfaceContainerHighest: GruvboxColors.bg2,
|
||||
onSurface: GruvboxColors.fg,
|
||||
onSurfaceVariant: GruvboxColors.fg2,
|
||||
outline: GruvboxColors.gray,
|
||||
outlineVariant: GruvboxColors.bg4,
|
||||
onSurface: GruvboxColors.fg,
|
||||
onSurfaceVariant: GruvboxColors.fg2,
|
||||
outline: GruvboxColors.gray,
|
||||
outlineVariant: GruvboxColors.bg4,
|
||||
|
||||
// Primary — Save button, selected nav indicators
|
||||
primary: GruvboxColors.brightOrange,
|
||||
onPrimary: GruvboxColors.bgHard,
|
||||
primaryContainer: GruvboxColors.bg1,
|
||||
primary: GruvboxColors.brightOrange,
|
||||
onPrimary: GruvboxColors.bgHard,
|
||||
primaryContainer: GruvboxColors.bg1,
|
||||
onPrimaryContainer: GruvboxColors.fg,
|
||||
|
||||
// Secondary — Test button (not-passing state)
|
||||
secondary: GruvboxColors.blue,
|
||||
onSecondary: GruvboxColors.fg,
|
||||
secondaryContainer: GruvboxColors.bg2,
|
||||
secondary: GruvboxColors.blue,
|
||||
onSecondary: GruvboxColors.fg,
|
||||
secondaryContainer: GruvboxColors.bg2,
|
||||
onSecondaryContainer: GruvboxColors.fg,
|
||||
|
||||
// Tertiary — swipe-to-unread background
|
||||
tertiary: GruvboxColors.aqua,
|
||||
onTertiary: GruvboxColors.bgHard,
|
||||
tertiaryContainer: GruvboxColors.bg1,
|
||||
tertiary: GruvboxColors.aqua,
|
||||
onTertiary: GruvboxColors.bgHard,
|
||||
tertiaryContainer: GruvboxColors.bg1,
|
||||
onTertiaryContainer: GruvboxColors.fg,
|
||||
|
||||
// Error
|
||||
error: GruvboxColors.brightRed,
|
||||
onError: GruvboxColors.bgHard,
|
||||
errorContainer: GruvboxColors.red,
|
||||
error: GruvboxColors.brightRed,
|
||||
onError: GruvboxColors.bgHard,
|
||||
errorContainer: GruvboxColors.red,
|
||||
onErrorContainer: GruvboxColors.fg,
|
||||
|
||||
// Inverse / scrim
|
||||
inverseSurface: GruvboxColors.fg,
|
||||
inverseSurface: GruvboxColors.fg,
|
||||
onInverseSurface: GruvboxColors.bgHard,
|
||||
inversePrimary: GruvboxColors.orange,
|
||||
scrim: GruvboxColors.bgHard,
|
||||
shadow: GruvboxColors.bgHard,
|
||||
inversePrimary: GruvboxColors.orange,
|
||||
scrim: GruvboxColors.bgHard,
|
||||
shadow: GruvboxColors.bgHard,
|
||||
);
|
||||
|
||||
final ThemeData gruvboxDarkTheme = ThemeData(
|
||||
final ThemeData gruvboxDarkTheme = buildAppTheme(
|
||||
colorScheme: _gruvboxColorScheme,
|
||||
extensions: [
|
||||
const AppColorExtension(
|
||||
success: GruvboxColors.brightGreen,
|
||||
onSuccess: GruvboxColors.bgHard,
|
||||
warning: GruvboxColors.brightYellow,
|
||||
onWarning: GruvboxColors.bgHard,
|
||||
info: GruvboxColors.brightBlue,
|
||||
onInfo: GruvboxColors.bgHard,
|
||||
),
|
||||
],
|
||||
appColors: const AppColorExtension(
|
||||
success: GruvboxColors.brightGreen,
|
||||
onSuccess: GruvboxColors.bgHard,
|
||||
warning: GruvboxColors.brightYellow,
|
||||
onWarning: GruvboxColors.bgHard,
|
||||
info: GruvboxColors.brightBlue,
|
||||
onInfo: GruvboxColors.bgHard,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import 'app_colors.dart';
|
||||
|
||||
/// Builds a Material 3 [ThemeData] from a [ColorScheme] and the app's custom
|
||||
/// [AppColorExtension], applying the shared OOTT typography.
|
||||
///
|
||||
/// Both bundled themes (Catppuccin Mocha, Gruvbox Dark) go through here so they
|
||||
/// stay visually consistent — only their colors differ.
|
||||
ThemeData buildAppTheme({
|
||||
required ColorScheme colorScheme,
|
||||
required AppColorExtension appColors,
|
||||
}) {
|
||||
final base = ThemeData(colorScheme: colorScheme, useMaterial3: true);
|
||||
return base.copyWith(
|
||||
textTheme: _brandTextTheme(base.textTheme),
|
||||
extensions: [appColors],
|
||||
);
|
||||
}
|
||||
|
||||
/// Applies the OOTT brand font (Barlow Condensed) to display, headline and
|
||||
/// large-title styles while leaving body/label styles on the default face for
|
||||
/// readability. Sizes and colors from [base] are preserved.
|
||||
TextTheme _brandTextTheme(TextTheme base) {
|
||||
final branded = GoogleFonts.barlowCondensedTextTheme(base);
|
||||
return base.copyWith(
|
||||
displayLarge: branded.displayLarge,
|
||||
displayMedium: branded.displayMedium,
|
||||
displaySmall: branded.displaySmall,
|
||||
headlineLarge: branded.headlineLarge,
|
||||
headlineMedium: branded.headlineMedium,
|
||||
headlineSmall: branded.headlineSmall,
|
||||
titleLarge: branded.titleLarge,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user