Merge pull request 'haroon_dev' (#177) from haroon_dev into master

Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/177
pull/179/head
Haroon6138 3 weeks ago
commit 31a7aa907b

@ -1526,8 +1526,6 @@
"trackerAndOthers": "المتتبع وأخرى",
"bmi": "مؤشر كتلة الجسم",
"yesPleaseINeedHelp": "نعم، من فضلك أريد المساعدة",
"noThanksIKnowTheClinic": "لا شكراً، أعرف العيادة"
"noThanksIKnowTheClinic": "لا شكراً، أعرف العيادة",
"unableToSendOTP": "تعذر إرسال رمز التحقق لمرة واحدة"
}

@ -1517,9 +1517,6 @@
"trackerAndOthers": "Tracker & Others",
"bmi": "BMI",
"yesPleaseINeedHelp": "Yes, Please I need help",
"noThanksIKnowTheClinic": "No, Thanks. I Know the clinic"
"noThanksIKnowTheClinic": "No, Thanks. I Know the clinic",
"unableToSendOTP": "Unable to send Verification OTP"
}

@ -54,6 +54,8 @@ post_install do |installer|
'PERMISSION_EVENTS_FULL_ACCESS=1',
## dart: PermissionGroup.reminders
'PERMISSION_REMINDERS=1',
## dart: PermissionGroup.notification
'PERMISSION_NOTIFICATIONS=1',
]
build_configuration.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64 i386'
build_configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'

