Polish navigation, Quran flows, and sharing UX
This commit is contained in:
@@ -6,6 +6,7 @@ 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/notification_bell_button.dart';
|
||||
import '../../../data/local/hive_boxes.dart';
|
||||
import '../../../data/local/models/app_settings.dart';
|
||||
import '../../../data/services/prayer_service.dart';
|
||||
@@ -56,8 +57,7 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
|
||||
|
||||
List<_DayRow> _createRows(Map<String, Map<String, String>>? apiData) {
|
||||
final selected = _months[_selectedMonthIndex];
|
||||
final daysInMonth =
|
||||
DateTime(selected.year, selected.month + 1, 0).day;
|
||||
final daysInMonth = DateTime(selected.year, selected.month + 1, 0).day;
|
||||
final rows = <_DayRow>[];
|
||||
|
||||
for (int d = 1; d <= daysInMonth; d++) {
|
||||
@@ -102,7 +102,8 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
|
||||
context: context,
|
||||
builder: (ctx) => StatefulBuilder(
|
||||
builder: (ctx, setDialogState) => AlertDialog(
|
||||
insetPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24),
|
||||
insetPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 24),
|
||||
title: const Text('Cari Kota/Kabupaten'),
|
||||
content: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.85,
|
||||
@@ -123,7 +124,7 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
|
||||
final res = await MyQuranSholatService.instance
|
||||
.searchCity(searchCtrl.text.trim());
|
||||
if (mounted) {
|
||||
setDialogState(() {
|
||||
setDialogState(() {
|
||||
results = res;
|
||||
isSearching = false;
|
||||
});
|
||||
@@ -133,21 +134,23 @@ 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 {
|
||||
debounce =
|
||||
Timer(const Duration(milliseconds: 500), () async {
|
||||
if (!mounted) return;
|
||||
setDialogState(() => isSearching = true);
|
||||
|
||||
|
||||
try {
|
||||
final res = await MyQuranSholatService.instance.searchCity(val.trim());
|
||||
final res = await MyQuranSholatService.instance
|
||||
.searchCity(val.trim());
|
||||
if (mounted) {
|
||||
setDialogState(() {
|
||||
results = res;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Error searching city: $e');
|
||||
debugPrint('Error searching city: $e');
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setDialogState(() {
|
||||
@@ -175,7 +178,8 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
|
||||
if (isSearching)
|
||||
const Center(child: CircularProgressIndicator())
|
||||
else if (results.isEmpty)
|
||||
const Text('Tidak ada hasil', style: TextStyle(color: Colors.grey))
|
||||
const Text('Tidak ada hasil',
|
||||
style: TextStyle(color: Colors.grey))
|
||||
else
|
||||
SizedBox(
|
||||
height: 200,
|
||||
@@ -193,11 +197,11 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
|
||||
if (id != null && name != null) {
|
||||
_settings.lastCityName = '$name|$id';
|
||||
_settings.save();
|
||||
|
||||
|
||||
// Update providers to refresh data
|
||||
ref.invalidate(selectedCityIdProvider);
|
||||
ref.invalidate(cityNameProvider);
|
||||
|
||||
|
||||
Navigator.pop(ctx);
|
||||
}
|
||||
},
|
||||
@@ -224,9 +228,11 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
|
||||
final theme = Theme.of(context);
|
||||
final isDark = theme.brightness == Brightness.dark;
|
||||
final today = DateTime.now();
|
||||
|
||||
const tableBottomSpacing = 28.0;
|
||||
|
||||
final selectedMonth = _months[_selectedMonthIndex];
|
||||
final monthArg = '${selectedMonth.year}-${selectedMonth.month.toString().padLeft(2, '0')}';
|
||||
final monthArg =
|
||||
'${selectedMonth.year}-${selectedMonth.month.toString().padLeft(2, '0')}';
|
||||
final cityNameAsync = ref.watch(cityNameProvider);
|
||||
final monthlyDataAsync = ref.watch(monthlyScheduleProvider(monthArg));
|
||||
|
||||
@@ -235,10 +241,7 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
|
||||
title: const Text('Kalender Sholat'),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(LucideIcons.bell),
|
||||
),
|
||||
const NotificationBellButton(),
|
||||
IconButton(
|
||||
onPressed: () => context.push('/settings'),
|
||||
icon: const Icon(LucideIcons.settings),
|
||||
@@ -266,7 +269,9 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? AppColors.primary
|
||||
: (isDark ? AppColors.surfaceDark : AppColors.surfaceLight),
|
||||
: (isDark
|
||||
? AppColors.surfaceDark
|
||||
: AppColors.surfaceLight),
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
border: isSelected
|
||||
? null
|
||||
@@ -306,7 +311,8 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight,
|
||||
color:
|
||||
isDark ? AppColors.surfaceDark : AppColors.surfaceLight,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
@@ -314,40 +320,40 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
|
||||
: AppColors.cream,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(LucideIcons.mapPin,
|
||||
color: AppColors.primary, size: 24),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Lokasi Anda',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(LucideIcons.mapPin,
|
||||
color: AppColors.primary, size: 24),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Lokasi Anda',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
cityNameAsync.value ?? 'Jakarta, Indonesia',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600, fontSize: 15),
|
||||
),
|
||||
],
|
||||
Text(
|
||||
cityNameAsync.value ?? 'Jakarta, Indonesia',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600, fontSize: 15),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Icon(LucideIcons.chevronDown,
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight),
|
||||
],
|
||||
Icon(LucideIcons.chevronDown,
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ── Table Header ──
|
||||
@@ -378,7 +384,12 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
|
||||
data: (apiData) {
|
||||
final rows = _createRows(apiData);
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
16,
|
||||
0,
|
||||
16,
|
||||
tableBottomSpacing,
|
||||
),
|
||||
itemCount: rows.length,
|
||||
itemBuilder: (context, i) {
|
||||
final row = rows[i];
|
||||
@@ -453,7 +464,12 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
|
||||
error: (_, __) {
|
||||
final rows = _createRows(null); // fallback
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
16,
|
||||
0,
|
||||
16,
|
||||
tableBottomSpacing,
|
||||
),
|
||||
itemCount: rows.length,
|
||||
itemBuilder: (context, i) {
|
||||
final row = rows[i];
|
||||
@@ -536,7 +552,7 @@ class _ImsakiyahScreenState extends ConsumerState<ImsakiyahScreen> {
|
||||
child: Center(
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
style: const TextStyle(
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 1,
|
||||
|
||||
Reference in New Issue
Block a user