9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
340 lines
12 KiB
Dart
340 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
import 'app_colors.dart';
|
|
import 'color_scheme.dart';
|
|
import 'grid.dart';
|
|
import 'text_theme.dart';
|
|
|
|
/// Border radius constants matching desktop shadcn "New York" style.
|
|
/// Desktop uses --radius: 0.625rem (10px) as base:
|
|
/// lg = 10px, md = 8px, sm = 6px
|
|
class Radii {
|
|
/// Small radius for compact UI elements.
|
|
static const double xs = 4.0;
|
|
static const double lg = 10.0;
|
|
static const double md = 8.0;
|
|
static const double sm = 6.0;
|
|
static const double card = 12.0; // grouped settings cards
|
|
static const double popover = 20.0;
|
|
static const double dialog = 24.0; // desktop uses rounded-3xl for dialogs
|
|
|
|
/// Fully rounds pills, circles, and other capsule shapes.
|
|
static const double full = 999.0;
|
|
}
|
|
|
|
class AppTheme {
|
|
static ThemeData light({
|
|
ColorScheme? colorScheme,
|
|
Gradient? topSectionGradient,
|
|
}) {
|
|
final scheme = colorScheme ?? lightColorScheme;
|
|
final appColors = AppColors(
|
|
success: const Color(0xFF40A02B), // Catppuccin Latte Green — universal
|
|
warning: const Color(0xFFDF8E1D), // Latte Yellow
|
|
accent: scheme.tertiary,
|
|
topSectionGradient: topSectionGradient,
|
|
);
|
|
|
|
return _buildTheme(
|
|
scheme: scheme,
|
|
appColors: appColors,
|
|
brightness: Brightness.light,
|
|
statusBarIconBrightness: Brightness.dark,
|
|
statusBarBrightness: Brightness.light,
|
|
);
|
|
}
|
|
|
|
static ThemeData dark({
|
|
ColorScheme? colorScheme,
|
|
Gradient? topSectionGradient,
|
|
}) {
|
|
final scheme = colorScheme ?? darkColorScheme;
|
|
final appColors = AppColors(
|
|
success: const Color(
|
|
0xFFA6DA95,
|
|
), // Catppuccin Macchiato Green — universal
|
|
warning: const Color(0xFFEED49F), // Macchiato Yellow
|
|
accent: scheme.tertiary,
|
|
topSectionGradient: topSectionGradient,
|
|
);
|
|
|
|
return _buildTheme(
|
|
scheme: scheme,
|
|
appColors: appColors,
|
|
brightness: Brightness.dark,
|
|
statusBarIconBrightness: Brightness.light,
|
|
statusBarBrightness: Brightness.dark,
|
|
);
|
|
}
|
|
|
|
static ThemeData _buildTheme({
|
|
required ColorScheme scheme,
|
|
required AppColors appColors,
|
|
required Brightness brightness,
|
|
required Brightness statusBarIconBrightness,
|
|
required Brightness statusBarBrightness,
|
|
}) {
|
|
return ThemeData(
|
|
useMaterial3: true,
|
|
colorScheme: scheme,
|
|
splashFactory: NoSplash.splashFactory,
|
|
scaffoldBackgroundColor: scheme.surface,
|
|
extensions: [appColors],
|
|
fontFamily: 'Inter',
|
|
textTheme: textTheme,
|
|
appBarTheme: AppBarTheme(
|
|
backgroundColor: Colors.transparent,
|
|
foregroundColor: scheme.onSurface,
|
|
surfaceTintColor: Colors.transparent,
|
|
elevation: 0,
|
|
scrolledUnderElevation: 0,
|
|
titleTextStyle: textTheme.titleMedium?.copyWith(
|
|
color: scheme.onSurface,
|
|
),
|
|
systemOverlayStyle: SystemUiOverlayStyle(
|
|
statusBarColor: Colors.transparent,
|
|
statusBarIconBrightness: statusBarIconBrightness,
|
|
statusBarBrightness: statusBarBrightness,
|
|
),
|
|
),
|
|
|
|
// Bottom navigation: clean style, no indicator pill
|
|
navigationBarTheme: NavigationBarThemeData(
|
|
backgroundColor: scheme.surface,
|
|
elevation: 0,
|
|
indicatorColor: Colors.transparent,
|
|
iconTheme: WidgetStateProperty.resolveWith((states) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return IconThemeData(color: scheme.primary, size: 24);
|
|
}
|
|
return IconThemeData(color: scheme.onSurfaceVariant, size: 24);
|
|
}),
|
|
labelTextStyle: WidgetStateProperty.resolveWith((states) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return textTheme.labelSmall?.copyWith(
|
|
color: scheme.primary,
|
|
fontWeight: FontWeight.w600,
|
|
);
|
|
}
|
|
return textTheme.labelSmall?.copyWith(color: scheme.onSurfaceVariant);
|
|
}),
|
|
),
|
|
|
|
// Buttons: desktop uses rounded-md (8px), h-9 (36px), px-4 (16px)
|
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: scheme.primary,
|
|
foregroundColor: scheme.onPrimary,
|
|
elevation: 0,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
|
minimumSize: const Size(0, 36),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(Radii.md),
|
|
),
|
|
textStyle: textTheme.labelMedium?.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
filledButtonTheme: FilledButtonThemeData(
|
|
style: FilledButton.styleFrom(
|
|
elevation: 0,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
|
minimumSize: const Size(0, 36),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(Radii.md),
|
|
),
|
|
textStyle: textTheme.labelMedium?.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
outlinedButtonTheme: OutlinedButtonThemeData(
|
|
style: OutlinedButton.styleFrom(
|
|
backgroundColor: scheme.surface,
|
|
foregroundColor: scheme.onSurface,
|
|
side: BorderSide(color: scheme.outline, width: 1),
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
|
minimumSize: const Size(0, 36),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(Radii.md),
|
|
),
|
|
textStyle: textTheme.labelMedium?.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
textButtonTheme: TextButtonThemeData(
|
|
style: TextButton.styleFrom(
|
|
foregroundColor: scheme.onSurface,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
minimumSize: const Size(0, 36),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(Radii.md),
|
|
),
|
|
textStyle: textTheme.labelMedium?.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
|
|
// Cards: desktop uses rounded-lg (10px), flat, no elevation
|
|
cardTheme: CardThemeData(
|
|
color: scheme.surfaceContainerHighest,
|
|
margin: EdgeInsets.zero,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(Radii.lg),
|
|
),
|
|
),
|
|
|
|
// Inputs: desktop uses outlined style, rounded-md (8px), h-9 (36px)
|
|
inputDecorationTheme: InputDecorationTheme(
|
|
filled: false,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(Radii.md),
|
|
borderSide: BorderSide(color: scheme.outline),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(Radii.md),
|
|
borderSide: BorderSide(color: scheme.outline),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(Radii.md),
|
|
borderSide: BorderSide(color: scheme.primary),
|
|
),
|
|
errorBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(Radii.md),
|
|
borderSide: BorderSide(color: scheme.error),
|
|
),
|
|
focusedErrorBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(Radii.md),
|
|
borderSide: BorderSide(color: scheme.error),
|
|
),
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 12,
|
|
vertical: 10,
|
|
),
|
|
isDense: true,
|
|
),
|
|
|
|
// Dialogs: desktop uses rounded-3xl (24px), custom overlay
|
|
dialogTheme: DialogThemeData(
|
|
backgroundColor: scheme.surface,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(Radii.dialog),
|
|
side: BorderSide(color: scheme.outline),
|
|
),
|
|
titleTextStyle: textTheme.titleLarge?.copyWith(
|
|
color: scheme.onSurface,
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w600,
|
|
letterSpacing: -0.3,
|
|
),
|
|
contentTextStyle: textTheme.bodyMedium?.copyWith(
|
|
color: scheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
|
|
progressIndicatorTheme: ProgressIndicatorThemeData(
|
|
strokeWidth: 2,
|
|
color: scheme.primary,
|
|
circularTrackColor: scheme.onSurfaceVariant.withValues(alpha: 0.2),
|
|
),
|
|
|
|
listTileTheme: ListTileThemeData(
|
|
titleTextStyle: textTheme.titleSmall?.copyWith(color: scheme.onSurface),
|
|
subtitleTextStyle: textTheme.bodyMedium?.copyWith(
|
|
color: scheme.secondary,
|
|
),
|
|
iconColor: scheme.secondary,
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: Grid.twelve),
|
|
minVerticalPadding: Grid.twelve,
|
|
horizontalTitleGap: Grid.twelve,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(Radii.md),
|
|
),
|
|
),
|
|
|
|
// Chips: desktop uses rounded-sm (6px)
|
|
chipTheme: ChipThemeData(
|
|
labelStyle: textTheme.bodySmall?.copyWith(color: scheme.secondary),
|
|
// M3 resolves the chip container via `color` (WidgetStateProperty);
|
|
// `selectedColor` is the legacy M2 path and is ignored here. Selected
|
|
// filter chips (Pulse/Search/Activity tabs) use the accent.
|
|
color: WidgetStateProperty.resolveWith((states) {
|
|
if (states.contains(WidgetState.selected)) return scheme.primary;
|
|
return scheme.surfaceContainerHighest;
|
|
}),
|
|
checkmarkColor: scheme.onPrimary,
|
|
shape: RoundedRectangleBorder(
|
|
side: BorderSide.none,
|
|
borderRadius: BorderRadius.circular(Radii.sm),
|
|
),
|
|
side: BorderSide.none,
|
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
labelPadding: EdgeInsets.zero,
|
|
),
|
|
|
|
// Popups/menus share the elevated 20px mobile popover treatment.
|
|
popupMenuTheme: PopupMenuThemeData(
|
|
color: scheme.surface.withValues(alpha: 0.98),
|
|
elevation: 8,
|
|
shadowColor: scheme.shadow.withValues(alpha: 0.18),
|
|
surfaceTintColor: Colors.transparent,
|
|
textStyle: textTheme.labelLarge?.copyWith(color: scheme.onSurface),
|
|
labelTextStyle: WidgetStatePropertyAll(
|
|
textTheme.labelLarge?.copyWith(color: scheme.onSurface),
|
|
),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(Radii.popover),
|
|
side: BorderSide(
|
|
color: Colors.black.withValues(alpha: 0.04),
|
|
width: 1,
|
|
),
|
|
),
|
|
),
|
|
|
|
// Bottom sheet: match dialog radius
|
|
bottomSheetTheme: BottomSheetThemeData(
|
|
backgroundColor: scheme.surface,
|
|
elevation: 0,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(
|
|
top: Radius.circular(Radii.dialog),
|
|
),
|
|
),
|
|
),
|
|
|
|
// Tooltips: desktop uses rounded-md, primary bg
|
|
tooltipTheme: TooltipThemeData(
|
|
decoration: BoxDecoration(
|
|
color: scheme.primary,
|
|
borderRadius: BorderRadius.circular(Radii.md),
|
|
),
|
|
textStyle: textTheme.bodySmall?.copyWith(
|
|
color: scheme.onPrimary,
|
|
fontSize: 12,
|
|
),
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
),
|
|
|
|
dividerTheme: DividerThemeData(
|
|
color: scheme.outline,
|
|
thickness: 1,
|
|
space: 1,
|
|
),
|
|
|
|
// Snackbar
|
|
snackBarTheme: SnackBarThemeData(
|
|
behavior: SnackBarBehavior.floating,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(Radii.md),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|