Polish navigation, Quran flows, and sharing UX
This commit is contained in:
@@ -5,11 +5,10 @@ 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 '../../../core/widgets/notification_bell_button.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';
|
||||
|
||||
class LaporanScreen extends ConsumerStatefulWidget {
|
||||
const LaporanScreen({super.key});
|
||||
@@ -74,48 +73,60 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
|
||||
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) {
|
||||
daysChecked++;
|
||||
|
||||
|
||||
// Fardhu
|
||||
totalCounts['fardhu'] = (totalCounts['fardhu'] ?? 0) + 5;
|
||||
int completedFardhu = log.shalatLogs.values.where((l) => l.completed).length;
|
||||
completionCounts['fardhu'] = (completionCounts['fardhu'] ?? 0) + completedFardhu;
|
||||
int completedFardhu =
|
||||
log.shalatLogs.values.where((l) => l.completed).length;
|
||||
completionCounts['fardhu'] =
|
||||
(completionCounts['fardhu'] ?? 0) + completedFardhu;
|
||||
|
||||
// Rawatib
|
||||
int rawatibTotal = 0;
|
||||
int rawatibCompleted = 0;
|
||||
for (var sLog in log.shalatLogs.values) {
|
||||
if (sLog.qabliyah != null) { rawatibTotal++; if (sLog.qabliyah!) rawatibCompleted++; }
|
||||
if (sLog.badiyah != null) { rawatibTotal++; if (sLog.badiyah!) rawatibCompleted++; }
|
||||
if (sLog.qabliyah != null) {
|
||||
rawatibTotal++;
|
||||
if (sLog.qabliyah!) rawatibCompleted++;
|
||||
}
|
||||
if (sLog.badiyah != null) {
|
||||
rawatibTotal++;
|
||||
if (sLog.badiyah!) rawatibCompleted++;
|
||||
}
|
||||
}
|
||||
if (rawatibTotal > 0) {
|
||||
totalCounts['rawatib'] = (totalCounts['rawatib'] ?? 0) + rawatibTotal;
|
||||
completionCounts['rawatib'] = (completionCounts['rawatib'] ?? 0) + rawatibCompleted;
|
||||
totalCounts['rawatib'] = (totalCounts['rawatib'] ?? 0) + rawatibTotal;
|
||||
completionCounts['rawatib'] =
|
||||
(completionCounts['rawatib'] ?? 0) + rawatibCompleted;
|
||||
}
|
||||
|
||||
// Tilawah
|
||||
if (log.tilawahLog != null) {
|
||||
totalCounts['tilawah'] = (totalCounts['tilawah'] ?? 0) + 1;
|
||||
if (log.tilawahLog!.isCompleted) {
|
||||
completionCounts['tilawah'] = (completionCounts['tilawah'] ?? 0) + 1;
|
||||
}
|
||||
totalCounts['tilawah'] = (totalCounts['tilawah'] ?? 0) + 1;
|
||||
if (log.tilawahLog!.isCompleted) {
|
||||
completionCounts['tilawah'] =
|
||||
(completionCounts['tilawah'] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Dzikir
|
||||
if (log.dzikirLog != null) {
|
||||
totalCounts['dzikir'] = (totalCounts['dzikir'] ?? 0) + 2;
|
||||
int dCompleted = (log.dzikirLog!.pagi ? 1 : 0) + (log.dzikirLog!.petang ? 1 : 0);
|
||||
completionCounts['dzikir'] = (completionCounts['dzikir'] ?? 0) + dCompleted;
|
||||
totalCounts['dzikir'] = (totalCounts['dzikir'] ?? 0) + 2;
|
||||
int dCompleted =
|
||||
(log.dzikirLog!.pagi ? 1 : 0) + (log.dzikirLog!.petang ? 1 : 0);
|
||||
completionCounts['dzikir'] =
|
||||
(completionCounts['dzikir'] ?? 0) + dCompleted;
|
||||
}
|
||||
|
||||
// Puasa
|
||||
if (log.puasaLog != null) {
|
||||
totalCounts['puasa'] = (totalCounts['puasa'] ?? 0) + 1;
|
||||
if (log.puasaLog!.completed) {
|
||||
completionCounts['puasa'] = (completionCounts['puasa'] ?? 0) + 1;
|
||||
}
|
||||
totalCounts['puasa'] = (totalCounts['puasa'] ?? 0) + 1;
|
||||
if (log.puasaLog!.completed) {
|
||||
completionCounts['puasa'] = (completionCounts['puasa'] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,7 +181,7 @@ 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;
|
||||
|
||||
@@ -180,10 +191,7 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
|
||||
title: const Text('Riwayat Ibadah'),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(LucideIcons.bell),
|
||||
),
|
||||
const NotificationBellButton(),
|
||||
IconButton(
|
||||
onPressed: () => context.push('/settings'),
|
||||
icon: const Icon(LucideIcons.settings),
|
||||
@@ -204,10 +212,7 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
|
||||
title: const Text('Laporan Kualitas Ibadah'),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(LucideIcons.bell),
|
||||
),
|
||||
const NotificationBellButton(),
|
||||
IconButton(
|
||||
onPressed: () => context.push('/settings'),
|
||||
icon: const Icon(LucideIcons.settings),
|
||||
@@ -253,7 +258,8 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_buildWeeklyView(context, isDark, weekData, avgPercent, insights),
|
||||
_buildWeeklyView(
|
||||
context, isDark, weekData, avgPercent, insights),
|
||||
_buildComingSoon(context, 'Bulanan'),
|
||||
_buildComingSoon(context, 'Tahunan'),
|
||||
],
|
||||
@@ -332,57 +338,71 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
|
||||
const SizedBox(height: 20),
|
||||
// ── Bar Chart ──
|
||||
SizedBox(
|
||||
height: 140,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final maxPts = weekData.map((d) => d.value).fold<double>(0.0, (a, b) => a > b ? a : b).clamp(50.0, 300.0);
|
||||
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: weekData.map((d) {
|
||||
final ratio = (d.value / maxPts).clamp(0.05, 1.0);
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 120 * ratio,
|
||||
decoration: BoxDecoration(
|
||||
color: d.isToday
|
||||
? AppColors.primary
|
||||
: AppColors.primary
|
||||
.withValues(alpha: 0.3 + ratio * 0.4),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
height: 162,
|
||||
child: Builder(builder: (context) {
|
||||
final maxPts = weekData
|
||||
.map((d) => d.value)
|
||||
.fold<double>(0.0, (a, b) => a > b ? a : b)
|
||||
.clamp(50.0, 300.0);
|
||||
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: weekData.map((d) {
|
||||
final ratio = (d.value / maxPts).clamp(0.05, 1.0);
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'${d.value.round()}',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: d.isToday
|
||||
? AppColors.primary
|
||||
: (isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
d.label,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: d.isToday
|
||||
? FontWeight.w700
|
||||
: FontWeight.w400,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Flexible(
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 120 * ratio,
|
||||
decoration: BoxDecoration(
|
||||
color: d.isToday
|
||||
? AppColors.primary
|
||||
: (isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight),
|
||||
: AppColors.primary.withValues(
|
||||
alpha: 0.3 + ratio * 0.4),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
d.label,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: d.isToday
|
||||
? FontWeight.w700
|
||||
: FontWeight.w400,
|
||||
color: d.isToday
|
||||
? AppColors.primary
|
||||
: (isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -587,9 +607,11 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(LucideIcons.history, size: 64, color: AppColors.sage.withValues(alpha: 0.5)),
|
||||
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)),
|
||||
const Text('Belum ada riwayat ibadah',
|
||||
style: TextStyle(color: AppColors.sage)),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -602,10 +624,11 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
|
||||
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;
|
||||
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) {
|
||||
@@ -635,7 +658,8 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
|
||||
color: AppColors.primary.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(LucideIcons.checkCircle2, color: AppColors.primary),
|
||||
child: const Icon(LucideIcons.checkCircle2,
|
||||
color: AppColors.primary),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
@@ -643,7 +667,10 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
isToday ? 'Hari Ini' : DateFormat('EEEE, d MMM yyyy').format(DateTime.parse(log.date)),
|
||||
isToday
|
||||
? 'Hari Ini'
|
||||
: DateFormat('EEEE, d MMM yyyy')
|
||||
.format(DateTime.parse(log.date)),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 15,
|
||||
@@ -651,10 +678,14 @@ class _LaporanScreenState extends ConsumerState<LaporanScreen>
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
finished.isNotEmpty ? finished.join(' • ') : 'Belum ada aktivitas',
|
||||
finished.isNotEmpty
|
||||
? finished.join(' • ')
|
||||
: 'Belum ada aktivitas',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight,
|
||||
color: isDark
|
||||
? AppColors.textSecondaryDark
|
||||
: AppColors.textSecondaryLight,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user