import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:go_router/go_router.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:lucide_icons/lucide_icons.dart'; import '../../../app/icons/app_icons.dart'; import '../../../app/theme/app_colors.dart'; import '../../../core/widgets/arabic_text.dart'; import '../../../core/widgets/ayat_today_card.dart'; import '../../../core/widgets/notification_bell_button.dart'; import '../../../core/widgets/prayer_time_card.dart'; import '../../../core/widgets/tool_card.dart'; import '../../../data/local/hive_boxes.dart'; import '../../../data/local/models/app_settings.dart'; import '../../../data/local/models/daily_worship_log.dart'; import '../../../data/local/models/quran_bookmark.dart'; import '../../../data/services/notification_service.dart'; import '../data/prayer_times_provider.dart'; class DashboardScreen extends ConsumerStatefulWidget { const DashboardScreen({super.key}); @override ConsumerState createState() => _DashboardScreenState(); } class _DashboardScreenState extends ConsumerState { Timer? _countdownTimer; final ValueNotifier _countdown = ValueNotifier(Duration.zero); final ValueNotifier _nextPrayerName = ValueNotifier(''); final ValueNotifier _nextPrayerTime = ValueNotifier(''); final ScrollController _prayerScrollController = ScrollController(); bool _hasAutoScrolled = false; String? _lastAutoScrollPrayerKey; DaySchedule? _currentSchedule; bool get _isSimpleMode { final box = Hive.box(HiveBoxes.settings); final settings = box.get('default'); return settings?.simpleMode ?? false; } bool get _isAdhanEnabled { final box = Hive.box(HiveBoxes.settings); final settings = box.get('default'); return settings?.adhanEnabled.values.any((v) => v) ?? false; } Future _toggleAdhanFromHero() async { final box = Hive.box(HiveBoxes.settings); final settings = box.get('default') ?? AppSettings(); final nextEnabled = !settings.adhanEnabled.values.any((v) => v); settings.adhanEnabled.updateAll((key, _) => nextEnabled); await settings.save(); if (!nextEnabled) { await NotificationService.instance.cancelAllPending(); } ref.invalidate(prayerTimesProvider); unawaited(ref.read(prayerTimesProvider.future)); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( nextEnabled ? 'Notifikasi adzan diaktifkan' : 'Notifikasi adzan dinonaktifkan', ), ), ); setState(() {}); } @override void dispose() { _countdownTimer?.cancel(); _prayerScrollController.dispose(); _countdown.dispose(); _nextPrayerName.dispose(); _nextPrayerTime.dispose(); super.dispose(); } void _startCountdown(DaySchedule schedule) { if (_currentSchedule == schedule) return; if (_currentSchedule?.date != schedule.date) { _hasAutoScrolled = false; _lastAutoScrollPrayerKey = null; } _currentSchedule = schedule; _countdownTimer?.cancel(); _updateCountdown(schedule); _countdownTimer = Timer.periodic(const Duration(seconds: 1), (_) { _updateCountdown(schedule); }); } void _updateCountdown(DaySchedule schedule) { final now = DateTime.now(); final next = _resolveNextPrayer(schedule, now); if (next == null) { _nextPrayerName.value = ''; _nextPrayerTime.value = ''; _countdown.value = Duration.zero; return; } _nextPrayerName.value = next.name; _nextPrayerTime.value = next.time; final diff = next.target.difference(now); _countdown.value = diff.isNegative ? Duration.zero : diff; } ({String name, String time, DateTime target})? _resolveNextPrayer( DaySchedule schedule, DateTime now) { final scheduleDate = DateTime.tryParse(schedule.date) ?? DateTime(now.year, now.month, now.day); final entries = <({String name, String time, DateTime target})>[]; const orderedPrayers = >[ MapEntry('Subuh', 'subuh'), MapEntry('Dzuhur', 'dzuhur'), MapEntry('Ashar', 'ashar'), MapEntry('Maghrib', 'maghrib'), MapEntry('Isya', 'isya'), ]; for (final prayer in orderedPrayers) { final time = (schedule.times[prayer.value] ?? '').trim(); final parts = _parseHourMinute(time); if (parts == null) continue; final target = DateTime(scheduleDate.year, scheduleDate.month, scheduleDate.day, parts.$1, parts.$2); entries.add((name: prayer.key, time: time, target: target)); } if (entries.isEmpty) return null; for (final entry in entries) { if (!entry.target.isBefore(now)) { return entry; } } final first = entries.first; return ( name: first.name, time: first.time, target: first.target.add(const Duration(days: 1)), ); } (int, int)? _parseHourMinute(String value) { final match = RegExp(r'(\d{1,2}):(\d{2})').firstMatch(value); if (match == null) return null; final hour = int.tryParse(match.group(1) ?? ''); final minute = int.tryParse(match.group(2) ?? ''); if (hour == null || minute == null) return null; if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null; return (hour, minute); } String _formatCountdown(Duration d) { final h = d.inHours.toString().padLeft(2, '0'); final m = (d.inMinutes % 60).toString().padLeft(2, '0'); final s = (d.inSeconds % 60).toString().padLeft(2, '0'); return '$h:$m:$s'; } @override Widget build(BuildContext context) { final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; ref.listen>(prayerTimesProvider, (previous, next) { next.whenData((schedule) { if (schedule != null) { _startCountdown(schedule); } }); }); return Scaffold( body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 8), _buildHeader(context, isDark), const SizedBox(height: 20), Consumer( builder: (context, ref, child) { final prayerTimesAsync = ref.watch(prayerTimesProvider); return prayerTimesAsync.when( data: (schedule) { if (schedule != null) { return _buildHeroCard(context, schedule); } return _buildHeroCardPlaceholder(context); }, loading: () => _buildHeroCardPlaceholder(context), error: (_, __) => _buildHeroCardPlaceholder(context), ); }, ), const SizedBox(height: 24), Consumer( builder: (context, ref, child) { final prayerTimesAsync = ref.watch(prayerTimesProvider); return _buildPrayerTimesSection(context, prayerTimesAsync); }, ), const SizedBox(height: 24), _buildLastReadQuranCard(context, isDark), // Checklist & Weekly Progress (hidden in Simple Mode) if (!_isSimpleMode) ...[ _buildChecklistSummary(context, isDark), const SizedBox(height: 24), _buildWeeklyProgress(context, isDark), ] else ...[ _buildQuickActions(context, isDark), const SizedBox(height: 24), _buildAyatHariIni(context, isDark), ], const SizedBox(height: 24), ], ), ), ), ); } String _quranReadingRoute(QuranBookmark bookmark) { final base = _isSimpleMode ? '/quran' : '/tools/quran'; return '$base/${bookmark.surahId}?startVerse=${bookmark.verseId}'; } Widget _buildLastReadQuranCard(BuildContext context, bool isDark) { return ValueListenableBuilder>( valueListenable: Hive.box(HiveBoxes.bookmarks).listenable(), builder: (context, box, _) { final lastRead = box.values .where((bookmark) => bookmark.isLastRead) .toList() ..sort((a, b) => b.savedAt.compareTo(a.savedAt)); if (lastRead.isEmpty) { return const SizedBox.shrink(); } final bookmark = lastRead.first; final arabic = bookmark.verseText.trim(); final translation = (bookmark.verseTranslation ?? '').trim(); final dateLabel = DateFormat('dd MMM • HH:mm').format(bookmark.savedAt); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'LANJUTKAN TILAWAH', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 1.5, color: AppColors.sage, ), ), const SizedBox(height: 12), InkWell( onTap: () => context.push(_quranReadingRoute(bookmark)), borderRadius: BorderRadius.circular(20), child: Container( width: double.infinity, padding: const EdgeInsets.all(18), decoration: BoxDecoration( color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight, borderRadius: BorderRadius.circular(20), border: Border.all( color: AppColors.primary.withValues(alpha: 0.22), ), boxShadow: [ BoxShadow( color: AppColors.primary .withValues(alpha: isDark ? 0.10 : 0.08), blurRadius: 18, offset: const Offset(0, 8), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 44, height: 44, decoration: BoxDecoration( color: AppColors.primary.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(14), ), child: const Icon( LucideIcons.bookOpen, size: 20, color: AppColors.primary, ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'QS. ${bookmark.surahName}: ${bookmark.verseId}', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w800, ), ), const SizedBox(height: 4), Text( 'Terakhir dibaca $dateLabel', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), ], ), ), Icon( LucideIcons.chevronRight, size: 18, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ], ), if (arabic.isNotEmpty) ...[ const SizedBox(height: 16), ArabicText( arabic, baseFontSize: 21, height: 1.75, maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.right, ), ], if (translation.isNotEmpty) ...[ const SizedBox(height: 10), Text( translation, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 13, height: 1.5, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), ], const SizedBox(height: 16), Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( color: AppColors.primary.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(999), ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( LucideIcons.bookMarked, size: 16, color: AppColors.primary, ), SizedBox(width: 8), Text( 'Lanjutkan Membaca', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primary, ), ), ], ), ), ], ), ), ), const SizedBox(height: 24), ], ); }, ); } Widget _buildHeader(BuildContext context, bool isDark) { return Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all(color: AppColors.primary, width: 2), color: AppColors.primary.withValues(alpha: 0.2), ), child: const Icon(LucideIcons.user, size: 20, color: AppColors.primary), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Selamat datang,', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), Text( "Assalamu'alaikum", style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w700, ), ), ], ), ), Row( children: [ NotificationBellButton( iconColor: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), IconButton( onPressed: () => context.push('/settings'), icon: AppIcon( glyph: AppIcons.settings, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), ], ), ], ); } Widget _buildHeroCard(BuildContext context, DaySchedule schedule) { final initialNext = _resolveNextPrayer(schedule, DateTime.now()); final fallbackPrayerName = initialNext?.name ?? 'Isya'; final fallbackTime = initialNext?.time ?? '--:--'; final isAdhanEnabled = _isAdhanEnabled; return Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AppColors.primary, borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: AppColors.primary.withValues(alpha: 0.3), blurRadius: 20, offset: const Offset(0, 8), ), ], ), child: Stack( clipBehavior: Clip.none, children: [ Positioned( top: -6, right: -4, child: IgnorePointer( child: Opacity( opacity: 0.22, child: Image.asset( 'assets/images/blob.png', width: 140, height: 140, fit: BoxFit.contain, ), ), ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(LucideIcons.clock, size: 16, color: AppColors.onPrimary.withValues(alpha: 0.8)), const SizedBox(width: 6), Text( 'SHOLAT BERIKUTNYA', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 1.5, color: AppColors.onPrimary.withValues(alpha: 0.8), ), ), ], ), const SizedBox(height: 8), AnimatedBuilder( animation: Listenable.merge([_nextPrayerName, _nextPrayerTime]), builder: (context, _) { final name = _nextPrayerName.value.isNotEmpty ? _nextPrayerName.value : fallbackPrayerName; final time = _nextPrayerTime.value.isNotEmpty ? _nextPrayerTime.value : fallbackTime; return Text( '$name — $time', style: const TextStyle( fontSize: 28, fontWeight: FontWeight.w800, color: AppColors.onPrimary, ), ); }, ), const SizedBox(height: 4), ValueListenableBuilder( valueListenable: _countdown, builder: (context, countdown, _) { return Text( 'Hitung mundur: ${_formatCountdown(countdown)}', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w400, color: AppColors.onPrimary.withValues(alpha: 0.8), ), ); }, ), const SizedBox(height: 4), // City name Row( mainAxisSize: MainAxisSize.min, children: [ AppIcon( glyph: AppIcons.location, size: 14, color: AppColors.onPrimary.withValues(alpha: 0.7), ), const SizedBox(width: 6), Text( schedule.cityName, style: TextStyle( fontSize: 13, color: AppColors.onPrimary.withValues(alpha: 0.7), ), ), ], ), const SizedBox(height: 16), Row( children: [ Expanded( child: GestureDetector( onTap: () => context.push('/tools/qibla'), child: Container( padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: AppColors.onPrimary, borderRadius: BorderRadius.circular(50), ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ AppIcon( glyph: AppIcons.qibla, size: 18, color: AppColors.primary, ), SizedBox(width: 8), Text( 'Arah Kiblat', style: TextStyle( color: AppColors.primary, fontWeight: FontWeight.w600, fontSize: 14, ), ), ], ), ), ), ), const SizedBox(width: 12), GestureDetector( onTap: _toggleAdhanFromHero, child: Container( width: 48, height: 48, decoration: BoxDecoration( color: isAdhanEnabled ? Colors.white.withValues(alpha: 0.2) : Colors.white.withValues(alpha: 0.12), shape: BoxShape.circle, border: Border.all( color: Colors.white.withValues( alpha: isAdhanEnabled ? 0.0 : 0.35, ), ), ), child: Icon( isAdhanEnabled ? LucideIcons.volume2 : LucideIcons.volumeX, color: AppColors.onPrimary, size: 22, ), ), ), ], ), ], ), ], ), ); } Widget _buildHeroCardPlaceholder(BuildContext context) { return Container( width: double.infinity, height: 180, padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AppColors.primary, borderRadius: BorderRadius.circular(24), ), child: const Center( child: CircularProgressIndicator(color: AppColors.onPrimary), ), ); } Widget _buildPrayerTimesSection( BuildContext context, AsyncValue prayerTimesAsync) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( prayerTimesAsync.value?.isTomorrow == true ? 'Jadwal Sholat Besok' : 'Jadwal Sholat Hari Ini', maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context) .textTheme .titleMedium ?.copyWith(fontWeight: FontWeight.w700), ), ), const SizedBox(width: 12), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), decoration: BoxDecoration( color: AppColors.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(50), ), child: Text( prayerTimesAsync.value?.isTomorrow == true ? 'BESOK' : 'HARI INI', style: const TextStyle( color: AppColors.primary, fontSize: 10, fontWeight: FontWeight.w700, letterSpacing: 1.5, ), ), ), ], ), const SizedBox(height: 12), SizedBox( height: 110, child: prayerTimesAsync.when( data: (schedule) { if (schedule == null) return const SizedBox(); final activePrayerName = _resolveNextPrayer(schedule, DateTime.now())?.name; final activePrayerKey = activePrayerName == null ? null : '${schedule.date}:$activePrayerName'; final prayers = schedule.prayerList .where( (p) => ['Subuh', 'Dzuhur', 'Ashar', 'Maghrib', 'Isya'] .contains(p.name), ) .toList(); return ListView.separated( controller: _prayerScrollController, scrollDirection: Axis.horizontal, itemCount: prayers.length, separatorBuilder: (_, __) => const SizedBox(width: 12), itemBuilder: (context, i) { final p = prayers[i]; final icon = _prayerIcon(p.name); final isActive = p.name == activePrayerName; // Auto-scroll to active prayer on first build if (isActive && i > 0 && (!_hasAutoScrolled || _lastAutoScrollPrayerKey != activePrayerKey)) { _hasAutoScrolled = true; _lastAutoScrollPrayerKey = activePrayerKey; WidgetsBinding.instance.addPostFrameCallback((_) { if (_prayerScrollController.hasClients) { final targetOffset = i * 124.0; // 112 width + 12 gap _prayerScrollController.animateTo( targetOffset.clamp(0, _prayerScrollController.position.maxScrollExtent), duration: const Duration(milliseconds: 400), curve: Curves.easeOut, ); } }); } return PrayerTimeCard( prayerName: p.name, time: p.time, icon: icon, isActive: isActive, ); }, ); }, loading: () => const Center(child: CircularProgressIndicator()), error: (_, __) => const Center(child: Text('Gagal memuat jadwal')), ), ), ], ); } IconData _prayerIcon(String name) { switch (name) { case 'Subuh': return LucideIcons.sunrise; case 'Dzuhur': return LucideIcons.sun; case 'Ashar': return LucideIcons.cloudSun; case 'Maghrib': return LucideIcons.sunset; case 'Isya': return LucideIcons.moon; default: return LucideIcons.clock; } } Widget _buildChecklistSummary(BuildContext context, bool isDark) { final todayKey = DateFormat('yyyy-MM-dd').format(DateTime.now()); final box = Hive.box(HiveBoxes.worshipLogs); final log = box.get(todayKey); final points = log?.totalPoints ?? 0; // We can assume a max "excellent" day is around 150 points for the progress ring scale final percent = (points / 150).clamp(0.0, 1.0); // Prepare dynamic preview lines int fardhuCompleted = 0; if (log != null) { fardhuCompleted = log.shalatLogs.values.where((l) => l.completed).length; } String amalanText = 'Belum ada data'; if (log != null) { final List aList = []; if (log.tilawahLog?.isCompleted == true) aList.add('Tilawah'); if (log.puasaLog?.completed == true) aList.add('Puasa'); if (log.dzikirLog?.pagi == true) aList.add('Dzikir'); if (aList.isNotEmpty) { amalanText = aList.join(', '); } } return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight, borderRadius: BorderRadius.circular(16), border: Border.all( color: isDark ? AppColors.primary.withValues(alpha: 0.1) : AppColors.cream, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Poin Ibadah Hari Ini', style: Theme.of(context) .textTheme .titleMedium ?.copyWith(fontWeight: FontWeight.w700)), const SizedBox(height: 4), Text( 'Kumpulkan poin dengan konsisten!', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), ], ), ), SizedBox( width: 48, height: 48, child: Stack( alignment: Alignment.center, children: [ CircularProgressIndicator( value: percent, strokeWidth: 4, backgroundColor: AppColors.primary.withValues(alpha: 0.15), valueColor: const AlwaysStoppedAnimation( AppColors.primary), ), Text( '$points', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w800, color: AppColors.primary, ), ), ], ), ), ], ), const SizedBox(height: 16), _checklistPreviewItem(context, isDark, 'Sholat Fardhu', '$fardhuCompleted dari 5 selesai', fardhuCompleted == 5), const SizedBox(height: 8), _checklistPreviewItem( context, isDark, 'Amalan Selesai', amalanText, points > 50), const SizedBox(height: 16), GestureDetector( onTap: () => context.go('/checklist'), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: AppColors.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(50), ), child: const Center( child: Text( 'Lihat Semua Checklist', style: TextStyle( color: AppColors.primary, fontWeight: FontWeight.w600, fontSize: 14, ), ), ), ), ), ], ), ); } Widget _checklistPreviewItem(BuildContext context, bool isDark, String title, String subtitle, bool completed) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: isDark ? AppColors.primary.withValues(alpha: 0.05) : AppColors.backgroundLight, borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Icon( completed ? LucideIcons.checkCircle2 : LucideIcons.circle, color: AppColors.primary, size: 22, ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: Theme.of(context) .textTheme .bodyMedium ?.copyWith(fontWeight: FontWeight.w600)), Text(subtitle, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, )), ], ), ], ), ); } Widget _buildWeeklyProgress(BuildContext context, bool isDark) { final box = Hive.box(HiveBoxes.worshipLogs); final now = DateTime.now(); // Reverse so today is on the far right (index 6) final last7Days = List.generate(7, (i) => now.subtract(Duration(days: 6 - i))); final daysLabels = ['Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab', 'Min']; final weekPoints = []; for (final d in last7Days) { final k = DateFormat('yyyy-MM-dd').format(d); final l = box.get(k); weekPoints.add(l?.totalPoints ?? 0); } // Find the max points acquired this week to scale the bars, with a minimum floor of 50 final maxPts = weekPoints.reduce((a, b) => a > b ? a : b).clamp(50, 300); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Progres Poin Mingguan', style: Theme.of(context) .textTheme .titleMedium ?.copyWith(fontWeight: FontWeight.w700)), const SizedBox(height: 12), Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight, borderRadius: BorderRadius.circular(16), border: Border.all( color: isDark ? AppColors.primary.withValues(alpha: 0.1) : AppColors.cream, ), ), child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: List.generate(7, (i) { final val = weekPoints[i]; final ratio = (val / maxPts).clamp(0.1, 1.0); final labelColor = i == 6 ? AppColors.primary : (isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight); return Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox( height: 96, child: Align( alignment: Alignment.bottomCenter, child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( '$val', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w700, color: labelColor, ), ), const SizedBox(height: 6), Container( width: 24, height: 80 * ratio, decoration: BoxDecoration( color: val > 0 ? AppColors.primary.withValues( alpha: 0.2 + ratio * 0.8, ) : AppColors.primary .withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), ), ], ), ), ), const SizedBox(height: 8), Text( daysLabels[ last7Days[i].weekday - 1], // Correct localized day style: TextStyle( fontSize: 10, fontWeight: i == 6 ? FontWeight.w800 : FontWeight.w600, color: labelColor, ), ), ], ), ), ); }), ), ), ], ); } Widget _buildQuickActions(BuildContext context, bool isDark) { final isSimpleMode = _isSimpleMode; final cards = [ if (!isSimpleMode) ToolCard( icon: AppIcons.quran, title: "Al-Qur'an\nTerjemahan", color: const Color(0xFF00B894), isDark: isDark, onTap: () => context.push('/tools/quran'), ), ToolCard( icon: AppIcons.murattal, title: "Qur'an\nMurattal", color: const Color(0xFF7B61FF), isDark: isDark, onTap: () { if (isSimpleMode) { context.go('/quran/1/murattal'); } else { context.push('/tools/quran/1/murattal'); } }, ), if (!isSimpleMode) ToolCard( icon: AppIcons.dzikir, title: 'Dzikir\nHarian', color: AppColors.primary, isDark: isDark, onTap: () => context.push('/tools/dzikir'), ), ToolCard( icon: AppIcons.doa, title: 'Kumpulan\nDoa', color: const Color(0xFFE17055), isDark: isDark, onTap: () { if (isSimpleMode) { context.push('/doa'); } else { context.push('/tools/doa'); } }, ), ToolCard( icon: AppIcons.hadits, title: "Hadits\nArba'in", color: const Color(0xFF6C5CE7), isDark: isDark, onTap: () { if (isSimpleMode) { context.push('/hadits'); } else { context.push('/tools/hadits'); } }, ), ToolCard( icon: AppIcons.quranEnrichment, title: "Pendalaman\nAl-Qur'an", color: const Color(0xFF00CEC9), isDark: isDark, onTap: () { if (isSimpleMode) { context.push('/quran/enrichment'); } else { context.push('/tools/quran/enrichment'); } }, ), ]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'AKSES CEPAT', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 1.5, color: AppColors.sage, ), ), const SizedBox(height: 12), _buildQuickActionsGrid(cards), ], ); } Widget _buildQuickActionsGrid(List cards) { const spacing = 12.0; return LayoutBuilder( builder: (context, constraints) { final cardWidth = (constraints.maxWidth - spacing) / 2; return Wrap( spacing: spacing, runSpacing: spacing, children: [ for (final card in cards) SizedBox(width: cardWidth, child: card), ], ); }, ); } Widget _buildAyatHariIni(BuildContext context, bool isDark) { return const AyatTodayCard( headerText: 'AYAT HARI INI', headerStyle: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 1.5, color: AppColors.sage, ), ); } }