@ -182,7 +182,7 @@ class ApiClientImp implements ApiClient {
}
// body['TokenID'] = "@dm!n";
// body['PatientID'] = 1018977;
// body['PatientID'] = 4769038;
// body['PatientTypeID'] = 1;
//
// body['PatientOutSA'] = 0;
@ -208,10 +208,10 @@ class ApiClientImp implements ApiClient {
// Handle body encoding based on isBodyPlainText flag
final dynamic requestBody = isBodyPlainText ? body : json.encode(body);
debugPrint("uri: ${Uri.parse(url.trim())}");
debugPrint("body: ${json.encode(body)}", wrapWidth: 2048);
final response = await http.post(Uri.parse(url.trim()), body: requestBody, headers: headers);
final int statusCode = response.statusCode;
log("uri: ${Uri.parse(url.trim())}");
log("body: ${json.encode(body)}");
// log("response.body: ${response.body}");
if (statusCode < 200 || statusCode >= 400) {
onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data"));

@ -680,7 +680,7 @@ const DASHBOARD = 'Services/Patients.svc/REST/PatientDashboard';
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

@ -62,6 +62,31 @@ extension EmailValidator on String {
decorationColor: color ?? AppColors.blackColor),
);
Widget toText9(
{Color? color,
FontWeight? weight,
bool isBold = false,
bool isUnderLine = false,
bool isCenter = false,
int? maxlines,
FontStyle? fontStyle,
TextOverflow? textOverflow,
double letterSpacing = 0}) =>
Text(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: maxlines,
overflow: textOverflow,
style: TextStyle(
fontSize: 9.f,
fontStyle: fontStyle ?? FontStyle.normal,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
color: color ?? AppColors.blackColor,
letterSpacing: letterSpacing,
decoration: isUnderLine ? TextDecoration.underline : null,
decorationColor: color ?? AppColors.blackColor),
);
Widget toText11(
{Color? color,
FontWeight? weight,

@ -224,7 +224,7 @@ class MyApp extends StatelessWidget {
return MaterialApp(
title: 'Dr. AlHabib',
builder: (context, mchild) {
return MediaQuery(data: MediaQuery.of(context).copyWith(textScaler: TextScaler.linear(1.0)), child: mchild!);
return MediaQuery(data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling), child: mchild!);
},
showSemanticsDebugger: false,
debugShowCheckedModeBanner: false,

@ -59,6 +59,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
late LabViewModel labViewModel;
late RadiologyViewModel radiologyViewModel;
String mapURL = "";
@override
void initState() {
scheduleMicrotask(() async {
@ -212,8 +214,11 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
borderRadius: BorderRadius.circular(24.r),
// Todo: what is this???? Api Key??? 😲
child: Image.network(
"https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=350x165&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=${ApiKeyConstants.googleMapsApiKey}",
"https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=${(MediaQuery.of(context).size.width * 1.5).toInt()}x165&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=${ApiKeyConstants.googleMapsApiKey}",
fit: BoxFit.contain,
// errorBuilder: (cxt, child, tr) {
// return SizedBox.shrink();
// },
),
),
Positioned(

@ -459,7 +459,6 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
await payfortViewModel.checkPaymentStatus(
transactionID: transID,
onSuccess: (apiResponse) async {
print(apiResponse.data);
if (payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!.toLowerCase() == "success") {
await myAppointmentsViewModel.createAdvancePayment(
paymentMethodName: selectedPaymentMethod,
@ -517,6 +516,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
});
});
} else {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)),
@ -564,14 +564,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
}
startApplePay() async {
showCommonBottomSheet(context,
child: Utils.getLoadingWidget(),
callBackFunc: (str) {},
title: "",
height: ResponsiveExtension.screenHeight * 0.3,
isCloseButtonVisible: false,
isDismissible: false,
isFullScreen: false);
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context));
transID = Utils.getAppointmentTransID(
widget.patientAppointmentHistoryResponseModel.projectID,
widget.patientAppointmentHistoryResponseModel.clinicID,
@ -642,7 +635,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
onFailed: (failureResult) async {
log("failureResult: ${failureResult.message.toString()}");
Navigator.of(context).pop();
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: failureResult.message.toString()),
@ -652,6 +645,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
);
},
onSucceeded: (successResult) async {
LoaderBottomSheet.hideLoader();
log("successResult: ${successResult.responseMessage.toString()}");
selectedPaymentMethod = successResult.paymentOption ?? "VISA";
checkPaymentStatus();

@ -151,7 +151,9 @@ class AppointmentCard extends StatelessWidget {
children: [
Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h),
SizedBox(height: 2.h),
"${patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor),
(isFoldable || isTablet)
? "${patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText9(isBold: true, color: AppColors.textColor)
: "${patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor),
],
),
).circle(100).toShimmer2(isShow: isLoading),
@ -224,83 +226,108 @@ class AppointmentCard extends StatelessWidget {
}
Widget _buildActionArea(BuildContext context, AppState appState) {
if (isFromMedicalReport) {
if (isForEyeMeasurements) {
return SizedBox.shrink();
} else {
return CustomButton(
text: LocaleKeys.selectAppointment.tr(context: context),
onPressed: () {
if (isForFeedback) {
contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel);
} else {
medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel);
}
Navigator.pop(context, false);
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
// height: isTablet || isFoldable ? 46.h : 40.h,
height: 40.h,
icon: AppAssets.checkmark_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
);
if ((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) && AppointmentType.isArrived(patientAppointmentHistoryResponseModel)) {
return CustomButton(
text: LocaleKeys.viewDetails.tr(context: context),
onPressed: () {
Navigator.of(context)
.push(
CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel),
),
)
.then((_) {
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
});
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
// height: isTablet || isFoldable ? 46.h : 40.h,
height: 40.h,
);
} else {
if (isFromMedicalReport) {
if (isForEyeMeasurements) {
return SizedBox.shrink();
} else {
return CustomButton(
text: LocaleKeys.selectAppointment.tr(context: context),
onPressed: () {
if (isForFeedback) {
contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel);
} else {
medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel);
}
Navigator.pop(context, false);
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
// height: isTablet || isFoldable ? 46.h : 40.h,
height: 40.h,
icon: AppAssets.checkmark_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
);
}
}
}
return Row(
children: [
Expanded(
flex: 6,
child: (AppointmentType.isArrived(patientAppointmentHistoryResponseModel)
? _getArrivedButton(context)
: CustomButton(
text: AppointmentType.getNextActionText(patientAppointmentHistoryResponseModel.nextAction),
onPressed: () => _goToDetails(context),
backgroundColor:
AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15),
borderColor:
AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01),
textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction),
fontSize: 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
icon: AppointmentType.getNextActionIcon(patientAppointmentHistoryResponseModel.nextAction),
iconColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction),
iconSize: 15.h,
))
.toShimmer2(isShow: isLoading),
),
SizedBox(width: 8.h),
Expanded(
flex: 1,
child: Container(
height: (isFoldable || isTablet) ? 50.h : 40.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.textColor, borderRadius: 10.h),
child: Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.whiteColor,
width: 24.w,
height: 24.h,
fit: BoxFit.contain,
return Row(
children: [
Expanded(
flex: 6,
child: (AppointmentType.isArrived(patientAppointmentHistoryResponseModel)
? _getArrivedButton(context)
: CustomButton(
text: AppointmentType.getNextActionText(patientAppointmentHistoryResponseModel.nextAction),
onPressed: () => _goToDetails(context),
backgroundColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15),
borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01),
textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction),
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
icon: AppointmentType.getNextActionIcon(patientAppointmentHistoryResponseModel.nextAction),
iconColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction),
iconSize: 15.h,
))
.toShimmer2(isShow: isLoading),
),
SizedBox(width: 8.h),
Expanded(
flex: 1,
child: Container(
height: (isFoldable || isTablet) ? 50.h : 40.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.textColor, borderRadius: 10.h),
child: Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.whiteColor,
width: 24.w,
height: 24.h,
fit: BoxFit.contain,
),
),
),
).toShimmer2(isShow: isLoading).onPress(() {
_goToDetails(context);
}),
),
],
);
).toShimmer2(isShow: isLoading).onPress(() {
_goToDetails(context);
}),
),
],
);
}
}
Widget _getArrivedButton(BuildContext context) {
@ -340,7 +367,7 @@ class AppointmentCard extends StatelessWidget {
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
@ -358,7 +385,7 @@ class AppointmentCard extends StatelessWidget {
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 14.f,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),

@ -36,6 +36,7 @@ class AppointmentCheckinBottomSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
locationUtils = getIt.get<LocationUtils>();
appState = getIt.get<AppState>();
FlutterNfcKit.nfcAvailability.then((value) {
_supportsNFC = (value == NFCAvailability.available);

@ -89,8 +89,8 @@ class AppointmentDoctorCard extends StatelessWidget {
runSpacing: 6.h,
children: [
AppCustomChipWidget(
labelText: (patientAppointmentHistoryResponseModel.clinicName!.length > 15
? '${patientAppointmentHistoryResponseModel.clinicName!.substring(0, 12)}...'
labelText: (patientAppointmentHistoryResponseModel.clinicName!.length > 20
? '${patientAppointmentHistoryResponseModel.clinicName!.substring(0, 20)}...'
: patientAppointmentHistoryResponseModel.clinicName!),
labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w),
),

@ -458,8 +458,8 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
),
SizedBox(width: 16.w),
CustomButton(
height: 40.h,
width: 40.w,
height: 42.h,
width: 42.w,
text: "",
onPressed: () => context.navigateWithName(AppRoutes.userInfoSelection),
icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward,

@ -18,6 +18,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immed
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart';
import 'package:hmg_patient_app_new/services/livecare_permission_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -208,7 +209,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
? CustomButton(
text: LocaleKeys.confirmLiveCare.tr(context: context),
onPressed: () async {
await askVideoCallPermission().then((val) async {
await askVideoCallPermission(context).then((val) async {
if (val) {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingLiveCareRequest.tr(context: context));
@ -271,31 +272,32 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
: CustomButton(
text: LocaleKeys.payNow.tr(context: context),
onPressed: () async {
await askVideoCallPermission().then((val) {
await askVideoCallPermission(context).then((val) {
if (val) {
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePaymentPage(),
),
);
} else {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
child: Utils.getWarningWidget(
loadingText: LocaleKeys.liveCarePermissionsMessage.tr(context: context),
isShowActionButtons: true,
onCancelTap: () {
Navigator.pop(context);
},
onConfirmTap: () async {
openAppSettings();
}),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
// else {
// showCommonBottomSheetWithoutHeight(
// title: LocaleKeys.notice.tr(context: context),
// context,
// child: Utils.getWarningWidget(
// loadingText: LocaleKeys.liveCarePermissionsMessage.tr(context: context),
// isShowActionButtons: true,
// onCancelTap: () {
// Navigator.pop(context);
// },
// onConfirmTap: () async {
// openAppSettings();
// }),
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// );
// }
});
},
backgroundColor: AppColors.infoColor,
@ -318,35 +320,111 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
);
}
Future<bool> askVideoCallPermission() async {
bool hasPermission = false;
if (Platform.isIOS) {
Map<Permission, PermissionStatus> statuses = await [Permission.camera, Permission.microphone].request();
Future<bool> askVideoCallPermission(BuildContext context) async {
final statuses = await LiveCarePermissionService.instance.requestCameraMicAndNotification(context);
// If service returned nothing (error), treat as not granted
if (statuses.isEmpty) return false;
bool cameraGranted = statuses[Permission.camera]?.isGranted ?? false;
bool micGranted = statuses[Permission.microphone]?.isGranted ?? false;
bool notifGranted = statuses[Permission.notification]?.isGranted ?? false;
if (statuses[Permission.camera] == PermissionStatus.granted && statuses[Permission.microphone] == PermissionStatus.granted) {
// Camera permission granted
hasPermission = true;
} else {
hasPermission = false;
}
} else {
Map<Permission, PermissionStatus> statuses = await [Permission.camera, Permission.microphone, Permission.notification].request();
// If all required permissions are already granted
if (cameraGranted && micGranted && notifGranted) return true;
if (statuses[Permission.camera] == PermissionStatus.granted && statuses[Permission.microphone] == PermissionStatus.granted && statuses[Permission.notification] == PermissionStatus.granted) {
// Camera permission granted
hasPermission = true;
} else {
hasPermission = false;
}
// Collect only the missing permissions
final missing = <Permission>[];
if (!cameraGranted) missing.add(Permission.camera);
if (!micGranted) missing.add(Permission.microphone);
if (!notifGranted) missing.add(Permission.notification);
// If any of the missing permissions are permanently denied/restricted -> open settings (single dialog)
final permanent = missing.where((p) => (statuses[p]?.isPermanentlyDenied ?? false) || (statuses[p]?.isRestricted ?? false)).toList();
if (permanent.isNotEmpty) {
final names = permanent.map((p) => LiveCarePermissionService.instance.friendlyName(p)).join(' and ');
final message = permanent.length == 1 ? '$names permission is permanently denied. Open app settings to allow it.' : '$names permissions are permanently denied. Open app settings to allow them.';
await LiveCarePermissionService.instance.showOpenSettingsDialog(
context,
title: "Permissions Required",
message: message,
);
return false;
}
return hasPermission;
// Show one combined retry dialog for all missing permissions
final missingNames = missing.map((p) => LiveCarePermissionService.instance.friendlyName(p)).join(' and ');
final retry = await showDialog<bool>(
context: context,
builder: (c) => AlertDialog(
title: const Text('Permissions required'),
content: Text('$missingNames permission${missing.length > 1 ? 's are' : ' is'} required to start the video call. Retry now?'),
actions: [
TextButton(onPressed: () => Navigator.of(c).pop(false), child: const Text('Cancel')),
TextButton(onPressed: () => Navigator.of(c).pop(true), child: const Text('Retry')),
],
),
);
if (retry != true) return false;
// if (!(await Permission.camera.request().isGranted) || !(await Permission.microphone.request().isGranted)) {
// return false;
// }
// Request only the missing permissions
final newStatuses = <Permission, PermissionStatus>{};
for (final p in missing) {
newStatuses[p] = await p.request();
}
// Update grant state (preserve previously granted ones)
cameraGranted = newStatuses[Permission.camera]?.isGranted ?? cameraGranted;
micGranted = newStatuses[Permission.microphone]?.isGranted ?? micGranted;
notifGranted = newStatuses[Permission.notification]?.isGranted ?? notifGranted;
// If any requested permission is now permanently denied -> open settings
final newlyPermanent = missing.where((p) => (newStatuses[p]?.isPermanentlyDenied ?? false) || (newStatuses[p]?.isRestricted ?? false)).toList();
if (newlyPermanent.isNotEmpty) {
final names = newlyPermanent.map((p) => LiveCarePermissionService.instance.friendlyName(p)).join(' and ');
final message =
newlyPermanent.length == 1 ? '$names permission is permanently denied. Open app settings to allow it.' : '$names permissions are permanently denied. Open app settings to allow them.';
await LiveCarePermissionService.instance.showOpenSettingsDialog(
context,
title: "Permissions Required",
message: message,
);
return false;
}
return cameraGranted && micGranted && notifGranted;
}
// Future<bool> askVideoCallPermission() async {
// bool hasPermission = false;
// if (Platform.isIOS) {
// Map<Permission, PermissionStatus> statuses = await [Permission.camera, Permission.microphone].request();
//
// if (statuses[Permission.camera] == PermissionStatus.granted && statuses[Permission.microphone] == PermissionStatus.granted) {
// // Camera permission granted
// hasPermission = true;
// } else {
// hasPermission = false;
// }
// } else {
// Map<Permission, PermissionStatus> statuses = await [Permission.camera, Permission.microphone, Permission.notification].request();
//
// if (statuses[Permission.camera] == PermissionStatus.granted && statuses[Permission.microphone] == PermissionStatus.granted && statuses[Permission.notification] == PermissionStatus.granted) {
// // Camera permission granted
// hasPermission = true;
// } else {
// hasPermission = false;
// }
// }
//
// return hasPermission;
//
// // if (!(await Permission.camera.request().isGranted) || !(await Permission.microphone.request().isGranted)) {
// // return false;
// // }
// }
String getLiveCareType(BuildContext context, int callType) {
switch (callType) {
case 1:

@ -432,6 +432,7 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
),
);
} else {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Unknown error occurred..."),
@ -441,6 +442,7 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
);
}
} else {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr()),

@ -62,25 +62,22 @@ class ErOnlineCheckinPaymentDetailsPage extends StatelessWidget {
SizedBox(height: 12.h),
Wrap(
direction: Axis.horizontal,
spacing: 6.w,
spacing: 4.w,
runSpacing: 6.h,
children: [
AppCustomChipWidget(
labelText: "File No.: ${appState.getAuthenticatedUser()!.patientId!.toString()}",
labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w),
labelText: "${LocaleKeys.fileno.tr(context: context)}.: ${appState.getAuthenticatedUser()!.patientId!.toString()}",
),
AppCustomChipWidget(
labelText: LocaleKeys.erClinic.tr(),
labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w),
),
AppCustomChipWidget(
labelText: emergencyServicesViewModel.selectedHospital!.name,
labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w),
),
AppCustomChipWidget(
icon: AppAssets.calendar,
labelText: DateUtil.formatDateToDate(DateTime.now(), false),
labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w),
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 8.w),
),
],
),

@ -337,8 +337,6 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
}
startApplePay() async {
// showCommonBottomSheet(context,
// child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false);
LoaderBottomSheet.showLoader();
transID = Utils.getAdvancePaymentTransID(
emergencyServicesViewModel.selectedHospital!.iD,
@ -405,8 +403,8 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest,
currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
onFailed: (failureResult) async {
LoaderBottomSheet.hideLoader();
log("failureResult: ${failureResult.message.toString()}");
Navigator.of(context).pop();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: failureResult.message.toString()),
@ -416,6 +414,7 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
);
},
onSucceeded: (successResult) async {
LoaderBottomSheet.hideLoader();
log("successResult: ${successResult.responseMessage.toString()}");
selectedPaymentMethod = successResult.paymentOption ?? "VISA";
checkPaymentStatus();

@ -316,6 +316,8 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
context,
child: Utils.getSuccessWidget(loadingText: "Payment Successful!"),
callBackFunc: () {
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
Navigator.of(context).pop();
Navigator.of(context).pop();
},

@ -652,7 +652,7 @@ class _LandingPageState extends State<LandingPage> {
],
).paddingSymmetrical(24.w, 0.h),
SizedBox(
height: 411.h,
height: 431.h,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: LandingPageData.getServiceCardsList.length,

@ -65,11 +65,11 @@ class SmallServiceCard extends StatelessWidget {
Utils.buildSvgWithAssets(icon: icon, iconColor: iconColor, width: 32.h, height: 32.h),
SizedBox(height: 6.h),
if (isFoldable || isTablet) ...[
title.tr(context: context).toText11(color: textColor, weight: FontWeight.w500, isCenter: true, letterSpacing: -0.2),
subtitle.tr(context: context).toText11(color: textColor, weight: FontWeight.w500, isCenter: true, letterSpacing: -0.2),
title.tr(context: context).toText10(color: textColor, weight: FontWeight.w500, isCenter: true, letterSpacing: -0.2, maxlines: 1),
subtitle.tr(context: context).toText10(color: textColor, weight: FontWeight.w500, isCenter: true, letterSpacing: -0.2, maxlines: 1),
] else ...[
title.tr(context: context).toText12(color: textColor, fontWeight: FontWeight.w500, isCenter: true, letterSpacing: -0.2),
subtitle.tr(context: context).toText12(color: textColor, fontWeight: FontWeight.w500, isCenter: true, letterSpacing: -0.2),
title.tr(context: context).toText12(color: textColor, fontWeight: FontWeight.w500, isCenter: true, letterSpacing: -0.2, maxLine: 1),
subtitle.tr(context: context).toText12(color: textColor, fontWeight: FontWeight.w500, isCenter: true, letterSpacing: -0.2, maxLine: 1),
]
],
),

@ -153,7 +153,7 @@ class _AncillaryOrderDetailsListState extends State<AncillaryOrderDetailsList> {
),
),
),
if (orderData != null) _buildStickyPaymentButton(orderData),
if (orderData != null && !viewModel.isAncillaryDetailsProceduresLoading) _buildStickyPaymentButton(orderData),
],
);
}),
@ -164,6 +164,7 @@ class _AncillaryOrderDetailsListState extends State<AncillaryOrderDetailsList> {
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: 3,
itemBuilder: (context, index) {
return AncillaryOrderCard(

@ -215,9 +215,7 @@ class AncillaryOrderCard extends StatelessWidget {
).toShimmer2(isShow: isLoading),
],
),
SizedBox(height: 12.h),
SizedBox(height: 6.h),
// Action Buttons
Row(
children: [
@ -236,7 +234,7 @@ class AncillaryOrderCard extends StatelessWidget {
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 14.f,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w500,
borderRadius: 10.r,
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
@ -260,7 +258,7 @@ class AncillaryOrderCard extends StatelessWidget {
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 14.f,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w500,
borderRadius: 10.r,
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),

@ -0,0 +1,92 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:permission_handler/permission_handler.dart';
class LiveCarePermissionService {
LiveCarePermissionService._();
static final LiveCarePermissionService instance = LiveCarePermissionService._();
// Guard to prevent concurrent permission requests
Completer<Map<Permission, PermissionStatus>>? _ongoingRequest;
/// Requests camera, microphone and notification permissions together and
/// returns the final map of permission statuses.
/// This method does NOT show any dialogs UI decisions must be done by caller.
Future<Map<Permission, PermissionStatus>> requestCameraMicAndNotification(BuildContext context) async {
if (_ongoingRequest != null) {
return _ongoingRequest!.future;
}
_ongoingRequest = Completer<Map<Permission, PermissionStatus>>();
final completer = _ongoingRequest!;
final permissions = <Permission>[
Permission.camera,
Permission.microphone,
Permission.notification,
];
try {
final statuses = await permissions.request();
// Ensure all requested keys exist in the returned map (some platforms may omit)
final result = <Permission, PermissionStatus>{};
for (final p in permissions) {
result[p] = statuses[p] ?? await p.status;
}
if (!completer.isCompleted) completer.complete(result);
return completer.future;
} catch (e) {
if (!completer.isCompleted) completer.complete(<Permission, PermissionStatus>{});
return completer.future;
} finally {
_ongoingRequest = null;
}
}
/// Show an "open settings" dialog (kept as helper for the UI layer).
Future<void> showOpenSettingsDialog(BuildContext context, {required String title, required String message}) async {
return showCommonBottomSheetWithoutHeight(
title: title,
context,
child: Utils.getWarningWidget(
loadingText: message,
isShowActionButtons: true,
onCancelTap: () {
Navigator.of(context).pop();
},
onConfirmTap: () async {
openAppSettings();
Navigator.of(context).pop();
}),
isFullScreen: false,
isCloseButtonVisible: true,
callBackFunc: () {},
);
// return showDialog<void>(
// context: context,
// builder: (c) => ConfirmDialog(
// title: '$title Permission',
// message: message,
// onTap: () {
// openAppSettings();
// Navigator.of(c).pop();
// },
// okTitle: LocaleKeys.ok.tr(context: c),
// ),
// );
}
String friendlyName(Permission permission) {
if (permission == Permission.camera) return 'Camera';
if (permission == Permission.microphone) return 'Microphone';
if (permission == Permission.notification) return 'Notifications';
return permission.toString();
}
}

@ -56,7 +56,7 @@ class _SplashScreenState extends State<SplashPage> {
await notificationService.initialize(onNotificationClick: (payload) {
// Handle notification click here
});
await ZoomService().initializeZoomSDK();
ZoomService().initializeZoomSDK();
if (isAppOpenedFromCall) {
navigateToTeleConsult();
} else {

Loading…
Cancel
Save