refactored and condensed technical documentation and comments across the codebase to improve readability
This commit is contained in:
@@ -45,11 +45,8 @@ class LoadableStateBloc extends Bloc<LoadableStateEvent, LoadableStateState>
|
||||
return;
|
||||
}
|
||||
_lastResumeRefetch = now;
|
||||
// Re-check connectivity. The resulting [ConnectivityChanged] event takes
|
||||
// it from there: its handler updates the offline/online indicator and
|
||||
// triggers [reFetch] when the device is connected, so a stale
|
||||
// "Verbindung fehlgeschlagen" bar from a suspend-time fetch clears as
|
||||
// soon as the network is reachable again.
|
||||
// Re-check connectivity so the resulting [ConnectivityChanged] handler
|
||||
// clears a stale error bar and triggers [reFetch] once reachable again.
|
||||
unawaited(
|
||||
Connectivity().checkConnectivity().then(
|
||||
(result) =>
|
||||
|
||||
@@ -65,11 +65,9 @@ class LoadableStateConsumer<
|
||||
final showError = hasError && !hasContent;
|
||||
final showErrorBar = hasError && hasContent;
|
||||
|
||||
// Keep the wrapper hierarchy stable across refresh cycles. The bloc clears
|
||||
// reFetch to null while a refetch is in flight and restores it on
|
||||
// completion; flipping the RefreshIndicator in and out on that signal
|
||||
// would change the widget tree under the ListView and reset its scroll
|
||||
// position every refresh.
|
||||
// Keep the wrapper hierarchy stable across refresh cycles: reFetch flips to
|
||||
// null mid-refetch, and toggling the RefreshIndicator on that signal would
|
||||
// rebuild the tree under the ListView and reset its scroll position.
|
||||
final content = SizedBox(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: hasContent
|
||||
|
||||
+2
-4
@@ -128,10 +128,8 @@ abstract class LoadableHydratedBloc<
|
||||
gatherData()
|
||||
.catchError((e) {
|
||||
log('Error while fetching ${TState.toString()}: ${e.toString()}');
|
||||
// The bloc may have been closed before this async error landed (e.g.
|
||||
// when its scoping widget tree was disposed mid-fetch). Adding to a
|
||||
// closed bloc throws "Cannot add new events after calling close",
|
||||
// so swallow that case quietly.
|
||||
// The bloc may have been closed before this async error landed;
|
||||
// adding to a closed bloc throws, so swallow that case.
|
||||
if (isClosed) return;
|
||||
add(
|
||||
Error(
|
||||
|
||||
@@ -196,12 +196,9 @@ class ChatBloc
|
||||
|
||||
void _startLongPoll(String token) {
|
||||
if (!_appResumed) return;
|
||||
// A load chain may finish AFTER the user already switched chats — e.g. a
|
||||
// notification tap resumes the app (lifecycle refresh starts loading the
|
||||
// still-open chat A) and then navigates to chat B. Without this guard the
|
||||
// stale chain's completion would hijack the long-poll back to A and its
|
||||
// responses would merge A's messages into B's state (chat B showing chat
|
||||
// A's content) while B never receives live updates.
|
||||
// A load chain may finish after the user switched chats (A→B); without this
|
||||
// guard the stale chain hijacks the long-poll back to A and merges A's
|
||||
// messages into B's state while B never receives live updates.
|
||||
if ((innerState?.currentToken ?? '') != token) return;
|
||||
if (_pollingToken == token) return;
|
||||
_stopLongPoll();
|
||||
|
||||
@@ -44,11 +44,9 @@ class FilesBloc
|
||||
await _query(path, renew: true);
|
||||
}
|
||||
|
||||
/// LoadableState.reFetch (used by the pull-to-refresh indicator and the
|
||||
/// error-screen retry button) routes through here. The inherited retry()
|
||||
/// goes via gatherData() which respects the cache TTL — for an explicit
|
||||
/// user-initiated reload we must bypass it, otherwise the root listing
|
||||
/// silently returns its day-old cached payload without hitting the server.
|
||||
/// Pull-to-refresh and error-screen retry route through here. Unlike the
|
||||
/// inherited retry() (via gatherData(), which respects the cache TTL), this
|
||||
/// bypasses the TTL — otherwise the root listing returns its day-old cache.
|
||||
@override
|
||||
void retry() {
|
||||
unawaited(refresh());
|
||||
|
||||
@@ -6,16 +6,13 @@ import '../../../../../api/marianumcloud/webdav/webdav_api.dart';
|
||||
import '../../../../../api/request_cache.dart';
|
||||
|
||||
class FilesDataProvider {
|
||||
/// Lists files at [path]. Cached payload is delivered via [onCacheData] as
|
||||
/// soon as it is read from disk, so callers can render stale data while the
|
||||
/// network call is still pending. The Future itself resolves once both the
|
||||
/// cache lookup and the network attempt have settled, throwing if no payload
|
||||
/// could be obtained at all.
|
||||
/// Lists files at [path]. Cached payload is delivered via [onCacheData] the
|
||||
/// moment it is read from disk so callers can render stale data while the
|
||||
/// network call is pending; the Future resolves once both have settled,
|
||||
/// throwing if no payload could be obtained at all.
|
||||
///
|
||||
/// Pass [renew] for explicit user-triggered reloads (pull-to-refresh, after
|
||||
/// a rename / delete / move / upload). It bypasses the per-path TTL in
|
||||
/// [ListFilesCache] so the root listing — which is otherwise cached for a
|
||||
/// full day — still refetches when the user actively asks for it.
|
||||
/// Pass [renew] for explicit user-triggered reloads to bypass the per-path
|
||||
/// TTL in [ListFilesCache] (the root listing is otherwise cached for a day).
|
||||
Future<ListFilesResponse> listFiles(
|
||||
String path, {
|
||||
void Function(ListFilesResponse)? onCacheData,
|
||||
|
||||
@@ -12,14 +12,11 @@ import '../../timetable/bloc/timetable_event.dart';
|
||||
import '../../timetable/bloc/timetable_state.dart';
|
||||
import '../repository/foreign_timetable_repository.dart';
|
||||
|
||||
/// Drives a foreign element's timetable. Mirrors the week-loading and
|
||||
/// week-navigation logic of `TimetableBloc` but (a) loads weeks from the
|
||||
/// element endpoint, (b) carries no custom events, and (c) does not persist —
|
||||
/// it is created per opened page and recreated for every selected element.
|
||||
///
|
||||
/// It reuses [TimetableState] verbatim so the existing render pipeline works
|
||||
/// unchanged; `customEvents` simply stays null (the foreign view uses an
|
||||
/// `isReady` predicate that ignores it).
|
||||
/// Drives a foreign element's timetable. Mirrors `TimetableBloc`'s week-loading
|
||||
/// and navigation but loads weeks from the element endpoint, carries no custom
|
||||
/// events, and does not persist (page-scoped, recreated per element). Reuses
|
||||
/// [TimetableState] verbatim so the render pipeline is unchanged; `customEvents`
|
||||
/// stays null (the foreign view's `isReady` predicate ignores it).
|
||||
class ForeignTimetableBloc
|
||||
extends
|
||||
LoadableHydratedBloc<
|
||||
@@ -56,10 +53,8 @@ class ForeignTimetableBloc
|
||||
);
|
||||
}
|
||||
|
||||
// Persistence is disabled: this bloc is page-scoped and element-specific, so
|
||||
// there is nothing worth restoring across launches. Returning null from
|
||||
// toJson means HydratedBloc never writes anything; fromJson ignores any
|
||||
// legacy payload and starts fresh.
|
||||
// Persistence disabled: page-scoped and element-specific, nothing worth
|
||||
// restoring. toJson returns null so nothing is written; fromJson starts fresh.
|
||||
@override
|
||||
Map<String, dynamic>? toJson(LoadableState<TimetableState> state) => null;
|
||||
|
||||
|
||||
+3
-3
@@ -10,9 +10,9 @@ import '../../timetable/data_provider/timetable_data_provider.dart';
|
||||
|
||||
/// Data access for a foreign element's timetable. The week comes from the
|
||||
/// element-specific endpoint; all reference data (rooms/subjects/holidays/
|
||||
/// school year/timegrid) is school-wide and identical to the user's own plan,
|
||||
/// so it is delegated to the existing [TimetableDataProvider] (which already
|
||||
/// caches it). Custom events are intentionally absent — they are user-private.
|
||||
/// school year/timegrid) is school-wide, so it delegates to the existing
|
||||
/// [TimetableDataProvider] (which caches it). Custom events are intentionally
|
||||
/// absent — they are user-private.
|
||||
class ForeignTimetableDataProvider {
|
||||
final TimetableDataProvider _base;
|
||||
|
||||
|
||||
@@ -17,9 +17,8 @@ class SettingsCubit extends HydratedCubit<Settings> {
|
||||
Settings val({bool write = false}) {
|
||||
if (write) {
|
||||
// Defer the emit until the synchronous mutation on the returned object
|
||||
// has finished. Without this scheduleMicrotask the cubit emits a copy
|
||||
// captured *before* the assignment runs, so listeners (and HydratedBloc
|
||||
// persistence) see the old value on the first emit.
|
||||
// has finished — without this microtask the cubit emits a copy captured
|
||||
// *before* the assignment, so listeners see the old value.
|
||||
if (!_emitScheduled) {
|
||||
_emitScheduled = true;
|
||||
scheduleMicrotask(() {
|
||||
|
||||
@@ -27,10 +27,9 @@ abstract class TimetableState with _$TimetableState {
|
||||
required DateTime startDate,
|
||||
required DateTime endDate,
|
||||
@Default(0) int dataVersion,
|
||||
// Boundaries learned from past server denials of inaccessible weeks.
|
||||
// Inclusive: weeks whose start is on/before `accessibleEndDate` and
|
||||
// whose end is on/after `accessibleStartDate` are within the user's
|
||||
// permitted range. Null = no upper / lower bound discovered yet.
|
||||
// Boundaries learned from past server denials. A week is permitted when its
|
||||
// start is on/before `accessibleEndDate` and its end on/after
|
||||
// `accessibleStartDate`. Null = that bound not discovered yet.
|
||||
DateTime? accessibleStartDate,
|
||||
DateTime? accessibleEndDate,
|
||||
}) = _TimetableState;
|
||||
|
||||
@@ -23,10 +23,9 @@ import '../../../../../api/mhsl/custom_timetable_event/update/update_custom_time
|
||||
import '../../../../../api/request_cache.dart';
|
||||
import '../../../../../model/account_data.dart';
|
||||
|
||||
/// Pulls the timetable from the Marianum-Connect mobile API. Each MC endpoint
|
||||
/// is its own HTTP call; this provider just exposes the lazy futures so the
|
||||
/// bloc can chain them without seeing the dio layer. Custom events still come
|
||||
/// from the MHSL backend and are unchanged.
|
||||
/// Pulls the timetable from the Marianum-Connect mobile API. Each endpoint is
|
||||
/// its own HTTP call; this provider exposes the lazy futures so the bloc can
|
||||
/// chain them without seeing the dio layer. Custom events still come from MHSL.
|
||||
class TimetableDataProvider {
|
||||
Future<TimetableGetWeekResponse> getWeek(
|
||||
DateTime startDate,
|
||||
|
||||
Reference in New Issue
Block a user