haroon_dev #341

Closed
Haroon6138 wants to merge 7 commits from haroon_dev into master

@ -2,4 +2,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip #distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-all.zip

@ -211,7 +211,7 @@ class ApiClientImp implements ApiClient {
} }
// body['TokenID'] = "@dm!n"; // body['TokenID'] = "@dm!n";
// body['PatientID'] = 3310954; // body['PatientID'] = 945786;
// body['PatientID'] = 53320; // body['PatientID'] = 53320;
// body['PatientTypeID'] = 1; // body['PatientTypeID'] = 1;
// body['PatientOutSA'] = 0; // body['PatientOutSA'] = 0;

@ -170,7 +170,6 @@ class AppState {
set setIsAuthenticated(v) => isAuthenticated = v; set setIsAuthenticated(v) => isAuthenticated = v;
String deviceTypeID = ""; String deviceTypeID = "";
set setDeviceTypeID(v) => deviceTypeID = v; set setDeviceTypeID(v) => deviceTypeID = v;
@ -179,6 +178,14 @@ class AppState {
String get getFamilyFileTokenID => _familyFileTokenID; String get getFamilyFileTokenID => _familyFileTokenID;
bool isSafeDevice = true;
// set setIsSafeDevice(v) => isSafeDevice = v;
set setIsSafeDevice(bool value) {
isSafeDevice = value;
}
set setFamilyFileTokenID(String value) { set setFamilyFileTokenID(String value) {
_familyFileTokenID = value; _familyFileTokenID = value;
} }

@ -84,6 +84,7 @@ 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/navigation_service.dart';
import 'package:hmg_patient_app_new/services/notification_service.dart'; import 'package:hmg_patient_app_new/services/notification_service.dart';
import 'package:hmg_patient_app_new/services/permission_service.dart'; import 'package:hmg_patient_app_new/services/permission_service.dart';
import 'package:hmg_patient_app_new/services/security_service.dart';
import 'package:hmg_patient_app_new/core/services/turnstile_service.dart'; import 'package:hmg_patient_app_new/core/services/turnstile_service.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart';
@ -157,6 +158,10 @@ class AppDependencies {
getIt.registerLazySingleton<PermissionService>(() => PermissionService()); getIt.registerLazySingleton<PermissionService>(() => PermissionService());
getIt.registerLazySingleton<TurnstileService>(() => TurnstileService(getIt<LoggerService>())); getIt.registerLazySingleton<TurnstileService>(() => TurnstileService(getIt<LoggerService>()));
getIt.registerLazySingleton<SecurityService>(() => SecurityServiceImpl(
appState: getIt(),
loggerService: getIt(),
));
// Repositories // Repositories
getIt.registerLazySingleton<CommonRepo>(() => CommonRepoImp(loggerService: getIt())); getIt.registerLazySingleton<CommonRepo>(() => CommonRepoImp(loggerService: getIt()));

@ -0,0 +1,15 @@
import 'package:freerasp/freerasp.dart';
final talsecConfig = TalsecConfig(
androidConfig: AndroidConfig(
packageName: 'com.cloudsolutions.HMGPatientApp',
signingCertHashes: ['6tvWaoN5coG4SnfxGbdQlcLmM0J4ePQwDjrKIg+QkV0=', 'j6VEqVhrypHMIiXiFdRLDdGwjGaMGWY7KAdBJA+Z4Pc='], // Must be the release cert hash
supportedStores: ['com.android.vending'], // Google Play Store
),
iosConfig: IOSConfig(
bundleIds: ['com.cloudsolutions.HMGPatientApp'], // iOS Bundle ID
teamId: '3A359E86ZF', // Found in Apple Developer portal
),
watcherMail: '', // Required to receive security alerts
isProd: true, // Enforces strict checks for release builds
);

@ -1064,9 +1064,42 @@ class Utils {
return isHavePrivilege; return isHavePrivilege;
} }
static void openWebView({required String url}) { static Future<void> openWebView({required String url}) async {
Uri uri = Uri.parse(url); try {
launchUrl(uri, mode: LaunchMode.inAppBrowserView); Uri uri = Uri.parse(url);
// Validate URL scheme for in-app browser
if (!uri.hasScheme || (!uri.scheme.startsWith('http'))) {
throw 'Invalid URL scheme. In-app browser only supports HTTP/HTTPS URLs';
}
// Check if URL can be launched
if (await canLaunchUrl(uri)) {
final launched = await launchUrl(
uri,
mode: LaunchMode.externalApplication,
);
if (!launched) {
// Fallback to external browser
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
} else {
// Fallback to external browser
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
} catch (e) {
debugPrint('❌ Failed to open URL: $url - Error: $e');
// Try external browser as last resort
try {
final uri = Uri.parse(url);
await launchUrl(uri, mode: LaunchMode.externalApplication);
} catch (e2) {
debugPrint('❌ Failed to open URL in external browser: $e2');
// Optionally show user-friendly error message
}
}
} }
static Color getCardBorderColor(int currentQueueStatus) { static Color getCardBorderColor(int currentQueueStatus) {

@ -2,3 +2,9 @@ const Map configs = {
'ZOOM_SDK_KEY': 'b9T74nhfTg-ioP9urm970A', 'ZOOM_SDK_KEY': 'b9T74nhfTg-ioP9urm970A',
'ZOOM_SDK_SECRET': 'KOzmjBNXQ1f4IPHpnngfL29uZvJMufSy2Fk8', 'ZOOM_SDK_SECRET': 'KOzmjBNXQ1f4IPHpnngfL29uZvJMufSy2Fk8',
}; };
// const Map configs = {
// 'ZOOM_SDK_KEY': 'jYHoRpSUMTTefOwLq94Kyf1Kak513TUHpu78',
// 'ZOOM_SDK_SECRET': 'MOqi1zc18VFOMEaBDshFSHKLB0C2n9M0K48H',
// };

@ -824,7 +824,11 @@ class AuthenticationViewModel extends ChangeNotifier {
onSuccess: (dynamic respData) async { onSuccess: (dynamic respData) async {
try { try {
if (respData != null) { if (respData != null) {
dynamic data = await SelectDeviceByImeiRespModelElement.fromJson(respData.toJson()); SelectDeviceByImeiRespModelElement data = SelectDeviceByImeiRespModelElement.fromJson(respData.toJson());
if (data.mobile == null || data.mobile == "" || data.identificationNo == null || data.identificationNo == "") {
return;
}
_appState.setSelectDeviceByImeiRespModelElement(data); _appState.setSelectDeviceByImeiRespModelElement(data);
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
@ -856,8 +860,6 @@ class AuthenticationViewModel extends ChangeNotifier {
} }
Future<void> checkUserAuthentication({required OTPTypeEnum otpTypeEnum, Function(dynamic)? onSuccess, Function(String)? onError}) async { Future<void> checkUserAuthentication({required OTPTypeEnum otpTypeEnum, Function(dynamic)? onSuccess, Function(String)? onError}) async {
// TODO: THIS SHOULD BE REMOVED LATER ON AND PASSED FROM APP STATE DIRECTLY INTO API CLIENT. BECAUSE THIS API ONLY NEEDS FEW PARAMS FROM USER
loginTypeEnum = otpTypeEnum == OTPTypeEnum.sms ? LoginTypeEnum.sms : LoginTypeEnum.whatsapp; loginTypeEnum = otpTypeEnum == OTPTypeEnum.sms ? LoginTypeEnum.sms : LoginTypeEnum.whatsapp;
// if (phoneNumberController.text.isEmpty) { // if (phoneNumberController.text.isEmpty) {

@ -2,7 +2,9 @@
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/core/api/api_client.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/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/appointment_nearest_gate_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/appointment_nearest_gate_response_model.dart';
@ -181,6 +183,9 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
"IsSearchAppointmnetByClinicID": isContinueDentalPlan ? false : true, "IsSearchAppointmnetByClinicID": isContinueDentalPlan ? false : true,
"isDentalAllowedBackend": clinicID == 17 ? true : isContinueDentalPlan, "isDentalAllowedBackend": clinicID == 17 ? true : isContinueDentalPlan,
"IsGetNearAppointment": isNearest, "IsGetNearAppointment": isNearest,
"gender": getIt.get<AppState>().isAuthenticated ? getIt.get<AppState>().getAuthenticatedUser()!.gender! : 0,
"age": getIt.get<AppState>().isAuthenticated ? getIt.get<AppState>().getAuthenticatedUser()!.age! : 0,
"DateofBirth": getIt.get<AppState>().isAuthenticated ? getIt.get<AppState>().getAuthenticatedUser()!.dateofBirth! : null,
if (isNearest) "SelectedDate": DateUtil.convertDateToString(DateTime.now()), if (isNearest) "SelectedDate": DateUtil.convertDateToString(DateTime.now()),
"License": true "License": true
}; };

@ -45,7 +45,6 @@ class MedicalFileViewModel extends ChangeNotifier {
List<SickLeaveList> patientSickLeavesViewList = []; List<SickLeaveList> patientSickLeavesViewList = [];
bool isSickLeavesSortByClinic = true; bool isSickLeavesSortByClinic = true;
bool isSickLeavesDataNeedsReloading = true; bool isSickLeavesDataNeedsReloading = true;
List<GetAllergiesResponseModel> patientAllergiesList = []; List<GetAllergiesResponseModel> patientAllergiesList = [];
@ -61,6 +60,7 @@ class MedicalFileViewModel extends ChangeNotifier {
List<MedicalReportList> patientMedicalReportsViewList = []; List<MedicalReportList> patientMedicalReportsViewList = [];
bool isMedicalReportsSortByClinic = true; bool isMedicalReportsSortByClinic = true;
bool isMedicalReportsDataNeedsReloading = true;
List<PatientAppointmentHistoryResponseModel> patientMedicalReportAppointmentHistoryList = []; List<PatientAppointmentHistoryResponseModel> patientMedicalReportAppointmentHistoryList = [];
PatientAppointmentHistoryResponseModel? patientMedicalReportSelectedAppointment; PatientAppointmentHistoryResponseModel? patientMedicalReportSelectedAppointment;
@ -192,7 +192,7 @@ class MedicalFileViewModel extends ChangeNotifier {
} }
setIsPatientMedicalReportsLoading(bool val) { setIsPatientMedicalReportsLoading(bool val) {
if (val) { if (val && isMedicalReportsDataNeedsReloading) {
onMedicalReportTabChange(0); onMedicalReportTabChange(0);
patientMedicalReportList.clear(); patientMedicalReportList.clear();
patientMedicalReportsByClinic.clear(); patientMedicalReportsByClinic.clear();
@ -200,8 +200,8 @@ class MedicalFileViewModel extends ChangeNotifier {
patientMedicalReportsViewList.clear(); patientMedicalReportsViewList.clear();
patientMedicalReportPDFBase64 = ""; patientMedicalReportPDFBase64 = "";
isMedicalReportsSortByClinic = true; isMedicalReportsSortByClinic = true;
isPatientMedicalReportsListLoading = val;
} }
isPatientMedicalReportsListLoading = val;
notifyListeners(); notifyListeners();
} }
@ -373,6 +373,10 @@ class MedicalFileViewModel extends ChangeNotifier {
} }
Future<void> getPatientMedicalReportList({Function(dynamic)? onSuccess, Function(String)? onError}) async { Future<void> getPatientMedicalReportList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (!isMedicalReportsDataNeedsReloading) {
return;
}
patientMedicalReportList.clear(); patientMedicalReportList.clear();
patientMedicalReportRequestedList.clear(); patientMedicalReportRequestedList.clear();
patientMedicalReportReadyList.clear(); patientMedicalReportReadyList.clear();
@ -385,6 +389,7 @@ class MedicalFileViewModel extends ChangeNotifier {
(failure) async => await errorHandlerService.handleError( (failure) async => await errorHandlerService.handleError(
failure: failure, failure: failure,
onOkPressed: () { onOkPressed: () {
isMedicalReportsDataNeedsReloading = true;
onError!(failure.message); onError!(failure.message);
}, },
), ),
@ -400,6 +405,7 @@ class MedicalFileViewModel extends ChangeNotifier {
} }
onMedicalReportTabChange(0); onMedicalReportTabChange(0);
isPatientMedicalReportsListLoading = false; isPatientMedicalReportsListLoading = false;
isMedicalReportsDataNeedsReloading = false;
notifyListeners(); notifyListeners();
if (onSuccess != null) { if (onSuccess != null) {
onSuccess(apiResponse); onSuccess(apiResponse);

@ -161,6 +161,20 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier {
page: DentalChiefComplaintsPage(), page: DentalChiefComplaintsPage(),
), ),
); );
} else {
if (appState.getAuthenticatedUser()!.age! > 12) {
navigationService.push(
CustomPageRoute(
page: DentalChiefComplaintsPage(),
),
);
} else {
navigationService.push(
CustomPageRoute(
page: SelectDoctorPage(),
),
);
}
} }
} }
if (clinicId == 253) { if (clinicId == 253) {

@ -771,7 +771,11 @@ class MyAppointmentsViewModel extends ChangeNotifier {
} else if (apiResponse.messageStatus == 1) { } else if (apiResponse.messageStatus == 1) {
patientMyDoctorsList = apiResponse.data!; patientMyDoctorsList = apiResponse.data!;
isPatientMyDoctorsLoading = false; isPatientMyDoctorsLoading = false;
isMyDoctorsDataToBeLoaded = false;
if (!isTop8) {
isMyDoctorsDataToBeLoaded = false;
}
notifyListeners(); notifyListeners();
if (onSuccess != null) { if (onSuccess != null) {
onSuccess(apiResponse); onSuccess(apiResponse);

@ -3,6 +3,7 @@ import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/core/api/api_client.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/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/common_models/tamara_request_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart';
@ -34,6 +35,8 @@ abstract class PayfortRepo {
Future<Either<Failure, GenericApiModel<dynamic>>> payfortRequestInsert({required PayfortRequestInsertModel payfortRequestInsertModel}); Future<Either<Failure, GenericApiModel<dynamic>>> payfortRequestInsert({required PayfortRequestInsertModel payfortRequestInsertModel});
Future<Either<Failure, GenericApiModel<dynamic>>> payfortResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel}); Future<Either<Failure, GenericApiModel<dynamic>>> payfortResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel});
Future<Either<Failure, GenericApiModel<dynamic>>> tamaraRequestInsert({required TamaraRequestModel tamaraRequestModel});
} }
class PayfortRepoImp implements PayfortRepo { class PayfortRepoImp implements PayfortRepo {
@ -350,4 +353,31 @@ class PayfortRepoImp implements PayfortRepo {
return Left(UnknownFailure(e.toString())); return Left(UnknownFailure(e.toString()));
} }
} }
@override
Future<Either<Failure, GenericApiModel>> tamaraRequestInsert({required TamaraRequestModel tamaraRequestModel}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(TAMARA_REQUEST_INSERT, body: tamaraRequestModel.toJson(), onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
}, onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
}, isAllowAny: true, isPaymentServices: true);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
} }

@ -9,6 +9,7 @@ import 'package:flutter_amazonpaymentservices/flutter_amazonpaymentservices.dart
import 'package:hmg_patient_app_new/core/api_consts.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/app_state.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/tamara_request_model.dart';
import 'package:hmg_patient_app_new/core/dependencies.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/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart';
@ -116,6 +117,25 @@ class PayfortViewModel extends ChangeNotifier {
); );
} }
Future<void> tamaraRequestInsert({required TamaraRequestModel tamaraRequestModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await payfortRepo.tamaraRequestInsert(tamaraRequestModel: tamaraRequestModel);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
// payfortProjectDetailsRespModel = apiResponse.data!;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
Future<void> payfortResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { Future<void> payfortResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await payfortRepo.payfortResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel); final result = await payfortRepo.payfortResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel);

@ -53,11 +53,15 @@ 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/app_lifecycle_service.dart';
import 'package:hmg_patient_app_new/services/logger_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/navigation_service.dart';
import 'package:hmg_patient_app_new/services/security_service.dart';
import 'package:hmg_patient_app_new/theme/app_theme.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'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart' show DateRangeSelectorRangeViewModel; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart' show DateRangeSelectorRangeViewModel;
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart'; import 'package:provider/single_child_widget.dart';
import 'package:safe_device/safe_device.dart';
import 'package:safe_device/safe_device_config.dart';
import 'core/utils/size_utils.dart'; import 'core/utils/size_utils.dart';
import 'features/monthly_reports/terms_conditions_view_model.dart'; import 'features/monthly_reports/terms_conditions_view_model.dart';
@ -72,12 +76,12 @@ Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// flutter3_32 pub run easy_localization:generate -O ./lib/generated -f keys -o locale_keys.g.dart --source-dir ./assets/langs // flutter3_32 pub run easy_localization:generate -O ./lib/generated -f keys -o locale_keys.g.dart --source-dir ./assets/langs
class MyHttpOverrides extends HttpOverrides { // class MyHttpOverrides extends HttpOverrides {
@override // @override
HttpClient createHttpClient(SecurityContext? context) { // HttpClient createHttpClient(SecurityContext? context) {
return super.createHttpClient(context)..badCertificateCallback = (X509Certificate cert, String host, int port) => true; // return super.createHttpClient(context)..badCertificateCallback = (X509Certificate cert, String host, int port) => true;
} // }
} // }
Future<void> callAppStateInitializations() async { Future<void> callAppStateInitializations() async {
final String deviceTypeId = (Platform.isIOS final String deviceTypeId = (Platform.isIOS
@ -110,6 +114,10 @@ Future<void> callInitializations() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized(); await EasyLocalization.ensureInitialized();
SafeDevice.init(
SafeDeviceConfig(mockLocationCheckEnabled: false), // disables mock location check on Android
);
try { try {
// Attempt to get the default app. If it exists, this avoids the error. // Attempt to get the default app. If it exists, this avoids the error.
await Firebase.app(); await Firebase.app();
@ -122,9 +130,29 @@ Future<void> callInitializations() async {
await AppDependencies.addDependencies(); await AppDependencies.addDependencies();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
HttpOverrides.global = MyHttpOverrides(); // HttpOverrides.global = MyHttpOverrides();
await callAppStateInitializations(); await callAppStateInitializations();
// Initialize Security Service early to catch threats before app logic runs
if (kReleaseMode) {
await getIt.get<SecurityService>().initialize();
}
// Set up critical threat callback to navigate to unsafe device page
getIt.get<SecurityService>().setOnCriticalThreatCallback(() {
final navigationService = getIt.get<NavigationService>();
final context = navigationService.navigatorKey.currentContext;
if (context != null) {
// Clear entire navigation stack and show unsafe device page
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => const UnsafeDevice()),
(route) => false, // Remove all previous routes
);
getIt.get<LoggerService>().logError('🚨 Navigated to UnsafeDevice page - Critical threat detected');
}
});
// Initialize App Lifecycle Service to monitor background/foreground transitions // Initialize App Lifecycle Service to monitor background/foreground transitions
getIt.get<AppLifecycleService>().initialize(); getIt.get<AppLifecycleService>().initialize();

@ -152,16 +152,13 @@ class AppointmentDoctorCard extends StatelessWidget {
), ),
AppCustomChipWidget( AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w),
icon: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! icon: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppAssets.walkin_appointment_icon
? AppAssets.walkin_appointment_icon
: AppAssets.small_livecare_icon, : AppAssets.small_livecare_icon,
iconColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : Colors.white, iconColor: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppColors.textColor : Colors.white,
labelText: patientAppointmentHistoryResponseModel.isLiveCareAppointment! labelText: (patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? LocaleKeys.livecare.tr(context: context)
? LocaleKeys.livecare.tr(context: context)
: LocaleKeys.walkin.tr(context: context), : LocaleKeys.walkin.tr(context: context),
backgroundColor: backgroundColor: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppColors.greyColor : AppColors.successColor,
!patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor, textColor: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppColors.textColor : Colors.white,
textColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : Colors.white,
), ),
], ],
), ),

@ -3,15 +3,19 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart';
import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart'; import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_filter/doctors_filter.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_filter/doctors_filter.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_doctor_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/doctor_card.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/doctor_card.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
@ -214,28 +218,46 @@ class _SearchDoctorByNameState extends State<SearchDoctorByName> {
bookAppointmentsViewModel: bookAppointmentsViewModel, bookAppointmentsViewModel: bookAppointmentsViewModel,
isDoctorNameSearch: true, isDoctorNameSearch: true,
).paddingSymmetrical(16.h, 0.h).onPress(() async { ).paddingSymmetrical(16.h, 0.h).onPress(() async {
bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.filteredDoctorList[index]); if (bookAppointmentsVM.filteredDoctorList[index].clinicID == 17 && getIt.get<AppState>().getAuthenticatedUser()!.age! < 12) {
LoaderBottomSheet.showLoader(); bookAppointmentsViewModel.setProjectID(bookAppointmentsVM.filteredDoctorList[index].projectID.toString());
await bookAppointmentsVM.getDoctorProfile( bookAppointmentsViewModel.setSelectedClinic(GetClinicsListResponseModel(
onSuccess: (dynamic respData) { clinicID: bookAppointmentsVM.filteredDoctorList[index].clinicID,
LoaderBottomSheet.hideLoader(); clinicDescription: bookAppointmentsVM.filteredDoctorList[index].clinicName,
Navigator.of(context).push( isLiveCareClinicAndOnline: false,
CustomPageRoute( liveCareServiceID: 0,
page: DoctorProfilePage(isDoctorAllowedToBook: true), liveCareClinicID: 0));
), bookAppointmentsViewModel.setIsDoctorsListLoading(true);
); Navigator.push(
}, context,
onError: (err) { CustomPageRoute(
LoaderBottomSheet.hideLoader(); page: SelectDoctorPage(),
showCommonBottomSheetWithoutHeight( ),
context, );
child: Utils.getErrorWidget(loadingText: err), } else {
callBackFunc: () {}, bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.filteredDoctorList[index]);
isFullScreen: false, LoaderBottomSheet.showLoader();
isCloseButtonVisible: true, await bookAppointmentsVM.getDoctorProfile(
); onSuccess: (dynamic respData) {
}, LoaderBottomSheet.hideLoader();
); Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(isDoctorAllowedToBook: true),
),
);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
);
}
// Column( // Column(
// children: bookAppointmentsVM.doctorsList[index].map<Widget>((entry) { // children: bookAppointmentsVM.doctorsList[index].map<Widget>((entry) {
// final doctorIndex = entry.key; // final doctorIndex = entry.key;

@ -1045,8 +1045,17 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
//Dental Clinic Flow //Dental Clinic Flow
if (clinic.clinicID == 17) { if (clinic.clinicID == 17) {
if (appState.isAuthenticated) { if (appState.isAuthenticated) {
initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel.currentlySelectedHospitalFromRegionFlow ?? "0")); if (appState.getAuthenticatedUser()!.age! > 12) {
return; initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel.currentlySelectedHospitalFromRegionFlow ?? "0"));
return;
} else {
Navigator.push(
context,
CustomPageRoute(
page: SelectDoctorPage(),
),
);
}
} else { } else {
bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true); bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true);
Navigator.of(context).push( Navigator.of(context).push(
@ -1174,8 +1183,12 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
if (bookAppointmentsViewModel.selectedClinic.clinicID == 17) { if (bookAppointmentsViewModel.selectedClinic.clinicID == 17) {
bookAppointmentsViewModel.setProjectID(id); bookAppointmentsViewModel.setProjectID(id);
if (appState.isAuthenticated) { if (appState.isAuthenticated) {
initDentalAppointment(); if (appState.getAuthenticatedUser()!.age! > 12) {
return SizedBox.shrink(); initDentalAppointment();
return SizedBox.shrink();
} else {
return SizedBox.shrink();
}
} else { } else {
bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true); bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true);
} }

@ -53,7 +53,11 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
bookAppointmentsViewModel.getLiveCareDoctorsList(); bookAppointmentsViewModel.getLiveCareDoctorsList();
} else { } else {
if (bookAppointmentsViewModel.selectedClinic.clinicID == 17) { if (bookAppointmentsViewModel.selectedClinic.clinicID == 17) {
bookAppointmentsViewModel.getDentalChiefComplaintDoctorsList(); if (appState.getAuthenticatedUser()!.age! > 12) {
bookAppointmentsViewModel.getDentalChiefComplaintDoctorsList();
} else {
bookAppointmentsViewModel.getDoctorsList(isNearest: false);
}
} else if (bookAppointmentsViewModel.isGetDocForHealthCal) { } else if (bookAppointmentsViewModel.isGetDocForHealthCal) {
bookAppointmentsViewModel.getDoctorsListByHealthCal(); bookAppointmentsViewModel.getDoctorsListByHealthCal();
} else { } else {

@ -278,11 +278,22 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
bookAppointmentsViewModel.setProjectID(bookAppointmentsViewModel.selectedDoctor.projectID.toString()); bookAppointmentsViewModel.setProjectID(bookAppointmentsViewModel.selectedDoctor.projectID.toString());
bookAppointmentsViewModel.setSelectedClinic(selectedClinic); bookAppointmentsViewModel.setSelectedClinic(selectedClinic);
bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true); bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true);
Navigator.of(context).push( if(appState.getAuthenticatedUser()!.age! > 12) {
CustomPageRoute( Navigator.of(context).push(
page: DentalChiefComplaintsPage(), CustomPageRoute(
), page: DentalChiefComplaintsPage(),
); ),
);
} else {
bookAppointmentsViewModel.getAppointmentNearestGate(projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!);
bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime, selectedDateDisplay);
Navigator.of(context).pop();
Navigator.of(context).push(
CustomPageRoute(
page: ReviewAppointmentPage(),
),
);
}
} else { } else {
bookAppointmentsViewModel.getAppointmentNearestGate(projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!); bookAppointmentsViewModel.getAppointmentNearestGate(projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!);
bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime, selectedDateDisplay); bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime, selectedDateDisplay);

