import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:lucide_icons/lucide_icons.dart'; import '../../../app/theme/app_colors.dart'; import '../../../core/providers/theme_provider.dart'; import '../../../core/widgets/ios_toggle.dart'; import '../../../data/local/hive_boxes.dart'; import '../../../data/local/models/app_settings.dart'; import '../../../data/services/location_service.dart'; import '../../../data/services/myquran_sholat_service.dart'; import '../../../data/services/notification_service.dart'; import '../../dashboard/data/prayer_times_provider.dart'; import 'package:intl/intl.dart'; import '../../../data/local/models/daily_worship_log.dart'; class SettingsScreen extends ConsumerStatefulWidget { const SettingsScreen({super.key}); @override ConsumerState createState() => _SettingsScreenState(); } class _SettingsScreenState extends ConsumerState { late AppSettings _settings; @override void initState() { super.initState(); final box = Hive.box(HiveBoxes.settings); _settings = box.get('default') ?? AppSettings(); _enforceTilawahAutoSyncIfNeeded(); } void _saveSettings() { _settings.save(); setState(() {}); } void _syncTodayTilawahAutoSync(bool value) { final todayKey = DateFormat('yyyy-MM-dd').format(DateTime.now()); final logBox = Hive.box(HiveBoxes.worshipLogs); final log = logBox.get(todayKey); if (log != null && log.tilawahLog != null) { log.tilawahLog!.autoSync = value; log.save(); } } void _enforceTilawahAutoSyncIfNeeded() { if (_settings.simpleMode || _settings.tilawahAutoSync) return; _settings.tilawahAutoSync = true; if (_settings.isInBox) { _settings.save(); } else { Hive.box(HiveBoxes.settings).put('default', _settings); } _syncTodayTilawahAutoSync(true); } bool get _isDarkMode => _settings.themeModeIndex != 1; bool get _prayerAlarmEnabled => _settings.adhanEnabled.values.any((v) => v); String get _displayCityName { final stored = _settings.lastCityName ?? 'Jakarta'; if (stored.contains('|')) { return stored.split('|').first; } return stored; } void _toggleDarkMode(bool value) { _settings.themeModeIndex = value ? 2 : 1; _saveSettings(); ref.read(themeProvider.notifier).state = value ? ThemeMode.dark : ThemeMode.light; } void _toggleNotifications(bool value) { _settings.adhanEnabled.updateAll((key, _) => value); _saveSettings(); if (!value) { unawaited(NotificationService.instance.cancelAllPending()); } ref.invalidate(prayerTimesProvider); unawaited(ref.read(prayerTimesProvider.future)); } void _toggleGlobalAlerts(bool value) { _settings.alertsEnabled = value; _saveSettings(); unawaited(NotificationService.instance.syncHabitNotifications( settings: _settings, )); ref.invalidate(prayerTimesProvider); unawaited(ref.read(prayerTimesProvider.future)); } void _toggleInbox(bool value) { _settings.inboxEnabled = value; _saveSettings(); } void _resyncPrayerNotifications() { ref.invalidate(prayerTimesProvider); unawaited(ref.read(prayerTimesProvider.future)); } String _normalizeDzikirDisplayMode(String raw) { if (raw == 'slide') return 'focus'; return raw; } String _normalizeDzikirCounterButtonPosition(String raw) { if (raw == 'fullwidth') return 'bottomPill'; if (raw == 'circle') return 'fabCircle'; return raw; } Future _showQuietHoursDialog(BuildContext context) async { final startController = TextEditingController(text: _settings.quietHoursStart); final endController = TextEditingController(text: _settings.quietHoursEnd); await showDialog( context: context, builder: (ctx) { return AlertDialog( title: const Text('Jam Tenang'), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: startController, keyboardType: TextInputType.datetime, decoration: const InputDecoration( labelText: 'Mulai (HH:mm)', ), ), const SizedBox(height: 10), TextField( controller: endController, keyboardType: TextInputType.datetime, decoration: const InputDecoration( labelText: 'Selesai (HH:mm)', ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Batal'), ), FilledButton( onPressed: () { final start = startController.text.trim(); final end = endController.text.trim(); final valid = RegExp(r'^\d{1,2}:\d{2}$'); if (!valid.hasMatch(start) || !valid.hasMatch(end)) { return; } _settings.quietHoursStart = start; _settings.quietHoursEnd = end; _saveSettings(); Navigator.pop(ctx); }, child: const Text('Simpan'), ), ], ); }, ); } Future _showPushCapDialog(BuildContext context) async { final controller = TextEditingController( text: _settings.maxNonPrayerPushPerDay.toString(), ); await showDialog( context: context, builder: (ctx) { return AlertDialog( title: const Text('Batas Push Non-Sholat'), content: TextField( controller: controller, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: 'Maksimal per hari', ), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Batal'), ), FilledButton( onPressed: () { final value = int.tryParse(controller.text.trim()); if (value == null || value < 0 || value > 20) return; _settings.maxNonPrayerPushPerDay = value; _saveSettings(); Navigator.pop(ctx); }, child: const Text('Simpan'), ), ], ); }, ); } Future _showChecklistReminderTimeDialog(BuildContext context) async { final controller = TextEditingController( text: _settings.checklistReminderTime ?? '09:00', ); await showDialog( context: context, builder: (ctx) { return AlertDialog( title: const Text('Waktu Pengingat Checklist'), content: TextField( controller: controller, keyboardType: TextInputType.datetime, decoration: const InputDecoration( labelText: 'HH:mm', ), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Batal'), ), FilledButton( onPressed: () { final raw = controller.text.trim(); if (!RegExp(r'^\d{1,2}:\d{2}$').hasMatch(raw)) return; _settings.checklistReminderTime = raw; _saveSettings(); unawaited(NotificationService.instance.syncHabitNotifications( settings: _settings, )); Navigator.pop(ctx); }, child: const Text('Simpan'), ), ], ); }, ); } Future _showShalatReportDelayDialog(BuildContext context) async { final controller = TextEditingController( text: _settings.shalatReportReminderDelayMinutes.toString(), ); await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Jeda Pengingat Lapor Shalat'), content: TextField( controller: controller, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: 'Menit setelah waktu shalat', ), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Batal'), ), FilledButton( onPressed: () { final value = int.tryParse(controller.text.trim()); if (value == null || value < 5 || value > 240) return; _settings.shalatReportReminderDelayMinutes = value; _saveSettings(); _resyncPrayerNotifications(); Navigator.pop(ctx); }, child: const Text('Simpan'), ), ], ), ); } Future _showShalatReportRepeatDialog(BuildContext context) async { final repeatController = TextEditingController( text: _settings.shalatReportReminderRepeatCount.toString(), ); final intervalController = TextEditingController( text: _settings.shalatReportReminderRepeatIntervalMinutes.toString(), ); await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Pengulangan Pengingat Lapor'), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: repeatController, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: 'Jumlah ulang (0-5)', ), ), const SizedBox(height: 10), TextField( controller: intervalController, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: 'Jeda antar ulang (menit)', ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Batal'), ), FilledButton( onPressed: () { final repeats = int.tryParse(repeatController.text.trim()); final interval = int.tryParse(intervalController.text.trim()); if (repeats == null || repeats < 0 || repeats > 5) return; if (interval == null || interval < 5 || interval > 180) return; _settings.shalatReportReminderRepeatCount = repeats; _settings.shalatReportReminderRepeatIntervalMinutes = interval; _saveSettings(); _resyncPrayerNotifications(); Navigator.pop(ctx); }, child: const Text('Simpan'), ), ], ), ); } @override @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return Scaffold( appBar: AppBar( title: const Text('Pengaturan'), ), body: SafeArea( top: false, bottom: true, child: ListView( padding: const EdgeInsets.all(16), children: [ // ── Top-level items ── Container( padding: const EdgeInsets.all(16), 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( children: [ Container( width: 56, height: 56, decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( colors: [ AppColors.primary, AppColors.primary.withValues(alpha: 0.6), ], ), ), child: Center( child: Text( _settings.userName.isNotEmpty ? _settings.userName[0].toUpperCase() : 'U', style: const TextStyle( fontSize: 24, fontWeight: FontWeight.w700, color: Colors.white, ), ), ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _settings.userName, style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w700, ), ), if (_settings.userEmail.isNotEmpty) Text( _settings.userEmail, style: TextStyle( fontSize: 13, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), ], ), ), IconButton( onPressed: () => _showEditProfileDialog(context), icon: Icon(LucideIcons.pencil, size: 20, color: AppColors.primary), ), ], ), ), const SizedBox(height: 24), _settingRow( isDark, icon: LucideIcons.layoutDashboard, iconColor: const Color(0xFF0984E3), title: 'Mode Aplikasi', subtitle: _settings.simpleMode ? 'Simpel — Jadwal & Al-Quran' : 'Lengkap — Dengan Checklist & Poin', trailing: IosToggle( value: !_settings.simpleMode, onChanged: (v) { _settings.simpleMode = !v; if (v) { _settings.tilawahAutoSync = true; } _saveSettings(); _syncTodayTilawahAutoSync(_settings.tilawahAutoSync); }, ), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.moon, iconColor: const Color(0xFF6C5CE7), title: 'Mode Gelap', trailing: IosToggle( value: _isDarkMode, onChanged: _toggleDarkMode, ), ), const SizedBox(height: 24), _sectionLabel('GRUP PENGATURAN'), const SizedBox(height: 12), _buildGroupEntry( isDark, icon: LucideIcons.clock3, iconColor: const Color(0xFF0984E3), title: 'Shalat & Waktu', subtitle: 'Kota, alarm shalat, iqamah, metode', onTap: () => _openSettingsGroup( context, title: 'Shalat & Waktu', childrenBuilder: _buildShalatGroupItems, ), ), const SizedBox(height: 10), _buildGroupEntry( isDark, icon: LucideIcons.bell, iconColor: const Color(0xFFE17055), title: 'Notifikasi', subtitle: 'Reminder, quiet hours, inbox, ringkasan', onTap: () => _openSettingsGroup( context, title: 'Notifikasi', childrenBuilder: _buildNotificationGroupItems, ), ), const SizedBox(height: 10), _buildGroupEntry( isDark, icon: LucideIcons.bookOpen, iconColor: Colors.amber, title: 'Tilawah', subtitle: 'Target, auto-sync, auto lanjut surah', onTap: () => _openSettingsGroup( context, title: 'Tilawah', childrenBuilder: _buildTilawahGroupItems, ), ), const SizedBox(height: 10), _buildGroupEntry( isDark, icon: LucideIcons.sparkles, iconColor: Colors.indigo, title: 'Dzikir', subtitle: 'Mode tampilan, tombol, haptic', onTap: () => _openSettingsGroup( context, title: 'Dzikir', childrenBuilder: _buildDzikirGroupItems, ), ), const SizedBox(height: 10), _buildGroupEntry( isDark, icon: LucideIcons.palette, iconColor: const Color(0xFF6C5CE7), title: 'Tampilan', subtitle: 'Mode gelap dan ukuran font arab', onTap: () => _openSettingsGroup( context, title: 'Tampilan', childrenBuilder: _buildDisplayGroupItems, ), ), const SizedBox(height: 10), _buildGroupEntry( isDark, icon: LucideIcons.shieldAlert, iconColor: Colors.red, title: 'Akun & Data', subtitle: 'Profil, versi aplikasi, reset data', onTap: () => _openSettingsGroup( context, title: 'Akun & Data', childrenBuilder: _buildAccountDataGroupItems, ), ), const SizedBox(height: 32), ], ), ), ); } void _openSettingsGroup( BuildContext context, { required String title, required List Function(bool isDark) childrenBuilder, }) { Navigator.of(context).push( MaterialPageRoute( builder: (ctx) { final isDark = Theme.of(ctx).brightness == Brightness.dark; return Scaffold( appBar: AppBar(title: Text(title)), body: ValueListenableBuilder>( valueListenable: Hive.box(HiveBoxes.settings) .listenable(keys: ['default']), builder: (_, settingsBox, __) { _settings = settingsBox.get('default') ?? AppSettings(); return SafeArea( top: false, child: ListView( padding: const EdgeInsets.all(16), children: [ ...childrenBuilder(isDark), const SizedBox(height: 24), Text( 'Tips: Pengaturan difokuskan per kategori agar lebih mudah dicari.', style: TextStyle( fontSize: 12, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), ], ), ); }, ), ); }, ), ); } List _buildShalatGroupItems(bool isDark) { return [ _settingRow( isDark, icon: LucideIcons.mapPin, iconColor: const Color(0xFF00B894), title: 'Kota', subtitle: _displayCityName, trailing: const Icon(LucideIcons.chevronRight, size: 20), onTap: () => _showLocationDialog(context), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.bell, iconColor: const Color(0xFFE17055), title: 'Alarm Sholat', trailing: IosToggle( value: _prayerAlarmEnabled, onChanged: _toggleNotifications, ), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.timer, iconColor: const Color(0xFF0984E3), title: 'Jeda Iqamah', trailing: const Icon(LucideIcons.chevronRight, size: 20), onTap: () => _showIqamahDialog(context), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.compass, iconColor: AppColors.sage, title: 'Metode Perhitungan', subtitle: 'Kemenag (myQuran)', trailing: const Icon(LucideIcons.chevronRight, size: 20), onTap: () => _showMethodDialog(context), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.building, iconColor: Colors.teal, title: 'Tingkat Sholat Rawatib', subtitle: _settings.rawatibLevel == 0 ? 'Mati' : (_settings.rawatibLevel == 1 ? 'Muakkad Saja' : 'Lengkap'), trailing: const Icon(LucideIcons.chevronRight, size: 20), onTap: () => _showRawatibDialog(context), ), ]; } List _buildNotificationGroupItems(bool isDark) { return [ _settingRow( isDark, icon: LucideIcons.alertCircle, iconColor: const Color(0xFF00B894), title: 'Peringatan Non-Sholat', trailing: IosToggle( value: _settings.alertsEnabled, onChanged: _toggleGlobalAlerts, ), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.inbox, iconColor: const Color(0xFF6C5CE7), title: 'Kotak Masuk Pesan', trailing: IosToggle( value: _settings.inboxEnabled, onChanged: _toggleInbox, ), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.checkSquare, iconColor: const Color(0xFF2D98DA), title: 'Pengingat Checklist Harian', subtitle: _settings.checklistReminderTime, trailing: IosToggle( value: _settings.dailyChecklistReminderEnabled, onChanged: (v) { _settings.dailyChecklistReminderEnabled = v; _saveSettings(); unawaited(NotificationService.instance.syncHabitNotifications( settings: _settings, )); }, ), onTap: () => _showChecklistReminderTimeDialog(context), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.siren, iconColor: const Color(0xFFC0392B), title: 'Pengingat Lapor Shalat', subtitle: _settings.shalatReportReminderEnabled ? 'Aktif' : 'Nonaktif', trailing: IosToggle( value: _settings.shalatReportReminderEnabled, onChanged: (v) { _settings.shalatReportReminderEnabled = v; _saveSettings(); _resyncPrayerNotifications(); }, ), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.clock3, iconColor: const Color(0xFF16A085), title: 'Jeda Pengingat Lapor', subtitle: '${_settings.shalatReportReminderDelayMinutes} menit', trailing: const Icon(LucideIcons.chevronRight, size: 20), onTap: () => _showShalatReportDelayDialog(context), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.repeat, iconColor: const Color(0xFF8E44AD), title: 'Ulangi Pengingat Lapor', subtitle: '${_settings.shalatReportReminderRepeatCount}x • ${_settings.shalatReportReminderRepeatIntervalMinutes} menit', trailing: const Icon(LucideIcons.chevronRight, size: 20), onTap: () => _showShalatReportRepeatDialog(context), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.moonStar, iconColor: const Color(0xFF636E72), title: 'Jam Tenang', subtitle: '${_settings.quietHoursStart} - ${_settings.quietHoursEnd}', trailing: const Icon(LucideIcons.chevronRight, size: 20), onTap: () => _showQuietHoursDialog(context), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.gauge, iconColor: const Color(0xFFE17055), title: 'Batas Push Non-Sholat', subtitle: '${_settings.maxNonPrayerPushPerDay} per hari', trailing: const Icon(LucideIcons.chevronRight, size: 20), onTap: () => _showPushCapDialog(context), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.flame, iconColor: const Color(0xFFE17055), title: 'Peringatan Streak', subtitle: 'Tilawah & dzikir belum tercatat', trailing: IosToggle( value: _settings.streakRiskEnabled, onChanged: (v) { _settings.streakRiskEnabled = v; _saveSettings(); }, ), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.calendarDays, iconColor: const Color(0xFF7B61FF), title: 'Ringkasan Mingguan', subtitle: 'Ringkasan ibadah setiap hari Senin', trailing: IosToggle( value: _settings.weeklySummaryEnabled, onChanged: (v) { _settings.weeklySummaryEnabled = v; _saveSettings(); }, ), ), ]; } List _buildTilawahGroupItems(bool isDark) { final forceTilawahAutoSync = !_settings.simpleMode; return [ _settingRow( isDark, icon: LucideIcons.bookOpen, iconColor: Colors.amber, title: 'Target Tilawah', subtitle: '${_settings.tilawahTargetValue} ${_settings.tilawahTargetUnit}', trailing: const Icon(LucideIcons.chevronRight, size: 20), onTap: () => _showTilawahDialog(context), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.refreshCw, iconColor: Colors.blue, title: 'Auto-Sync Tilawah', subtitle: forceTilawahAutoSync ? 'Mode Lengkap: selalu aktif' : 'Catat otomatis dari menu Al-Quran', trailing: IgnorePointer( ignoring: forceTilawahAutoSync, child: Opacity( opacity: forceTilawahAutoSync ? 0.78 : 1, child: IosToggle( value: forceTilawahAutoSync ? true : _settings.tilawahAutoSync, onChanged: (v) { _settings.tilawahAutoSync = v; _saveSettings(); _syncTodayTilawahAutoSync(v); }, ), ), ), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.arrowRightCircle, iconColor: Colors.green, title: 'Lanjut Surah Otomatis', trailing: IosToggle( value: _settings.tilawahAutoContinueNextSurah, onChanged: (v) { _settings.tilawahAutoContinueNextSurah = v; _saveSettings(); }, ), ), ]; } List _buildDzikirGroupItems(bool isDark) { final displayMode = _normalizeDzikirDisplayMode(_settings.dzikirDisplayMode); final counterButtonPosition = _normalizeDzikirCounterButtonPosition( _settings.dzikirCounterButtonPosition, ); return [ _buildSegmentSettingCard( isDark, title: 'Mode Tampilan Dzikir', subtitle: 'Pilih tampilan dzikir: daftar atau slide fokus', value: displayMode, options: const {'list': 'Daftar', 'focus': 'Slide'}, onChanged: (value) { _settings.dzikirDisplayMode = value; _saveSettings(); }, ), if (displayMode == 'focus') ...[ const SizedBox(height: 10), _buildSegmentSettingCard( isDark, title: 'Posisi Tombol Hitung', subtitle: 'Pilih letak tombol hitung pada mode slide', value: counterButtonPosition, options: const { 'bottomPill': 'Pill Bawah', 'fabCircle': 'Lingkaran Kanan', }, onChanged: (value) { _settings.dzikirCounterButtonPosition = value; _saveSettings(); }, ), ], const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.vibrate, iconColor: const Color(0xFF8E44AD), title: 'Haptic Saat Hitung', subtitle: 'Aktifkan getaran kecil saat tombol hitung ditekan', trailing: IosToggle( value: _settings.dzikirHapticOnCount, onChanged: (v) { _settings.dzikirHapticOnCount = v; _saveSettings(); }, ), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.skipForward, iconColor: const Color(0xFF16A085), title: 'Auto-Advance Dzikir', subtitle: 'Aktifkan lanjut otomatis saat hitungan dzikir selesai', trailing: IosToggle( value: _settings.dzikirAutoAdvance, onChanged: (v) { _settings.dzikirAutoAdvance = v; _saveSettings(); }, ), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.listChecks, iconColor: Colors.indigo, title: 'Amalan Tambahan', subtitle: 'Kelola dzikir tambahan dan puasa sunnah', trailing: const Icon(LucideIcons.chevronRight, size: 20), onTap: () => _showAmalanDialog(context), ), ]; } List _buildDisplayGroupItems(bool isDark) { return [ _settingRow( isDark, icon: LucideIcons.moon, iconColor: const Color(0xFF6C5CE7), title: 'Mode Gelap', trailing: IosToggle( value: _isDarkMode, onChanged: _toggleDarkMode, ), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.type, iconColor: const Color(0xFF636E72), title: 'Ukuran Font Arab', subtitle: '${_settings.arabicFontSize.round()}pt', ), const SizedBox(height: 10), Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight, borderRadius: BorderRadius.circular(16), border: Border.all( color: isDark ? AppColors.primary.withValues(alpha: 0.08) : AppColors.cream, ), ), child: Row( children: [ const Text( '18', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600), ), Expanded( child: Slider( value: _settings.arabicFontSize, min: 18, max: 40, divisions: 22, label: '${_settings.arabicFontSize.round()}', activeColor: AppColors.primary, onChanged: (v) { _settings.arabicFontSize = v; _saveSettings(); }, ), ), const Text( '40', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600), ), ], ), ), ]; } List _buildAccountDataGroupItems(bool isDark) { return [ _settingRow( isDark, icon: LucideIcons.user, iconColor: AppColors.primary, title: 'Profil', subtitle: _settings.userName, trailing: const Icon(LucideIcons.chevronRight, size: 20), onTap: () => _showEditProfileDialog(context), ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.info, iconColor: AppColors.sage, title: 'Versi Aplikasi', subtitle: '1.1.0', ), const SizedBox(height: 10), _settingRow( isDark, icon: LucideIcons.trash2, iconColor: Colors.red, title: 'Reset Semua Data', subtitle: 'Hapus riwayat dan reset pengaturan', trailing: const Icon(LucideIcons.chevronRight, size: 20), onTap: () => _showResetDialog(context), ), ]; } Widget _buildGroupEntry( bool isDark, { required IconData icon, required Color iconColor, required String title, required String subtitle, required VoidCallback onTap, }) { return _settingRow( isDark, icon: icon, iconColor: iconColor, title: title, subtitle: subtitle, trailing: const Icon(LucideIcons.chevronRight, size: 20), onTap: onTap, ); } Widget _sectionLabel(String text) { return Text( text, style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 1.5, color: AppColors.sage, ), ); } Widget _settingRow( bool isDark, { required IconData icon, required Color iconColor, required String title, String? subtitle, Widget? trailing, VoidCallback? onTap, }) { return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight, borderRadius: BorderRadius.circular(16), border: Border.all( color: isDark ? AppColors.primary.withValues(alpha: 0.08) : AppColors.cream, ), ), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: iconColor.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(10), ), child: Icon(icon, color: iconColor, size: 20), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, ), ), if (subtitle != null) Text( subtitle, style: TextStyle( fontSize: 12, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), ], ), ), if (trailing != null) trailing, ], ), ), ); } Widget _buildSegmentSettingCard( bool isDark, { required String title, String? subtitle, required String value, required Map options, required ValueChanged onChanged, }) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight, borderRadius: BorderRadius.circular(16), border: Border.all( color: isDark ? AppColors.primary.withValues(alpha: 0.08) : AppColors.cream, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, ), ), if (subtitle != null) ...[ const SizedBox(height: 4), Text( subtitle, style: TextStyle( fontSize: 12, color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight, ), ), ], const SizedBox(height: 12), Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: isDark ? AppColors.backgroundDark : AppColors.backgroundLight, borderRadius: BorderRadius.circular(12), border: Border.all( color: isDark ? AppColors.primary.withValues(alpha: 0.08) : AppColors.cream, ), ), child: Row( children: options.entries.map((entry) { final selected = value == entry.key; return Expanded( child: GestureDetector( onTap: () => onChanged(entry.key), child: AnimatedContainer( duration: const Duration(milliseconds: 160), padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 10, ), decoration: BoxDecoration( color: selected ? AppColors.primary : Colors.transparent, borderRadius: BorderRadius.circular(10), ), child: Text( entry.value, textAlign: TextAlign.center, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: selected ? AppColors.onPrimary : (isDark ? AppColors.textPrimaryDark : AppColors.textPrimaryLight), ), ), ), ), ); }).toList(), ), ), ], ), ); } void _showMethodDialog(BuildContext context) { showDialog( context: context, builder: (ctx) => AlertDialog( insetPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), title: const Text('Metode Perhitungan'), content: SizedBox( width: MediaQuery.of(context).size.width * 0.85, child: const Text( 'Aplikasi ini menggunakan data resmi dari Kementerian Agama RI (Kemenag) melalui API myQuran.\n\nData Kemenag sudah standar dan akurat untuk seluruh wilayah Indonesia, sehingga tidak perlu diubah.', ), ), actions: [ FilledButton( onPressed: () => Navigator.pop(ctx), child: const Text('Tutup'), ), ], ), ); } void _showLocationDialog(BuildContext context) { final searchCtrl = TextEditingController(); bool isSearching = false; bool isDetecting = false; String? currentCityLabel; String? currentCityId; String? currentCityError; List> results = []; Timer? debounce; showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, setDialogState) => AlertDialog( insetPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), title: const Text('Cari Kota/Kabupaten'), content: SizedBox( width: MediaQuery.of(context).size.width * 0.85, child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.primary.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(12), border: Border.all( color: AppColors.primary.withValues(alpha: 0.2), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon( LucideIcons.mapPin, size: 16, color: AppColors.primary, ), const SizedBox(width: 8), Expanded( child: Text( currentCityLabel == null ? 'Deteksi lokasi saat ini' : 'Anda di $currentCityLabel', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w600, ), ), ), if (isDetecting) const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ), ], ), if (currentCityError != null) ...[ const SizedBox(height: 6), Text( currentCityError!, style: const TextStyle( fontSize: 12, color: Colors.red, ), ), ], const SizedBox(height: 8), Row( children: [ TextButton( onPressed: isDetecting ? null : () async { setDialogState(() { isDetecting = true; currentCityError = null; }); try { final pos = await LocationService.instance .getCurrentLocation(); final fallbackLocation = pos == null ? LocationService.instance .getLastKnownLocation() : null; final lat = pos?.latitude ?? fallbackLocation?.lat; final lng = pos?.longitude ?? fallbackLocation?.lng; if (lat == null || lng == null) { setDialogState(() { currentCityError = 'Lokasi tidak tersedia. Pastikan izin lokasi aktif.'; isDetecting = false; }); return; } final detectedLabel = (await LocationService.instance .getCityName(lat, lng)) .split(',') .first .trim(); final resolved = await LocationService .instance .resolveMyQuranCityFromCoordinates( lat: lat, lng: lng, ); if (resolved == null) { setDialogState(() { final fallbackCity = detectedLabel.isEmpty ? 'lokasi saat ini' : detectedLabel; currentCityError = 'Lokasi Anda terdeteksi di $fallbackCity. Kami belum menemukan ID kota yang cocok di data jadwal. Silakan cari manual kota terdekat.'; isDetecting = false; }); return; } setDialogState(() { currentCityId = resolved.id; currentCityLabel = resolved.name; isDetecting = false; }); } catch (_) { setDialogState(() { currentCityError = 'Deteksi lokasi gagal. Coba lagi.'; isDetecting = false; }); } }, child: const Text('Deteksi Lokasi'), ), const SizedBox(width: 8), if (currentCityId != null && currentCityLabel != null) FilledButton( onPressed: () { _settings.lastCityName = '$currentCityLabel|$currentCityId'; _saveSettings(); ref.invalidate(selectedCityIdProvider); ref.invalidate(cityNameProvider); ref.invalidate(prayerTimesProvider); unawaited(ref.read(prayerTimesProvider.future)); Navigator.pop(ctx); }, child: const Text('Gunakan Lokasi Ini'), ), ], ), ], ), ), const SizedBox(height: 12), TextField( controller: searchCtrl, autofocus: true, decoration: InputDecoration( hintText: 'Cth: Jakarta', border: const OutlineInputBorder(), suffixIcon: IconButton( icon: const Icon(LucideIcons.search), onPressed: () async { if (searchCtrl.text.trim().isEmpty) return; setDialogState(() => isSearching = true); final res = await MyQuranSholatService.instance .searchCity(searchCtrl.text.trim()); setDialogState(() { results = res; isSearching = false; }); }, ), ), onChanged: (val) { if (val.trim().length < 3) return; if (debounce?.isActive ?? false) debounce!.cancel(); debounce = Timer(const Duration(milliseconds: 500), () async { if (!mounted) return; setDialogState(() => isSearching = true); try { final res = await MyQuranSholatService.instance .searchCity(val.trim()); if (mounted) { setDialogState(() { results = res; }); } } catch (e) { debugPrint('Error searching city: $e'); } finally { if (mounted) { setDialogState(() { isSearching = false; }); } } }); }, onSubmitted: (val) async { if (val.trim().isEmpty) return; if (debounce?.isActive ?? false) debounce!.cancel(); setDialogState(() => isSearching = true); final res = await MyQuranSholatService.instance .searchCity(val.trim()); if (mounted) { setDialogState(() { results = res; isSearching = false; }); } }, ), const SizedBox(height: 16), if (isSearching) const Center(child: CircularProgressIndicator()) else if (results.isEmpty) const Text('Tidak ada hasil', style: TextStyle(color: Colors.grey)) else SizedBox( height: 200, width: double.maxFinite, child: ListView.builder( shrinkWrap: true, itemCount: results.length, itemBuilder: (context, i) { final city = results[i]; return ListTile( title: Text(city['lokasi'] ?? ''), onTap: () { final id = city['id']; final name = city['lokasi']; if (id != null && name != null) { _settings.lastCityName = '$name|$id'; _saveSettings(); // Update providers to refresh data ref.invalidate(selectedCityIdProvider); ref.invalidate(cityNameProvider); ref.invalidate(prayerTimesProvider); unawaited(ref.read(prayerTimesProvider.future)); Navigator.pop(ctx); } }, ); }, ), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Batal'), ), ], ), ), ); } void _showEditProfileDialog(BuildContext context) { final nameCtrl = TextEditingController(text: _settings.userName); final emailCtrl = TextEditingController(text: _settings.userEmail); showDialog( context: context, builder: (ctx) => AlertDialog( insetPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), title: const Text('Edit Profil'), content: SizedBox( width: MediaQuery.of(context).size.width * 0.85, child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: nameCtrl, decoration: const InputDecoration( labelText: 'Nama', border: OutlineInputBorder(), ), ), const SizedBox(height: 12), TextField( controller: emailCtrl, decoration: const InputDecoration( labelText: 'Email', border: OutlineInputBorder(), ), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Batal'), ), FilledButton( onPressed: () { _settings.userName = nameCtrl.text.trim(); _settings.userEmail = emailCtrl.text.trim(); _saveSettings(); Navigator.pop(ctx); }, child: const Text('Simpan'), ), ], ), ); } void _showIqamahDialog(BuildContext context) { final offsets = Map.from(_settings.iqamahOffset); showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, setDialogState) => AlertDialog( insetPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), title: const Text('Waktu Iqamah (menit)'), content: SizedBox( width: MediaQuery.of(context).size.width * 0.85, child: Column( mainAxisSize: MainAxisSize.min, children: offsets.entries.map((e) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ SizedBox( width: 80, child: Text( e.key[0].toUpperCase() + e.key.substring(1), style: const TextStyle(fontWeight: FontWeight.w600), ), ), Expanded( child: Slider( value: e.value.toDouble(), min: 0, max: 30, divisions: 30, label: '${e.value} min', activeColor: AppColors.primary, onChanged: (v) { setDialogState(() { offsets[e.key] = v.round(); }); }, ), ), SizedBox( width: 40, child: Text( '${e.value}m', style: const TextStyle(fontWeight: FontWeight.w600), ), ), ], ), ); }).toList(), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Batal'), ), FilledButton( onPressed: () { _settings.iqamahOffset = offsets; _saveSettings(); ref.invalidate(prayerTimesProvider); unawaited(ref.read(prayerTimesProvider.future)); Navigator.pop(ctx); }, child: const Text('Simpan'), ), ], ), ), ); } void _showRawatibDialog(BuildContext context) { int tempLevel = _settings.rawatibLevel; showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, setDialogState) => AlertDialog( title: Row( children: [ const Text('Sholat Rawatib', style: TextStyle(fontSize: 18)), const Spacer(), IconButton( icon: const Icon(LucideIcons.info, color: AppColors.primary), onPressed: () { showModalBottomSheet( context: context, isScrollControlled: true, useSafeArea: true, builder: (bCtx) { final keyboardInset = MediaQuery.of(bCtx).viewInsets.bottom; return Padding( padding: EdgeInsets.fromLTRB(24, 24, 24, 24 + keyboardInset), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Informasi Sholat Rawatib', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), const Text('Muakkad (Sangat Ditekankan)', style: TextStyle( fontWeight: FontWeight.bold, color: AppColors.primary)), const SizedBox(height: 8), const Text('Total 10 atau 12 Rakaat:'), const Padding( padding: EdgeInsets.only(left: 12, top: 4), child: Text( '• 2 Rakaat sebelum Subuh\n• 2 atau 4 Rakaat sebelum Dzuhur\n• 2 Rakaat sesudah Dzuhur\n• 2 Rakaat sesudah Maghrib\n• 2 Rakaat sesudah Isya', style: TextStyle(height: 1.5)), ), const SizedBox(height: 16), const Text('Ghairu Muakkad (Tambahan)', style: TextStyle( fontWeight: FontWeight.bold, color: AppColors.primary)), const SizedBox(height: 8), const Padding( padding: EdgeInsets.only(left: 12), child: Text( '• Tambahan 2 Rakaat sesudah Dzuhur\n• 4 Rakaat sebelum Ashar\n• 2 Rakaat sebelum Maghrib\n• 2 Rakaat sebelum Isya', style: TextStyle(height: 1.5)), ), const SizedBox(height: 24), ], ), ); }, ); }, ), ], ), content: Column( mainAxisSize: MainAxisSize.min, children: [ RadioListTile( title: const Text('Mati (Tanpa Rawatib)'), value: 0, groupValue: tempLevel, onChanged: (v) => setDialogState(() => tempLevel = v!), ), RadioListTile( title: const Text('Muakkad Saja'), value: 1, groupValue: tempLevel, onChanged: (v) => setDialogState(() => tempLevel = v!), ), RadioListTile( title: const Text('Lengkap (Semua)'), value: 2, groupValue: tempLevel, onChanged: (v) => setDialogState(() => tempLevel = v!), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Batal'), ), FilledButton( onPressed: () { _settings.rawatibLevel = tempLevel; _saveSettings(); Navigator.pop(ctx); }, child: const Text('Simpan'), ), ], ), ), ); } void _showTilawahDialog(BuildContext context) { final qtyCtrl = TextEditingController(text: _settings.tilawahTargetValue.toString()); String tempUnit = _settings.tilawahTargetUnit; showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, setDialogState) => AlertDialog( title: const Text('Target Tilawah Harian'), content: Row( children: [ Expanded( flex: 1, child: TextField( controller: qtyCtrl, keyboardType: TextInputType.number, decoration: const InputDecoration(border: OutlineInputBorder()), ), ), const SizedBox(width: 16), Expanded( flex: 2, child: DropdownButtonFormField( value: tempUnit, decoration: const InputDecoration(border: OutlineInputBorder()), items: ['Juz', 'Halaman', 'Ayat'] .map((u) => DropdownMenuItem(value: u, child: Text(u))) .toList(), onChanged: (v) => setDialogState(() => tempUnit = v!), ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Batal'), ), FilledButton( onPressed: () { final qty = int.tryParse(qtyCtrl.text.trim()) ?? 1; _settings.tilawahTargetValue = qty > 0 ? qty : 1; _settings.tilawahTargetUnit = tempUnit; _saveSettings(); // Update today's active checklist immediately final todayKey = DateFormat('yyyy-MM-dd').format(DateTime.now()); final logBox = Hive.box(HiveBoxes.worshipLogs); final log = logBox.get(todayKey); if (log != null && log.tilawahLog != null) { log.tilawahLog!.targetValue = _settings.tilawahTargetValue; log.tilawahLog!.targetUnit = _settings.tilawahTargetUnit; log.save(); } Navigator.pop(ctx); }, child: const Text('Simpan'), ), ], ), ), ); } void _showAmalanDialog(BuildContext context) { bool tDzikir = _settings.trackDzikir; bool tPuasa = _settings.trackPuasa; showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, setDialogState) => AlertDialog( title: const Text('Amalan Tambahan'), content: Column( mainAxisSize: MainAxisSize.min, children: [ SwitchListTile( title: const Text('Dzikir Pagi & Petang'), value: tDzikir, onChanged: (v) => setDialogState(() => tDzikir = v), ), SwitchListTile( title: const Text('Puasa Sunnah'), value: tPuasa, onChanged: (v) => setDialogState(() => tPuasa = v), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Batal'), ), FilledButton( onPressed: () { _settings.trackDzikir = tDzikir; _settings.trackPuasa = tPuasa; _saveSettings(); Navigator.pop(ctx); }, child: const Text('Simpan'), ), ], ), ), ); } void _showResetDialog(BuildContext context) { showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Hapus Semua Data?'), content: const Text( 'Ini akan menghapus semua riwayat ibadah, marka quran, penghitung dzikir, dan mereset pengaturan. Tindakan ini tidak dapat dibatalkan.', ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Batal'), ), FilledButton( style: FilledButton.styleFrom(backgroundColor: Colors.red), onPressed: () async { await Hive.box(HiveBoxes.worshipLogs).clear(); await Hive.box(HiveBoxes.bookmarks).clear(); await Hive.box(HiveBoxes.dzikirCounters).clear(); final box = Hive.box(HiveBoxes.settings); await box.clear(); await box.put('default', AppSettings()); setState(() { _settings = box.get('default')!; }); ref.read(themeProvider.notifier).state = ThemeMode.system; if (ctx.mounted) Navigator.pop(ctx); }, child: const Text('Hapus'), ), ], ), ); } }