nkbmusic/lib/app/theme.dart
metatroncubeswdev 178d2d00ab Nanga Kathai: Flutter app, self-hosted backend and song tools
- Flutter app (Android/iOS): home, explore, albums, search, player with
  equalizer, mini player, sign up/sign in with email codes, profile,
  encrypted offline downloads, About page
- server/: one-command deploy of self-hosted Supabase behind HestiaCP/nginx
  (or Caddy), migrations, backups, email templates
- supabase/migrations: schema, row-level security, profiles
- tools/: song scanner and importer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:39:26 -04:00

132 lines
4.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
/// Nanga Kathai palette, taken from the heritage emblem.
abstract final class NkColors {
static const ground = Color(0xFF0B1A14); // night tea
static const surface = Color(0xFF14281F); // estate
static const raised = Color(0xFF1F3A2B);
static const miniPlayer = Color(0xFF1A3326);
static const outline = Color(0xFF2A4537);
static const emblemGreen = Color(0xFF1F4A2E);
static const teaLeaf = Color(0xFF7FA857);
static const sun = Color(0xFFF5B335); // accent
static const roofRed = Color(0xFFC9502F);
static const cream = Color(0xFFFBF7EE); // primary text
static const muted = Color(0xFF9FB3A6); // secondary text
static const mutedOnPhoto = Color(0xFFDCE5DF);
static const like = Color(0xFFE0503F);
}
ThemeData buildNkTheme() {
final base = ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
scaffoldBackgroundColor: NkColors.ground,
colorScheme: const ColorScheme.dark(
primary: NkColors.sun,
onPrimary: NkColors.ground,
secondary: NkColors.teaLeaf,
surface: NkColors.ground,
onSurface: NkColors.cream,
surfaceContainer: NkColors.surface,
surfaceContainerHigh: NkColors.raised,
outline: NkColors.outline,
error: NkColors.roofRed,
),
);
// google_fonts 9 builds its TextTheme helpers on package:material_ui, so only its
// TextStyle API is used here.
final body = base.textTheme.apply(
fontFamily: GoogleFonts.manrope().fontFamily,
bodyColor: NkColors.cream,
displayColor: NkColors.cream,
);
TextStyle display(double size, [FontWeight w = FontWeight.w800]) =>
GoogleFonts.sora(fontSize: size, fontWeight: w, color: NkColors.cream, height: 1.1);
return base.copyWith(
textTheme: body.copyWith(
headlineLarge: display(32),
headlineMedium: display(28),
headlineSmall: display(24),
titleLarge: display(20, FontWeight.w700),
titleMedium: body.titleMedium?.copyWith(fontWeight: FontWeight.w700),
bodySmall: body.bodySmall?.copyWith(color: NkColors.muted),
),
splashFactory: InkSparkle.splashFactory,
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: NkColors.sun,
foregroundColor: NkColors.ground,
minimumSize: const Size(64, 52),
shape: const StadiumBorder(),
textStyle: GoogleFonts.manrope(fontSize: 16, fontWeight: FontWeight.w700),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: NkColors.cream,
minimumSize: const Size(64, 50),
side: const BorderSide(color: NkColors.outline, width: 1.5),
shape: const StadiumBorder(),
textStyle: GoogleFonts.manrope(fontSize: 15, fontWeight: FontWeight.w700),
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: NkColors.surface,
hintStyle: const TextStyle(color: NkColors.muted),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(color: NkColors.outline, width: 1.5),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(color: NkColors.outline, width: 1.5),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(color: NkColors.sun, width: 1.5),
),
),
chipTheme: base.chipTheme.copyWith(
backgroundColor: NkColors.raised,
selectedColor: NkColors.sun,
side: BorderSide.none,
shape: const StadiumBorder(),
labelStyle: GoogleFonts.manrope(fontWeight: FontWeight.w600, color: NkColors.cream),
secondaryLabelStyle: GoogleFonts.manrope(fontWeight: FontWeight.w700, color: NkColors.ground),
showCheckmark: false,
),
sliderTheme: const SliderThemeData(
activeTrackColor: NkColors.sun,
inactiveTrackColor: Color(0x38FBF7EE),
thumbColor: NkColors.sun,
trackHeight: 4,
overlayShape: RoundSliderOverlayShape(overlayRadius: 18),
),
navigationBarTheme: NavigationBarThemeData(
backgroundColor: NkColors.ground,
indicatorColor: Colors.transparent,
height: 72,
labelTextStyle: WidgetStateProperty.resolveWith(
(s) => GoogleFonts.manrope(
fontSize: 12,
fontWeight: FontWeight.w600,
color: s.contains(WidgetState.selected) ? NkColors.sun : NkColors.muted,
),
),
iconTheme: WidgetStateProperty.resolveWith(
(s) => IconThemeData(color: s.contains(WidgetState.selected) ? NkColors.sun : NkColors.muted),
),
),
snackBarTheme: const SnackBarThemeData(
backgroundColor: NkColors.raised,
contentTextStyle: TextStyle(color: NkColors.cream),
behavior: SnackBarBehavior.floating,
),
);
}