Compare commits

..
9 changed files with 31 additions and 104 deletions

View file

@ -13,7 +13,6 @@ import "package:nexus/models/capabilities.dart";
import "package:nexus/models/content/message.dart"; import "package:nexus/models/content/message.dart";
import "package:nexus/models/event.dart"; import "package:nexus/models/event.dart";
import "package:nexus/models/event_context.dart"; import "package:nexus/models/event_context.dart";
import "package:nexus/models/mentions_response.dart";
import "package:nexus/models/gomuks_config.dart"; import "package:nexus/models/gomuks_config.dart";
import "package:nexus/models/oauth_auth_code_response.dart"; import "package:nexus/models/oauth_auth_code_response.dart";
import "package:nexus/models/open_graph_data.dart"; import "package:nexus/models/open_graph_data.dart";
@ -215,8 +214,11 @@ class ClientController extends AsyncNotifier<int> {
return .new(response?.map((event) => .fromJson(event))); return .new(response?.map((event) => .fromJson(event)));
} }
Future<MentionsResponse> getMentions(GetMentionsRequest request) async => Future<IList<Event>> getMentions(GetMentionsRequest request) async => .new(
.fromJson(await _sendCommand("get_mentions", request.toJson())); // TODO: Handle `related_events`
((await _sendCommand("get_mentions", request.toJson()))["events"] as List)
.map((event) => .fromJson(event)),
);
Future<Event?> getEvent(GetEventRequest request) async { Future<Event?> getEvent(GetEventRequest request) async {
final json = await _sendCommand("get_event", request.toJson()); final json = await _sendCommand("get_event", request.toJson());

View file

@ -43,9 +43,9 @@ class NotificationsController(final NotificationsRequest request)
), ),
); );
storeEvents(mentions.events.addAll(mentions.relatedEvents)); storeEvents(mentions);
return .new(mentions.events.map((event) => (event.rowId, event.roomId))); return .new(mentions.map((event) => (event.rowId, event.roomId)));
} }
Future<void> loadOlder() async { Future<void> loadOlder() async {
@ -76,12 +76,10 @@ class NotificationsController(final NotificationsRequest request)
), ),
); );
storeEvents( storeEvents(newNotifications);
newNotifications.events.addAll(newNotifications.relatedEvents),
);
return currentNotifications.addAll( return currentNotifications.addAll(
newNotifications.events.map((event) => (event.rowId, event.roomId)), newNotifications.map((event) => (event.rowId, event.roomId)),
); );
}); });
} }

View file

