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.
463 lines
14 KiB
Dart
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,
|
|
);
|
|
}
|