* 'master' of http://34.17.182.140/Haroon6138/HMG_Patient_App_New:
  Implemented CloudFlare TurnStile
  Update to stores VersionID 21.6
  updates
  update
  Updates
  hardcoded admin removed
  LAB RESULT CHANGES ADDED
  LAB RESULT CHANGES ADDED
  updates for dental & laser clinics
  Fixes for Apple Watch integration
  updates
  Update to stores VersionID 21.4
pull/344/head^2
Sultan khan 2 weeks ago
commit 0536d8b978

@ -1966,5 +1966,6 @@
"doctorSchedule": "جدول الطبيب", "doctorSchedule": "جدول الطبيب",
"weeklyShift": "الفترات الأسبوعية", "weeklyShift": "الفترات الأسبوعية",
"dayOff": "إجازة", "dayOff": "إجازة",
"notAvailable": "غير متاح" "notAvailable": "غير متاح",
"hide": "إخفاء"
} }

@ -1955,7 +1955,8 @@
"doctorSchedule": "Doctor Schedule", "doctorSchedule": "Doctor Schedule",
"weeklyShift": "Weekly Shift", "weeklyShift": "Weekly Shift",
"dayOff": "Off", "dayOff": "Off",
"notAvailable": "Not Available" "notAvailable": "Not Available",
"hide": "Hide"
} }

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

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

@ -56,7 +56,7 @@ class AppState {
void setIsPayfortHostedPageEnabled(bool value) { void setIsPayfortHostedPageEnabled(bool value) {
isPayfortHostedPageEnabled = value; isPayfortHostedPageEnabled = value;
// isPayfortHostedPageEnabled = true; // isPayfortHostedPageEnabled = false;
} }
int? _superUserID; int? _superUserID;

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

@ -18,7 +18,8 @@ class DateUtil {
final endIndex = date.indexOf(end, startIndex + start.length); final endIndex = date.indexOf(end, startIndex + start.length);
return DateTime.fromMillisecondsSinceEpoch(int.parse( return DateTime.fromMillisecondsSinceEpoch(int.parse(
date.substring(startIndex + start.length, endIndex), date.substring(startIndex + start.length, endIndex),
), isUtc: true).add(Duration(hours: 3)); // ), isUtc: true).add(Duration(hours: 3));
), isUtc: true).toLocal();
} }
static DateTime convertStringToDateSaudiTimezone(String date, int projectId) { static DateTime convertStringToDateSaudiTimezone(String date, int projectId) {

@ -147,6 +147,9 @@ class AuthenticationRepoImp implements AuthenticationRepo {
bool isRegister = false, bool isRegister = false,
bool isFormFamilyFile = false, bool isFormFamilyFile = false,
}) async { }) async {
int isOutKsa = (sendActivationCodeReq.zipCode == '966' || sendActivationCodeReq.zipCode == '+966') ? 0 : 1; int isOutKsa = (sendActivationCodeReq.zipCode == '966' || sendActivationCodeReq.zipCode == '+966') ? 0 : 1;
sendActivationCodeReq.patientOutSA = isOutKsa; sendActivationCodeReq.patientOutSA = isOutKsa;
sendActivationCodeReq.isDentalAllowedBackend = false; 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/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/localauth_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/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/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/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.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 DialogService _dialogService;
final NavigationService _navigationService; final NavigationService _navigationService;
final LocalAuthService _localAuthService; final LocalAuthService _localAuthService;
final TurnstileService _turnstileService;
AuthenticationViewModel({ AuthenticationViewModel({
required AppState appState, required AppState appState,
@ -69,12 +71,14 @@ class AuthenticationViewModel extends ChangeNotifier {
required NavigationService navigationService, required NavigationService navigationService,
required CacheService cacheService, required CacheService cacheService,
required LocalAuthService localAuthService, required LocalAuthService localAuthService,
required TurnstileService turnstileService,
}) : _navigationService = navigationService, }) : _navigationService = navigationService,
_dialogService = dialogService, _dialogService = dialogService,
_errorHandlerService = errorHandlerService, _errorHandlerService = errorHandlerService,
_appState = appState, _appState = appState,
_authenticationRepo = authenticationRepo, _authenticationRepo = authenticationRepo,
_localAuthService = localAuthService {} _localAuthService = localAuthService,
_turnstileService = turnstileService {}
final TextEditingController nationalIdController = TextEditingController(), final TextEditingController nationalIdController = TextEditingController(),
phoneNumberController = TextEditingController(), phoneNumberController = TextEditingController(),
dobController = TextEditingController(), dobController = TextEditingController(),
@ -974,6 +978,15 @@ class AuthenticationViewModel extends ChangeNotifier {
// TODO: GET APP SMS SIGNATURE HERE // TODO: GET APP SMS SIGNATURE HERE
request.sMSSignature = await getSignature(); 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) { if (isNeedLoading) {
LoaderBottomSheet.showLoader(); LoaderBottomSheet.showLoader();
} }
@ -1209,13 +1222,15 @@ class AuthenticationViewModel extends ChangeNotifier {
} }
// Fetch profile image after switching user // Fetch profile image after switching user
await _fetchProfileImage(activation.list!.first.patientId); // await _fetchProfileImage(activation.list!.first.patientId);
_fetchProfileImage(activation.list!.first.patientId);
} else { } else {
activation.list!.first.isParentUser = true; activation.list!.first.isParentUser = true;
_appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: true); _appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: true);
// Fetch profile image after login // Fetch profile image after login
await _fetchProfileImage(activation.list!.first.patientId); // await _fetchProfileImage(activation.list!.first.patientId);
_fetchProfileImage(activation.list!.first.patientId);
} }
activation.list!.first.bloodGroup = activation.patientBloodType; activation.list!.first.bloodGroup = activation.patientBloodType;
activation.list!.first.zipCode = selectedCountrySignup == CountryEnum.others ? '0' : selectedCountrySignup.countryCode; activation.list!.first.zipCode = selectedCountrySignup == CountryEnum.others ? '0' : selectedCountrySignup.countryCode;

