diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index ffe48033..b10aeef0 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -168,7 +168,7 @@
android:enabled="true"
android:exported="true"
android:hardwareAccelerated="true"
- android:launchMode="singleTop"
+ android:launchMode="singleTask"
android:showOnLockScreen="true"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize"
@@ -205,6 +205,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 85d84646..3df3ba06 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -30,6 +30,8 @@
FIVKSupportVideo
+ FlutterDeepLinkingEnabled
+
LSApplicationQueriesSchemes
comgooglemaps
diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements
index 2c37e774..874ac0d6 100644
--- a/ios/Runner/Runner.entitlements
+++ b/ios/Runner/Runner.entitlements
@@ -21,5 +21,10 @@
TAG
+ com.apple.developer.associated-domains
+
+ applinks:hmgapps.com
+ applinks:www.hmgapps.com
+
diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart
index bf30324e..7623a8e3 100644
--- a/lib/core/api_consts.dart
+++ b/lib/core/api_consts.dart
@@ -6,7 +6,7 @@ import 'package:hmg_patient_app_new/core/enums.dart';
class ApiConsts {
static const maxSmallScreen = 660;
- static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat;
+ static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT
diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart
index 25753e1a..7ca730b8 100644
--- a/lib/core/app_state.dart
+++ b/lib/core/app_state.dart
@@ -51,6 +51,9 @@ class AppState {
bool isMyFatoorahEnabled = false;
// bool isMyFatoorahEnabled = true;
+ String? deepLinkJoinSessionId;
+ bool isSplashInitializationDone = false;
+
void setIsPaytabsEnabled(bool value) {
isPaytabsEnabled = false;
// isPaytabsEnabled = true;
diff --git a/lib/main.dart b/lib/main.dart
index 47416b49..36c59870 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -11,6 +11,7 @@ import 'package:flutter/services.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
+import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
@@ -55,11 +56,14 @@ import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_mode
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
+import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
+import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/app_lifecycle_service.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/services/security_service.dart';
+import 'package:hmg_patient_app_new/splashPage.dart';
import 'package:hmg_patient_app_new/theme/app_theme.dart';
import 'package:hmg_patient_app_new/unsafe_device.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart';
@@ -142,13 +146,13 @@ Future callInitializations() async {
await callAppStateInitializations();
// Initialize Security Service early to catch threats before app logic runs
- if (kReleaseMode) {
- try {
- await getIt.get().initialize();
- } catch (e) {
- getIt.get().logError('SecurityService.initialize failed, continuing app startup: $e');
- }
- }
+ // if (kReleaseMode) {
+ // try {
+ // await getIt.get().initialize();
+ // } catch (e) {
+ // getIt.get().logError('SecurityService.initialize failed, continuing app startup: $e');
+ // }
+ // }
// Set up critical threat callback to navigate to unsafe device page
getIt.get().setOnCriticalThreatCallback(() {
@@ -386,6 +390,16 @@ class MyApp extends StatelessWidget {
}
}
+ String? _extractJoinSessionId(String? routeName) {
+ if (routeName == null || routeName.isEmpty) return null;
+ // Matches both "https://hmgapps.com/join/{id}" and "/join/{id}"
+ final match = RegExp(r'/join/([^?#]+)').firstMatch(routeName);
+ if (match != null && match.group(1)!.isNotEmpty) {
+ return Uri.decodeComponent(match.group(1)!);
+ }
+ return null;
+ }
+
@override
Widget build(BuildContext context) {
return SafeArea(
@@ -422,6 +436,51 @@ class MyApp extends StatelessWidget {
locale: context.locale,
initialRoute: AppRoutes.initialRoute,
routes: AppRoutes.routes,
+ onGenerateInitialRoutes: (String initialRouteName) {
+ final defaultRoute = WidgetsBinding.instance.platformDispatcher.defaultRouteName;
+ final sessionId = _extractJoinSessionId(initialRouteName) ?? _extractJoinSessionId(defaultRoute);
+ if (sessionId != null) {
+ getIt.get().deepLinkJoinSessionId = sessionId;
+ Utils.saveStringFromPrefs(CacheConst.zoomRoomID, sessionId);
+ debugPrint('DeepLink (Cold Start) Join Session ID: $sessionId');
+ }
+ return [
+ MaterialPageRoute(builder: (_) => const SplashPage()),
+ ];
+ },
+ onGenerateRoute: (RouteSettings settings) {
+ final sessionId = _extractJoinSessionId(settings.name);
+ if (sessionId != null) {
+ final appState = getIt.get();
+ Utils.saveStringFromPrefs(CacheConst.zoomRoomID, sessionId);
+ debugPrint('DeepLink (Route) Join Session ID: $sessionId');
+ // If SplashPage is still initializing, save it so SplashPage navigates when ready
+ if (!appState.isSplashInitializationDone) {
+ appState.deepLinkJoinSessionId = sessionId;
+ return MaterialPageRoute(builder: (_) => const SplashPage());
+ }
+ // Warm Start: App is already initialized, open CallScreen directly
+ return MaterialPageRoute(
+ settings: RouteSettings(
+ name: AppRoutes.zoomCallPage,
+ arguments: CallArguments(sessionId, "123", "Patient", "40", "0", true, 1),
+ ),
+ builder: (_) => const CallScreen(),
+ );
+ }
+ // Fallback for normal named routes if any reach onGenerateRoute
+ final builder = AppRoutes.routes[settings.name];
+ if (builder != null) {
+ return MaterialPageRoute(builder: builder, settings: settings);
+ }
+ return null;
+ },
+ onUnknownRoute: (RouteSettings settings) {
+ // Prevents crash when opened via root link https://hmgapps.com
+ return MaterialPageRoute(
+ builder: (_) => getIt.get().isSplashInitializationDone ? LandingNavigation() : const SplashPage(),
+ );
+ },
theme: AppTheme.getTheme(isArabic),
darkTheme: AppTheme.getDarkTheme(isArabic),
themeMode: profileVm.isDarkMode ? ThemeMode.dark : ThemeMode.light,
diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart
index 496be3cb..970e46fb 100644
--- a/lib/routes/app_routes.dart
+++ b/lib/routes/app_routes.dart
@@ -115,6 +115,7 @@ class AppRoutes {
static const String refundRequestDetailScreen = '/refundRequestDetailScreen';
static Map get routes => {
+ '/': (context) => SplashPage(),
initialRoute: (context) => SplashPage(),
loginScreen: (context) => LoginScreen(),
landingScreen: (context) => LandingNavigation(),
diff --git a/lib/services/app_lifecycle_service.dart b/lib/services/app_lifecycle_service.dart
index 4a0dd947..7bdf7058 100644
--- a/lib/services/app_lifecycle_service.dart
+++ b/lib/services/app_lifecycle_service.dart
@@ -1,5 +1,12 @@
import 'dart:async';
import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/app_state.dart';
+import 'package:hmg_patient_app_new/core/cache_consts.dart';
+import 'package:hmg_patient_app_new/core/dependencies.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart';
+import 'package:hmg_patient_app_new/routes/app_routes.dart';
+import 'package:hmg_patient_app_new/services/navigation_service.dart';
/// Service to monitor and track app lifecycle state changes
/// This helps detect when app goes to background/foreground
@@ -60,7 +67,52 @@ class AppLifecycleService with WidgetsBindingObserver {
_stateController.add(state);
}
-
+
+ String? _extractJoinSessionId(String? routeName) {
+ if (routeName == null || routeName.isEmpty) return null;
+ final match = RegExp(r'/join/([^?#]+)').firstMatch(routeName);
+ if (match != null && match.group(1)!.isNotEmpty) {
+ return Uri.decodeComponent(match.group(1)!);
+ }
+ return null;
+ }
+
+ Future _handleIncomingDeepLink(String? route) async {
+ debugPrint('🔗 DeepLink received in AppLifecycleService: $route');
+ final sessionId = _extractJoinSessionId(route);
+ if (sessionId != null) {
+ final appState = getIt.get();
+ await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, sessionId);
+ debugPrint('🔗 DeepLink Join Session ID parsed: $sessionId (splashDone=${appState.isSplashInitializationDone})');
+
+ if (!appState.isSplashInitializationDone) {
+ appState.deepLinkJoinSessionId = sessionId;
+ return true;
+ }
+
+ final navState = getIt.get().navigatorKey.currentState;
+ if (navState != null) {
+ String callTypeID = await Utils.getStringFromPrefs(CacheConst.callTypeID);
+ navState.pushNamed(
+ AppRoutes.zoomCallPage,
+ arguments: CallArguments(sessionId, "123", "Patient", "40", "0", true, int.tryParse(callTypeID) ?? 1),
+ );
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @override
+ Future didPushRouteInformation(RouteInformation routeInformation) {
+ return _handleIncomingDeepLink(routeInformation.uri.toString());
+ }
+
+ @override
+ Future didPushRoute(String route) {
+ return _handleIncomingDeepLink(route);
+ }
+
/// Initialize the lifecycle observer
void initialize() {
WidgetsBinding.instance.addObserver(this);
diff --git a/lib/splashPage.dart b/lib/splashPage.dart
index b1d03c80..3fc7a57e 100644
--- a/lib/splashPage.dart
+++ b/lib/splashPage.dart
@@ -90,8 +90,13 @@ class _SplashScreenState extends State {
_initializeClarity();
}
+ getIt.get().isSplashInitializationDone = true;
+
if (isAppOpenedFromCall) {
navigateToTeleConsult();
+ } else if (getIt.get().deepLinkJoinSessionId != null) {
+ getIt.get().deepLinkJoinSessionId = null;
+ navigateToTeleConsult();
} else {
if (await Utils.getBoolFromPrefs(CacheConst.firstLaunch)) {
// Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: OnboardingScreen())));
@@ -166,13 +171,13 @@ class _SplashScreenState extends State {
// GetIt.instance().remove(key: CacheConst.isAppOpenedFromCall);
Utils.removeFromPrefs(CacheConst.isAppOpenedFromCall);
- Navigator.of(GetIt.instance().navigatorKey.currentContext!).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LandingNavigation())));
- Navigator.pushReplacementNamed(
+ Navigator.of(GetIt.instance().navigatorKey.currentContext!).pushReplacement(FadePage(page: LandingNavigation()));
+ Navigator.pushNamed(
// context,
GetIt.instance().navigatorKey.currentContext!,
AppRoutes.zoomCallPage,
// arguments: CallArguments(appointmentID, "111", "Patient", "40", "1", true, 1),
- arguments: CallArguments(roomID, "123", "Patient", "40", "0", true, int.parse(callTypeID)),
+ arguments: CallArguments(roomID, "123", "Patient", "40", "0", true, int.tryParse(callTypeID) ?? 1),
// arguments: CallArguments("SmallDailyStandup9875", "123", "Patient", "40", "0", false, int.parse(widget.incomingCallData!.appointmentNo!)),
);
}