♻️ Fab menu overhaul
This commit is contained in:
		| @@ -10,12 +10,13 @@ import 'package:island/pods/chat/call.dart'; | ||||
| import 'package:island/pods/chat/chat_summary.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/screens/realm/realms.dart'; | ||||
| import 'package:island/services/event_bus.dart'; | ||||
| import 'package:island/services/responsive.dart'; | ||||
| import 'package:island/widgets/account/account_picker.dart'; | ||||
| import 'package:island/widgets/alert.dart'; | ||||
| import 'package:island/widgets/app_scaffold.dart'; | ||||
| import 'package:island/widgets/content/cloud_files.dart'; | ||||
| import 'package:island/widgets/content/sheet.dart'; | ||||
| import 'package:island/widgets/navigation/fab_menu.dart'; | ||||
| import 'package:island/widgets/response.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:relative_time/relative_time.dart'; | ||||
| @@ -333,28 +334,30 @@ class ChatListScreen extends HookConsumerWidget { | ||||
|       tabController.addListener(() { | ||||
|         selectedTab.value = tabController.index; | ||||
|       }); | ||||
|       return null; | ||||
|  | ||||
|       // Listen for chat rooms refresh events | ||||
|       final subscription = eventBus.on<ChatRoomsRefreshEvent>().listen((event) { | ||||
|         ref.invalidate(chatroomsJoinedProvider); | ||||
|       }); | ||||
|  | ||||
|       return () { | ||||
|         subscription.cancel(); | ||||
|       }; | ||||
|     }, [tabController]); | ||||
|  | ||||
|     Future<void> createDirectMessage() async { | ||||
|       final result = await showModalBottomSheet( | ||||
|         context: context, | ||||
|         useRootNavigator: true, | ||||
|         isScrollControlled: true, | ||||
|         builder: (context) => const AccountPickerSheet(), | ||||
|       ); | ||||
|       if (result == null) return; | ||||
|       final client = ref.read(apiClientProvider); | ||||
|       try { | ||||
|         await client.post( | ||||
|           '/sphere/chat/direct', | ||||
|           data: {'related_user_id': result.id}, | ||||
|         ); | ||||
|         ref.invalidate(chatroomsJoinedProvider); | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } | ||||
|     } | ||||
|     useEffect(() { | ||||
|       // Set FAB type to chat | ||||
|       final fabMenuNotifier = ref.read(fabMenuTypeProvider.notifier); | ||||
|       WidgetsBinding.instance.addPostFrameCallback((_) { | ||||
|         fabMenuNotifier.state = FabMenuType.chat; | ||||
|       }); | ||||
|       return () { | ||||
|         // Clean up: reset FAB type to main | ||||
|         WidgetsBinding.instance.addPostFrameCallback((_) { | ||||
|           fabMenuNotifier.state = FabMenuType.main; | ||||
|         }); | ||||
|       }; | ||||
|     }, []); | ||||
|  | ||||
|     if (isAside) { | ||||
|       return Card( | ||||
| @@ -491,43 +494,7 @@ class ChatListScreen extends HookConsumerWidget { | ||||
|           const Gap(8), | ||||
|         ], | ||||
|       ), | ||||
|       floatingActionButton: FloatingActionButton( | ||||
|         onPressed: () { | ||||
|           showModalBottomSheet( | ||||
|             context: context, | ||||
|             useRootNavigator: true, | ||||
|             builder: | ||||
|                 (context) => Column( | ||||
|                   mainAxisSize: MainAxisSize.min, | ||||
|                   crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                   children: [ | ||||
|                     ListTile( | ||||
|                       title: const Text('createChatRoom').tr(), | ||||
|                       leading: const Icon(Symbols.add), | ||||
|                       onTap: () { | ||||
|                         Navigator.pop(context); | ||||
|                         context.pushNamed('chatNew').then((value) { | ||||
|                           if (value != null) { | ||||
|                             ref.invalidate(chatroomsJoinedProvider); | ||||
|                           } | ||||
|                         }); | ||||
|                       }, | ||||
|                     ), | ||||
|                     ListTile( | ||||
|                       title: const Text('createDirectMessage').tr(), | ||||
|                       leading: const Icon(Symbols.person), | ||||
|                       onTap: () { | ||||
|                         Navigator.pop(context); | ||||
|                         createDirectMessage(); | ||||
|                       }, | ||||
|                     ), | ||||
|                     Gap(MediaQuery.of(context).padding.bottom + 16), | ||||
|                   ], | ||||
|                 ), | ||||
|           ); | ||||
|         }, | ||||
|         child: const Icon(Symbols.add), | ||||
|       ), | ||||
|       floatingActionButton: const FabMenu(), | ||||
|       body: ChatListBodyWidget( | ||||
|         isFloating: false, | ||||
|         tabController: tabController, | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @@ -9,6 +10,7 @@ import 'package:island/widgets/alert.dart'; | ||||
| import 'package:island/widgets/app_scaffold.dart'; | ||||
| import 'package:island/widgets/content/cloud_files.dart'; | ||||
| import 'package:island/widgets/content/sheet.dart'; | ||||
| import 'package:island/widgets/navigation/fab_menu.dart'; | ||||
| import 'package:island/widgets/response.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||
| @@ -41,6 +43,20 @@ class RealmListScreen extends HookConsumerWidget { | ||||
|     final realms = ref.watch(realmsJoinedProvider); | ||||
|     final realmInvites = ref.watch(realmInvitesProvider); | ||||
|  | ||||
|     useEffect(() { | ||||
|       // Set FAB type to realm | ||||
|       final fabMenuNotifier = ref.read(fabMenuTypeProvider.notifier); | ||||
|       WidgetsBinding.instance.addPostFrameCallback((_) { | ||||
|         fabMenuNotifier.state = FabMenuType.realm; | ||||
|       }); | ||||
|       return () { | ||||
|         // Clean up: reset FAB type to main | ||||
|         WidgetsBinding.instance.addPostFrameCallback((_) { | ||||
|           fabMenuNotifier.state = FabMenuType.main; | ||||
|         }); | ||||
|       }; | ||||
|     }, []); | ||||
|  | ||||
|     return AppScaffold( | ||||
|       isNoBackground: false, | ||||
|       appBar: AppBar( | ||||
| @@ -78,17 +94,7 @@ class RealmListScreen extends HookConsumerWidget { | ||||
|           const Gap(8), | ||||
|         ], | ||||
|       ), | ||||
|       floatingActionButton: FloatingActionButton( | ||||
|         heroTag: const Key("realms-page-fab"), | ||||
|         child: const Icon(Symbols.add), | ||||
|         onPressed: () { | ||||
|           context.pushNamed('realmNew').then((value) { | ||||
|             if (value != null) { | ||||
|               ref.invalidate(realmsJoinedProvider); | ||||
|             } | ||||
|           }); | ||||
|         }, | ||||
|       ), | ||||
|       floatingActionButton: const FabMenu(), | ||||
|       body: ExtendedRefreshIndicator( | ||||
|         child: realms.when( | ||||
|           data: | ||||
|   | ||||
| @@ -3,14 +3,14 @@ import 'dart:ui'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:go_router/go_router.dart'; | ||||
| import 'package:island/screens/notification.dart'; | ||||
| import 'package:island/services/responsive.dart'; | ||||
| import 'package:island/widgets/navigation/conditional_bottom_nav.dart'; | ||||
| import 'package:island/widgets/post/compose_dialog.dart'; | ||||
| import 'package:island/widgets/navigation/fab_menu.dart'; | ||||
| import 'package:material_symbols_icons/symbols.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
|  | ||||
| final currentRouteProvider = StateProvider<String?>((ref) => null); | ||||
|  | ||||
| @@ -120,6 +120,10 @@ class TabsScreen extends HookConsumerWidget { | ||||
|                       .toList(), | ||||
|               selectedIndex: currentIndex, | ||||
|               onDestinationSelected: onDestinationSelected, | ||||
|               trailingAtBottom: true, | ||||
|               trailing: const FabMenu( | ||||
|                 elevation: 0, | ||||
|               ).padding(bottom: MediaQuery.of(context).padding.bottom + 16), | ||||
|             ), | ||||
|             Expanded( | ||||
|               child: ClipRRect( | ||||
| @@ -145,78 +149,9 @@ class TabsScreen extends HookConsumerWidget { | ||||
|         ), | ||||
|         child: child ?? const SizedBox.shrink(), | ||||
|       ), | ||||
|       floatingActionButton: | ||||
|           shouldShowFab | ||||
|               ? FloatingActionButton( | ||||
|                 child: const Icon(Symbols.menu), | ||||
|                 onPressed: () { | ||||
|                   showModalBottomSheet( | ||||
|                     context: context, | ||||
|                     builder: (BuildContext context) { | ||||
|                       return Column( | ||||
|                         mainAxisSize: MainAxisSize.min, | ||||
|                         children: [ | ||||
|                           const Gap(24), | ||||
|                           ListTile( | ||||
|                             contentPadding: const EdgeInsets.symmetric( | ||||
|                               horizontal: 24, | ||||
|                             ), | ||||
|                             leading: const Icon(Symbols.post_add_rounded), | ||||
|                             title: Text('postCompose'.tr()), | ||||
|                             onTap: () async { | ||||
|                               Navigator.of(context).pop(); | ||||
|                               await PostComposeDialog.show(context); | ||||
|                             }, | ||||
|                           ), | ||||
|                           ListTile( | ||||
|                             contentPadding: const EdgeInsets.symmetric( | ||||
|                               horizontal: 24, | ||||
|                             ), | ||||
|                             leading: const Icon(Symbols.bubble_chart), | ||||
|                             title: Text('aiThoughtTitle'.tr()), | ||||
|                             onTap: () async { | ||||
|                               Navigator.of(context).pop(); | ||||
|                               context.pushNamed('thought'); | ||||
|                             }, | ||||
|                           ), | ||||
|                           Consumer( | ||||
|                             builder: (context, ref, _) { | ||||
|                               final notificationCount = ref.watch( | ||||
|                                 notificationUnreadCountNotifierProvider, | ||||
|                               ); | ||||
|                               return ListTile( | ||||
|                                 contentPadding: const EdgeInsets.symmetric( | ||||
|                                   horizontal: 24, | ||||
|                                 ), | ||||
|                                 leading: const Icon(Symbols.notifications), | ||||
|                                 trailing: Badge( | ||||
|                                   label: Text(notificationCount.toString()), | ||||
|                                   isLabelVisible: notificationCount.value! > 0, | ||||
|                                 ), | ||||
|                                 title: Text('notifications'.tr()), | ||||
|                                 onTap: () async { | ||||
|                                   Navigator.of(context).pop(); | ||||
|                                   showModalBottomSheet( | ||||
|                                     context: context, | ||||
|                                     isScrollControlled: true, | ||||
|                                     useRootNavigator: true, | ||||
|                                     builder: | ||||
|                                         (context) => const NotificationSheet(), | ||||
|                                   ); | ||||
|                                 }, | ||||
|                               ); | ||||
|                             }, | ||||
|                           ), | ||||
|                           Gap(MediaQuery.of(context).padding.bottom + 16), | ||||
|                         ], | ||||
|                       ); | ||||
|                     }, | ||||
|                   ); | ||||
|                 }, | ||||
|               ) | ||||
|               : null, | ||||
|       floatingActionButton: shouldShowFab ? const FabMenu() : null, | ||||
|       floatingActionButtonLocation: | ||||
|           shouldShowFab ? TabbedFabLocation(context) : null, | ||||
|           shouldShowFab ? _DockedFabLocation(context) : null, | ||||
|       bottomNavigationBar: ConditionalBottomNav( | ||||
|         child: ClipRRect( | ||||
|           borderRadius: BorderRadius.only( | ||||
| @@ -269,10 +204,10 @@ class TabsScreen extends HookConsumerWidget { | ||||
|   } | ||||
| } | ||||
|  | ||||
| class TabbedFabLocation extends FloatingActionButtonLocation { | ||||
| class _DockedFabLocation extends FloatingActionButtonLocation { | ||||
|   final BuildContext context; | ||||
|  | ||||
|   const TabbedFabLocation(this.context); | ||||
|   const _DockedFabLocation(this.context); | ||||
|  | ||||
|   @override | ||||
|   Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user