import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/controller.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/manager/app_manager.dart'; import 'package:fl_clash/models/common.dart'; import 'package:fl_clash/providers/providers.dart'; import 'package:fl_clash/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; typedef OnSelected = void Function(int index); class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return HomeBackScopeContainer( child: AppSidebarContainer( child: Material( color: context.colorScheme.surface, child: Consumer( builder: (context, ref, child) { final state = ref.watch(navigationStateProvider); final systemUiOverlayStyle = ref.read( systemUiOverlayStyleStateProvider, ); final isMobile = state.viewMode == ViewMode.mobile; final navigationItems = state.navigationItems; final currentIndex = state.currentIndex; final bottomNavigationBar = NavigationBarTheme( data: _NavigationBarDefaultsM3(context), child: NavigationBar( destinations: navigationItems .map( (e) => NavigationDestination( icon: e.icon, label: Intl.message(e.label.name), ), ) .toList(), onDestinationSelected: (index) { appController.toPage(navigationItems[index].label); }, selectedIndex: currentIndex, ), ); if (isMobile) { return AnnotatedRegion( value: systemUiOverlayStyle.copyWith( systemNavigationBarColor: context.colorScheme.surfaceContainer, ), child: Column( children: [ Flexible( flex: 1, child: MediaQuery.removePadding( removeTop: false, removeBottom: true, removeLeft: true, removeRight: true, context: context, child: child!, ), ), MediaQuery.removePadding( removeTop: true, removeBottom: false, removeLeft: true, removeRight: true, context: context, child: bottomNavigationBar, ), ], ), ); } else { return child!; } }, child: Consumer( builder: (_, ref, _) { final navigationItems = ref .watch(currentNavigationItemsStateProvider) .value; final isMobile = ref.watch(isMobileViewProvider); return _HomePageView( navigationItems: navigationItems, pageBuilder: (_, index) { final navigationItem = navigationItems[index]; final navigationView = navigationItem.builder(context); final view = KeepScope( keep: navigationItem.keep, child: isMobile ? navigationView : Navigator( pages: [MaterialPage(child: navigationView)], onDidRemovePage: (_) {}, ), ); return view; }, ); }, ), ), ), ), ); } } class _HomePageView extends ConsumerStatefulWidget { final IndexedWidgetBuilder pageBuilder; final List navigationItems; const _HomePageView({ required this.pageBuilder, required this.navigationItems, }); @override ConsumerState createState() => _HomePageViewState(); } class _HomePageViewState extends ConsumerState<_HomePageView> { late PageController _pageController; @override initState() { super.initState(); _pageController = PageController(initialPage: _pageIndex); ref.listenManual(currentPageLabelProvider, (prev, next) { if (prev != next) { _toPage(next); } }); } @override void didUpdateWidget(covariant _HomePageView oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.navigationItems.length != widget.navigationItems.length) { _updatePageController(); } } int get _pageIndex { final pageLabel = ref.read(currentPageLabelProvider); return widget.navigationItems.indexWhere((item) => item.label == pageLabel); } Future _toPage( PageLabel pageLabel, [ bool ignoreAnimateTo = false, ]) async { if (!mounted) { return; } final index = widget.navigationItems.indexWhere( (item) => item.label == pageLabel, ); if (index == -1) { return; } final isAnimateToPage = ref.read(appSettingProvider).isAnimateToPage; final isMobile = ref.read(isMobileViewProvider); if (isAnimateToPage && isMobile && !ignoreAnimateTo) { await _pageController.animateToPage( index, duration: kTabScrollDuration, curve: Curves.easeOut, ); } else { _pageController.jumpToPage(index); } } void _updatePageController() { final pageLabel = ref.read(currentPageLabelProvider); _toPage(pageLabel, true); } @override void dispose() { _pageController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final itemCount = ref.watch( currentNavigationItemsStateProvider.select((state) => state.value.length), ); return PageView.builder( controller: _pageController, physics: const NeverScrollableScrollPhysics(), itemCount: itemCount, itemBuilder: (context, index) { return widget.pageBuilder(context, index); }, ); } } class _NavigationBarDefaultsM3 extends NavigationBarThemeData { _NavigationBarDefaultsM3(this.context) : super( height: 80.0, elevation: 3.0, labelBehavior: NavigationDestinationLabelBehavior.alwaysShow, ); final BuildContext context; late final ColorScheme _colors = Theme.of(context).colorScheme; late final TextTheme _textTheme = Theme.of(context).textTheme; @override Color? get backgroundColor => _colors.surfaceContainer; @override Color? get shadowColor => Colors.transparent; @override Color? get surfaceTintColor => Colors.transparent; @override WidgetStateProperty? get iconTheme { return WidgetStateProperty.resolveWith((Set states) { return IconThemeData( size: 24.0, color: states.contains(WidgetState.disabled) ? _colors.onSurfaceVariant.opacity38 : states.contains(WidgetState.selected) ? _colors.onSecondaryContainer : _colors.onSurfaceVariant, ); }); } @override Color? get indicatorColor => _colors.secondaryContainer; @override ShapeBorder? get indicatorShape => const StadiumBorder(); @override WidgetStateProperty? get labelTextStyle { return WidgetStateProperty.resolveWith((Set states) { final TextStyle style = _textTheme.labelMedium!; return style.apply( overflow: TextOverflow.ellipsis, color: states.contains(WidgetState.disabled) ? _colors.onSurfaceVariant.opacity38 : states.contains(WidgetState.selected) ? _colors.onSurface : _colors.onSurfaceVariant, ); }); } } class HomeBackScopeContainer extends ConsumerWidget { final Widget child; const HomeBackScopeContainer({super.key, required this.child}); @override Widget build(BuildContext context, ref) { return CommonPopScope( onPop: (context) async { final pageLabel = ref.read(currentPageLabelProvider); final realContext = GlobalObjectKey(pageLabel).currentContext ?? context; final canPop = Navigator.canPop(realContext); if (canPop) { Navigator.of(realContext).pop(); } else { await appController.handleBackOrExit(); } return false; }, child: child, ); } }