move NotificationsPage into sidebar via NavigationBar
This commit is contained in:
parent
3b2cf6b2a4
commit
45d69b0b9f
6 changed files with 458 additions and 389 deletions
174
lib/widgets/notifications_list.dart
Normal file
174
lib/widgets/notifications_list.dart
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
import "dart:async";
|
||||
|
||||
import "package:m3e_buttons/m3e_buttons.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:flutter_hooks/flutter_hooks.dart";
|
||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
import "package:nexus/controllers/notifications.dart";
|
||||
import "package:nexus/helpers/extensions/focus_room.dart";
|
||||
import "package:nexus/models/event.dart";
|
||||
import "package:nexus/models/nav_page.dart";
|
||||
import "package:nexus/widgets/error_dialog.dart";
|
||||
import "package:nexus/widgets/highlight_wrapper.dart";
|
||||
import "package:nexus/widgets/loading.dart";
|
||||
import "package:nexus/widgets/renderers/event.dart";
|
||||
import "package:super_sliver_list/super_sliver_list.dart";
|
||||
|
||||
class const NotificationsList({
|
||||
final int? highlightedEvent,
|
||||
final bool defaultToAllNotifications = false,
|
||||
super.key,
|
||||
}) extends HookConsumerWidget implements NavPage {
|
||||
@override
|
||||
String get title => "Notifications";
|
||||
|
||||
@override
|
||||
IconData get icon => Icons.notifications;
|
||||
|
||||
// TODO: Show notification badge here
|
||||
@override
|
||||
Future<int>? badgeBuilder(WidgetRef ref) => null;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final options = <M3EToggleButtonGroupAction, UnreadType>{
|
||||
M3EToggleButtonGroupAction(
|
||||
checkedLabel: Text("Mentions"),
|
||||
icon: Icon(Icons.alternate_email),
|
||||
): .highlight,
|
||||
M3EToggleButtonGroupAction(
|
||||
checkedLabel: Text("All Notifications"),
|
||||
icon: Icon(Icons.notifications),
|
||||
): .notify,
|
||||
};
|
||||
final unreadTypeIndex = useState(defaultToAllNotifications ? 1 : 0);
|
||||
|
||||
final highlightedId = useState(highlightedEvent);
|
||||
final listController = useRef(ListController());
|
||||
final scrollController = useScrollController();
|
||||
|
||||
final provider = NotificationsController.provider((
|
||||
options.values.toList()[unreadTypeIndex.value],
|
||||
null,
|
||||
));
|
||||
final notifications = ref.watch(provider);
|
||||
final notifier = ref.watch(provider.notifier);
|
||||
|
||||
useEffect(() {
|
||||
if (highlightedId.value == null) return null;
|
||||
Timer? timer;
|
||||
|
||||
void listener() =>
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
if (!listController.value.isAttached) return;
|
||||
|
||||
final notifications = await ref.watch(provider.future);
|
||||
|
||||
final index = notifications.indexWhere(
|
||||
(tuple) => tuple.$1 == highlightedId.value!,
|
||||
);
|
||||
|
||||
if (index == -1) return;
|
||||
|
||||
listController.value.animateToItem(
|
||||
index: index,
|
||||
scrollController: scrollController,
|
||||
alignment: 0.5,
|
||||
duration: (_) => .new(milliseconds: 700),
|
||||
curve: (_) => Curves.easeInOut,
|
||||
);
|
||||
timer = Timer(.new(seconds: 1), () {
|
||||
highlightedId.value = null;
|
||||
});
|
||||
listController.value.removeListener(listener);
|
||||
});
|
||||
|
||||
listController.value.addListener(listener);
|
||||
return timer?.cancel;
|
||||
}, []);
|
||||
|
||||
useEffect(() {
|
||||
Future<void> listener() async {
|
||||
if (!scrollController.hasClients || notifications.isLoading) return;
|
||||
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent) {
|
||||
await notifier.loadOlder();
|
||||
}
|
||||
}
|
||||
|
||||
scrollController.addListener(listener);
|
||||
return () => scrollController.removeListener(listener);
|
||||
}, [scrollController, notifications]);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
if (notifications is AsyncLoading && notifications.value != null)
|
||||
const LinearProgressIndicator(minHeight: 2),
|
||||
Expanded(
|
||||
child: switch (notifications) {
|
||||
AsyncData(:final value) || AsyncLoading(:final value?) =>
|
||||
value.isEmpty
|
||||
? Center(
|
||||
child: Text(
|
||||
"No notifications yet",
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
)
|
||||
: SuperListView.builder(
|
||||
listController: listController.value,
|
||||
controller: scrollController,
|
||||
itemCount: value.length,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 12,
|
||||
),
|
||||
reverse: true,
|
||||
itemBuilder: (context, index) {
|
||||
final (rowId, roomId) = value[index];
|
||||
final isHighlighted = rowId == highlightedId.value;
|
||||
|
||||
return Padding(
|
||||
padding: .only(top: 8),
|
||||
child: HighlightWrapper(
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop();
|
||||
await ref.focusRoom(roomId, rowId);
|
||||
},
|
||||
child: IgnorePointer(
|
||||
child: EventRenderer(rowId, roomId: roomId),
|
||||
),
|
||||
),
|
||||
isHighlighted: isHighlighted,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
AsyncLoading() => const Loading(),
|
||||
AsyncError(:final error, :final stackTrace) => ErrorDialog(
|
||||
error,
|
||||
stackTrace,
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: .topRight,
|
||||
child: Padding(
|
||||
padding: .all(16),
|
||||
child: M3EToggleButtonGroup(
|
||||
selectedIndex: unreadTypeIndex.value,
|
||||
onSelectedIndexChanged: (index) =>
|
||||
unreadTypeIndex.value = index ?? unreadTypeIndex.value,
|
||||
actions: options.keys.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue