176 lines
6.1 KiB
Dart
176 lines
6.1 KiB
Dart
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 {
|
|
final navigator = Navigator.of(context);
|
|
if (navigator.canPop()) navigator.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(),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|