UI: Cyber/IT design overhaul with semantic status tokens (Material 3)

Re-theme TasQ with a refined enterprise "Cyber/IT" identity on Material 3.
Functionality is unchanged — only color/shape tokens and a few state widgets.

Foundation:
- Hand-tuned light + dark ColorSchemes (deep-slate surfaces, azure/teal/violet
  accents), tightened 12-16dp shape language, flat hairline-outlined cards.
- New AppStatusColors theme extension (success/warning/danger/info/neutral,
  per-brightness) replacing scattered raw Colors.green/orange/red/grey so status
  color is consistent and legible in dark mode.
- New TechChip mono primitive; StatusPill.kind; M3Card hairline variants;
  tightened AppSurfaces radii.

Per-screen:
- Tokenized status colors + radii across dashboard, tickets, tasks, IT service
  requests, attendance, whereabouts, network map, reports charts, auth, and
  shared/admin screens. Added _ticketStatusKind/_taskStatusKind/_isrStatusKind
  mappers so status carries meaning consistently.
- Fixed white pie-slice borders for dark mode; dropped ad-hoc dialog radius
  overrides in favor of the theme default. Categorical palettes (leave-type
  icons, team color picker, distribution charts) intentionally preserved.

Verified: flutter analyze lib clean; flutter build web green; visual QA in
light and dark (dashboard, tickets, attendance).

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-09-26 15:13:47 +08:00
parent 3cb980b629
commit 57377b1b21
28 changed files with 986 additions and 470 deletions
+183
View File
@@ -0,0 +1,183 @@
import 'package:flutter/material.dart';
/// Semantic status categories shared across status pills, chips and badges.
enum StatusKind { success, warning, danger, info, neutral }
/// A single semantic status swatch: a vivid [color] for icons/dots/text, plus a
/// tonal [container] / [onContainer] pair for filled pills and badges.
@immutable
class StatusSwatch {
const StatusSwatch({
required this.color,
required this.container,
required this.onContainer,
});
/// Vivid accent — use for status dots, icons, borders and emphasized text.
final Color color;
/// Tonal fill for pills/badges/callout surfaces.
final Color container;
/// Foreground (text/icon) drawn on top of [container].
final Color onContainer;
StatusSwatch copyWith({Color? color, Color? container, Color? onContainer}) {
return StatusSwatch(
color: color ?? this.color,
container: container ?? this.container,
onContainer: onContainer ?? this.onContainer,
);
}
static StatusSwatch lerp(StatusSwatch a, StatusSwatch b, double t) {
return StatusSwatch(
color: Color.lerp(a.color, b.color, t) ?? a.color,
container: Color.lerp(a.container, b.container, t) ?? a.container,
onContainer: Color.lerp(a.onContainer, b.onContainer, t) ?? a.onContainer,
);
}
}
/// Cyber/IT semantic status tokens.
///
/// These replace scattered raw `Colors.green/orange/red/grey` usages so status
/// meaning is consistent and legible in **both** light and dark themes. Access
/// via [AppStatusColors.of] and pick a swatch by meaning:
///
/// ```dart
/// final status = AppStatusColors.of(context);
/// Icon(Icons.circle, size: 10, color: status.success.color); // online / up
/// ```
@immutable
class AppStatusColors extends ThemeExtension<AppStatusColors> {
const AppStatusColors({
required this.success,
required this.warning,
required this.danger,
required this.info,
required this.neutral,
});
/// Resolved, present, online, device-up, healthy.
final StatusSwatch success;
/// In-progress, pending, warning, degraded.
final StatusSwatch warning;
/// Urgent, critical, absent, error, device-down.
final StatusSwatch danger;
/// Informational / low-priority highlight.
final StatusSwatch info;
/// Idle, offline, disabled, muted.
final StatusSwatch neutral;
// ── Dark (cyber-first) ──────────────────────────────────────────────────
static const AppStatusColors dark = AppStatusColors(
success: StatusSwatch(
color: Color(0xFF34D399),
container: Color(0xFF0C4A34),
onContainer: Color(0xFF8FF0C4),
),
warning: StatusSwatch(
color: Color(0xFFFBBF24),
container: Color(0xFF4A3506),
onContainer: Color(0xFFFCE199),
),
danger: StatusSwatch(
color: Color(0xFFF87171),
container: Color(0xFF5A1A1A),
onContainer: Color(0xFFFFC9C4),
),
info: StatusSwatch(
color: Color(0xFF60A5FA),
container: Color(0xFF0E3A5F),
onContainer: Color(0xFFCFE7FF),
),
neutral: StatusSwatch(
color: Color(0xFF94A3B8),
container: Color(0xFF26313D),
onContainer: Color(0xFFCBD5E1),
),
);
// ── Light ───────────────────────────────────────────────────────────────
static const AppStatusColors light = AppStatusColors(
success: StatusSwatch(
color: Color(0xFF15803D),
container: Color(0xFFCDEFD8),
onContainer: Color(0xFF052E16),
),
warning: StatusSwatch(
color: Color(0xFFB45309),
container: Color(0xFFFCEBC7),
onContainer: Color(0xFF3A2606),
),
danger: StatusSwatch(
color: Color(0xFFDC2626),
container: Color(0xFFFBD9D6),
onContainer: Color(0xFF450A0A),
),
info: StatusSwatch(
color: Color(0xFF2563EB),
container: Color(0xFFD6E4FF),
onContainer: Color(0xFF0A2A6B),
),
neutral: StatusSwatch(
color: Color(0xFF64748B),
container: Color(0xFFE2E8F0),
onContainer: Color(0xFF1E293B),
),
);
static AppStatusColors of(BuildContext context) {
return Theme.of(context).extension<AppStatusColors>() ?? dark;
}
/// Resolve the swatch for a semantic [StatusKind].
StatusSwatch byKind(StatusKind kind) {
switch (kind) {
case StatusKind.success:
return success;
case StatusKind.warning:
return warning;
case StatusKind.danger:
return danger;
case StatusKind.info:
return info;
case StatusKind.neutral:
return neutral;
}
}
@override
AppStatusColors copyWith({
StatusSwatch? success,
StatusSwatch? warning,
StatusSwatch? danger,
StatusSwatch? info,
StatusSwatch? neutral,
}) {
return AppStatusColors(
success: success ?? this.success,
warning: warning ?? this.warning,
danger: danger ?? this.danger,
info: info ?? this.info,
neutral: neutral ?? this.neutral,
);
}
@override
AppStatusColors lerp(ThemeExtension<AppStatusColors>? other, double t) {
if (other is! AppStatusColors) return this;
return AppStatusColors(
success: StatusSwatch.lerp(success, other.success, t),
warning: StatusSwatch.lerp(warning, other.warning, t),
danger: StatusSwatch.lerp(danger, other.danger, t),
info: StatusSwatch.lerp(info, other.info, t),
neutral: StatusSwatch.lerp(neutral, other.neutral, t),
);
}
}
+15 -14
View File
@@ -1,10 +1,11 @@
import 'package:flutter/material.dart';
/// M3 Expressive surface tokens.
/// Cyber/IT surface tokens.
///
/// Cards now use **tonal elevation** (color tints) instead of drop-shadows.
/// Large containers adopt the M3 standard 28 dp corner radius; compact items
/// use 16 dp; small chips/badges use 12 dp.
/// Cards use **tonal elevation** (color tints) plus a hairline outline instead
/// of drop-shadows. The shape language is tightened to a precise 12–16 dp
/// technical feel: standard cards 12 dp, compact items 10 dp, large containers
/// 16 dp, dialogs/sheets 20 dp, and chips/badges 8 dp.
@immutable
class AppSurfaces extends ThemeExtension<AppSurfaces> {
const AppSurfaces({
@@ -15,19 +16,19 @@ class AppSurfaces extends ThemeExtension<AppSurfaces> {
required this.chipRadius,
});
/// Standard card radius – 16 dp (M3 medium shape).
/// Standard card radius – 12 dp (cyber/IT medium shape).
final double cardRadius;
/// Compact card radius for dense list tiles – 12 dp.
/// Compact card radius for dense list tiles – 10 dp.
final double compactCardRadius;
/// Large container radius – 28 dp (M3 Expressive).
/// Large container radius – 16 dp.
final double containerRadius;
/// Dialog / bottom-sheet radius – 28 dp.
/// Dialog / bottom-sheet radius – 20 dp.
final double dialogRadius;
/// Chip / badge radius – 12 dp.
/// Chip / badge radius – 8 dp.
final double chipRadius;
// convenience shapes
@@ -46,11 +47,11 @@ class AppSurfaces extends ThemeExtension<AppSurfaces> {
final ext = Theme.of(context).extension<AppSurfaces>();
return ext ??
const AppSurfaces(
cardRadius: 16,
compactCardRadius: 12,
containerRadius: 28,
dialogRadius: 28,
chipRadius: 12,
cardRadius: 12,
compactCardRadius: 10,
containerRadius: 16,
dialogRadius: 20,
chipRadius: 8,
);
}
+187 -91
View File
@@ -3,57 +3,140 @@ import 'package:google_fonts/google_fonts.dart';
import 'app_typography.dart';
import 'app_surfaces.dart';
import 'app_status_colors.dart';
/// M3 Expressive theme for TasQ.
/// Refined enterprise **Cyber/IT** theme for TasQ, built on Material 3.
///
/// Key differences from the previous Hybrid M2/M3 theme:
/// * Cards use **tonal elevation** (surfaceTint color overlays) instead of
/// drop-shadows, giving surfaces an organic, seed-tinted look.
/// * Large containers use the M3 standard **28 dp** corner radius.
/// * Buttons follow the M3 hierarchy: FilledButton (primary), Tonal, Elevated,
/// Outlined, and Text.
/// * NavigationBar / NavigationRail use pill-shaped indicators with the
/// secondary-container tonal color.
/// * Spring-physics inspired durations: transitions default to 400 ms with an
/// emphasized easing curve.
/// Identity:
/// * Deep slate surfaces (dark) / cool near-white (light) with an electric
/// **azure** primary, **teal** secondary and **violet** tertiary.
/// * Cards use a flat tonal fill plus a hairline outline (no drop shadow) for a
/// precise, technical feel.
/// * A tightened 12–16 dp shape language (see [AppSurfaces]).
/// * Monospace ([AppMonoText]) reserved for technical data — IDs, IPs, ports,
/// timestamps — and chip labels.
/// * Semantic status tokens ([AppStatusColors]) drive all status/priority color.
class AppTheme {
/// The seed color drives M3's entire tonal palette generation.
static const Color _seed = Color(0xFF4A6FA5);
const AppTheme._();
/// Brand azure — anchors M3's neutral tonal ramp generation.
static const Color _seed = Color(0xFF2D7FF9);
static const AppSurfaces _surfaces = AppSurfaces(
cardRadius: 12,
compactCardRadius: 10,
containerRadius: 16,
dialogRadius: 20,
chipRadius: 8,
);
// ────────────────────────────────────────────────────────────
// COLOR SCHEMES (hand-tuned cyber palette)
// ────────────────────────────────────────────────────────────
static ColorScheme _darkScheme() {
return ColorScheme.fromSeed(
seedColor: _seed,
brightness: Brightness.dark,
).copyWith(
primary: const Color(0xFF4EA8FF),
onPrimary: const Color(0xFF04263F),
primaryContainer: const Color(0xFF0E3A5F),
onPrimaryContainer: const Color(0xFFCFE7FF),
secondary: const Color(0xFF2DD4BF),
onSecondary: const Color(0xFF00201C),
secondaryContainer: const Color(0xFF0C4B45),
onSecondaryContainer: const Color(0xFFB8F5EC),
tertiary: const Color(0xFFA78BFA),
onTertiary: const Color(0xFF1C0E3F),
tertiaryContainer: const Color(0xFF3A2A6B),
onTertiaryContainer: const Color(0xFFE7DDFF),
error: const Color(0xFFF87171),
onError: const Color(0xFF2A0A0A),
errorContainer: const Color(0xFF5A1A1A),
onErrorContainer: const Color(0xFFFFC9C4),
surface: const Color(0xFF0E141B),
onSurface: const Color(0xFFE3E9F0),
onSurfaceVariant: const Color(0xFFA7B4C2),
surfaceContainerLowest: const Color(0xFF080C11),
surfaceContainerLow: const Color(0xFF141B23),
surfaceContainer: const Color(0xFF17202A),
surfaceContainerHigh: const Color(0xFF1E2833),
surfaceContainerHighest: const Color(0xFF26313D),
surfaceDim: const Color(0xFF0B0F14),
surfaceBright: const Color(0xFF2A3542),
outline: const Color(0xFF3A4A5A),
outlineVariant: const Color(0xFF283440),
surfaceTint: const Color(0xFF4EA8FF),
inverseSurface: const Color(0xFFE3E9F0),
onInverseSurface: const Color(0xFF111820),
inversePrimary: const Color(0xFF0B6BCB),
);
}
static ColorScheme _lightScheme() {
return ColorScheme.fromSeed(
seedColor: _seed,
brightness: Brightness.light,
).copyWith(
primary: const Color(0xFF0B6BCB),
onPrimary: const Color(0xFFFFFFFF),
primaryContainer: const Color(0xFFD3E7FF),
onPrimaryContainer: const Color(0xFF00294A),
secondary: const Color(0xFF0F766E),
onSecondary: const Color(0xFFFFFFFF),
secondaryContainer: const Color(0xFFB8F1E8),
onSecondaryContainer: const Color(0xFF00201C),
tertiary: const Color(0xFF6D28D9),
onTertiary: const Color(0xFFFFFFFF),
tertiaryContainer: const Color(0xFFE9DDFF),
onTertiaryContainer: const Color(0xFF23005C),
error: const Color(0xFFDC2626),
onError: const Color(0xFFFFFFFF),
errorContainer: const Color(0xFFFBD9D6),
onErrorContainer: const Color(0xFF450A0A),
surface: const Color(0xFFF6F8FB),
onSurface: const Color(0xFF111820),
onSurfaceVariant: const Color(0xFF42505F),
surfaceContainerLowest: const Color(0xFFFFFFFF),
surfaceContainerLow: const Color(0xFFF0F4F9),
surfaceContainer: const Color(0xFFEDF1F6),
surfaceContainerHigh: const Color(0xFFE4EAF2),
surfaceContainerHighest: const Color(0xFFDCE4EE),
surfaceDim: const Color(0xFFD7DEE8),
surfaceBright: const Color(0xFFF6F8FB),
outline: const Color(0xFF73808F),
outlineVariant: const Color(0xFFC3CDDA),
surfaceTint: const Color(0xFF0B6BCB),
inverseSurface: const Color(0xFF111820),
onInverseSurface: const Color(0xFFEFF3F8),
inversePrimary: const Color(0xFFA6C8FF),
);
}
// ────────────────────────────────────────────────────────────
// LIGHT
// ────────────────────────────────────────────────────────────
static ThemeData light() {
final base = ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: _seed,
brightness: Brightness.light,
),
useMaterial3: true,
return _apply(
ThemeData(colorScheme: _lightScheme(), useMaterial3: true),
AppStatusColors.light,
);
return _apply(base);
}
// ────────────────────────────────────────────────────────────
// DARK
// ────────────────────────────────────────────────────────────
static ThemeData dark() {
final base = ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: _seed,
brightness: Brightness.dark,
),
useMaterial3: true,
return _apply(
ThemeData(colorScheme: _darkScheme(), useMaterial3: true),
AppStatusColors.dark,
);
return _apply(base);
}
// ────────────────────────────────────────────────────────────
// SHARED BUILDER
// ────────────────────────────────────────────────────────────
static ThemeData _apply(ThemeData base) {
static ThemeData _apply(ThemeData base, AppStatusColors status) {
final cs = base.colorScheme;
final isDark = cs.brightness == Brightness.dark;
@@ -68,18 +151,17 @@ class AppTheme {
const TextStyle(letterSpacing: 0.2),
);
const surfaces = AppSurfaces(
cardRadius: 16,
compactCardRadius: 12,
containerRadius: 28,
dialogRadius: 28,
chipRadius: 12,
);
final surfaces = _surfaces;
// Card fill: flat tonal surface + hairline outline (cyber "panel" look).
final cardColor = isDark ? cs.surfaceContainer : cs.surfaceContainerLowest;
return base.copyWith(
textTheme: textTheme,
scaffoldBackgroundColor: isDark ? cs.surface : cs.surfaceContainerLowest,
extensions: [mono, surfaces],
scaffoldBackgroundColor: isDark
? const Color(0xFF0B0F14)
: cs.surfaceContainerLowest,
extensions: [mono, surfaces, status],
// ── AppBar ──────────────────────────────────────────────
appBarTheme: AppBarTheme(
@@ -99,22 +181,27 @@ class AppTheme {
),
),
// ── Cards — M3 Elevated (tonal surface tint, no hard shadow) ──
// ── Cards — flat tonal fill + hairline outline (no hard shadow) ──
cardTheme: CardThemeData(
color: isDark ? cs.surfaceContainer : cs.surfaceContainerLow,
elevation: 1,
color: cardColor,
elevation: 0,
margin: EdgeInsets.zero,
shadowColor: Colors.transparent,
surfaceTintColor: cs.surfaceTint,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(surfaces.cardRadius),
side: BorderSide(color: cs.outlineVariant),
),
),
// ── Chips ───────────────────────────────────────────────
// ── Chips — mono label, tight radius ────────────────────
chipTheme: ChipThemeData(
backgroundColor: cs.surfaceContainerHighest,
side: BorderSide.none,
labelStyle: textTheme.labelSmall,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
side: BorderSide(color: cs.outlineVariant),
labelStyle: mono.label,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(surfaces.chipRadius),
),
),
// ── Dividers ────────────────────────────────────────────
@@ -123,17 +210,17 @@ class AppTheme {
// ── Input Fields ────────────────────────────────────────
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: cs.surfaceContainerLow,
fillColor: isDark ? cs.surfaceContainerLow : cs.surfaceContainer,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(surfaces.cardRadius),
borderSide: BorderSide(color: cs.outlineVariant),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(surfaces.cardRadius),
borderSide: BorderSide(color: cs.outlineVariant),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(surfaces.cardRadius),
borderSide: BorderSide(color: cs.primary, width: 2),
),
contentPadding: const EdgeInsets.symmetric(
@@ -142,87 +229,84 @@ class AppTheme {
),
),
// ── Buttons — M3 Expressive hierarchy ───────────────────
// ── Buttons — M3 hierarchy, tightened shapes ────────────
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(surfaces.cardRadius),
),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
textStyle: textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
),
textStyle: textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w600),
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: cs.surfaceContainerLow,
backgroundColor: cs.surfaceContainerHigh,
foregroundColor: cs.primary,
elevation: 1,
elevation: 0,
shadowColor: Colors.transparent,
surfaceTintColor: cs.surfaceTint,
surfaceTintColor: Colors.transparent,
side: BorderSide(color: cs.outlineVariant),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(surfaces.cardRadius),
),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
textStyle: textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
),
textStyle: textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w600),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(surfaces.cardRadius),
),
side: BorderSide(color: cs.outline),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
textStyle: textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
),
textStyle: textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w600),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(surfaces.cardRadius),
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
textStyle: textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
),
textStyle: textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w600),
),
),
iconButtonTheme: IconButtonThemeData(
style: IconButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(surfaces.compactCardRadius),
),
),
),
segmentedButtonTheme: SegmentedButtonThemeData(
style: ButtonStyle(
shape: WidgetStateProperty.all(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(surfaces.cardRadius),
),
),
),
),
// ── FAB — M3 Expressive ──────────────────────────────────
// ── FAB ──────────────────────────────────────────────────
floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: cs.primaryContainer,
foregroundColor: cs.onPrimaryContainer,
elevation: 3,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(surfaces.containerRadius),
),
),
// ── Navigation — M3 Expressive pill indicators ──────────
// ── Navigation — pill indicators ────────────────────────
navigationDrawerTheme: NavigationDrawerThemeData(
backgroundColor: cs.surface,
indicatorColor: cs.secondaryContainer,
tileHeight: 56,
indicatorShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(28),
borderRadius: BorderRadius.circular(surfaces.containerRadius),
),
),
navigationRailTheme: NavigationRailThemeData(
@@ -238,14 +322,14 @@ class AppTheme {
),
indicatorColor: cs.secondaryContainer,
indicatorShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(28),
borderRadius: BorderRadius.circular(surfaces.containerRadius),
),
),
navigationBarTheme: NavigationBarThemeData(
backgroundColor: cs.surfaceContainer,
indicatorColor: cs.secondaryContainer,
indicatorShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(surfaces.cardRadius),
),
labelTextStyle: WidgetStateProperty.resolveWith((states) {
final selected = states.contains(WidgetState.selected);
@@ -260,15 +344,19 @@ class AppTheme {
// ── List Tiles ──────────────────────────────────────────
listTileTheme: ListTileThemeData(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(surfaces.cardRadius),
),
tileColor: Colors.transparent,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
),
// ── Dialogs ─────────────────────────────────────────────
dialogTheme: DialogThemeData(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)),
surfaceTintColor: cs.surfaceTint,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(surfaces.dialogRadius),
),
surfaceTintColor: Colors.transparent,
backgroundColor: isDark
? cs.surfaceContainerHigh
: cs.surfaceContainerLowest,
@@ -276,20 +364,24 @@ class AppTheme {
// ── Bottom Sheets ───────────────────────────────────────
bottomSheetTheme: BottomSheetThemeData(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(surfaces.dialogRadius),
),
),
backgroundColor: isDark
? cs.surfaceContainerHigh
: cs.surfaceContainerLowest,
surfaceTintColor: cs.surfaceTint,
surfaceTintColor: Colors.transparent,
showDragHandle: true,
),
// ── Snackbar ────────────────────────────────────────────
snackBarTheme: SnackBarThemeData(
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(surfaces.compactCardRadius),
),
),
// ── Search Bar ──────────────────────────────────────────
@@ -297,7 +389,9 @@ class AppTheme {
elevation: WidgetStateProperty.all(0),
backgroundColor: WidgetStateProperty.all(cs.surfaceContainerHigh),
shape: WidgetStateProperty.all(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)),
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(surfaces.containerRadius),
),
),
),
@@ -305,7 +399,7 @@ class AppTheme {
tooltipTheme: TooltipThemeData(
decoration: BoxDecoration(
color: cs.inverseSurface,
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(surfaces.chipRadius),
),
textStyle: textTheme.bodySmall?.copyWith(color: cs.onInverseSurface),
),
@@ -323,8 +417,10 @@ class AppTheme {
// ── PopupMenu ───────────────────────────────────────────
popupMenuTheme: PopupMenuThemeData(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
surfaceTintColor: cs.surfaceTint,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(surfaces.compactCardRadius),
),
surfaceTintColor: Colors.transparent,
color: isDark ? cs.surfaceContainerHigh : cs.surfaceContainerLowest,
),
);