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/modules_settings_page.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. Dialogs and bottom sheets /// stay at the call sites; only full-page pushes go through here. class AppRoutes { AppRoutes._(); /// Set by [openChatByToken] (e.g. from a tapped notification) and consumed /// by `ChatList` once the matching room is loaded. static final ValueNotifier pendingChatToken = ValueNotifier(null); 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), ); } static void openCustomEvents(BuildContext context) { pushScreen(context, withNavBar: false, screen: const CustomEventsView()); } static void openMarianumMessage(BuildContext context, String basePath, MarianumMessage message) { pushScreen( context, withNavBar: false, screen: MessageView(basePath: basePath, message: message), ); } 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 openModulesSettings(BuildContext context) { pushScreen(context, withNavBar: false, screen: const ModulesSettingsPage()); } 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()); } static void openMessageReactions(BuildContext context, String token, int messageId) { pushScreen( context, withNavBar: false, screen: MessageReactions(token: token, messageId: messageId), ); } 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 once the room is loaded. /// Use when only the token is known (e.g. from a tapped notification). 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]). Returns null /// if the room or account is not ready yet — callers should retry when /// [pendingChatToken] or the bloc state change. 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; } /// Pushes a module from the "Mehr" tab list. Modules already in the bottom /// bar are switched to via [goToTab] instead. static void openModule(BuildContext context, AppModule module) { pushScreen(context, withNavBar: false, screen: module.create()); } /// Switches the bottom navigation to [module]. Returns false when the /// module is not currently in the bottom bar. 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; } 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, }); }