replaced account data singleton with a session model

This commit is contained in:
2026-09-27 14:49:38 +02:00
parent 79b33ab248
commit 5e65c4671a
60 changed files with 1019 additions and 679 deletions
@@ -2,8 +2,8 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import '../../../../api/marianumcloud/webdav/queries/list_files/cacheable_file.dart';
import '../../../../model/account_data.dart';
import '../../../../model/endpoint_data.dart';
import '../../../../session/session_manager.dart';
import '../data/file_type_icon.dart';
/// Leading slot for a file row: shows the Nextcloud thumbnail when the
@@ -35,7 +35,7 @@ class FileLeading extends StatelessWidget {
'https://${EndpointData().nextcloud().full()}'
'/index.php/core/preview'
'?fileId=$fileId&x=128&y=128&a=0',
httpHeaders: AccountData().authHeaders(),
httpHeaders: SessionManager().requireNextcloud().authHeaders,
fit: BoxFit.cover,
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
@@ -7,7 +7,7 @@ import '../../../api/marianumconnect/queries/timetable_get_element_week/timetabl
import '../../../api/marianumconnect/queries/timetable_get_rooms/timetable_get_rooms.dart';
import '../../../api/marianumconnect/queries/timetable_get_students/timetable_get_students.dart';
import '../../../api/marianumconnect/queries/timetable_get_teachers/timetable_get_teachers.dart';
import '../../../model/account_data.dart';
import '../../../session/session_manager.dart';
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
import '../../../storage/timetable_favorites_settings.dart';
import '../../../utils/haptics.dart';
@@ -56,7 +56,7 @@ class _ElementPickerPageState extends State<ElementPickerPage> {
}
Future<List<_PickerItem>> _loadFor(TimetableElementType type) {
final epoch = AccountData().sessionEpoch;
final epoch = SessionManager().sessionEpoch;
final cached = _futures[type];
if (cached != null &&
cached.epoch == epoch &&
+15 -9
View File
@@ -13,10 +13,11 @@ import '../../../api/marianumconnect/marianumconnect_endpoint.dart';
import '../../../api/marianumconnect/queries/telemetry_heartbeat/telemetry_device_id.dart';
import '../../../background/widget_background_task.dart';
import '../../../extensions/date_time.dart';
import '../../../model/account_data.dart';
import '../../../model/endpoint_data.dart';
import '../../../push/push_registration_store.dart';
import '../../../push/push_registration_type.dart';
import '../../../session/session.dart';
import '../../../session/session_manager.dart';
import '../../../state/app/modules/account/bloc/account_bloc.dart';
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
import '../../../utils/clipboard_helper.dart';
@@ -44,7 +45,8 @@ class _DiagnosticsPageState extends State<DiagnosticsPage> {
Future<List<_Section>> _collect() async {
final settings = context.read<SettingsCubit>().val();
final accountStatus = context.read<AccountBloc>().state.status;
final account = AccountData();
final session = SessionManager().current;
final nextcloud = session?.nextcloud;
const tokens = MarianumConnectTokenStorage();
final push = PushRegistrationStore();
@@ -101,13 +103,17 @@ class _DiagnosticsPageState extends State<DiagnosticsPage> {
title: 'Sitzung',
rows: await rows({
'Status': () async => accountStatus.name,
'Benutzer': () async =>
account.isPopulated() ? account.getUsername() : null,
'Demo-Modus': () async => account.isDemo,
'Login Flow v2': () async => account.usesLoginFlow,
'App-Passwort (Dateien)': () async => account.hasAppPassword(),
'App-Passwort (Talk)': () async => account.hasAppPasswordTalk(),
'Session-Epoche': () async => account.sessionEpoch,
'Benutzer': () async => switch (session) {
CredentialSession(:final username) => username,
null => null,
},
'Demo-Modus': () async => session?.isDemo ?? false,
'Login Flow v2': () async => nextcloud?.usesLoginFlow ?? false,
'App-Passwort (Dateien)': () async =>
nextcloud?.hasAppPassword ?? false,
'App-Passwort (Talk)': () async =>
nextcloud?.hasAppPasswordTalk ?? false,
'Session-Epoche': () async => SessionManager().sessionEpoch,
'MC-Token-ID': tokens.readTokenId,
'MC-Token läuft ab': tokens.readExpiresAt,
}),
@@ -4,10 +4,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../api/marianumcloud/cloud_users/cloud_users_actions.dart';
import '../../../../api/marianumconnect/queries/auth_logout/auth_logout.dart';
import '../../../../model/account_data.dart';
import '../../../../push/push_registration.dart';
import '../../../../routing/app_routes.dart';
import '../../../../session/session_lifecycle.dart';
import '../../../../session/session_manager.dart';
import '../../../../state/app/modules/account/bloc/account_bloc.dart';
import '../../../../state/app/modules/account/bloc/account_state.dart';
import '../../../../widget/app_progress_indicator.dart';
@@ -84,13 +84,17 @@ class _AccountSectionState extends State<AccountSection> {
setState(() => _avatarBusy = false);
if (!ok) return;
invalidateAvatarCache(id: AccountData().getUsername(), isGroup: false);
invalidateAvatarCache(
id: SessionManager().requireNextcloud().username,
isGroup: false,
);
setState(() => _avatarVersion++);
}
@override
Widget build(BuildContext context) {
final username = AccountData().getUsername();
final nextcloud = SessionManager().requireNextcloud();
final username = nextcloud.username;
final displayName = _displayName;
final theme = Theme.of(context);
@@ -175,7 +179,7 @@ class _AccountSectionState extends State<AccountSection> {
// Nur für Login-Flow-Konten (2FA) sichtbar — Passwort-Konten heilen
// sich still über das App-Passwort-Minting und sollen von dem ganzen
// Flow-Mechanismus nichts mitbekommen.
if (!AccountData().isDemo && AccountData().usesLoginFlow)
if (!SessionManager().isDemo && nextcloud.usesLoginFlow)
AsyncListTile(
leading: const Icon(Icons.cloud_sync_outlined),
title: const Text('Nextcloud neu verbinden'),
@@ -203,7 +207,7 @@ class _AccountSectionState extends State<AccountSection> {
Future<void> _showLogoutDialog(BuildContext context) async {
// Flip AccountBloc state only after the dialog fully closes: doing it from
// inside removeData (the previous approach) raced AsyncDialogAction's
// inside the sign-out (the previous approach) raced AsyncDialogAction's
// pop(true) against the listener's popUntil(isFirst) and could leave the
// navigator in an inconsistent state.
final confirmed = await showDialog<bool>(
@@ -219,14 +223,8 @@ class _AccountSectionState extends State<AccountSection> {
context.read<AccountBloc>().setStatus(AccountStatus.loggedOut);
}
// Ordered teardown: unregister push at Nextcloud + proxy and revoke the app
// password (while Nextcloud credentials are still available), THEN revoke the
// MC bearer token, and finally wipe local credentials. Each step is
// best-effort so an offline logout still reaches a clean local state.
Future<void> _performLogout() async {
await PushRegistration().logoutCleanup();
await AuthLogout().run();
await AccountData().removeData();
await SessionLifecycle.signOut();
_cachedDisplayName = null;
}
}
+2 -2
View File
@@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
import '../../../../api/marianumcloud/talk/chat/get_chat_response.dart';
import '../../../../api/marianumcloud/talk/chat/rich_object_string_processor.dart';
import '../../../../model/account_data.dart';
import '../../../../model/endpoint_data.dart';
import '../../../../session/session_manager.dart';
import '../../../../utils/emoji_detection.dart';
import '../../../../utils/url_opener.dart';
import '../widgets/highlighted_linkify.dart';
@@ -105,7 +105,7 @@ class ChatMessage {
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
errorListener: (value) {},
httpHeaders: AccountData().authHeaders(),
httpHeaders: SessionManager().requireNextcloud().authHeaders,
imageUrl:
'https://${EndpointData().nextcloud().full()}/index.php/core/preview?fileId=${file!.id}&x=130&y=-1&a=1',
),
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import '../../../../api/marianumcloud/talk/get_reactions/get_reactions.dart';
import '../../../../api/marianumcloud/talk/get_reactions/get_reactions_response.dart';
import '../../../../model/account_data.dart';
import '../../../../session/session_manager.dart';
import '../../../../widget/centered_leading.dart';
import '../../../../widget/emoji_text.dart';
import '../../../../widget/loading_spinner.dart';
@@ -63,10 +63,10 @@ class _MessageReactionsState extends State<MessageReactions> {
leading: CenteredLeading(EmojiText(entry.key)),
title: Text('${entry.value.length} mal reagiert'),
children: entry.value.map((e) {
final isSelf = AccountData().getUsername() == e.actorId;
final isSelf =
SessionManager().requireNextcloud().username == e.actorId;
final isGuest =
e.actorType ==
GetReactionsResponseObjectActorType.guests;
e.actorType == GetReactionsResponseObjectActorType.guests;
return ListTile(
leading: UserAvatar(id: e.actorId, isGroup: false),
title: Text(e.actorDisplayName),
@@ -2,7 +2,7 @@ import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import '../../../../api/marianumcloud/talk/get_participants/get_participants_response.dart';
import '../../../../model/account_data.dart';
import '../../../../session/session_manager.dart';
import '../../../../widget/user_avatar.dart';
import '../data/open_direct_chat.dart';
@@ -36,7 +36,7 @@ class ParticipantsListView extends StatelessWidget {
(participant) => participant.participantType,
);
final selfId = AccountData().getUsername();
final selfId = SessionManager().requireNextcloud().username;
return Scaffold(
appBar: AppBar(title: const Text('Mitglieder')),
body: ListView(
@@ -9,8 +9,8 @@ import '../../../../api/marianumcloud/talk/get_shared_items/get_shared_items_ove
import '../../../../api/marianumcloud/talk/get_shared_items/get_shared_items_response.dart';
import '../../../../api/marianumcloud/talk/room/get_room_response.dart';
import '../../../../extensions/date_time.dart';
import '../../../../model/account_data.dart';
import '../../../../model/endpoint_data.dart';
import '../../../../session/session_manager.dart';
import '../../../../share_intent/remote_file_ref.dart';
import '../../../../utils/downloads/download_job.dart';
import '../../../../widget/app_progress_indicator.dart';
@@ -54,9 +54,10 @@ class SharedItemsPage {
const SharedItemsPage(this.items, this.lastKnownMessageId, this.hasMore);
}
List<GetChatResponseObject> _fileItems(List<GetChatResponseObject> items) => items
.where((item) => item.messageParameters?['file']?.path != null)
.toList();
List<GetChatResponseObject> _fileItems(List<GetChatResponseObject> items) =>
items
.where((item) => item.messageParameters?['file']?.path != null)
.toList();
SharedItemsPage buildSharedItemsPage(
GetSharedItemsResponse response,
@@ -140,7 +141,9 @@ class _SharedItemsViewState extends State<SharedItemsView>
Future<void> _load() async {
setState(() => _error = null);
try {
final overview = await SharedItemsView.prefetchOverview(widget.room.token);
final overview = await SharedItemsView.prefetchOverview(
widget.room.token,
);
if (!mounted) return;
_overview = overview;
_prepareTabs();
@@ -501,7 +504,10 @@ class _SharedItemTileState extends State<_SharedItemTile>
if (isDownloading) {
confirmCancelDownload();
} else {
startDownload(name: _file.name, remoteFile: RemoteFileRef.fromTalk(_file));
startDownload(
name: _file.name,
remoteFile: RemoteFileRef.fromTalk(_file),
);
}
}
@@ -533,7 +539,7 @@ class _SharedItemTileState extends State<_SharedItemTile>
children: [
CachedNetworkImage(
imageUrl: _previewUrl,
httpHeaders: AccountData().authHeaders(),
httpHeaders: SessionManager().requireNextcloud().authHeaders,
fit: BoxFit.cover,
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
+3 -7
View File
@@ -9,9 +9,9 @@ import '../../../../api/marianumcloud/talk/room/get_room_response.dart';
import '../../../../api/marianumcloud/talk/room/notification_level.dart';
import '../../../../api/marianumcloud/talk/set_read_marker/set_read_marker.dart';
import '../../../../extensions/date_time.dart';
import '../../../../model/account_data.dart';
import '../../../../notification/notification_tasks.dart';
import '../../../../routing/app_routes.dart';
import '../../../../session/session_manager.dart';
import '../../../../state/app/modules/chat/bloc/chat_bloc.dart';
import '../../../../state/app/modules/chat_list/bloc/chat_list_bloc.dart';
import '../../../../utils/haptics.dart';
@@ -53,13 +53,9 @@ class _ChatTileState extends State<ChatTile> {
@override
void initState() {
super.initState();
AccountData().waitForPopulation().then((_) {
SessionManager().waitForLoad().then((session) {
if (!mounted) return;
setState(
() => selfUsername = AccountData().isPopulated()
? AccountData().getUsername()
: null,
);
setState(() => selfUsername = session?.nextcloud?.username);
});
}
@@ -5,7 +5,7 @@ import '../../../../api/marianumcloud/talk/get_poll/get_poll_state_response.dart
import '../../../../api/marianumcloud/talk/room/get_room_response.dart';
import '../../../../api/marianumcloud/talk/vote_poll/vote_poll.dart';
import '../../../../api/marianumcloud/talk/vote_poll/vote_poll_params.dart';
import '../../../../model/account_data.dart';
import '../../../../session/session_manager.dart';
import '../../../../widget/async_action_button.dart';
import '../../../../widget/confirm_dialog.dart';
import '../../../../widget/demo_restricted.dart';
@@ -186,7 +186,7 @@ class _PollOptionsListState extends State<PollOptionsList> {
Widget _actionBar(GetPollStateResponseObject poll, ThemeData theme) {
final canClose = poll.canClose(
selfId: AccountData().getUsername(),
selfId: SessionManager().requireNextcloud().username,
participantType: widget.room.participantType,
);
if (!_isInteractive && !canClose) return const SizedBox.shrink();