# Conflicts:
#	assets/langs/ar-SA.json
#	assets/langs/en-US.json
#	lib/generated/locale_keys.g.dart
#	lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart
dev_sultan
Sultan khan 13 hours ago
commit caa9da2cce

@ -42,8 +42,8 @@
tools:node="remove" />
<!-- Gallery/Photo permissions for Android 13+ (API 33+) -->
<!-- <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" tools:node="remove" />-->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" tools:node="remove" />
<!-- <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />-->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" tools:node="remove" />
<!-- Gallery/Photo permissions for Android 12 and below -->

@ -1289,7 +1289,7 @@
"quickLinks": "روابط سريعة",
"viewMedicalFileLandingPage": "عرض الملف الطبي",
"immediateLiveCareRequest": "طلب لايف كير فوري",
"yourTurnIsAfterPatients": "دورك بعد {count} مريض. يرجى الانتظار حتى يحين دورك، وسيناديك الطبيب قريباً.",
"yourTurnIsAfterPatients": "أمامك {count} مريض. سيقوم الطبيب بالتواصل معكً.",
"dontHaveHHCOrders": "ليس لديك أي أوامر رعاية صحية منزلية حتى الآن.",
"hhcOrders": "أوامر الرعاية الصحية المنزلية",
"requestedServices": "الخدمات المطلوبة",
@ -1826,6 +1826,7 @@
"selectFamilyFile": "اختيار ",
"ancillaryOrdersListNew": "طلبات غير مكتملة",
"medicalReportNew": "التقارير الطبية",
"livecarePendingReuqestNew": "انتظار الطبيب"
"medicinesAnalysis": "تحليل الأدوية",
"importantWarnings": "تحذيرات مهمة",
"medicineInteractions": "تفاعلات الأدوية",

@ -1279,7 +1279,7 @@
"quickLinks": "Quick Links",
"viewMedicalFileLandingPage": "View medical file",
"immediateLiveCareRequest": "Immediate LiveCare Request",
"yourTurnIsAfterPatients": "Your turn is after {count} patients. Please wait for your turn, The doctor will call you shortly.",
"yourTurnIsAfterPatients": "There are {count} patients ahead of you. The doctor will call you shortly.",
"dontHaveHHCOrders": "You don't have any Home Health Care orders yet.",
"hhcOrders": "HHC Orders",
"requestedServices": "Requested Services",
@ -1816,6 +1816,7 @@
"selectFamilyFile": "Select",
"ancillaryOrdersListNew": "Ancillary Orders List",
"medicalReportNew": "Medical Report",
"livecarePendingReuqestNew": "Live Care Pending Request"
"medicinesAnalysis": "Medicines Analysis",
"importantWarnings": "Important Warnings",
"medicineInteractions": "Medicine Interactions",

