Refine TV admin navigation and simulation flow

This commit is contained in:
dwindown
2026-03-31 14:00:29 +07:00
parent 6aa315dc91
commit 8774735e38
2 changed files with 3211 additions and 1480 deletions

View File

@@ -41,7 +41,9 @@ class _HomeViewState extends ConsumerState<HomeView> {
final FocusNode _homeFocusNode = FocusNode(debugLabel: 'home_tv_root');
final List<LogicalKeyboardKey> _recentKeys = [];
final List<LogicalKeyboardKey> _simulationShortcutKeys = [];
Timer? _comboResetTimer;
Timer? _simulationShortcutTimer;
@override
void initState() {
@@ -57,6 +59,7 @@ class _HomeViewState extends ConsumerState<HomeView> {
@override
void dispose() {
_comboResetTimer?.cancel();
_simulationShortcutTimer?.cancel();
_homeFocusNode.dispose();
super.dispose();
}
@@ -79,6 +82,50 @@ class _HomeViewState extends ConsumerState<HomeView> {
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;
@@ -135,6 +182,17 @@ class _HomeViewState extends ConsumerState<HomeView> {
_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
@@ -189,8 +247,6 @@ class _HomeViewState extends ConsumerState<HomeView> {
break;
}
final isSimulating = ref.watch(mockTimeOffsetProvider) != Duration.zero;
return Focus(
autofocus: true,
focusNode: _homeFocusNode,
@@ -206,32 +262,6 @@ class _HomeViewState extends ConsumerState<HomeView> {
},
child: screen,
),
if (isSimulating)
Positioned(
right: 64,
bottom: 64,
child: ElevatedButton.icon(
onPressed: () {
ref.read(mockTimeOffsetProvider.notifier).state = Duration.zero;
},
icon: const Icon(Icons.cancel, color: Colors.white),
label: const Text(
'BATALKAN SIMULASI',
style: TextStyle(
fontWeight: FontWeight.bold,
letterSpacing: 2,
color: Colors.white,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red.shade800,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
elevation: 10,
),
),
),
],
),
),