import "package:collection/collection.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:navigation_rail_m3e/navigation_rail_m3e.dart"; import "package:nexus/controllers/key_controller.dart"; import "package:nexus/controllers/spaces_controller.dart"; import "package:nexus/models/room.dart"; import "package:nexus/pages/settings_page.dart"; import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/divider_widget.dart"; import "package:nexus/widgets/join_dialog.dart"; import "package:nexus/widgets/room_menu.dart"; class Sidebar extends HookConsumerWidget { final bool isDesktop; const Sidebar({required this.isDesktop, super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final selectedSpaceProvider = KeyController.provider( KeyController.spaceKey, ); final selectedSpaceId = ref.watch(selectedSpaceProvider); final selectedSpaceIdNotifier = ref.watch(selectedSpaceProvider.notifier); final selectedRoomController = KeyController.provider( KeyController.roomKey, ); final selectedRoomId = ref.watch(selectedRoomController); final selectedRoomIdNotifier = ref.watch(selectedRoomController.notifier); final spaces = ref.watch(SpacesController.provider); final indexOfSelected = spaces.indexWhere( (space) => space.id == selectedSpaceId, ); final selectedIndex = indexOfSelected == -1 ? 0 : indexOfSelected; final selectedSpace = spaces.firstWhereOrNull((space) => space.id == selectedSpaceId) ?? spaces.first; final indexOfSelectedRoom = selectedSpace.children .addAll( selectedSpace.subSpaces.map((element) => element.children).flattened, ) .indexWhere((room) => room.metadata?.id == selectedRoomId); final selectedRoomIndex = indexOfSelectedRoom == -1 ? null : indexOfSelectedRoom; List roomsToDestinations(IList rooms) => rooms .map( (room) => NavigationRailM3EDestination( label: room.metadata?.name ?? "Unnamed Room", badgeCount: switch (room.metadata?.unreadNotifications) { 0 || null => room.metadata?.unreadMessages == 0 ? null : 0, int unread => unread, }, icon: AvatarOrHash( room.metadata?.avatar, room.metadata?.name ?? "Unnamed Room", fallback: selectedSpaceId == "dms" ? null : Icon(Icons.numbers), ), ), ) .toList(); return Drawer( width: 330, shape: Border(), child: Row( children: [ Theme( data: Theme.of(context).copyWith( extensions: [ NavigationRailM3ETheme( itemCollapsedHeight: 48, itemVerticalGap: 0, ), ], ), child: Container( color: NavigationRailTokensAdapter(context).containerColor, padding: EdgeInsets.only(top: 16), child: NavigationRailM3E( type: .alwaysCollapse, labelBehavior: .alwaysHide, scrollable: true, onDestinationSelected: (value) { selectedSpaceIdNotifier.set(spaces[value].id); selectedRoomIdNotifier.set( spaces[value].children.firstOrNull?.metadata?.id, ); }, sections: [ .new( destinations: spaces .map( (space) => NavigationRailM3EDestination( badgeCount: switch (space.children .addAll( space.subSpaces .map((element) => element.children) .flattened, ) .fold( 0, (previousValue, room) => previousValue + (room.metadata?.unreadNotifications ?? 0), )) { 0 => space.children .addAll( space.subSpaces .map( (element) => element.children, ) .flattened, ) .any( (room) => room.metadata?.unreadMessages != 0, ) ? 0 : null, int badgeCount => badgeCount, }, short: true, icon: AvatarOrHash( height: 28, space.room?.metadata?.avatar, fallback: space.icon == null ? null : Icon(space.icon), space.title, ), label: space.title, ), ) .toList(), ), ], selectedIndex: selectedIndex, trailingAtBottom: true, trailing: Padding( padding: .symmetric(vertical: 16), child: Column( spacing: 8, children: [ PopupMenuButton( itemBuilder: (_) => [ PopupMenuItem( onTap: () => showDialog( context: context, builder: (_) => JoinDialog(ref), ), child: ListTile( title: Text("Join an existing room (or space)"), leading: Icon(Icons.numbers), ), ), PopupMenuItem( onTap: null, child: ListTile( title: Text("Create a new room"), leading: Icon(Icons.add), ), ), ], icon: Icon(Icons.add), ), IconButton( tooltip: "Explore other rooms", onPressed: null, icon: Icon(Icons.explore), ), IconButton( tooltip: "Open settings", onPressed: () => showDialog( context: context, builder: (_) => SettingsPage(), ), icon: Icon(Icons.settings), ), ], ), ), ), ), ), Expanded( child: Scaffold( backgroundColor: Colors.transparent, appBar: AppBar( leading: AvatarOrHash( selectedSpace.room?.metadata?.avatar, fallback: selectedSpace.icon == null ? null : Icon(selectedSpace.icon), selectedSpace.title, ), title: Text(selectedSpace.title, overflow: .ellipsis), backgroundColor: Colors.transparent, actions: [ RoomMenu( selectedSpace.room, children: selectedSpace.children.addAll( selectedSpace.subSpaces .map((element) => element.children) .flattened, ), ), ], ), body: Theme( data: Theme.of(context).copyWith( extensions: [ NavigationRailM3ETheme( itemExpandedHeight: 48, iconLabelGap: 16, ), ], ), child: NavigationRailM3E( expandedWidth: double.infinity, scrollable: true, background: Colors.transparent, type: .alwaysExpand, selectedIndex: selectedRoomIndex ?? 0, sections: [ .new( header: selectedSpace.room == null ? null : DividerWidget(Text("Rooms")), destinations: roomsToDestinations(selectedSpace.children), ), for (final subSpace in selectedSpace.subSpaces) .new( header: DividerWidget( Row( mainAxisSize: MainAxisSize.min, spacing: 8, children: [ if (subSpace.room.metadata?.avatar != null) AvatarOrHash( subSpace.room.metadata?.avatar, subSpace.room.metadata?.name ?? "Unnamed Room", height: 16, ), Flexible( child: Text( subSpace.room.metadata?.name ?? "Unnamed Space", maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), ), destinations: roomsToDestinations(subSpace.children), ), ], onDestinationSelected: (value) { final children = selectedSpace.children.addAll( selectedSpace.subSpaces .map((element) => element.children) .flattened, ); selectedRoomIdNotifier.set( children[value].metadata?.id, // ); if (!isDesktop) Navigator.of(context).pop(); }, ), ), ), ), ], ), ); } }