@ -4,7 +4,7 @@ import 'package:hmg_patient_app_new/core/enums.dart';
class ApiConsts {
static const maxSmallScreen = 660;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT

@ -53,8 +53,8 @@ class ImmediateLiveCareViewModel extends ChangeNotifier {
late AppState _appState;
Duration countdownDuration = Duration(minutes: 1, seconds: 0);
late ValueNotifier<Duration> durationNotifier;
late Timer timer;
ValueNotifier<Duration> durationNotifier = ValueNotifier<Duration>(Duration(minutes: 1, seconds: 0));
Timer? timer;
bool isShowNotEligibleDialog = false;
String notEligibleErrorMsg = "";
@ -264,16 +264,20 @@ class ImmediateLiveCareViewModel extends ChangeNotifier {
patientLiveCareHistoryList = apiResponse.data!;
if (patientLiveCareHistoryList.isNotEmpty) {
if (patientLiveCareHistoryList[0].callStatus! < 4) {
countdownDuration = Duration(seconds: 0);
patientHasPendingLiveCareRequest = true;
countdownDuration = Duration(minutes: patientLiveCareHistoryList.first.watingtimeInteger!, seconds: 0);
durationNotifier = ValueNotifier<Duration>(countdownDuration);
startTimer();
durationNotifier.value = countdownDuration;
// Start timer if not already running
if (timer == null || !timer!.isActive) {
startTimer();
}
} else {
patientHasPendingLiveCareRequest = false;
stopTimer(); // Stop timer if call status >= 4
}
} else {
patientHasPendingLiveCareRequest = false;
stopTimer(); // Stop timer if no history
}
notifyListeners();
if (onSuccess != null) {
@ -312,18 +316,35 @@ class ImmediateLiveCareViewModel extends ChangeNotifier {
}
void startTimer() {
timer = Timer.periodic(const Duration(seconds: 1), (_) => addTime());
notifyListeners();
// Only start timer if it's not already active
if (timer == null || !timer!.isActive) {
timer = Timer.periodic(const Duration(seconds: 1), (_) {
addTime();
});
}
}
void addTime() {
final seconds = durationNotifier.value.inSeconds - 1;
if (seconds < 0) {
final currentSeconds = durationNotifier.value.inSeconds;
if (currentSeconds <= 0) {
// Timer has reached zero, stop it
timer?.cancel();
// Handle end of timer here
// showEndMessage();
durationNotifier.value = Duration.zero;
} else {
durationNotifier.value = Duration(seconds: seconds);
// Decrease by 1 second and continue
durationNotifier.value = Duration(seconds: currentSeconds - 1);
}
}
void stopTimer() {
timer?.cancel();
}
@override
void dispose() {
timer?.cancel();
durationNotifier.dispose();
super.dispose();
}
}

@ -1818,15 +1818,6 @@ abstract class LocaleKeys {
static const selectFamilyFile = 'selectFamilyFile';
static const ancillaryOrdersListNew = 'ancillaryOrdersListNew';
static const medicalReportNew = 'medicalReportNew';
static const medicinesAnalysis = 'medicinesAnalysis';
static const importantWarnings = 'importantWarnings';
static const medicineInteractions = 'medicineInteractions';
static const followUpNeeded = 'followUpNeeded';
static const benefit = 'benefit';
static const commonSideEffects = 'commonSideEffects';
static const seriousWarnings = 'seriousWarnings';
static const storage = 'storage';
static const aiDisclaimerPrescription = 'aiDisclaimerPrescription';
static const generateAiAnalysisPrescription = 'generateAiAnalysisPrescription';
static const livecarePendingReuqestNew = 'livecarePendingReuqestNew';
}

@ -240,7 +240,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
],
).paddingSymmetrical(24.h, 0.h),
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" || immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0")
// (true)
// (true)
? CustomButton(
text: LocaleKeys.confirmLiveCare.tr(context: context),
onPressed: () async {
@ -357,24 +357,25 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
}
Future<bool> askVideoCallPermission(BuildContext context) async {
final statuses = await LiveCarePermissionService.instance.requestCameraMicAndNotification(context);
final statuses = await LiveCarePermissionService.instance.requestCameraMicAndNotification(false, 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 cameraGranted = statuses[Permission.camera]?.isGranted ?? false;
// bool micGranted = statuses[Permission.microphone]?.isGranted ?? false;
bool notifGranted = statuses[Permission.notification]?.isGranted ?? false;
// bool alertWindowGranted = Platform.isAndroid ? (statuses[Permission.systemAlertWindow]?.isGranted ?? false) : true;
bool alertWindowGranted = true;
// If all required permissions are already granted
if (cameraGranted && micGranted && notifGranted && alertWindowGranted) return true;
// if (cameraGranted && micGranted && notifGranted && alertWindowGranted) return true;
if (notifGranted && alertWindowGranted) return true;
// Collect only the missing permissions
final missing = <Permission>[];
if (!cameraGranted) missing.add(Permission.camera);
if (!micGranted) missing.add(Permission.microphone);
// if (!cameraGranted) missing.add(Permission.camera);
// if (!micGranted) missing.add(Permission.microphone);
if (!notifGranted) missing.add(Permission.notification);
if (Platform.isAndroid && !alertWindowGranted) missing.add(Permission.systemAlertWindow);
@ -415,8 +416,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
}
// Update grant state (preserve previously granted ones)
cameraGranted = newStatuses[Permission.camera]?.isGranted ?? cameraGranted;
micGranted = newStatuses[Permission.microphone]?.isGranted ?? micGranted;
// cameraGranted = newStatuses[Permission.camera]?.isGranted ?? cameraGranted;
// micGranted = newStatuses[Permission.microphone]?.isGranted ?? micGranted;
notifGranted = newStatuses[Permission.notification]?.isGranted ?? notifGranted;
alertWindowGranted = newStatuses[Permission.systemAlertWindow]?.isGranted ?? alertWindowGranted;
@ -435,7 +436,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
}
// return cameraGranted && micGranted && notifGranted && alertWindowGranted;
return cameraGranted && micGranted && notifGranted;
// return cameraGranted && micGranted && notifGranted;
return notifGranted;
}
// Future<bool> askVideoCallPermission() async {

@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:ui' as ui;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -33,24 +32,19 @@ class _ImmediateLiveCarePendingRequestPageState extends State<ImmediateLiveCareP
late AppState appState;
// static Duration countdownDuration = Duration(minutes: 1, seconds: 0);
// late ValueNotifier<Duration> durationNotifier = ValueNotifier<Duration>(immediateLiveCareViewModel.countdownDuration);
// Timer? timer;
@override
void initState() {
super.initState();
scheduleMicrotask(() {
// countdownDuration = Duration(minutes: immediateLiveCareViewModel.patientLiveCareHistoryList[0].watingtimeInteger!, seconds: 0);
// durationNotifier = ValueNotifier<Duration>(immediateLiveCareViewModel.countdownDuration);
// startTimer();
});
}
@override
void dispose() {
// timer?.cancel();
// durationNotifier.dispose();
// Cancel the timer when leaving the page
try {
immediateLiveCareViewModel.stopTimer();
} catch (e) {
// Handle case where viewModel might not be initialized
}
super.dispose();
}
@ -65,7 +59,7 @@ class _ImmediateLiveCarePendingRequestPageState extends State<ImmediateLiveCareP
children: [
Expanded(
child: CollapsingListView(
title: LocaleKeys.liveCarePendingRequest.tr(),
title: LocaleKeys.livecareAppo.tr(),
child: Padding(
padding: EdgeInsets.all(24.0),
child: Column(
@ -147,7 +141,7 @@ class _ImmediateLiveCarePendingRequestPageState extends State<ImmediateLiveCareP
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
buildTime(duration),
Directionality(textDirection: ui.TextDirection.ltr, child: buildTime(duration)),
],
);
},
@ -176,7 +170,7 @@ class _ImmediateLiveCarePendingRequestPageState extends State<ImmediateLiveCareP
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 50.h,
icon: AppAssets.livechatMessage,
iconColor: AppColors.whiteColor,
iconColor: !Utils.havePrivilege(118) ? AppColors.textColor : AppColors.whiteColor,
iconSize: 21.h,
),
SizedBox(
@ -186,8 +180,8 @@ class _ImmediateLiveCarePendingRequestPageState extends State<ImmediateLiveCareP
mainAxisAlignment: MainAxisAlignment.center,
children: [
LocaleKeys.oR.tr().toText19(
color: AppColors.textColor,
),
color: AppColors.textColor,
),
],
),
SizedBox(
@ -208,7 +202,7 @@ class _ImmediateLiveCarePendingRequestPageState extends State<ImmediateLiveCareP
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 50.h,
icon: AppAssets.call_fill,
iconColor: AppColors.whiteColor,
iconColor: AppColors.primaryRedColor,
iconSize: 21.h,
)
],
@ -216,140 +210,139 @@ class _ImmediateLiveCarePendingRequestPageState extends State<ImmediateLiveCareP
),
),
),
],
);
}),
);
}
// void startTimer() {
// // timer = Timer.periodic(const Duration(seconds: 1), (_) => addTime());
// // setState(() {});
// }
//
// void addTime() {
// final seconds = durationNotifier.value.inSeconds - 1;
// if (seconds < 0) {
// timer?.cancel();
// // Handle end of timer here
// // showEndMessage();
// } else {
// durationNotifier.value = Duration(seconds: seconds);
// }
// }
//
// Future<bool> _onWillPop() async {
// timer?.cancel();
// Navigator.of(context).pop();
// return true;
// }
//
// Widget buildTime(Duration duration) {
// String twoDigits(int n) => n.toString().padLeft(2, '0');
// final hours = twoDigits(duration.inHours);
// final minutes = twoDigits(duration.inMinutes.remainder(60));
// final seconds = twoDigits(duration.inSeconds.remainder(60));
//
// return Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// buildTimeColumn(hours, "Hours".needTranslation),
// buildTimeColumn(minutes, "Mins".needTranslation),
// buildTimeColumn(seconds, "Secs".needTranslation, isLast: true),
// ],
// );
// }
//
// Widget buildTimeColumn(String time, String label, {bool isLast = false}) {
// return Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Row(
// children: [
// buildDigit(time[0]),
// buildDigit(time[1]),
// if (!isLast) buildTimeSeparator(),
// ],
// ),
// buildLabel(label),
// ],
// );
// }
//
// Widget buildDigit(String digit) {
// return Container(
// padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
// // margin: const EdgeInsets.symmetric(horizontal: 2),
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(8),
// ),
// child: ClipRect(
// child: AnimatedSwitcher(
// duration: const Duration(milliseconds: 600),
// switchInCurve: Curves.easeOutExpo,
// switchOutCurve: Curves.easeInExpo,
// transitionBuilder: (Widget child, Animation<double> animation) {
// return Stack(
// children: <Widget>[
// SlideTransition(
// position: Tween<Offset>(
// begin: const Offset(0, -1),
// end: const Offset(0, 1),
// ).animate(CurvedAnimation(
// parent: animation,
// curve: Curves.easeOutCubic,
// )),
// child: FadeTransition(
// opacity: animation,
// child: child,
// ),
// ),
// SlideTransition(
// position: Tween<Offset>(
// begin: const Offset(0, -1),
// end: const Offset(0, 0),
// ).animate(CurvedAnimation(
// parent: animation,
// curve: Curves.bounceIn,
// )),
// child: FadeTransition(
// opacity: animation,
// child: child,
// ),
// ),
// ],
// );
// },
// child: Text(
// digit,
// key: ValueKey<String>(digit),
// style: TextStyle(
// fontWeight: FontWeight.bold,
// color: Colors.black,
// fontSize: 20.f,
// ),
// ),
// ),
// ),
// );
// }
//
// Widget buildLabel(String label) {
// return label.toText14(isBold: true);
// }
//
// Widget buildTimeSeparator() {
// return const Padding(
// padding: EdgeInsets.symmetric(horizontal: 2.0),
// child: Text(
// ":",
// style: TextStyle(
// color: Colors.black,
// fontSize: 20,
// ),
// ),
// );
// }
// void startTimer() {
// // timer = Timer.periodic(const Duration(seconds: 1), (_) => addTime());
// // setState(() {});
// }
//
// void addTime() {
// final seconds = durationNotifier.value.inSeconds - 1;
// if (seconds < 0) {
// timer?.cancel();
// // Handle end of timer here
// // showEndMessage();
// } else {
// durationNotifier.value = Duration(seconds: seconds);
// }
// }
//
// Future<bool> _onWillPop() async {
// timer?.cancel();
// Navigator.of(context).pop();
// return true;
// }
//
// Widget buildTime(Duration duration) {
// String twoDigits(int n) => n.toString().padLeft(2, '0');
// final hours = twoDigits(duration.inHours);
// final minutes = twoDigits(duration.inMinutes.remainder(60));
// final seconds = twoDigits(duration.inSeconds.remainder(60));
//
// return Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// buildTimeColumn(hours, "Hours".needTranslation),
// buildTimeColumn(minutes, "Mins".needTranslation),
// buildTimeColumn(seconds, "Secs".needTranslation, isLast: true),
// ],
// );
// }
//
// Widget buildTimeColumn(String time, String label, {bool isLast = false}) {
// return Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Row(
// children: [
// buildDigit(time[0]),
// buildDigit(time[1]),
// if (!isLast) buildTimeSeparator(),
// ],
// ),
// buildLabel(label),
// ],
// );
// }
//
// Widget buildDigit(String digit) {
// return Container(
// padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
// // margin: const EdgeInsets.symmetric(horizontal: 2),
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(8),
// ),
// child: ClipRect(
// child: AnimatedSwitcher(
// duration: const Duration(milliseconds: 600),
// switchInCurve: Curves.easeOutExpo,
// switchOutCurve: Curves.easeInExpo,
// transitionBuilder: (Widget child, Animation<double> animation) {
// return Stack(
// children: <Widget>[
// SlideTransition(
// position: Tween<Offset>(
// begin: const Offset(0, -1),
// end: const Offset(0, 1),
// ).animate(CurvedAnimation(
// parent: animation,
// curve: Curves.easeOutCubic,
// )),
// child: FadeTransition(
// opacity: animation,
// child: child,
// ),
// ),
// SlideTransition(
// position: Tween<Offset>(
// begin: const Offset(0, -1),
// end: const Offset(0, 0),
// ).animate(CurvedAnimation(
// parent: animation,
// curve: Curves.bounceIn,
// )),
// child: FadeTransition(
// opacity: animation,
// child: child,
// ),
// ),
// ],
// );
// },
// child: Text(
// digit,
// key: ValueKey<String>(digit),
// style: TextStyle(
// fontWeight: FontWeight.bold,
// color: Colors.black,
// fontSize: 20.f,
// ),
// ),
// ),
// ),
// );
// }
//
// Widget buildLabel(String label) {
// return label.toText14(isBold: true);
// }
//
// Widget buildTimeSeparator() {
// return const Padding(
// padding: EdgeInsets.symmetric(horizontal: 2.0),
// child: Text(
// ":",
// style: TextStyle(
// color: Colors.black,
// fontSize: 20,
// ),
// ),
// );
// }
}