@ -30,6 +30,7 @@ class SendActivationRequest {
int? status; int? status;
int? familyRegionID; int? familyRegionID;
bool? isPatientExcluded; bool? isPatientExcluded;
String? turnstileToken;
SendActivationRequest( SendActivationRequest(
{this.patientMobileNumber, {this.patientMobileNumber,
@ -62,7 +63,8 @@ class SendActivationRequest {
this.responseID, this.responseID,
this.status, this.status,
this.familyRegionID, this.familyRegionID,
this.isPatientExcluded}); this.isPatientExcluded,
this.turnstileToken});
SendActivationRequest.fromJson(Map<String, dynamic> json) { SendActivationRequest.fromJson(Map<String, dynamic> json) {
patientMobileNumber = json['PatientMobileNumber']; patientMobileNumber = json['PatientMobileNumber'];
@ -96,6 +98,7 @@ class SendActivationRequest {
status = json['Status']; status = json['Status'];
familyRegionID = json['FamilyRegionID']; familyRegionID = json['FamilyRegionID'];
isPatientExcluded = json['IsPatientExcluded']; isPatientExcluded = json['IsPatientExcluded'];
turnstileToken = json['TurnstileToken'];
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -135,6 +138,9 @@ class SendActivationRequest {
data['Status'] = status; data['Status'] = status;
data['FamilyRegionID'] = familyRegionID; data['FamilyRegionID'] = familyRegionID;
data['IsPatientExcluded'] = isPatientExcluded; data['IsPatientExcluded'] = isPatientExcluded;
if (turnstileToken != null) {
data['TurnstileToken'] = turnstileToken;
}
return data; 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/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/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/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
class ContactUsViewModel extends ChangeNotifier { class ContactUsViewModel extends ChangeNotifier {
@ -308,7 +309,15 @@ class ContactUsViewModel extends ChangeNotifier {
final result = await contactUsRepo.insertCOCItem(requestInsertCOCItem: requestInsertCOCItem, patientSelectedAppointment: patientFeedbackSelectedAppointment); final result = await contactUsRepo.insertCOCItem(requestInsertCOCItem: requestInsertCOCItem, patientSelectedAppointment: patientFeedbackSelectedAppointment);
result.fold( 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) { (apiResponse) {
if (apiResponse.messageStatus == 2) { if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});

@ -29,6 +29,7 @@ abstract class LabRepo {
Future<Either<Failure, GenericApiModel<LabOrdersResponseByAi>>> getPatientAiOverViewLabOrders({required int langId, required dynamic payload}); Future<Either<Failure, GenericApiModel<LabOrdersResponseByAi>>> getPatientAiOverViewLabOrders({required int langId, required dynamic payload});
Future<Either<Failure, GenericApiModel<LabOrderResponseByAi>>> getPatientAiOverViewLabOrder(var payloa); Future<Either<Failure, GenericApiModel<LabOrderResponseByAi>>> getPatientAiOverViewLabOrder(var payloa);
Map<String, List<LabResult>> groupLabResultsByPackageName(List<LabResult> labResults);
} }
class LabRepoImp implements LabRepo { class LabRepoImp implements LabRepo {
@ -37,6 +38,55 @@ class LabRepoImp implements LabRepo {
LabRepoImp({required this.loggerService, required this.apiClient}); LabRepoImp({required this.loggerService, required this.apiClient});
/// Groups a list of LabResult objects by their PackageName
/// Returns a Map where the key is the PackageName and the value is a List of LabResult objects
/// Both the keys and the lists are sorted alphabetically
///
/// Example usage:
/// ```dart
/// List<LabResult> labResults = [/* your lab results */];
/// Map<String, List<LabResult>> groupedResults = labRepo.groupLabResultsByPackageName(labResults);
///
/// // Access results by package name
/// groupedResults.forEach((packageName, results) {
/// print('Package: $packageName has ${results.length} results');
/// });
/// ```
Map<String, List<LabResult>> groupLabResultsByPackageName(List<LabResult> labResults) {
Map<String, List<LabResult>> groupedResults = {};
// Group results by package name
for (var labResult in labResults) {
String packageName = labResult.PackageName ?? 'Unknown Package';
if (groupedResults.containsKey(packageName)) {
// Package name exists, add to existing list
groupedResults[packageName]!.add(labResult);
} else {
// Package name doesn't exist, create new key with new list
groupedResults[packageName] = [labResult];
}
}
// Sort each list alphabetically by description
groupedResults.forEach((key, list) {
list.sort((a, b) {
String descA = (a.description ?? '').toLowerCase();
String descB = (b.description ?? '').toLowerCase();
return descA.compareTo(descB);
});
});
// Sort the map keys alphabetically and return a new sorted map
var sortedKeys = groupedResults.keys.toList()..sort((a, b) => a.toLowerCase().compareTo(b.toLowerCase()));
Map<String, List<LabResult>> sortedGroupedResults = {};
for (var key in sortedKeys) {
sortedGroupedResults[key] = groupedResults[key]!;
}
return sortedGroupedResults;
}
@override @override
Future<Either<Failure, GenericApiModel<List<PatientLabOrdersResponseModel>>>> getPatientLabOrders() async { Future<Either<Failure, GenericApiModel<List<PatientLabOrdersResponseModel>>>> getPatientLabOrders() async {
Map<String, dynamic> mapDevice = {}; Map<String, dynamic> mapDevice = {};

@ -57,7 +57,11 @@ class LabViewModel extends ChangeNotifier {
PatientLabOrdersResponseModel? currentlySelectedPatientOrder; PatientLabOrdersResponseModel? currentlySelectedPatientOrder;
List<LabResult> mainLabResultsByHospitals = []; // Flat list for UI compatibility
List<LabResult> mainLabResultsByHospitalsList = [];
// Grouped map by package name for efficient lookups
Map<String, List<LabResult>> mainLabResultsByPackageName = {};
List<LabResult> mainLabResults = []; List<LabResult> mainLabResults = [];
List<DataPoint> mainGraphPoints = []; List<DataPoint> mainGraphPoints = [];
@ -383,7 +387,8 @@ class LabViewModel extends ChangeNotifier {
Future<void> getPatientLabResultByHospital(PatientLabOrdersResponseModel laborder) async { Future<void> getPatientLabResultByHospital(PatientLabOrdersResponseModel laborder) async {
isLabResultByHospitalLoading = true; isLabResultByHospitalLoading = true;
notifyListeners(); notifyListeners();
mainLabResultsByHospitals.clear(); mainLabResultsByHospitalsList.clear();
mainLabResultsByPackageName.clear();
final result = await labRepo.getPatientLabResultsByHospitals(laborder, Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0"))); final result = await labRepo.getPatientLabResultsByHospitals(laborder, Utils.isVidaPlusProject(int.parse(laborder.projectID ?? "0")));
@ -397,7 +402,9 @@ class LabViewModel extends ChangeNotifier {
isLabResultByHospitalLoading = false; isLabResultByHospitalLoading = false;
if (apiResponse.messageStatus == 2) { if (apiResponse.messageStatus == 2) {
} else if (apiResponse.messageStatus == 1) { } else if (apiResponse.messageStatus == 1) {
mainLabResultsByHospitals = apiResponse.data ?? []; mainLabResultsByHospitalsList = apiResponse.data ?? [];
// Group the results by package name for efficient lookups
mainLabResultsByPackageName = labRepo.groupLabResultsByPackageName(mainLabResultsByHospitalsList);
notifyListeners(); notifyListeners();
} }
}, },
@ -842,6 +849,25 @@ class LabViewModel extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
// /// Finds a matching LabResult from the grouped map by package name
// /// Searches through all packages to find a result matching the test code or description
// LabResult? _findMatchingLabResult(TestDetails testDetail) {
// // Search through all packages in the grouped map
// for (var packageResults in mainLabResultsByPackageName.values) {
// try {
// var matchingResult = packageResults.firstWhere(
// (labResult) => labResult.testCode == testDetail.testCode || labResult.description == testDetail.description,
// );
// return matchingResult;
// } catch (e) {
// // Continue searching in other packages
// continue;
// }
// }
// // Return null if no match found
// return LabResult();
// }
Future<void> getAiOverviewLabOrders({required int langId, required PatientLabOrdersResponseModel labOrder, required String loadingText}) async { Future<void> getAiOverviewLabOrders({required int langId, required PatientLabOrdersResponseModel labOrder, required String loadingText}) async {
isLabAIAnalysisNeedsToBeShown = true; isLabAIAnalysisNeedsToBeShown = true;
notifyListeners(); notifyListeners();
@ -858,7 +884,7 @@ class LabViewModel extends ChangeNotifier {
if (labOrder.testDetails != null && labOrder.testDetails!.isNotEmpty) { if (labOrder.testDetails != null && labOrder.testDetails!.isNotEmpty) {
for (var testDetail in labOrder.testDetails!) { for (var testDetail in labOrder.testDetails!) {
LabResult? matchingResult = LabResult? matchingResult =
mainLabResultsByHospitals.firstWhere((labResult) => labResult.testCode == testDetail.testCode || labResult.description == testDetail.description, orElse: () => LabResult()); mainLabResultsByHospitalsList.firstWhere((labResult) => labResult.testCode == testDetail.testCode || labResult.description == testDetail.description, orElse: () => LabResult());
testResults.add({ testResults.add({
"test_name": testDetail.description ?? "", "test_name": testDetail.description ?? "",
"result_value": matchingResult.resultValue ?? "", "result_value": matchingResult.resultValue ?? "",

@ -28,6 +28,7 @@ class LabResult {
String? packageShortDescription; String? packageShortDescription;
int? testId; int? testId;
String? testShortDescription; String? testShortDescription;
String? PackageName;
LabResult( LabResult(
{this.description, {this.description,
@ -56,6 +57,7 @@ class LabResult {
this.referenceLow, this.referenceLow,
this.testId, this.testId,
this.testShortDescription, this.testShortDescription,
this.PackageName
}); });
LabResult.fromJson(Map<String, dynamic> json) { LabResult.fromJson(Map<String, dynamic> json) {
@ -88,6 +90,7 @@ class LabResult {
resultTypeID = json['ResultTypeID']; resultTypeID = json['ResultTypeID'];
testId = json['TestID']; testId = json['TestID'];
testShortDescription = json['TestShortDescription']; testShortDescription = json['TestShortDescription'];
PackageName = json['PackageName'];
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -118,6 +121,7 @@ class LabResult {
data['ReferenceLow'] = this.referenceLow; data['ReferenceLow'] = this.referenceLow;
data['TestID'] = this.testId; data['TestID'] = this.testId;
data['TestShortDescription'] = this.testShortDescription; data['TestShortDescription'] = this.testShortDescription;
data['PackageName'] = this.PackageName;
return data; return data;
} }

@ -42,7 +42,7 @@ class MedicalFileViewModel extends ChangeNotifier {
bool isSickLeavesSortByClinic = true; bool isSickLeavesSortByClinic = true;
// bool isSickLeavesDataNeedsReloading = true; bool isSickLeavesDataNeedsReloading = true;
List<GetAllergiesResponseModel> patientAllergiesList = []; List<GetAllergiesResponseModel> patientAllergiesList = [];
@ -165,7 +165,7 @@ class MedicalFileViewModel extends ChangeNotifier {
} }
setIsPatientSickLeaveListLoading(bool val) { setIsPatientSickLeaveListLoading(bool val) {
// if (val && isSickLeavesDataNeedsReloading) { if (val && isSickLeavesDataNeedsReloading) {
patientSickLeaveList.clear(); patientSickLeaveList.clear();
patientSickLeavesByClinic.clear(); patientSickLeavesByClinic.clear();
patientSickLeavesByHospital.clear(); patientSickLeavesByHospital.clear();
@ -173,7 +173,7 @@ class MedicalFileViewModel extends ChangeNotifier {
patientSickLeavePDFBase64 = ""; patientSickLeavePDFBase64 = "";
isSickLeavesSortByClinic = true; isSickLeavesSortByClinic = true;
isPatientSickLeaveListLoading = val; isPatientSickLeaveListLoading = val;
// } }
notifyListeners(); notifyListeners();
} }
@ -282,9 +282,9 @@ class MedicalFileViewModel extends ChangeNotifier {
} }
Future<void> getPatientSickLeaveList({Function(dynamic)? onSuccess, Function(String)? onError}) async { Future<void> getPatientSickLeaveList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
// if (!isSickLeavesDataNeedsReloading) { if (!isSickLeavesDataNeedsReloading) {
// return; return;
// } }
patientSickLeaveList.clear(); patientSickLeaveList.clear();
notifyListeners(); notifyListeners();
@ -300,6 +300,7 @@ class MedicalFileViewModel extends ChangeNotifier {
// ), // ),
(failure) async { (failure) async {
isPatientSickLeaveListLoading = false; isPatientSickLeaveListLoading = false;
isSickLeavesDataNeedsReloading = true;
notifyListeners(); notifyListeners();
}, },
(apiResponse) { (apiResponse) {
@ -330,7 +331,7 @@ class MedicalFileViewModel extends ChangeNotifier {
} }
} }
patientSickLeavesViewList = patientSickLeavesByClinic; patientSickLeavesViewList = patientSickLeavesByClinic;
// isSickLeavesDataNeedsReloading = false; isSickLeavesDataNeedsReloading = false;
notifyListeners(); notifyListeners();
if (onSuccess != null) { if (onSuccess != null) {

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

@ -54,6 +54,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
bool isFavouriteDoctorsDataFetched = false; bool isFavouriteDoctorsDataFetched = false;
bool isAppointmentDataToBeLoaded = true; bool isAppointmentDataToBeLoaded = true;
bool isMyDoctorsDataToBeLoaded = true;
bool isEyeMeasurementsAppointmentsLoading = false; bool isEyeMeasurementsAppointmentsLoading = false;
@ -210,9 +211,15 @@ class MyAppointmentsViewModel extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
setIsMyDoctorsDataToBeLoaded(bool val) {
isMyDoctorsDataToBeLoaded = val;
notifyListeners();
}
setIsAppointmentDataToBeLoaded(bool val) { setIsAppointmentDataToBeLoaded(bool val) {
if (val) { if (val) {
isMyAppointmentsLoading = true; isMyAppointmentsLoading = true;
isMyDoctorsDataToBeLoaded = true;
} }
isAppointmentDataToBeLoaded = val; isAppointmentDataToBeLoaded = val;
notifyListeners(); notifyListeners();
@ -673,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 (!isAppointmentDataToBeLoaded) return; if (!isMyDoctorsDataToBeLoaded) return;
isPatientMyDoctorsLoading = true; isPatientMyDoctorsLoading = true;
patientMyDoctorsList.clear(); patientMyDoctorsList.clear();
notifyListeners(); notifyListeners();
final result = await myAppointmentsRepo.getPatientDoctorsList(); final result = await myAppointmentsRepo.getPatientDoctorsList(isTop8: isTop8);
result.fold( result.fold(
// (failure) async => await errorHandlerService.handleError(failure: failure), // (failure) async => await errorHandlerService.handleError(failure: failure),
@ -692,6 +699,7 @@ 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;
notifyListeners(); notifyListeners();
if (onSuccess != null) { if (onSuccess != null) {
onSuccess(apiResponse); onSuccess(apiResponse);

@ -64,7 +64,7 @@ class PrescriptionsViewModel extends ChangeNotifier {
PrescriptionsViewModel({required this.prescriptionsRepo, required this.errorHandlerService, required this.navServices}); PrescriptionsViewModel({required this.prescriptionsRepo, required this.errorHandlerService, required this.navServices});
initPrescriptionsViewModel() { initPrescriptionsViewModel() {
// if (isPrescriptionsDataNeedsReloading) { if (isPrescriptionsDataNeedsReloading) {
patientPrescriptionOrders.clear(); patientPrescriptionOrders.clear();
patientPrescriptionOrdersByClinic.clear(); patientPrescriptionOrdersByClinic.clear();
patientPrescriptionOrdersByHospital.clear(); patientPrescriptionOrdersByHospital.clear();
@ -72,7 +72,7 @@ class PrescriptionsViewModel extends ChangeNotifier {
prescriptionsOrderList.clear(); prescriptionsOrderList.clear();
isPrescriptionsOrdersLoading = true; isPrescriptionsOrdersLoading = true;
getPatientPrescriptionOrders(); getPatientPrescriptionOrders();
// } }
isSortByClinic = true; isSortByClinic = true;
isPrescriptionsDeliveryOrdersLoading = true; isPrescriptionsDeliveryOrdersLoading = true;
notifyListeners(); notifyListeners();
@ -138,9 +138,9 @@ class PrescriptionsViewModel extends ChangeNotifier {
} }
Future<void> getPatientPrescriptionOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async { Future<void> getPatientPrescriptionOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async {
// if (!isPrescriptionsDataNeedsReloading) { if (!isPrescriptionsDataNeedsReloading) {
// return; return;
// } }
final result = await prescriptionsRepo.getPatientPrescriptionOrders(patientId: "1231755"); final result = await prescriptionsRepo.getPatientPrescriptionOrders(patientId: "1231755");
@ -235,11 +235,16 @@ class PrescriptionsViewModel extends ChangeNotifier {
result.fold( result.fold(
(failure) async { (failure) async {
onError!(failure.message); if (onError != null) {
onError(failure.message);
}
}, },
(apiResponse) { (apiResponse) {
if (apiResponse.messageStatus == 2) { 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: () {}); // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) { } else if (apiResponse.messageStatus == 1) {
prescriptionInstructionsPDFLink = apiResponse.data; prescriptionInstructionsPDFLink = apiResponse.data;

@ -13,11 +13,16 @@ enum Durations {
yearly("yearly"); yearly("yearly");
final String value; final String value;
const Durations(this.value); const Durations(this.value);
} }
class HealthDataTransformation { class HealthDataTransformation {
Map<String, List<DataPoint>> transformVitalsToDataPoints(VitalsWRTType vitals, String filterType, String selectedSection,) { Map<String, List<DataPoint>> transformVitalsToDataPoints(
VitalsWRTType vitals,
String filterType,
String selectedSection,
) {
final Map<String, List<DataPoint>> dataPointMap = {}; final Map<String, List<DataPoint>> dataPointMap = {};
Map<String, List<Vitals>> data = vitals.getVitals(); Map<String, List<Vitals>> data = vitals.getVitals();
// Group data based on the filter type // Group data based on the filter type
@ -32,31 +37,36 @@ class HealthDataTransformation {
for (var vital in item) { for (var vital in item) {
String key = ""; String key = "";
if (vital.value == "" || vital.timestamp == "") continue; if (vital.value == "" || vital.timestamp == "") continue;
var parseDate = DateTime.parse(vital.timestamp);
DateTime parseDate;
try {
parseDate = DateTime.parse(vital.timestamp);
} catch (e) {
print('Error parsing timestamp "${vital.timestamp}": $e');
continue; // Skip this vital if timestamp is invalid
}
var currentDate = normalizeToStartOfDay(DateTime.now()); var currentDate = normalizeToStartOfDay(DateTime.now());
if (filterType == Durations.daily.value) { if (filterType == Durations.daily.value) {
if (isBetweenInclusive(parseDate, currentDate, DateTime.now())) { if (isBetweenInclusive(parseDate, currentDate, DateTime.now())) {
key = DateFormat('yyyy-MM-dd HH').format(DateTime.parse(vital.timestamp)); key = DateFormat('yyyy-MM-dd HH').format(parseDate);
groupedData.putIfAbsent(key, () => []).add(vital); groupedData.putIfAbsent(key, () => []).add(vital);
} // Group by hour } // Group by hour
} else if (filterType == Durations.weekly.value) { } else if (filterType == Durations.weekly.value) {
if (isBetweenInclusive(parseDate, currentDate.subtract(Duration(days: 7)), DateTime.now())) { if (isBetweenInclusive(parseDate, currentDate.subtract(Duration(days: 7)), DateTime.now())) {
key = DateFormat('yyyy-MM-dd').format(DateTime.parse(vital.timestamp)); key = DateFormat('yyyy-MM-dd').format(parseDate);
groupedData.putIfAbsent(key, () => []).add(vital); groupedData.putIfAbsent(key, () => []).add(vital);
} // Group by day } // Group by day
} else if (filterType == Durations.monthly.value) { } else if (filterType == Durations.monthly.value) {
if (isBetweenInclusive(parseDate, currentDate.subtract(Duration(days: 30)), DateTime.now())) { if (isBetweenInclusive(parseDate, currentDate.subtract(Duration(days: 30)), DateTime.now())) {
print("the value for the monthly filter is ${vital.value} with the timestamp ${vital.timestamp} and the current date is $currentDate and the parse date is $parseDate"); print("the value for the monthly filter is ${vital.value} with the timestamp ${vital.timestamp} and the current date is $currentDate and the parse date is $parseDate");
key = DateFormat('yyyy-MM-dd').format(DateTime.parse(vital.timestamp)); key = DateFormat('yyyy-MM-dd').format(parseDate);
groupedData.putIfAbsent(key, () => []).add(vital); groupedData.putIfAbsent(key, () => []).add(vital);
} // Group by day } // Group by day
} else if (filterType == Durations.halfYearly.value || filterType == Durations.yearly.value) { } else if (filterType == Durations.halfYearly.value || filterType == Durations.yearly.value) {
if (isBetweenInclusive(parseDate, currentDate.subtract(Duration(days: filterType == Durations.halfYearly.value ? 180 : 365)), DateTime.now())) { if (isBetweenInclusive(parseDate, currentDate.subtract(Duration(days: filterType == Durations.halfYearly.value ? 180 : 365)), DateTime.now())) {
key = DateFormat('yyyy-MM').format(DateTime.parse(vital.timestamp)); key = DateFormat('yyyy-MM').format(parseDate);
groupedData.putIfAbsent(key, () => []).add(vital); groupedData.putIfAbsent(key, () => []).add(vital);
} // Group by month } // Group by month
} else { } else {
throw ArgumentError('Invalid filter type'); throw ArgumentError('Invalid filter type');
@ -122,6 +132,7 @@ class HealthDataTransformation {
DateTime normalizeToStartOfDay(DateTime date) { DateTime normalizeToStartOfDay(DateTime date) {
return DateTime(date.year, date.month, date.day); return DateTime(date.year, date.month, date.day);
} }
bool isBetweenInclusive( bool isBetweenInclusive(
DateTime target, DateTime target,
DateTime start, DateTime start,
@ -129,6 +140,4 @@ class HealthDataTransformation {
) { ) {
return !normalizeToStartOfDay(target).isBefore(start) && !normalizeToStartOfDay(target).isAfter(end); return !normalizeToStartOfDay(target).isBefore(start) && !normalizeToStartOfDay(target).isAfter(end);
} }
} }

@ -202,7 +202,7 @@ class HealthService {
} }
var response = jsonDecode(data.asValue?.value?.toString()?.trim().replaceAll("\n", "") ?? ""); var response = jsonDecode(data.asValue?.value?.toString()?.trim().replaceAll("\n", "") ?? "");
VitalsWRTType vitals = VitalsWRTType.fromMap(response); VitalsWRTType vitals = VitalsWRTType.fromMap(response);
log("the data is ${vitals}"); // log("the data is ${vitals}");
return vitals; return vitals;
} catch (e) { } catch (e) {
print('Error getting heart rate: $e'); print('Error getting heart rate: $e');

@ -9,10 +9,22 @@ class Vitals {
this.unitOfMeasure = "", this.unitOfMeasure = "",
}); });
Map<String, dynamic> toJson() {
return {
'value': value,
'timestamp': timestamp,
'uom': unitOfMeasure,
};
}
factory Vitals.fromMap(Map<dynamic, dynamic> map) { factory Vitals.fromMap(Map<dynamic, dynamic> map) {
String timestamp = map['timestamp'] ?? map['timeStamp'] ?? "";
if (timestamp.isEmpty) {
timestamp = DateTime.now().toIso8601String();
}
return Vitals( return Vitals(
value: map['value'] ?? "", value: map['value'] ?? "",
timestamp: map['timeStamp'] ?? "", timestamp: timestamp,
unitOfMeasure: map['uom'] ?? "", unitOfMeasure: map['uom'] ?? "",
); );
} }

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:async/src/result/result.dart'; import 'package:async/src/result/result.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -80,7 +81,10 @@ class HealthConnectHelper extends WatchHelper {
@override @override
Future<dynamic> retrieveData() async { Future<dynamic> retrieveData() async {
// String retData;
// retData = Result.value(getMappedData()) as String;
return Result.value(getMappedData()); return Result.value(getMappedData());
// return retData;
} }
@override @override
@ -151,9 +155,22 @@ class HealthConnectHelper extends WatchHelper {
for (var point in data) { for (var point in data) {
if (point.value is NumericHealthValue) { if (point.value is NumericHealthValue) {
final numericValue = (point.value as NumericHealthValue).numericValue; final numericValue = (point.value as NumericHealthValue).numericValue;
String timestamp = DateTime.now().toIso8601String();
try {
if (point.dateFrom != null) {
String tempTimestamp = point.dateFrom.toIso8601String();
if (tempTimestamp.isNotEmpty) {
timestamp = tempTimestamp;
}
}
} catch (e) {
debugPrint('Error converting dateFrom to ISO8601 for $s: $e');
}
Vitals vitals = Vitals( Vitals vitals = Vitals(
value: (point.value as NumericHealthValue).numericValue.toStringAsFixed(2), value: numericValue.toStringAsFixed(2),
timestamp: point.dateFrom.toString() timestamp: timestamp
); );
mappedData[s]?.add(vitals); mappedData[s]?.add(vitals);
} }
@ -165,17 +182,41 @@ class HealthConnectHelper extends WatchHelper {
for (var point in data) { for (var point in data) {
if (point.value is NumericHealthValue) { if (point.value is NumericHealthValue) {
final numericValue = (point.value as NumericHealthValue).numericValue; final numericValue = (point.value as NumericHealthValue).numericValue;
String timestamp = DateTime.now().toIso8601String();
try {
if (point.dateFrom != null) {
String tempTimestamp = point.dateFrom.toIso8601String();
if (tempTimestamp.isNotEmpty) {
timestamp = tempTimestamp;
}
}
} catch (e) {
debugPrint('Error converting dateFrom to ISO8601 for $s: $e');
}
point.value = NumericHealthValue( point.value = NumericHealthValue(
numericValue: numericValue * 100, numericValue: numericValue * 100,
); );
Vitals vitals = Vitals(value: (point.value as NumericHealthValue).numericValue.toStringAsFixed(2), timestamp: point.dateFrom.toString()); Vitals vitals = Vitals(
value: (point.value as NumericHealthValue).numericValue.toStringAsFixed(2),
timestamp: timestamp
);
mappedData[s]?.add(vitals); mappedData[s]?.add(vitals);
} }
} }
} }
getMappedData() { getMappedData() {
return " { \"heartRate\": ${mappedData["heartRate"] ?? []}, \"sleep\": ${mappedData["sleep"] ?? []}, \"steps\": ${mappedData["steps"] ?? []}, \"activity\": ${mappedData["activity"] ?? []}, \"bloodOxygen\": ${mappedData["bloodOxygen"] ?? []}, \"bodyTemperature\": ${mappedData["bodyTemperature"] ?? []}, \"distance\": ${mappedData["distance"] ?? []} }"; return jsonEncode({
"heartRate": (mappedData["heartRate"] ?? []).map((v) => v.toJson()).toList(),
"sleep": (mappedData["sleep"] ?? []).map((v) => v.toJson()).toList(),
"steps": (mappedData["steps"] ?? []).map((v) => v.toJson()).toList(),
"activity": (mappedData["activity"] ?? []).map((v) => v.toJson()).toList(),
"bloodOxygen": (mappedData["bloodOxygen"] ?? []).map((v) => v.toJson()).toList(),
"bodyTemperature": (mappedData["bodyTemperature"] ?? []).map((v) => v.toJson()).toList(),
"distance": (mappedData["distance"] ?? []).map((v) => v.toJson()).toList(),
});
} }
getHeartData(DateTime startDate, DateTime endDate, HealthDataType types) async { getHeartData(DateTime startDate, DateTime endDate, HealthDataType types) async {

@ -1947,5 +1947,6 @@ abstract class LocaleKeys {
static const doctorSchedule = 'doctorSchedule'; static const doctorSchedule = 'doctorSchedule';
static const weeklyShift = 'weeklyShift'; static const weeklyShift = 'weeklyShift';
static const dayOff = 'dayOff'; static const dayOff = 'dayOff';
static const hide = 'hide';
} }

@ -63,6 +63,7 @@ import 'core/utils/size_utils.dart';
import 'features/monthly_reports/terms_conditions_view_model.dart'; import 'features/monthly_reports/terms_conditions_view_model.dart';
import 'firebase_options.dart'; import 'firebase_options.dart';
@pragma('vm:entry-point') @pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async { Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
@ -109,11 +110,6 @@ Future<void> callInitializations() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized(); await EasyLocalization.ensureInitialized();
// Download the latest JSONs from your server
// await TranslationService.downloadTranslations();
// await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
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();

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

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

@ -277,7 +277,7 @@ class _AppointmentCardState extends State<AppointmentCard> {
(widget.isLoading ? 'Dr' : "${widget.patientAppointmentHistoryResponseModel.doctorTitle}").toText16(isBold: true, maxlines: 1), (widget.isLoading ? 'Dr' : "${widget.patientAppointmentHistoryResponseModel.doctorTitle}").toText16(isBold: true, maxlines: 1),
Expanded( Expanded(
child: (widget.isLoading ? 'John Doe' : " ${widget.patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(30)}").toText16( child: (widget.isLoading ? 'John Doe' : " ${widget.patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(30)}").toText16(
isBold: true, maxlines: 2, textOverflow: TextOverflow.ellipsis, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")), isBold: true, maxlines: 1, textOverflow: TextOverflow.ellipsis, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")),
), ),
SizedBox(width: 12.w), SizedBox(width: 12.w),
(widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && (widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null &&

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

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

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

@ -212,6 +212,7 @@ class _SearchDoctorByNameState extends State<SearchDoctorByName> {
doctorsListResponseModel: bookAppointmentsVM.filteredDoctorList[index], doctorsListResponseModel: bookAppointmentsVM.filteredDoctorList[index],
isLoading: false, isLoading: false,
bookAppointmentsViewModel: bookAppointmentsViewModel, bookAppointmentsViewModel: bookAppointmentsViewModel,
isDoctorNameSearch: true,
).paddingSymmetrical(16.h, 0.h).onPress(() async { ).paddingSymmetrical(16.h, 0.h).onPress(() async {
bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.filteredDoctorList[index]); bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.filteredDoctorList[index]);
LoaderBottomSheet.showLoader(); LoaderBottomSheet.showLoader();

@ -10,13 +10,17 @@ 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/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_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/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/laser/laser_appointment.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';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:lottie/lottie.dart'; import 'package:lottie/lottie.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -25,11 +29,13 @@ class DoctorCard extends StatelessWidget {
super.key, super.key,
required this.doctorsListResponseModel, required this.doctorsListResponseModel,
required this.isLoading, required this.isLoading,
this.isDoctorNameSearch = false,
required this.bookAppointmentsViewModel, required this.bookAppointmentsViewModel,
}); });
final DoctorsListResponseModel doctorsListResponseModel; final DoctorsListResponseModel doctorsListResponseModel;
final bool isLoading; final bool isLoading;
final bool isDoctorNameSearch;
final BookAppointmentsViewModel bookAppointmentsViewModel; final BookAppointmentsViewModel bookAppointmentsViewModel;
@ -173,6 +179,29 @@ class DoctorCard extends StatelessWidget {
CustomButton( CustomButton(
text: LocaleKeys.bookAppo.tr(context: context), text: LocaleKeys.bookAppo.tr(context: context),
onPressed: () async { onPressed: () async {
if (isDoctorNameSearch && doctorsListResponseModel.clinicID == 17) {
GetClinicsListResponseModel selectedClinic = GetClinicsListResponseModel(
clinicID: doctorsListResponseModel.clinicID, clinicDescription: doctorsListResponseModel.clinicName, isLiveCareClinicAndOnline: false, liveCareServiceID: 0, liveCareClinicID: 0);
bookAppointmentsViewModel.setProjectID(doctorsListResponseModel.projectID.toString());
bookAppointmentsViewModel.setSelectedClinic(selectedClinic);
bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true);
Navigator.of(context).push(
CustomPageRoute(
page: DentalChiefComplaintsPage(),
),
);
} else if (isDoctorNameSearch && doctorsListResponseModel.clinicID == 253) {
GetClinicsListResponseModel selectedClinic = GetClinicsListResponseModel(
clinicID: doctorsListResponseModel.clinicID, clinicDescription: doctorsListResponseModel.clinicName, isLiveCareClinicAndOnline: false, liveCareServiceID: 0, liveCareClinicID: 0);
bookAppointmentsViewModel.setSelectedClinic(selectedClinic);
bookAppointmentsViewModel.getLaserClinic();
bookAppointmentsViewModel.setIsDoctorsListLoading(true);
Navigator.of(context).push(
CustomPageRoute(
page: LaserAppointment(),
),
);
} else {
bookAppointmentsViewModel.setSelectedDoctor(doctorsListResponseModel); bookAppointmentsViewModel.setSelectedDoctor(doctorsListResponseModel);
LoaderBottomSheet.showLoader(); LoaderBottomSheet.showLoader();
bookAppointmentsViewModel.isLiveCareSchedule bookAppointmentsViewModel.isLiveCareSchedule
@ -261,6 +290,7 @@ class DoctorCard extends StatelessWidget {
// isCloseButtonVisible: true, // isCloseButtonVisible: true,
// ); // );
}); });
}
}, },
backgroundColor: Color(0xffFEE9EA), backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA),

File diff suppressed because one or more lines are too long

@ -1,44 +1,274 @@
import 'dart:ui' as ui;
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/app_state.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/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart';
import 'package:provider/provider.dart' show Selector, Provider, ReadContext; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart';
import 'package:provider/provider.dart';
import '../../../core/dependencies.dart' show getIt;
import '../../../core/utils/size_utils.dart' show ResponsiveExtension;
import '../../../generated/locale_keys.g.dart' show LocaleKeys;
import '../../../widgets/date_range_selector/viewmodel/date_range_view_model.dart'; class LabResultList extends StatefulWidget {
@override
State<LabResultList> createState() => _LabResultListState();
}
class LabResultList extends StatelessWidget { class _LabResultListState extends State<LabResultList> {
late LabViewModel model; late LabViewModel model;
int expandedIndex = -1;
Map<int, bool> descriptionExpandedMap = {};
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
model = Provider.of<LabViewModel>(context); model = Provider.of<LabViewModel>(context);
return Selector<LabViewModel, List<LabResult>>( return Selector<LabViewModel, Map<String, List<LabResult>>>(
selector: (_, model) => model.mainLabResultsByHospitals, selector: (_, model) => model.mainLabResultsByPackageName,
builder: (__, list, ___) { builder: (__, groupedResults, ___) {
if (list.isEmpty && context.read<LabViewModel>().labSpecialResult.isEmpty) { if (groupedResults.isEmpty && context.read<LabViewModel>().labSpecialResult.isEmpty) {
// return Utils.getNoDataWidget(context, // return Utils.getNoDataWidget(context,
// noDataText: LocaleKeys.noLabResults.tr(context: context)); // noDataText: LocaleKeys.noLabResults.tr(context: context));
return Container(); return Container();
} else { } else {
// Convert map entries to a list for indexed access
final packageEntries = groupedResults.entries.toList();
return ListView.builder( return ListView.builder(
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
shrinkWrap: true,itemCount: list.length,itemBuilder: (____, index) { shrinkWrap: true,
var labItem = list[index]; itemCount: packageEntries.length,
return LabOrderResultItem(onTap: () { itemBuilder: (context, packageIndex) {
final packageName = packageEntries[packageIndex].key;
final labResults = packageEntries[packageIndex].value;
final isExpanded = expandedIndex == packageIndex;
return AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
margin: EdgeInsets.symmetric(vertical: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Package name as title/header
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.0.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8.h,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: packageName.toText18(
isBold: true
),
),
AnimatedRotation(
turns: isExpanded ? 0.5 : 0,
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: Icon(Icons.expand_more),
),
],
),
if(labResults.first.packageShortDescription != null)
_ExpandableDescriptionText(
description: labResults.first.packageShortDescription!,
isExpanded: descriptionExpandedMap[packageIndex] ?? false,
onToggle: () {
setState(() {
descriptionExpandedMap[packageIndex] = !(descriptionExpandedMap[packageIndex] ?? false);
});
},
)
else
SizedBox.shrink()
],
),),
// Lab results under this package with animation
AnimatedSize(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: isExpanded
? Column(
children: labResults.asMap().entries.map((entry) {
final index = entry.key;
final labItem = entry.value;
return TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: Duration(milliseconds: 300 + (index * 50)),
curve: Curves.easeOut,
builder: (context, value, child) {
return Opacity(
opacity: value,
child: Transform.translate(
offset: Offset(0, 20 * (1 - value)),
child: child,
),
);
},
child: Column(
children: [
LabOrderResultItem(
onTap: () {
context.read<DateRangeSelectorRangeViewModel>().flush(); context.read<DateRangeSelectorRangeViewModel>().flush();
model.getPatientLabResult(model.currentlySelectedPatientOrder!, labItem.description ?? "", labItem.testShortDescription ?? "", labItem.uOM ?? ""); model.getPatientLabResult(
model.currentlySelectedPatientOrder!,
labItem.description ?? "",
labItem.testShortDescription ?? "",
labItem.uOM ?? ""
);
}, },
tests: labItem, tests: labItem,
index: index, index: index,
iconColor: model.getColor(labItem.calculatedResultFlag ?? "N"), iconColor: model.getColor(labItem.calculatedResultFlag ?? "N"),
severityText: model.getSeverityText(labItem.calculatedResultFlag ?? "N")); severityText: model.getSeverityText(labItem.calculatedResultFlag ?? "N")
),
if(labResults.length>1)
...[SizedBox(
height: 8.h,
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.h),
child: Divider(
color: AppColors.dividerColor,
thickness: 1.h,
),
)]
],
),
);
}).toList(),
)
: SizedBox.shrink(),
),
],
),
).onPress((){
setState(() {
expandedIndex = isExpanded ? -1 : packageIndex;
});
}); });
} }
);
}
},
);
}
}
class _ExpandableDescriptionText extends StatelessWidget {
final String description;
final bool isExpanded;
final VoidCallback onToggle;
const _ExpandableDescriptionText({
required this.description,
required this.isExpanded,
required this.onToggle,
});
static TextStyle get textStyle => TextStyle(
fontSize: 14.f,
color: AppColors.textColorLight,
// or Colors.black87
// fontFamily: 'Poppins',
fontWeight: FontWeight.w600,
fontStyle: FontStyle.normal,
height: 1.4,
letterSpacing: 0.0,
decoration: TextDecoration.none,
);
@override
Widget build(BuildContext context) {
final readMoreText = isExpanded
? ' ${LocaleKeys.hide.tr()}'
: ' ${LocaleKeys.readMore.tr(context: context)}';
final readMoreStyle = TextStyle(
fontSize: 12.f,
color: AppColors.primaryRedColor,
// fontFamily: 'Poppins',
fontWeight: FontWeight.w500,
);
return LayoutBuilder(
builder: (context, constraints) {
// Check if description alone overflows 2 lines
final descriptionPainter = TextPainter(
text: TextSpan(text: description, style: textStyle),
maxLines: 2,
textDirection: Directionality.of(context),
)..layout(maxWidth: constraints.maxWidth);
final isOverflowing = descriptionPainter.didExceedMaxLines;
// No overflow, or already expanded -> show full text
if (!isOverflowing || isExpanded) {
return GestureDetector(
onTap: isOverflowing ? onToggle : null,
child: Text.rich(
TextSpan(
style: textStyle,
children: [
TextSpan(text: description, style: textStyle),
if (isOverflowing)
TextSpan(text: readMoreText, style: readMoreStyle),
],
),
),
);
}
// Binary search for max chars that fit in 2 lines with "Read more" appended
int low = 0, high = description.length;
while (low < high) {
final mid = (low + high + 1) ~/ 2;
final testPainter = TextPainter(
text: TextSpan(
style: textStyle,
children: [
TextSpan(text: '${description.substring(0, mid)}...'),
TextSpan(text: readMoreText, style: readMoreStyle),
],
),
maxLines: 2,
textDirection: Directionality.of(context),
)..layout(maxWidth: constraints.maxWidth);
if (testPainter.didExceedMaxLines) {
high = mid - 1;
} else {
low = mid;
}
}
final truncated = description.substring(0, low).trimRight();
return GestureDetector(
onTap: onToggle,
child: Text.rich(
TextSpan(
style: textStyle,
children: [
TextSpan(text: '$truncated...'),
TextSpan(text: readMoreText, style: readMoreStyle),
],
),
),
);
}, },
); );
} }

@ -4,6 +4,7 @@ import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_export.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/dependencies.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; 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';
@ -12,6 +13,7 @@ import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.d
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/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';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'dart:ui' as ui; import 'dart:ui' as ui;
@ -24,7 +26,16 @@ class LabOrderResultItem extends StatelessWidget {
final bool isExpanded; final bool isExpanded;
final Color iconColor; final Color iconColor;
const LabOrderResultItem({super.key, required this.onTap, this.tests, required this.index, this.isLoading = false, this.isExpanded = false, required this.iconColor, required this.severityText}); const LabOrderResultItem({
super.key,
required this.onTap,
this.tests,
required this.index,
this.isLoading = false,
this.isExpanded = false,
required this.iconColor,
required this.severityText
});
@override @override
build(BuildContext context) { build(BuildContext context) {
@ -32,10 +43,10 @@ class LabOrderResultItem extends StatelessWidget {
duration: Duration(milliseconds: 300), duration: Duration(milliseconds: 300),
curve: Curves.easeInOut, curve: Curves.easeInOut,
margin: EdgeInsets.symmetric(vertical: 8.h), margin: EdgeInsets.symmetric(vertical: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false),
child: Container( child: Container(
key: ValueKey<int>(index), key: ValueKey<int>(index),
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 16.h), padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 4.h),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -84,7 +95,20 @@ class LabOrderResultItem extends StatelessWidget {
), ),
softWrap: true, softWrap: true,
), ),
) ),
Visibility(
visible: tests?.notes != null && tests?.notes?.isNotEmpty == true,
child: Text(
tests?.notes??"",
style: TextStyle(
fontSize: 12.f,
fontWeight: FontWeight.w600,
fontFamily: getIt.get<AppState>().isArabic() ? 'CairoArabic' : 'Poppins',
color: AppColors.greyTextColor,
),
softWrap: true,
),
),
], ],
), ),
), ),
@ -104,7 +128,9 @@ class LabOrderResultItem extends StatelessWidget {
severityText.tr().toText10(isBold: true, color: AppColors.greyTextColor), severityText.tr().toText10(isBold: true, color: AppColors.greyTextColor),
Utils.buildSvgWithAssets(icon: AppAssets.lab_result_indicator, width: 21, height: 23, iconColor: iconColor), Utils.buildSvgWithAssets(icon: AppAssets.lab_result_indicator, width: 21, height: 23, iconColor: iconColor),
], ],
), ).onPress((){
_buildSeverityDetailsBottomsheet(severity: tests?.calculatedResultFlag ?? "N", context: context);
}),
), ),
), ),
Flexible( Flexible(
@ -136,4 +162,77 @@ class LabOrderResultItem extends StatelessWidget {
), ),
)); ));
} }
_buildSeverityDetailsBottomsheet({required String severity,required BuildContext context}) {
return showCommonBottomSheetWithoutHeight(
title: LocaleKeys.colorScience.tr(context: context),
context,
child: Container(
padding: EdgeInsets.all(16.w),
width: double.infinity,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: Column(
children: [
_buildInfoTile(severityValue: severity, context: context),
],
),
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
Widget _buildInfoTile({required String severityValue, required BuildContext context}) {
String title = LocaleKeys.emergency.tr(context: context);
String description = LocaleKeys.emergencyDescription.tr(context: context);
Color dotColor = AppColors.chipColorEmergency;
switch (severityValue) {
case 'CL':
case 'LCL':
title = LocaleKeys.emergency.tr(context: context);
description = LocaleKeys.emergencyDescription.tr(context: context);
dotColor = AppColors.chipColorEmergency;
case 'L':
title = LocaleKeys.seekMedicalAdvice.tr(context: context);
description = LocaleKeys.seekMedicalAdviceDescription.tr(context: context);
dotColor = AppColors.chipColorSeekMedicalAdvice;
case 'N':
title = LocaleKeys.monitor.tr(context: context);
description = LocaleKeys.monitorDescription.tr(context: context);
dotColor = AppColors.chipColorMonitor;
case 'H':
title = LocaleKeys.seekMedicalAdvice.tr(context: context);
description = LocaleKeys.seekMedicalAdviceDescription.tr(context: context);
dotColor = AppColors.chipColorSeekMedicalAdvice;
case 'CH':
case 'HCH':
title = LocaleKeys.emergency.tr(context: context);
description = LocaleKeys.emergencyDescription.tr(context: context);
dotColor = AppColors.chipColorEmergency;
}
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 14.h,
width: 14.w,
decoration: BoxDecoration(shape: BoxShape.circle, color: dotColor),
).paddingOnly(top: 3.h),
SizedBox(width: 8.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title.toText14(isBold: true, color: AppColors.textColor),
SizedBox(height: 4.h),
description.toText12(isBold: true, color: AppColors.greyTextColor),
],
),
),
],
);
}
} }

@ -110,7 +110,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
appState = getIt.get<AppState>(); appState = getIt.get<AppState>();
scheduleMicrotask(() { scheduleMicrotask(() {
if (appState.isAuthenticated) { if (appState.isAuthenticated) {
myAppointmentsViewModel.getPatientMyDoctors(); myAppointmentsViewModel.getPatientMyDoctors(isTop8: true);
prescriptionsViewModel.initPrescriptionsViewModel(); prescriptionsViewModel.initPrescriptionsViewModel();
insuranceViewModel.initInsuranceProvider(); insuranceViewModel.initInsuranceProvider();
medicalFileViewModel.setIsPatientSickLeaveListLoading(true); medicalFileViewModel.setIsPatientSickLeaveListLoading(true);
@ -1046,6 +1046,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
], ],
).onPress(() { ).onPress(() {
// myAppointmentsViewModel.getPatientMyDoctors(); // myAppointmentsViewModel.getPatientMyDoctors();
myAppointmentsViewModel.setIsMyDoctorsDataToBeLoaded(true);
Navigator.of(context).push( Navigator.of(context).push(
CustomPageRoute( CustomPageRoute(
page: MyDoctorsPage(), page: MyDoctorsPage(),
@ -1099,7 +1100,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList( return AnimationConfiguration.staggeredList(
position: index, position: index,
duration: const Duration(milliseconds: 1000), duration: const Duration(milliseconds: 250),
child: SlideAnimation( child: SlideAnimation(
horizontalOffset: 100.0, horizontalOffset: 100.0,
child: FadeInAnimation( child: FadeInAnimation(

@ -92,7 +92,7 @@ class MedicalReportCard extends StatelessWidget {
// fontWeight: FontWeight.w600, // fontWeight: FontWeight.w600,
isBold: true, isBold: true,
borderRadius: 12.r, borderRadius: 12.r,
height: 48.h, height: 40.h,
).toShimmer2(isShow: isLoading), ).toShimmer2(isShow: isLoading),
], ],
), ),

@ -242,7 +242,7 @@ class _MedicalReportsPageState extends State<MedicalReportsPage> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Utils.buildImgWithNetwork( Utils.buildImgWithNetwork(
url: report.doctorImageURL!, url: report.doctorImageURL ?? "",
iconColor: AppColors.transparent, iconColor: AppColors.transparent,
width: 24.h, width: 24.h,
height: 24.h, height: 24.h,

@ -1,5 +1,6 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/date_util.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/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -20,7 +21,16 @@ class NotificationDetailsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CollapsingListView( 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(), title: LocaleKeys.notificationDetails.tr(),
// trailing: IconButton( // trailing: IconButton(
// icon: Icon( // icon: Icon(
@ -43,6 +53,7 @@ class NotificationDetailsPage extends StatelessWidget {
], ],
).paddingSymmetrical(24.w, 0.h), ).paddingSymmetrical(24.w, 0.h),
), ),
),
); );
} }
@ -123,8 +134,7 @@ class NotificationDetailsPage extends StatelessWidget {
), ),
// Notification image (if MessageType is "image") // Notification image (if MessageType is "image")
if (notification.messageType != null && if (notification.messageType != null && notification.messageType!.toLowerCase() == "image")
notification.messageType!.toLowerCase() == "image")
Builder( Builder(
builder: (context) { builder: (context) {
// Try to get image URL from videoURL or messageTypeData // Try to get image URL from videoURL or messageTypeData
@ -205,10 +215,7 @@ class NotificationDetailsPage extends StatelessWidget {
), ),
child: Center( child: Center(
child: CircularProgressIndicator( child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null,
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
), ),
), ),
); );
@ -249,7 +256,8 @@ ${notification.message ?? 'Notification'}
Time: ${_formatTime(notification.isSentOn)} Time: ${_formatTime(notification.isSentOn)}
Date: ${_formatDate(notification.isSentOn)} Date: ${_formatDate(notification.isSentOn)}
${notification.notificationType != null ? '\nType: ${notification.notificationType}' : ''} ${notification.notificationType != null ? '\nType: ${notification.notificationType}' : ''}
'''.trim(); '''
.trim();
await Share.share(shareText); 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( child: ListView.builder(
itemCount: notificationsVM.isNotificationsLoading itemCount: notificationsVM.isNotificationsLoading
? 4 ? 4
: notificationsVM.notificationsList.length + (notificationsVM.hasMoreNotifications ? 1 : 0), // : notificationsVM.notificationsList.length + (notificationsVM.hasMoreNotifications ? 1 : 0),
: notificationsVM.notificationsList.length,
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
shrinkWrap: true, shrinkWrap: true,
padding: EdgeInsetsGeometry.zero, padding: EdgeInsetsGeometry.zero,

@ -240,7 +240,8 @@ class _ActivityDetailsState extends State<ActivityDetails> {
return Selector<HealthProvider, Map<String, List<DataPoint>>?>( return Selector<HealthProvider, Map<String, List<DataPoint>>?>(
selector: (_, model) => model.selectedData, selector: (_, model) => model.selectedData,
builder: (_, data, __) { builder: (_, data, __) {
if (context.read<HealthProvider>().selectedData.values.toList().first?.isEmpty == true) return SizedBox(); // if (context.read<HealthProvider>().selectedData.values.toList().first.isEmpty == true) return SizedBox();
if (context.read<HealthProvider>().selectedData.values.toList().isEmpty == true) return SizedBox();
return CustomBarChart( return CustomBarChart(
dataPoints: context.read<HealthProvider>().selectedData.values.toList().first, dataPoints: context.read<HealthProvider>().selectedData.values.toList().first,

@ -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.37+36 version: 0.0.39+38
#version: 0.0.6+3 #version: 0.0.8+1
environment: environment:
sdk: ">=3.6.0 <4.0.0" sdk: ">=3.6.0 <4.0.0"
@ -108,6 +108,8 @@ dependencies:
clarity_flutter: 1.9.0 clarity_flutter: 1.9.0
flutter_amazonpaymentservices: ^0.0.13 flutter_amazonpaymentservices: ^0.0.13
screen_brightness: ^1.0.1 screen_brightness: ^1.0.1
flutter_screenshot_blocker: ^1.0.4
cloudflare_turnstile: ^3.7.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

Loading…
Cancel
Save