import 'dart:io'; import 'package:auto_route/auto_route.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/ui/components/drawers/support_drawer.dart'; import 'package:selfprivacy/ui/components/pre_styled_buttons/flash_fab.dart'; import 'package:selfprivacy/ui/router/root_destinations.dart'; import 'package:selfprivacy/utils/breakpoints.dart'; class RootScaffoldWithNavigation extends StatelessWidget { const RootScaffoldWithNavigation({ required this.child, required this.title, required this.destinations, this.showBottomBar = true, this.showFab = true, super.key, }); final Widget child; final String title; final bool showBottomBar; final List destinations; final bool showFab; @override // ignore: prefer_expression_function_bodies Widget build(final BuildContext context) { return Scaffold( appBar: Breakpoints.mediumAndUp.isActive(context) ? PreferredSize( preferredSize: const Size.fromHeight(52), child: _RootAppBar(title: title), ) : null, endDrawer: const SupportDrawer(), endDrawerEnableOpenDragGesture: false, body: Row( children: [ if (Breakpoints.medium.isActive(context)) _MainScreenNavigationRail( destinations: destinations, showFab: showFab, ), if (Breakpoints.large.isActive(context)) _MainScreenNavigationDrawer( destinations: destinations, showFab: showFab, ), Expanded(child: child), ], ), bottomNavigationBar: _BottomBar( destinations: destinations, hidden: !(Breakpoints.small.isActive(context) && showBottomBar), key: const Key('bottomBar'), ), floatingActionButton: showFab && Breakpoints.small.isActive(context) && showBottomBar ? const BrandFab() : null, ); } } class _RootAppBar extends StatelessWidget { const _RootAppBar({ required this.title, }); final String title; @override Widget build(final BuildContext context) => AppBar( title: AnimatedSwitcher( duration: const Duration(milliseconds: 200), transitionBuilder: (final Widget child, final Animation animation) => SlideTransition( position: animation.drive( Tween( begin: const Offset(0.0, 0.2), end: Offset.zero, ), ), child: FadeTransition( opacity: animation, child: child, ), ), child: SizedBox( key: ValueKey(title), width: double.infinity, child: Text( title, ), ), ), leading: context.router.pageCount > 1 ? IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => context.router.maybePop(), ) : null, actions: const [ SizedBox.shrink(), ], ); } class _MainScreenNavigationRail extends StatelessWidget { const _MainScreenNavigationRail({ required this.destinations, this.showFab = true, }); final List destinations; final bool showFab; @override Widget build(final BuildContext context) { int? activeIndex = destinations.indexWhere( (final destination) => context.router.isRouteActive(destination.route.routeName), ); final prevActiveIndex = destinations.indexWhere( (final destination) => context.router.stack .any((final route) => route.name == destination.route.routeName), ); if (activeIndex == -1) { if (prevActiveIndex != -1) { activeIndex = prevActiveIndex; } else { activeIndex = 0; } } final isExtended = Breakpoints.large.isActive(context); return LayoutBuilder( builder: (final context, final constraints) => SingleChildScrollView( child: ConstrainedBox( constraints: BoxConstraints(minHeight: constraints.maxHeight), child: IntrinsicHeight( child: NavigationRail( backgroundColor: Colors.transparent, labelType: isExtended ? NavigationRailLabelType.none : NavigationRailLabelType.all, extended: isExtended, leading: showFab ? const BrandFab( extended: false, ) : null, groupAlignment: 0.0, destinations: destinations .map( (final destination) => NavigationRailDestination( icon: Icon(destination.icon), label: Text(destination.label), ), ) .toList(), selectedIndex: activeIndex, onDestinationSelected: (final index) { context.router.replaceAll([destinations[index].route]); }, ), ), ), ), ); } } class _BottomBar extends StatelessWidget { const _BottomBar({ required this.destinations, required this.hidden, super.key, }); final List destinations; final bool hidden; @override Widget build(final BuildContext context) { final prevActiveIndex = destinations.indexWhere( (final destination) => context.router.stack .any((final route) => route.name == destination.route.routeName), ); return AnimatedContainer( duration: const Duration(milliseconds: 500), height: hidden ? 0 : 80, curve: Curves.easeInOutCubicEmphasized, clipBehavior: Clip.antiAlias, decoration: BoxDecoration( color: Theme.of(context).scaffoldBackgroundColor, ), child: Platform.isIOS ? CupertinoTabBar( currentIndex: prevActiveIndex == -1 ? 0 : prevActiveIndex, onTap: (final index) { context.router.replaceAll([destinations[index].route]); }, items: destinations .map( (final destination) => BottomNavigationBarItem( icon: Icon(destination.icon), label: destination.label, ), ) .toList(), ) : NavigationBar( selectedIndex: prevActiveIndex == -1 ? 0 : prevActiveIndex, labelBehavior: NavigationDestinationLabelBehavior.alwaysShow, onDestinationSelected: (final index) { context.router.replaceAll([destinations[index].route]); }, destinations: destinations .map( (final destination) => NavigationDestination( icon: Icon(destination.icon), label: destination.label, ), ) .toList(), ), ); } } class _MainScreenNavigationDrawer extends StatelessWidget { const _MainScreenNavigationDrawer({ required this.destinations, this.showFab = true, }); final List destinations; final bool showFab; @override Widget build(final BuildContext context) { int? activeIndex = destinations.indexWhere( (final destination) => context.router.isRouteActive(destination.route.routeName), ); final prevActiveIndex = destinations.indexWhere( (final destination) => context.router.stack .any((final route) => route.name == destination.route.routeName), ); if (activeIndex == -1) { if (prevActiveIndex != -1) { activeIndex = prevActiveIndex; } else { activeIndex = 0; } } return SizedBox( height: MediaQuery.of(context).size.height, width: 296, child: NavigationDrawer( key: const Key('PrimaryNavigationDrawer'), backgroundColor: Theme.of(context).scaffoldBackgroundColor, surfaceTintColor: Colors.transparent, selectedIndex: activeIndex, onDestinationSelected: (final index) { context.router.replaceAll([destinations[index].route]); }, children: [ const Padding( padding: EdgeInsets.all(16.0), child: BrandFab(extended: true), ), const SizedBox(height: 16), ...destinations.map( (final destination) => NavigationDrawerDestination( icon: Icon(destination.icon), label: Text(destination.label), ), ), ], ), ); } }