diff --git a/lib/widgets/avatar_or_hash.dart b/lib/widgets/avatar_or_hash.dart index e24dd3d..4931684 100644 --- a/lib/widgets/avatar_or_hash.dart +++ b/lib/widgets/avatar_or_hash.dart @@ -11,15 +11,11 @@ class AvatarOrHash extends ConsumerWidget { final Uri? avatar; final String title; final Widget? fallback; - final bool hasBadge; - final int badgeNumber; final double height; const AvatarOrHash( this.avatar, this.title, { this.fallback, - this.badgeNumber = 0, - this.hasBadge = false, this.height = 24, super.key, }); @@ -44,30 +40,24 @@ class AvatarOrHash extends ConsumerWidget { width: height, height: height, child: Center( - child: Badge( - isLabelVisible: hasBadge, - label: badgeNumber != 0 ? Text(badgeNumber.toString()) : null, - smallSize: 12, - backgroundColor: Theme.of(context).colorScheme.primary, - child: ClipRRect( - borderRadius: .all(.circular((height - 8) / 2.5)), - child: SizedBox( - width: height, - height: height, - child: parsedAvatar == null - ? fallback ?? box - : Image( - image: CachedNetworkImage( - parsedAvatar.toString(), - ref.watch(CrossCacheController.provider), - headers: ref.headers, - ), - fit: .cover, - loadingBuilder: (_, child, loadingProgress) => - loadingProgress == null ? child : fallback ?? box, - errorBuilder: (_, _, _) => fallback ?? box, + child: ClipRRect( + borderRadius: .all(.circular((height - 8) / 2.5)), + child: SizedBox( + width: height, + height: height, + child: parsedAvatar == null + ? fallback ?? box + : Image( + image: CachedNetworkImage( + parsedAvatar.toString(), + ref.watch(CrossCacheController.provider), + headers: ref.headers, ), - ), + fit: .cover, + loadingBuilder: (_, child, loadingProgress) => + loadingProgress == null ? child : fallback ?? box, + errorBuilder: (_, _, _) => fallback ?? box, + ), ), ), ), diff --git a/lib/widgets/member_list.dart b/lib/widgets/member_list.dart index fbb3555..bdac414 100644 --- a/lib/widgets/member_list.dart +++ b/lib/widgets/member_list.dart @@ -28,10 +28,10 @@ class MemberList extends HookConsumerWidget { ), ); - final options = { - "Joined": MembershipStatus.join, - "Invited": MembershipStatus.invite, - "Banned": MembershipStatus.ban, + final options = { + "Joined": .join, + "Invited": .invite, + "Banned": .ban, }; return Drawer( @@ -53,7 +53,7 @@ class MemberList extends HookConsumerWidget { ], ), M3EToggleButtonGroup( - type: M3EButtonGroupType.connected, + type: .connected, selectedIndex: options.values.toIList().indexOf(status.value), onSelectedIndexChanged: (index) => status.value = options.values.elementAt(index ?? 0), @@ -99,7 +99,7 @@ class MemberList extends HookConsumerWidget { .compareTo(a?.key ?? double.infinity), )) ...[ Padding( - padding: EdgeInsets.symmetric(horizontal: 4), + padding: .symmetric(horizontal: 4), child: DividerText( powerLevel == null ? "Creators" diff --git a/lib/widgets/sidebar.dart b/lib/widgets/sidebar.dart index dcbb671..6fb4780 100644 --- a/lib/widgets/sidebar.dart +++ b/lib/widgets/sidebar.dart @@ -1,6 +1,7 @@ import "package:collection/collection.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/widgets/avatar_or_hash.dart"; @@ -43,82 +44,110 @@ class Sidebar extends HookConsumerWidget { : indexOfSelectedRoom; return Drawer( + width: 340, shape: Border(), child: Row( children: [ - NavigationRail( - scrollable: true, - onDestinationSelected: (value) { - selectedSpaceIdNotifier.set(spaces[value].id); - selectedRoomIdNotifier.set( - spaces[value].children.firstOrNull?.metadata?.id, - ); - }, - destinations: spaces - .map( - (space) => NavigationRailDestination( - icon: AvatarOrHash( - space.room?.metadata?.avatar, - fallback: space.icon == null ? null : Icon(space.icon), - space.title, - hasBadge: space.children.any( - (room) => room.metadata?.unreadMessages != 0, - ), - badgeNumber: space.children.fold( - 0, - (previousValue, room) => - previousValue + - (room.metadata?.unreadNotifications ?? 0), - ), - ), - label: Text(space.title), - padding: .only(top: 4), - ), - ) - .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: null, - // () => Navigator.of( - // context, - // ).push(MaterialPageRoute(builder: (_) => SettingsPage())), - icon: Icon(Icons.settings), + Theme( + data: Theme.of(context).copyWith( + extensions: [ + NavigationRailM3ETheme( + itemCollapsedHeight: 48, + itemVerticalGap: 0, + ), + ], + ), + child: Padding( + 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.fold( + 0, + (previousValue, room) => + previousValue + + (room.metadata?.unreadNotifications ?? 0), + )) { + 0 => + space.children.any( + (room) => + room.metadata?.unreadMessages != 0, + ) + ? 0 + : null, + int badgeCount => badgeCount, + }, + short: true, + icon: AvatarOrHash( + 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: null, + // () => Navigator.of( + // context, + // ).push(MaterialPageRoute(builder: (_) => SettingsPage())), + icon: Icon(Icons.settings), + ), + ], + ), + ), ), ), ), @@ -143,34 +172,54 @@ class Sidebar extends HookConsumerWidget { ), ], ), - body: NavigationRail( - scrollable: true, - backgroundColor: Colors.transparent, - extended: true, - selectedIndex: selectedRoomIndex, - destinations: selectedSpace.children - .map( - (room) => NavigationRailDestination( - label: Text(room.metadata?.name ?? "Unnamed Room"), - icon: AvatarOrHash( - room.metadata?.avatar, - hasBadge: room.metadata?.unreadMessages != 0, - badgeNumber: room.metadata?.unreadNotifications ?? 0, - room.metadata?.name ?? "Unnamed Room", - fallback: selectedSpaceId == "dms" - ? null - : Icon(Icons.numbers), - // space.client.headers, - ), - ), - ) - .toList(), - onDestinationSelected: (value) { - selectedRoomIdNotifier.set( - selectedSpace.children[value].metadata?.id, - ); - if (!isDesktop) Navigator.of(context).pop(); - }, + body: Theme( + data: Theme.of(context).copyWith( + extensions: [ + NavigationRailM3ETheme( + itemExpandedHeight: 48, + iconLabelGap: 16, + ), + ], + ), + child: NavigationRailM3E( + expandedWidth: 360, + scrollable: true, + background: Colors.transparent, + type: .alwaysExpand, + selectedIndex: selectedRoomIndex ?? 0, + sections: [ + .new( + destinations: selectedSpace.children + .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), + // space.client.headers, + ), + ), + ) + .toList(), + ), + ], + onDestinationSelected: (value) { + selectedRoomIdNotifier.set( + selectedSpace.children[value].metadata?.id, + ); + if (!isDesktop) Navigator.of(context).pop(); + }, + ), ), ), ), diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 45c0f94..d126d18 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -15,6 +16,9 @@ #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) dynamic_color_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin"); + dynamic_color_plugin_register_with_registrar(dynamic_color_registrar); g_autoptr(FlPluginRegistrar) dynamic_system_colors_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin"); dynamic_color_plugin_register_with_registrar(dynamic_system_colors_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 4e3b41b..89af22f 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + dynamic_color dynamic_system_colors file_selector_linux media_kit_libs_linux diff --git a/pubspec.lock b/pubspec.lock index 385367d..b9d18e6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -121,6 +121,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.12.6" + button_m3e: + dependency: transitive + description: + name: button_m3e + sha256: "6754ddeb9068ad2005bd26d5ceabc41268029465095686d7d228296c2e706909" + url: "https://pub.dev" + source: hosted + version: "0.1.2" characters: dependency: transitive description: @@ -273,6 +281,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + dynamic_color: + dependency: transitive + description: + name: dynamic_color + sha256: "43a5a6679649a7731ab860334a5812f2067c2d9ce6452cf069c5e0c25336c17c" + url: "https://pub.dev" + source: hosted + version: "1.8.1" dynamic_polls: dependency: "direct main" description: @@ -307,6 +323,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.8" + fab_m3e: + dependency: transitive + description: + name: fab_m3e + sha256: e4f5abfa3c8c092005449d56dcac45b85e2dbe9c32789d672c5ed71428e43b59 + url: "https://pub.dev" + source: hosted + version: "0.1.1" fake_async: dependency: transitive description: @@ -591,6 +615,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + icon_button_m3e: + dependency: transitive + description: + name: icon_button_m3e + sha256: c4524d6141a468679821bbb635b833ac6831925d8a6ae4a4511430b0e4ab9c67 + url: "https://pub.dev" + source: hosted + version: "0.2.1" idb_shim: dependency: transitive description: @@ -760,6 +792,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.3" + m3e_design: + dependency: transitive + description: + name: m3e_design + sha256: "15ff0ef4c43553d855c5e866a9aee8231d44919fe2bb354b1259337bdfd659b4" + url: "https://pub.dev" + source: hosted + version: "0.2.1" matcher: dependency: transitive description: @@ -888,6 +928,15 @@ packages: url: "https://pub.dev" source: hosted version: "0.17.6" + navigation_rail_m3e: + dependency: "direct main" + description: + path: "packages/navigation_rail_m3e" + ref: HEAD + resolved-ref: a403b67b41f6fba7f91273bfd52b4f835872c004 + url: "https://github.com/Henry-Hiles/material_3_expressive" + source: git + version: "0.3.5" node_preamble: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 37b4ed4..17034a1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -66,6 +66,10 @@ dependencies: measure_size: ^5.0.2 material_segmented_list: ^1.0.5 m3e_buttons: ^0.0.3 + navigation_rail_m3e: + git: + url: https://github.com/Henry-Hiles/material_3_expressive + path: packages/navigation_rail_m3e dev_dependencies: build_runner: 2.15.0 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8c54692..b507c00 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -15,6 +16,8 @@ #include void RegisterPlugins(flutter::PluginRegistry* registry) { + DynamicColorPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); DynamicColorPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); FileSelectorWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index f769d6e..b26701d 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + dynamic_color dynamic_system_colors file_selector_windows media_kit_libs_windows_video