Files
Tracker/lib/theme/app_theme.dart
Sterlen b4b2bfe749 feat: Implement husband features and fix iOS Safari web startup
Implement initial features for husband's companion app, including mock data
service and husband notes screen. Refactor scripture and cycle services
for improved stability and testability. Address iOS Safari web app
startup issue by removing deprecated initialization.

- Implemented MockDataService and HusbandNotesScreen.
- Converted _DashboardTab and DevotionalScreen to StatefulWidgets for robust
  scripture provider initialization.
- Refactored CycleService to use immutable CycleInfo class, reducing UI rebuilds.
- Removed deprecated window.flutterConfiguration from index.html, resolving
  Flutter web app startup failure on iOS Safari.
- Updated and fixed related tests.
2025-12-26 22:40:52 -06:00

463 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
/// App color palette for wife's experience
class AppColors {
// Primary Colors (Wife's App)
static const Color blushPink = Color(0xFFF8E1E7);
static const Color rose = Color(0xFFE8A0B0);
static const Color sageGreen = Color(0xFFA8C5A8);
static const Color lavender = Color(0xFFD4C4E8);
static const Color cream = Color(0xFFFDF8F5);
static const Color softGold = Color(0xFFD4A574);
// Text Colors
static const Color charcoal = Color(0xFF3D3D3D);
static const Color warmGray = Color(0xFF7A7A7A);
static const Color lightGray = Color(0xFFB8B8B8);
// Husband's App Colors
static const Color navyBlue = Color(0xFF2C3E50);
static const Color steelBlue = Color(0xFF5D7B93);
static const Color warmCream = Color(0xFFF5F0E8);
static const Color gold = Color(0xFFC9A961);
static const Color softCoral = Color(0xFFE8B4A8);
// Phase Colors
static const Color menstrualPhase = Color(0xFFE88A9E);
static const Color follicularPhase = Color(0xFF8BC5A3);
static const Color ovulationPhase = Color(0xFFB8A5D4);
static const Color lutealPhase = Color(0xFF8BA5C5);
// Semantic Colors
static const Color success = Color(0xFF7AB98A);
static const Color warning = Color(0xFFE8C567);
static const Color error = Color(0xFFE87B7B);
static const Color info = Color(0xFF7BB8E8);
}
/// App theme configuration
class AppTheme {
static ThemeData get lightTheme {
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
// Color Scheme
colorScheme: const ColorScheme.light(
primary: AppColors.sageGreen,
secondary: AppColors.rose,
tertiary: AppColors.lavender,
surface: AppColors.cream,
error: AppColors.error,
onPrimary: Colors.white,
onSecondary: Colors.white,
onSurface: AppColors.charcoal,
),
// Scaffold
scaffoldBackgroundColor: AppColors.cream,
// AppBar
appBarTheme: AppBarTheme(
backgroundColor: AppColors.cream,
foregroundColor: AppColors.charcoal,
elevation: 0,
centerTitle: true,
titleTextStyle: GoogleFonts.outfit(
fontSize: 20,
fontWeight: FontWeight.w600,
color: AppColors.charcoal,
),
),
// Text Theme
textTheme: TextTheme(
displayLarge: GoogleFonts.outfit(
fontSize: 32,
fontWeight: FontWeight.w600,
color: AppColors.charcoal,
),
displayMedium: GoogleFonts.outfit(
fontSize: 28,
fontWeight: FontWeight.w600,
color: AppColors.charcoal,
),
headlineLarge: GoogleFonts.outfit(
fontSize: 24,
fontWeight: FontWeight.w600,
color: AppColors.charcoal,
),
headlineMedium: GoogleFonts.outfit(
fontSize: 20,
fontWeight: FontWeight.w500,
color: AppColors.charcoal,
),
titleLarge: GoogleFonts.outfit(
fontSize: 18,
fontWeight: FontWeight.w500,
color: AppColors.charcoal,
),
titleMedium: GoogleFonts.outfit(
fontSize: 16,
fontWeight: FontWeight.w500,
color: AppColors.charcoal,
),
bodyLarge: GoogleFonts.outfit(
fontSize: 16,
fontWeight: FontWeight.w400,
color: AppColors.charcoal,
),
bodyMedium: GoogleFonts.outfit(
fontSize: 14,
fontWeight: FontWeight.w400,
color: AppColors.charcoal,
),
bodySmall: GoogleFonts.outfit(
fontSize: 12,
fontWeight: FontWeight.w400,
color: AppColors.warmGray,
),
labelLarge: GoogleFonts.outfit(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.charcoal,
),
),
// Card Theme
cardTheme: CardThemeData(
color: Colors.white,
elevation: 2,
shadowColor: AppColors.charcoal.withOpacity(0.1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
// Button Themes
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.sageGreen,
foregroundColor: Colors.white,
elevation: 2,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
textStyle: GoogleFonts.outfit(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.sageGreen,
side: const BorderSide(color: AppColors.sageGreen, width: 1.5),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
textStyle: GoogleFonts.outfit(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: AppColors.rose,
textStyle: GoogleFonts.outfit(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
// Input Decoration
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: AppColors.lightGray.withOpacity(0.5)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: AppColors.lightGray.withOpacity(0.5)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColors.sageGreen, width: 2),
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
hintStyle: GoogleFonts.outfit(
color: AppColors.lightGray,
fontSize: 14,
),
),
// Bottom Navigation
bottomNavigationBarTheme: BottomNavigationBarThemeData(
backgroundColor: Colors.white,
selectedItemColor: AppColors.sageGreen,
unselectedItemColor: AppColors.warmGray,
type: BottomNavigationBarType.fixed,
elevation: 8,
selectedLabelStyle: GoogleFonts.outfit(
fontSize: 12,
fontWeight: FontWeight.w500,
),
unselectedLabelStyle: GoogleFonts.outfit(
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
// Floating Action Button
floatingActionButtonTheme: const FloatingActionButtonThemeData(
backgroundColor: AppColors.sageGreen,
foregroundColor: Colors.white,
elevation: 4,
),
// Slider Theme
sliderTheme: SliderThemeData(
activeTrackColor: AppColors.sageGreen,
inactiveTrackColor: AppColors.lightGray.withOpacity(0.3),
thumbColor: AppColors.sageGreen,
overlayColor: AppColors.sageGreen.withOpacity(0.2),
trackHeight: 4,
),
// Divider
dividerTheme: DividerThemeData(
color: AppColors.lightGray.withOpacity(0.3),
thickness: 1,
space: 24,
),
);
}
static ThemeData get darkTheme {
return ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
// Color Scheme
colorScheme: ColorScheme.dark(
primary: AppColors.sageGreen,
secondary: AppColors.rose,
tertiary: AppColors.lavender,
surface: const Color(0xFF1E1E1E),
error: AppColors.error,
onPrimary: Colors.white,
onSecondary: Colors.white,
onSurface: Colors.white,
onSurfaceVariant: Colors.white70,
outline: Colors.white.withOpacity(0.1),
),
// Scaffold
scaffoldBackgroundColor: const Color(0xFF121212),
// AppBar
appBarTheme: AppBarTheme(
backgroundColor: const Color(0xFF121212),
foregroundColor: Colors.white,
elevation: 0,
centerTitle: true,
titleTextStyle: GoogleFonts.outfit(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
// Text Theme
textTheme: TextTheme(
displayLarge: GoogleFonts.outfit(
fontSize: 32,
fontWeight: FontWeight.w600,
color: Colors.white,
),
displayMedium: GoogleFonts.outfit(
fontSize: 28,
fontWeight: FontWeight.w600,
color: Colors.white,
),
headlineLarge: GoogleFonts.outfit(
fontSize: 24,
fontWeight: FontWeight.w600,
color: Colors.white,
),
headlineMedium: GoogleFonts.outfit(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white,
),
titleLarge: GoogleFonts.outfit(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Colors.white,
),
titleMedium: GoogleFonts.outfit(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.white,
),
bodyLarge: GoogleFonts.outfit(
fontSize: 16,
fontWeight: FontWeight.w400,
color: Colors.white,
),
bodyMedium: GoogleFonts.outfit(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Colors.white70,
),
bodySmall: GoogleFonts.outfit(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.white54,
),
labelLarge: GoogleFonts.outfit(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
// Card Theme
cardTheme: CardThemeData(
color: const Color(0xFF1E1E1E),
elevation: 0, // Material 3 uses color/opacity for elevation in dark mode
shadowColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(color: Colors.white.withOpacity(0.05)),
),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
// Button Themes
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.sageGreen,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
textStyle: GoogleFonts.outfit(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.sageGreen,
side: BorderSide(color: AppColors.sageGreen.withOpacity(0.5), width: 1.5),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
textStyle: GoogleFonts.outfit(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
// Input Decoration
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: const Color(0xFF1E1E1E),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.white.withOpacity(0.1)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.white.withOpacity(0.1)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColors.sageGreen, width: 2),
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
hintStyle: GoogleFonts.outfit(
color: Colors.white38,
fontSize: 14,
),
),
// Bottom Navigation
bottomNavigationBarTheme: BottomNavigationBarThemeData(
backgroundColor: const Color(0xFF1E1E1E),
selectedItemColor: AppColors.sageGreen,
unselectedItemColor: Colors.white38,
type: BottomNavigationBarType.fixed,
elevation: 0,
selectedLabelStyle: GoogleFonts.outfit(
fontSize: 12,
fontWeight: FontWeight.w500,
),
unselectedLabelStyle: GoogleFonts.outfit(
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
// Slider Theme
sliderTheme: SliderThemeData(
activeTrackColor: AppColors.sageGreen,
inactiveTrackColor: Colors.white.withOpacity(0.1),
thumbColor: AppColors.sageGreen,
overlayColor: AppColors.sageGreen.withOpacity(0.2),
trackHeight: 4,
tickMarkShape: const RoundSliderTickMarkShape(),
activeTickMarkColor: Colors.white24,
inactiveTickMarkColor: Colors.white10,
),
// Divider
dividerTheme: DividerThemeData(
color: Colors.white.withOpacity(0.05),
thickness: 1,
space: 24,
),
);
}
}
/// Scripture text style
TextStyle scriptureStyle(BuildContext context, {double? fontSize}) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return GoogleFonts.lora(
fontSize: fontSize ?? 16,
fontStyle: FontStyle.italic,
color: isDark ? Colors.white : AppColors.charcoal,
height: 1.6,
);
}
/// Scripture reference style
TextStyle scriptureRefStyle(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return GoogleFonts.outfit(
fontSize: 12,
fontWeight: FontWeight.w500,
color: isDark ? Colors.white54 : AppColors.warmGray,
);
}