feat: complete Simple Mode contextual routing and navigation state synchronization

This commit is contained in:
dwindown
2026-03-15 07:24:13 +07:00
parent faadc1865d
commit 25728583b3
21 changed files with 1095 additions and 320 deletions

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:go_router/go_router.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:intl/intl.dart';
import '../../../app/theme/app_colors.dart';
import '../../../core/widgets/progress_bar.dart';
@@ -163,11 +164,11 @@ class _ChecklistScreenState extends ConsumerState<ChecklistScreen> {
actions: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.notifications_outlined),
icon: const Icon(LucideIcons.bell),
),
IconButton(
onPressed: () => context.push('/settings'),
icon: const Icon(Icons.settings_outlined),
icon: const Icon(LucideIcons.settings),
),
const SizedBox(width: 8),
],
@@ -176,8 +177,10 @@ class _ChecklistScreenState extends ConsumerState<ChecklistScreen> {
padding: const EdgeInsets.symmetric(horizontal: 16),
children: [
const SizedBox(height: 12),
_buildProgressCard(log, isDark),
const SizedBox(height: 24),
if (!_settings.simpleMode) ...[
_buildProgressCard(log, isDark),
const SizedBox(height: 24),
],
_sectionLabel('SHOLAT FARDHU & RAWATIB'),
const SizedBox(height: 12),
..._fardhuPrayers.map((p) => _buildShalatCard(p, isDark)).toList(),
@@ -250,7 +253,7 @@ class _ChecklistScreenState extends ConsumerState<ChecklistScreen> {
),
child: Row(
children: [
const Icon(Icons.stars, color: AppColors.primary, size: 14),
const Icon(LucideIcons.star, color: AppColors.primary, size: 14),
const SizedBox(width: 4),
Text(
'${log.totalPoints} pts',
@@ -347,7 +350,7 @@ class _ChecklistScreenState extends ConsumerState<ChecklistScreen> {
: (isDark ? AppColors.primary.withValues(alpha: 0.08) : AppColors.cream.withValues(alpha: 0.5)),
borderRadius: BorderRadius.circular(12),
),
child: Icon(Icons.mosque, size: 22, color: isCompleted ? AppColors.primary : AppColors.sage),
child: Icon(LucideIcons.building, size: 22, color: isCompleted ? AppColors.primary : AppColors.sage),
),
title: Text(
'Sholat $prayerName',
@@ -414,7 +417,7 @@ class _ChecklistScreenState extends ConsumerState<ChecklistScreen> {
child: Row(
children: [
Icon(
selected ? Icons.radio_button_checked : Icons.radio_button_off,
selected ? LucideIcons.checkCircle2 : LucideIcons.circle,
size: 18,
color: selected ? AppColors.primary : Colors.grey,
),
@@ -467,7 +470,7 @@ class _ChecklistScreenState extends ConsumerState<ChecklistScreen> {
: (isDark ? AppColors.primary.withValues(alpha: 0.08) : AppColors.cream.withValues(alpha: 0.5)),
borderRadius: BorderRadius.circular(12),
),
child: Icon(Icons.menu_book, size: 22, color: log.isCompleted ? AppColors.primary : AppColors.sage),
child: Icon(LucideIcons.bookOpen, size: 22, color: log.isCompleted ? AppColors.primary : AppColors.sage),
),
const SizedBox(width: 14),
Expanded(
@@ -505,7 +508,7 @@ class _ChecklistScreenState extends ConsumerState<ChecklistScreen> {
// ── Row 2: Ayat Tracker ──
Row(
children: [
Icon(Icons.auto_stories, size: 18, color: AppColors.sage),
Icon(LucideIcons.bookOpen, size: 18, color: AppColors.sage),
const SizedBox(width: 8),
Expanded(
child: Text(
@@ -520,10 +523,10 @@ class _ChecklistScreenState extends ConsumerState<ChecklistScreen> {
if (log.autoSync)
Tooltip(
message: 'Sinkron dari Al-Quran',
child: Icon(Icons.sync, size: 16, color: AppColors.primary),
child: Icon(LucideIcons.refreshCw, size: 16, color: AppColors.primary),
),
IconButton(
icon: const Icon(Icons.remove_circle_outline, size: 20),
icon: const Icon(LucideIcons.minusCircle, size: 20),
visualDensity: VisualDensity.compact,
onPressed: log.rawAyatRead > 0
? () {
@@ -533,7 +536,7 @@ class _ChecklistScreenState extends ConsumerState<ChecklistScreen> {
: null,
),
IconButton(
icon: const Icon(Icons.add_circle_outline, size: 20, color: AppColors.primary),
icon: const Icon(LucideIcons.plusCircle, size: 20, color: AppColors.primary),
visualDensity: VisualDensity.compact,
onPressed: () {
log.rawAyatRead++;
@@ -563,7 +566,7 @@ class _ChecklistScreenState extends ConsumerState<ChecklistScreen> {
children: [
Row(
children: [
Icon(Icons.auto_awesome, size: 20, color: AppColors.sage),
Icon(LucideIcons.sparkles, size: 20, color: AppColors.sage),
const SizedBox(width: 8),
const Text('Dzikir Harian', style: TextStyle(fontWeight: FontWeight.w600, fontSize: 15)),
],
@@ -594,7 +597,7 @@ class _ChecklistScreenState extends ConsumerState<ChecklistScreen> {
),
child: Row(
children: [
const Icon(Icons.nightlight_round, size: 20, color: AppColors.sage),
const Icon(LucideIcons.moonStar, size: 20, color: AppColors.sage),
const SizedBox(width: 8),
const Expanded(child: Text('Puasa Sunnah', style: TextStyle(fontWeight: FontWeight.w600, fontSize: 15))),
DropdownButton<String>(
@@ -641,7 +644,7 @@ class _CustomCheckbox extends StatelessWidget {
borderRadius: BorderRadius.circular(6),
border: value ? null : Border.all(color: Colors.grey, width: 2),
),
child: value ? const Icon(Icons.check, size: 16, color: Colors.white) : null,
child: value ? const Icon(LucideIcons.check, size: 16, color: Colors.white) : null,
),
);
}

View File

@@ -4,10 +4,14 @@ 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 {
@@ -19,18 +23,31 @@ class DashboardScreen extends ConsumerStatefulWidget {
class _DashboardScreenState extends ConsumerState<DashboardScreen> {
Timer? _countdownTimer;
Duration _countdown = Duration.zero;
String _nextPrayerName = '';
final ValueNotifier<Duration> _countdown = ValueNotifier(Duration.zero);
final ValueNotifier<String> _nextPrayerName = ValueNotifier('');
final ScrollController _prayerScrollController = ScrollController();
bool _hasAutoScrolled = false;
DaySchedule? _currentSchedule;
bool get _isSimpleMode {
final box = Hive.box<AppSettings>(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), (_) {
@@ -49,11 +66,9 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
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;
});
_nextPrayerName.value = next.name;
final diff = target.difference(now);
_countdown.value = diff.isNegative ? Duration.zero : diff;
}
}
}
@@ -69,7 +84,14 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
final prayerTimesAsync = ref.watch(prayerTimesProvider);
ref.listen<AsyncValue<DaySchedule?>>(prayerTimesProvider, (previous, next) {
next.whenData((schedule) {
if (schedule != null) {
_startCountdown(schedule);
}
});
});
return Scaffold(
body: SafeArea(
@@ -81,23 +103,40 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
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);
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),
);
},
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),
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),
],
),
@@ -117,7 +156,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
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),
child: const Icon(LucideIcons.user, size: 20, color: AppColors.primary),
),
const SizedBox(width: 12),
Expanded(
@@ -146,7 +185,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
IconButton(
onPressed: () {},
icon: Icon(
Icons.notifications_outlined,
LucideIcons.bell,
color: isDark
? AppColors.textSecondaryDark
: AppColors.textSecondaryLight,
@@ -155,7 +194,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
IconButton(
onPressed: () => context.push('/settings'),
icon: Icon(
Icons.settings_outlined,
LucideIcons.settings,
color: isDark
? AppColors.textSecondaryDark
: AppColors.textSecondaryLight,
@@ -169,9 +208,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
Widget _buildHeroCard(BuildContext context, DaySchedule schedule) {
final next = schedule.nextPrayer;
final name = _nextPrayerName.isNotEmpty
? _nextPrayerName
: (next?.name ?? 'Isya');
final time = next?.time ?? '--:--';
return Container(
@@ -207,7 +243,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
children: [
Row(
children: [
Icon(Icons.schedule,
Icon(LucideIcons.clock,
size: 16,
color: AppColors.onPrimary.withValues(alpha: 0.8)),
const SizedBox(width: 6),
@@ -223,22 +259,35 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
],
),
const SizedBox(height: 8),
Text(
'$name$time',
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.w800,
color: AppColors.onPrimary,
),
ValueListenableBuilder<String>(
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),
Text(
'Hitung mundur: ${_formatCountdown(_countdown)}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: AppColors.onPrimary.withValues(alpha: 0.8),
),
ValueListenableBuilder<Duration>(
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
@@ -264,7 +313,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.explore, size: 18, color: Colors.white),
Icon(LucideIcons.compass, size: 18, color: Colors.white),
SizedBox(width: 8),
Text(
'Arah Kiblat',
@@ -288,7 +337,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
shape: BoxShape.circle,
),
child: const Icon(
Icons.volume_up,
LucideIcons.volume2,
color: AppColors.onPrimary,
size: 22,
),
@@ -342,7 +391,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
),
child: Text(
prayerTimesAsync.value?.isTomorrow == true ? 'BESOK' : 'HARI INI',
style: TextStyle(
style: const TextStyle(
color: AppColors.primary,
fontSize: 10,
fontWeight: FontWeight.w700,
@@ -371,7 +420,8 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
final p = prayers[i];
final icon = _prayerIcon(p.name);
// Auto-scroll to active prayer on first build
if (p.isActive && i > 0) {
if (p.isActive && i > 0 && !_hasAutoScrolled) {
_hasAutoScrolled = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_prayerScrollController.hasClients) {
final targetOffset = i * 124.0; // 112 width + 12 gap
@@ -405,17 +455,17 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
IconData _prayerIcon(String name) {
switch (name) {
case 'Subuh':
return Icons.wb_twilight;
return LucideIcons.sunrise;
case 'Dzuhur':
return Icons.wb_sunny;
return LucideIcons.sun;
case 'Ashar':
return Icons.filter_drama;
return LucideIcons.cloudSun;
case 'Maghrib':
return Icons.wb_twilight;
return LucideIcons.sunset;
case 'Isya':
return Icons.dark_mode;
return LucideIcons.moon;
default:
return Icons.schedule;
return LucideIcons.clock;
}
}
@@ -436,7 +486,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
String amalanText = 'Belum ada data';
if (log != null) {
List<String> aList = [];
final List<String> 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');
@@ -556,7 +606,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
child: Row(
children: [
Icon(
completed ? Icons.check_circle : Icons.radio_button_unchecked,
completed ? LucideIcons.checkCircle2 : LucideIcons.circle,
color: AppColors.primary,
size: 22,
),
@@ -670,4 +720,184 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
],
);
}
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<AppSettings>(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<AppSettings>(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<AppSettings>(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<AppSettings>(HiveBoxes.settings).get('default')?.simpleMode ?? false;
if (isSimple) {
context.go('/dzikir');
} else {
context.push('/tools/dzikir');
}
},
),
),
],
),
],
);
}
Widget _buildAyatHariIni(BuildContext context, bool isDark) {
return FutureBuilder<Map<String, dynamic>?>(
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,
),
),
],
),
);
},
);
}
}

View File

@@ -2,14 +2,17 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:intl/intl.dart';
import '../../../app/theme/app_colors.dart';
import '../../../data/local/hive_boxes.dart';
import '../../../data/local/models/dzikir_counter.dart';
import '../../../data/local/models/app_settings.dart';
class DzikirScreen extends ConsumerStatefulWidget {
const DzikirScreen({super.key});
final bool isSimpleModeTab;
const DzikirScreen({super.key, this.isSimpleModeTab = false});
@override
ConsumerState<DzikirScreen> createState() => _DzikirScreenState();
@@ -85,14 +88,17 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final box = Hive.box<AppSettings>(HiveBoxes.settings);
final isSimpleMode = box.get('default')?.simpleMode ?? false;
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: !widget.isSimpleModeTab,
title: const Text('Dzikir Pagi & Petang'),
actions: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.info_outline),
icon: const Icon(LucideIcons.info),
),
],
),
@@ -275,7 +281,7 @@ class _DzikirScreenState extends ConsumerState<DzikirScreen>
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
isComplete ? Icons.check : Icons.touch_app,
isComplete ? LucideIcons.check : LucideIcons.fingerprint,
size: 18,
color: isComplete
? AppColors.primary

View File

@@ -1,7 +1,9 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:go_router/go_router.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:intl/intl.dart';
import '../../../app/theme/app_colors.dart';
import '../../../data/local/hive_boxes.dart';
@@ -94,6 +96,7 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
final searchCtrl = TextEditingController();
bool isSearching = false;
List<Map<String, dynamic>> results = [];
Timer? debounce;
showDialog(
context: context,
@@ -113,7 +116,7 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
hintText: 'Cth: Jakarta',
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.search),
icon: const Icon(LucideIcons.search),
onPressed: () async {
if (searchCtrl.text.trim().isEmpty) return;
setDialogState(() => isSearching = true);
@@ -128,8 +131,35 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
},
),
),
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());
@@ -207,11 +237,11 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
actions: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.notifications_outlined),
icon: const Icon(LucideIcons.bell),
),
IconButton(
onPressed: () => context.push('/settings'),
icon: const Icon(Icons.settings_outlined),
icon: const Icon(LucideIcons.settings),
),
const SizedBox(width: 8),
],
@@ -286,7 +316,7 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
),
child: Row(
children: [
const Icon(Icons.location_on,
const Icon(LucideIcons.mapPin,
color: AppColors.primary, size: 24),
const SizedBox(width: 12),
Expanded(
@@ -309,7 +339,7 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
],
),
),
Icon(Icons.expand_more,
Icon(LucideIcons.chevronDown,
color: isDark
? AppColors.textSecondaryDark
: AppColors.textSecondaryLight),

View File

@@ -3,9 +3,11 @@ import 'package:go_router/go_router.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:intl/intl.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../../app/theme/app_colors.dart';
import '../../../core/widgets/progress_bar.dart';
import '../../../data/local/hive_boxes.dart';
import '../../../data/local/models/app_settings.dart';
import '../../../data/local/models/daily_worship_log.dart';
import '../../../data/local/models/checklist_item.dart';
@@ -168,6 +170,31 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
final settingsBox = Hive.box<AppSettings>(HiveBoxes.settings);
final isSimpleMode = settingsBox.get('default')?.simpleMode ?? false;
if (isSimpleMode) {
return Scaffold(
appBar: AppBar(
title: const Text('Riwayat Ibadah'),
centerTitle: false,
actions: [
IconButton(
onPressed: () {},
icon: const Icon(LucideIcons.bell),
),
IconButton(
onPressed: () => context.push('/settings'),
icon: const Icon(LucideIcons.settings),
),
const SizedBox(width: 8),
],
),
body: _buildRiwayatSimpel(context, isDark),
);
}
final weekData = _getWeeklyData();
final avgPercent = _weekAverage(weekData);
final insights = _getInsights();
@@ -179,11 +206,11 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
actions: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.notifications_outlined),
icon: const Icon(LucideIcons.bell),
),
IconButton(
onPressed: () => context.push('/settings'),
icon: const Icon(Icons.settings_outlined),
icon: const Icon(LucideIcons.settings),
),
const SizedBox(width: 8),
],
@@ -283,7 +310,7 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
color: AppColors.primary.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(10),
),
child: const Icon(Icons.stars,
child: const Icon(LucideIcons.star,
color: AppColors.primary, size: 18),
),
],
@@ -373,7 +400,7 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
_insightCard(
context,
isDark,
icon: Icons.star,
icon: LucideIcons.star,
iconBg: AppColors.primary.withValues(alpha: 0.15),
iconColor: AppColors.primary,
label: 'PALING RAJIN',
@@ -386,7 +413,7 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
_insightCard(
context,
isDark,
icon: Icons.trending_up,
icon: LucideIcons.trendingUp,
iconBg: const Color(0xFFFFF3E0),
iconColor: Colors.orange,
label: 'PERLU DITINGKATKAN',
@@ -520,21 +547,16 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
);
}
Widget _buildComingSoon(BuildContext context, String period) {
Widget _buildComingSoon(BuildContext context, String title) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.bar_chart,
Icon(LucideIcons.barChart3,
size: 48, color: AppColors.primary.withValues(alpha: 0.3)),
const SizedBox(height: 12),
Text(
'Laporan $period',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
const SizedBox(height: 4),
Text(
'Segera hadir',
'$title: Segera hadir',
style: TextStyle(
color: Theme.of(context).brightness == Brightness.dark
? AppColors.textSecondaryDark
@@ -544,6 +566,107 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
),
);
}
Widget _buildRiwayatSimpel(BuildContext context, bool isDark) {
final logBox = Hive.box<DailyWorshipLog>(HiveBoxes.worshipLogs);
final now = DateTime.now();
final logs = <DailyWorshipLog>[];
// Fetch up to 14 days of history
for (int i = 0; i < 14; i++) {
final date = now.subtract(Duration(days: i));
final key = DateFormat('yyyy-MM-dd').format(date);
final log = logBox.get(key);
if (log != null && log.totalItems > 0 && log.completedCount > 0) {
logs.add(log);
}
}
if (logs.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(LucideIcons.history, size: 64, color: AppColors.sage.withValues(alpha: 0.5)),
const SizedBox(height: 16),
const Text('Belum ada riwayat ibadah', style: TextStyle(color: AppColors.sage)),
],
),
);
}
return ListView.separated(
padding: const EdgeInsets.all(16),
itemCount: logs.length,
separatorBuilder: (_, __) => const SizedBox(height: 12),
itemBuilder: (context, index) {
final log = logs[index];
final isToday = log.date == DateFormat('yyyy-MM-dd').format(now);
// Build summary text
final List<String> finished = [];
int fardhuCount = log.shalatLogs.values.where((l) => l.completed).length;
if (fardhuCount > 0) finished.add('$fardhuCount Fardhu');
if (log.tilawahLog?.isCompleted == true) finished.add('Tilawah');
if (log.dzikirLog != null) {
int d = 0;
if (log.dzikirLog!.pagi) d++;
if (log.dzikirLog!.petang) d++;
if (d > 0) finished.add('$d Dzikir');
}
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.1)
: AppColors.cream,
),
),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: AppColors.primary.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(LucideIcons.checkCircle2, color: AppColors.primary),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isToday ? 'Hari Ini' : DateFormat('EEEE, d MMM yyyy').format(DateTime.parse(log.date)),
style: const TextStyle(
fontWeight: FontWeight.w700,
fontSize: 15,
),
),
const SizedBox(height: 4),
Text(
finished.isNotEmpty ? finished.join('') : 'Belum ada aktivitas',
style: TextStyle(
fontSize: 13,
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
height: 1.4,
),
),
],
),
),
],
),
);
},
);
}
}
class _DayData {

View File

@@ -3,6 +3,7 @@ import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_qiblah/flutter_qiblah.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../../app/theme/app_colors.dart';
class QiblaScreen extends ConsumerStatefulWidget {
@@ -163,7 +164,7 @@ class _QiblaScreenState extends ConsumerState<QiblaScreen> {
: AppColors.surfaceLight,
border: Border.all(color: AppColors.cream),
),
child: const Icon(Icons.arrow_back, size: 18),
child: const Icon(LucideIcons.arrowLeft, size: 18),
),
onPressed: () => Navigator.pop(context),
),
@@ -176,7 +177,7 @@ class _QiblaScreenState extends ConsumerState<QiblaScreen> {
color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight,
border: Border.all(color: AppColors.cream),
),
child: Icon(isLive ? Icons.my_location : Icons.location_disabled, size: 18),
child: Icon(isLive ? LucideIcons.locate : LucideIcons.locateOff, size: 18),
),
onPressed: () {
if (isLive) {
@@ -213,7 +214,7 @@ class _QiblaScreenState extends ConsumerState<QiblaScreen> {
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.location_on,
Icon(LucideIcons.mapPin,
size: 16, color: AppColors.primary),
const SizedBox(width: 4),
Text(

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import '../../../app/theme/app_colors.dart';
@@ -91,7 +92,7 @@ class _QuranBookmarksScreenState extends State<QuranBookmarksScreen> {
centerTitle: false,
actions: [
IconButton(
icon: const Icon(Icons.settings_display),
icon: const Icon(LucideIcons.settings2),
onPressed: _showDisplaySettings,
),
],
@@ -105,7 +106,7 @@ class _QuranBookmarksScreenState extends State<QuranBookmarksScreen> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.bookmark_border,
LucideIcons.bookmark,
size: 64,
color: AppColors.primary.withValues(alpha: 0.3),
),
@@ -220,7 +221,7 @@ class _QuranBookmarksScreenState extends State<QuranBookmarksScreen> {
mainAxisSize: MainAxisSize.min,
children: [
if (isLastRead) ...[
const Icon(Icons.push_pin, size: 12, color: AppColors.primary),
const Icon(LucideIcons.pin, size: 12, color: AppColors.primary),
const SizedBox(width: 4),
],
Text(
@@ -235,7 +236,7 @@ class _QuranBookmarksScreenState extends State<QuranBookmarksScreen> {
),
),
IconButton(
icon: const Icon(Icons.delete_outline, color: Colors.red, size: 20),
icon: const Icon(LucideIcons.trash2, color: Colors.red, size: 20),
onPressed: () => box.delete(bookmark.key),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
@@ -287,7 +288,7 @@ class _QuranBookmarksScreenState extends State<QuranBookmarksScreen> {
width: double.infinity,
child: FilledButton.icon(
onPressed: () => context.push('/tools/quran/${bookmark.surahId}?startVerse=${bookmark.verseId}'),
icon: const Icon(Icons.menu_book, size: 18),
icon: const Icon(LucideIcons.bookOpen, size: 18),
label: const Text('Lanjutkan Membaca'),
style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
@@ -301,7 +302,7 @@ class _QuranBookmarksScreenState extends State<QuranBookmarksScreen> {
Row(
children: [
Icon(
Icons.access_time,
LucideIcons.clock,
size: 12,
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
),

View File

@@ -5,11 +5,15 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:just_audio/just_audio.dart';
import 'package:go_router/go_router.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../app/theme/app_colors.dart';
import '../../../data/services/equran_service.dart';
import '../../../data/services/unsplash_service.dart';
import 'package:hive_flutter/hive_flutter.dart';
import '../../../data/local/hive_boxes.dart';
import '../../../data/local/models/app_settings.dart';
/// Quran Murattal (audio player) screen.
/// Implements full Surah playback using just_audio and EQuran v2 API.
@@ -17,11 +21,13 @@ class QuranMurattalScreen extends ConsumerStatefulWidget {
final String surahId;
final String? initialQariId;
final bool autoPlay;
final bool isSimpleModeTab;
const QuranMurattalScreen({
super.key,
required this.surahId,
this.initialQariId,
this.autoPlay = false,
this.isSimpleModeTab = false,
});
@override
@@ -217,7 +223,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
final isSelected = entry.key == _selectedQariId;
return ListTile(
leading: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
isSelected ? LucideIcons.checkCircle2 : LucideIcons.circle,
color: isSelected ? AppColors.primary : Colors.grey,
),
title: Text(
@@ -327,7 +333,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
style: const TextStyle(fontSize: 12),
),
trailing: isCurrentSurah
? Icon(Icons.graphic_eq, color: AppColors.primary, size: 20)
? Icon(LucideIcons.music, color: AppColors.primary, size: 20)
: null,
onTap: () {
Navigator.pop(context);
@@ -354,6 +360,8 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final box = Hive.box<AppSettings>(HiveBoxes.settings);
final isSimpleMode = box.get('default')?.simpleMode ?? false;
final surahName = _surahData?['namaLatin'] ?? 'Surah ${widget.surahId}';
final hasPhoto = _unsplashPhoto != null;
@@ -361,6 +369,17 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
return Scaffold(
extendBodyBehindAppBar: hasPhoto,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back,
color: hasPhoto ? Colors.white : null),
onPressed: () {
if (widget.isSimpleModeTab) {
context.go('/');
} else {
context.pop();
}
},
),
backgroundColor: hasPhoto ? Colors.transparent : null,
elevation: hasPhoto ? 0 : null,
iconTheme: hasPhoto ? const IconThemeData(color: Colors.white) : null,
@@ -393,7 +412,6 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
),
],
),
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
@@ -607,7 +625,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
IconButton(
onPressed: () => setState(() => _isShuffleEnabled = !_isShuffleEnabled),
icon: Icon(
Icons.shuffle_rounded,
LucideIcons.shuffle,
size: 24,
color: _isShuffleEnabled
? (_unsplashPhoto != null ? Colors.white : AppColors.primary)
@@ -622,7 +640,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
? () => _navigateToSurah(-1)
: null,
icon: Icon(
Icons.skip_previous_rounded,
LucideIcons.skipBack,
size: 36,
color: (int.tryParse(widget.surahId) ?? 1) > 1
? (_unsplashPhoto != null ? Colors.white : (isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight))
@@ -669,8 +687,8 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
)
: Icon(
_isPlaying
? Icons.pause_rounded
: Icons.play_arrow_rounded,
? LucideIcons.pause
: LucideIcons.play,
size: 36,
color: _unsplashPhoto != null
? Colors.black87
@@ -684,7 +702,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
? () => _navigateToSurah(1)
: null,
icon: Icon(
Icons.skip_next_rounded,
LucideIcons.skipForward,
size: 36,
color: (int.tryParse(widget.surahId) ?? 1) < 114
? (_unsplashPhoto != null ? Colors.white : (isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight))
@@ -695,7 +713,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
IconButton(
onPressed: _showSurahPlaylist,
icon: Icon(
Icons.playlist_play_rounded,
LucideIcons.listMusic,
size: 28,
color: _unsplashPhoto != null
? Colors.white70
@@ -720,7 +738,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.person, size: 16,
Icon(LucideIcons.user, size: 16,
color: _unsplashPhoto != null ? Colors.white : AppColors.primary),
const SizedBox(width: 8),
Text(
@@ -732,7 +750,7 @@ class _QuranMurattalScreenState extends ConsumerState<QuranMurattalScreen> {
),
),
const SizedBox(width: 4),
Icon(Icons.expand_more,
Icon(LucideIcons.chevronDown,
size: 16,
color: _unsplashPhoto != null ? Colors.white : AppColors.primary),
],

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:just_audio/just_audio.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:intl/intl.dart';
@@ -17,7 +18,14 @@ import '../../../core/providers/tilawah_tracking_provider.dart';
class QuranReadingScreen extends ConsumerStatefulWidget {
final String surahId;
final int? initialVerse;
const QuranReadingScreen({super.key, required this.surahId, this.initialVerse});
final bool isSimpleModeTab;
const QuranReadingScreen({
super.key,
required this.surahId,
this.initialVerse,
this.isSimpleModeTab = false,
});
@override
ConsumerState<QuranReadingScreen> createState() => _QuranReadingScreenState();
@@ -49,6 +57,14 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
bool _isHafalanPlaying = false;
StreamSubscription? _playerStateSubscription;
void _navigateToMurattal() {
if (widget.isSimpleModeTab) {
context.push('/quran/${widget.surahId}/murattal');
} else {
context.push('/tools/quran/${widget.surahId}/murattal');
}
}
@override
void initState() {
super.initState();
@@ -274,7 +290,7 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
),
),
ListTile(
leading: const Icon(Icons.push_pin, color: AppColors.primary),
leading: const Icon(LucideIcons.pin, color: AppColors.primary),
title: const Text('Tandai Terakhir Dibaca', style: TextStyle(fontWeight: FontWeight.w600)),
subtitle: const Text('Jadikan ayat ini sebagai titik lanjut membaca anda'),
onTap: () {
@@ -284,7 +300,7 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
),
const Divider(height: 1),
ListTile(
leading: const Icon(Icons.favorite, color: Colors.pink),
leading: const Icon(LucideIcons.heart, color: Colors.pink),
title: const Text('Tambah ke Favorit', style: TextStyle(fontWeight: FontWeight.w600)),
subtitle: const Text('Simpan ayat ini ke daftar favorit anda'),
onTap: () {
@@ -416,7 +432,7 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
const SizedBox(height: 16),
Row(
children: [
const Icon(Icons.auto_stories, size: 20, color: AppColors.primary),
const Icon(LucideIcons.bookOpen, size: 20, color: AppColors.primary),
const SizedBox(width: 8),
Text('Total Dibaca: $calculatedAyat Ayat', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15)),
],
@@ -558,7 +574,7 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
actions: [
IconButton(
icon: Icon(
Icons.psychology,
LucideIcons.brain,
color: _isHafalanMode ? AppColors.primary : (isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight),
),
tooltip: 'Mode Hafalan',
@@ -572,7 +588,7 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
},
),
IconButton(
icon: const Icon(Icons.settings_display),
icon: const Icon(LucideIcons.settings2),
onPressed: _showDisplaySettings,
),
],
@@ -643,7 +659,7 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8),
child: Icon(Icons.diamond,
child: Icon(LucideIcons.gem,
size: 10,
color: AppColors.primary
.withValues(alpha: 0.3)),
@@ -724,8 +740,8 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
color: AppColors.primary,
),
)
: Icon(Icons.stop_circle, color: AppColors.primary, size: 24))
: Icon(Icons.play_circle_outline,
: Icon(LucideIcons.stopCircle, color: AppColors.primary, size: 24))
: Icon(LucideIcons.playCircle,
color: isDark
? AppColors.textSecondaryDark
: AppColors.textSecondaryLight,
@@ -755,8 +771,8 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
},
icon: Icon(
trackingSession == null
? Icons.flag_outlined
: Icons.stop_circle,
? LucideIcons.flag
: LucideIcons.stopCircle,
color: trackingSession == null
? (isDark
? AppColors.textSecondaryDark
@@ -767,7 +783,7 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
IconButton(
onPressed: () => _showBookmarkOptions(i),
icon: Icon(
isLastRead ? Icons.push_pin : (isFav ? Icons.favorite : Icons.bookmark_outline),
isLastRead ? LucideIcons.pin : (isFav ? LucideIcons.heart : LucideIcons.bookmark),
color: isLastRead
? AppColors.primary
: (isFav ? Colors.pink : (isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight)),
@@ -958,7 +974,7 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
],
),
child: Icon(
_isHafalanPlaying ? Icons.stop_rounded : Icons.play_arrow_rounded,
_isHafalanPlaying ? LucideIcons.square : LucideIcons.play,
color: Colors.white,
size: 28,
),
@@ -1015,7 +1031,7 @@ class _QuranReadingScreenState extends ConsumerState<QuranReadingScreen> {
);
}).toList(),
onChanged: onChanged,
icon: const Icon(Icons.expand_more, size: 16),
icon: const Icon(LucideIcons.chevronDown, size: 16),
isDense: true,
borderRadius: BorderRadius.circular(12),
),

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:hive_flutter/hive_flutter.dart';
import '../../../app/theme/app_colors.dart';
import '../../../data/local/hive_boxes.dart';
@@ -9,7 +10,8 @@ import '../../../data/local/models/quran_bookmark.dart';
import '../../../data/services/equran_service.dart';
class QuranScreen extends ConsumerStatefulWidget {
const QuranScreen({super.key});
final bool isSimpleModeTab;
const QuranScreen({super.key, this.isSimpleModeTab = false});
@override
ConsumerState<QuranScreen> createState() => _QuranScreenState();
@@ -98,6 +100,8 @@ class _QuranScreenState extends ConsumerState<QuranScreen> {
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final box = Hive.box<AppSettings>(HiveBoxes.settings);
final isSimpleMode = box.get('default')?.simpleMode ?? false;
final filtered = _searchQuery.isEmpty
? _surahs
: _surahs
@@ -110,14 +114,15 @@ class _QuranScreenState extends ConsumerState<QuranScreen> {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: !widget.isSimpleModeTab,
title: const Text('Al-Quran'),
actions: [
IconButton(
icon: const Icon(Icons.bookmark_outline),
icon: const Icon(LucideIcons.bookmark),
onPressed: () => context.push('/tools/quran/bookmarks'),
),
IconButton(
icon: const Icon(Icons.settings_display),
icon: const Icon(LucideIcons.settings2),
onPressed: _showDisplaySettings,
),
],
@@ -141,7 +146,7 @@ class _QuranScreenState extends ConsumerState<QuranScreen> {
onChanged: (v) => setState(() => _searchQuery = v),
decoration: InputDecoration(
hintText: 'Cari surah...',
prefixIcon: Icon(Icons.search,
prefixIcon: Icon(LucideIcons.search,
color: isDark
? AppColors.textSecondaryDark
: AppColors.textSecondaryLight),
@@ -227,7 +232,7 @@ class _QuranScreenState extends ConsumerState<QuranScreen> {
),
if (hasLastRead) ...[
const SizedBox(width: 8),
const Icon(Icons.push_pin, size: 14, color: AppColors.primary),
const Icon(LucideIcons.pin, size: 14, color: AppColors.primary),
],
],
),

View File

@@ -1,6 +1,8 @@
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';
@@ -136,7 +138,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
),
IconButton(
onPressed: () => _showEditProfileDialog(context),
icon: Icon(Icons.edit,
icon: Icon(LucideIcons.pencil,
size: 20, color: AppColors.primary),
),
],
@@ -149,7 +151,22 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
const SizedBox(height: 12),
_settingRow(
isDark,
icon: Icons.dark_mode,
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(
@@ -160,7 +177,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
const SizedBox(height: 10),
_settingRow(
isDark,
icon: Icons.notifications,
icon: LucideIcons.bell,
iconColor: const Color(0xFFE17055),
title: 'Notifikasi',
trailing: IosToggle(
@@ -170,32 +187,32 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
),
const SizedBox(height: 24),
// ── CHECKLIST IBADAH ──
// ── CHECKLIST IBADAH (always visible, even in Simple Mode per user request) ──
_sectionLabel('CHECKLIST IBADAH'),
const SizedBox(height: 12),
_settingRow(
isDark,
icon: Icons.mosque_outlined,
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(Icons.chevron_right, size: 20),
trailing: const Icon(LucideIcons.chevronRight, size: 20),
onTap: () => _showRawatibDialog(context),
),
const SizedBox(height: 10),
_settingRow(
isDark,
icon: Icons.menu_book,
icon: LucideIcons.bookOpen,
iconColor: Colors.amber,
title: 'Target Tilawah',
subtitle: '${_settings.tilawahTargetValue} ${_settings.tilawahTargetUnit}',
trailing: const Icon(Icons.chevron_right, size: 20),
trailing: const Icon(LucideIcons.chevronRight, size: 20),
onTap: () => _showTilawahDialog(context),
),
const SizedBox(height: 10),
_settingRow(
isDark,
icon: Icons.sync,
icon: LucideIcons.refreshCw,
iconColor: Colors.blue,
title: 'Auto-Sync Tilawah',
subtitle: 'Catat otomatis dari menu Al-Quran',
@@ -218,11 +235,11 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
const SizedBox(height: 10),
_settingRow(
isDark,
icon: Icons.library_add_check,
icon: LucideIcons.listChecks,
iconColor: Colors.indigo,
title: 'Amalan Tambahan',
subtitle: 'Dzikir & Puasa Sunnah',
trailing: const Icon(Icons.chevron_right, size: 20),
trailing: const Icon(LucideIcons.chevronRight, size: 20),
onTap: () => _showAmalanDialog(context),
),
const SizedBox(height: 24),
@@ -232,31 +249,31 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
const SizedBox(height: 12),
_settingRow(
isDark,
icon: Icons.mosque,
icon: LucideIcons.building,
iconColor: AppColors.primary,
title: 'Metode Perhitungan',
subtitle: 'Kemenag RI',
trailing: const Icon(Icons.chevron_right, size: 20),
trailing: const Icon(LucideIcons.chevronRight, size: 20),
onTap: () => _showMethodDialog(context),
),
const SizedBox(height: 10),
_settingRow(
isDark,
icon: Icons.location_on,
icon: LucideIcons.mapPin,
iconColor: const Color(0xFF00B894),
title: 'Lokasi',
subtitle: _displayCityName,
trailing: const Icon(Icons.chevron_right, size: 20),
trailing: const Icon(LucideIcons.chevronRight, size: 20),
onTap: () => _showLocationDialog(context),
),
const SizedBox(height: 10),
_settingRow(
isDark,
icon: Icons.timer,
icon: LucideIcons.timer,
iconColor: const Color(0xFFFDAA5E),
title: 'Waktu Iqamah',
subtitle: 'Atur per waktu sholat',
trailing: const Icon(Icons.chevron_right, size: 20),
trailing: const Icon(LucideIcons.chevronRight, size: 20),
onTap: () => _showIqamahDialog(context),
),
const SizedBox(height: 24),
@@ -266,7 +283,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
const SizedBox(height: 12),
_settingRow(
isDark,
icon: Icons.text_fields,
icon: LucideIcons.type,
iconColor: const Color(0xFF636E72),
title: 'Ukuran Font Arab',
subtitle: '${_settings.arabicFontSize.round()}pt',
@@ -292,7 +309,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
const SizedBox(height: 12),
_settingRow(
isDark,
icon: Icons.info_outline,
icon: LucideIcons.info,
iconColor: AppColors.sage,
title: 'Versi Aplikasi',
subtitle: '1.0.0',
@@ -300,10 +317,10 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
const SizedBox(height: 10),
_settingRow(
isDark,
icon: Icons.favorite_outline,
icon: LucideIcons.heart,
iconColor: Colors.red,
title: 'Beri Nilai Kami',
trailing: const Icon(Icons.chevron_right, size: 20),
trailing: const Icon(LucideIcons.chevronRight, size: 20),
onTap: () {},
),
const SizedBox(height: 24),
@@ -324,7 +341,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.logout, color: Colors.red, size: 20),
Icon(LucideIcons.logOut, color: Colors.red, size: 20),
SizedBox(width: 8),
Text(
'Hapus Semua Data',
@@ -447,6 +464,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
final searchCtrl = TextEditingController();
bool isSearching = false;
List<Map<String, dynamic>> results = [];
Timer? debounce;
showDialog(
context: context,
@@ -466,7 +484,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
hintText: 'Cth: Jakarta',
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.search),
icon: const Icon(LucideIcons.search),
onPressed: () async {
if (searchCtrl.text.trim().isEmpty) return;
setDialogState(() => isSearching = true);
@@ -479,15 +497,45 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
},
),
),
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());
setDialogState(() {
results = res;
isSearching = false;
});
if (mounted) {
setDialogState(() {
results = res;
isSearching = false;
});
}
},
),
const SizedBox(height: 16),
@@ -670,7 +718,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
const Text('Sholat Rawatib', style: TextStyle(fontSize: 18)),
const Spacer(),
IconButton(
icon: const Icon(Icons.info_outline, color: AppColors.primary),
icon: const Icon(LucideIcons.info, color: AppColors.primary),
onPressed: () {
showModalBottomSheet(
context: context,

View File

@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../../app/theme/app_colors.dart';
import '../../../core/widgets/tool_card.dart';
import '../../../data/services/equran_service.dart';
class ToolsScreen extends ConsumerWidget {
@@ -18,11 +20,11 @@ class ToolsScreen extends ConsumerWidget {
actions: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.notifications_outlined),
icon: const Icon(LucideIcons.bell),
),
IconButton(
onPressed: () => context.push('/settings'),
icon: const Icon(Icons.settings_outlined),
icon: const Icon(LucideIcons.settings),
),
const SizedBox(width: 8),
],
@@ -45,22 +47,22 @@ class ToolsScreen extends ConsumerWidget {
Row(
children: [
Expanded(
child: _ToolCard(
icon: Icons.explore,
title: 'Arah\nKiblat',
color: AppColors.primary,
child: ToolCard(
icon: LucideIcons.bookOpen,
title: 'Al-Quran\nTerjemahan',
color: const Color(0xFF00b894),
isDark: isDark,
onTap: () => context.push('/tools/qibla'),
onTap: () => context.push('/quran'),
),
),
const SizedBox(width: 12),
Expanded(
child: _ToolCard(
icon: Icons.menu_book,
title: 'Baca\nQuran',
color: const Color(0xFF4A90D9),
child: ToolCard(
icon: LucideIcons.headphones,
title: 'Quran\nMurattal',
color: const Color(0xFF7B61FF),
isDark: isDark,
onTap: () => context.push('/tools/quran'),
onTap: () => context.push('/quran/1/murattal'),
),
),
],
@@ -69,22 +71,22 @@ class ToolsScreen extends ConsumerWidget {
Row(
children: [
Expanded(
child: _ToolCard(
icon: Icons.auto_awesome,
title: 'Penghitung\nDzikir',
color: const Color(0xFFE8A838),
child: ToolCard(
icon: LucideIcons.compass,
title: 'Arah\nKiblat',
color: const Color(0xFF0984E3),
isDark: isDark,
onTap: () => context.push('/tools/dzikir'),
onTap: () => context.push('/tools/qibla'),
),
),
const SizedBox(width: 12),
Expanded(
child: _ToolCard(
icon: Icons.headphones,
title: 'Quran\nMurattal',
color: const Color(0xFF7B61FF),
child: ToolCard(
icon: LucideIcons.sparkles,
title: 'Tasbih\nDigital',
color: AppColors.primary,
isDark: isDark,
onTap: () => context.push('/tools/quran/1/murattal'),
onTap: () => context.push('/dzikir'),
),
),
],
@@ -133,7 +135,7 @@ class ToolsScreen extends ConsumerWidget {
),
),
IconButton(
icon: Icon(Icons.share,
icon: Icon(LucideIcons.share2,
size: 18,
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight),
onPressed: () {},
@@ -183,69 +185,3 @@ class ToolsScreen extends ConsumerWidget {
);
}
}
class _ToolCard extends StatelessWidget {
final IconData icon;
final String title;
final Color color;
final bool isDark;
final VoidCallback onTap;
const _ToolCard({
required this.icon,
required this.title,
required this.color,
required this.isDark,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
height: 140,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: isDark
? color.withValues(alpha: 0.15)
: AppColors.cream,
),
boxShadow: [
BoxShadow(
color: color.withValues(alpha: 0.08),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: color.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 24),
),
Text(
title,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
height: 1.3,
),
),
],
),
),
);
}
}