@ -9,7 +9,6 @@ extension SchemeToTheme on ColorScheme {
).textTheme; ).textTheme;
return .from(colorScheme: this).copyWith( return .from(colorScheme: this).copyWith(
cardTheme: .new(color: primaryContainer), cardTheme: .new(color: primaryContainer),
badgeTheme: .new(backgroundColor: primary, textColor: onPrimary),
popupMenuTheme: .new( popupMenuTheme: .new(
shape: RoundedRectangleBorder(borderRadius: .circular(16)), shape: RoundedRectangleBorder(borderRadius: .circular(16)),
color: surfaceContainerHigh, color: surfaceContainerHigh,

View file

@ -1,16 +0,0 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/event.dart";
part "mentions_response.freezed.dart";
part "mentions_response.g.dart";
@Freezed(toJson: false, fromJson: false)
@JsonSerializable(createToJson: false)
class const MentionsResponse({
required final IList<Event> events,
final IList<Event> relatedEvents = const .empty(),
}) with _$MentionsResponse {
factory MentionsResponse.fromJson(Map<String, Object?> json) =>
_$MentionsResponseFromJson(json);
}

View file

@ -5,6 +5,5 @@ abstract class const NavPage({super.key}) extends Widget {
String get title; String get title;
IconData get icon; IconData get icon;
/// Null hides the badge, and 0 shows a dot.
int? badgeCount(WidgetRef ref) => null; int? badgeCount(WidgetRef ref) => null;
} }

View file

@ -1,16 +0,0 @@
import "package:material_ui/material_ui.dart";
/// Shows [count] in a badge on [child]: nothing if null, a dot if 0, and the
/// number otherwise.
class const CountBadge(
final int? count, {
required final Widget child,
super.key,
}) extends StatelessWidget {
@override
Widget build(BuildContext context) => switch (count) {
null => child,
0 => Badge(child: child),
final count => Badge.count(count: count, child: child),
};
}

View file

@ -3,10 +3,8 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/rooms.dart"; import "package:nexus/controllers/rooms.dart";
import "package:nexus/widgets/appbar.dart"; import "package:nexus/widgets/appbar.dart";
import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/widgets/count_badge.dart";
import "package:nexus/widgets/room_menu_button.dart"; import "package:nexus/widgets/room_menu_button.dart";
import "package:nexus/widgets/room_summary_dialog.dart"; import "package:nexus/widgets/room_summary_dialog.dart";
import "package:nexus/widgets/sidebar.dart";
final class const RoomAppbar({ final class const RoomAppbar({
required final String? roomId, required final String? roomId,
@ -51,14 +49,7 @@ final class const RoomAppbar({
dimension: 24, dimension: 24,
fallback: Icon(Icons.numbers), fallback: Icon(Icons.numbers),
) )
: IconButton( : DrawerButton(onPressed: onOpenDrawer),
onPressed: onOpenDrawer,
tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
icon: CountBadge(
Sidebar.totalBadgeCount(ref),
child: const DrawerButtonIcon(),
),
),
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
title: room == null title: room == null
? null ? null

View file

@ -13,7 +13,6 @@ import "package:nexus/widgets/pages/settings.dart";
import "package:nexus/widgets/join_dialog.dart"; import "package:nexus/widgets/join_dialog.dart";
import "package:nexus/widgets/room_menu_button.dart"; import "package:nexus/widgets/room_menu_button.dart";
import "package:nexus/controllers/key.dart"; import "package:nexus/controllers/key.dart";
import "package:nexus/controllers/rooms.dart";
import "package:nexus/controllers/spaces.dart"; import "package:nexus/controllers/spaces.dart";
import "package:collection/collection.dart"; import "package:collection/collection.dart";
@ -27,26 +26,9 @@ class const RoomList({super.key}) extends ConsumerWidget implements NavPage {
@override @override
IconData get icon => Icons.list; IconData get icon => Icons.list;
// TODO: Show unread badge here and on sidebar hamburger
@override @override
int? badgeCount(WidgetRef ref) { int? badgeCount(WidgetRef ref) => null;
final (notifications, hasUnread) = ref.watch(
RoomsController.provider.select(
(rooms) => rooms.values.fold(
(0, false),
(total, room) => (
total.$1 + (room.metadata?.unreadNotifications ?? 0),
total.$2 || (room.metadata?.unreadMessages ?? 0) > 0,
),
),
),
);
return notifications > 0
? notifications
: hasUnread
? 0
: null;
}
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {

View file

@ -1,36 +1,23 @@
import "package:collection/collection.dart";
import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:material_ui/material_ui.dart"; import "package:material_ui/material_ui.dart";
import "package:nexus/controllers/sidebar_page.dart"; import "package:nexus/controllers/sidebar_page.dart";
import "package:nexus/models/nav_page.dart"; import "package:nexus/models/nav_page.dart";
import "package:nexus/models/sidebar_page.dart"; import "package:nexus/models/sidebar_page.dart";
import "package:nexus/widgets/count_badge.dart";
import "package:nexus/widgets/invites_list.dart"; import "package:nexus/widgets/invites_list.dart";
import "package:nexus/widgets/notifications_list.dart"; import "package:nexus/widgets/notifications_list.dart";
import "package:nexus/widgets/room_list.dart"; import "package:nexus/widgets/room_list.dart";
class const Sidebar({super.key}) extends ConsumerWidget { class const Sidebar({super.key}) extends ConsumerWidget {
static NavPage pageFor(SidebarPage page) => switch (page) {
.rooms => const RoomList(),
.notifications => const NotificationsList(),
.invites => const InvitesList(),
};
/// The badges of every tab added together, e.g. for the drawer button.
///
/// Like [NavPage.badgeCount], null means no badges and 0 means only dots.
static int? totalBadgeCount(WidgetRef ref) {
final counts = SidebarPage.values
.map((page) => pageFor(page).badgeCount(ref))
.nonNulls;
return counts.isEmpty ? null : counts.sum;
}
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final selected = ref.watch(SidebarPageController.provider); final selected = ref.watch(SidebarPageController.provider);
final pages = SidebarPage.values.map(pageFor); final pages = SidebarPage.values.map<NavPage>(
(page) => switch (page) {
.rooms => const RoomList(),
.notifications => const NotificationsList(),
.invites => const InvitesList(),
},
);
return Drawer( return Drawer(
width: 330, width: 330,
@ -40,17 +27,18 @@ class const Sidebar({super.key}) extends ConsumerWidget {
body: pages.elementAt(selected.index), body: pages.elementAt(selected.index),
bottomNavigationBar: NavigationBar( bottomNavigationBar: NavigationBar(
selectedIndex: selected.index, selectedIndex: selected.index,
destinations: pages destinations: pages.map((page) {
.map( final badgeCount = page.badgeCount(ref);
(page) => NavigationDestination(
icon: CountBadge( return NavigationDestination(
page.badgeCount(ref), icon: Badge.count(
count: badgeCount ?? 0,
isLabelVisible: badgeCount != null,
child: Icon(page.icon), child: Icon(page.icon),
), ),
label: page.title, label: page.title,
), );
) }).toList(),
.toList(),
onDestinationSelected: (index) => ref onDestinationSelected: (index) => ref
.read(SidebarPageController.provider.notifier) .read(SidebarPageController.provider.notifier)
.set(SidebarPage.values[index]), .set(SidebarPage.values[index]),