1698 lines
71 KiB
Dart
1698 lines
71 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import 'package:google_fonts/google_fonts.dart';
|
||
import 'package:hugeicons/hugeicons.dart';
|
||
|
||
import '../../core/sacred_tokens.dart';
|
||
import '../../providers.dart';
|
||
import '../../data/services/sync_service.dart';
|
||
import '../../data/services/myquran_service.dart';
|
||
import 'package:file_picker/file_picker.dart';
|
||
import 'dart:io';
|
||
|
||
class AdminScreen extends ConsumerStatefulWidget {
|
||
const AdminScreen({super.key});
|
||
|
||
@override
|
||
ConsumerState<AdminScreen> createState() => _AdminScreenState();
|
||
}
|
||
|
||
class _AdminScreenState extends ConsumerState<AdminScreen> {
|
||
final _masjidNameCtrl = TextEditingController();
|
||
final _masjidAddressCtrl = TextEditingController();
|
||
final _cityCtrl = TextEditingController(); // Displays DisplayName or CityID
|
||
|
||
final _mainDurCtrl = TextEditingController();
|
||
final _slideDurCtrl = TextEditingController();
|
||
|
||
int _selectedTab = 0;
|
||
bool _isSyncing = false;
|
||
int _textScaleIndex = 1;
|
||
List<String> _slideshowImages = [];
|
||
bool _useUnsplash = false;
|
||
final _unsplashKeywordCtrl = TextEditingController();
|
||
final _unsplashRotationCtrl = TextEditingController();
|
||
|
||
// Branded background
|
||
String? _brandedBgImage;
|
||
|
||
// Running text repeater
|
||
String _marqueeAnimType = 'marquee';
|
||
List<String> _runningTexts = [];
|
||
List<int> _runningTextDurations = [];
|
||
|
||
// Granular text group scales
|
||
double _scaleCardLabel = 1.0;
|
||
double _scaleCardBody = 1.0;
|
||
double _scaleRunningText = 1.0;
|
||
|
||
// Jumat fields
|
||
final _khatibCtrl = TextEditingController();
|
||
final _imamCtrl = TextEditingController();
|
||
|
||
// Iqomah Jeda fields
|
||
final _iqomahSubuhCtrl = TextEditingController();
|
||
final _iqomahDzuhurCtrl = TextEditingController();
|
||
final _iqomahAsharCtrl = TextEditingController();
|
||
final _iqomahMaghribCtrl = TextEditingController();
|
||
final _iqomahIsyaCtrl = TextEditingController();
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
final settings = ref.read(settingsProvider);
|
||
_masjidNameCtrl.text = settings.masjidName;
|
||
_masjidAddressCtrl.text = settings.masjidAddress;
|
||
_cityCtrl.text = '${settings.cityDisplayName} (${settings.cityIdApi})';
|
||
_mainDurCtrl.text = settings.mainScreenDurationSec.toString();
|
||
_slideDurCtrl.text = settings.slideDurationSec.toString();
|
||
_textScaleIndex = settings.textScaleIndex;
|
||
_slideshowImages = List.from(settings.slideshowImages);
|
||
_useUnsplash = settings.useUnsplashBackground;
|
||
_unsplashKeywordCtrl.text = settings.unsplashKeyword;
|
||
_unsplashRotationCtrl.text = settings.unsplashRotationHours.toString();
|
||
_brandedBgImage = settings.brandedBgImage;
|
||
_marqueeAnimType = settings.marqueeAnimType;
|
||
_runningTexts = List.from(settings.runningTexts);
|
||
_runningTextDurations = List.from(
|
||
settings.runningTextDurations.isNotEmpty
|
||
? settings.runningTextDurations
|
||
: List.filled(settings.runningTexts.length, 12),
|
||
);
|
||
// Ensure durations list length matches texts
|
||
while (_runningTextDurations.length < _runningTexts.length) {
|
||
_runningTextDurations.add(12);
|
||
}
|
||
_scaleCardLabel = settings.scaleCardLabel;
|
||
_scaleCardBody = settings.scaleCardBody;
|
||
_scaleRunningText = settings.scaleRunningText;
|
||
_khatibCtrl.text = settings.khatibName;
|
||
_imamCtrl.text = settings.imamName;
|
||
|
||
_iqomahSubuhCtrl.text = settings.iqomahSubuh.toString();
|
||
_iqomahDzuhurCtrl.text = settings.iqomahDzuhur.toString();
|
||
_iqomahAsharCtrl.text = settings.iqomahAshar.toString();
|
||
_iqomahMaghribCtrl.text = settings.iqomahMaghrib.toString();
|
||
_iqomahIsyaCtrl.text = settings.iqomahIsya.toString();
|
||
|
||
// Update preview live as admin types
|
||
_khatibCtrl.addListener(() => setState(() {}));
|
||
_imamCtrl.addListener(() => setState(() {}));
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_masjidNameCtrl.dispose();
|
||
_masjidAddressCtrl.dispose();
|
||
_cityCtrl.dispose();
|
||
_mainDurCtrl.dispose();
|
||
_slideDurCtrl.dispose();
|
||
_unsplashKeywordCtrl.dispose();
|
||
_unsplashRotationCtrl.dispose();
|
||
_khatibCtrl.dispose();
|
||
_imamCtrl.dispose();
|
||
_iqomahSubuhCtrl.dispose();
|
||
_iqomahDzuhurCtrl.dispose();
|
||
_iqomahAsharCtrl.dispose();
|
||
_iqomahMaghribCtrl.dispose();
|
||
_iqomahIsyaCtrl.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
Future<void> _saveIdentity() async {
|
||
await ref.read(settingsProvider.notifier).updateSettings((s) {
|
||
s.masjidName = _masjidNameCtrl.text.trim();
|
||
s.masjidAddress = _masjidAddressCtrl.text.trim();
|
||
// cityId is saved instantly when selected from dialog
|
||
return s;
|
||
});
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text('Pengaturan berhasil disimpan',
|
||
style: GoogleFonts.manrope()),
|
||
backgroundColor: SacredColors.primaryContainer,
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
Future<void> _saveTampilan() async {
|
||
await ref.read(settingsProvider.notifier).updateSettings((s) {
|
||
s.textScaleIndex = _textScaleIndex;
|
||
s.slideshowImages = List.from(_slideshowImages);
|
||
s.mainScreenDurationSec = int.tryParse(_mainDurCtrl.text.trim()) ?? 15;
|
||
s.slideDurationSec = int.tryParse(_slideDurCtrl.text.trim()) ?? 10;
|
||
s.useUnsplashBackground = _useUnsplash;
|
||
s.unsplashKeyword = _unsplashKeywordCtrl.text.trim().isEmpty ? 'mosque' : _unsplashKeywordCtrl.text.trim();
|
||
s.unsplashRotationHours = int.tryParse(_unsplashRotationCtrl.text.trim()) ?? 6;
|
||
s.brandedBgImage = _brandedBgImage;
|
||
s.runningTexts = List.from(_runningTexts);
|
||
s.runningTextDurations = List.from(_runningTextDurations);
|
||
s.marqueeAnimType = _marqueeAnimType;
|
||
s.scaleCardLabel = _scaleCardLabel;
|
||
s.scaleCardBody = _scaleCardBody;
|
||
s.scaleRunningText = _scaleRunningText;
|
||
return s;
|
||
});
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text('Pengaturan Tampilan berhasil disimpan', style: GoogleFonts.manrope()),
|
||
backgroundColor: SacredColors.primaryContainer,
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
Future<void> _saveIqomahSettings() async {
|
||
await ref.read(settingsProvider.notifier).updateSettings((s) {
|
||
s.iqomahSubuh = int.tryParse(_iqomahSubuhCtrl.text.trim()) ?? 15;
|
||
s.iqomahDzuhur = int.tryParse(_iqomahDzuhurCtrl.text.trim()) ?? 10;
|
||
s.iqomahAshar = int.tryParse(_iqomahAsharCtrl.text.trim()) ?? 10;
|
||
s.iqomahMaghrib = int.tryParse(_iqomahMaghribCtrl.text.trim()) ?? 10;
|
||
s.iqomahIsya = int.tryParse(_iqomahIsyaCtrl.text.trim()) ?? 10;
|
||
return s;
|
||
});
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text('Jeda Iqamah berhasil disimpan', style: GoogleFonts.manrope()),
|
||
backgroundColor: SacredColors.primaryContainer,
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
Future<void> _syncData() async {
|
||
setState(() => _isSyncing = true);
|
||
final success = await SyncService.instance.syncMonthlyData();
|
||
setState(() => _isSyncing = false);
|
||
|
||
if (mounted) {
|
||
ref.invalidate(todayScheduleProvider);
|
||
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text(
|
||
success ? 'Sinkronisasi jadwal berhasil' : 'Sinkronisasi gagal. Periksa koneksi internet.',
|
||
style: GoogleFonts.manrope()),
|
||
backgroundColor: success ? SacredColors.primaryContainer : SacredColors.errorContainer,
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
Future<void> _showCitySearchDialog(double s) async {
|
||
final queryCtrl = TextEditingController();
|
||
List<Map<String, dynamic>> results = [];
|
||
bool isSearching = false;
|
||
|
||
await showDialog(
|
||
context: context,
|
||
builder: (ctx) {
|
||
return StatefulBuilder(
|
||
builder: (context, setDialogState) {
|
||
return Dialog(
|
||
backgroundColor: SacredColors.surfaceContainerLowest,
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.xl)),
|
||
child: Container(
|
||
width: 800 * s,
|
||
height: 600 * s,
|
||
padding: EdgeInsets.all(40 * s),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text('Cari Kota / Kabupaten',
|
||
style: GoogleFonts.plusJakartaSans(
|
||
fontSize: 32 * s, fontWeight: FontWeight.bold, color: SacredColors.primary)),
|
||
SizedBox(height: 24 * s),
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: TextField(
|
||
controller: queryCtrl,
|
||
style: GoogleFonts.manrope(fontSize: 24 * s, color: SacredColors.onSurface),
|
||
decoration: InputDecoration(
|
||
hintText: 'Misal: Yogyakarta',
|
||
filled: true,
|
||
fillColor: SacredColors.surfaceContainerLow,
|
||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(SacredRadii.md)),
|
||
),
|
||
),
|
||
),
|
||
SizedBox(width: 16 * s),
|
||
ElevatedButton.icon(
|
||
onPressed: () async {
|
||
final query = queryCtrl.text.trim();
|
||
if (query.isEmpty) return;
|
||
setDialogState(() => isSearching = true);
|
||
final res = await MyQuranSholatService.instance.searchCity(query);
|
||
setDialogState(() {
|
||
results = res;
|
||
isSearching = false;
|
||
});
|
||
},
|
||
icon: isSearching
|
||
? SizedBox(width: 20*s, height: 20*s, child: const CircularProgressIndicator(color: SacredColors.onPrimary, strokeWidth: 2))
|
||
: const HugeIcon(icon: HugeIcons.strokeRoundedSearch01, color: SacredColors.onPrimary),
|
||
label: Text('CARI', style: GoogleFonts.plusJakartaSans(fontSize: 20*s, fontWeight: FontWeight.bold)),
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: SacredColors.primary,
|
||
foregroundColor: SacredColors.onPrimary,
|
||
padding: EdgeInsets.symmetric(horizontal: 32 * s, vertical: 24 * s),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
SizedBox(height: 32 * s),
|
||
Expanded(
|
||
child: results.isEmpty && !isSearching
|
||
? Center(child: Text('Tidak ada hasil', style: GoogleFonts.manrope(fontSize: 20 * s, color: SacredColors.onSurfaceVariant)))
|
||
: ListView.builder(
|
||
itemCount: results.length,
|
||
itemBuilder: (context, index) {
|
||
final city = results[index];
|
||
return Padding(
|
||
padding: EdgeInsets.only(bottom: 8 * s),
|
||
child: ListTile(
|
||
title: Text(city['lokasi'] ?? '', style: GoogleFonts.plusJakartaSans(fontSize: 24 * s, color: SacredColors.onSurface)),
|
||
subtitle: Text('ID: ${city['id']}', style: GoogleFonts.manrope(fontSize: 18 * s, color: SacredColors.primary)),
|
||
tileColor: SacredColors.surfaceContainerLow,
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.sm)),
|
||
contentPadding: EdgeInsets.symmetric(horizontal: 24 * s, vertical: 8 * s),
|
||
onTap: () async {
|
||
final id = city['id'].toString();
|
||
final loc = city['lokasi'].toString();
|
||
|
||
await ref.read(settingsProvider.notifier).updateSettings((s) {
|
||
s.cityIdApi = id;
|
||
s.cityDisplayName = loc;
|
||
return s;
|
||
});
|
||
if (!mounted || !ctx.mounted) return;
|
||
setState(() {
|
||
_cityCtrl.text = '$loc ($id)';
|
||
});
|
||
Navigator.pop(ctx);
|
||
},
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final size = MediaQuery.of(context).size;
|
||
final s = size.width / 1920;
|
||
|
||
return Scaffold(
|
||
backgroundColor: SacredColors.background,
|
||
appBar: AppBar(
|
||
backgroundColor: SacredColors.surfaceContainerLowest,
|
||
title: Text(
|
||
'PENGATURAN SISTEM',
|
||
style: GoogleFonts.plusJakartaSans(
|
||
fontSize: 24 * s,
|
||
fontWeight: FontWeight.w700,
|
||
color: SacredColors.onSurface,
|
||
letterSpacing: 2 * s,
|
||
),
|
||
),
|
||
iconTheme: const IconThemeData(color: SacredColors.primary),
|
||
),
|
||
body: Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// Nav rail area
|
||
Container(
|
||
width: 350 * s,
|
||
color: SacredColors.surfaceContainerLow,
|
||
padding: EdgeInsets.all(32 * s),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
_NavButton(
|
||
title: 'IDENTITAS MASJID',
|
||
icon: HugeIcons.strokeRoundedHome01,
|
||
isActive: _selectedTab == 0,
|
||
scale: s,
|
||
onTap: () => setState(() => _selectedTab = 0),
|
||
),
|
||
SizedBox(height: 16 * s),
|
||
_NavButton(
|
||
title: 'JADWAL & SINKRONISASI',
|
||
icon: HugeIcons.strokeRoundedCalendar01,
|
||
isActive: _selectedTab == 1,
|
||
scale: s,
|
||
onTap: () => setState(() => _selectedTab = 1),
|
||
),
|
||
SizedBox(height: 16 * s),
|
||
_NavButton(
|
||
title: 'TAMPILAN & MEDIA',
|
||
icon: HugeIcons.strokeRoundedImage01,
|
||
isActive: _selectedTab == 2,
|
||
scale: s,
|
||
onTap: () => setState(() => _selectedTab = 2),
|
||
),
|
||
SizedBox(height: 16 * s),
|
||
_NavButton(
|
||
title: 'PENGATURAN JUMAT',
|
||
icon: HugeIcons.strokeRoundedCalendar01,
|
||
isActive: _selectedTab == 3,
|
||
scale: s,
|
||
onTap: () => setState(() => _selectedTab = 3),
|
||
),
|
||
SizedBox(height: 16 * s),
|
||
_NavButton(
|
||
title: 'SIMULASI',
|
||
icon: HugeIcons.strokeRoundedClock01,
|
||
isActive: _selectedTab == 4,
|
||
scale: s,
|
||
onTap: () => setState(() => _selectedTab = 4),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
|
||
// Content area
|
||
Expanded(
|
||
child: Padding(
|
||
padding: EdgeInsets.all(64 * s),
|
||
child: _selectedTab == 0
|
||
? _buildIdentityTab(s)
|
||
: _selectedTab == 1
|
||
? _buildJadwalTab(s)
|
||
: _selectedTab == 2
|
||
? _buildTampilanTab(s)
|
||
: _selectedTab == 3
|
||
? _buildJumatTab(s)
|
||
: _buildSimulasiTab(s),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Future<void> _saveJumat() async {
|
||
await ref.read(settingsProvider.notifier).updateSettings((s) {
|
||
s.khatibName = _khatibCtrl.text.trim();
|
||
s.imamName = _imamCtrl.text.trim();
|
||
return s;
|
||
});
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text('Data Jumat berhasil disimpan', style: GoogleFonts.manrope()),
|
||
backgroundColor: SacredColors.primaryContainer,
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
Widget _buildJumatTab(double s) {
|
||
return SingleChildScrollView(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'Pengaturan Jumat',
|
||
style: GoogleFonts.plusJakartaSans(
|
||
fontSize: 48 * s, fontWeight: FontWeight.w700, color: SacredColors.secondary),
|
||
),
|
||
SizedBox(height: 8 * s),
|
||
Text(
|
||
'Data di bawah akan tampil setiap hari Jumat: pada layar utama (banner bawah jam) dan layar Persiapan Khutbah.',
|
||
style: GoogleFonts.manrope(fontSize: 16 * s, color: SacredColors.onSurfaceVariant),
|
||
),
|
||
SizedBox(height: 40 * s),
|
||
|
||
_adminCard(s, child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
_sectionLabel('Petugas Shalat Jumat', s),
|
||
SizedBox(height: 8 * s),
|
||
Text(
|
||
'Nama Khatib dan Imam tampil di layar utama setiap Jumat dan di layar Persiapan Khutbah.',
|
||
style: GoogleFonts.manrope(fontSize: 14 * s, color: SacredColors.onSurfaceVariant),
|
||
),
|
||
SizedBox(height: 24 * s),
|
||
_buildTextField('Nama Khatib Minggu Ini', _khatibCtrl, s),
|
||
SizedBox(height: 16 * s),
|
||
_buildTextField('Nama Imam Minggu Ini', _imamCtrl, s),
|
||
SizedBox(height: 32 * s),
|
||
|
||
// Preview chip
|
||
if (_khatibCtrl.text.isNotEmpty || _imamCtrl.text.isNotEmpty) ...[
|
||
Text('Preview tampilan:', style: GoogleFonts.manrope(fontSize: 14 * s, color: SacredColors.onSurfaceVariant)),
|
||
SizedBox(height: 10 * s),
|
||
Container(
|
||
padding: EdgeInsets.all(20 * s),
|
||
decoration: BoxDecoration(
|
||
color: SacredColors.background,
|
||
borderRadius: BorderRadius.circular(SacredRadii.lg),
|
||
border: Border.all(color: SacredColors.secondary.withValues(alpha: 0.2)),
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Icon(Icons.star_rounded, color: SacredColors.secondary, size: 16 * s),
|
||
SizedBox(width: 8 * s),
|
||
Text('JUMAT MUBARAK', style: GoogleFonts.plusJakartaSans(
|
||
fontSize: 14 * s, fontWeight: FontWeight.w800, color: SacredColors.secondary, letterSpacing: 2)),
|
||
SizedBox(width: 8 * s),
|
||
Icon(Icons.star_rounded, color: SacredColors.secondary, size: 16 * s),
|
||
SizedBox(width: 24 * s),
|
||
if (_khatibCtrl.text.isNotEmpty)
|
||
Text('KHATIB ${_khatibCtrl.text}', style: GoogleFonts.manrope(
|
||
fontSize: 14 * s, color: SacredColors.onSurface)),
|
||
if (_khatibCtrl.text.isNotEmpty && _imamCtrl.text.isNotEmpty)
|
||
Text(' | ', style: GoogleFonts.manrope(fontSize: 14 * s, color: SacredColors.onSurfaceVariant)),
|
||
if (_imamCtrl.text.isNotEmpty)
|
||
Text('IMAM ${_imamCtrl.text}', style: GoogleFonts.manrope(
|
||
fontSize: 14 * s, color: SacredColors.onSurface)),
|
||
],
|
||
),
|
||
),
|
||
SizedBox(height: 24 * s),
|
||
],
|
||
|
||
ElevatedButton.icon(
|
||
onPressed: _saveJumat,
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: SacredColors.secondary,
|
||
foregroundColor: Colors.black,
|
||
padding: EdgeInsets.symmetric(horizontal: 40 * s, vertical: 20 * s),
|
||
textStyle: TextStyle(fontSize: 18 * s, fontWeight: FontWeight.bold),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.lg)),
|
||
),
|
||
icon: const Icon(Icons.save_rounded),
|
||
label: const Text('SIMPAN DATA JUMAT'),
|
||
),
|
||
],
|
||
)),
|
||
|
||
SizedBox(height: 32 * s),
|
||
|
||
// Info box
|
||
_adminCard(s, child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
_sectionLabel('Kapan Digunakan?', s),
|
||
SizedBox(height: 16 * s),
|
||
_infoRow(Icons.tv, 'Layar Utama (Jumat)', 'Banner bawah jam berubah ke JUMAT MUBARAK, nama khatib & imam tampil di bawahnya.', s),
|
||
SizedBox(height: 12 * s),
|
||
_infoRow(Icons.timer_outlined, 'Layar Persiapan Khutbah', 'Saat menuju iqomah Dzuhur di hari Jumat, layar menampilkan judul PERSIAPAN KHUTBAH beserta nama petugas.', s),
|
||
SizedBox(height: 12 * s),
|
||
_infoRow(Icons.info_outline, 'Durasi Blank Screen', 'Durasi Black Screen setelah shalat Jumat dapat diatur di tab Jadwal & Sinkronisasi.', s),
|
||
],
|
||
)),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _infoRow(IconData icon, String title, String desc, double s) {
|
||
return Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Icon(icon, color: SacredColors.secondary, size: 22 * s),
|
||
SizedBox(width: 12 * s),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(title, style: GoogleFonts.manrope(fontSize: 15 * s, fontWeight: FontWeight.w700, color: SacredColors.onSurface)),
|
||
SizedBox(height: 4 * s),
|
||
Text(desc, style: GoogleFonts.manrope(fontSize: 13 * s, color: SacredColors.onSurfaceVariant)),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildTampilanTab(double s) {
|
||
return SingleChildScrollView(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'Pengaturan Tampilan & Media',
|
||
style: GoogleFonts.plusJakartaSans(
|
||
fontSize: 48 * s,
|
||
fontWeight: FontWeight.w700,
|
||
color: SacredColors.primary,
|
||
),
|
||
),
|
||
SizedBox(height: 48 * s),
|
||
|
||
// ── Row 1: General settings + Background ──
|
||
Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// Left: Typography & Timers
|
||
Expanded(
|
||
child: _adminCard(s, child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
_sectionLabel('Tipografi & Skala Teks', s),
|
||
SizedBox(height: 12 * s),
|
||
DropdownButtonFormField<int>(
|
||
initialValue: _textScaleIndex,
|
||
onChanged: (val) => setState(() => _textScaleIndex = val ?? 1),
|
||
items: const [
|
||
DropdownMenuItem(value: 0, child: Text('Kecil (Small)')),
|
||
DropdownMenuItem(value: 1, child: Text('Normal (Medium)')),
|
||
DropdownMenuItem(value: 2, child: Text('Besar (Large)')),
|
||
],
|
||
style: GoogleFonts.plusJakartaSans(fontSize: 22 * s, color: SacredColors.onSurface),
|
||
dropdownColor: SacredColors.surfaceContainerHighest,
|
||
decoration: InputDecoration(
|
||
filled: true,
|
||
fillColor: SacredColors.surfaceContainerLowest,
|
||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(SacredRadii.md)),
|
||
),
|
||
),
|
||
SizedBox(height: 28 * s),
|
||
_buildTextField('Durasi Layar Utama (Detik)', _mainDurCtrl, s),
|
||
SizedBox(height: 24 * s),
|
||
_buildTextField('Durasi Tiap Slideshow (Detik)', _slideDurCtrl, s),
|
||
SizedBox(height: 40 * s),
|
||
|
||
_sectionLabel('Ukuran Teks Per Kelompok', s),
|
||
SizedBox(height: 8 * s),
|
||
Text(
|
||
'Kontrol ukuran teks secara spesifik per kelompok, terlepas dari skala global di atas.',
|
||
style: GoogleFonts.manrope(fontSize: 14 * s, color: SacredColors.onSurfaceVariant),
|
||
),
|
||
SizedBox(height: 20 * s),
|
||
_scaleSlider(
|
||
s: s,
|
||
label: 'Label Shalat (Nama: SUBUH, DZUHUR…)',
|
||
value: _scaleCardLabel,
|
||
onChanged: (v) => setState(() => _scaleCardLabel = v),
|
||
),
|
||
SizedBox(height: 16 * s),
|
||
_scaleSlider(
|
||
s: s,
|
||
label: 'Waktu & Iqamah pada kartu jadwal',
|
||
value: _scaleCardBody,
|
||
onChanged: (v) => setState(() => _scaleCardBody = v),
|
||
),
|
||
SizedBox(height: 16 * s),
|
||
_scaleSlider(
|
||
s: s,
|
||
label: 'Teks Berjalan (Running Text)',
|
||
value: _scaleRunningText,
|
||
onChanged: (v) => setState(() => _scaleRunningText = v),
|
||
),
|
||
|
||
SizedBox(height: 40 * s),
|
||
|
||
_sectionLabel('Background Layar Utama (Unsplash)', s),
|
||
SizedBox(height: 12 * s),
|
||
SwitchListTile(
|
||
title: Text('Gunakan Foto Unsplash API', style: GoogleFonts.plusJakartaSans(fontSize: 18 * s, color: SacredColors.onSurface)),
|
||
value: _useUnsplash,
|
||
onChanged: (val) => setState(() => _useUnsplash = val),
|
||
activeThumbColor: SacredColors.primary,
|
||
contentPadding: EdgeInsets.zero,
|
||
),
|
||
if (_useUnsplash) ...[
|
||
SizedBox(height: 12 * s),
|
||
_buildTextField('Kata Kunci (Contoh: mosque, architecture)', _unsplashKeywordCtrl, s),
|
||
SizedBox(height: 12 * s),
|
||
_buildTextField('Rotasi Foto (Jam)', _unsplashRotationCtrl, s),
|
||
],
|
||
|
||
SizedBox(height: 56 * s),
|
||
ElevatedButton.icon(
|
||
onPressed: _saveTampilan,
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: SacredColors.primary,
|
||
foregroundColor: SacredColors.onPrimary,
|
||
padding: EdgeInsets.symmetric(horizontal: 48 * s, vertical: 24 * s),
|
||
textStyle: TextStyle(fontSize: 18 * s, fontWeight: FontWeight.bold),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.lg)),
|
||
),
|
||
icon: HugeIcon(icon: HugeIcons.strokeRoundedFloppyDisk, color: SacredColors.onPrimary),
|
||
label: const Text('SIMPAN TAMPILAN'),
|
||
),
|
||
],
|
||
)),
|
||
),
|
||
SizedBox(width: 32 * s),
|
||
|
||
// Right: Branded Background + Slideshow
|
||
Expanded(
|
||
child: Column(
|
||
children: [
|
||
// Branded Background Card
|
||
_adminCard(s, child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
_sectionLabel('Foto Latar Utama (Branding Masjid)', s),
|
||
SizedBox(height: 16 * s),
|
||
if (_brandedBgImage != null && _brandedBgImage!.isNotEmpty) ...[
|
||
ClipRRect(
|
||
borderRadius: BorderRadius.circular(SacredRadii.md),
|
||
child: Image.file(
|
||
File(_brandedBgImage!),
|
||
height: 120 * s,
|
||
width: double.infinity,
|
||
fit: BoxFit.cover,
|
||
),
|
||
),
|
||
SizedBox(height: 12 * s),
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: Text(
|
||
_brandedBgImage!.split('/').last,
|
||
style: GoogleFonts.manrope(fontSize: 14 * s, color: SacredColors.onSurfaceVariant),
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
),
|
||
IconButton(
|
||
icon: HugeIcon(icon: HugeIcons.strokeRoundedDelete01, color: SacredColors.error, size: 22 * s),
|
||
onPressed: () {
|
||
setState(() => _brandedBgImage = null);
|
||
_saveTampilan();
|
||
},
|
||
),
|
||
],
|
||
),
|
||
] else
|
||
Text('Belum ada foto latar masjid.', style: GoogleFonts.manrope(fontSize: 16 * s, color: SacredColors.onSurfaceVariant)),
|
||
SizedBox(height: 16 * s),
|
||
ElevatedButton.icon(
|
||
onPressed: () async {
|
||
final res = await FilePicker.platform.pickFiles(type: FileType.image);
|
||
if (res != null && res.files.single.path != null) {
|
||
setState(() => _brandedBgImage = res.files.single.path);
|
||
_saveTampilan();
|
||
}
|
||
},
|
||
icon: HugeIcon(icon: HugeIcons.strokeRoundedImage01, color: SacredColors.onPrimary, size: 20 * s),
|
||
label: Text('PILIH FOTO MASJID', style: TextStyle(fontSize: 16 * s)),
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: SacredColors.secondary,
|
||
foregroundColor: SacredColors.onSecondary,
|
||
padding: EdgeInsets.symmetric(horizontal: 24 * s, vertical: 16 * s),
|
||
),
|
||
),
|
||
],
|
||
)),
|
||
SizedBox(height: 24 * s),
|
||
|
||
// Slideshow Gallery Card
|
||
_adminCard(s, child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
_sectionLabel('Galeri Gambar Slideshow', s),
|
||
ElevatedButton.icon(
|
||
onPressed: () async {
|
||
final res = await FilePicker.platform.pickFiles(type: FileType.image, allowMultiple: true);
|
||
if (res != null) {
|
||
setState(() {
|
||
for (var path in res.paths) {
|
||
if (path != null && !_slideshowImages.contains(path)) {
|
||
_slideshowImages.add(path);
|
||
}
|
||
}
|
||
});
|
||
_saveTampilan();
|
||
}
|
||
},
|
||
icon: HugeIcon(icon: HugeIcons.strokeRoundedPlusSign, color: SacredColors.onSecondary, size: 18 * s),
|
||
label: Text('TAMBAH FOTO', style: TextStyle(fontSize: 14 * s)),
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: SacredColors.secondary,
|
||
foregroundColor: SacredColors.onSecondary,
|
||
padding: EdgeInsets.symmetric(horizontal: 20 * s, vertical: 14 * s),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
SizedBox(height: 16 * s),
|
||
if (_slideshowImages.isEmpty)
|
||
Text('Belum ada gambar slideshow.', style: GoogleFonts.manrope(fontSize: 16 * s, color: SacredColors.onSurfaceVariant))
|
||
else
|
||
ListView.builder(
|
||
shrinkWrap: true,
|
||
physics: const NeverScrollableScrollPhysics(),
|
||
itemCount: _slideshowImages.length,
|
||
itemBuilder: (context, idx) {
|
||
final path = _slideshowImages[idx];
|
||
return ListTile(
|
||
leading: ClipRRect(
|
||
borderRadius: BorderRadius.circular(SacredRadii.sm),
|
||
child: Image.file(File(path), width: 56 * s, height: 56 * s, fit: BoxFit.cover),
|
||
),
|
||
title: Text(path.split('/').last, maxLines: 1, overflow: TextOverflow.ellipsis,
|
||
style: GoogleFonts.manrope(fontSize: 16 * s, color: SacredColors.onSurface)),
|
||
trailing: IconButton(
|
||
icon: HugeIcon(icon: HugeIcons.strokeRoundedDelete01, color: SacredColors.error, size: 20 * s),
|
||
onPressed: () {
|
||
setState(() => _slideshowImages.removeAt(idx));
|
||
_saveTampilan();
|
||
},
|
||
),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
)),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
|
||
SizedBox(height: 40 * s),
|
||
|
||
// ── Row 2: Running Text Repeater ──
|
||
_adminCard(s, child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
_sectionLabel('Running Text / Pengumuman', s),
|
||
Row(
|
||
children: [
|
||
Text('Mode Animasi:', style: GoogleFonts.manrope(fontSize: 16 * s, color: SacredColors.onSurfaceVariant)),
|
||
SizedBox(width: 12 * s),
|
||
SegmentedButton<String>(
|
||
segments: [
|
||
ButtonSegment(value: 'marquee', label: Text('Marquee', style: GoogleFonts.manrope(fontSize: 16 * s))),
|
||
ButtonSegment(value: 'fade', label: Text('Fade In-Out', style: GoogleFonts.manrope(fontSize: 16 * s))),
|
||
],
|
||
selected: {_marqueeAnimType},
|
||
onSelectionChanged: (val) => setState(() => _marqueeAnimType = val.first),
|
||
style: ButtonStyle(
|
||
backgroundColor: WidgetStateProperty.resolveWith((states) =>
|
||
states.contains(WidgetState.selected) ? SacredColors.primary : SacredColors.surfaceContainerLowest),
|
||
foregroundColor: WidgetStateProperty.resolveWith((states) =>
|
||
states.contains(WidgetState.selected) ? SacredColors.onPrimary : SacredColors.onSurfaceVariant),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
SizedBox(height: 24 * s),
|
||
|
||
// Repeater list
|
||
if (_runningTexts.isEmpty)
|
||
Padding(
|
||
padding: EdgeInsets.symmetric(vertical: 16 * s),
|
||
child: Text('Belum ada teks. Klik TAMBAH untuk menambah baris.', style: GoogleFonts.manrope(fontSize: 16 * s, color: SacredColors.onSurfaceVariant)),
|
||
)
|
||
else
|
||
ListView.separated(
|
||
shrinkWrap: true,
|
||
physics: const NeverScrollableScrollPhysics(),
|
||
itemCount: _runningTexts.length,
|
||
separatorBuilder: (_, __) => SizedBox(height: 12 * s),
|
||
itemBuilder: (context, idx) {
|
||
final textCtrl = TextEditingController(text: _runningTexts[idx])
|
||
..selection = TextSelection.fromPosition(TextPosition(offset: _runningTexts[idx].length));
|
||
final durCtrl = TextEditingController(text: _runningTextDurations[idx].toString());
|
||
return Container(
|
||
padding: EdgeInsets.all(20 * s),
|
||
decoration: BoxDecoration(
|
||
color: SacredColors.surfaceContainerLowest,
|
||
borderRadius: BorderRadius.circular(SacredRadii.md),
|
||
border: Border.all(color: SacredColors.outlineVariant.withValues(alpha: 0.3)),
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Container(
|
||
width: 32 * s,
|
||
height: 32 * s,
|
||
alignment: Alignment.center,
|
||
decoration: BoxDecoration(
|
||
color: SacredColors.surfaceContainerHighest,
|
||
shape: BoxShape.circle,
|
||
),
|
||
child: Text('${idx + 1}', style: GoogleFonts.manrope(fontSize: 14 * s, fontWeight: FontWeight.w700, color: SacredColors.primary)),
|
||
),
|
||
SizedBox(width: 16 * s),
|
||
Expanded(
|
||
flex: 5,
|
||
child: TextField(
|
||
controller: textCtrl,
|
||
style: GoogleFonts.plusJakartaSans(fontSize: 20 * s, color: SacredColors.onSurface),
|
||
decoration: InputDecoration(
|
||
hintText: 'Teks pengumuman...',
|
||
filled: true,
|
||
fillColor: SacredColors.surfaceContainerLow,
|
||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(SacredRadii.sm), borderSide: BorderSide.none),
|
||
isDense: true,
|
||
contentPadding: EdgeInsets.symmetric(horizontal: 16 * s, vertical: 14 * s),
|
||
),
|
||
onChanged: (val) => _runningTexts[idx] = val,
|
||
),
|
||
),
|
||
SizedBox(width: 12 * s),
|
||
SizedBox(
|
||
width: 100 * s,
|
||
child: TextField(
|
||
controller: durCtrl,
|
||
keyboardType: TextInputType.number,
|
||
style: GoogleFonts.plusJakartaSans(fontSize: 20 * s, color: SacredColors.onSurface),
|
||
decoration: InputDecoration(
|
||
hintText: 'Detik',
|
||
filled: true,
|
||
fillColor: SacredColors.surfaceContainerLow,
|
||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(SacredRadii.sm), borderSide: BorderSide.none),
|
||
isDense: true,
|
||
contentPadding: EdgeInsets.symmetric(horizontal: 16 * s, vertical: 14 * s),
|
||
suffixText: 'dtk',
|
||
),
|
||
onChanged: (val) => _runningTextDurations[idx] = int.tryParse(val) ?? 12,
|
||
),
|
||
),
|
||
SizedBox(width: 8 * s),
|
||
IconButton(
|
||
icon: HugeIcon(icon: HugeIcons.strokeRoundedDelete01, color: SacredColors.error, size: 22 * s),
|
||
onPressed: () {
|
||
setState(() {
|
||
_runningTexts.removeAt(idx);
|
||
_runningTextDurations.removeAt(idx);
|
||
});
|
||
},
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
),
|
||
|
||
SizedBox(height: 20 * s),
|
||
Row(
|
||
children: [
|
||
OutlinedButton.icon(
|
||
onPressed: () {
|
||
setState(() {
|
||
_runningTexts.add('');
|
||
_runningTextDurations.add(12);
|
||
});
|
||
},
|
||
icon: HugeIcon(icon: HugeIcons.strokeRoundedPlusSign, color: SacredColors.primary, size: 20 * s),
|
||
label: Text('TAMBAH BARIS', style: GoogleFonts.plusJakartaSans(fontSize: 16 * s, color: SacredColors.primary)),
|
||
style: OutlinedButton.styleFrom(
|
||
side: BorderSide(color: SacredColors.primary.withValues(alpha: 0.5)),
|
||
padding: EdgeInsets.symmetric(horizontal: 24 * s, vertical: 16 * s),
|
||
),
|
||
),
|
||
SizedBox(width: 16 * s),
|
||
ElevatedButton.icon(
|
||
onPressed: _saveTampilan,
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: SacredColors.primary,
|
||
foregroundColor: SacredColors.onPrimary,
|
||
padding: EdgeInsets.symmetric(horizontal: 32 * s, vertical: 16 * s),
|
||
),
|
||
icon: HugeIcon(icon: HugeIcons.strokeRoundedFloppyDisk, color: SacredColors.onPrimary, size: 18 * s),
|
||
label: Text('SIMPAN TEKS', style: GoogleFonts.plusJakartaSans(fontSize: 16 * s, fontWeight: FontWeight.bold)),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
)),
|
||
|
||
SizedBox(height: 40 * s),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _adminCard(double s, {required Widget child}) {
|
||
return Container(
|
||
width: double.infinity,
|
||
padding: EdgeInsets.all(36 * s),
|
||
decoration: BoxDecoration(
|
||
color: SacredColors.surfaceContainerHighest.withValues(alpha: 0.3),
|
||
borderRadius: BorderRadius.circular(SacredRadii.xl),
|
||
border: Border.all(color: SacredColors.outlineVariant.withValues(alpha: 0.2)),
|
||
),
|
||
child: child,
|
||
);
|
||
}
|
||
|
||
Widget _sectionLabel(String label, double s) {
|
||
return Text(
|
||
label,
|
||
style: GoogleFonts.plusJakartaSans(
|
||
fontSize: 20 * s,
|
||
fontWeight: FontWeight.w700,
|
||
color: SacredColors.primary,
|
||
),
|
||
);
|
||
}
|
||
|
||
|
||
|
||
Widget _buildIdentityTab(double s) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'Identitas & Lokasi Masjid',
|
||
style: GoogleFonts.plusJakartaSans(
|
||
fontSize: 48 * s,
|
||
fontWeight: FontWeight.w700,
|
||
color: SacredColors.primary,
|
||
),
|
||
),
|
||
SizedBox(height: 48 * s),
|
||
Container(
|
||
width: 800 * s,
|
||
padding: EdgeInsets.all(40 * s),
|
||
decoration: BoxDecoration(
|
||
color: SacredColors.surfaceContainerHighest.withValues(alpha: 0.3),
|
||
borderRadius: BorderRadius.circular(SacredRadii.xl),
|
||
border: Border.all(color: SacredColors.outlineVariant.withValues(alpha: 0.2)),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
_buildTextField('Nama Masjid', _masjidNameCtrl, s),
|
||
SizedBox(height: 32 * s),
|
||
_buildTextField('Alamat Lengkap', _masjidAddressCtrl, s, maxLines: 2),
|
||
SizedBox(height: 32 * s),
|
||
|
||
// City API Config
|
||
Text(
|
||
'Lokasi Jadwal Shalat (MyQuran API)',
|
||
style: GoogleFonts.manrope(
|
||
fontSize: 16 * s,
|
||
fontWeight: FontWeight.w600,
|
||
color: SacredColors.onSurfaceVariant,
|
||
),
|
||
),
|
||
SizedBox(height: 12 * s),
|
||
Row(
|
||
children: [
|
||
Expanded(
|
||
child: TextField(
|
||
controller: _cityCtrl,
|
||
readOnly: true,
|
||
style: GoogleFonts.plusJakartaSans(fontSize: 24 * s, color: SacredColors.onSurface),
|
||
decoration: InputDecoration(
|
||
filled: true,
|
||
fillColor: SacredColors.surfaceContainerLowest,
|
||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(SacredRadii.md), borderSide: BorderSide.none),
|
||
),
|
||
),
|
||
),
|
||
SizedBox(width: 16 * s),
|
||
ElevatedButton.icon(
|
||
onPressed: () => _showCitySearchDialog(s),
|
||
icon: HugeIcon(icon: HugeIcons.strokeRoundedSearch01, color: SacredColors.onPrimary),
|
||
label: Text('CARI KOTA', style: TextStyle(fontSize: 16 * s)),
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: SacredColors.secondary,
|
||
foregroundColor: SacredColors.onPrimary,
|
||
padding: EdgeInsets.symmetric(horizontal: 24 * s, vertical: 24 * s),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
|
||
SizedBox(height: 64 * s),
|
||
ElevatedButton.icon(
|
||
onPressed: _saveIdentity,
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: SacredColors.primary,
|
||
foregroundColor: SacredColors.onPrimary,
|
||
padding: EdgeInsets.symmetric(horizontal: 48 * s, vertical: 24 * s),
|
||
textStyle: TextStyle(fontSize: 20 * s, fontWeight: FontWeight.bold),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.lg)),
|
||
),
|
||
icon: HugeIcon(icon: HugeIcons.strokeRoundedFloppyDisk, color: SacredColors.onPrimary),
|
||
label: const Text('SIMPAN PERUBAHAN TULISAN'),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildJadwalTab(double s) {
|
||
final settings = ref.watch(settingsProvider);
|
||
final todayScheduleOption = ref.watch(todayScheduleProvider);
|
||
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'Jadwal & Sinkronisasi',
|
||
style: GoogleFonts.plusJakartaSans(
|
||
fontSize: 48 * s,
|
||
fontWeight: FontWeight.w700,
|
||
color: SacredColors.primary,
|
||
),
|
||
),
|
||
SizedBox(height: 48 * s),
|
||
|
||
// Sync Card
|
||
Container(
|
||
width: double.infinity,
|
||
padding: EdgeInsets.all(40 * s),
|
||
decoration: BoxDecoration(
|
||
color: SacredColors.surfaceContainerLow,
|
||
borderRadius: BorderRadius.circular(SacredRadii.xl),
|
||
border: Border.all(color: SacredColors.outlineVariant.withValues(alpha: 0.4)),
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'Status Data Jadwal',
|
||
style: GoogleFonts.manrope(fontSize: 20 * s, fontWeight: FontWeight.w700, color: SacredColors.onSurfaceVariant, letterSpacing: 1 * s),
|
||
),
|
||
SizedBox(height: 24 * s),
|
||
Row(
|
||
children: [
|
||
_buildStatusRow('Terakhir Sync', settings.lastSyncDate ?? 'Belum pernah', HugeIcons.strokeRoundedClock01, s),
|
||
SizedBox(width: 48 * s),
|
||
_buildStatusRow('Sumber Data', 'api.myquran.com', HugeIcons.strokeRoundedDatabase01, s),
|
||
SizedBox(width: 48 * s),
|
||
_buildStatusRow('Lokasi Data', settings.cityDisplayName, HugeIcons.strokeRoundedLocation01, s),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
ElevatedButton.icon(
|
||
onPressed: _isSyncing ? null : _syncData,
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: SacredColors.secondary,
|
||
foregroundColor: SacredColors.onSecondary,
|
||
padding: EdgeInsets.symmetric(horizontal: 48 * s, vertical: 32 * s),
|
||
textStyle: TextStyle(fontSize: 20 * s, fontWeight: FontWeight.bold),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.lg)),
|
||
),
|
||
icon: _isSyncing
|
||
? SizedBox(width: 24*s, height: 24*s, child: const CircularProgressIndicator(color: SacredColors.onSecondary, strokeWidth: 3))
|
||
: HugeIcon(icon: HugeIcons.strokeRoundedCloudDownload, color: SacredColors.onSecondary),
|
||
label: Text(_isSyncing ? 'MENYINKRONKAN...' : 'SINKRONKAN DATA BULAN INI'),
|
||
)
|
||
],
|
||
),
|
||
),
|
||
|
||
SizedBox(height: 64 * s),
|
||
|
||
// Jeda Waktu Iqamah Settings Card
|
||
_adminCard(s, child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
_sectionLabel('Jeda Waktu Iqamah (Menit)', s),
|
||
SizedBox(height: 8 * s),
|
||
Text(
|
||
'Tentukan durasi hitung mundur dari Adzan selesai (1 menit setelah masuk waktu) hingga iqamah. Selama jeda ini, jamaah dapat melakukan shalat sunnah.',
|
||
style: GoogleFonts.manrope(fontSize: 14 * s, color: SacredColors.onSurfaceVariant),
|
||
),
|
||
SizedBox(height: 32 * s),
|
||
Row(
|
||
children: [
|
||
Expanded(child: _buildTextField('Iqamah Subuh', _iqomahSubuhCtrl, s)),
|
||
SizedBox(width: 16 * s),
|
||
Expanded(child: _buildTextField('Iqamah Dzuhur', _iqomahDzuhurCtrl, s)),
|
||
SizedBox(width: 16 * s),
|
||
Expanded(child: _buildTextField('Iqamah Ashar', _iqomahAsharCtrl, s)),
|
||
],
|
||
),
|
||
SizedBox(height: 16 * s),
|
||
Row(
|
||
children: [
|
||
Expanded(child: _buildTextField('Iqamah Maghrib', _iqomahMaghribCtrl, s)),
|
||
SizedBox(width: 16 * s),
|
||
Expanded(child: _buildTextField('Iqamah Isya', _iqomahIsyaCtrl, s)),
|
||
SizedBox(width: 16 * s),
|
||
Expanded(child: SizedBox()), // spacer
|
||
],
|
||
),
|
||
SizedBox(height: 32 * s),
|
||
ElevatedButton.icon(
|
||
onPressed: _saveIqomahSettings,
|
||
style: ElevatedButton.styleFrom(
|
||
backgroundColor: SacredColors.secondary,
|
||
foregroundColor: Colors.black,
|
||
padding: EdgeInsets.symmetric(horizontal: 40 * s, vertical: 20 * s),
|
||
textStyle: TextStyle(fontSize: 18 * s, fontWeight: FontWeight.bold),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(SacredRadii.lg)),
|
||
),
|
||
icon: const Icon(Icons.timer),
|
||
label: const Text('SIMPAN JEDA IQAMAH'),
|
||
),
|
||
],
|
||
)),
|
||
|
||
SizedBox(height: 64 * s),
|
||
|
||
Text(
|
||
'Pratinjau Jadwal Hari Ini',
|
||
style: GoogleFonts.plusJakartaSans(
|
||
fontSize: 32 * s,
|
||
fontWeight: FontWeight.w700,
|
||
color: SacredColors.onSurface,
|
||
),
|
||
),
|
||
SizedBox(height: 32 * s),
|
||
|
||
// Schedule Grid
|
||
Expanded(
|
||
child: Builder(
|
||
builder: (context) {
|
||
if (todayScheduleOption == null) {
|
||
return Center(
|
||
child: Text('Data jadwal kosong. Silakan lakukan sinkronisasi.', style: GoogleFonts.manrope(fontSize: 24 * s, color: SacredColors.error)),
|
||
);
|
||
}
|
||
|
||
final prayerMap = {
|
||
'IMSAK': todayScheduleOption.imsak,
|
||
'SUBUH': todayScheduleOption.subuh,
|
||
'TERBIT': todayScheduleOption.terbit,
|
||
'DHUHA': todayScheduleOption.dhuha,
|
||
'DZUHUR': todayScheduleOption.dzuhur,
|
||
'ASHAR': todayScheduleOption.ashar,
|
||
'MAGHRIB': todayScheduleOption.maghrib,
|
||
'ISYA': todayScheduleOption.isya,
|
||
};
|
||
|
||
return GridView.builder(
|
||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||
crossAxisCount: 4,
|
||
crossAxisSpacing: 24 * s,
|
||
mainAxisSpacing: 24 * s,
|
||
childAspectRatio: 2.2, // wide rectangular Google Stitch cards
|
||
),
|
||
itemCount: prayerMap.length,
|
||
itemBuilder: (context, index) {
|
||
final key = prayerMap.keys.elementAt(index);
|
||
final time = prayerMap[key]!;
|
||
return _buildPrayerCard(key, time, s);
|
||
},
|
||
);
|
||
},
|
||
),
|
||
)
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildPrayerCard(String name, String time, double s) {
|
||
return Container(
|
||
decoration: BoxDecoration(
|
||
color: SacredColors.surfaceContainerLowest.withValues(alpha: 0.5),
|
||
borderRadius: BorderRadius.circular(SacredRadii.lg),
|
||
border: Border.all(color: SacredColors.outlineVariant.withValues(alpha: 0.3)),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.black.withValues(alpha: 0.2),
|
||
blurRadius: 10 * s,
|
||
offset: Offset(0, 4 * s),
|
||
)
|
||
]
|
||
),
|
||
padding: EdgeInsets.symmetric(horizontal: 32 * s, vertical: 24 * s),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Text(
|
||
name,
|
||
style: GoogleFonts.manrope(
|
||
fontSize: 18 * s,
|
||
fontWeight: FontWeight.w600,
|
||
color: SacredColors.onSurfaceVariant,
|
||
letterSpacing: 2 * s,
|
||
),
|
||
),
|
||
SizedBox(height: 8 * s),
|
||
Text(
|
||
time,
|
||
style: GoogleFonts.plusJakartaSans(
|
||
fontSize: 42 * s,
|
||
fontWeight: FontWeight.w800,
|
||
color: SacredColors.primary,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildTextField(String label, TextEditingController ctrl, double s, {int maxLines = 1}) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
label,
|
||
style: GoogleFonts.manrope(
|
||
fontSize: 16 * s,
|
||
fontWeight: FontWeight.w600,
|
||
color: SacredColors.onSurfaceVariant,
|
||
),
|
||
),
|
||
SizedBox(height: 12 * s),
|
||
TextField(
|
||
controller: ctrl,
|
||
maxLines: maxLines,
|
||
style: GoogleFonts.plusJakartaSans(
|
||
fontSize: 24 * s,
|
||
color: SacredColors.onSurface,
|
||
),
|
||
decoration: InputDecoration(
|
||
filled: true,
|
||
fillColor: SacredColors.surfaceContainerLowest,
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(SacredRadii.md),
|
||
borderSide: BorderSide(color: SacredColors.outlineVariant.withValues(alpha: 0.5)),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(SacredRadii.md),
|
||
borderSide: const BorderSide(color: SacredColors.primary, width: 2),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildStatusRow(String label, String value, dynamic icon, double s) {
|
||
return Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Container(
|
||
padding: EdgeInsets.all(12 * s),
|
||
decoration: BoxDecoration(
|
||
color: SacredColors.surfaceContainerHighest,
|
||
borderRadius: BorderRadius.circular(SacredRadii.sm),
|
||
),
|
||
child: HugeIcon(icon: icon, color: SacredColors.secondary, size: 24 * s),
|
||
),
|
||
SizedBox(width: 16 * s),
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(label, style: GoogleFonts.manrope(fontSize: 12 * s, color: SacredColors.onSurfaceVariant)),
|
||
Text(value, style: GoogleFonts.plusJakartaSans(fontSize: 18 * s, fontWeight: FontWeight.w600, color: SacredColors.onSurface)),
|
||
],
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _scaleSlider({
|
||
required double s,
|
||
required String label,
|
||
required double value,
|
||
required ValueChanged<double> onChanged,
|
||
}) {
|
||
final pct = (value * 100).round();
|
||
const step = 0.05;
|
||
const presets = [0.75, 1.0, 1.25, 1.5];
|
||
|
||
return Container(
|
||
padding: EdgeInsets.all(16 * s),
|
||
decoration: BoxDecoration(
|
||
color: SacredColors.surfaceContainerLowest,
|
||
borderRadius: BorderRadius.circular(SacredRadii.md),
|
||
border: Border.all(color: SacredColors.outlineVariant.withValues(alpha: 0.25)),
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Expanded(
|
||
child: Text(label, style: GoogleFonts.manrope(
|
||
fontSize: 15 * s, fontWeight: FontWeight.w500, color: SacredColors.onSurface)),
|
||
),
|
||
Container(
|
||
padding: EdgeInsets.symmetric(horizontal: 14 * s, vertical: 5 * s),
|
||
decoration: BoxDecoration(
|
||
color: SacredColors.primary.withValues(alpha: 0.15),
|
||
borderRadius: BorderRadius.circular(SacredRadii.sm),
|
||
),
|
||
child: Text('$pct%', style: GoogleFonts.manrope(
|
||
fontSize: 16 * s, fontWeight: FontWeight.w800, color: SacredColors.primary)),
|
||
),
|
||
],
|
||
),
|
||
SizedBox(height: 14 * s),
|
||
// TV-remote control row
|
||
Row(
|
||
children: [
|
||
_tvStepBtn(s: s, label: '−−', onPressed: () => onChanged((value - step * 4).clamp(0.5, 2.0))),
|
||
SizedBox(width: 6 * s),
|
||
_tvStepBtn(s: s, label: '−', onPressed: () => onChanged((value - step).clamp(0.5, 2.0))),
|
||
SizedBox(width: 10 * s),
|
||
Expanded(
|
||
child: Stack(
|
||
alignment: Alignment.centerLeft,
|
||
children: [
|
||
Container(
|
||
height: 6 * s,
|
||
decoration: BoxDecoration(
|
||
color: SacredColors.outlineVariant.withValues(alpha: 0.2),
|
||
borderRadius: BorderRadius.circular(3 * s),
|
||
),
|
||
),
|
||
FractionallySizedBox(
|
||
widthFactor: ((value - 0.5) / 1.5).clamp(0.0, 1.0),
|
||
child: Container(
|
||
height: 6 * s,
|
||
decoration: BoxDecoration(
|
||
color: SacredColors.primary,
|
||
borderRadius: BorderRadius.circular(3 * s),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
SizedBox(width: 10 * s),
|
||
_tvStepBtn(s: s, label: '+', onPressed: () => onChanged((value + step).clamp(0.5, 2.0))),
|
||
SizedBox(width: 6 * s),
|
||
_tvStepBtn(s: s, label: '++', onPressed: () => onChanged((value + step * 4).clamp(0.5, 2.0))),
|
||
],
|
||
),
|
||
SizedBox(height: 12 * s),
|
||
// Quick preset chips
|
||
Row(
|
||
children: [
|
||
Text('Cepat: ', style: GoogleFonts.manrope(fontSize: 12 * s, color: SacredColors.onSurfaceVariant)),
|
||
...presets.map((p) {
|
||
final isActive = (value - p).abs() < 0.02;
|
||
return Padding(
|
||
padding: EdgeInsets.only(right: 8 * s),
|
||
child: InkWell(
|
||
focusColor: SacredColors.primary.withValues(alpha: 0.3),
|
||
borderRadius: BorderRadius.circular(SacredRadii.sm),
|
||
onTap: () => onChanged(p),
|
||
child: Container(
|
||
padding: EdgeInsets.symmetric(horizontal: 12 * s, vertical: 6 * s),
|
||
decoration: BoxDecoration(
|
||
color: isActive ? SacredColors.primary : SacredColors.surfaceContainerHighest,
|
||
borderRadius: BorderRadius.circular(SacredRadii.sm),
|
||
border: isActive ? null : Border.all(color: SacredColors.outlineVariant.withValues(alpha: 0.3)),
|
||
),
|
||
child: Text('${(p * 100).round()}%', style: GoogleFonts.manrope(
|
||
fontSize: 13 * s, fontWeight: FontWeight.w600,
|
||
color: isActive ? SacredColors.onPrimary : SacredColors.onSurfaceVariant)),
|
||
),
|
||
),
|
||
);
|
||
}),
|
||
],
|
||
),
|
||
SizedBox(height: 6 * s),
|
||
Text(
|
||
'TV Remote: gunakan ↑↓ untuk pindah fokus, tekan OK pada −/+ untuk mengubah nilai.',
|
||
style: GoogleFonts.manrope(fontSize: 11 * s, color: SacredColors.onSurfaceVariant.withValues(alpha: 0.7)),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _tvStepBtn({required double s, required String label, required VoidCallback onPressed}) {
|
||
return Material(
|
||
color: Colors.transparent,
|
||
child: InkWell(
|
||
focusColor: SacredColors.primary.withValues(alpha: 0.35),
|
||
hoverColor: SacredColors.primary.withValues(alpha: 0.15),
|
||
borderRadius: BorderRadius.circular(SacredRadii.sm),
|
||
onTap: onPressed,
|
||
child: Container(
|
||
width: 42 * s,
|
||
height: 38 * s,
|
||
alignment: Alignment.center,
|
||
decoration: BoxDecoration(
|
||
border: Border.all(color: SacredColors.outlineVariant.withValues(alpha: 0.4)),
|
||
borderRadius: BorderRadius.circular(SacredRadii.sm),
|
||
color: SacredColors.surfaceContainerHighest,
|
||
),
|
||
child: Text(
|
||
label,
|
||
style: GoogleFonts.manrope(
|
||
fontSize: 15 * s,
|
||
fontWeight: FontWeight.w700,
|
||
color: SacredColors.onSurface,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildSimulasiTab(double s) {
|
||
return SingleChildScrollView(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'Mode Simulasi Pengembang',
|
||
style: GoogleFonts.plusJakartaSans(
|
||
fontSize: 48 * s,
|
||
fontWeight: FontWeight.w700,
|
||
color: SacredColors.primary,
|
||
),
|
||
),
|
||
SizedBox(height: 16 * s),
|
||
Text(
|
||
'Gunakan tombol di bawah ini untuk melihat pratinjau bagaimana aplikasi bereaksi terhadap berbagai waktu dan status tanpa harus menunggu waktu sebenarnya.\nFitur ini bekerja dengan menggeser waktu aplikasi (Time Travel).',
|
||
style: GoogleFonts.manrope(fontSize: 18 * s, color: SacredColors.onSurfaceVariant),
|
||
),
|
||
SizedBox(height: 48 * s),
|
||
|
||
Wrap(
|
||
spacing: 24 * s,
|
||
runSpacing: 24 * s,
|
||
children: [
|
||
_simulasiCard(
|
||
s: s,
|
||
title: 'Reset Waktu Asli',
|
||
icon: HugeIcons.strokeRoundedHome01,
|
||
desc: 'Kembali ke waktu saat ini secara sinkron dengan jam sistem.',
|
||
onTap: () => _simulateTimeOffset(Duration.zero),
|
||
),
|
||
_simulasiCard(
|
||
s: s,
|
||
title: 'Menuju Adzan',
|
||
icon: HugeIcons.strokeRoundedClock01,
|
||
desc: 'Melompat ke 2 menit sebelum Adzan Dzuhur hari ini.',
|
||
onTap: () => _simulateEvent('pre_adzan'),
|
||
),
|
||
_simulasiCard(
|
||
s: s,
|
||
title: 'Selama Adzan',
|
||
icon: HugeIcons.strokeRoundedMegaphone01,
|
||
desc: 'Melompat ke tepat waktu Adzan Dzuhur berkumandang.',
|
||
onTap: () => _simulateEvent('adzan'),
|
||
),
|
||
_simulasiCard(
|
||
s: s,
|
||
title: 'Menuju Iqomah',
|
||
icon: HugeIcons.strokeRoundedTimer02,
|
||
desc: 'Melompat ke saat waktu iqomah sedang menghitung mundur (1 menit setelah Adzan).',
|
||
onTap: () => _simulateEvent('iqomah'),
|
||
),
|
||
_simulasiCard(
|
||
s: s,
|
||
title: 'Persiapan Jumat',
|
||
icon: HugeIcons.strokeRoundedCalendar03,
|
||
desc: 'Menyimulasikan layar khusus persiapan Jumat (30 menit sebelum Adzan Dzuhur).',
|
||
onTap: () => _simulateEvent('jumat_incoming'),
|
||
),
|
||
_simulasiCard(
|
||
s: s,
|
||
title: 'Khutbah Berlangsung',
|
||
icon: HugeIcons.strokeRoundedUserGroup,
|
||
desc: 'Menyimulasikan layar saat Khutbah sedang berlangsung tanpa hitungan mundur (2 menit setelah Adzan Dzuhur).',
|
||
onTap: () => _simulateEvent('jumat_khutbah'),
|
||
),
|
||
_simulasiCard(
|
||
s: s,
|
||
title: 'Mode Shalat',
|
||
icon: HugeIcons.strokeRoundedMoon02,
|
||
desc: 'Layar menjadi hitam atau gelap selama shalat berlangsung.',
|
||
onTap: () => _simulateEvent('shalat'),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _simulasiCard({required double s, required String title, required dynamic icon, required String desc, required VoidCallback onTap}) {
|
||
return InkWell(
|
||
onTap: onTap,
|
||
borderRadius: BorderRadius.circular(SacredRadii.lg),
|
||
child: Container(
|
||
width: 320 * s,
|
||
padding: EdgeInsets.all(24 * s),
|
||
decoration: BoxDecoration(
|
||
color: SacredColors.surfaceContainerLowest,
|
||
borderRadius: BorderRadius.circular(SacredRadii.lg),
|
||
border: Border.all(color: SacredColors.outlineVariant.withValues(alpha: 0.5)),
|
||
boxShadow: [
|
||
BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 10 * s, offset: Offset(0, 4 * s)),
|
||
],
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
HugeIcon(icon: icon, color: SacredColors.primary, size: 40 * s),
|
||
SizedBox(height: 16 * s),
|
||
Text(title, style: GoogleFonts.plusJakartaSans(fontSize: 20 * s, fontWeight: FontWeight.bold, color: SacredColors.onSurface)),
|
||
SizedBox(height: 8 * s),
|
||
Text(desc, style: GoogleFonts.manrope(fontSize: 14 * s, color: SacredColors.onSurfaceVariant)),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _simulateTimeOffset(Duration offset) {
|
||
ref.read(mockTimeOffsetProvider.notifier).state = offset;
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(content: Text('Offset Waktu disetel ke: ${offset.inMinutes} Menit', style: GoogleFonts.manrope())),
|
||
);
|
||
}
|
||
|
||
void _simulateEvent(String eventType) {
|
||
final schedule = ref.read(todayScheduleProvider);
|
||
if (schedule == null) return;
|
||
// We simulate using schedule.dzuhur
|
||
final dzuhurStr = schedule.dzuhur;
|
||
|
||
final parts = dzuhurStr.split(':');
|
||
final realNow = DateTime.now();
|
||
final dzuhurTime = DateTime(realNow.year, realNow.month, realNow.day, int.parse(parts[0]), int.parse(parts[1]));
|
||
|
||
DateTime targetTime;
|
||
|
||
switch (eventType) {
|
||
case 'pre_adzan':
|
||
targetTime = dzuhurTime.subtract(const Duration(minutes: 2));
|
||
break;
|
||
case 'adzan':
|
||
targetTime = dzuhurTime;
|
||
break;
|
||
case 'iqomah':
|
||
targetTime = dzuhurTime.add(const Duration(seconds: 45)); // During iqomah
|
||
break;
|
||
case 'jumat_incoming':
|
||
int diff = DateTime.friday - realNow.weekday;
|
||
DateTime nextFriday = realNow.add(Duration(days: diff));
|
||
// Target: next Friday at dzuhur time - 30 minutes
|
||
targetTime = DateTime(nextFriday.year, nextFriday.month, nextFriday.day, dzuhurTime.hour, dzuhurTime.minute).subtract(const Duration(minutes: 30));
|
||
break;
|
||
case 'jumat_khutbah':
|
||
int diff = DateTime.friday - realNow.weekday;
|
||
DateTime nextFriday = realNow.add(Duration(days: diff));
|
||
// Target: next Friday at dzuhur time + 3 minutes (safely past 2-min Adzan)
|
||
targetTime = DateTime(nextFriday.year, nextFriday.month, nextFriday.day, dzuhurTime.hour, dzuhurTime.minute).add(const Duration(minutes: 3));
|
||
break;
|
||
case 'shalat':
|
||
// Shalat mode usually happens after iqomah ends
|
||
final settings = ref.read(settingsProvider);
|
||
targetTime = dzuhurTime.add(Duration(minutes: settings.iqomahDzuhur + 1));
|
||
break;
|
||
default:
|
||
targetTime = realNow;
|
||
}
|
||
|
||
final offset = targetTime.difference(realNow);
|
||
_simulateTimeOffset(offset);
|
||
}
|
||
}
|
||
|
||
class _NavButton extends StatelessWidget {
|
||
final String title;
|
||
final dynamic icon;
|
||
final bool isActive;
|
||
final double scale;
|
||
final VoidCallback onTap;
|
||
|
||
const _NavButton({
|
||
required this.title,
|
||
required this.icon,
|
||
required this.isActive,
|
||
required this.scale,
|
||
required this.onTap,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final s = scale;
|
||
return InkWell(
|
||
onTap: onTap,
|
||
borderRadius: BorderRadius.circular(SacredRadii.lg),
|
||
child: Container(
|
||
width: double.infinity,
|
||
padding: EdgeInsets.symmetric(horizontal: 24 * s, vertical: 24 * s),
|
||
decoration: BoxDecoration(
|
||
color: isActive ? SacredColors.primaryContainer : Colors.transparent,
|
||
borderRadius: BorderRadius.circular(SacredRadii.lg),
|
||
border: isActive ? Border.all(color: SacredColors.primary.withValues(alpha: 0.3)) : null,
|
||
),
|
||
child: Row(
|
||
children: [
|
||
HugeIcon(
|
||
icon: icon,
|
||
color: isActive ? SacredColors.onPrimaryContainer : SacredColors.onSurfaceVariant,
|
||
size: 28 * s,
|
||
),
|
||
SizedBox(width: 20 * s),
|
||
Expanded(
|
||
child: Text(
|
||
title,
|
||
style: GoogleFonts.plusJakartaSans(
|
||
fontSize: 18 * s,
|
||
fontWeight: FontWeight.bold,
|
||
color: isActive ? SacredColors.onPrimaryContainer : SacredColors.onSurfaceVariant,
|
||
letterSpacing: 1 * s,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|