feat: Murattal player enhancements & prayer schedule auto-scroll

- Murattal: Spotify-style 5-button controls [Shuffle, Prev, Play, Next, Playlist]
- Murattal: Animated 7-bar equalizer visualization in player circle
- Murattal: Unsplash API background with frosted glass player overlay
- Murattal: Transparent AppBar with backdrop blur
- Murattal: Surah playlist bottom sheet with full 114 Surah list
- Murattal: Auto-play disabled on screen open, enabled on navigation
- Murattal: Shuffle mode for random Surah playback
- Murattal: Photographer attribution per Unsplash guidelines
- Dashboard: Auto-scroll prayer schedule to next active prayer
- Fix: setState lifecycle errors on Reading & Murattal screens
- Setup: flutter_dotenv, cached_network_image, url_launcher deps
This commit is contained in:
dwindown
2026-03-13 15:42:17 +07:00
commit faadc1865d
189 changed files with 23834 additions and 0 deletions

0
lib/app/theme/.gitkeep Normal file
View File

View File

@@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
/// All color tokens from PRD §3.1 — light and dark values as static constants.
class AppColors {
AppColors._();
// ── Primary ──
static const Color primary = Color(0xFF70DF20);
static const Color onPrimary = Color(0xFF0A1A00);
// ── Background ──
static const Color backgroundLight = Color(0xFFF7F8F6);
static const Color backgroundDark = Color(0xFF182111);
// ── Surface ──
static const Color surfaceLight = Color(0xFFFFFFFF);
static const Color surfaceDark = Color(0xFF1E2A14);
// ── Sage (secondary text / section labels) ──
static const Color sage = Color(0xFF728764);
// ── Cream (dividers, borders — light mode only) ──
static const Color cream = Color(0xFFF2F4F0);
// ── Text ──
static const Color textPrimaryLight = Color(0xFF1A2A0A);
static const Color textPrimaryDark = Color(0xFFF2F4F0);
static const Color textSecondaryLight = Color(0xFF64748B);
static const Color textSecondaryDark = Color(0xFF94A3B8);
// ── Semantic ──
static const Color errorLight = Color(0xFFEF4444);
static const Color errorDark = Color(0xFFF87171);
static const Color successLight = Color(0xFF22C55E);
static const Color successDark = Color(0xFF4ADE80);
// ── Convenience helpers for theme building ──
static ColorScheme get lightColorScheme => ColorScheme.light(
primary: primary,
onPrimary: onPrimary,
surface: surfaceLight,
onSurface: textPrimaryLight,
error: errorLight,
onError: Colors.white,
secondary: sage,
onSecondary: Colors.white,
);
static ColorScheme get darkColorScheme => ColorScheme.dark(
primary: primary,
onPrimary: onPrimary,
surface: surfaceDark,
onSurface: textPrimaryDark,
error: errorDark,
onError: Colors.black,
secondary: sage,
onSecondary: Colors.white,
);
}

View File

@@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
/// Typography definitions from PRD §3.2.
/// Plus Jakarta Sans (bundled) for UI text, Amiri (bundled) for Arabic content.
class AppTextStyles {
AppTextStyles._();
static const String _fontFamily = 'PlusJakartaSans';
/// Builds the full TextTheme for the app using bundled Plus Jakarta Sans.
static const TextTheme textTheme = TextTheme(
displayLarge: TextStyle(
fontFamily: _fontFamily,
fontSize: 32,
fontWeight: FontWeight.w800,
),
headlineMedium: TextStyle(
fontFamily: _fontFamily,
fontSize: 24,
fontWeight: FontWeight.w700,
),
titleLarge: TextStyle(
fontFamily: _fontFamily,
fontSize: 20,
fontWeight: FontWeight.w700,
),
titleMedium: TextStyle(
fontFamily: _fontFamily,
fontSize: 16,
fontWeight: FontWeight.w600,
),
bodyLarge: TextStyle(
fontFamily: _fontFamily,
fontSize: 16,
fontWeight: FontWeight.w400,
),
bodyMedium: TextStyle(
fontFamily: _fontFamily,
fontSize: 14,
fontWeight: FontWeight.w400,
),
bodySmall: TextStyle(
fontFamily: _fontFamily,
fontSize: 12,
fontWeight: FontWeight.w400,
),
labelSmall: TextStyle(
fontFamily: _fontFamily,
fontSize: 10,
fontWeight: FontWeight.w700,
letterSpacing: 1.5,
),
);
// ── Arabic text styles (Amiri — bundled font) ──
static const TextStyle arabicBody = TextStyle(
fontFamily: 'Amiri',
fontSize: 24,
fontWeight: FontWeight.w400,
height: 2.0,
);
static const TextStyle arabicLarge = TextStyle(
fontFamily: 'Amiri',
fontSize: 28,
fontWeight: FontWeight.w700,
height: 2.2,
);
}

View File

@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'app_colors.dart';
import 'app_text_styles.dart';
/// ThemeData for light and dark modes, Material 3 enabled.
class AppTheme {
AppTheme._();
static ThemeData get light => ThemeData(
useMaterial3: true,
brightness: Brightness.light,
colorScheme: AppColors.lightColorScheme,
scaffoldBackgroundColor: AppColors.backgroundLight,
textTheme: AppTextStyles.textTheme.apply(
bodyColor: AppColors.textPrimaryLight,
displayColor: AppColors.textPrimaryLight,
),
appBarTheme: const AppBarTheme(
backgroundColor: Colors.transparent,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
iconTheme: IconThemeData(color: AppColors.textPrimaryLight),
titleTextStyle: TextStyle(
color: AppColors.textPrimaryLight,
fontSize: 20,
fontWeight: FontWeight.w700,
),
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: AppColors.surfaceLight,
selectedItemColor: AppColors.primary,
unselectedItemColor: AppColors.textSecondaryLight,
type: BottomNavigationBarType.fixed,
elevation: 0,
),
cardTheme: CardThemeData(
color: AppColors.surfaceLight,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(
color: AppColors.cream,
),
),
),
dividerColor: AppColors.cream,
);
static ThemeData get dark => ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorScheme: AppColors.darkColorScheme,
scaffoldBackgroundColor: AppColors.backgroundDark,
textTheme: AppTextStyles.textTheme.apply(
bodyColor: AppColors.textPrimaryDark,
displayColor: AppColors.textPrimaryDark,
),
appBarTheme: const AppBarTheme(
backgroundColor: Colors.transparent,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
iconTheme: IconThemeData(color: AppColors.textPrimaryDark),
titleTextStyle: TextStyle(
color: AppColors.textPrimaryDark,
fontSize: 20,
fontWeight: FontWeight.w700,
),
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: AppColors.surfaceDark,
selectedItemColor: AppColors.primary,
unselectedItemColor: AppColors.textSecondaryDark,
type: BottomNavigationBarType.fixed,
elevation: 0,
),
cardTheme: CardThemeData(
color: AppColors.surfaceDark,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(
color: AppColors.primary.withValues(alpha: 0.1),
),
),
),
dividerColor: AppColors.surfaceDark,
);
}

4
lib/app/theme/theme.dart Normal file
View File

@@ -0,0 +1,4 @@
// Barrel file for theme exports.
export 'app_colors.dart';
export 'app_text_styles.dart';
export 'app_theme.dart';