Files
App/lib/hooks/use_room_scroll.dart
2026-01-10 14:18:59 +08:00

128 lines
4.0 KiB
Dart

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<double> bottomGradientOpacity;
bool isScrollingToMessage;
final void Function({
required String messageId,
required List<LocalChatMessage> 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<int> Function(String) jumpToMessage,
AsyncValue<List<LocalChatMessage>> messagesAsync,
) {
final scrollController = useScrollController();
final listController = useMemoized(() => ListController(), []);
final bottomGradientOpacity = useState(ValueNotifier<double>(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<LocalChatMessage> 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,
);
}