simplified code across the app and consolidated duplicated helpers

This commit is contained in:
2026-09-27 17:29:54 +02:00
parent 3e856d0b1c
commit ee1240f71a
155 changed files with 1772 additions and 2546 deletions
+33 -30
View File
@@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:developer';
import 'dart:math' as math;
import 'package:flutter/widgets.dart';
@@ -10,6 +9,7 @@ import '../../../../../api/marianumcloud/talk/chat/long_poll_chat.dart';
import '../../../../../api/marianumcloud/talk/room/get_room_response.dart';
import '../../../../../api/marianumcloud/talk/set_read_marker/set_read_marker.dart';
import '../../../../../api/marianumcloud/talk/set_read_marker/set_read_marker_params.dart';
import '../../../../../utils/exponential_backoff.dart';
import '../../../infrastructure/utility_widgets/loadable_hydrated_bloc/loadable_hydrated_bloc.dart';
import '../../../infrastructure/utility_widgets/loadable_hydrated_bloc/loadable_hydrated_bloc_event.dart';
import '../../chat_list/bloc/chat_list_bloc.dart';
@@ -26,7 +26,6 @@ class ChatBloc
// A loop parked in its request survives stop+start for the same token
// (pause/resume); the generation retires it so only one loop polls.
int _pollGeneration = 0;
int _backoffMs = 0;
int _lastKnownMessageId = 0;
bool _appResumed = true;
@@ -68,8 +67,7 @@ class ChatBloc
@override
Map<String, dynamic>? toStorage(ChatState state) {
final response = state.chatResponse;
if (response == null ||
response.data.length <= _kMaxPersistedMessages) {
if (response == null || response.data.length <= _kMaxPersistedMessages) {
return state.toJson();
}
// Keep only the newest N; trimming drops older messages, so there is
@@ -129,9 +127,6 @@ class ChatBloc
add(Emit((s) => s.copyWith(referenceMessageId: messageId)));
}
/// No-op when the bloc has already moved on to a different token: when
/// popping a stacked chat (B over A), A's didPopNext runs setToken(A)
/// before B's dispose fires.
/// The chat view may still be popping when the sign-out resets this bloc,
/// so leaveChat would find no token and the long-poll would keep running.
@override
@@ -141,6 +136,9 @@ class ChatBloc
return super.reset();
}
/// No-op when the bloc has already moved on to a different token: when
/// popping a stacked chat (B over A), A's didPopNext runs setToken(A)
/// before B's dispose fires.
void leaveChat(String fromToken) {
if ((innerState?.currentToken ?? '') != fromToken) return;
_chatViewActive = false;
@@ -272,7 +270,6 @@ class ChatBloc
if (_pollingToken == token) return;
_stopLongPoll();
_pollingToken = token;
_backoffMs = 0;
_lastKnownMessageId = _maxMessageId(innerState?.chatResponse);
unawaited(_pollLoop(token, ++_pollGeneration));
}
@@ -280,11 +277,11 @@ class ChatBloc
void _stopLongPoll() {
_pollingToken = null;
_pollGeneration++;
_backoffMs = 0;
}
Future<void> _pollLoop(String token, int generation) async {
bool active() => generation == _pollGeneration && !isClosed;
var failures = 0;
while (active()) {
try {
final response = await LongPollChat(
@@ -293,7 +290,7 @@ class ChatBloc
).run();
if (!active()) return;
_backoffMs = 0;
failures = 0;
if (response == null) continue;
@@ -318,8 +315,13 @@ class ChatBloc
} on Object catch (e) {
if (!active()) return;
log('LongPoll error for $token: $e');
_backoffMs = _backoffMs == 0 ? 2000 : math.min(_backoffMs * 2, 30000);
await Future.delayed(Duration(milliseconds: _backoffMs));
await Future<void>.delayed(
exponentialBackoff(
++failures,
base: const Duration(seconds: 2),
max: const Duration(seconds: 30),
),
);
}
}
}
@@ -339,14 +341,7 @@ class ChatBloc
);
return;
}
final byId = <int, GetChatResponseObject>{};
for (final m in current.data) {
byId[m.id] = m;
}
for (final m in incoming.data) {
byId[m.id] = m;
}
final merged = GetChatResponse(byId.values.toSet())
final merged = _mergeById(current, incoming, incomingWins: true)
..headers = incoming.headers;
add(DataGathered((s) => s.copyWith(chatResponse: merged)));
}
@@ -357,14 +352,7 @@ class ChatBloc
void _applyOlderResponse(GetChatResponse older) {
final current = innerState?.chatResponse;
if (current == null) return;
final byId = <int, GetChatResponseObject>{};
for (final m in current.data) {
byId[m.id] = m;
}
for (final m in older.data) {
byId.putIfAbsent(m.id, () => m);
}
final merged = GetChatResponse(byId.values.toSet())
final merged = _mergeById(current, older, incomingWins: false)
..headers = current.headers;
add(
DataGathered(
@@ -377,6 +365,22 @@ class ChatBloc
);
}
GetChatResponse _mergeById(
GetChatResponse current,
GetChatResponse incoming, {
required bool incomingWins,
}) {
final byId = {for (final m in current.data) m.id: m};
for (final m in incoming.data) {
if (incomingWins) {
byId[m.id] = m;
} else {
byId.putIfAbsent(m.id, () => m);
}
}
return GetChatResponse(byId.values.toSet());
}
int _maxMessageId(GetChatResponse? response) {
if (response == null) return 0;
var max = 0;
@@ -386,8 +390,7 @@ class ChatBloc
return max;
}
int _minMessageId(GetChatResponse? response) {
if (response == null) return 0;
int _minMessageId(GetChatResponse response) {
var min = 0;
for (final m in response.data) {
if (m.id <= 0) continue; // skip dummies