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/theme/app_colors.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/services/equran_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 ScrollController _prayerScrollController = ScrollController(); bool _hasAutoScrolled = false; DaySchedule? _currentSchedule; bool get _isSimpleMode { final box = Hive.box(HiveBoxes.settings); final settings = box.get('default'); return settings?.simpleMode ?? false; } @override void dispose() { _countdownTimer?.cancel(); _prayerScrollController.dispose(); _countdown.dispose(); _nextPrayerName.dispose(); super.dispose(); } void _startCountdown(DaySchedule schedule) { if (_currentSchedule == schedule) return; _currentSchedule = 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)); } _nextPrayerName.value = next.name; final diff = target.difference(now); _countdown.value = diff.isNegative ? Duration.zero : diff; } } } 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); }, ), // Checklist & Weekly Progress (hidden in Simple Mode) if (!_isSimpleMode) ...[ const SizedBox(height: 24), _buildChecklistSummary(context, isDark), const SizedBox(height: 24), _buildWeeklyProgress(context, isDark), ] else ...[ const SizedBox(height: 24), _buildQuickActions(context, isDark), const SizedBox(height: 24), _buildAyatHariIni(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(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: [ IconButton( onPressed: () {}, icon: Icon( LucideIcons.bell, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), IconButton( onPressed: () => context.push('/settings'), icon: Icon( LucideIcons.settings, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), ], ), ], ); } Widget _buildHeroCard(BuildContext context, DaySchedule schedule) { final next = schedule.nextPrayer; 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(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), ValueListenableBuilder( valueListenable: _nextPrayerName, builder: (context, prayerName, _) { final name = prayerName.isNotEmpty ? prayerName : (next?.name ?? 'Isya'); 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 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(LucideIcons.compass, 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( LucideIcons.volume2, 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: 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 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 && !_hasAutoScrolled) { _hasAutoScrolled = true; 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 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); 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), ), ), ], ), ), ); }), ), ), ], ); } Widget _buildQuickActions(BuildContext context, bool isDark) { 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), Row( children: [ Expanded( child: ToolCard( icon: LucideIcons.bookOpen, title: 'Al-Quran\nTerjemahan', color: const Color(0xFF00B894), isDark: isDark, onTap: () { final isSimple = Hive.box(HiveBoxes.settings).get('default')?.simpleMode ?? false; if (isSimple) { context.go('/quran'); } else { context.push('/tools/quran'); } }, ), ), const SizedBox(width: 12), Expanded( child: ToolCard( icon: LucideIcons.headphones, title: 'Quran\nMurattal', color: const Color(0xFF7B61FF), isDark: isDark, onTap: () { final isSimple = Hive.box(HiveBoxes.settings).get('default')?.simpleMode ?? false; if (isSimple) { context.go('/quran/1/murattal'); } else { context.push('/tools/quran/1/murattal'); } }, ), ), ], ), const SizedBox(height: 12), Row( children: [ Expanded( child: ToolCard( icon: LucideIcons.compass, title: 'Arah\nKiblat', color: const Color(0xFF0984E3), isDark: isDark, onTap: () { final isSimple = Hive.box(HiveBoxes.settings).get('default')?.simpleMode ?? false; if (isSimple) { context.push('/qibla'); } else { context.push('/tools/qibla'); } }, ), ), const SizedBox(width: 12), Expanded( child: ToolCard( icon: LucideIcons.sparkles, title: 'Tasbih\nDigital', color: AppColors.primary, isDark: isDark, onTap: () { final isSimple = Hive.box(HiveBoxes.settings).get('default')?.simpleMode ?? false; if (isSimple) { context.go('/dzikir'); } else { context.push('/tools/dzikir'); } }, ), ), ], ), ], ); } Widget _buildAyatHariIni(BuildContext context, bool isDark) { return FutureBuilder?>( future: EQuranService.instance.getDailyAyat(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: isDark ? AppColors.primary.withValues(alpha: 0.08) : const Color(0xFFF5F9F0), borderRadius: BorderRadius.circular(16), ), child: const Center(child: CircularProgressIndicator()), ); } if (!snapshot.hasData || snapshot.data == null) { return const SizedBox.shrink(); } final data = snapshot.data!; return Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: isDark ? AppColors.primary.withValues(alpha: 0.08) : const Color(0xFFF5F9F0), borderRadius: BorderRadius.circular(16), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'AYAT HARI INI', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 1.5, color: AppColors.sage, ), ), Icon(LucideIcons.quote, size: 20, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight), ], ), const SizedBox(height: 16), Align( alignment: Alignment.centerRight, child: Text( data['teksArab'] ?? '', style: const TextStyle( fontFamily: 'Amiri', fontSize: 24, height: 1.8, ), textAlign: TextAlign.right, ), ), const SizedBox(height: 16), Text( '"${data['teksIndonesia'] ?? ''}"', style: TextStyle( fontSize: 14, fontStyle: FontStyle.italic, height: 1.5, color: isDark ? Colors.white : Colors.black87, ), ), const SizedBox(height: 12), Text( 'QS. ${data['surahName']}: ${data['nomorAyat']}', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.primary, ), ), ], ), ); }, ); } }