init
This commit is contained in:
commit
d205277cdd
182 changed files with 22978 additions and 0 deletions
24
lib/theme/accent.dart
Normal file
24
lib/theme/accent.dart
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'tokens.dart';
|
||||
|
||||
/// Holds the current accent color — the "hero" of Ratune's aesthetic.
|
||||
///
|
||||
/// In the `dynamic` theme this is extracted from the playing track's album
|
||||
/// art and boosted for readability in OKLab space (Ratune `color.rs:11-64`),
|
||||
/// then interpolated toward over ~400ms. Phase 1 just exposes the default and
|
||||
/// a setter; the extraction + animation land in Phase 2 with playback.
|
||||
class AccentNotifier extends StateNotifier<Color> {
|
||||
AccentNotifier() : super(RatuneColors.accentDefault);
|
||||
|
||||
/// Snap to a new accent (e.g. from album-art extraction).
|
||||
void set(Color color) => state = color;
|
||||
|
||||
/// Return to the fixed default (e.g. no art / `static` theme).
|
||||
void reset() => state = RatuneColors.accentDefault;
|
||||
}
|
||||
|
||||
final accentProvider = StateNotifierProvider<AccentNotifier, Color>(
|
||||
(ref) => AccentNotifier(),
|
||||
);
|
||||
29
lib/theme/accent_extract.dart
Normal file
29
lib/theme/accent_extract.dart
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:palette_generator/palette_generator.dart';
|
||||
|
||||
/// Extract a lively accent color from album art, mirroring Ratune's
|
||||
/// art-driven `dynamic` theme (`color.rs`): pick the most vibrant swatch, then
|
||||
/// nudge it into a readable lightness/saturation band. Ratune does the boost in
|
||||
/// OKLab; this HSL approximation is close enough for now (OKLab is a later
|
||||
/// refinement noted in the roadmap).
|
||||
Future<Color?> extractAccent(ImageProvider image) async {
|
||||
final palette = await PaletteGenerator.fromImageProvider(
|
||||
image,
|
||||
size: const Size(200, 200),
|
||||
maximumColorCount: 8,
|
||||
);
|
||||
|
||||
final picked = palette.vibrantColor?.color ??
|
||||
palette.lightVibrantColor?.color ??
|
||||
palette.dominantColor?.color;
|
||||
if (picked == null) return null;
|
||||
return _ensureReadable(picked);
|
||||
}
|
||||
|
||||
Color _ensureReadable(Color c) {
|
||||
final hsl = HSLColor.fromColor(c);
|
||||
// Keep it bright enough to read on near-black, saturated enough to feel alive.
|
||||
final lightness = hsl.lightness.clamp(0.45, 0.70);
|
||||
final saturation = hsl.saturation < 0.40 ? 0.55 : hsl.saturation;
|
||||
return hsl.withLightness(lightness).withSaturation(saturation).toColor();
|
||||
}
|
||||
52
lib/theme/app_theme.dart
Normal file
52
lib/theme/app_theme.dart
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import 'tokens.dart';
|
||||
|
||||
/// Builds the app [ThemeData] from the Ratune tokens.
|
||||
///
|
||||
/// Monospace type is core to the identity — every surface uses JetBrains Mono
|
||||
/// (a close analog to the terminal fonts in Ratune's screenshots). The [accent]
|
||||
/// is passed in so the theme rebuilds when album-art extraction changes it.
|
||||
ThemeData buildRatuneTheme(Color accent) {
|
||||
final mono = GoogleFonts.jetBrainsMonoTextTheme(
|
||||
ThemeData.dark().textTheme,
|
||||
).apply(
|
||||
bodyColor: RatuneColors.foreground,
|
||||
displayColor: RatuneColors.foreground,
|
||||
);
|
||||
|
||||
final scheme = ColorScheme.fromSeed(
|
||||
seedColor: accent,
|
||||
brightness: Brightness.dark,
|
||||
surface: RatuneColors.surface,
|
||||
).copyWith(
|
||||
primary: accent,
|
||||
secondary: accent,
|
||||
onSurface: RatuneColors.foreground,
|
||||
);
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: RatuneColors.background,
|
||||
canvasColor: RatuneColors.background,
|
||||
colorScheme: scheme,
|
||||
textTheme: mono,
|
||||
primaryTextTheme: mono,
|
||||
dividerColor: RatuneColors.border,
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
highlightColor: Colors.transparent,
|
||||
// Keep chrome flat and quiet — the content (and accent) carry the look.
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: RatuneColors.background,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
centerTitle: false,
|
||||
),
|
||||
listTileTheme: const ListTileThemeData(
|
||||
dense: true,
|
||||
minVerticalPadding: RatuneSpacing.sm,
|
||||
),
|
||||
);
|
||||
}
|
||||
49
lib/theme/tokens.dart
Normal file
49
lib/theme/tokens.dart
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
|
||||
/// Design tokens mirroring Ratune's `dynamic`/`static` theme defaults
|
||||
/// (see ratune `docs/sample-config.toml`, `theme.rs`, `color.rs`).
|
||||
///
|
||||
/// The palette is intentionally near-black and low-contrast; the *accent*
|
||||
/// is the one lively color and, in the `dynamic` theme, is extracted live
|
||||
/// from the current album art (Phase 2). Everything else stays fixed.
|
||||
class RatuneColors {
|
||||
const RatuneColors._();
|
||||
|
||||
/// App canvas — `background = #1a1a1a`.
|
||||
static const Color background = Color(0xFF1A1A1A);
|
||||
|
||||
/// Panel/surface fill — `surface = #161616` (slightly darker than canvas).
|
||||
static const Color surface = Color(0xFF161616);
|
||||
|
||||
/// Primary text — `foreground = #d4d0c8` (warm off-white, not pure white).
|
||||
static const Color foreground = Color(0xFFD4D0C8);
|
||||
|
||||
/// Secondary/muted text — `dimmed = #5a5858`.
|
||||
static const Color dimmed = Color(0xFF5A5858);
|
||||
|
||||
/// Hairline borders (inactive) — `border = #252525`.
|
||||
static const Color border = Color(0xFF252525);
|
||||
|
||||
/// Hairline borders when a pane is focused — `border_active`.
|
||||
/// Ratune tints this toward the accent; we start with a lighter grey and
|
||||
/// swap in the accent at runtime once art extraction lands.
|
||||
static const Color borderActive = Color(0xFF3A3A3A);
|
||||
|
||||
/// Default accent — `accent = #ff8c00`. Overridden per-track in `dynamic`.
|
||||
static const Color accentDefault = Color(0xFFFF8C00);
|
||||
}
|
||||
|
||||
/// Spacing scale — dense, "player as instrument" rather than airy mobile cards.
|
||||
class RatuneSpacing {
|
||||
const RatuneSpacing._();
|
||||
|
||||
static const double xs = 2;
|
||||
static const double sm = 4;
|
||||
static const double md = 8;
|
||||
static const double lg = 12;
|
||||
static const double xl = 16;
|
||||
|
||||
/// Minimum touch target per Apple/Material guidance — keep rows dense
|
||||
/// visually but pad hit areas to at least this.
|
||||
static const double minTouchTarget = 44;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue