Files
jamshalat-diary/lib/features/settings/presentation/settings_screen.dart
2026-03-16 00:30:32 +07:00

1101 lines
38 KiB
Dart

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/myquran_sholat_service.dart';
import '../../../data/services/myquran_sholat_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<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends ConsumerState<SettingsScreen> {
late AppSettings _settings;
@override
void initState() {
super.initState();
final box = Hive.box<AppSettings>(HiveBoxes.settings);
_settings = box.get('default') ?? AppSettings();
}
void _saveSettings() {
_settings.save();
setState(() {});
}
bool get _isDarkMode => _settings.themeModeIndex != 1;
bool get _notificationsEnabled =>
_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();
}
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
appBar: AppBar(
title: const Text('Pengaturan'),
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// ── Profile Card ──
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: [
// Avatar
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),
// ── PREFERENCES ──
_sectionLabel('PREFERENSI'),
const SizedBox(height: 12),
_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;
_saveSettings();
},
),
),
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: 10),
_settingRow(
isDark,
icon: LucideIcons.bell,
iconColor: const Color(0xFFE17055),
title: 'Notifikasi',
trailing: IosToggle(
value: _notificationsEnabled,
onChanged: _toggleNotifications,
),
),
const SizedBox(height: 24),
// ── CHECKLIST IBADAH (always visible, even in Simple Mode per user request) ──
_sectionLabel('CHECKLIST IBADAH'),
const SizedBox(height: 12),
_settingRow(
isDark,
icon: LucideIcons.building,
iconColor: Colors.teal,
title: 'Tingkat Sholat Rawatib',
subtitle: _settings.rawatibLevel == 0 ? 'Mati' : (_settings.rawatibLevel == 1 ? 'Muakkad Saja' : 'Lengkap (Semua)'),
trailing: const Icon(LucideIcons.chevronRight, size: 20),
onTap: () => _showRawatibDialog(context),
),
const SizedBox(height: 10),
_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: 'Catat otomatis dari menu Al-Quran',
trailing: IosToggle(
value: _settings.tilawahAutoSync,
onChanged: (v) {
_settings.tilawahAutoSync = v;
_saveSettings();
final todayKey = DateFormat('yyyy-MM-dd').format(DateTime.now());
final logBox = Hive.box<DailyWorshipLog>(HiveBoxes.worshipLogs);
final log = logBox.get(todayKey);
if (log != null && log.tilawahLog != null) {
log.tilawahLog!.autoSync = v;
log.save();
}
},
),
),
const SizedBox(height: 10),
_settingRow(
isDark,
icon: LucideIcons.listChecks,
iconColor: Colors.indigo,
title: 'Amalan Tambahan',
subtitle: 'Dzikir & Puasa Sunnah',
trailing: const Icon(LucideIcons.chevronRight, size: 20),
onTap: () => _showAmalanDialog(context),
),
const SizedBox(height: 24),
// ── DZIKIR DISPLAY ──
_sectionLabel('TAMPILAN DZIKIR'),
const SizedBox(height: 12),
_buildSegmentSettingCard(
isDark,
title: 'Mode Tampilan Dzikir',
subtitle: 'Pilih daftar baris atau fokus per slide',
value: _settings.dzikirDisplayMode,
options: const {
'list': 'Daftar (Baris)',
'focus': 'Fokus (Slide)',
},
onChanged: (value) {
_settings.dzikirDisplayMode = value;
_saveSettings();
},
),
if (_settings.dzikirDisplayMode == 'focus') ...[
const SizedBox(height: 10),
_buildSegmentSettingCard(
isDark,
title: 'Posisi Tombol Hitung',
subtitle: 'Atur posisi tombol pada mode fokus',
value: _settings.dzikirCounterButtonPosition,
options: const {
'bottomPill': 'Pill Bawah',
'fabCircle': 'Bulat Kanan Bawah',
},
onChanged: (value) {
_settings.dzikirCounterButtonPosition = value;
_saveSettings();
},
),
const SizedBox(height: 10),
_settingRow(
isDark,
icon: LucideIcons.arrowRight,
iconColor: const Color(0xFF00B894),
title: 'Lanjut Otomatis Saat Target Tercapai',
trailing: IosToggle(
value: _settings.dzikirAutoAdvance,
onChanged: (v) {
_settings.dzikirAutoAdvance = v;
_saveSettings();
},
),
),
],
const SizedBox(height: 10),
_settingRow(
isDark,
icon: LucideIcons.vibrate,
iconColor: const Color(0xFF6C5CE7),
title: 'Getaran Saat Hitung',
trailing: IosToggle(
value: _settings.dzikirHapticOnCount,
onChanged: (v) {
_settings.dzikirHapticOnCount = v;
_saveSettings();
},
),
),
const SizedBox(height: 24),
// ── PRAYER SETTINGS ──
_sectionLabel('WAKTU SHOLAT'),
const SizedBox(height: 12),
_settingRow(
isDark,
icon: LucideIcons.building,
iconColor: AppColors.primary,
title: 'Metode Perhitungan',
subtitle: 'Kemenag RI',
trailing: const Icon(LucideIcons.chevronRight, size: 20),
onTap: () => _showMethodDialog(context),
),
const SizedBox(height: 10),
_settingRow(
isDark,
icon: LucideIcons.mapPin,
iconColor: const Color(0xFF00B894),
title: 'Lokasi',
subtitle: _displayCityName,
trailing: const Icon(LucideIcons.chevronRight, size: 20),
onTap: () => _showLocationDialog(context),
),
const SizedBox(height: 10),
_settingRow(
isDark,
icon: LucideIcons.timer,
iconColor: const Color(0xFFFDAA5E),
title: 'Waktu Iqamah',
subtitle: 'Atur per waktu sholat',
trailing: const Icon(LucideIcons.chevronRight, size: 20),
onTap: () => _showIqamahDialog(context),
),
const SizedBox(height: 24),
// ── DISPLAY ──
_sectionLabel('TAMPILAN'),
const SizedBox(height: 12),
_settingRow(
isDark,
icon: LucideIcons.type,
iconColor: const Color(0xFF636E72),
title: 'Ukuran Font Arab',
subtitle: '${_settings.arabicFontSize.round()}pt',
trailing: SizedBox(
width: 120,
child: Slider(
value: _settings.arabicFontSize,
min: 16,
max: 40,
divisions: 12,
activeColor: AppColors.primary,
onChanged: (v) {
_settings.arabicFontSize = v;
_saveSettings();
},
),
),
),
const SizedBox(height: 24),
// ── ABOUT ──
_sectionLabel('TENTANG'),
const SizedBox(height: 12),
_settingRow(
isDark,
icon: LucideIcons.info,
iconColor: AppColors.sage,
title: 'Versi Aplikasi',
subtitle: '1.0.0',
),
const SizedBox(height: 10),
_settingRow(
isDark,
icon: LucideIcons.heart,
iconColor: Colors.red,
title: 'Beri Nilai Kami',
trailing: const Icon(LucideIcons.chevronRight, size: 20),
onTap: () {},
),
const SizedBox(height: 24),
// ── Reset Button ──
GestureDetector(
onTap: () => _showResetDialog(context),
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.red.withValues(alpha: 0.3),
width: 1.5,
),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(LucideIcons.logOut, color: Colors.red, size: 20),
SizedBox(width: 8),
Text(
'Hapus Semua Data',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
],
),
),
),
const SizedBox(height: 32),
],
),
);
}
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<String, String> options,
required ValueChanged<String> 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;
List<Map<String, dynamic>> 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: [
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);
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<String, int>.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();
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,
builder: (bCtx) => Padding(
padding: const EdgeInsets.all(24.0),
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<int>(
title: const Text('Mati (Tanpa Rawatib)'),
value: 0,
groupValue: tempLevel,
onChanged: (v) => setDialogState(() => tempLevel = v!),
),
RadioListTile<int>(
title: const Text('Muakkad Saja'),
value: 1,
groupValue: tempLevel,
onChanged: (v) => setDialogState(() => tempLevel = v!),
),
RadioListTile<int>(
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<String>(
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<DailyWorshipLog>(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<AppSettings>(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'),
),
],
),
);
}
}