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'; class RoomScrollManager { final ScrollController scrollController; final ValueNotifier bottomGradientOpacity; bool isScrollingToMessage; final void Function({ required String messageId, required List messageList, }) scrollToMessage; RoomScrollManager({ required this.scrollController, 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 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 { messagesAsync.when( data: (messageList) { if (!scrollController.hasClients) return; final messageIndex = index; final totalMessages = messageList.length; if (messageIndex < 0 || messageIndex >= totalMessages) return; scrollController.animateTo( messageIndex * 80.0, duration: const Duration(milliseconds: 300), curve: Curves.easeOutCubic, ); }, loading: () {}, error: (_, _) {}, ); 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, bottomGradientOpacity: bottomGradientOpacity.value, scrollToMessage: scrollToMessageWrapper, isScrollingToMessage: isScrollingToMessage, ); }