- Murattal: Spotify-style 5-button controls [Shuffle, Prev, Play, Next, Playlist] - Murattal: Animated 7-bar equalizer visualization in player circle - Murattal: Unsplash API background with frosted glass player overlay - Murattal: Transparent AppBar with backdrop blur - Murattal: Surah playlist bottom sheet with full 114 Surah list - Murattal: Auto-play disabled on screen open, enabled on navigation - Murattal: Shuffle mode for random Surah playback - Murattal: Photographer attribution per Unsplash guidelines - Dashboard: Auto-scroll prayer schedule to next active prayer - Fix: setState lifecycle errors on Reading & Murattal screens - Setup: flutter_dotenv, cached_network_image, url_launcher deps
674 lines
23 KiB
Dart
674 lines
23 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 '../../../app/theme/app_colors.dart';
|
|
import '../../../core/widgets/prayer_time_card.dart';
|
|
import '../../../data/local/hive_boxes.dart';
|
|
import '../../../data/local/models/daily_worship_log.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;
|
|
Duration _countdown = Duration.zero;
|
|
String _nextPrayerName = '';
|
|
final ScrollController _prayerScrollController = ScrollController();
|
|
|
|
@override
|
|
void dispose() {
|
|
_countdownTimer?.cancel();
|
|
_prayerScrollController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _startCountdown(DaySchedule 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));
|
|
}
|
|
setState(() {
|
|
_nextPrayerName = next.name;
|
|
_countdown = target.difference(now);
|
|
if (_countdown.isNegative) _countdown = Duration.zero;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
final prayerTimesAsync = ref.watch(prayerTimesProvider);
|
|
|
|
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),
|
|
prayerTimesAsync.when(
|
|
data: (schedule) {
|
|
if (schedule != null) {
|
|
_startCountdown(schedule);
|
|
return _buildHeroCard(context, schedule);
|
|
}
|
|
return _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),
|
|
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(Icons.person, 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(
|
|
Icons.notifications_outlined,
|
|
color: isDark
|
|
? AppColors.textSecondaryDark
|
|
: AppColors.textSecondaryLight,
|
|
),
|
|
),
|
|
IconButton(
|
|
onPressed: () => context.push('/settings'),
|
|
icon: Icon(
|
|
Icons.settings_outlined,
|
|
color: isDark
|
|
? AppColors.textSecondaryDark
|
|
: AppColors.textSecondaryLight,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildHeroCard(BuildContext context, DaySchedule schedule) {
|
|
final next = schedule.nextPrayer;
|
|
final name = _nextPrayerName.isNotEmpty
|
|
? _nextPrayerName
|
|
: (next?.name ?? 'Isya');
|
|
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(Icons.schedule,
|
|
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),
|
|
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),
|
|
),
|
|
),
|
|
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(Icons.explore, 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(
|
|
Icons.volume_up,
|
|
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: 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) {
|
|
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 Icons.wb_twilight;
|
|
case 'Dzuhur':
|
|
return Icons.wb_sunny;
|
|
case 'Ashar':
|
|
return Icons.filter_drama;
|
|
case 'Maghrib':
|
|
return Icons.wb_twilight;
|
|
case 'Isya':
|
|
return Icons.dark_mode;
|
|
default:
|
|
return Icons.schedule;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
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 ? Icons.check_circle : Icons.radio_button_unchecked,
|
|
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),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|