mirror of
				https://gitlab.com/foxixus/neomovies_mobile.git
				synced 2025-10-30 09:58:50 +05:00 
			
		
		
		
	
		
			
				
	
	
		
			163 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			163 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'package:flutter/material.dart';
 | ||
| import 'package:flutter/services.dart';
 | ||
| 
 | ||
| /// Глобальный менеджер фокуса для управления навигацией между элементами интерфейса
 | ||
| class GlobalFocusManager {
 | ||
|   static final GlobalFocusManager _instance = GlobalFocusManager._internal();
 | ||
|   factory GlobalFocusManager() => _instance;
 | ||
|   GlobalFocusManager._internal();
 | ||
| 
 | ||
|   // Фокус ноды для разных элементов интерфейса
 | ||
|   FocusNode? _appBarFocusNode;
 | ||
|   FocusNode? _contentFocusNode;
 | ||
|   FocusNode? _bottomNavFocusNode;
 | ||
|   
 | ||
|   // Текущее состояние фокуса
 | ||
|   FocusArea _currentFocusArea = FocusArea.content;
 | ||
|   
 | ||
|   // Callback для уведомления об изменении фокуса
 | ||
|   VoidCallback? _onFocusChanged;
 | ||
| 
 | ||
|   void initialize({
 | ||
|     FocusNode? appBarFocusNode,
 | ||
|     FocusNode? contentFocusNode,
 | ||
|     FocusNode? bottomNavFocusNode,
 | ||
|     VoidCallback? onFocusChanged,
 | ||
|   }) {
 | ||
|     _appBarFocusNode = appBarFocusNode;
 | ||
|     _contentFocusNode = contentFocusNode;
 | ||
|     _bottomNavFocusNode = bottomNavFocusNode;
 | ||
|     _onFocusChanged = onFocusChanged;
 | ||
|   }
 | ||
| 
 | ||
|   /// Обработка глобальных клавиш
 | ||
|   KeyEventResult handleGlobalKey(KeyEvent event) {
 | ||
|     if (event is KeyDownEvent) {
 | ||
|       switch (event.logicalKey) {
 | ||
|         case LogicalKeyboardKey.escape:
 | ||
|         case LogicalKeyboardKey.goBack:
 | ||
|           _focusAppBar();
 | ||
|           return KeyEventResult.handled;
 | ||
|           
 | ||
|         case LogicalKeyboardKey.arrowUp:
 | ||
|           if (_currentFocusArea == FocusArea.appBar) {
 | ||
|             _focusContent();
 | ||
|             return KeyEventResult.handled;
 | ||
|           }
 | ||
|           break;
 | ||
|           
 | ||
|         case LogicalKeyboardKey.arrowDown:
 | ||
|           if (_currentFocusArea == FocusArea.content) {
 | ||
|             _focusBottomNav();
 | ||
|             return KeyEventResult.handled;
 | ||
|           } else if (_currentFocusArea == FocusArea.appBar) {
 | ||
|             _focusContent();
 | ||
|             return KeyEventResult.handled;
 | ||
|           }
 | ||
|           break;
 | ||
|       }
 | ||
|     }
 | ||
|     return KeyEventResult.ignored;
 | ||
|   }
 | ||
| 
 | ||
|   void _focusAppBar() {
 | ||
|     if (_appBarFocusNode != null) {
 | ||
|       _currentFocusArea = FocusArea.appBar;
 | ||
|       _appBarFocusNode!.requestFocus();
 | ||
|       _onFocusChanged?.call();
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   void _focusContent() {
 | ||
|     if (_contentFocusNode != null) {
 | ||
|       _currentFocusArea = FocusArea.content;
 | ||
|       _contentFocusNode!.requestFocus();
 | ||
|       _onFocusChanged?.call();
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   void _focusBottomNav() {
 | ||
|     if (_bottomNavFocusNode != null) {
 | ||
|       _currentFocusArea = FocusArea.bottomNav;
 | ||
|       _bottomNavFocusNode!.requestFocus();
 | ||
|       _onFocusChanged?.call();
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   /// Установить фокус на контент (для использования извне)
 | ||
|   void focusContent() => _focusContent();
 | ||
|   
 | ||
|   /// Установить фокус на навбар (для использования извне)
 | ||
|   void focusAppBar() => _focusAppBar();
 | ||
|   
 | ||
|   /// Получить текущую область фокуса
 | ||
|   FocusArea get currentFocusArea => _currentFocusArea;
 | ||
|   
 | ||
|   /// Проверить, находится ли фокус в контенте
 | ||
|   bool get isContentFocused => _currentFocusArea == FocusArea.content;
 | ||
|   
 | ||
|   /// Проверить, находится ли фокус в навбаре
 | ||
|   bool get isAppBarFocused => _currentFocusArea == FocusArea.appBar;
 | ||
| 
 | ||
|   void dispose() {
 | ||
|     _appBarFocusNode = null;
 | ||
|     _contentFocusNode = null;
 | ||
|     _bottomNavFocusNode = null;
 | ||
|     _onFocusChanged = null;
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| /// Области фокуса в приложении
 | ||
| enum FocusArea {
 | ||
|   appBar,
 | ||
|   content,
 | ||
|   bottomNav,
 | ||
| }
 | ||
| 
 | ||
| /// Виджет-обертка для глобального управления фокусом
 | ||
| class GlobalFocusWrapper extends StatefulWidget {
 | ||
|   final Widget child;
 | ||
|   final FocusNode? contentFocusNode;
 | ||
| 
 | ||
|   const GlobalFocusWrapper({
 | ||
|     super.key,
 | ||
|     required this.child,
 | ||
|     this.contentFocusNode,
 | ||
|   });
 | ||
| 
 | ||
|   @override
 | ||
|   State<GlobalFocusWrapper> createState() => _GlobalFocusWrapperState();
 | ||
| }
 | ||
| 
 | ||
| class _GlobalFocusWrapperState extends State<GlobalFocusWrapper> {
 | ||
|   final GlobalFocusManager _focusManager = GlobalFocusManager();
 | ||
|   late final FocusNode _wrapperFocusNode;
 | ||
| 
 | ||
|   @override
 | ||
|   void initState() {
 | ||
|     super.initState();
 | ||
|     _wrapperFocusNode = FocusNode();
 | ||
|     
 | ||
|     // Инициализируем глобальный менеджер фокуса
 | ||
|     _focusManager.initialize(
 | ||
|       contentFocusNode: widget.contentFocusNode ?? _wrapperFocusNode,
 | ||
|       onFocusChanged: () => setState(() {}),
 | ||
|     );
 | ||
|   }
 | ||
| 
 | ||
|   @override
 | ||
|   void dispose() {
 | ||
|     _wrapperFocusNode.dispose();
 | ||
|     super.dispose();
 | ||
|   }
 | ||
| 
 | ||
|   @override
 | ||
|   Widget build(BuildContext context) {
 | ||
|     return Focus(
 | ||
|       focusNode: _wrapperFocusNode,
 | ||
|       onKeyEvent: (node, event) => _focusManager.handleGlobalKey(event),
 | ||
|       child: widget.child,
 | ||
|     );
 | ||
|   }
 | ||
| }
 |