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 '../../../app/theme/app_colors.dart'; import '../../../core/widgets/prayer_time_card.dart'; import '../../../data/local/hive_boxes.dart'; import '../../../data/local/models/daily_worship_log.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; Duration _countdown = Duration.zero; String _nextPrayerName = ''; final ScrollController _prayerScrollController = ScrollController(); @override void dispose() { _countdownTimer?.cancel(); _prayerScrollController.dispose(); super.dispose(); } void _startCountdown(DaySchedule schedule) { _countdownTimer?.cancel(); _updateCountdown(schedule); _countdownTimer = Timer.periodic(const Duration(seconds: 1), (_) { _updateCountdown(schedule); }); } void _updateCountdown(DaySchedule schedule) { final next = schedule.nextPrayer; if (next != null && next.time != '-') { final parts = next.time.split(':'); if (parts.length == 2) { final now = DateTime.now(); var target = DateTime(now.year, now.month, now.day, int.parse(parts[0]), int.parse(parts[1])); if (target.isBefore(now)) { target = target.add(const Duration(days: 1)); } setState(() { _nextPrayerName = next.name; _countdown = target.difference(now); if (_countdown.isNegative) _countdown = Duration.zero; }); } } } 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; final prayerTimesAsync = ref.watch(prayerTimesProvider); 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), prayerTimesAsync.when( data: (schedule) { if (schedule != null) { _startCountdown(schedule); return _buildHeroCard(context, schedule); } return _buildHeroCardPlaceholder(context); }, loading: () => _buildHeroCardPlaceholder(context), error: (_, __) => _buildHeroCardPlaceholder(context), ), const SizedBox(height: 24), _buildPrayerTimesSection(context, prayerTimesAsync), const SizedBox(height: 24), _buildChecklistSummary(context, isDark), const SizedBox(height: 24), _buildWeeklyProgress(context, isDark), 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(Icons.person, 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: [ IconButton( onPressed: () {}, icon: Icon( Icons.notifications_outlined, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), IconButton( onPressed: () => context.push('/settings'), icon: Icon( Icons.settings_outlined, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), ], ), ], ); } Widget _buildHeroCard(BuildContext context, DaySchedule schedule) { final next = schedule.nextPrayer; final name = _nextPrayerName.isNotEmpty ? _nextPrayerName : (next?.name ?? 'Isya'); final time = next?.time ?? '--:--'; 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( children: [ Positioned( top: -20, right: -20, child: Container( width: 120, height: 120, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.white.withValues(alpha: 0.15), ), ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.schedule, 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), Text( '$name — $time', style: const TextStyle( fontSize: 28, fontWeight: FontWeight.w800, color: AppColors.onPrimary, ), ), const SizedBox(height: 4), 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 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: [ Icon(Icons.explore, size: 18, color: Colors.white), SizedBox(width: 8), Text( 'Arah Kiblat', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w600, fontSize: 14, ), ), ], ), ), ), ), const SizedBox(width: 12), Container( width: 48, height: 48, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), shape: BoxShape.circle, ), child: const Icon( Icons.volume_up, 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( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( prayerTimesAsync.value?.isTomorrow == true ? 'Jadwal Sholat Besok' : 'Jadwal Sholat Hari Ini', style: Theme.of(context) .textTheme .titleMedium ?.copyWith(fontWeight: FontWeight.w700)), 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: 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 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); // Auto-scroll to active prayer on first build if (p.isActive && i > 0) { 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: p.isActive, ); }, ); }, loading: () => const Center(child: CircularProgressIndicator()), error: (_, __) => const Center(child: Text('Gagal memuat jadwal')), ), ), ], ); } IconData _prayerIcon(String name) { switch (name) { case 'Subuh': return Icons.wb_twilight; case 'Dzuhur': return Icons.wb_sunny; case 'Ashar': return Icons.filter_drama; case 'Maghrib': return Icons.wb_twilight; case 'Isya': return Icons.dark_mode; default: return Icons.schedule; } } 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) { 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 ? Icons.check_circle : Icons.radio_button_unchecked, 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); return Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox( height: 80, child: Align( alignment: Alignment.bottomCenter, child: 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: i == 6 ? AppColors.primary : (isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight), ), ), ], ), ), ); }), ), ), ], ); } }