@ -135,13 +135,13 @@ class _ProfilePictureWidgetState extends State<ProfilePictureWidget> {
print('✅ Profile image loaded successfully for user: $currentPatientId');
if (mounted) {
_tryCacheExistingImage();
setState(() {}); // Force rebuild to show new data
setState(() {}); // Force rebuild to show new data
}
},
onError: (error) {
print('❌ Error loading profile image: $error');
if (mounted) {
setState(() {}); // Force rebuild to show default avatar
setState(() {}); // Force rebuild to show default avatar
}
},
);
@ -365,16 +365,16 @@ class _ProfilePictureWidgetState extends State<ProfilePictureWidget> {
try {
print('=== Checking gallery permission ===');
// Determine which permission to check based on platform and Android version
Permission galleryPermission;
if (Platform.isIOS) {
galleryPermission = Permission.photos;
} else {
// Android: use photos permission which handles API level differences automatically
galleryPermission = Permission.photos;
// For Android 13+ (API 33+), the Android Photo Picker handles permissions internally
// No need to request READ_MEDIA_IMAGES or READ_EXTERNAL_STORAGE permissions
if (Platform.isAndroid) {
print('✅ Android Photo Picker will handle permissions internally');
return true;
}
// iOS: Check photos permission
Permission galleryPermission = Permission.photos;
// First check current status
PermissionStatus currentStatus = await galleryPermission.status;
print('Current gallery permission status: $currentStatus');
@ -520,9 +520,7 @@ class _ProfilePictureWidgetState extends State<ProfilePictureWidget> {
// Use cached decoded bytes update cache if source data changed
final String? imageData = GetIt.instance<AppState>().getProfileImageData;
final String? currentHash = (imageData != null && imageData.isNotEmpty)
? '${imageData.length}_${imageData.hashCode}'
: null;
final String? currentHash = (imageData != null && imageData.isNotEmpty) ? '${imageData.length}_${imageData.hashCode}' : null;
// Re-decode only if the underlying data actually changed
if (currentHash != null && currentHash != _cachedImageDataHash) {
@ -574,9 +572,7 @@ class _ProfilePictureWidgetState extends State<ProfilePictureWidget> {
builder: (context, profileVm, _) {
// If we already have cached bytes, show the image even while "loading"
// to prevent the shimmerimage blink on page open
final bool showShimmer = profileVm.isProfileImageLoading &&
_cachedImageBytes == null &&
_selectedImage == null;
final bool showShimmer = profileVm.isProfileImageLoading && _cachedImageBytes == null && _selectedImage == null;
return Center(
child: Stack(
@ -644,4 +640,3 @@ class _ProfilePictureWidgetState extends State<ProfilePictureWidget> {
);
}
}

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -21,11 +22,14 @@ import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/utils/jwt.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/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/video_view.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/routes/custom_page_route.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
class CallScreen extends StatefulHookWidget {
const CallScreen({Key? key}) : super(key: key);
@ -88,6 +92,18 @@ class _CallScreenState extends State<CallScreen> {
useEffect(() {
Future<void>.microtask(() async {
final statuses = await LiveCarePermissionService.instance.requestCameraMicAndNotification(true, context);
bool cameraGranted = statuses[Permission.camera]?.isGranted ?? false;
bool micGranted = statuses[Permission.microphone]?.isGranted ?? false;
if (cameraGranted && micGranted) {
} else {
await LiveCarePermissionService.instance.showOpenSettingsDialog(
context,
title: LocaleKeys.permissionsProfile.tr(),
message: LocaleKeys.liveCarePermissions.tr(),
);
}
var token = generateJwt(args.sessionName, args.role);
Utils.removeFromPrefs(CacheConst.isAppOpenedFromCall);
@ -607,32 +623,56 @@ class _CallScreenState extends State<CallScreen> {
}
void onPressAudio() async {
ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf();
if (mySelf != null) {
final audioStatus = mySelf.audioStatus;
if (audioStatus != null) {
var muted = await audioStatus.isMuted();
if (muted) {
await zoom.audioHelper.unMuteAudio(mySelf.userId);
} else {
await zoom.audioHelper.muteAudio(mySelf.userId);
final statuses = await LiveCarePermissionService.instance.requestCameraMicAndNotification(true, context);
bool cameraGranted = statuses[Permission.camera]?.isGranted ?? false;
bool micGranted = statuses[Permission.microphone]?.isGranted ?? false;
if (cameraGranted && micGranted) {
ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf();
if (mySelf != null) {
final audioStatus = mySelf.audioStatus;
if (audioStatus != null) {
var muted = await audioStatus.isMuted();
if (muted) {
await zoom.audioHelper.unMuteAudio(mySelf.userId);
} else {
await zoom.audioHelper.muteAudio(mySelf.userId);
}
}
}
} else {
await LiveCarePermissionService.instance.showOpenSettingsDialog(
context,
title: LocaleKeys.permissionsProfile.tr(),
message: LocaleKeys.liveCarePermissions.tr(),
);
}
}
void onPressVideo() async {
ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf();
if (mySelf != null) {
final videoStatus = mySelf.videoStatus;
if (videoStatus != null) {
var videoOn = await videoStatus.isOn();
if (videoOn) {
await zoom.videoHelper.stopVideo();
} else {
await zoom.videoHelper.startVideo();
final statuses = await LiveCarePermissionService.instance.requestCameraMicAndNotification(true, context);
bool cameraGranted = statuses[Permission.camera]?.isGranted ?? false;
bool micGranted = statuses[Permission.microphone]?.isGranted ?? false;
if (cameraGranted && micGranted) {
ZoomVideoSdkUser? mySelf = await zoom.session.getMySelf();
if (mySelf != null) {
final videoStatus = mySelf.videoStatus;
if (videoStatus != null) {
var videoOn = await videoStatus.isOn();
if (videoOn) {
await zoom.videoHelper.stopVideo();
} else {
await zoom.videoHelper.startVideo();
}
}
}
} else {
await LiveCarePermissionService.instance.showOpenSettingsDialog(
context,
title: LocaleKeys.permissionsProfile.tr(),
message: LocaleKeys.liveCarePermissions.tr(),
);
}
}

@ -18,7 +18,7 @@ class LiveCarePermissionService {
/// 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 {
Future<Map<Permission, PermissionStatus>> requestCameraMicAndNotification(bool isCheckCameraMicPermissions, BuildContext context) async {
if (_ongoingRequest != null) {
return _ongoingRequest!.future;
}
@ -26,10 +26,17 @@ class LiveCarePermissionService {
_ongoingRequest = Completer<Map<Permission, PermissionStatus>>();
final completer = _ongoingRequest!;
final permissions = <Permission>[
Permission.camera,
final permissions = isCheckCameraMicPermissions
? <Permission>[
Permission.camera,
Permission.microphone,
Permission.notification,
Permission.notification,
if (Platform.isAndroid) Permission.systemAlertWindow,
]
: <Permission>[
// Permission.camera,
// Permission.microphone,
Permission.notification,
if (Platform.isAndroid) Permission.systemAlertWindow,
];

@ -23,93 +23,42 @@ Widget buildTime(Duration duration, {bool isHomePage = false}) {
Widget buildTimeColumn(String time, String label, {bool isLast = false}) {
return Column(
children: [
Row(
children: [
Column(
children: [
Row(
children: [
buildDigit(time[0]),
SizedBox(width: 2.w),
buildDigit(time[1]),
],
),
SizedBox(height: 4.h),
buildLabel(label),
],
),
if (!isLast) ...[
SizedBox(width: 8.w,),
SizedBox(width: 8.w),
buildTimeSeparator(),
SizedBox(width: 8.w,),
SizedBox(width: 8.w),
],
],
),
],
);
}
Widget buildDigit(String digit) {
return
// Container(
// padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
// // margin: const EdgeInsets.symmetric(horizontal: 2),
// decoration: BoxDecoration(
// color: AppColors.whiteColor,
// borderRadius: BorderRadius.circular(8),
// ),
// child:
ClipRect(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 600),
switchInCurve: Curves.easeOutExpo,
switchOutCurve: Curves.easeInExpo,
transitionBuilder: (Widget child, Animation<double> animation) {
return Stack(
children: <Widget>[
SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, -1),
end: const Offset(0, 1),
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeOutCubic,
)),
child: FadeTransition(
opacity: animation,
child: child,
),
),
SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, -1),
end: const Offset(0, 0),
).animate(CurvedAnimation(
parent: animation,
curve: Curves.bounceIn,
)),
child: FadeTransition(
opacity: animation,
child: child,
),
),
],
);
},
child: Text(
digit,
key: ValueKey<String>(digit),
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 27.f,
),
),
),
);
// );
return Text(
digit,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 27.f,
),
);
}
Widget buildLabel(String label) {
@ -118,12 +67,13 @@ Widget buildLabel(String label) {
Widget buildTimeSeparator() {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 2.0),
padding: EdgeInsets.only(bottom: 16.h),
child: Text(
":",
style: TextStyle(
color: Colors.black,
fontSize: 28.f,
fontWeight: FontWeight.bold,
),
),
);

@ -9,6 +9,7 @@ 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/widget_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'dart:ui' as ui;
class CustomCountryDropdown extends StatefulWidget {
final List<CountryEnum> countryList;
@ -160,9 +161,13 @@ class CustomCountryDropdownState extends State<CustomCountryDropdown> {
AppState appState = getIt.get<AppState>();
RenderBox renderBox = context.findRenderObject() as RenderBox;
Offset offset = renderBox.localToGlobal(Offset.zero);
bool isRtl = appState.getLanguageCode() == "ar";
double leftPosition = isRtl ? offset.dx + 8 + renderBox.size.width - (!widget.isFromBottomSheet ? renderBox.size.width : 60.h) : offset.dx;
double leftPosition;
if (isRtl) {
leftPosition = offset.dx;
} else {
leftPosition = offset.dx;
}
_overlayEntry = OverlayEntry(
builder: (context) => Stack(

@ -10,8 +10,11 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:image_picker/image_picker.dart';
import 'package:image_picker_android/image_picker_android.dart';
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
final ImagePicker picker = ImagePicker();
final ImagePickerPlatform _pickerPlatform = ImagePickerPlatform.instance;
class ImageOptions {
static void showImageOptionsNew(
@ -214,25 +217,47 @@ class ImageOptions {
}
void galleryImageAndroid(Function(String, File) image) async {
XFile? pickedFile = await picker.pickImage(source: ImageSource.gallery, imageQuality: 20);
if (pickedFile == null) return;
// Enable Android Photo Picker if available
if (_pickerPlatform is ImagePickerAndroid) {
(_pickerPlatform as ImagePickerAndroid).useAndroidPhotoPicker = true;
}
try {
final XFile? pickedFile = await _pickerPlatform.getImageFromSource(source: ImageSource.gallery, options: const ImagePickerOptions(imageQuality: 20));
if (pickedFile == null) return;
File _image = File(pickedFile.path);
String fileName = _image.path;
var bytes = File(fileName).readAsBytesSync();
String base64Encode = base64.encode(bytes);
image(base64Encode, _image);
File _image = File(pickedFile.path);
String fileName = _image.path;
var bytes = File(fileName).readAsBytesSync();
String base64Encode = base64.encode(bytes);
image(base64Encode, _image);
} catch (e) {
print('Error picking image: $e');
}
}
void cameraImageAndroid(Function(String, File) image) async {
XFile? pickedFile = await picker.pickImage(source: ImageSource.camera, imageQuality: 20);
if (pickedFile == null) return;
if (_pickerPlatform is ImagePickerAndroid) {
(_pickerPlatform as ImagePickerAndroid).useAndroidPhotoPicker = true;
}
File _image = File(pickedFile.path);
String fileName = _image.path;
var bytes = File(fileName).readAsBytesSync();
String base64Encode = base64.encode(bytes);
image(base64Encode, _image);
try {
final XFile? pickedFile = await _pickerPlatform.getImageFromSource(
source: ImageSource.camera,
options: const ImagePickerOptions(imageQuality: 20),
);
if (pickedFile == null) return;
File _image = File(pickedFile.path);
String fileName = _image.path;
var bytes = File(fileName).readAsBytesSync();
String base64Encode = base64.encode(bytes);
image(base64Encode, _image);
} catch (e) {
print('Error taking picture: $e');
}
}
class _BottomSheet extends StatelessWidget {

@ -90,6 +90,8 @@ dependencies:
flutter_callkit_incoming: ^3.0.0
in_app_update: ^4.2.5
# device_preview: ^1.3.1
image_picker_android: ^0.8.13+16
image_picker_platform_interface: ^2.10.0
location: ^8.0.1
gms_check: ^1.0.4

Loading…
Cancel
Save