haroon_dev #343

Merged
Haroon6138 merged 4 commits from haroon_dev into master 2 weeks ago

@ -211,10 +211,8 @@ class ApiClientImp implements ApiClient {
}
// body['TokenID'] = "@dm!n";
// body['PatientID'] = 1018977;
// body['PatientID'] = 89754444;
// body['PatientID'] = 2122445;
// body['PatientID'] = 53320;
// body['PatientID'] = 4769748;
// body['PatientTypeID'] = 1;
// body['PatientOutSA'] = 0;
// body['SessionID'] = "45786230487560q";

@ -5,7 +5,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
@ -301,7 +301,7 @@ class ApiConsts {
static String googleCloudStorageENTranslationFileBaseURL = "https://storage.googleapis.com/hmg-patientapp-translations";
// ************ static values for Api ****************
static final double appVersionID = 21.5;
static final double appVersionID = 21.6;
// static final double appVersionID = 50.7;
static final int appChannelId = 3;

@ -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/notification_service.dart';
import 'package:hmg_patient_app_new/services/permission_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_view_model.dart';
import 'package:local_auth/local_auth.dart';
@ -155,6 +156,7 @@ class AppDependencies {
);
getIt.registerLazySingleton<PermissionService>(() => PermissionService());
getIt.registerLazySingleton<TurnstileService>(() => TurnstileService(getIt<LoggerService>()));
// Repositories
getIt.registerLazySingleton<CommonRepo>(() => CommonRepoImp(loggerService: getIt()));
@ -255,7 +257,8 @@ class AppDependencies {
dialogService: getIt(),
appState: getIt(),
errorHandlerService: getIt(),
localAuthService: getIt()),
localAuthService: getIt(),
turnstileService: getIt()),
);
getIt.registerLazySingleton<ProfileSettingsViewModel>(() => ProfileSettingsViewModel(

@ -0,0 +1,56 @@
import 'dart:async';
import 'package:cloudflare_turnstile/cloudflare_turnstile.dart';
import 'package:flutter/foundation.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
/// Service to handle Cloudflare Turnstile captcha verification
class TurnstileService {
final LoggerService _loggerService;
// Replace with your Cloudflare Turnstile site key
static const String _siteKey = '0x4AAAAAAAkmbvEy63pcxBBt';
TurnstileService(this._loggerService);
/// Get Turnstile token using invisible mode
/// Returns the token on success, null on failure
Future<String?> getTurnstileToken() async {
// Initialize an instance of invisible Cloudflare Turnstile with your site key
final turnstile = CloudflareTurnstile.invisible(
siteKey: _siteKey,
);
try {
// Get the Turnstile token
final token = await turnstile.getToken();
_loggerService.logInfo('Turnstile token retrieved successfully');
return token; // Return the token upon success
} on TurnstileException catch (e) {
// Handle Turnstile failure
_loggerService.logError('Turnstile challenge failed: ${e.message}');
if (kDebugMode) {
print('Challenge failed: ${e.message}');
}
} catch (e) {
// Handle any other exceptions
_loggerService.logError('Unexpected error during Turnstile challenge: $e');
if (kDebugMode) {
print('Unexpected error: $e');
}
} finally {
// Ensure the Turnstile instance is properly disposed of
turnstile.dispose();
}
// Return null if the token couldn't be generated
return null;
}
/// Get the site key for Turnstile widget
String getSiteKey() => _siteKey;
/// Validate if Turnstile is properly configured
bool isConfigured() {
return _siteKey.isNotEmpty && _siteKey != '0x4AAAAAAAkmbvEy63pcxBBt';
}
}

@ -147,6 +147,9 @@ class AuthenticationRepoImp implements AuthenticationRepo {
bool isRegister = false,
bool isFormFamilyFile = false,
}) async {
int isOutKsa = (sendActivationCodeReq.zipCode == '966' || sendActivationCodeReq.zipCode == '+966') ? 0 : 1;
sendActivationCodeReq.patientOutSA = isOutKsa;
sendActivationCodeReq.isDentalAllowedBackend = false;

@ -42,6 +42,7 @@ import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/localauth_service.dart';
import 'package:hmg_patient_app_new/services/navigation_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/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
@ -60,6 +61,7 @@ class AuthenticationViewModel extends ChangeNotifier {
final DialogService _dialogService;
final NavigationService _navigationService;
final LocalAuthService _localAuthService;
final TurnstileService _turnstileService;
AuthenticationViewModel({
required AppState appState,
@ -69,12 +71,14 @@ class AuthenticationViewModel extends ChangeNotifier {
required NavigationService navigationService,
required CacheService cacheService,
required LocalAuthService localAuthService,
required TurnstileService turnstileService,
}) : _navigationService = navigationService,
_dialogService = dialogService,
_errorHandlerService = errorHandlerService,
_appState = appState,
_authenticationRepo = authenticationRepo,
_localAuthService = localAuthService {}
_localAuthService = localAuthService,
_turnstileService = turnstileService {}
final TextEditingController nationalIdController = TextEditingController(),
phoneNumberController = TextEditingController(),
dobController = TextEditingController(),
@ -974,6 +978,15 @@ class AuthenticationViewModel extends ChangeNotifier {
// TODO: GET APP SMS SIGNATURE HERE
request.sMSSignature = await getSignature();
// Get Turnstile token if configured
final turnstileToken = await _turnstileService.getTurnstileToken();
if (turnstileToken != null) {
request.turnstileToken = turnstileToken;
} else {
request.turnstileToken = "";
}
if (isNeedLoading) {
LoaderBottomSheet.showLoader();
}

@ -30,6 +30,7 @@ class SendActivationRequest {
int? status;
int? familyRegionID;
bool? isPatientExcluded;
String? turnstileToken;
SendActivationRequest(
{this.patientMobileNumber,
@ -62,7 +63,8 @@ class SendActivationRequest {
this.responseID,
this.status,
this.familyRegionID,
this.isPatientExcluded});
this.isPatientExcluded,
this.turnstileToken});
SendActivationRequest.fromJson(Map<String, dynamic> json) {
patientMobileNumber = json['PatientMobileNumber'];
@ -96,6 +98,7 @@ class SendActivationRequest {
status = json['Status'];
familyRegionID = json['FamilyRegionID'];
isPatientExcluded = json['IsPatientExcluded'];
turnstileToken = json['TurnstileToken'];
}
Map<String, dynamic> toJson() {
@ -135,6 +138,9 @@ class SendActivationRequest {
data['Status'] = status;
data['FamilyRegionID'] = familyRegionID;
data['IsPatientExcluded'] = isPatientExcluded;
if (turnstileToken != null) {
data['TurnstileToken'] = turnstileToken;
}
return data;
}
}

@ -12,6 +12,7 @@ import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_p
import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_status_coc_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:permission_handler/permission_handler.dart';
class ContactUsViewModel extends ChangeNotifier {
@ -308,7 +309,15 @@ class ContactUsViewModel extends ChangeNotifier {
final result = await contactUsRepo.insertCOCItem(requestInsertCOCItem: requestInsertCOCItem, patientSelectedAppointment: patientFeedbackSelectedAppointment);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(failure) async {
LoaderBottomSheet.hideLoader();
await errorHandlerService.handleError(failure: failure);
},
// (failure) async {
// if(onError != null) {
// onError(failure.toString());
// }
// },
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});

@ -53,7 +53,7 @@ abstract class MyAppointmentsRepo {
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientAppointmentsForTimeLine();
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientDoctorsList();
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientDoctorsList({bool isTop8 = false});
Future<Either<Failure, GenericApiModel<List<GetFavoriteDoctorsListModel>>>> getFavouriteDoctorsList();
@ -514,11 +514,12 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
}
@override
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientDoctorsList() async {
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientDoctorsList({bool isTop8 = false}) async {
Map<String, dynamic> mapDevice = {
"Top": 0,
"beforeDays": 0,
"exludType": 4,
"IsTop8": isTop8,
};
try {

@ -211,6 +211,11 @@ class MyAppointmentsViewModel extends ChangeNotifier {
notifyListeners();
}
setIsMyDoctorsDataToBeLoaded(bool val) {
isMyDoctorsDataToBeLoaded = val;
notifyListeners();
}
setIsAppointmentDataToBeLoaded(bool val) {
if (val) {
isMyAppointmentsLoading = true;
@ -675,13 +680,13 @@ class MyAppointmentsViewModel extends ChangeNotifier {
);
}
Future<void> getPatientMyDoctors({Function(dynamic)? onSuccess, Function(String)? onError}) async {
Future<void> getPatientMyDoctors({bool isTop8 = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (!isMyDoctorsDataToBeLoaded) return;
isPatientMyDoctorsLoading = true;
patientMyDoctorsList.clear();
notifyListeners();
final result = await myAppointmentsRepo.getPatientDoctorsList();
final result = await myAppointmentsRepo.getPatientDoctorsList(isTop8: isTop8);
result.fold(
// (failure) async => await errorHandlerService.handleError(failure: failure),

@ -235,11 +235,16 @@ class PrescriptionsViewModel extends ChangeNotifier {
result.fold(
(failure) async {
onError!(failure.message);
if (onError != null) {
onError(failure.message);
}
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage!);
if (onError != null) {
onError(apiResponse.errorMessage ?? "An error occurred while fetching the prescription instructions PDF.");
}
// onError!(apiResponse.errorMessage!);
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
prescriptionInstructionsPDFLink = apiResponse.data;

@ -43,8 +43,10 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
@override
void initState() {
scheduleMicrotask(() {
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
if (!myAppointmentsViewModel.isMyAppointmentsLoading) {
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
}
});
super.initState();
}

@ -39,7 +39,7 @@ class _MyDoctorsPageState extends State<MyDoctorsPage> {
@override
void initState() {
scheduleMicrotask(() {
myAppointmentsViewModel.getPatientMyDoctors();
myAppointmentsViewModel.getPatientMyDoctors(isTop8: false);
});
super.initState();
}

@ -101,7 +101,7 @@ class AppointmentDoctorCard extends StatelessWidget {
Row(
children: [
Expanded(
child: patientAppointmentHistoryResponseModel.doctorNameObj!.toText16(
child: (patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText16(
isBold: true,
isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? ""),
textOverflow: TextOverflow.ellipsis,

@ -66,14 +66,14 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
// bookAppointmentsViewModel.getLocation();
immediateLiveCareViewModel.initImmediateLiveCare();
if (appState.isAuthenticated) {
getIt.get<MyAppointmentsViewModel>().getPatientMyDoctors();
getIt.get<MyAppointmentsViewModel>().getPatientMyDoctors(isTop8: true);
getIt.get<MyAppointmentsViewModel>().getPatientFavouriteDoctors();
}
});
WidgetsBinding.instance.addPostFrameCallback((_) {
if (bookAppointmentsViewModel.selectedTabIndex == 1) {
if (appState.isAuthenticated) {
getIt.get<MyAppointmentsViewModel>().getPatientMyDoctors();
getIt.get<MyAppointmentsViewModel>().getPatientMyDoctors(isTop8: true);
getIt.get<MyAppointmentsViewModel>().getPatientFavouriteDoctors();
showUnKnownClinicBottomSheet();
}
@ -166,6 +166,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h),
],
).paddingSymmetrical(24.h, 0.h).onPress(() {
myAppointmentsVM.setIsMyDoctorsDataToBeLoaded(true);
Navigator.of(context).push(
CustomPageRoute(
page: MyDoctorsPage(),

@ -544,12 +544,12 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
if (selectedPaymentMethod == "TAMARA") {
if (Platform.isAndroid) {
Uri uri = new Uri.dataFromString(url);
tamaraPaymentStatus = uri.queryParameters['status']!;
tamaraOrderID = uri.queryParameters['AuthorizePaymentId']!;
tamaraPaymentStatus = uri.queryParameters['status'] ?? "";
tamaraOrderID = uri.queryParameters['AuthorizePaymentId'] ?? "";
} else {
Uri uri = new Uri.dataFromString(url);
tamaraPaymentStatus = uri.queryParameters['paymentStatus']!;
tamaraOrderID = uri.queryParameters['orderId']!;
tamaraPaymentStatus = uri.queryParameters['paymentStatus'] ?? "";
tamaraOrderID = uri.queryParameters['orderId'] ?? "";
}
}

File diff suppressed because one or more lines are too long

@ -181,10 +181,11 @@ class _ExpandableDescriptionText extends StatelessWidget {
static TextStyle get textStyle => TextStyle(
fontSize: 14.f,
color: AppColors.textColor, // or Colors.black87
fontFamily: 'Poppins',
fontWeight: FontWeight.w400,
fontStyle: FontStyle.normal,
color: AppColors.textColorLight,
// or Colors.black87
// fontFamily: 'Poppins',
fontWeight: FontWeight.w600,
fontStyle: FontStyle.normal,
height: 1.4,
letterSpacing: 0.0,
decoration: TextDecoration.none,
@ -199,7 +200,7 @@ class _ExpandableDescriptionText extends StatelessWidget {
final readMoreStyle = TextStyle(
fontSize: 12.f,
color: AppColors.primaryRedColor,
fontFamily: 'Poppins',
// fontFamily: 'Poppins',
fontWeight: FontWeight.w500,
);

@ -110,7 +110,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
appState = getIt.get<AppState>();
scheduleMicrotask(() {
if (appState.isAuthenticated) {
myAppointmentsViewModel.getPatientMyDoctors();
myAppointmentsViewModel.getPatientMyDoctors(isTop8: true);
prescriptionsViewModel.initPrescriptionsViewModel();
insuranceViewModel.initInsuranceProvider();
medicalFileViewModel.setIsPatientSickLeaveListLoading(true);
@ -1046,6 +1046,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
],
).onPress(() {
// myAppointmentsViewModel.getPatientMyDoctors();
myAppointmentsViewModel.setIsMyDoctorsDataToBeLoaded(true);
Navigator.of(context).push(
CustomPageRoute(
page: MyDoctorsPage(),

@ -1,5 +1,6 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenshot_blocker/flutter_screenshot_blocker.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -20,28 +21,38 @@ class NotificationDetailsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CollapsingListView(
title: LocaleKeys.notificationDetails.tr(),
// trailing: IconButton(
// icon: Icon(
// Icons.share_outlined,
// size: 24.h,
// color: AppColors.textColor,
// ),
// onPressed: () {
// _shareNotification();
// },
// ),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24.h),
// Notification content card
_buildNotificationCard(context),
SizedBox(height: 24.h),
],
).paddingSymmetrical(24.w, 0.h),
return ScreenshotBlockerWidget(
blockScreenshots: true, // makes this widget appear black in screenshots
detectScreenshots: true, // listen for screenshot attempts
onScreenshotDetected: (event) {
// event.type 'screenshot_taken' or 'screenshot_blocked'
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(content: Text('Screenshots are not allowed!')),
// );
},
child: CollapsingListView(
title: LocaleKeys.notificationDetails.tr(),
// trailing: IconButton(
// icon: Icon(
// Icons.share_outlined,
// size: 24.h,
// color: AppColors.textColor,
// ),
// onPressed: () {
// _shareNotification();
// },
// ),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24.h),
// Notification content card
_buildNotificationCard(context),
SizedBox(height: 24.h),
],
).paddingSymmetrical(24.w, 0.h),
),
),
);
}
@ -109,9 +120,9 @@ class NotificationDetailsPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.messageNotification.tr().toText14(
weight: FontWeight.w600,
color: AppColors.greyTextColor,
),
weight: FontWeight.w600,
color: AppColors.greyTextColor,
),
SizedBox(height: 8.h),
notification.message!.toText16(
weight: FontWeight.w400,
@ -123,8 +134,7 @@ class NotificationDetailsPage extends StatelessWidget {
),
// Notification image (if MessageType is "image")
if (notification.messageType != null &&
notification.messageType!.toLowerCase() == "image")
if (notification.messageType != null && notification.messageType!.toLowerCase() == "image")
Builder(
builder: (context) {
// Try to get image URL from videoURL or messageTypeData
@ -147,9 +157,9 @@ class NotificationDetailsPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.attachedImage.tr().toText14(
weight: FontWeight.w600,
color: AppColors.greyTextColor,
),
weight: FontWeight.w600,
color: AppColors.greyTextColor,
),
SizedBox(height: 8.h),
ClipRRect(
borderRadius: BorderRadius.circular(12.h),
@ -177,8 +187,8 @@ class NotificationDetailsPage extends StatelessWidget {
),
SizedBox(height: 8.h),
LocaleKeys.failedToLoadImage.tr().toText12(
color: AppColors.greyTextColor,
),
color: AppColors.greyTextColor,
),
SizedBox(height: 4.h),
Text(
imageUrl!,
@ -205,10 +215,7 @@ class NotificationDetailsPage extends StatelessWidget {
),
child: Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null,
),
),
);
@ -249,7 +256,8 @@ ${notification.message ?? 'Notification'}
Time: ${_formatTime(notification.isSentOn)}
Date: ${_formatDate(notification.isSentOn)}
${notification.notificationType != null ? '\nType: ${notification.notificationType}' : ''}
'''.trim();
'''
.trim();
await Share.share(shareText);
}
@ -274,4 +282,3 @@ ${notification.notificationType != null ? '\nType: ${notification.notificationTy
}
}
}

@ -61,7 +61,8 @@ class _NotificationsListPageState extends State<NotificationsListPage> {
child: ListView.builder(
itemCount: notificationsVM.isNotificationsLoading
? 4
: notificationsVM.notificationsList.length + (notificationsVM.hasMoreNotifications ? 1 : 0),
// : notificationsVM.notificationsList.length + (notificationsVM.hasMoreNotifications ? 1 : 0),
: notificationsVM.notificationsList.length,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsetsGeometry.zero,

@ -2,8 +2,8 @@ name: hmg_patient_app_new
description: "New HMG Patient App"
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
#version: 0.0.38+37
version: 0.0.7+1
version: 0.0.39+38
#version: 0.0.8+1
environment:
sdk: ">=3.6.0 <4.0.0"
@ -108,6 +108,8 @@ dependencies:
clarity_flutter: 1.9.0
flutter_amazonpaymentservices: ^0.0.13
screen_brightness: ^1.0.1
flutter_screenshot_blocker: ^1.0.4
cloudflare_turnstile: ^3.7.2
dev_dependencies:
flutter_test:

Loading…
Cancel
Save