sojorn/sojorn_app/lib/theme/app_theme.dart
2026-02-15 00:33:24 -06:00

338 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'theme_extensions.dart';
import 'tokens.dart';
enum AppThemeType {
basic,
pop,
}
class AppTheme {
static AppThemeType _currentThemeType = AppThemeType.basic;
static void setThemeType(AppThemeType type) {
_currentThemeType = type;
}
static const Map<AppThemeType, SojornExt> _extensions = {
AppThemeType.basic: SojornExt.basic,
AppThemeType.pop: SojornExt.pop,
};
static SojornExt get ext => _extensions[_currentThemeType]!;
static SojornBrandColors get _brand => ext.brandColors;
static SojornFlowLines get _lines => ext.flowLines;
// Backward compatible color getters.
static Color get navyBlue => _brand.navyBlue;
static Color get navyText => _brand.navyText;
static Color get egyptianBlue => _brand.egyptianBlue;
static Color get brightNavy => _brand.brightNavy;
static Color get royalPurple => _brand.royalPurple;
static Color get ksuPurple => _brand.ksuPurple;
static Color get queenPink => _brand.queenPink;
static Color get queenPinkLight => _brand.queenPinkLight;
static Color get scaffoldBg => _brand.scaffoldBg;
static Color get cardSurface => _brand.cardSurface;
static const Color white = SojornColors.basicWhite;
// Semantic
static const Color error = SojornColors.error;
static const Color destructive = SojornColors.destructive;
static Color get success => ksuPurple;
static const Color warning = SojornColors.warning;
static const Color info = SojornColors.info;
// NSFW / Sensitive
static const Color nsfwWarningBg = SojornColors.nsfwWarningBg;
static const Color nsfwWarningBorder = SojornColors.nsfwWarningBorder;
static const Color nsfwWarningIcon = SojornColors.nsfwWarningIcon;
static const Color nsfwWarningText = SojornColors.nsfwWarningText;
static const Color nsfwWarningSubText = SojornColors.nsfwWarningSubText;
static const Color nsfwRevealText = SojornColors.nsfwRevealText;
// Sponsored / Ad
static const Color sponsoredBadgeBg = SojornColors.sponsoredBadgeBg;
static const Color sponsoredBadgeText = SojornColors.sponsoredBadgeText;
// Overlays
static const Color overlayDark = SojornColors.overlayDark;
static const Color overlayLight = SojornColors.overlayLight;
static const Color mediaErrorBg = SojornColors.mediaErrorBg;
static const Color mediaLoadingBg = SojornColors.mediaLoadingBg;
// Trust Tiers
static Color get tierEstablished => ext.trustTierColors.established;
static Color get tierTrusted => ext.trustTierColors.trusted;
static Color get tierNew => ext.trustTierColors.fresh;
// Dimensions
static const double spacingSm = SojornSpacing.sm;
static const double spacingMd = SojornSpacing.md;
static const double spacingLg = SojornSpacing.lg;
static const double spacingXs = SojornSpacing.xs;
static const double spacing2xs = SojornSpacing.xxs;
static const double radiusSm = SojornRadii.sm;
static const double radiusXs = SojornRadii.xs;
static const double radiusMd = SojornRadii.md;
static const double radiusMdValue = SojornRadii.md;
static const double radiusFull = SojornRadii.full;
// Text Colors - COLOR HIERARCHY: Content neutral, UI branded.
static Color get textPrimary => navyText;
static Color get textSecondary => navyText;
static Color get textTertiary => navyText;
static const Color textDisabled = SojornColors.textDisabled;
static const Color textOnAccent = SojornColors.textOnAccent;
static Color get border => egyptianBlue;
// Post Content - Neutral for contrast with purple UI.
static const Color postContent = SojornColors.postContent;
static const Color postContentLight = SojornColors.postContentLight;
// Lines
static const double borderWidth = SojornLines.border;
static const double dividerThickness = SojornLines.dividerStrong;
static const double flowLineWidth = SojornLines.flow;
// Post Specific Spacing
static const double spacingPostShort = SojornSpacing.postShort;
static const double spacingPostMedium = SojornSpacing.postMedium;
static const double spacingPostLong = SojornSpacing.postLong;
// Typography
static TextTheme get textTheme => _buildTextTheme(_brand);
// Backward Compat Getters
static TextStyle get postBody => textTheme.bodyLarge!;
static TextStyle get postBodyShort => textTheme.bodyLarge!.copyWith(fontSize: 22);
static TextStyle get postBodyLong => textTheme.bodyLarge!.copyWith(fontSize: 18);
static TextStyle get postBodyReflective => textTheme.bodyLarge!.copyWith(
fontStyle: FontStyle.italic,
color: ksuPurple,
);
// Text Style Getters
static TextStyle get bodyMedium => textTheme.bodyMedium!;
static TextStyle get bodyLarge => textTheme.bodyLarge!;
static TextStyle get headlineMedium => textTheme.headlineMedium!;
static TextStyle get headlineSmall => textTheme.headlineSmall!;
static TextStyle get labelLarge => textTheme.labelLarge!;
static TextStyle get labelMedium => textTheme.labelMedium!;
static TextStyle get labelSmall => textTheme.labelSmall!;
// Theme Data
static ThemeData get lightTheme => themeFor(_currentThemeType);
static ThemeData themeFor(AppThemeType type) {
final ext = _extensions[type]!;
return _buildTheme(ext);
}
static ThemeData _buildTheme(SojornExt ext) {
final brand = ext.brandColors;
final lines = ext.flowLines;
final textTheme = _buildTextTheme(brand);
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
scaffoldBackgroundColor: brand.scaffoldBg,
primaryColor: brand.navyBlue,
colorScheme: _buildColorScheme(brand),
textTheme: textTheme,
fontFamily: GoogleFonts.literata().fontFamily,
fontFamilyFallback: const ['Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji'],
appBarTheme: _buildAppBarTheme(brand, textTheme, lines),
cardTheme: _buildCardTheme(brand, lines),
elevatedButtonTheme: _buildElevatedButtonTheme(brand),
textButtonTheme: _buildTextButtonTheme(brand),
bottomNavigationBarTheme: _buildBottomNavTheme(brand, ext.options),
floatingActionButtonTheme: _buildFabTheme(brand, ext.options),
dividerTheme: _buildDividerTheme(brand, lines),
inputDecorationTheme: _buildInputTheme(brand, lines),
extensions: <ThemeExtension<dynamic>>[ext],
);
}
static TextTheme _buildTextTheme(SojornBrandColors brand) {
return GoogleFonts.literataTextTheme().copyWith(
bodyLarge: GoogleFonts.literata(
fontSize: 17,
height: 1.5,
fontWeight: FontWeight.w400,
color: SojornColors.postContent,
),
bodyMedium: GoogleFonts.literata(
fontSize: 16,
height: 1.5,
fontWeight: FontWeight.w400,
color: SojornColors.postContentLight,
),
labelSmall: GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w600,
color: brand.egyptianBlue,
letterSpacing: 0.2,
),
labelMedium: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w600,
color: brand.brightNavy,
letterSpacing: 0,
),
labelLarge: GoogleFonts.inter(
fontSize: 15,
fontWeight: FontWeight.w800,
color: brand.navyBlue,
),
headlineSmall: GoogleFonts.literata(
fontSize: 22,
fontWeight: FontWeight.w700,
color: brand.navyBlue,
letterSpacing: -0.5,
),
headlineMedium: GoogleFonts.literata(
fontSize: 26,
fontWeight: FontWeight.w700,
color: brand.navyBlue,
letterSpacing: 0,
),
);
}
static ColorScheme _buildColorScheme(SojornBrandColors brand) {
return ColorScheme.light(
primary: brand.navyBlue,
secondary: brand.brightNavy,
tertiary: brand.royalPurple,
surface: brand.cardSurface,
onSurface: brand.navyText,
error: SojornColors.error,
);
}
static AppBarTheme _buildAppBarTheme(
SojornBrandColors brand,
TextTheme textTheme,
SojornFlowLines lines,
) {
return AppBarTheme(
backgroundColor: brand.cardSurface,
surfaceTintColor: const Color(0x00000000),
elevation: 0,
centerTitle: false,
iconTheme: IconThemeData(color: brand.navyBlue),
titleTextStyle: textTheme.headlineSmall,
systemOverlayStyle: SystemUiOverlayStyle.dark,
shape: Border(
bottom: BorderSide(color: brand.egyptianBlue, width: lines.appBarBorder),
),
);
}
static CardThemeData _buildCardTheme(SojornBrandColors brand, SojornFlowLines lines) {
return CardThemeData(
color: brand.cardSurface,
elevation: 0,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(SojornRadii.lg),
side: BorderSide(color: brand.egyptianBlue, width: lines.cardBorder),
),
);
}
static ElevatedButtonThemeData _buildElevatedButtonTheme(SojornBrandColors brand) {
return ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: brand.brightNavy,
foregroundColor: SojornColors.textOnAccent,
elevation: 0,
padding: const EdgeInsets.symmetric(
horizontal: SojornSpacing.lg,
vertical: SojornSpacing.md,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(SojornRadii.md),
),
textStyle: GoogleFonts.inter(fontWeight: FontWeight.bold),
),
);
}
static TextButtonThemeData _buildTextButtonTheme(SojornBrandColors brand) {
return TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: brand.egyptianBlue,
textStyle: GoogleFonts.inter(fontWeight: FontWeight.w600),
),
);
}
static BottomNavigationBarThemeData _buildBottomNavTheme(
SojornBrandColors brand,
SojornThemeOptions options,
) {
return BottomNavigationBarThemeData(
backgroundColor: brand.cardSurface,
selectedItemColor: brand.royalPurple,
unselectedItemColor: SojornColors.bottomNavUnselected,
type: BottomNavigationBarType.fixed,
showSelectedLabels: options.showBottomNavLabels,
showUnselectedLabels: options.showBottomNavLabels,
elevation: options.bottomNavElevation,
);
}
static FloatingActionButtonThemeData _buildFabTheme(
SojornBrandColors brand,
SojornThemeOptions options,
) {
return FloatingActionButtonThemeData(
backgroundColor: brand.brightNavy,
foregroundColor: SojornColors.textOnAccent,
elevation: options.fabElevation,
shape: options.fabShape,
);
}
static DividerThemeData _buildDividerTheme(
SojornBrandColors brand,
SojornFlowLines lines,
) {
return DividerThemeData(
color: brand.queenPink,
thickness: lines.divider,
space: SojornSpacing.lg,
);
}
static InputDecorationTheme _buildInputTheme(
SojornBrandColors brand,
SojornFlowLines lines,
) {
return InputDecorationTheme(
filled: true,
fillColor: brand.cardSurface,
contentPadding: const EdgeInsets.all(SojornSpacing.md),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(SojornRadii.md),
borderSide: BorderSide(color: brand.egyptianBlue, width: lines.inputBorder),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(SojornRadii.md),
borderSide: BorderSide(color: brand.egyptianBlue, width: lines.inputBorder),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(SojornRadii.md),
borderSide: BorderSide(color: brand.royalPurple, width: lines.inputFocusBorder),
),
);
}
}