Files
plezy/lib/theme/mono_theme.dart
T
edde746 7d35132509 feat: M3 2024 slider style for video player timelines
Use GappedTrackShape (no stop indicator) globally.
Timeline/volume sliders get local overrides for thinner
track, zero padding, and no overlay. Chapter markers
rendered as track gaps. Live TV timeline matches style.
Timeline thumb hidden when unfocused in dpad mode.
2026-04-03 23:14:11 +02:00

170 lines
5.7 KiB
Dart

import 'package:flutter/material.dart';
import 'gapped_track_shape.dart';
import 'mono_tokens.dart';
ThemeData monoTheme({required bool dark, bool oled = false}) {
// neutral greys tuned for crisp contrast
final ({Color bg, Color surface, Color outline, Color text, Color textMuted}) c;
if (oled) {
c = (
bg: const Color(0xFF000000), // Pure black for OLED
surface: const Color(0xFF0A0A0A), // Very dark gray
outline: const Color(0x1FFFFFFF),
text: const Color(0xFFEDEDED),
textMuted: const Color(0x99EDEDED),
);
} else if (dark) {
c = (
bg: const Color(0xFF0E0F12),
surface: const Color(0xFF15171C),
outline: const Color(0x1FFFFFFF),
text: const Color(0xFFEDEDED),
textMuted: const Color(0x99EDEDED),
);
} else {
c = (
bg: const Color(0xFFF7F7F8),
surface: const Color(0xFFFFFFFF),
outline: const Color(0x19000000),
text: const Color(0xFF111111),
textMuted: const Color(0x99111111),
);
}
final isDark = dark || oled;
final buttonStyle = ButtonStyle(
padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(horizontal: 18, vertical: 14)),
elevation: const WidgetStatePropertyAll(0),
backgroundColor: WidgetStatePropertyAll(c.text),
foregroundColor: WidgetStatePropertyAll(isDark ? c.bg : Colors.white),
shape: const WidgetStatePropertyAll(StadiumBorder()),
);
final base = ThemeData(
useMaterial3: true,
brightness: isDark ? Brightness.dark : Brightness.light,
colorScheme: ColorScheme(
brightness: isDark ? Brightness.dark : Brightness.light,
primary: c.text,
onPrimary: isDark ? c.bg : Colors.white,
secondary: c.text,
onSecondary: c.bg,
surface: c.surface,
onSurface: c.text,
error: const Color(0xFFB00020),
onError: Colors.white,
tertiary: c.text,
onTertiary: c.bg,
primaryContainer: c.surface,
onPrimaryContainer: c.text,
secondaryContainer: c.surface,
onSecondaryContainer: c.text,
surfaceContainerHighest: c.surface,
surfaceContainerLow: c.bg,
surfaceDim: c.bg,
surfaceBright: c.surface,
outline: c.outline,
shadow: Colors.transparent,
scrim: Colors.black,
inverseSurface: c.text,
onInverseSurface: c.bg,
inversePrimary: c.bg,
),
// remove "Material feel"
splashFactory: NoSplash.splashFactory,
highlightColor: Colors.transparent,
dividerColor: c.outline,
scaffoldBackgroundColor: c.bg,
appBarTheme: AppBarTheme(
backgroundColor: c.bg,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: false,
foregroundColor: c.text,
titleTextStyle: TextStyle(color: c.text, fontSize: 18, fontWeight: FontWeight.w700, letterSpacing: -0.2),
),
textTheme: Typography.englishLike2021
.apply(bodyColor: c.text, displayColor: c.text)
.copyWith(
displayLarge: const TextStyle(fontWeight: FontWeight.w700, letterSpacing: -0.5),
titleMedium: const TextStyle(fontWeight: FontWeight.w600),
bodyMedium: TextStyle(color: c.text),
bodySmall: TextStyle(color: c.textMuted),
),
cardTheme: CardThemeData(
color: c.surface,
elevation: 0,
margin: EdgeInsets.zero,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(14))),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: c.text.withValues(alpha: 0.08),
isDense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderSide: BorderSide.none,
),
enabledBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderSide: BorderSide.none,
),
focusedBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderSide: BorderSide.none,
),
hintStyle: TextStyle(color: c.textMuted),
),
elevatedButtonTheme: ElevatedButtonThemeData(style: buttonStyle),
filledButtonTheme: FilledButtonThemeData(style: buttonStyle),
sliderTheme: SliderThemeData(
trackHeight: 16,
trackGap: 6,
thumbSize: const WidgetStatePropertyAll(Size(4, 20)),
thumbShape: const HandleThumbShape(),
trackShape: const GappedTrackShape(),
tickMarkShape: const RoundSliderTickMarkShape(tickMarkRadius: 2),
year2023: false,
),
dividerTheme: DividerThemeData(space: 0, thickness: 1, color: c.outline),
listTileTheme: ListTileThemeData(
dense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
iconColor: c.text,
textColor: c.text,
),
// minimal bottom bar
navigationBarTheme: NavigationBarThemeData(
backgroundColor: c.bg,
elevation: 0,
indicatorColor: Colors.transparent,
labelTextStyle: WidgetStatePropertyAll(TextStyle(color: c.textMuted, fontSize: 11)),
iconTheme: WidgetStateProperty.resolveWith((states) {
final active = states.contains(WidgetState.selected);
return IconThemeData(opacity: active ? 1 : 0.6, size: 22, color: c.text);
}),
),
);
return base.copyWith(
extensions: [
MonoTokens(
radiusSm: 8,
radiusMd: 12,
space: 12,
fast: const Duration(milliseconds: 120),
normal: const Duration(milliseconds: 200),
slow: const Duration(milliseconds: 300),
bg: c.bg,
surface: c.surface,
outline: c.outline,
text: c.text,
textMuted: c.textMuted,
splashFactory: NoSplash.splashFactory,
),
],
);
}