simplified code across the app and consolidated duplicated helpers
This commit is contained in:
@@ -61,15 +61,15 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
|
||||
// Captured in initState because the framework has unmounted us by the
|
||||
// time dispose runs.
|
||||
ChatBloc? _chatBlocRef;
|
||||
ChatListBloc? _chatListBlocRef;
|
||||
late final ChatBloc _chatBloc;
|
||||
late final ChatListBloc _chatListBloc;
|
||||
PageRoute<dynamic>? _subscribedRoute;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_chatBlocRef = context.read<ChatBloc>();
|
||||
_chatListBlocRef = context.read<ChatListBloc>();
|
||||
_chatBloc = context.read<ChatBloc>();
|
||||
_chatListBloc = context.read<ChatListBloc>();
|
||||
_positionsListener.itemPositions.addListener(_onScrollPositions);
|
||||
NotificationTasks.clearNotificationsForChat(widget.room.token);
|
||||
}
|
||||
@@ -83,9 +83,7 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
// before the user reaches the top — the load stays invisible.
|
||||
final maxIndex = positions.map((p) => p.index).reduce(math.max);
|
||||
if (maxIndex < _itemCount - _kLoadOlderPrefetchRows) return;
|
||||
final data = _chatBlocRef?.state.data;
|
||||
if (data == null || !data.hasMoreOld || data.isLoadingOlder) return;
|
||||
_chatBlocRef?.loadOlder();
|
||||
_chatBloc.loadOlder();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -107,7 +105,7 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
// A stacked chat above us was just popped (typical: notification tap
|
||||
// opened another chat). The global ChatBloc currently points at that
|
||||
// other chat's token, so our isReady predicate fails until we re-claim.
|
||||
_chatBlocRef?.setToken(widget.room.token);
|
||||
_chatBloc.setToken(widget.room.token);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -117,7 +115,7 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
}
|
||||
_positionsListener.itemPositions.removeListener(_onScrollPositions);
|
||||
_markAsReadFinal();
|
||||
_chatBlocRef?.leaveChat(widget.room.token);
|
||||
_chatBloc.leaveChat(widget.room.token);
|
||||
_searchTextController.dispose();
|
||||
Debouncer.cancel(_searchDebounceTag);
|
||||
super.dispose();
|
||||
@@ -129,15 +127,15 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
/// belongs to a different room, and writing its max-id as our marker
|
||||
/// would regress our server cursor.
|
||||
void _markAsReadFinal() {
|
||||
final state = _chatBlocRef?.state.data;
|
||||
final state = _chatBloc.state.data;
|
||||
if (state == null) return;
|
||||
if (state.currentToken != widget.room.token) return;
|
||||
final response = state.chatResponse;
|
||||
if (response == null) return;
|
||||
final maxId = response.data.map((m) => m.id).fold<int>(0, math.max);
|
||||
if (maxId == 0) return;
|
||||
_chatListBlocRef?.markRoomAsRead(widget.room.token, maxId);
|
||||
unawaited(_chatBlocRef!.sendServerReadMarker(widget.room.token, maxId));
|
||||
_chatListBloc.markRoomAsRead(widget.room.token, maxId);
|
||||
unawaited(_chatBloc.sendServerReadMarker(widget.room.token, maxId));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -148,31 +146,34 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
}
|
||||
}
|
||||
|
||||
void _refresh() {
|
||||
context.read<ChatBloc>().setToken(widget.room.token);
|
||||
}
|
||||
|
||||
void _refetch({bool renew = false}) => _refresh();
|
||||
void _refetch() => _chatBloc.setToken(widget.room.token);
|
||||
|
||||
// The built rows only depend on the chat data and the search state; the list
|
||||
// itself rebuilds far more often (loading flips, keyboard, parent rebuilds),
|
||||
// and re-sorting plus re-creating every bubble each time is wasted work.
|
||||
// The top marker is cached separately so a loading-older flip doesn't
|
||||
// rebuild every message row.
|
||||
Object? _messagesKey;
|
||||
List<Widget> _messages = const [];
|
||||
Object? _itemsKey;
|
||||
List<Widget> _items = const [];
|
||||
|
||||
List<Widget> _itemsFor(ChatState state) {
|
||||
final key = (
|
||||
final messagesKey = (
|
||||
state.chatResponse,
|
||||
state.isLoadingOlder,
|
||||
state.hasMoreOld,
|
||||
_searchActive,
|
||||
_searchQuery,
|
||||
_activeMatchIndex,
|
||||
widget.room,
|
||||
);
|
||||
if (messagesKey != _messagesKey) {
|
||||
_messagesKey = messagesKey;
|
||||
_messages = _buildMessages(state.chatResponse!).reversed.toList();
|
||||
}
|
||||
final key = (_messages, state.isLoadingOlder, state.hasMoreOld);
|
||||
if (key == _itemsKey) return _items;
|
||||
_itemsKey = key;
|
||||
final items = _buildMessages(state.chatResponse!).reversed.toList();
|
||||
final items = [..._messages];
|
||||
// reverse:true renders index 0 at the bottom, so the top marker
|
||||
// (spinner / start-of-chat) goes at the end.
|
||||
if (state.isLoadingOlder) {
|
||||
@@ -191,26 +192,20 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
return _items = items;
|
||||
}
|
||||
|
||||
void _enterSearchMode() {
|
||||
setState(() {
|
||||
_searchActive = true;
|
||||
_searchQuery = '';
|
||||
_matches = const [];
|
||||
_activeMatchIndex = 0;
|
||||
_matchesComputedFor = null;
|
||||
_matchesComputedQuery = null;
|
||||
_searchTextController.clear();
|
||||
});
|
||||
}
|
||||
void _enterSearchMode() => _resetSearch(active: true);
|
||||
|
||||
void _exitSearchMode() {
|
||||
Debouncer.cancel(_searchDebounceTag);
|
||||
_matchIndices.clear();
|
||||
_resetSearch(active: false);
|
||||
}
|
||||
|
||||
void _resetSearch({required bool active}) {
|
||||
setState(() {
|
||||
_searchActive = false;
|
||||
_searchActive = active;
|
||||
_searchQuery = '';
|
||||
_matches = const [];
|
||||
_activeMatchIndex = 0;
|
||||
_matchIndices.clear();
|
||||
_matchesComputedFor = null;
|
||||
_matchesComputedQuery = null;
|
||||
_searchTextController.clear();
|
||||
@@ -233,7 +228,7 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
}
|
||||
|
||||
void _applySearch(String q) {
|
||||
final chatResponse = context.read<ChatBloc>().state.data?.chatResponse;
|
||||
final chatResponse = _chatBloc.state.data?.chatResponse;
|
||||
setState(() {
|
||||
_searchQuery = q;
|
||||
_activeMatchIndex = 0;
|
||||
@@ -260,19 +255,12 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
_matchesComputedQuery = _searchQuery;
|
||||
}
|
||||
|
||||
void _goToPreviousMatch() {
|
||||
if (_matches.isEmpty) return;
|
||||
setState(() {
|
||||
_activeMatchIndex = (_activeMatchIndex + 1) % _matches.length;
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToActiveMatch());
|
||||
}
|
||||
|
||||
void _goToNextMatch() {
|
||||
// Matches are newest-first, so "previous" (older) steps forward.
|
||||
void _stepMatch(int delta) {
|
||||
if (_matches.isEmpty) return;
|
||||
setState(() {
|
||||
_activeMatchIndex =
|
||||
(_activeMatchIndex - 1 + _matches.length) % _matches.length;
|
||||
(_activeMatchIndex + delta + _matches.length) % _matches.length;
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToActiveMatch());
|
||||
}
|
||||
@@ -379,6 +367,11 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final input = ChatTextfield(
|
||||
widget.room.token,
|
||||
selfId: widget.selfId,
|
||||
roomType: widget.room.type,
|
||||
);
|
||||
return _KeyboardDismissPopScope(
|
||||
child: Scaffold(
|
||||
backgroundColor: const Color(0xffefeae2),
|
||||
@@ -389,8 +382,8 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
activeIndex: _matches.isEmpty ? -1 : _activeMatchIndex,
|
||||
onChanged: _onSearchChanged,
|
||||
onClose: _exitSearchMode,
|
||||
onPrevious: _matches.isEmpty ? null : _goToPreviousMatch,
|
||||
onNext: _matches.isEmpty ? null : _goToNextMatch,
|
||||
onPrevious: _matches.isEmpty ? null : () => _stepMatch(1),
|
||||
onNext: _matches.isEmpty ? null : () => _stepMatch(-1),
|
||||
)
|
||||
: ClickableAppBar(
|
||||
onTap: () =>
|
||||
@@ -443,18 +436,8 @@ class _ChatViewState extends State<ChatView> with RouteAware {
|
||||
ColoredBox(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: TalkNavigator.isSecondaryVisible(context)
|
||||
? ChatTextfield(
|
||||
widget.room.token,
|
||||
selfId: widget.selfId,
|
||||
roomType: widget.room.type,
|
||||
)
|
||||
: SafeArea(
|
||||
child: ChatTextfield(
|
||||
widget.room.token,
|
||||
selfId: widget.selfId,
|
||||
roomType: widget.room.type,
|
||||
),
|
||||
),
|
||||
? input
|
||||
: SafeArea(child: input),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user