Files
jamshalat-diary/lib/features/dzikir/presentation/dzikir_screen.dart

313 lines
11 KiB
Dart

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 {
final bool isSimpleModeTab;
const DzikirScreen({super.key, this.isSimpleModeTab = false});
@override
ConsumerState<DzikirScreen> createState() => _DzikirScreenState();
}
class _DzikirScreenState extends ConsumerState<DzikirScreen>
with SingleTickerProviderStateMixin {
late TabController _tabController;
List<Map<String, dynamic>> _pagiItems = [];
List<Map<String, dynamic>> _petangItems = [];
late Box<DzikirCounter> _counterBox;
late String _todayKey;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
_counterBox = Hive.box<DzikirCounter>(HiveBoxes.dzikirCounters);
_todayKey = DateFormat('yyyy-MM-dd').format(DateTime.now());
_loadData();
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
Future<void> _loadData() async {
final pagiJson =
await rootBundle.loadString('assets/dzikir/dzikir_pagi.json');
final petangJson =
await rootBundle.loadString('assets/dzikir/dzikir_petang.json');
setState(() {
_pagiItems = List<Map<String, dynamic>>.from(json.decode(pagiJson));
_petangItems = List<Map<String, dynamic>>.from(json.decode(petangJson));
});
}
DzikirCounter _getCounter(String dzikirId, int target) {
final key = '${dzikirId}_$_todayKey';
return _counterBox.get(key) ??
DzikirCounter(
dzikirId: dzikirId,
date: _todayKey,
count: 0,
target: target,
);
}
void _increment(String dzikirId, int target) {
final key = '${dzikirId}_$_todayKey';
var counter = _counterBox.get(key);
if (counter == null) {
counter = DzikirCounter(
dzikirId: dzikirId,
date: _todayKey,
count: 1,
target: target,
);
_counterBox.put(key, counter);
} else {
if (counter.count < counter.target) {
counter.count++;
counter.save();
}
}
setState(() {});
// Haptic feedback
HapticFeedback.lightImpact();
}
@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(LucideIcons.info),
),
],
),
body: Column(
children: [
// Tabs
Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
child: TabBar(
controller: _tabController,
labelColor: AppColors.primary,
unselectedLabelColor: isDark
? AppColors.textSecondaryDark
: AppColors.textSecondaryLight,
indicatorColor: AppColors.primary,
indicatorWeight: 3,
labelStyle:
const TextStyle(fontWeight: FontWeight.w700, fontSize: 14),
tabs: const [
Tab(text: 'Pagi'),
Tab(text: 'Petang'),
],
),
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
_buildDzikirList(context, isDark, _pagiItems, 'pagi',
'Dzikir Pagi', 'Dibaca setelah shalat Shubuh hingga terbit matahari'),
_buildDzikirList(context, isDark, _petangItems, 'petang',
'Dzikir Petang', 'Dibaca setelah shalat Ashar hingga terbenam matahari'),
],
),
),
],
),
);
}
Widget _buildDzikirList(BuildContext context, bool isDark,
List<Map<String, dynamic>> items, String prefix, String title, String subtitle) {
if (items.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: items.length + 1, // +1 for header
itemBuilder: (context, index) {
if (index == 0) {
return Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Column(
children: [
Text(title,
style: const TextStyle(
fontSize: 22, fontWeight: FontWeight.w800)),
const SizedBox(height: 4),
Text(
subtitle,
textAlign: TextAlign.center,
style: TextStyle(
color: isDark
? AppColors.textSecondaryDark
: AppColors.textSecondaryLight,
fontSize: 13,
),
),
],
),
);
}
final item = items[index - 1];
final dzikirId = '${prefix}_${item['id']}';
final target = (item['count'] as num?)?.toInt() ?? 1;
final counter = _getCounter(dzikirId, target);
final isComplete = counter.count >= counter.target;
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: isDark ? AppColors.surfaceDark : AppColors.surfaceLight,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isComplete
? AppColors.primary.withValues(alpha: 0.3)
: (isDark
? AppColors.primary.withValues(alpha: 0.08)
: AppColors.cream),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header row: count badge + number
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: AppColors.primary.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(50),
),
child: Text(
'$target KALI',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w700,
color: AppColors.primary,
),
),
),
Text(
'${(index).toString().padLeft(2, '0')}',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: isDark
? AppColors.textSecondaryDark
: AppColors.textSecondaryLight,
),
),
],
),
const SizedBox(height: 16),
// Arabic text
SizedBox(
width: double.infinity,
child: Text(
item['arabic'] ?? '',
textAlign: TextAlign.right,
style: const TextStyle(
fontFamily: 'Amiri',
fontSize: 24,
height: 2.0,
),
),
),
const SizedBox(height: 12),
// Transliteration
Text(
item['transliteration'] ?? '',
style: TextStyle(
fontSize: 13,
fontStyle: FontStyle.italic,
color: AppColors.primary,
),
),
const SizedBox(height: 8),
// Translation
Text(
'"${item['translation'] ?? ''}"',
style: TextStyle(
fontSize: 13,
color: isDark
? AppColors.textSecondaryDark
: AppColors.textSecondaryLight,
),
),
const SizedBox(height: 16),
// Counter button
GestureDetector(
onTap: () => _increment(dzikirId, target),
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 14),
decoration: BoxDecoration(
color: isComplete
? AppColors.primary.withValues(alpha: 0.15)
: AppColors.primary,
borderRadius: BorderRadius.circular(50),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
isComplete ? LucideIcons.check : LucideIcons.fingerprint,
size: 18,
color: isComplete
? AppColors.primary
: AppColors.onPrimary,
),
const SizedBox(width: 8),
Text(
'${counter.count} / $target',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
color: isComplete
? AppColors.primary
: AppColors.onPrimary,
),
),
],
),
),
),
],
),
),
);
},
);
}
}