import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; import '../api/marianumcloud/talk/room/get_room_response.dart'; import '../main.dart'; import '../model/account_data.dart'; import '../state/app/modules/app_modules.dart'; import '../state/app/modules/chat/bloc/chat_bloc.dart'; import '../state/app/modules/chat_list/bloc/chat_list_bloc.dart'; import '../state/app/modules/marianum_message/bloc/marianum_message_state.dart'; import '../view/pages/files/files.dart'; import '../view/pages/marianum_message/marianum_message_view.dart'; import '../view/pages/more/feedback/feedback_dialog.dart'; import '../view/pages/more/roomplan/roomplan.dart'; import '../view/pages/more/share/qr_share_view.dart'; import '../view/pages/settings/settings.dart'; import '../view/pages/talk/chat_view.dart'; import '../view/pages/talk/details/message_reactions.dart'; import '../view/pages/talk/talk_navigator.dart'; import '../view/pages/timetable/custom_events/custom_events_view.dart'; import '../widget/debug/cache_view.dart'; import '../widget/file_viewer.dart'; import '../widget/user_avatar.dart'; /// Single entry point for full-page navigations across the app. /// /// Every full-page push in modules should go through one of these methods. /// Dialogs (`showDialog`), bottom sheets (`showStickyFlexibleBottomSheet`, /// `showAppointmentBottomSheet`), and `Navigator.pop` for closing those /// remain unchanged and live at the call sites. class AppRoutes { AppRoutes._(); /// Token of a chat that should be auto-opened in the Talk tab once /// the chat list view picks it up. Set by [openChatByToken] (e.g. from /// a tapped notification) and consumed by the `ChatList` widget. static final ValueNotifier pendingChatToken = ValueNotifier(null); // -- Files -------------------------------------------------------------- static void openFolder(BuildContext context, List path) { pushScreen(context, withNavBar: false, screen: Files(path: path)); } static void openFileViewer(BuildContext context, String localPath, {bool openExternal = false}) { pushScreen( context, withNavBar: false, screen: FileViewer(path: localPath, openExternal: openExternal), ); } // -- Timetable ---------------------------------------------------------- static void openCustomEvents(BuildContext context) { pushScreen(context, withNavBar: false, screen: const CustomEventsView()); } // -- Marianum Message --------------------------------------------------- static void openMarianumMessage(BuildContext context, String basePath, MarianumMessage message) { pushScreen( context, withNavBar: false, screen: MessageView(basePath: basePath, message: message), ); } // -- Sharing / Settings / Feedback / DevTools --------------------------- static void openQrShare(BuildContext context) { pushScreen(context, withNavBar: false, screen: const QrShareView()); } static void openSettings(BuildContext context) { pushScreen(context, withNavBar: false, screen: const Settings()); } static void openFeedback(BuildContext context) { pushScreen(context, withNavBar: false, screen: const FeedbackDialog()); } static void openCacheView(BuildContext context) { pushScreen(context, withNavBar: false, screen: const CacheView()); } static void openRoomplan(BuildContext context) { pushScreen(context, withNavBar: false, screen: const Roomplan()); } // -- Talk --------------------------------------------------------------- static void openMessageReactions(BuildContext context, String token, int messageId) { pushScreen( context, withNavBar: false, screen: MessageReactions(token: token, messageId: messageId), ); } /// Opens a chat from a known [GetRoomResponseObject]. Delegates to /// [TalkNavigator.pushSplitView] so tablet split-view behaviour stays intact. static void openChatView( BuildContext context, { required GetRoomResponseObject room, required String selfId, required UserAvatar avatar, bool overrideToSingleSubScreen = true, }) { TalkNavigator.pushSplitView( context, ChatView(room: room, selfId: selfId, avatar: avatar), overrideToSingleSubScreen: overrideToSingleSubScreen, ); context.read().setToken(room.token); } /// Schedules a chat to be opened in the Talk tab. Use this when only the /// token is known (e.g. from a tapped notification) — the actual push /// happens inside the `ChatList` widget once the room is available. static void openChatByToken(BuildContext context, String token) { pendingChatToken.value = token; goToTalkTab(context); try { context.read().refresh(); } catch (e) { if (kDebugMode) debugPrint('openChatByToken: ChatListBloc refresh failed: $e'); } } /// Resolves a pending chat token (set via [openChatByToken]) using the /// [ChatListBloc]'s current rooms and the active [AccountData] credentials. /// Returns `null` if the token cannot yet be matched (e.g. the room is /// still being loaded). Callers should keep listening to [pendingChatToken] /// and the bloc state and retry when either changes. static ResolvedPendingChat? resolvePendingChat(BuildContext context) { final token = pendingChatToken.value; if (token == null) return null; if (!AccountData().isPopulated()) return null; final rooms = context.read().state.data?.rooms; final room = _findRoomByToken(rooms, token); if (room == null) return null; final isGroup = room.type != GetRoomResponseObjectConversationType.oneToOne; final avatar = UserAvatar(id: isGroup ? room.token : room.name, isGroup: isGroup); return ResolvedPendingChat( room: room, selfId: AccountData().getUsername(), avatar: avatar, ); } static GetRoomResponseObject? _findRoomByToken(GetRoomResponse? rooms, String token) { if (rooms == null) return null; for (final room in rooms.data) { if (room.token == token) return room; } return null; } // -- Module / Tab navigation ------------------------------------------- /// Opens an [AppModule]'s root view as a full screen push (used by the /// "Mehr" tab list). Modules that live in the bottom bar are reached via /// [goToTab] instead. static void openModule(BuildContext context, AppModule module) { pushScreen(context, withNavBar: false, screen: module.create()); } /// Switches the bottom navigation to the given [module] if it is currently /// in the bottom bar. Returns `true` if the jump happened. static bool goToTab(BuildContext context, Modules module) { final index = AppModule.getBottomBarModules(context) .map((e) => e.module) .toList() .indexOf(module); if (index == -1) return false; Main.bottomNavigator.jumpToTab(index); return true; } /// Convenience wrapper for the Talk tab — preserved for the notification /// handler API which only knows about Talk. static void goToTalkTab(BuildContext context) { goToTab(context, Modules.talk); } } class ResolvedPendingChat { final GetRoomResponseObject room; final String selfId; final UserAvatar avatar; const ResolvedPendingChat({ required this.room, required this.selfId, required this.avatar, }); }