import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../data/services/sync_service.dart'; import '../../data/services/sound_service.dart'; import '../../core/enums.dart'; import '../../core/sacred_tokens.dart'; import '../../providers.dart'; import '../admin/admin_screen.dart'; import 'main_screen.dart'; import 'adzan_screen.dart'; import 'iqomah_screen.dart'; import 'black_screen.dart'; import 'slideshow_screen.dart'; import 'jumat_screen.dart'; import 'khutbah_screen.dart'; /// The root view that orchestrates all screen states via AnimatedSwitcher. class HomeView extends ConsumerStatefulWidget { const HomeView({super.key}); @override ConsumerState createState() => _HomeViewState(); } class _HomeViewState extends ConsumerState { static const List _adminUnlockSequence = [ LogicalKeyboardKey.arrowUp, LogicalKeyboardKey.arrowUp, LogicalKeyboardKey.arrowDown, LogicalKeyboardKey.arrowDown, LogicalKeyboardKey.arrowLeft, LogicalKeyboardKey.arrowRight, LogicalKeyboardKey.arrowLeft, LogicalKeyboardKey.arrowRight, LogicalKeyboardKey.select, ]; final FocusNode _homeFocusNode = FocusNode(debugLabel: 'home_tv_root'); final List _recentKeys = []; final List _simulationShortcutKeys = []; Timer? _comboResetTimer; Timer? _simulationShortcutTimer; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _checkAutoSync(); if (mounted) { _homeFocusNode.requestFocus(); } }); } @override void dispose() { _comboResetTimer?.cancel(); _simulationShortcutTimer?.cancel(); _homeFocusNode.dispose(); super.dispose(); } Future _checkAutoSync() async { final schedule = ref.read(todayScheduleProvider); if (schedule == null) { debugPrint('[AutoSync] No schedule found for today! Starting auto-sync...'); final success = await SyncService.instance.syncMonthlyData(); if (success && mounted) { debugPrint('[AutoSync] Success! Invalidating todayScheduleProvider.'); ref.invalidate(todayScheduleProvider); } else { debugPrint('[AutoSync] Failed or data remained empty.'); } } } KeyEventResult _handleTvKey(FocusNode node, KeyEvent event) { if (event is! KeyDownEvent) return KeyEventResult.ignored; final key = event.logicalKey; final isSimulating = ref.read(mockTimeOffsetProvider) != Duration.zero; if (isSimulating && key == LogicalKeyboardKey.arrowLeft) { _comboResetTimer?.cancel(); _recentKeys.clear(); _simulationShortcutTimer?.cancel(); _simulationShortcutTimer = Timer( const Duration(seconds: 2), _resetSimulationShortcut, ); _simulationShortcutKeys.add(key); if (_simulationShortcutKeys.length > 2) { _simulationShortcutKeys.removeAt(0); } if (_matchesSimulationShortcut()) { _resetSimulationShortcut(); ref.read(mockTimeOffsetProvider.notifier).state = Duration.zero; WidgetsBinding.instance.addPostFrameCallback((_) async { if (!mounted) return; await Navigator.of(context).push( MaterialPageRoute( builder: (_) => const AdminScreen( initialTab: 4, focusSelectedTabOnOpen: true, ), ), ); if (mounted) { _homeFocusNode.requestFocus(); } }); } return KeyEventResult.handled; } else if (isSimulating) { _resetSimulationShortcut(); } if (isSimulating && (key == LogicalKeyboardKey.escape || key == LogicalKeyboardKey.goBack || key == LogicalKeyboardKey.browserBack)) { ref.read(mockTimeOffsetProvider.notifier).state = Duration.zero; return KeyEventResult.handled; } if (!_isComboKey(key)) { _resetCombo(); return KeyEventResult.ignored; } _comboResetTimer?.cancel(); _comboResetTimer = Timer(const Duration(seconds: 3), _resetCombo); _recentKeys.add(key); if (_recentKeys.length > _adminUnlockSequence.length) { _recentKeys.removeAt(0); } if (_matchesUnlockSequence()) { _resetCombo(); WidgetsBinding.instance.addPostFrameCallback((_) async { if (!mounted) return; await Navigator.of(context).push( MaterialPageRoute(builder: (_) => const AdminScreen()), ); if (mounted) { _homeFocusNode.requestFocus(); } }); return KeyEventResult.handled; } return KeyEventResult.ignored; } bool _isComboKey(LogicalKeyboardKey key) { return key == LogicalKeyboardKey.arrowUp || key == LogicalKeyboardKey.arrowDown || key == LogicalKeyboardKey.arrowLeft || key == LogicalKeyboardKey.arrowRight || key == LogicalKeyboardKey.select || key == LogicalKeyboardKey.enter; } bool _matchesUnlockSequence() { if (_recentKeys.length != _adminUnlockSequence.length) return false; for (var i = 0; i < _adminUnlockSequence.length; i++) { final current = _recentKeys[i] == LogicalKeyboardKey.enter ? LogicalKeyboardKey.select : _recentKeys[i]; if (current != _adminUnlockSequence[i]) return false; } return true; } void _resetCombo() { _comboResetTimer?.cancel(); _recentKeys.clear(); } bool _matchesSimulationShortcut() { return _simulationShortcutKeys.length == 2 && _simulationShortcutKeys[0] == LogicalKeyboardKey.arrowLeft && _simulationShortcutKeys[1] == LogicalKeyboardKey.arrowLeft; } void _resetSimulationShortcut() { _simulationShortcutTimer?.cancel(); _simulationShortcutKeys.clear(); } @override Widget build(BuildContext context) { // Audio trigger listener ref.listen(screenStateProvider, (previous, next) { if (previous == null) return; // TRIGGER 1: Adzan Beep (Fires precisely when transitioning to Adzan) if (previous.state != ScreenState.adzan && next.state == ScreenState.adzan) { SoundService.instance.playAdzanBeep(); } // TRIGGER 2: 3-Second Iqomah Countdown if (next.state == ScreenState.menujuIqomah && next.iqomahRemaining != null) { // Play precisely on the tick where it is 3 seconds. if (previous.iqomahRemaining?.inSeconds != 3 && next.iqomahRemaining!.inSeconds == 3) { SoundService.instance.playIqomahCountdown(); } } }); final screenData = ref.watch(screenStateProvider); final isMainScreen = ref.watch(isMainScreenProvider); // Determine which screen to display Widget screen; switch (screenData.state) { case ScreenState.normal: case ScreenState.menujuAdzan: if (screenData.isFriday && screenData.nextPrayer?.id == 'dzuhur') { screen = const JumatScreen(key: ValueKey('jumat')); } else { screen = isMainScreen ? const MainScreen(key: ValueKey('main')) : const SlideshowScreen(key: ValueKey('slideshow')); } break; case ScreenState.kembaliNormal: screen = const MainScreen(key: ValueKey('main')); break; case ScreenState.adzan: screen = const AdzanAlertScreen(key: ValueKey('adzan')); break; case ScreenState.menujuIqomah: if (screenData.isFriday && screenData.activePrayer?.id == 'dzuhur') { screen = const KhutbahScreen(key: ValueKey('khutbah')); } else { screen = const IqomahScreen(key: ValueKey('iqomah')); } break; case ScreenState.shalat: screen = const BlackScreen(key: ValueKey('black')); break; } return Focus( autofocus: true, focusNode: _homeFocusNode, onKeyEvent: _handleTvKey, child: Scaffold( backgroundColor: SacredColors.background, body: Stack( children: [ AnimatedSwitcher( duration: const Duration(milliseconds: 800), transitionBuilder: (child, animation) { return FadeTransition(opacity: animation, child: child); }, child: screen, ), ], ), ), ); } }