import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/pods/chat/chat_room.dart'; import 'package:island/database/message.dart'; import 'package:island/pods/chat/messages_notifier.dart'; import 'package:super_sliver_list/super_sliver_list.dart'; class RoomScrollManager { final ScrollController scrollController; final ListController listController; final ValueNotifier bottomGradientOpacity; bool isScrollingToMessage; final void Function({ required String messageId, required List messageList, }) scrollToMessage; RoomScrollManager({ required this.scrollController, required this.listController, required this.bottomGradientOpacity, required this.scrollToMessage, this.isScrollingToMessage = false, }); } RoomScrollManager useRoomScrollManager( WidgetRef ref, String roomId, Future Function(String) jumpToMessage, AsyncValue> messagesAsync, ) { final scrollController = useScrollController(); final listController = useMemoized(() => ListController(), []); final bottomGradientOpacity = useState(ValueNotifier(0.0)); var isLoading = false; var isScrollingToMessage = false; final messagesNotifier = ref.read(messagesProvider(roomId).notifier); final flashingMessagesNotifier = ref.read(flashingMessagesProvider.notifier); void performScrollAnimation({required int index, required String messageId}) { flashingMessagesNotifier.update((set) => set.union({messageId})); WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) { try { listController.animateToItem( index: index, scrollController: scrollController, alignment: 0.5, duration: (estimatedDistance) => Duration( milliseconds: (estimatedDistance * 0.5).clamp(200, 800).toInt(), ), curve: (estimatedDistance) => Curves.easeOutCubic, ); Future.delayed(const Duration(milliseconds: 800), () { isScrollingToMessage = false; }); } catch (e) { isScrollingToMessage = false; } }); }); } void scrollToMessageWrapper({ required String messageId, required List messageList, }) { if (isScrollingToMessage) return; isScrollingToMessage = true; final messageIndex = messageList.indexWhere((m) => m.id == messageId); if (messageIndex == -1) { jumpToMessage(messageId).then((index) { if (index != -1) { WidgetsBinding.instance.addPostFrameCallback((_) { performScrollAnimation(index: index, messageId: messageId); }); } else { isScrollingToMessage = false; } }); } else { WidgetsBinding.instance.addPostFrameCallback((_) { performScrollAnimation(index: messageIndex, messageId: messageId); }); } } useEffect(() { void onScroll() { messagesAsync.when( data: (messageList) { if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 200) { if (!isLoading) { isLoading = true; messagesNotifier.loadMore().then((_) => isLoading = false); } } final pixels = scrollController.position.pixels; bottomGradientOpacity.value.value = (pixels / 500.0).clamp(0.0, 1.0); }, loading: () {}, error: (_, _) {}, ); } scrollController.addListener(onScroll); return () => scrollController.removeListener(onScroll); }, [scrollController, messagesAsync]); return RoomScrollManager( scrollController: scrollController, listController: listController, bottomGradientOpacity: bottomGradientOpacity.value, scrollToMessage: scrollToMessageWrapper, isScrollingToMessage: isScrollingToMessage, ); }