diff --git a/lib/routing/app_routes.dart b/lib/routing/app_routes.dart index 143bf52..d7a0c82 100644 --- a/lib/routing/app_routes.dart +++ b/lib/routing/app_routes.dart @@ -37,8 +37,10 @@ import '../view/pages/talk/talk_navigator.dart'; import '../view/pages/ticker/ticker_page_view.dart'; import '../view/pages/timetable/custom_events/custom_events_view.dart'; import '../view/pages/timetable/subject_colors/subject_colors_view.dart'; +import '../widget/avatar_crop_page.dart'; import '../widget/debug/cache_view.dart'; import '../widget/file_viewer.dart'; +import '../widget/large_profile_picture_view.dart'; import '../widget/user_avatar.dart'; /// Single entry point for full-page navigations. Dialogs and bottom sheets @@ -95,6 +97,30 @@ class AppRoutes { pushScreen(context, withNavBar: false, screen: const SubjectColorsView()); } + /// Opens the full-screen cropper on [imageBytes] and resolves to the cropped + /// bytes (or null if cancelled). [aspectRatio] defaults to 1:1 for avatars; + /// pass null for a free-form crop (e.g. chat backgrounds). + static Future openAvatarCrop( + BuildContext context, { + required Uint8List imageBytes, + double? aspectRatio = 1, + }) { + return Navigator.of(context).push( + MaterialPageRoute( + fullscreenDialog: true, + builder: (_) => + AvatarCropPage(imageBytes: imageBytes, aspectRatio: aspectRatio), + ), + ); + } + + /// Opens the tappable, zoomable profile-picture viewer for [id]. + static void openLargeProfilePicture(BuildContext context, String id) { + Navigator.of(context).push( + MaterialPageRoute(builder: (_) => LargeProfilePictureView(id: id)), + ); + } + /// Opens the picker for choosing a foreign timetable element and resolves to /// the selected element (or null if dismissed). The timetable view renders /// the chosen plan inline. Gated behind the `viewForeignTimetables` diff --git a/lib/view/pages/settings/sections/account_section.dart b/lib/view/pages/settings/sections/account_section.dart index df33022..1bd678a 100644 --- a/lib/view/pages/settings/sections/account_section.dart +++ b/lib/view/pages/settings/sections/account_section.dart @@ -5,6 +5,7 @@ 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 '../../../../state/app/modules/account/bloc/account_bloc.dart'; import '../../../../state/app/modules/account/bloc/account_state.dart'; import '../../../../widget/app_progress_indicator.dart'; @@ -12,7 +13,6 @@ import '../../../../widget/async_action_button.dart'; import '../../../../widget/avatar_actions_sheet.dart'; import '../../../../widget/confirm_dialog.dart'; import '../../../../widget/demo_restricted.dart'; -import '../../../../widget/large_profile_picture_view.dart'; import '../../../../widget/user_avatar.dart'; // Display-name is process-wide stable until the user logs out; cache it so @@ -107,12 +107,8 @@ class _AccountSectionState extends State { children: [ Center( child: GestureDetector( - onTap: () => Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => - LargeProfilePictureView(id: username), - ), - ), + onTap: () => + AppRoutes.openLargeProfilePicture(context, username), child: UserAvatar( key: ValueKey(_avatarVersion), id: username, diff --git a/lib/widget/avatar_actions_sheet.dart b/lib/widget/avatar_actions_sheet.dart index f267109..460a0f4 100644 --- a/lib/widget/avatar_actions_sheet.dart +++ b/lib/widget/avatar_actions_sheet.dart @@ -3,7 +3,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; -import 'avatar_crop_page.dart'; +import '../routing/app_routes.dart'; import 'file_pick.dart'; /// Result of the user's choice inside [showAvatarActionsSheet]. The sheet @@ -107,10 +107,5 @@ Future _pickAndCrop( if (picked == null) return null; final bytes = await picked.readAsBytes(); if (!context.mounted) return null; - return Navigator.of(context).push( - MaterialPageRoute( - fullscreenDialog: true, - builder: (_) => AvatarCropPage(imageBytes: bytes), - ), - ); + return AppRoutes.openAvatarCrop(context, imageBytes: bytes); } diff --git a/lib/widget/chat_background_picker_sheet.dart b/lib/widget/chat_background_picker_sheet.dart index 54eec36..9d3a5d3 100644 --- a/lib/widget/chat_background_picker_sheet.dart +++ b/lib/widget/chat_background_picker_sheet.dart @@ -3,7 +3,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; -import 'avatar_crop_page.dart'; +import '../routing/app_routes.dart'; import 'file_pick.dart'; /// Bottom sheet with "from gallery" and "take photo" actions for choosing a @@ -61,12 +61,7 @@ Future showChatBackgroundPickerSheet(BuildContext context) async { Future cropChatBackgroundImage( BuildContext context, Uint8List bytes, -) => Navigator.of(context).push( - MaterialPageRoute( - fullscreenDialog: true, - builder: (_) => AvatarCropPage(imageBytes: bytes, aspectRatio: null), - ), -); +) => AppRoutes.openAvatarCrop(context, imageBytes: bytes, aspectRatio: null); Future _pickRaw(Future Function() pick) async { final picked = await pick();