@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/resp_model
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/dental_chief_complaints_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/dental_chief_complaints_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/laser/laser_appointment.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/laser/laser_appointment.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_doctor_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -185,14 +186,26 @@ class DoctorCard extends StatelessWidget {
if (isDoctorNameSearch && doctorsListResponseModel.clinicID == 17) { if (isDoctorNameSearch && doctorsListResponseModel.clinicID == 17) {
GetClinicsListResponseModel selectedClinic = GetClinicsListResponseModel( GetClinicsListResponseModel selectedClinic = GetClinicsListResponseModel(
clinicID: doctorsListResponseModel.clinicID, clinicDescription: doctorsListResponseModel.clinicName, isLiveCareClinicAndOnline: false, liveCareServiceID: 0, liveCareClinicID: 0); clinicID: doctorsListResponseModel.clinicID, clinicDescription: doctorsListResponseModel.clinicName, isLiveCareClinicAndOnline: false, liveCareServiceID: 0, liveCareClinicID: 0);
bookAppointmentsViewModel.setProjectID(doctorsListResponseModel.projectID.toString()); if (getIt.get<AppState>().getAuthenticatedUser()!.age! > 12) {
bookAppointmentsViewModel.setSelectedClinic(selectedClinic); bookAppointmentsViewModel.setProjectID(doctorsListResponseModel.projectID.toString());
bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true); bookAppointmentsViewModel.setSelectedClinic(selectedClinic);
Navigator.of(context).push( bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true);
CustomPageRoute( Navigator.of(context).push(
page: DentalChiefComplaintsPage(), CustomPageRoute(
), page: DentalChiefComplaintsPage(),
); ),
);
} else {
bookAppointmentsViewModel.setProjectID(doctorsListResponseModel.projectID.toString());
bookAppointmentsViewModel.setSelectedClinic(selectedClinic);
bookAppointmentsViewModel.setIsDoctorsListLoading(true);
Navigator.push(
context,
CustomPageRoute(
page: SelectDoctorPage(),
),
);
}
} else if (isDoctorNameSearch && doctorsListResponseModel.clinicID == 253) { } else if (isDoctorNameSearch && doctorsListResponseModel.clinicID == 253) {
GetClinicsListResponseModel selectedClinic = GetClinicsListResponseModel( GetClinicsListResponseModel selectedClinic = GetClinicsListResponseModel(
clinicID: doctorsListResponseModel.clinicID, clinicDescription: doctorsListResponseModel.clinicName, isLiveCareClinicAndOnline: false, liveCareServiceID: 0, liveCareClinicID: 0); clinicID: doctorsListResponseModel.clinicID, clinicDescription: doctorsListResponseModel.clinicName, isLiveCareClinicAndOnline: false, liveCareServiceID: 0, liveCareClinicID: 0);

@ -58,6 +58,7 @@ import 'package:hmg_patient_app_new/presentation/notifications/notifications_lis
import 'package:hmg_patient_app_new/presentation/offers_and_discounts/offers_and_discounts_page.dart'; import 'package:hmg_patient_app_new/presentation/offers_and_discounts/offers_and_discounts_page.dart';
import 'package:hmg_patient_app_new/presentation/offers_and_discounts/widgets/offers_and_discounts.dart'; import 'package:hmg_patient_app_new/presentation/offers_and_discounts/widgets/offers_and_discounts.dart';
import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointment_doctor.dart'; import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointment_doctor.dart';
import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_procedures_details_page.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_procedures_details_page.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart'; import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart';
@ -291,6 +292,15 @@ class _LandingPageState extends State<LandingPage> {
page: FamilyMedicalScreen(), page: FamilyMedicalScreen(),
), ),
); );
// Navigator.pushReplacementNamed(
// // context,
// GetIt.instance<NavigationService>().navigatorKey.currentContext!,
// AppRoutes.zoomCallPage,
// // arguments: CallArguments(appointmentID, "111", "Patient", "40", "1", true, 1),
// arguments: CallArguments("yosemite-338", "123", "Patient", "40", "0", true, 1),
// // arguments: CallArguments("SmallDailyStandup9875", "123", "Patient", "40", "0", false, int.parse(widget.incomingCallData!.appointmentNo!)),
// );
}, },
name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'),
imageWidget: Selector<ProfileSettingsViewModel, String?>( imageWidget: Selector<ProfileSettingsViewModel, String?>(
@ -314,6 +324,13 @@ class _LandingPageState extends State<LandingPage> {
onPressed: () async { onPressed: () async {
await authVM.onLoginPressed(); await authVM.onLoginPressed();
// Navigator.pushReplacementNamed(
// // context,
// GetIt.instance<NavigationService>().navigatorKey.currentContext!,
// AppRoutes.zoomCallPage,
// arguments: CallArguments("lake-tahoe-289", "123", "Patient", "40", "0", true, 1),
// );
// Navigator.of(context).push( // Navigator.of(context).push(
// CustomPageRoute( // CustomPageRoute(
// // page: NotificationsListPage(), // // page: NotificationsListPage(),

@ -1063,7 +1063,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
], ],
).onPress(() { ).onPress(() {
// myAppointmentsViewModel.getPatientMyDoctors(); // myAppointmentsViewModel.getPatientMyDoctors();
myAppointmentsViewModel.setIsMyDoctorsDataToBeLoaded(true); // myAppointmentsViewModel.setIsMyDoctorsDataToBeLoaded(true);
Navigator.of(context).push( Navigator.of(context).push(
CustomPageRoute( CustomPageRoute(
page: MyDoctorsPage(), page: MyDoctorsPage(),

@ -84,7 +84,7 @@ class _CallScreenState extends State<CallScreen> {
//hide status bar //hide status bar
SystemChrome.setEnabledSystemUIMode(SystemUiMode.leanBack); SystemChrome.setEnabledSystemUIMode(SystemUiMode.leanBack);
var circleButtonSize = 65.0; var circleButtonSize = 65.h;
Color backgroundColor = const Color(0xFF232323); Color backgroundColor = const Color(0xFF232323);
Color buttonBackgroundColor = const Color.fromRGBO(0, 0, 0, 0.6); Color buttonBackgroundColor = const Color.fromRGBO(0, 0, 0, 0.6);
Color chatTextColor = const Color(0xFFAAAAAA); Color chatTextColor = const Color(0xFFAAAAAA);
@ -178,12 +178,12 @@ class _CallScreenState extends State<CallScreen> {
// "Join", // "Join",
// arguments: JoinArguments(args.isJoin, sessionName.value, sessionPassword.value, args.displayName, args.sessionIdleTimeoutMins, args.role), // arguments: JoinArguments(args.isJoin, sessionName.value, sessionPassword.value, args.displayName, args.sessionIdleTimeoutMins, args.role),
// ); // );
Navigator.pushAndRemoveUntil( // Navigator.pushAndRemoveUntil(
context, // context,
CustomPageRoute( // CustomPageRoute(
page: LandingNavigation(), // page: LandingNavigation(),
), // ),
(r) => false); // (r) => false);
}); });
final sessionNeedPasswordListener = eventListener.addListener(EventType.onSessionNeedPassword, (data) async { final sessionNeedPasswordListener = eventListener.addListener(EventType.onSessionNeedPassword, (data) async {
@ -1773,16 +1773,17 @@ class _CallScreenState extends State<CallScreen> {
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: FractionallySizedBox( child: FractionallySizedBox(
widthFactor: 0.2, widthFactor: 0.2,
heightFactor: 0.6, heightFactor: 0.8,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
IconButton( IconButton(
onPressed: onPressAudio, onPressed: onPressAudio,
icon: isMuted.value ? Utils.buildImgWithAssets(icon: "assets/images/png/zoom/unmute@2x.png") : Utils.buildImgWithAssets(icon: "assets/images/png/zoom/mute@2x.png"), icon: isMuted.value
iconSize: circleButtonSize, ? Utils.buildImgWithAssets(icon: "assets/images/png/zoom/unmute@2x.png", width: circleButtonSize.h, height: circleButtonSize.h)
tooltip: isMuted.value == true ? "Unmute" : "Mute", : Utils.buildImgWithAssets(icon: "assets/images/png/zoom/mute@2x.png", width: circleButtonSize.h, height: circleButtonSize.h),
), iconSize: circleButtonSize,
),
// IconButton( // IconButton(
// onPressed: onPressShare, // onPressed: onPressShare,
// icon: isSharing.value ? Image.asset("assets/images/png/zoom/share-off@2x.png") : Image.asset("assets/images/png/zoom/share-on@2x.png"), // icon: isSharing.value ? Image.asset("assets/images/png/zoom/share-off@2x.png") : Image.asset("assets/images/png/zoom/share-on@2x.png"),
@ -1791,9 +1792,11 @@ class _CallScreenState extends State<CallScreen> {
IconButton( IconButton(
onPressed: onPressVideo, onPressed: onPressVideo,
iconSize: circleButtonSize, iconSize: circleButtonSize,
icon: isVideoOn.value ? Utils.buildImgWithAssets(icon: "assets/images/png/zoom/video-off@2x.png") : Utils.buildImgWithAssets(icon: "assets/images/png/zoom/video-on@2x.png"), icon: isVideoOn.value
), ? Utils.buildImgWithAssets(icon: "assets/images/png/zoom/video-off@2x.png", width: circleButtonSize.h, height: circleButtonSize.h)
Column( : Utils.buildImgWithAssets(icon: "assets/images/png/zoom/video-on@2x.png", width: circleButtonSize.h, height: circleButtonSize.h),
),
Column(
children: [ children: [
IconButton( IconButton(
onPressed: () async { onPressed: () async {
@ -1808,7 +1811,8 @@ class _CallScreenState extends State<CallScreen> {
), ),
], ],
), ),
)), ),
),
// Container( // Container(
// margin: const EdgeInsets.only(left: 16, right: 16, bottom: 40, top: 10), // margin: const EdgeInsets.only(left: 16, right: 16, bottom: 40, top: 10),
// alignment: Alignment.bottomCenter, // alignment: Alignment.bottomCenter,

@ -31,14 +31,14 @@ class LiveCarePermissionService {
Permission.camera, Permission.camera,
Permission.microphone, Permission.microphone,
Permission.notification, Permission.notification,
if (Platform.isAndroid) Permission.systemAlertWindow, // if (Platform.isAndroid) Permission.systemAlertWindow,
] ]
: <Permission>[ : <Permission>[
// Permission.camera, // Permission.camera,
// Permission.microphone, // Permission.microphone,
Permission.notification, Permission.notification,
if (Platform.isAndroid) Permission.systemAlertWindow, // if (Platform.isAndroid) Permission.systemAlertWindow,
]; ];
try { try {
final statuses = await permissions.request(); final statuses = await permissions.request();

@ -0,0 +1,157 @@
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/foundation.dart';
import 'package:freerasp/freerasp.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/talsec_config.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
/// Enum to categorize threat severity levels
enum ThreatSeverity {
critical, // Block app usage
warning, // Log only, don't block
}
/// Model to track threat details
class ThreatEvent {
final String threatType;
final ThreatSeverity severity;
final DateTime timestamp;
final String? additionalInfo;
ThreatEvent({
required this.threatType,
required this.severity,
this.additionalInfo,
}) : timestamp = DateTime.now();
}
/// Callback type for critical threat detection
typedef OnCriticalThreatDetected = void Function();
/// Abstract class defining the security service interface
abstract class SecurityService {
/// Initialize and start the security monitoring
Future<void> initialize();
/// Check if device is currently safe
bool get isSafeDevice;
/// Get list of detected threats
List<ThreatEvent> get detectedThreats;
/// Set callback for when critical threat is detected
void setOnCriticalThreatCallback(OnCriticalThreatDetected callback);
}
/// Implementation of SecurityService using Talsec (freeRASP)
class SecurityServiceImpl implements SecurityService {
final AppState appState;
final LoggerService loggerService;
final List<ThreatEvent> _detectedThreats = [];
bool _isInitialized = false;
OnCriticalThreatDetected? _onCriticalThreatCallback;
SecurityServiceImpl({
required this.appState,
required this.loggerService,
});
@override
bool get isSafeDevice => appState.isSafeDevice;
@override
List<ThreatEvent> get detectedThreats => List.unmodifiable(_detectedThreats);
@override
Future<void> initialize() async {
if (_isInitialized) {
loggerService.logInfo('SecurityService already initialized');
return;
}
try {
loggerService.logInfo('Initializing SecurityService with Talsec');
// Start the RASP engine
await Talsec.instance.start(talsecConfig);
// Setup threat callbacks
final callback = ThreatCallback(
onAppIntegrity: () => _handleThreat('App Integrity', ThreatSeverity.critical),
onObfuscationIssues: () => _handleThreat('Obfuscation Issues', ThreatSeverity.warning),
onDebug: () => _handleThreat('Debug Mode', ThreatSeverity.critical),
onDeviceBinding: () => _handleThreat('Device Binding', ThreatSeverity.critical),
onDeviceID: () => _handleThreat('Device ID Mismatch', ThreatSeverity.critical),
onHooks: () => _handleThreat('Hooks Detected', ThreatSeverity.critical),
onPasscode: () => _handleThreat('Passcode Not Set', ThreatSeverity.warning),
onPrivilegedAccess: () => _handleThreat('Privileged Access (Root/Jailbreak)', ThreatSeverity.critical),
onSecureHardwareNotAvailable: () => _handleThreat('Secure Hardware Not Available', ThreatSeverity.warning),
onSimulator: () => _handleThreat('Simulator/Emulator Detected', ThreatSeverity.critical),
onSystemVPN: () => _handleThreat('System VPN Active', ThreatSeverity.warning),
onDevMode: () => _handleThreat('Developer Mode', ThreatSeverity.warning),
onADBEnabled: () => _handleThreat('USB Debugging Enabled', ThreatSeverity.warning),
onUnofficialStore: () => _handleThreat('Unofficial Store Installation', ThreatSeverity.critical),
onScreenshot: () => _handleThreat('Screenshot Detected', ThreatSeverity.warning),
onScreenRecording: () => _handleThreat('Screen Recording Active', ThreatSeverity.warning),
onMultiInstance: () => _handleThreat('Multiple Instances', ThreatSeverity.warning),
onLocationSpoofing: () => _handleThreat('Location Spoofing', ThreatSeverity.warning),
onTimeSpoofing: () => _handleThreat('Time Spoofing', ThreatSeverity.warning),
onAutomation: () => _handleThreat('Automation Detected', ThreatSeverity.warning),
onBootloader: () => _handleThreat('Unlocked Bootloader', ThreatSeverity.critical),
onMalware: (suspiciousApps) => _handleThreat('Malware/Suspicious Apps', ThreatSeverity.critical, additionalInfo: suspiciousApps.toString()),
);
Talsec.instance.attachListener(callback);
_isInitialized = true;
loggerService.logInfo('SecurityService initialized successfully');
} catch (e) {
loggerService.logError('Failed to initialize SecurityService: $e');
if (!kDebugMode) {
FirebaseCrashlytics.instance.recordError(
e,
StackTrace.current,
reason: 'SecurityService initialization failed',
fatal: false,
);
}
rethrow;
}
}
@override
void setOnCriticalThreatCallback(OnCriticalThreatDetected callback) {
_onCriticalThreatCallback = callback;
loggerService.logInfo('Critical threat callback registered');
}
/// Handle detected threats with appropriate severity
void _handleThreat(String threatType, ThreatSeverity severity, {String? additionalInfo}) {
final threat = ThreatEvent(
threatType: threatType,
severity: severity,
additionalInfo: additionalInfo,
);
_detectedThreats.add(threat);
// Log to console
if (severity == ThreatSeverity.critical) {
loggerService.logError('🔴 CRITICAL THREAT: $threatType ${additionalInfo != null ? "- $additionalInfo" : ""}');
} else {
loggerService.logInfo('⚠️ WARNING: $threatType ${additionalInfo != null ? "- $additionalInfo" : ""}');
}
// Block app if critical threat
if (severity == ThreatSeverity.critical) {
appState.setIsSafeDevice = false;
loggerService.logError('Device marked as UNSAFE due to: $threatType');
// Trigger callback to navigate to unsafe device page
if (_onCriticalThreatCallback != null) {
_onCriticalThreatCallback!();
}
}
}
}

@ -16,6 +16,7 @@ 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/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/presentation/home/app_update_page.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/onboarding/onboarding_screen.dart'; import 'package:hmg_patient_app_new/presentation/onboarding/onboarding_screen.dart';
import 'package:hmg_patient_app_new/presentation/onboarding/splash_animation_screen.dart'; import 'package:hmg_patient_app_new/presentation/onboarding/splash_animation_screen.dart';
@ -25,8 +26,10 @@ import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/services/notification_service.dart'; import 'package:hmg_patient_app_new/services/notification_service.dart';
import 'package:hmg_patient_app_new/services/zoom_service.dart'; import 'package:hmg_patient_app_new/services/zoom_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/unsafe_device.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:lottie/lottie.dart'; import 'package:lottie/lottie.dart';
import 'package:safe_device/safe_device.dart';
import 'core/cache_consts.dart'; import 'core/cache_consts.dart';
import 'core/utils/push_notification_handler.dart'; import 'core/utils/push_notification_handler.dart';
@ -41,8 +44,15 @@ class SplashPage extends StatefulWidget {
class _SplashScreenState extends State<SplashPage> { class _SplashScreenState extends State<SplashPage> {
late AuthenticationViewModel authVm; late AuthenticationViewModel authVm;
bool isJailBroken = false;
bool isRealDevice = true;
bool isDevelopmentModeEnable = false;
Future<void> initializeStuff() async { Future<void> initializeStuff() async {
listenerEvent(); listenerEvent();
if (kReleaseMode) {
checkDeviceSafety();
}
Timer( Timer(
Duration(milliseconds: 500), Duration(milliseconds: 500),
() async { () async {
@ -50,34 +60,43 @@ class _SplashScreenState extends State<SplashPage> {
PushNotificationHandler().init(context); // Asyncronously PushNotificationHandler().init(context); // Asyncronously
}, },
); );
await authVm.getServicePrivilege(); await authVm.getServicePrivilege();
Timer(Duration(seconds: 2, milliseconds: 500), () async { Timer(Duration(seconds: 2, milliseconds: 500), () async {
bool isAppOpenedFromCall = getIt.get<CacheService>().getBool(key: CacheConst.isAppOpenedFromCall) ?? false; if (isJailBroken || !isRealDevice || !getIt.get<AppState>().isSafeDevice) {
// Critical threat detected - navigate to unsafe device page
// Initialize NotificationService using dependency injection Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pushAndRemoveUntil(
final notificationService = getIt.get<NotificationService>(); MaterialPageRoute(builder: (_) => const UnsafeDevice()),
await notificationService.initialize(onNotificationClick: (payload) { (route) => false, // Remove all previous routes
// Handle notification click here );
}); } else {
bool isAppOpenedFromCall = getIt.get<CacheService>().getBool(key: CacheConst.isAppOpenedFromCall) ?? false;
// Initialize NotificationService using dependency injection
final notificationService = getIt.get<NotificationService>();
await notificationService.initialize(onNotificationClick: (payload) {
// Handle notification click here
});
ZoomService().initializeZoomSDK(); ZoomService().initializeZoomSDK();
if (!kDebugMode) { if (!kDebugMode) {
_initializeClarity(); _initializeClarity();
} }
if (isAppOpenedFromCall) { if (isAppOpenedFromCall) {
navigateToTeleConsult(); navigateToTeleConsult();
} else {
if (await Utils.getBoolFromPrefs(CacheConst.firstLaunch)) {
// Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: OnboardingScreen())));
Navigator.of(context).pushReplacement(FadePage(page: OnboardingScreen()));
} else { } else {
// Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LandingNavigation()))); if (await Utils.getBoolFromPrefs(CacheConst.firstLaunch)) {
Navigator.of(context).pushReplacement(FadePage(page: LandingNavigation())); // Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: OnboardingScreen())));
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pushReplacement(FadePage(page: OnboardingScreen()));
} else {
// Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LandingNavigation())));
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pushReplacement(FadePage(page: LandingNavigation()));
}
} }
} }
}); });
// var zoom = ZoomVideoSdk(); // var zoom = ZoomVideoSdk();
// InitConfig initConfig = InitConfig( // InitConfig initConfig = InitConfig(
// domain: "zoom.us", // domain: "zoom.us",
@ -153,6 +172,29 @@ class _SplashScreenState extends State<SplashPage> {
); );
} }
void checkDeviceSafety() {
try {
SafeDevice.isJailBroken.then((bool value) {
isJailBroken = value;
});
SafeDevice.isJailBrokenCustom.then((bool value) {
isJailBroken = value;
});
SafeDevice.isRealDevice.then((value) {
isRealDevice = value;
});
if (Platform.isAndroid) {
// isOnExternalStorage = await SafeDevice.isOnExternalStorage;
// SafeDevice.isDevelopmentModeEnable.then((value) {
// isDevelopmentModeEnable = value;
// });
}
} catch (error) {
print(error);
}
}
Future<void> listenerEvent() async { Future<void> listenerEvent() async {
print('Call Canceled : ------->'); print('Call Canceled : ------->');

@ -0,0 +1,117 @@
import 'package:flutter/material.dart';
import 'package:freerasp/freerasp.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/services/security_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class UnsafeDevice extends StatefulWidget {
const UnsafeDevice({super.key});
@override
State<UnsafeDevice> createState() => _UnsafeDeviceState();
}
class _UnsafeDeviceState extends State<UnsafeDevice> {
@override
void initState() {
Talsec.instance.detachListener();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.whiteColor,
body: SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo
Utils.buildImgWithAssets(icon: AppAssets.hmgLogo, width: MediaQuery.of(context).size.width * 0.7, height: 90.h, fit: BoxFit.contain),
SizedBox(height: 32.h),
// Warning Icon
Icon(
Icons.security,
size: 80.h,
color: AppColors.primaryRedColor,
),
SizedBox(height: 24.h),
// Title
Text(
'Unsafe Device Detected',
style: TextStyle(
fontSize: 24.f,
fontWeight: FontWeight.bold,
color: AppColors.primaryRedColor,
),
textAlign: TextAlign.center,
),
SizedBox(height: 16.h),
// Description
Text(
'For your security, this app cannot run on devices with security vulnerabilities.',
style: TextStyle(
fontSize: 16.f,
color: Colors.black87,
),
textAlign: TextAlign.center,
),
SizedBox(height: 24.h),
// if (getIt.get<SecurityService>().detectedThreats.isNotEmpty) ...[
// Text(
// 'Detected Issues:',
// style: TextStyle(
// fontSize: 14.f,
// fontWeight: FontWeight.bold,
// color: Colors.black87,
// ),
// ),
// SizedBox(height: 8.h),
// Container(
// padding: EdgeInsets.all(12.w),
// decoration: BoxDecoration(
// color: Colors.red.withOpacity(0.1),
// borderRadius: BorderRadius.circular(8.r),
// border: Border.all(color: Colors.red.withOpacity(0.3)),
// ),
// child: Column(
// children: getIt
// .get<SecurityService>()
// .detectedThreats
// .where((t) => t.severity == ThreatSeverity.critical)
// .take(5) // Show max 5 threats
// .map((threat) => Padding(
// padding: EdgeInsets.symmetric(vertical: 4.h),
// child: Row(
// children: [
// Icon(Icons.error_outline, size: 16.f, color: Colors.red),
// SizedBox(width: 8.w),
// Expanded(
// child: Text(
// threat.threatType,
// style: TextStyle(fontSize: 12.f),
// ),
// ),
// ],
// ),
// ))
// .toList(),
// ),
// ),
// SizedBox(height: 24.h),
// ],
],
),
),
),
);
}
}

@ -56,8 +56,9 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
late DateRangeSelectorRangeViewModel model; late DateRangeSelectorRangeViewModel model;
PickerViewMode _viewMode = PickerViewMode.date; PickerViewMode _viewMode = PickerViewMode.date;
// Cache for Hijri conversions to avoid repeated calculations // Prevent Syncfusion's initial onViewChanged callback from scheduling an
final Map<String, HijriGregDate> _hijriCache = {}; // unnecessary second build of the same month.
late int _displayedMonthKey;
// Track the current Hijri month/year being displayed (for header display accuracy) // Track the current Hijri month/year being displayed (for header display accuracy)
HijriGregDate? _currentHijriDisplay; HijriGregDate? _currentHijriDisplay;
@ -65,10 +66,14 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
@override @override
void initState() { void initState() {
_calendarController = DateRangePickerController(); _calendarController = DateRangePickerController();
final today = DateTime.now();
_displayedMonthKey = _monthKey(today);
if (widget.designType == CalendarDesignType.designV2) {
_calendarController.displayDate = today;
}
scheduleMicrotask(() { scheduleMicrotask(() {
if (widget.designType == CalendarDesignType.designV2) { if (widget.designType == CalendarDesignType.designV2) {
// For V2, select today's date by default // For V2, select today's date by default
final today = DateTime.now();
_calendarController.selectedDate = today; _calendarController.selectedDate = today;
model.updateSelectedDate(today); model.updateSelectedDate(today);
} else { } else {
@ -83,7 +88,9 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
model = Provider.of<DateRangeSelectorRangeViewModel>(context); model = Provider.of<DateRangeSelectorRangeViewModel>(context);
_calendarController.selectedRange = PickerDateRange(model.fromDate, model.toDate); if (widget.designType != CalendarDesignType.designV2) {
_calendarController.selectedRange = PickerDateRange(model.fromDate, model.toDate);
}
return widget.designType == CalendarDesignType.designV2 ? _buildDesignV2(widget.btnTitle) : _buildDefaultUI(); return widget.designType == CalendarDesignType.designV2 ? _buildDesignV2(widget.btnTitle) : _buildDefaultUI();
} }
@ -273,18 +280,17 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
tabs: [CustomTabBarModel(null, LocaleKeys.gregorianDate.tr()), CustomTabBarModel(null, LocaleKeys.hijriDate.tr())], tabs: [CustomTabBarModel(null, LocaleKeys.gregorianDate.tr()), CustomTabBarModel(null, LocaleKeys.hijriDate.tr())],
onTabChange: (index) { onTabChange: (index) {
final calendarModel = Provider.of<DateRangCalenderModel>(context, listen: false); final calendarModel = Provider.of<DateRangCalenderModel>(context, listen: false);
if (calendarModel.getSelectedTabIndex == index) return;
// Update local fields before notifying the provider so
// the calendar changes with a single rebuild.
_viewMode = PickerViewMode.date;
_currentHijriDisplay = null;
calendarModel.setTabIndex(index); calendarModel.setTabIndex(index);
// Notify parent widget about calendar type change (e.g., AuthenticationViewModel) // Notify parent widget about calendar type change (e.g., AuthenticationViewModel)
final isGregorian = index == 0; final isGregorian = index == 0;
widget.onCalendarTypeChanged?.call(isGregorian); widget.onCalendarTypeChanged?.call(isGregorian);
print('📅 Calendar type changed: ${isGregorian ? "Gregorian" : "Hijri"}');
// Reset view mode and Hijri display when switching calendar types
setState(() {
_viewMode = PickerViewMode.date;
_currentHijriDisplay = null;
});
}, },
), ),
), ),
@ -306,12 +312,17 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
child: Material( child: Material(
color: AppColors.whiteColor, color: AppColors.whiteColor,
// Rebuild when view mode or calendar type changes // Rebuild when view mode or calendar type changes
// Syncfusion caches its rendered date cells. Include
// the calendar type in the key so Gregorian/Hijri day
// labels refresh immediately when the tab changes.
// Hijri conversion is now constant-time, so this
// targeted recreation remains responsive.
key: ValueKey('${calendarModel.getSelectedTabIndex}-$_viewMode'), key: ValueKey('${calendarModel.getSelectedTabIndex}-$_viewMode'),
child: _viewMode == PickerViewMode.month child: _viewMode == PickerViewMode.month
? _buildMonthPicker(isArabic, calendarModel) ? _buildMonthPicker(isArabic, calendarModel)
: _viewMode == PickerViewMode.year : _viewMode == PickerViewMode.year
? _buildYearPicker(isArabic, calendarModel) ? _buildYearPicker(isArabic, calendarModel)
: (calendarModel.isHijri ? _buildHijriCalendar(isArabic) : _buildGregorianCalendar(isArabic)), : (calendarModel.isHijri ? _buildHijriCalendar(isArabic) : _buildGregorianCalendar(isArabic)),
), ),
), ),
], ],
@ -343,6 +354,8 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
// Build Gregorian calendar (Design V2 - Single Date Selection) // Build Gregorian calendar (Design V2 - Single Date Selection)
Widget _buildGregorianCalendar(bool isArabic) { Widget _buildGregorianCalendar(bool isArabic) {
final today = DateTime.now();
return SfDateRangePicker( return SfDateRangePicker(
controller: _calendarController, controller: _calendarController,
selectionMode: DateRangePickerSelectionMode.single, selectionMode: DateRangePickerSelectionMode.single,
@ -366,7 +379,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
// Custom cell builder for square border selection // Custom cell builder for square border selection
cellBuilder: (BuildContext context, DateRangePickerCellDetails cellDetails) { cellBuilder: (BuildContext context, DateRangePickerCellDetails cellDetails) {
if (cellDetails.date != DateTime(0)) { if (cellDetails.date != DateTime(0)) {
final isToday = cellDetails.date.day == DateTime.now().day && cellDetails.date.month == DateTime.now().month && cellDetails.date.year == DateTime.now().year; final isToday = cellDetails.date.day == today.day && cellDetails.date.month == today.month && cellDetails.date.year == today.year;
final isSelected = _calendarController.selectedDate != null && final isSelected = _calendarController.selectedDate != null &&
cellDetails.date.day == _calendarController.selectedDate!.day && cellDetails.date.day == _calendarController.selectedDate!.day &&
cellDetails.date.month == _calendarController.selectedDate!.month && cellDetails.date.month == _calendarController.selectedDate!.month &&
@ -414,12 +427,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
), ),
), ),
onViewChanged: (DateRangePickerViewChangedArgs args) { onViewChanged: (DateRangePickerViewChangedArgs args) {
// Trigger rebuild when month changes _handleCalendarViewChanged(args);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {});
}
});
}, },
onSelectionChanged: (DateRangePickerSelectionChangedArgs args) { onSelectionChanged: (DateRangePickerSelectionChangedArgs args) {
if (args.value is DateTime) { if (args.value is DateTime) {
@ -433,105 +441,155 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
); );
} }
// Build Hijri calendar (Design V2 - Single Date Selection) // Build one complete Hijri month instead of relabelling the dates in a
// Note: SfDateRangePicker doesn't have built-in Hijri support // Gregorian month. A Gregorian month overlaps two Hijri months, which would
// Using Gregorian calendar with Hijri date conversion in the model // otherwise produce sequences such as 16...30, 1...17 in the same grid.
Widget _buildHijriCalendar(bool isArabic) { Widget _buildHijriCalendar(bool isArabic) {
final calendarModel = Provider.of<DateRangCalenderModel>(context, listen: false); final calendarModel = Provider.of<DateRangCalenderModel>(context, listen: false);
final today = DateTime.now();
final displayedGregorian = _calendarController.displayDate ?? today;
final displayedHijri = _currentHijriDisplay ?? calendarModel.gregorianToHijri(displayedGregorian);
final firstHijriDay = HijriGregDate(day: 1, month: displayedHijri.month, year: displayedHijri.year);
final firstGregorianDay = calendarModel.hijriToGregorian(firstHijriDay);
final leadingEmptyCells = firstGregorianDay.weekday % DateTime.daysPerWeek;
final daysInMonth = calendarModel.getDaysInMonth(displayedHijri.year, displayedHijri.month);
final selectedDate = _calendarController.selectedDate;
const weekdays = [
DateTime.sunday,
DateTime.monday,
DateTime.tuesday,
DateTime.wednesday,
DateTime.thursday,
DateTime.friday,
DateTime.saturday,
];
return SfDateRangePicker( return Column(
controller: _calendarController, children: [
selectionMode: DateRangePickerSelectionMode.single, SizedBox(
showNavigationArrow: false, height: 32.h,
headerHeight: 0, child: Row(
backgroundColor: AppColors.whiteColor, children: weekdays
monthViewSettings: DateRangePickerMonthViewSettings( .map(
viewHeaderStyle: DateRangePickerViewHeaderStyle( (weekday) => Expanded(
backgroundColor: AppColors.whiteColor, child: Center(
textStyle: TextStyle( child: Text(
fontSize: 12.f, calendarModel.getWeekdayNameLocalized(weekday, isArabic),
fontWeight: FontWeight.w600, style: TextStyle(
letterSpacing: -0.46, fontSize: 12.f,
color: AppColors.textColor, fontWeight: FontWeight.w600,
letterSpacing: -0.46,
color: AppColors.textColor,
),
),
),
),
)
.toList(),
), ),
), ),
showTrailingAndLeadingDates: false, Expanded(
dayFormat: "EEE", child: GridView.builder(
), padding: EdgeInsets.zero,
cellBuilder: (BuildContext context, DateRangePickerCellDetails cellDetails) { physics: const NeverScrollableScrollPhysics(),
if (cellDetails.date != DateTime(0)) { gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
// Use cached Hijri conversion crossAxisCount: DateTime.daysPerWeek,
final dateKey = '${cellDetails.date.year}-${cellDetails.date.month}-${cellDetails.date.day}'; mainAxisExtent: 36.h,
final hijriDate = _hijriCache.putIfAbsent(dateKey, () => calendarModel.gregorianToHijri(cellDetails.date)); mainAxisSpacing: 4.h,
),
itemCount: 42,
itemBuilder: (context, index) {
final hijriDay = index - leadingEmptyCells + 1;
if (hijriDay < 1 || hijriDay > daysInMonth) {
return const SizedBox.shrink();
}
final isToday = cellDetails.date.day == DateTime.now().day && cellDetails.date.month == DateTime.now().month && cellDetails.date.year == DateTime.now().year; final hijriDate = HijriGregDate(day: hijriDay, month: displayedHijri.month, year: displayedHijri.year);
final isSelected = _calendarController.selectedDate != null && final gregorianDate = calendarModel.hijriToGregorian(hijriDate);
cellDetails.date.day == _calendarController.selectedDate!.day && final isToday = _isSameDate(gregorianDate, today);
cellDetails.date.month == _calendarController.selectedDate!.month && final isSelected = selectedDate != null && _isSameDate(gregorianDate, selectedDate);
cellDetails.date.year == _calendarController.selectedDate!.year;
return Container( return GestureDetector(
alignment: Alignment.center, behavior: HitTestBehavior.opaque,
decoration: BoxDecoration( onTap: () {
borderRadius: BorderRadius.circular(8.h), _calendarController.selectedDate = gregorianDate;
border: isSelected ? Border.all(color: AppColors.primaryRedColor, width: 2) : null, setState(() {
color: Colors.transparent, start = gregorianDate;
), end = gregorianDate;
child: Text( });
hijriDate.day.toString(), model.updateSelectedDate(gregorianDate);
style: TextStyle( },
fontFamily: "Poppins", child: Container(
fontSize: 12.f, alignment: Alignment.center,
color: AppColors.textColor, decoration: BoxDecoration(
fontWeight: isToday ? FontWeight.bold : (isSelected ? FontWeight.w600 : FontWeight.normal), borderRadius: BorderRadius.circular(8.h),
), border: isSelected ? Border.all(color: AppColors.primaryRedColor, width: 2) : null,
), color: Colors.transparent,
); ),
} child: Text(
return Container(); hijriDay.toString(),
}, style: TextStyle(
selectionShape: DateRangePickerSelectionShape.rectangle, fontFamily: "Poppins",
selectionRadius: 8.h, fontSize: 12.f,
selectionColor: Colors.transparent, color: AppColors.textColor,
selectionTextStyle: TextStyle( fontWeight: isToday ? FontWeight.bold : (isSelected ? FontWeight.w600 : FontWeight.normal),
fontFamily: "Poppins", ),
color: AppColors.textColor, ),
fontWeight: FontWeight.w600, ),
), );
todayHighlightColor: Colors.transparent, },
monthCellStyle: DateRangePickerMonthCellStyle( ),
textStyle: TextStyle(
fontFamily: "Poppins",
fontSize: 12.f,
color: AppColors.textColor,
),
todayTextStyle: TextStyle(
fontFamily: "Poppins",
color: AppColors.textColor,
fontWeight: FontWeight.bold,
), ),
), ],
onViewChanged: (DateRangePickerViewChangedArgs args) {
// Clear cache when month changes to avoid stale data
_hijriCache.clear();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {});
}
});
},
onSelectionChanged: (DateRangePickerSelectionChangedArgs args) {
if (args.value is DateTime) {
setState(() {
start = args.value;
end = args.value;
});
model.updateSelectedDate(args.value);
}
},
); );
} }
bool _isSameDate(DateTime first, DateTime second) {
return first.year == second.year && first.month == second.month && first.day == second.day;
}
int _monthKey(DateTime date) => (date.year * 100) + date.month;
void _changeHijriMonth(DateRangCalenderModel calendarModel, int monthDelta) {
final displayedDate = _calendarController.displayDate ?? DateTime.now();
final currentHijri = _currentHijriDisplay ?? calendarModel.gregorianToHijri(displayedDate);
final zeroBasedMonth = (currentHijri.year * 12) + currentHijri.month - 1 + monthDelta;
final nextHijriDate = HijriGregDate(
day: 1,
month: (zeroBasedMonth % 12) + 1,
year: zeroBasedMonth ~/ 12,
);
final nextGregorianDate = calendarModel.hijriToGregorian(nextHijriDate);
setState(() {
_currentHijriDisplay = nextHijriDate;
_calendarController.displayDate = nextGregorianDate;
_displayedMonthKey = _monthKey(nextGregorianDate);
});
}
void _handleCalendarViewChanged(DateRangePickerViewChangedArgs args) {
final rangeStart = args.visibleDateRange.startDate;
final rangeEnd = args.visibleDateRange.endDate;
final displayedDate = _calendarController.displayDate ??
(rangeEnd == null
? rangeStart
: rangeStart?.add(Duration(days: rangeEnd.difference(rangeStart).inDays ~/ 2)));
if (displayedDate == null) return;
final newMonthKey = _monthKey(displayedDate);
if (newMonthKey == _displayedMonthKey) return;
_displayedMonthKey = newMonthKey;
_currentHijriDisplay = null;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {});
}
});
}
// Build unified calendar header for both Gregorian and Hijri // Build unified calendar header for both Gregorian and Hijri
Widget _buildCalendarHeader(bool isArabic, DateRangCalenderModel calendarModel) { Widget _buildCalendarHeader(bool isArabic, DateRangCalenderModel calendarModel) {
final displayedDate = _calendarController.displayDate ?? DateTime.now(); final displayedDate = _calendarController.displayDate ?? DateTime.now();
@ -609,12 +667,10 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
icon: Icon(Icons.chevron_left, color: AppColors.primaryRedColor), icon: Icon(Icons.chevron_left, color: AppColors.primaryRedColor),
onPressed: () { onPressed: () {
if (_viewMode == PickerViewMode.date) { if (_viewMode == PickerViewMode.date) {
_calendarController.backward!();
// Update tracked Hijri display when navigating months
if (calendarModel.isHijri) { if (calendarModel.isHijri) {
setState(() { _changeHijriMonth(calendarModel, -1);
_currentHijriDisplay = null; // Will recalculate on next build } else {
}); _calendarController.backward!();
} }
} else if (_viewMode == PickerViewMode.year) { } else if (_viewMode == PickerViewMode.year) {
// Navigate years backward by 12 // Navigate years backward by 12
@ -625,7 +681,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
try { try {
final newHijriDate = HijriGregDate(day: 1, month: hijriDate.month, year: hijriDate.year - 12); final newHijriDate = HijriGregDate(day: 1, month: hijriDate.month, year: hijriDate.year - 12);
final newGregorianDate = calendarModel.hijriToGregorian(newHijriDate); final newGregorianDate = calendarModel.hijriToGregorian(newHijriDate);
_calendarController.displayDate = DateTime(newGregorianDate.year, newGregorianDate.month, 1); _calendarController.displayDate = newGregorianDate;
_currentHijriDisplay = newHijriDate; _currentHijriDisplay = newHijriDate;
} catch (e) { } catch (e) {
// Fallback // Fallback
@ -645,12 +701,10 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
icon: Icon(Icons.chevron_right, color: AppColors.primaryRedColor), icon: Icon(Icons.chevron_right, color: AppColors.primaryRedColor),
onPressed: () { onPressed: () {
if (_viewMode == PickerViewMode.date) { if (_viewMode == PickerViewMode.date) {
_calendarController.forward!();
// Update tracked Hijri display when navigating months
if (calendarModel.isHijri) { if (calendarModel.isHijri) {
setState(() { _changeHijriMonth(calendarModel, 1);
_currentHijriDisplay = null; // Will recalculate on next build } else {
}); _calendarController.forward!();
} }
} else if (_viewMode == PickerViewMode.year) { } else if (_viewMode == PickerViewMode.year) {
// Navigate years forward by 12 // Navigate years forward by 12
@ -661,7 +715,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
try { try {
final newHijriDate = HijriGregDate(day: 1, month: hijriDate.month, year: hijriDate.year + 12); final newHijriDate = HijriGregDate(day: 1, month: hijriDate.month, year: hijriDate.year + 12);
final newGregorianDate = calendarModel.hijriToGregorian(newHijriDate); final newGregorianDate = calendarModel.hijriToGregorian(newHijriDate);
_calendarController.displayDate = DateTime(newGregorianDate.year, newGregorianDate.month, 1); _calendarController.displayDate = newGregorianDate;
_currentHijriDisplay = newHijriDate; _currentHijriDisplay = newHijriDate;
} catch (e) { } catch (e) {
// Fallback // Fallback
@ -696,7 +750,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
if (calendarModel.isHijri) { if (calendarModel.isHijri) {
// For Hijri calendar // For Hijri calendar
final hijriCurrent = calendarModel.gregorianToHijri(currentDate); final hijriCurrent = calendarModel.gregorianToHijri(currentDate);
final hijriDisplayed = calendarModel.gregorianToHijri(displayedDate); final hijriDisplayed = _currentHijriDisplay ?? calendarModel.gregorianToHijri(displayedDate);
currentMonth = hijriCurrent.month; currentMonth = hijriCurrent.month;
currentYear = hijriCurrent.year; currentYear = hijriCurrent.year;
displayedYear = hijriDisplayed.year; displayedYear = hijriDisplayed.year;
@ -730,11 +784,9 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
try { try {
final hijriDate = HijriGregDate(day: 1, month: monthIndex, year: displayedYear); final hijriDate = HijriGregDate(day: 1, month: monthIndex, year: displayedYear);
final gregorianDate = calendarModel.hijriToGregorian(hijriDate); final gregorianDate = calendarModel.hijriToGregorian(hijriDate);
_calendarController.displayDate = DateTime(gregorianDate.year, gregorianDate.month, 1); _calendarController.displayDate = gregorianDate;
// Track the Hijri month/year for accurate header display // Track the Hijri month/year for accurate header display
_currentHijriDisplay = hijriDate; _currentHijriDisplay = hijriDate;
// Clear cache since we changed the month
_hijriCache.clear();
} catch (e) { } catch (e) {
// Fallback if conversion fails // Fallback if conversion fails
_calendarController.displayDate = DateTime(displayedDate.year, monthIndex, 1); _calendarController.displayDate = DateTime(displayedDate.year, monthIndex, 1);
@ -784,7 +836,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
if (calendarModel.isHijri) { if (calendarModel.isHijri) {
// For Hijri calendar // For Hijri calendar
final hijriDisplayed = calendarModel.gregorianToHijri(displayedDate); final hijriDisplayed = _currentHijriDisplay ?? calendarModel.gregorianToHijri(displayedDate);
final hijriCurrent = calendarModel.gregorianToHijri(DateTime.now()); final hijriCurrent = calendarModel.gregorianToHijri(DateTime.now());
displayedYear = hijriDisplayed.year; displayedYear = hijriDisplayed.year;
currentDisplayYear = hijriCurrent.year; currentDisplayYear = hijriCurrent.year;
@ -818,7 +870,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
final currentHijriMonth = _currentHijriDisplay?.month ?? calendarModel.gregorianToHijri(displayedDate).month; final currentHijriMonth = _currentHijriDisplay?.month ?? calendarModel.gregorianToHijri(displayedDate).month;
final hijriDate = HijriGregDate(day: 1, month: currentHijriMonth, year: year); final hijriDate = HijriGregDate(day: 1, month: currentHijriMonth, year: year);
final gregorianDate = calendarModel.hijriToGregorian(hijriDate); final gregorianDate = calendarModel.hijriToGregorian(hijriDate);
_calendarController.displayDate = DateTime(gregorianDate.year, gregorianDate.month, 1); _calendarController.displayDate = gregorianDate;
// Track the Hijri month/year for accurate header display // Track the Hijri month/year for accurate header display
_currentHijriDisplay = hijriDate; _currentHijriDisplay = hijriDate;
} catch (e) { } catch (e) {
@ -876,34 +928,34 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
} }
displayDate(String label, String? date, bool isNotSelected) => Expanded( displayDate(String label, String? date, bool isNotSelected) => Expanded(
child: Row( child: Row(
spacing: 12.h, spacing: 12.h,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.rangeCalendar, iconColor: isNotSelected ? AppColors.borderOnlyColor : AppColors.blackColor, height: 24, width: 24),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Utils.buildSvgWithAssets(icon: AppAssets.rangeCalendar, iconColor: isNotSelected ? AppColors.borderOnlyColor : AppColors.blackColor, height: 24, width: 24), Text(
Column( label,
crossAxisAlignment: CrossAxisAlignment.start, style: TextStyle(
children: [ color: AppColors.inputLabelTextColor,
Text( fontSize: 12.f,
label, fontWeight: FontWeight.w600,
style: TextStyle( ),
color: AppColors.inputLabelTextColor, ),
fontSize: 12.f, Text(
fontWeight: FontWeight.w600, date!,
), style: TextStyle(
), color: AppColors.textColor,
Text( fontSize: 14.f,
date!, fontWeight: FontWeight.w600,
style: TextStyle( ),
color: AppColors.textColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
),
)
],
) )
], ],
), )
); ],
),
);
selectionChip(DateRangeSelectorRangeViewModel model) { selectionChip(DateRangeSelectorRangeViewModel model) {
return Row( return Row(

@ -122,12 +122,12 @@ class HijriGregConverter {
static int _hijriYearStartJulian(int hijriYear) { static int _hijriYearStartJulian(int hijriYear) {
if (hijriYear <= 1) return _hijriEpoch; if (hijriYear <= 1) return _hijriEpoch;
int totalDays = 0; // A Hijri year has 354 days, with 11 leap days in every 30-year
for (int year = 1; year < hijriYear; year++) { // cycle. Calculate the completed years directly instead of iterating
totalDays += _hijriYearLength(year); // from year 1 for every calendar cell.
} final completedYears = hijriYear - 1;
final completedLeapDays = (3 + (11 * hijriYear)) ~/ 30;
return _hijriEpoch + totalDays; return _hijriEpoch + (completedYears * 354) + completedLeapDays;
} }
static int _hijriYearLength(int year) { static int _hijriYearLength(int year) {
@ -358,6 +358,8 @@ class DateRangCalenderModel extends ChangeNotifier {
} }
void setTabIndex(int index) { void setTabIndex(int index) {
if (_selectedTabIndex == index) return;
_selectedTabIndex = index; _selectedTabIndex = index;
_calendarType = index == 0 ? CalendarType.gregorian : CalendarType.hijri; _calendarType = index == 0 ? CalendarType.gregorian : CalendarType.hijri;
// Persist the selection for next time the widget opens // Persist the selection for next time the widget opens

@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
enum _PAYMENT_TYPE { PACKAGES, PHARMACY, PATIENT } enum _PAYMENT_TYPE { PACKAGES, PHARMACY, PATIENT }
@ -217,6 +218,8 @@ class MyInAppBrowser extends InAppBrowser {
tamaraRequestModel.appointmentDate = (appoDate != null && appoDate != "") ? appoDate : null; tamaraRequestModel.appointmentDate = (appoDate != null && appoDate != "") ? appoDate : null;
tamaraRequestModel.isSchedule = ((appoNo != null && appoNo != "") && (appoDate != null && appoDate != "")) ? true : false; tamaraRequestModel.isSchedule = ((appoNo != null && appoNo != "") && (appoDate != null && appoDate != "")) ? true : false;
getIt.get<PayfortViewModel>().tamaraRequestInsert(tamaraRequestModel: tamaraRequestModel);
// service.tamaraInsertRequest(tamaraRequestModel, context).then((res) { // service.tamaraInsertRequest(tamaraRequestModel, context).then((res) {
// // if (context != null) GifLoaderDialogUtils.hideDialog(context); // // if (context != null) GifLoaderDialogUtils.hideDialog(context);
generateTamaraURL(amount, orderDesc, transactionID, projId, emailId, paymentMethod, patientType, patientName, patientID, authenticatedUser, isLiveCareAppo, servID, LiveServID, appoDate, appoNo, generateTamaraURL(amount, orderDesc, transactionID, projId, emailId, paymentMethod, patientType, patientName, patientID, authenticatedUser, isLiveCareAppo, servID, LiveServID, appoDate, appoNo,

@ -198,7 +198,8 @@ class TextInputWidget extends StatelessWidget {
], ],
), ),
), ),
(suffix != null) ? suffix! : SizedBox.shrink() // (suffix != null) ? suffix : SizedBox.shrink()
suffix ?? SizedBox.shrink()
], ],
), ),
), ),

@ -2,8 +2,8 @@ name: hmg_patient_app_new
description: "New HMG Patient App" description: "New HMG Patient App"
publish_to: 'none' # Remove this line if you wish to publish to pub.dev publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 0.0.44+45 #version: 0.0.45+46
#version: 0.0.14+1 version: 0.0.14+3
environment: environment:
sdk: ">=3.6.0 <4.0.0" sdk: ">=3.6.0 <4.0.0"
@ -63,7 +63,8 @@ dependencies:
geolocator: ^14.0.2 geolocator: ^14.0.2
dropdown_search: ^6.0.2 dropdown_search: ^6.0.2
google_maps_flutter: ^2.13.1 google_maps_flutter: ^2.13.1
flutter_zoom_videosdk: 2.1.10 # flutter_zoom_videosdk: 2.1.10
flutter_zoom_videosdk: ^2.5.10
dart_jsonwebtoken: ^3.2.0 dart_jsonwebtoken: ^3.2.0
dartz: ^0.10.1 dartz: ^0.10.1
equatable: ^2.0.7 equatable: ^2.0.7
@ -110,6 +111,8 @@ dependencies:
screen_brightness: ^1.0.1 screen_brightness: ^1.0.1
flutter_screenshot_blocker: ^1.0.4 flutter_screenshot_blocker: ^1.0.4
cloudflare_turnstile: ^3.7.2 cloudflare_turnstile: ^3.7.2
freerasp: ^8.2.1
safe_device: ^1.4.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

Loading…
Cancel
Save