904 lines
31 KiB
Dart
904 lines
31 KiB
Dart
import 'dart:async';
|
|
import 'package:flutter/material.dart';
|
|
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 {
|
|
const DashboardScreen({super.key});
|
|
|
|
@override
|
|
ConsumerState<DashboardScreen> createState() => _DashboardScreenState();
|
|
}
|
|
|
|
class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
|
Timer? _countdownTimer;
|
|
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), (_) {
|
|
_updateCountdown(schedule);
|
|
});
|
|
}
|
|
|
|
void _updateCountdown(DaySchedule schedule) {
|
|
final next = schedule.nextPrayer;
|
|
if (next != null && next.time != '-') {
|
|
final parts = next.time.split(':');
|
|
if (parts.length == 2) {
|
|
final now = DateTime.now();
|
|
var target = DateTime(now.year, now.month, now.day,
|
|
int.parse(parts[0]), int.parse(parts[1]));
|
|
if (target.isBefore(now)) {
|
|
target = target.add(const Duration(days: 1));
|
|
}
|
|
_nextPrayerName.value = next.name;
|
|
final diff = target.difference(now);
|
|
_countdown.value = diff.isNegative ? Duration.zero : diff;
|
|
}
|
|
}
|
|
}
|
|
|
|
String _formatCountdown(Duration d) {
|
|
final h = d.inHours.toString().padLeft(2, '0');
|
|
final m = (d.inMinutes % 60).toString().padLeft(2, '0');
|
|
final s = (d.inSeconds % 60).toString().padLeft(2, '0');
|
|
return '$h:$m:$s';
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final isDark = theme.brightness == Brightness.dark;
|
|
|
|
ref.listen<AsyncValue<DaySchedule?>>(prayerTimesProvider, (previous, next) {
|
|
next.whenData((schedule) {
|
|
if (schedule != null) {
|
|
_startCountdown(schedule);
|
|
}
|
|
});
|
|
});
|
|
|
|
return Scaffold(
|
|
body: SafeArea(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const SizedBox(height: 8),
|
|
_buildHeader(context, isDark),
|
|
const SizedBox(height: 20),
|
|
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),
|
|
);
|
|
},
|
|
),
|
|
const SizedBox(height: 24),
|
|
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),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildHeader(BuildContext context, bool isDark) {
|
|
return Row(
|
|
children: [
|
|
Container(
|
|
width: 40,
|
|
height: 40,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
border: Border.all(color: AppColors.primary, width: 2),
|
|
color: AppColors.primary.withValues(alpha: 0.2),
|
|
),
|
|
child: const Icon(LucideIcons.user, size: 20, color: AppColors.primary),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Selamat datang,',
|
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
color: isDark
|
|
? AppColors.textSecondaryDark
|
|
: AppColors.textSecondaryLight,
|
|
),
|
|
),
|
|
Text(
|
|
"Assalamu'alaikum",
|
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Row(
|
|
children: [
|
|
IconButton(
|
|
onPressed: () {},
|
|
icon: Icon(
|
|
LucideIcons.bell,
|
|
color: isDark
|
|
? AppColors.textSecondaryDark
|
|
: AppColors.textSecondaryLight,
|
|
),
|
|
),
|
|
IconButton(
|
|
onPressed: () => context.push('/settings'),
|
|
icon: Icon(
|
|
LucideIcons.settings,
|
|
color: isDark
|
|
? AppColors.textSecondaryDark
|
|
: AppColors.textSecondaryLight,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildHeroCard(BuildContext context, DaySchedule schedule) {
|
|
final next = schedule.nextPrayer;
|
|
final time = next?.time ?? '--:--';
|
|
|
|
return Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.primary,
|
|
borderRadius: BorderRadius.circular(24),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: AppColors.primary.withValues(alpha: 0.3),
|
|
blurRadius: 20,
|
|
offset: const Offset(0, 8),
|
|
),
|
|
],
|
|
),
|
|
child: Stack(
|
|
children: [
|
|
Positioned(
|
|
top: -20,
|
|
right: -20,
|
|
child: Container(
|
|
width: 120,
|
|
height: 120,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
color: Colors.white.withValues(alpha: 0.15),
|
|
),
|
|
),
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(LucideIcons.clock,
|
|
size: 16,
|
|
color: AppColors.onPrimary.withValues(alpha: 0.8)),
|
|
const SizedBox(width: 6),
|
|
Text(
|
|
'SHOLAT BERIKUTNYA',
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w700,
|
|
letterSpacing: 1.5,
|
|
color: AppColors.onPrimary.withValues(alpha: 0.8),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
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),
|
|
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
|
|
Text(
|
|
'📍 ${schedule.cityName}',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
color: AppColors.onPrimary.withValues(alpha: 0.7),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: GestureDetector(
|
|
onTap: () => context.push('/tools/qibla'),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.onPrimary,
|
|
borderRadius: BorderRadius.circular(50),
|
|
),
|
|
child: const Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(LucideIcons.compass, size: 18, color: Colors.white),
|
|
SizedBox(width: 8),
|
|
Text(
|
|
'Arah Kiblat',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Container(
|
|
width: 48,
|
|
height: 48,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withValues(alpha: 0.2),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: const Icon(
|
|
LucideIcons.volume2,
|
|
color: AppColors.onPrimary,
|
|
size: 22,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildHeroCardPlaceholder(BuildContext context) {
|
|
return Container(
|
|
width: double.infinity,
|
|
height: 180,
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.primary,
|
|
borderRadius: BorderRadius.circular(24),
|
|
),
|
|
child: const Center(
|
|
child: CircularProgressIndicator(color: AppColors.onPrimary),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPrayerTimesSection(
|
|
BuildContext context, AsyncValue<DaySchedule?> prayerTimesAsync) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
prayerTimesAsync.value?.isTomorrow == true
|
|
? 'Jadwal Sholat Besok'
|
|
: 'Jadwal Sholat Hari Ini',
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleMedium
|
|
?.copyWith(fontWeight: FontWeight.w700)),
|
|
Container(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.primary.withValues(alpha: 0.1),
|
|
borderRadius: BorderRadius.circular(50),
|
|
),
|
|
child: Text(
|
|
prayerTimesAsync.value?.isTomorrow == true ? 'BESOK' : 'HARI INI',
|
|
style: const TextStyle(
|
|
color: AppColors.primary,
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.w700,
|
|
letterSpacing: 1.5,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
SizedBox(
|
|
height: 110,
|
|
child: prayerTimesAsync.when(
|
|
data: (schedule) {
|
|
if (schedule == null) return const SizedBox();
|
|
final prayers = schedule.prayerList.where(
|
|
(p) => ['Subuh', 'Dzuhur', 'Ashar', 'Maghrib', 'Isya']
|
|
.contains(p.name),
|
|
).toList();
|
|
return ListView.separated(
|
|
controller: _prayerScrollController,
|
|
scrollDirection: Axis.horizontal,
|
|
itemCount: prayers.length,
|
|
separatorBuilder: (_, __) => const SizedBox(width: 12),
|
|
itemBuilder: (context, i) {
|
|
final p = prayers[i];
|
|
final icon = _prayerIcon(p.name);
|
|
// Auto-scroll to active prayer on first build
|
|
if (p.isActive && i > 0 && !_hasAutoScrolled) {
|
|
_hasAutoScrolled = true;
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (_prayerScrollController.hasClients) {
|
|
final targetOffset = i * 124.0; // 112 width + 12 gap
|
|
_prayerScrollController.animateTo(
|
|
targetOffset.clamp(0, _prayerScrollController.position.maxScrollExtent),
|
|
duration: const Duration(milliseconds: 400),
|
|
curve: Curves.easeOut,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
return PrayerTimeCard(
|
|
prayerName: p.name,
|
|
time: p.time,
|
|
icon: icon,
|
|
isActive: p.isActive,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
loading: () =>
|
|
const Center(child: CircularProgressIndicator()),
|
|
error: (_, __) =>
|
|
const Center(child: Text('Gagal memuat jadwal')),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
IconData _prayerIcon(String name) {
|
|
switch (name) {
|
|
case 'Subuh':
|
|
return LucideIcons.sunrise;
|
|
case 'Dzuhur':
|
|
return LucideIcons.sun;
|
|
case 'Ashar':
|
|
return LucideIcons.cloudSun;
|
|
case 'Maghrib':
|
|
return LucideIcons.sunset;
|
|
case 'Isya':
|
|
return LucideIcons.moon;
|
|
default:
|
|
return LucideIcons.clock;
|
|
}
|
|
}
|
|
|
|
Widget _buildChecklistSummary(BuildContext context, bool isDark) {
|
|
final todayKey = DateFormat('yyyy-MM-dd').format(DateTime.now());
|
|
final box = Hive.box<DailyWorshipLog>(HiveBoxes.worshipLogs);
|
|
final log = box.get(todayKey);
|
|
|
|
final points = log?.totalPoints ?? 0;
|
|
// We can assume a max "excellent" day is around 150 points for the progress ring scale
|
|
final percent = (points / 150).clamp(0.0, 1.0);
|
|
|
|
// Prepare dynamic preview lines
|
|
int fardhuCompleted = 0;
|
|
if (log != null) {
|
|
fardhuCompleted = log.shalatLogs.values.where((l) => l.completed).length;
|
|
}
|
|
|
|
String amalanText = 'Belum ada data';
|
|
if (log != null) {
|
|
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');
|
|
if (aList.isNotEmpty) {
|
|
amalanText = aList.join(', ');
|
|
}
|
|
}
|
|
|
|
return Container(
|
|
padding: const EdgeInsets.all(20),
|
|
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: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Poin Ibadah Hari Ini',
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleMedium
|
|
?.copyWith(fontWeight: FontWeight.w700)),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'Kumpulkan poin dengan konsisten!',
|
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
color: isDark
|
|
? AppColors.textSecondaryDark
|
|
: AppColors.textSecondaryLight,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(
|
|
width: 48,
|
|
height: 48,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
CircularProgressIndicator(
|
|
value: percent,
|
|
strokeWidth: 4,
|
|
backgroundColor:
|
|
AppColors.primary.withValues(alpha: 0.15),
|
|
valueColor: const AlwaysStoppedAnimation<Color>(
|
|
AppColors.primary),
|
|
),
|
|
Text(
|
|
'$points',
|
|
style: const TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w800,
|
|
color: AppColors.primary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
_checklistPreviewItem(
|
|
context, isDark, 'Sholat Fardhu', '$fardhuCompleted dari 5 selesai', fardhuCompleted == 5),
|
|
const SizedBox(height: 8),
|
|
_checklistPreviewItem(
|
|
context, isDark, 'Amalan Selesai', amalanText, points > 50),
|
|
const SizedBox(height: 16),
|
|
GestureDetector(
|
|
onTap: () => context.go('/checklist'),
|
|
child: Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.primary.withValues(alpha: 0.1),
|
|
borderRadius: BorderRadius.circular(50),
|
|
),
|
|
child: const Center(
|
|
child: Text(
|
|
'Lihat Semua Checklist',
|
|
style: TextStyle(
|
|
color: AppColors.primary,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _checklistPreviewItem(BuildContext context, bool isDark, String title,
|
|
String subtitle, bool completed) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: isDark
|
|
? AppColors.primary.withValues(alpha: 0.05)
|
|
: AppColors.backgroundLight,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
completed ? LucideIcons.checkCircle2 : LucideIcons.circle,
|
|
color: AppColors.primary,
|
|
size: 22,
|
|
),
|
|
const SizedBox(width: 12),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(title,
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.bodyMedium
|
|
?.copyWith(fontWeight: FontWeight.w600)),
|
|
Text(subtitle,
|
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
color: isDark
|
|
? AppColors.textSecondaryDark
|
|
: AppColors.textSecondaryLight,
|
|
)),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildWeeklyProgress(BuildContext context, bool isDark) {
|
|
final box = Hive.box<DailyWorshipLog>(HiveBoxes.worshipLogs);
|
|
final now = DateTime.now();
|
|
|
|
// Reverse so today is on the far right (index 6)
|
|
final last7Days = List.generate(7, (i) => now.subtract(Duration(days: 6 - i)));
|
|
final daysLabels = ['Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab', 'Min'];
|
|
|
|
final weekPoints = <int>[];
|
|
for (final d in last7Days) {
|
|
final k = DateFormat('yyyy-MM-dd').format(d);
|
|
final l = box.get(k);
|
|
weekPoints.add(l?.totalPoints ?? 0);
|
|
}
|
|
|
|
// Find the max points acquired this week to scale the bars, with a minimum floor of 50
|
|
final maxPts = weekPoints.reduce((a, b) => a > b ? a : b).clamp(50, 300);
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Progres Poin Mingguan',
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleMedium
|
|
?.copyWith(fontWeight: FontWeight.w700)),
|
|
const SizedBox(height: 12),
|
|
Container(
|
|
padding: const EdgeInsets.all(20),
|
|
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(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: List.generate(7, (i) {
|
|
final val = weekPoints[i];
|
|
final ratio = (val / maxPts).clamp(0.1, 1.0);
|
|
|
|
return Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
SizedBox(
|
|
height: 80,
|
|
child: Align(
|
|
alignment: Alignment.bottomCenter,
|
|
child: Container(
|
|
width: 24,
|
|
height: 80 * ratio,
|
|
decoration: BoxDecoration(
|
|
color: val > 0
|
|
? AppColors.primary.withValues(
|
|
alpha: 0.2 + ratio * 0.8)
|
|
: AppColors.primary.withValues(alpha: 0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
daysLabels[last7Days[i].weekday - 1], // Correct localized day
|
|
style: TextStyle(
|
|
fontSize: 10,
|
|
fontWeight: i == 6 ? FontWeight.w800 : FontWeight.w600,
|
|
color: i == 6
|
|
? AppColors.primary
|
|
: (isDark ? AppColors.textSecondaryDark : AppColors.textSecondaryLight),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|