* 'master' of http://34.17.182.140/Haroon6138/HMG_Patient_App_New:
  updated scheduleApi Fix
  Completed the SymptomsChecker Changes
  Updates & http client enhancements
  Updates
  updates
  Symptoms checker flow updates and Design Fixes on Fold
  Habib Wallet fix in services page
  Design fixes on Fold
  Updates & fixes
  Design changes on fold
  updates

# Conflicts:
#	assets/langs/ar-SA.json
#	assets/langs/en-US.json
pull/306/head
Sultan khan 2 days ago
commit d9d7a8b7c2

@ -175,11 +175,11 @@ dependencies {
implementation("androidx.navigation:navigation-ui-ktx:2.9.0") implementation("androidx.navigation:navigation-ui-ktx:2.9.0")
implementation("androidx.activity:activity-ktx:1.10.1") implementation("androidx.activity:activity-ktx:1.10.1")
// val room_version = "2.6.1" val room_version = "2.6.1"
// implementation("androidx.room:room-runtime:$room_version") implementation("androidx.room:room-runtime:$room_version")
// annotationProcessor("androidx.room:room-compiler:$room_version") annotationProcessor("androidx.room:room-compiler:$room_version")
// implementation("net.zetetic:android-database-sqlcipher:4.5.4") implementation("net.zetetic:android-database-sqlcipher:4.5.4")
implementation("com.intuit.ssp:ssp-android:1.1.0") implementation("com.intuit.ssp:ssp-android:1.1.0")
implementation("com.intuit.sdp:sdp-android:1.1.0") implementation("com.intuit.sdp:sdp-android:1.1.0")

@ -16,11 +16,9 @@ import GoogleMaps
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
func initializePlatformChannels(){ func initializePlatformChannels(){
// if let mainViewController = window?.rootViewController as? FlutterViewController{ // platform initialization suppose to be in foreground if let mainViewController = window?.rootViewController as? FlutterViewController{ // platform initialization suppose to be in foreground
// HMGPenguinInPlatformBridge.initialize(flutterViewController: mainViewController)
//// HMGPenguinInPlatformBridge.initialize(flutterViewController: mainViewController) }
//
// }
} }
override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken:Data){ override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken:Data){
// Messaging.messaging().apnsToken = deviceToken // Messaging.messaging().apnsToken = deviceToken

@ -4,20 +4,21 @@ import 'dart:io';
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/api/http_client_manager.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/home/app_update_page.dart'; import 'package:hmg_patient_app_new/presentation/home/app_update_page.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart';
import 'package:hmg_patient_app_new/services/app_lifecycle_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/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import '../exceptions/api_failure.dart';
abstract class ApiClient { abstract class ApiClient {
static final NavigationService _navigationService = getIt.get<NavigationService>(); static final NavigationService _navigationService = getIt.get<NavigationService>();
@ -88,10 +89,14 @@ abstract class ApiClient {
class ApiClientImp implements ApiClient { class ApiClientImp implements ApiClient {
final _analytics = getIt<GAnalytics>(); final _analytics = getIt<GAnalytics>();
final AppState _appState; final AppState _appState;
final HttpClientManager _httpClient;
ApiClientImp({ ApiClientImp({
required AppState appState, required AppState appState,
}) : _appState = appState; HttpClientManager? httpClient,
}) : _appState = appState,
_httpClient = httpClient ??
HttpClientManager(lifecycleService: getIt<AppLifecycleService>());
@override @override
post( post(
@ -209,7 +214,7 @@ class ApiClientImp implements ApiClient {
// body['PatientOutSA'] = 0; // body['PatientOutSA'] = 0;
// body['SessionID'] = "45786230487560q"; // body['SessionID'] = "45786230487560q";
//VIP Patient: 1181868 //VIP Patient: 1181868body:
// body['IdentificationNo'] = "2235558844"; // body['IdentificationNo'] = "2235558844";
// body['MobileNo'] = "966533147722"; // body['MobileNo'] = "966533147722";
@ -243,7 +248,12 @@ class ApiClientImp implements ApiClient {
http.Response response; http.Response response;
try { try {
response = await http.post(Uri.parse(url.trim()), body: requestBody, headers: headers); response = await _httpClient.post(
uri: Uri.parse(url.trim()),
body: requestBody,
headers: headers,
);
// debugPrint("response: ${response.body}", wrapWidth: 2048);
} on SocketException catch (e) { } on SocketException catch (e) {
final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr(); final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr();
onFailure(message, -1, failureType: ConnectivityFailure(message)); onFailure(message, -1, failureType: ConnectivityFailure(message));
@ -445,8 +455,8 @@ class ApiClientImp implements ApiClient {
if (await Utils.checkConnection(bypassConnectionCheck: true)) { if (await Utils.checkConnection(bypassConnectionCheck: true)) {
http.Response response; http.Response response;
try { try {
response = await http.get( response = await _httpClient.get(
Uri.parse(url.trim()), uri: Uri.parse(url.trim()),
headers: apiHeaders ?? {'Content-Type': 'application/json', 'Accept': 'application/json'}, headers: apiHeaders ?? {'Content-Type': 'application/json', 'Accept': 'application/json'},
); );
} on SocketException catch (e) { } on SocketException catch (e) {

@ -0,0 +1,224 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/services/app_lifecycle_service.dart';
import 'package:http/http.dart' as http;
/// HTTP Client Manager with automatic retry logic for transient network errors
/// Handles background/foreground transitions gracefully
class HttpClientManager {
final http.Client _client;
final AppLifecycleService _lifecycleService;
static const Duration _defaultTimeout = Duration(seconds: 30);
static const int _maxRetries = 2;
HttpClientManager({
required AppLifecycleService lifecycleService,
http.Client? client,
}) : _lifecycleService = lifecycleService,
_client = client ?? http.Client();
/// Execute POST request with retry logic
Future<http.Response> post({
required Uri uri,
Map<String, String>? headers,
Object? body,
Duration? timeout,
int maxRetries = _maxRetries,
}) async {
return _executeWithRetry(
() => _client
.post(uri, headers: headers, body: body)
.timeout(timeout ?? _defaultTimeout),
endpoint: uri.pathSegments.isNotEmpty ? uri.pathSegments.last : uri.toString(),
maxRetries: maxRetries,
);
}
/// Execute GET request with retry logic
Future<http.Response> get({
required Uri uri,
Map<String, String>? headers,
Duration? timeout,
int maxRetries = _maxRetries,
}) async {
return _executeWithRetry(
() => _client
.get(uri, headers: headers)
.timeout(timeout ?? _defaultTimeout),
endpoint: uri.pathSegments.isNotEmpty ? uri.pathSegments.last : uri.toString(),
maxRetries: maxRetries,
);
}
/// Core retry logic for HTTP requests
Future<http.Response> _executeWithRetry(
Future<http.Response> Function() request, {
required String endpoint,
int maxRetries = _maxRetries,
}) async {
int attempt = 0;
while (attempt <= maxRetries) {
try {
// Wait for app to be in foreground before attempting request
await _waitForAppForeground();
// Check if app just came from background
if (_lifecycleService.lastResumedTime != null && attempt == 0) {
final timeSinceResume = DateTime.now().difference(_lifecycleService.lastResumedTime!);
if (timeSinceResume.inSeconds < 5) {
debugPrint('🔄 App recently resumed (${timeSinceResume.inSeconds}s ago), adding small delay before request...');
await Future.delayed(const Duration(milliseconds: 500));
}
}
return await request();
} on SocketException catch (e) {
if (attempt >= maxRetries) {
// If app is in background, wait for it to come back before throwing
if (_lifecycleService.isAppInBackground) {
debugPrint('⏸️ App in background, waiting to resume before final error for $endpoint');
await _waitForAppForeground();
}
rethrow;
}
await _handleRetry(
attempt: attempt++,
errorType: 'SocketException',
endpoint: endpoint,
errorDetails: e.message,
);
} on http.ClientException catch (e) {
if (attempt >= maxRetries) {
// If app is in background, wait for it to come back before throwing
if (_lifecycleService.isAppInBackground) {
debugPrint('⏸️ App in background, waiting to resume before final error for $endpoint');
await _waitForAppForeground();
}
rethrow;
}
await _handleRetry(
attempt: attempt++,
errorType: 'ClientException',
endpoint: endpoint,
errorDetails: e.message,
);
} on TimeoutException catch (e) {
// For timeout, only retry once
if (attempt >= 1) {
// If app is in background, wait for it to come back before throwing
if (_lifecycleService.isAppInBackground) {
debugPrint('⏸️ App in background, waiting to resume before final error for $endpoint');
await _waitForAppForeground();
}
rethrow;
}
await _handleRetry(
attempt: attempt++,
errorType: 'TimeoutException',
endpoint: endpoint,
errorDetails: e.message ?? 'Request timed out',
);
}
}
throw Exception('Max retries exceeded for $endpoint');
}
/// Wait for app to be in foreground before proceeding
Future<void> _waitForAppForeground() async {
if (!_lifecycleService.isAppInBackground) {
return; // App is already in foreground
}
debugPrint('⏸️ App is in background, pausing request until app resumes...');
// Wait for app to resume with a timeout
final completer = Completer<void>();
late StreamSubscription<AppLifecycleState> subscription;
// Set up a listener for app state changes
subscription = _lifecycleService.appStateStream.listen((state) {
if (state == AppLifecycleState.resumed) {
if (!completer.isCompleted) {
debugPrint('✅ App resumed, continuing with request');
completer.complete();
}
}
});
// Also check current state in case it changed
if (_lifecycleService.currentState == AppLifecycleState.resumed) {
if (!completer.isCompleted) {
completer.complete();
}
}
try {
// Wait for app to resume with 60 second timeout
await completer.future.timeout(
const Duration(seconds: 60),
onTimeout: () {
debugPrint('⚠️ Timeout waiting for app to resume');
},
);
} finally {
await subscription.cancel();
}
// Add small delay after resume to let things stabilize
await Future.delayed(const Duration(milliseconds: 300));
}
/// Handle retry delay with exponential backoff and validation
Future<void> _handleRetry({
required int attempt,
required String errorType,
required String endpoint,
required String errorDetails,
}) async {
// If app went to background during the error, wait for it to come back
if (_lifecycleService.isAppInBackground) {
debugPrint('⏸️ Error occurred while app in background, waiting for app to resume...');
await _waitForAppForeground();
}
// Check network connectivity before retrying
final hasConnection = await Utils.checkConnection(bypassConnectionCheck: true);
if (!hasConnection) {
debugPrint('⚠️ No network connection available, waiting before retry...');
// Wait a bit and check again instead of immediately throwing
await Future.delayed(const Duration(seconds: 2));
final recheckConnection = await Utils.checkConnection(bypassConnectionCheck: true);
if (!recheckConnection) {
throw SocketException('No network connection available for retry');
}
}
// Exponential backoff: 300ms, 900ms, 2700ms
final delay = Duration(milliseconds: 300 * (1 << attempt));
debugPrint(
'🔄 Retry attempt ${attempt + 1}/$_maxRetries for $endpoint\n'
' Error: $errorType - $errorDetails\n'
' Waiting: ${delay.inMilliseconds}ms\n'
' App State: ${_lifecycleService.currentState}'
);
await Future.delayed(delay);
}
/// Close the HTTP client
void dispose() {
_client.close();
}
}

@ -11,8 +11,15 @@ class ApiConsts {
static String baseUrl = 'https://hmgwebservices.com/'; // HIS API URL PROD static String baseUrl = 'https://hmgwebservices.com/'; // HIS API URL PROD
static String rcBaseUrl = 'https://rc.hmg.com/'; // dRC API URL PROD static String rcBaseUrl = 'https://rc.hmg.com/'; // dRC API URL PROD
static String hmgPharmacyApiBaseUrl = 'https://hmgpharmacyapi.hmg.com/'; // dRC API URL PROD static String hmgPharmacyApiBaseUrl = 'https://hmgpharmacyapi.hmg.com/'; // symptoms API URL PROD
static String symptomsCheckerApi = '${hmgPharmacyApiBaseUrl}symptomsapi/api/SymptomChecker'; // dRC API URL PROD static String symptomsCheckerApiUAT = '${hmgPharmacyApiBaseUrl}symptomsapi/api/SymptomChecker'; // dRC API URL PROD
static String symptomsCheckerApiLive = '${hmgPharmacyApiBaseUrl}symptomsapi_live/api/SymptomChecker'; // dRC API URL PROD
static String symptomsCheckerApi = '${hmgPharmacyApiBaseUrl}symptomsapi_live/api/SymptomChecker'; // dRC API URL PROD
static String symptomsCheckerScheduleAppointment = '$symptomsCheckerApi/ScheduleAppointment';
// Symptoms Checker Credentials
static String symptomsCheckerUsername = 'mobile_user';
static String symptomsCheckerPassword = '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92';
static var payFortEnvironment = FortEnvironment.production; static var payFortEnvironment = FortEnvironment.production;
static var applePayMerchantId = "merchant.com.hmgwebservices"; static var applePayMerchantId = "merchant.com.hmgwebservices";
@ -25,6 +32,7 @@ class ApiConsts {
static String GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; static String GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid=';
static String QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; static String QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail";
// static String QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail"; // static String QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail";
static String CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId="; static String CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=";
@ -46,6 +54,11 @@ class ApiConsts {
GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid=';
rcBaseUrl = 'https://rc.hmg.com/'; rcBaseUrl = 'https://rc.hmg.com/';
QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail"; QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail";
CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId=";
symptomsCheckerApi = symptomsCheckerApiLive;
symptomsCheckerUsername = 'mobile_user';
symptomsCheckerPassword = '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92';
CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId="; CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=";
break; break;
case AppEnvironmentTypeEnum.dev: case AppEnvironmentTypeEnum.dev:
@ -59,6 +72,10 @@ class ApiConsts {
rcBaseUrl = 'https://rc.hmg.com/uat/'; rcBaseUrl = 'https://rc.hmg.com/uat/';
QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail";
CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId="; CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=";
symptomsCheckerApi = symptomsCheckerApiUAT;
symptomsCheckerUsername = 'guest_user';
symptomsCheckerPassword = '123456';
break; break;
case AppEnvironmentTypeEnum.uat: case AppEnvironmentTypeEnum.uat:
baseUrl = "https://uat.hmgwebservices.com/"; baseUrl = "https://uat.hmgwebservices.com/";
@ -71,6 +88,10 @@ class ApiConsts {
rcBaseUrl = 'https://rc.hmg.com/uat/'; rcBaseUrl = 'https://rc.hmg.com/uat/';
QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail";
CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId="; CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=";
symptomsCheckerApi = symptomsCheckerApiUAT;
symptomsCheckerUsername = 'guest_user';
symptomsCheckerPassword = '123456';
break; break;
case AppEnvironmentTypeEnum.preProd: case AppEnvironmentTypeEnum.preProd:
baseUrl = "https://webservices.hmg.com/"; baseUrl = "https://webservices.hmg.com/";
@ -83,6 +104,10 @@ class ApiConsts {
rcBaseUrl = 'https://rc.hmg.com/'; rcBaseUrl = 'https://rc.hmg.com/';
QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail"; QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail";
CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId="; CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=";
symptomsCheckerApi = symptomsCheckerApiUAT;
symptomsCheckerUsername = 'guest_user';
symptomsCheckerPassword = '123456';
break; break;
case AppEnvironmentTypeEnum.qa: case AppEnvironmentTypeEnum.qa:
baseUrl = "https://uat.hmgwebservices.com/"; baseUrl = "https://uat.hmgwebservices.com/";
@ -95,6 +120,10 @@ class ApiConsts {
rcBaseUrl = 'https://rc.hmg.com/uat/'; rcBaseUrl = 'https://rc.hmg.com/uat/';
QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail";
CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId="; CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=";
symptomsCheckerApi = symptomsCheckerApiUAT;
symptomsCheckerUsername = 'guest_user';
symptomsCheckerPassword = '123456';
break; break;
case AppEnvironmentTypeEnum.staging: case AppEnvironmentTypeEnum.staging:
baseUrl = "https://uat.hmgwebservices.com/"; baseUrl = "https://uat.hmgwebservices.com/";
@ -107,6 +136,10 @@ class ApiConsts {
rcBaseUrl = 'https://rc.hmg.com/uat/'; rcBaseUrl = 'https://rc.hmg.com/uat/';
QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail"; QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail";
CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId="; CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=";
symptomsCheckerApi = symptomsCheckerApiUAT;
symptomsCheckerUsername = 'guest_user';
symptomsCheckerPassword = '123456';
break; break;
} }
} }
@ -189,7 +222,6 @@ class ApiConsts {
static final createEReferral = "Services/Patients.svc/REST/CreateEReferral"; static final createEReferral = "Services/Patients.svc/REST/CreateEReferral";
static final getEReferrals = "Services/Patients.svc/REST/GetEReferrals"; static final getEReferrals = "Services/Patients.svc/REST/GetEReferrals";
//WATER CONSUMPTION //WATER CONSUMPTION
static String h2oGetUserProgress = "Services/H2ORemainder.svc/REST/H2O_GetUserProgress"; static String h2oGetUserProgress = "Services/H2ORemainder.svc/REST/H2O_GetUserProgress";
static String h2oInsertUserActivity = "Services/H2ORemainder.svc/REST/H2O_InsertUserActivity"; static String h2oInsertUserActivity = "Services/H2ORemainder.svc/REST/H2O_InsertUserActivity";
@ -235,6 +267,7 @@ class ApiConsts {
static String getPatientBloodGroup = "services/PatientVarification.svc/REST/BloodDonation_GetBloodGroupDetails"; static String getPatientBloodGroup = "services/PatientVarification.svc/REST/BloodDonation_GetBloodGroupDetails";
static String getPatientBloodAgreement = "Services/PatientVarification.svc/REST/CheckUserAgreementForBloodDonation"; static String getPatientBloodAgreement = "Services/PatientVarification.svc/REST/CheckUserAgreementForBloodDonation";
static String getPatientBloodTypeNew = "Services/Patients.svc/REST/HIS_GetPatientBloodType_New"; static String getPatientBloodTypeNew = "Services/Patients.svc/REST/HIS_GetPatientBloodType_New";
// static String getAiOverViewLabOrders = "Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API"; // static String getAiOverViewLabOrders = "Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API";
// static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; // static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API";
@ -250,7 +283,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 = 20.9; static final double appVersionID = 21.0;
// static final double appVersionID = 50.7; // static final double appVersionID = 50.7;
static final int appChannelId = 3; static final int appChannelId = 3;
@ -806,7 +839,8 @@ var GET_CUSTOMER_INFO = "VerifyCustomer";
//Pharmacy //Pharmacy
var GET_PHARMACY_CATEGORISE = 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id=0'; var GET_PHARMACY_CATEGORISE =
'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id=0';
var GET_OFFERS_CATEGORISE = 'discountcategories'; var GET_OFFERS_CATEGORISE = 'discountcategories';
var GET_OFFERS_PRODUCTS = 'offerproducts/'; var GET_OFFERS_PRODUCTS = 'offerproducts/';
var GET_CATEGORISE_PARENT = 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id='; var GET_CATEGORISE_PARENT = 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id=';
@ -834,7 +868,7 @@ var FILTERED_PRODUCTS = 'products?categoryids=';
var GET_DOCTOR_LIST_CALCULATION = "Services/Doctors.svc/REST/GetCallculationDoctors"; var GET_DOCTOR_LIST_CALCULATION = "Services/Doctors.svc/REST/GetCallculationDoctors";
// var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC = "Services/Patients.svc/REST/GetDentalAppointments"; // var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC = "Services/Patients.svc/REST/GetDentalAppointments";
var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC ='Services/Patients.svc/REST/GetAllInvoices'; var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC = 'Services/Patients.svc/REST/GetAllInvoices';
var GET_DENTAL_APPOINTMENT_INVOICE = "Services/Patients.svc/REST/HIS_eInvoiceForDentalByAppointmentNo"; var GET_DENTAL_APPOINTMENT_INVOICE = "Services/Patients.svc/REST/HIS_eInvoiceForDentalByAppointmentNo";
var SEND_DENTAL_APPOINTMENT_INVOICE_EMAIL = "Services/Notifications.svc/REST/SendInvoiceForDental"; var SEND_DENTAL_APPOINTMENT_INVOICE_EMAIL = "Services/Notifications.svc/REST/SendInvoiceForDental";

@ -68,6 +68,7 @@ import 'package:hmg_patient_app_new/features/weather/weather_repo.dart';
import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart'; import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart';
import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart';
import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart';
import 'package:hmg_patient_app_new/services/app_lifecycle_service.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart'; 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';
@ -140,6 +141,9 @@ class AppDependencies {
loggerService: getIt(), loggerService: getIt(),
)); ));
// App Lifecycle Service - must be registered before ApiClient
getIt.registerLazySingleton<AppLifecycleService>(() => AppLifecycleService());
getIt.registerLazySingleton<ApiClient>(() => ApiClientImp(appState: getIt())); getIt.registerLazySingleton<ApiClient>(() => ApiClientImp(appState: getIt()));
getIt.registerLazySingleton<LocalAuthService>( getIt.registerLazySingleton<LocalAuthService>(
() => LocalAuthService(loggerService: getIt<LoggerService>(), localAuth: getIt<LocalAuthentication>()), () => LocalAuthService(loggerService: getIt<LoggerService>(), localAuth: getIt<LocalAuthentication>()),

@ -9,18 +9,17 @@ import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_callkit_incoming/entities/android_params.dart'; import 'package:flutter_callkit_incoming/entities/android_params.dart';
import 'package:flutter_callkit_incoming/entities/call_event.dart';
import 'package:flutter_callkit_incoming/entities/call_kit_params.dart'; import 'package:flutter_callkit_incoming/entities/call_kit_params.dart';
import 'package:flutter_callkit_incoming/entities/ios_params.dart'; import 'package:flutter_callkit_incoming/entities/ios_params.dart';
import 'package:flutter_callkit_incoming/entities/notification_params.dart'; import 'package:flutter_callkit_incoming/entities/notification_params.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
import 'package:flutter_ios_voip_kit_karmm/call_state_type.dart'; import 'package:flutter_ios_voip_kit_karmm/call_state_type.dart';
import 'package:flutter_ios_voip_kit_karmm/flutter_ios_voip_kit.dart'; import 'package:flutter_ios_voip_kit_karmm/flutter_ios_voip_kit.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@ -33,7 +32,7 @@ String? _currentSessionId; // Store session ID for API call
// |--> Push Notification Background // |--> Push Notification Background
@pragma('vm:entry-point') @pragma('vm:entry-point')
Future<dynamic> backgroundMessageHandler(dynamic message) async { Future<dynamic> backgroundMessageHandler(dynamic message) async {
print("Firebase backgroundMessageHandler!!!"); log("Firebase backgroundMessageHandler!!!");
await Firebase.initializeApp(); await Firebase.initializeApp();
fir.RemoteMessage message_; fir.RemoteMessage message_;
@ -47,20 +46,20 @@ Future<dynamic> backgroundMessageHandler(dynamic message) async {
callPage(String sessionID, String token) async {} callPage(String sessionID, String token) async {}
_incomingCall(Map<String, dynamic> data) async { _incomingCall(Map<String, dynamic> data) async {
print('the value of the _incomingCall remote message is $data'); log('the value of the _incomingCall remote message is $data');
// Check if there's already a call in progress to prevent duplicates // Check if there's already a call in progress to prevent duplicates
if (_isCallInProgress && _currentCallId != null) { if (_isCallInProgress && _currentCallId != null) {
print('⚠️ Call already in progress (ID: $_currentCallId), ignoring duplicate notification'); log('⚠️ Call already in progress (ID: $_currentCallId), ignoring duplicate notification');
return; return;
} }
String roomID = data['session_id'] ?? ''; String roomID = data['session_id'] ?? '';
String callTypeID = data['AppointmentNo'] ?? ''; String callTypeID = data['AppointmentNo'] ?? '';
print('🔍 Extracted from notification data:'); log('🔍 Extracted from notification data:');
print(' - roomID (session_id): "$roomID"'); log(' - roomID (session_id): "$roomID"');
print(' - callTypeID (AppointmentNo): "$callTypeID"'); log(' - callTypeID (AppointmentNo): "$callTypeID"');
// Generate unique call ID // Generate unique call ID
var _currentUuid = Uuid().v4(); var _currentUuid = Uuid().v4();
@ -68,16 +67,16 @@ _incomingCall(Map<String, dynamic> data) async {
_isCallInProgress = true; _isCallInProgress = true;
_currentSessionId = roomID; // Store session ID for decline API call _currentSessionId = roomID; // Store session ID for decline API call
print('📞 Starting new call with ID: $_currentCallId, Session: $_currentSessionId'); log('📞 Starting new call with ID: $_currentCallId, Session: $_currentSessionId');
await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, roomID); await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, roomID);
await Utils.saveStringFromPrefs(CacheConst.callTypeID, callTypeID); await Utils.saveStringFromPrefs(CacheConst.callTypeID, callTypeID);
await Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, true); await Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, true);
print('💾 Saved to cache:'); log('💾 Saved to cache:');
print(' - CacheConst.zoomRoomID: "$roomID"'); log(' - CacheConst.zoomRoomID: "$roomID"');
print(' - CacheConst.callTypeID: "$callTypeID"'); log(' - CacheConst.callTypeID: "$callTypeID"');
print(' - CacheConst.isAppOpenedFromCall: true'); log(' - CacheConst.isAppOpenedFromCall: true');
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -148,7 +147,7 @@ _incomingCall(Map<String, dynamic> data) async {
// Start 60-second timeout timer to auto-dismiss call // Start 60-second timeout timer to auto-dismiss call
_callTimeoutTimer?.cancel(); // Cancel any existing timer _callTimeoutTimer?.cancel(); // Cancel any existing timer
_callTimeoutTimer = Timer(Duration(seconds: 40), () async { _callTimeoutTimer = Timer(Duration(seconds: 40), () async {
print('⏱️ Call timeout (60 seconds) - auto dismissing call notification'); log('⏱️ Call timeout (60 seconds) - auto dismissing call notification');
await _endCallAndCleanup(_currentUuid); await _endCallAndCleanup(_currentUuid);
}); });
@ -158,8 +157,6 @@ _incomingCall(Map<String, dynamic> data) async {
// Helper method to end call and cleanup // Helper method to end call and cleanup
Future<void> _endCallAndCleanup(String callId, {bool isDeclined = false}) async { Future<void> _endCallAndCleanup(String callId, {bool isDeclined = false}) async {
try { try {
print('🧹 Cleaning up call: $callId');
// If call was declined, update session status via API // If call was declined, update session status via API
if (isDeclined && _currentSessionId != null && _currentSessionId!.isNotEmpty) { if (isDeclined && _currentSessionId != null && _currentSessionId!.isNotEmpty) {
await _updateSessionStatus(_currentSessionId!, 3, 'Patient'); await _updateSessionStatus(_currentSessionId!, 3, 'Patient');
@ -184,10 +181,7 @@ Future<void> _endCallAndCleanup(String callId, {bool isDeclined = false}) async
await Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, false); await Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, false);
await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, ''); await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, '');
await Utils.saveStringFromPrefs(CacheConst.callTypeID, ''); await Utils.saveStringFromPrefs(CacheConst.callTypeID, '');
print('✅ Call and push notification cleanup completed successfully');
} catch (e) { } catch (e) {
print('❌ Error during call cleanup: $e');
// Force reset flags even if there's an error // Force reset flags even if there's an error
_isCallInProgress = false; _isCallInProgress = false;
_currentCallId = null; _currentCallId = null;
@ -200,27 +194,20 @@ Future<void> _endCallAndCleanup(String callId, {bool isDeclined = false}) async
// Helper method to update session status via API // Helper method to update session status via API
Future<void> _updateSessionStatus(String sessionId, int sessionStatus, String sessionEndedBy) async { Future<void> _updateSessionStatus(String sessionId, int sessionStatus, String sessionEndedBy) async {
try { try {
final apiClient = getIt.get<ApiClient>(); final apiClient = getIt.get<ApiClient>();
Map<String, dynamic> requestBody = { Map<String, dynamic> requestBody = {"Open_SessionID": sessionId, "SessionStatus": sessionStatus, "SessionEndedBy": sessionEndedBy};
"Open_SessionID": sessionId,
"SessionStatus": sessionStatus,
"SessionEndedBy": sessionEndedBy
};
await apiClient.post( await apiClient.post(
CHANGE_PATIENT_ER_SESSION, CHANGE_PATIENT_ER_SESSION,
body: requestBody, body: requestBody,
onSuccess: (response, statusCode, {messageStatus, errorMessage}) { onSuccess: (response, statusCode, {messageStatus, errorMessage}) {},
print('✅ Session status updated successfully: $response');
},
onFailure: (error, statusCode, {messageStatus, failureType}) { onFailure: (error, statusCode, {messageStatus, failureType}) {
print('❌ Failed to update session status: $error'); log('❌ Failed to update session status: $error');
}, },
); );
} catch (e) { } catch (e) {
print('❌ Exception updating session status: $e'); log('❌ Exception updating session status: $e');
} }
} }
@ -252,7 +239,7 @@ Future<void> openCallPage(BuildContext context) async {
// ); // );
// } // }
} catch (err) { } catch (err) {
print(err); log(err.toString());
// await PlatformExceptionAlertDialog( // await PlatformExceptionAlertDialog(
// exception: Exception(err), // exception: Exception(err),
// ).show(context); // ).show(context);
@ -351,7 +338,7 @@ class PushNotificationHandler {
// int seconds = 30, // int seconds = 30,
// }) async { // }) async {
// timeOutTimer = Timer(Duration(seconds: seconds), () async { // timeOutTimer = Timer(Duration(seconds: seconds), () async {
// print('🎈 example: timeOut'); // log('🎈 example: timeOut');
// final incomingCallerName = await voIPKit.getIncomingCallerName(); // final incomingCallerName = await voIPKit.getIncomingCallerName();
// voIPKit.unansweredIncomingCall( // voIPKit.unansweredIncomingCall(
// skipLocalNotification: false, // skipLocalNotification: false,
@ -368,19 +355,19 @@ class PushNotificationHandler {
if (Platform.isIOS) { if (Platform.isIOS) {
voIPKit.getVoIPToken().then((value) { voIPKit.getVoIPToken().then((value) {
print("🎈 APNS VOIP KIT TOKEN: $value"); log("🎈 APNS VOIP KIT TOKEN: $value");
Utils.saveStringFromPrefs(CacheConst.voipToken, value ?? ""); Utils.saveStringFromPrefs(CacheConst.voipToken, value ?? "");
// AppSharedPreferences().setString(APNS_TOKEN, value!); // AppSharedPreferences().setString(APNS_TOKEN, value!);
}); });
voIPKit.onDidUpdatePushToken = (String token) { voIPKit.onDidUpdatePushToken = (String token) {
print('🎈 example: onDidUpdatePushToken: $token'); log('🎈 example: onDidUpdatePushToken: $token');
}; };
voIPKit.onDidReceiveIncomingPush = ( voIPKit.onDidReceiveIncomingPush = (
Map<String, dynamic> payload, Map<String, dynamic> payload,
) async { ) async {
print('🎈 example: onDidReceiveIncomingPush $payload'); log('🎈 example: onDidReceiveIncomingPush $payload');
// _timeOut(); // _timeOut();
}; };
@ -389,20 +376,22 @@ class PushNotificationHandler {
String callerId, String callerId,
) async { ) async {
try { try {
print('🎈 example: onDidRejectIncomingCall $uuid - $callerId'); log('🎈 example: onDidRejectIncomingCall $uuid - $callerId');
timeOutTimer.cancel(); timeOutTimer.cancel();
// Cleanup on reject with API call // Cleanup on reject with API call
if (_currentCallId != null) { if (_currentCallId != null) {
await _endCallAndCleanup(_currentCallId!, isDeclined: true); await _endCallAndCleanup(_currentCallId!, isDeclined: true);
} }
} catch (err) {} } catch (err) {
log(err.toString());
}
}; };
voIPKit.onDidAcceptIncomingCall = ( voIPKit.onDidAcceptIncomingCall = (
String uuid, String uuid,
String callerId, String callerId,
) async { ) async {
print('🎈 example: onDidAcceptIncomingCall $uuid - $callerId'); log('🎈 example: onDidAcceptIncomingCall $uuid - $callerId');
await voIPKit.acceptIncomingCall(callerState: CallStateType.calling); await voIPKit.acceptIncomingCall(callerState: CallStateType.calling);
await voIPKit.callConnected(); await voIPKit.callConnected();
@ -432,12 +421,12 @@ class PushNotificationHandler {
if (Platform.isAndroid) { if (Platform.isAndroid) {
try { try {
final fcmToken = await FirebaseMessaging.instance.getToken().catchError((err) { final fcmToken = await FirebaseMessaging.instance.getToken().catchError((err) {
print(err); log(err);
}); });
if (fcmToken != null) onToken(fcmToken); if (fcmToken != null) onToken(fcmToken);
// } // }
} catch (ex) { } catch (ex) {
print("Notification Exception: $ex"); log("Notification Exception: $ex");
} }
FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler); FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler);
} }
@ -471,7 +460,7 @@ class PushNotificationHandler {
} catch (ex) {} } catch (ex) {}
FirebaseMessaging.onMessage.listen((RemoteMessage message) async { FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
print("Firebase onMessage!!!"); log("Firebase onMessage!!!");
// showCallkitIncoming(); // showCallkitIncoming();
if (Platform.isIOS) { if (Platform.isIOS) {
await Future.delayed(Duration(milliseconds: 3000)).then((value) { await Future.delayed(Duration(milliseconds: 3000)).then((value) {
@ -483,7 +472,7 @@ class PushNotificationHandler {
}); });
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async { FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
print("Firebase onMessageOpenedApp!!!"); log("Firebase onMessageOpenedApp!!!");
if (Platform.isIOS) { if (Platform.isIOS) {
await Future.delayed(Duration(milliseconds: 3000)).then((value) { await Future.delayed(Duration(milliseconds: 3000)).then((value) {
newMessage(message); newMessage(message);
@ -495,21 +484,21 @@ class PushNotificationHandler {
if (Platform.isIOS) { if (Platform.isIOS) {
FirebaseMessaging.instance.getAPNSToken().then((String? token) { FirebaseMessaging.instance.getAPNSToken().then((String? token) {
print("Push Notification getAPNSToken: ${token!}"); log("Push Notification getAPNSToken: ${token!}");
}).catchError((err) { }).catchError((err) {
print("Push Notification getAPNSToken ERR: ${err.toString()}"); log("Push Notification getAPNSToken ERR: ${err.toString()}");
}); });
} }
FirebaseMessaging.instance.getToken().then((String? token) { FirebaseMessaging.instance.getToken().then((String? token) {
print("Push Notification getToken: ${token!}"); log("Push Notification getToken: ${token!}");
onToken(token!); onToken(token!);
}).catchError((err) { }).catchError((err) {
print(err); log(err);
}); });
FirebaseMessaging.instance.onTokenRefresh.listen((fcm_token) { FirebaseMessaging.instance.onTokenRefresh.listen((fcm_token) {
print("Push Notification onTokenRefresh: $fcm_token"); log("Push Notification onTokenRefresh: $fcm_token");
onToken(fcm_token); onToken(fcm_token);
}); });
@ -525,11 +514,11 @@ class PushNotificationHandler {
} }
newMessage(RemoteMessage remoteMessage) async { newMessage(RemoteMessage remoteMessage) async {
print("Remote Message: ${remoteMessage.data}"); log("Remote Message: ${remoteMessage.data}");
if (remoteMessage.data.isEmpty) { if (remoteMessage.data.isEmpty) {
return; return;
} }
debugPrint('the value of the remote message is ${remoteMessage.data}'); log('the value of the remote message is ${remoteMessage.data}');
if (remoteMessage.data['is_call'] == 'true' || remoteMessage.data['is_call'] == true) { if (remoteMessage.data['is_call'] == 'true' || remoteMessage.data['is_call'] == true) {
_incomingCall(remoteMessage.data); _incomingCall(remoteMessage.data);
// showCallkitIncoming(); // showCallkitIncoming();
@ -551,7 +540,7 @@ class PushNotificationHandler {
} }
onToken(String token) async { onToken(String token) async {
print("Push Notification Token: $token"); log("Push Notification Token: $token");
await Utils.saveStringFromPrefs(CacheConst.pushToken, token); await Utils.saveStringFromPrefs(CacheConst.pushToken, token);
} }
@ -573,11 +562,11 @@ class PushNotificationHandler {
// Permission.audio, // Permission.audio,
// Permission.microphone, // Permission.microphone,
].request(); ].request();
print("=-=-=-=-=-=-=-=-=-=-"); log("=-=-=-=-=-=-=-=-=-=-");
print(statuses[Permission.notification]); log(statuses[Permission.notification].toString());
} }
} catch (_) { } catch (_) {
debugPrint(_.toString()); log(_.toString());
} }
} }
} }

@ -1,4 +1,3 @@
import 'dart:developer';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flutter/material.dart'; // These are the Viewport values of your Figma Design. import 'package:flutter/material.dart'; // These are the Viewport values of your Figma Design.
@ -26,8 +25,14 @@ extension ResponsiveExtension on num {
/// Check if device is likely a foldable /// Check if device is likely a foldable
bool get _isFoldable { bool get _isFoldable {
double aspectRatio = _screenWidth / _screenHeight; double aspectRatio = _screenWidth / _screenHeight;
// Foldable devices typically have aspect ratios close to 1:1 when unfolded double shorterSide = _screenWidth < _screenHeight ? _screenWidth : _screenHeight;
return (aspectRatio > 0.9 && aspectRatio < 1.1) && (_screenWidth > 700 || _screenHeight > 700);
// Foldable devices (unfolded) typically have:
// - Shorter side > 600 logical pixels (to exclude regular phones)
// - Aspect ratio between 0.80 and 0.92 (almost square, like Galaxy Z Fold)
// Galaxy Z Fold 5: 1812x2176 physical, ~690x796 logical = 0.866 aspect ratio
// Regular phones: typically 375-430 width, aspect ratio 0.45-0.55
return (shorterSide > 600) && (aspectRatio > 0.80 && aspectRatio < 0.92);
} }
/// Scale text size - enhanced for foldable devices /// Scale text size - enhanced for foldable devices
@ -38,8 +43,13 @@ extension ResponsiveExtension on num {
// Enhanced clamping for different device types // Enhanced clamping for different device types
double clamp; double clamp;
if (SizeUtils.deviceType == DeviceType.tablet || _isFoldable) { if (SizeUtils.deviceType == DeviceType.tablet || _isFoldable) {
// More conservative scaling for tablets and foldables if (SizeUtils.deviceType != DeviceType.tablet && _isFoldable) {
clamp = (aspectRatio > 1.5 || aspectRatio < 0.67) ? 1.6 : 1.4; // clamp = (aspectRatio > 1.5 || aspectRatio < 0.67) ? 1.4 : 1.4;
clamp = 1.1;
} else {
// More conservative scaling for tablets and foldables
clamp = (aspectRatio > 1.5 || aspectRatio < 0.67) ? 1.6 : 1.4;
}
} else { } else {
// Original logic for phones // Original logic for phones
clamp = (aspectRatio > 1.3 || aspectRatio < 0.77) ? 1.6 : 1.2; clamp = (aspectRatio > 1.3 || aspectRatio < 0.77) ? 1.6 : 1.2;
@ -53,7 +63,7 @@ extension ResponsiveExtension on num {
double get w { double get w {
double baseScale = (this * _screenWidth) / figmaDesignWidth; double baseScale = (this * _screenWidth) / figmaDesignWidth;
if (_isFoldable|| isTablet ) { if (_isFoldable || isTablet) {
// For foldables, use more conservative width scaling // For foldables, use more conservative width scaling
double scale = _screenWidth / figmaDesignWidthTF; double scale = _screenWidth / figmaDesignWidthTF;
scale = scale.clamp(0.8, 1.4); scale = scale.clamp(0.8, 1.4);
@ -67,7 +77,7 @@ extension ResponsiveExtension on num {
double get h { double get h {
double baseScale = (this * _screenHeight) / figmaDesignHeight; double baseScale = (this * _screenHeight) / figmaDesignHeight;
if (_isFoldable || isTablet ) { if (_isFoldable || isTablet) {
// For foldables, use height-based scaling but with constraints // For foldables, use height-based scaling but with constraints
double scale = (_screenHeight / figmaDesignHeightTF).clamp(0.8, 1.4); double scale = (_screenHeight / figmaDesignHeightTF).clamp(0.8, 1.4);
return this * scale; return this * scale;
@ -225,10 +235,16 @@ class SizeUtils {
deviceType = DeviceType.mobile; deviceType = DeviceType.mobile;
} }
log("longerSide: $longerSide"); debugPrint("============ Device Detection ============");
log("shorterSide: $shorterSide"); debugPrint("longerSide: $longerSide");
log("isTablet: $isTablet"); debugPrint("shorterSide: $shorterSide");
log("isFoldable: $isFoldable"); debugPrint("width: $width");
debugPrint("height: $height");
debugPrint("deviceType: $deviceType");
debugPrint("isTablet: $isTablet");
debugPrint("isFoldable: $isFoldable");
debugPrint("aspectRatio: ${width / height}");
debugPrint("==========================================");
} }
} }
@ -241,6 +257,12 @@ bool get isDesktop => SizeUtils.deviceType == DeviceType.desktop;
bool get isFoldable { bool get isFoldable {
double aspectRatio = SizeUtils.width / SizeUtils.height; double aspectRatio = SizeUtils.width / SizeUtils.height;
// Foldable devices typically have aspect ratios close to 1:1 when unfolded double shorterSide = SizeUtils.width < SizeUtils.height ? SizeUtils.width : SizeUtils.height;
return (aspectRatio > 0.9 && aspectRatio < 1.1) && (SizeUtils.width > 700 || SizeUtils.height > 700);
// Foldable devices (unfolded) typically have:
// - Shorter side > 600 logical pixels (to exclude regular phones)
// - Aspect ratio between 0.80 and 0.92 (almost square, like Galaxy Z Fold)
// Galaxy Z Fold 5: 1812x2176 physical, ~690x796 logical = 0.866 aspect ratio
// Regular phones: typically 375-430 width, aspect ratio 0.45-0.55
return (shorterSide > 600) && (aspectRatio > 0.80 && aspectRatio < 0.92);
} }

@ -61,7 +61,8 @@ class Utils {
"ProjectOutSA": false, "ProjectOutSA": false,
"UsingInDoctorApp": false, "UsingInDoctorApp": false,
"IsHMC": false "IsHMC": false
},{ },
{
"Desciption": "Jeddah Fayhaa Hospital", "Desciption": "Jeddah Fayhaa Hospital",
"DesciptionN": "مستشفى جدة الفيحاء", "DesciptionN": "مستشفى جدة الفيحاء",
"ID": 3, // Campus ID "ID": 3, // Campus ID
@ -153,10 +154,10 @@ class Utils {
static String getDayMonthYearDateFormatted(DateTime? dateTime) { static String getDayMonthYearDateFormatted(DateTime? dateTime) {
if (dateTime == null) return ""; if (dateTime == null) return "";
return return
// appState.isArabic() // appState.isArabic()
// ? "${dateTime.day.toString()} ${getMonthArabic(dateTime.month)}, ${dateTime.year.toString()}" // ? "${dateTime.day.toString()} ${getMonthArabic(dateTime.month)}, ${dateTime.year.toString()}"
// : // :
"${dateTime.day.toString()} ${getMonth(dateTime.month)}, ${dateTime.year.toString()}"; "${dateTime.day.toString()} ${getMonth(dateTime.month)}, ${dateTime.year.toString()}";
} }
/// get month by /// get month by
@ -539,26 +540,26 @@ class Utils {
), ),
], ],
) )
: showOkButton? : showOkButton
Row( ? Row(
children: [ children: [
Expanded( Expanded(
child: CustomButton( child: CustomButton(
text: LocaleKeys.ok.tr(), text: LocaleKeys.ok.tr(),
onPressed: () async { onPressed: () async {
if (onConfirmTap != null) { if (onConfirmTap != null) {
onConfirmTap(); onConfirmTap();
} }
}, },
backgroundColor: AppColors.bgGreenColor, backgroundColor: AppColors.bgGreenColor,
borderColor: AppColors.bgGreenColor, borderColor: AppColors.bgGreenColor,
textColor: Colors.white, textColor: Colors.white,
// icon: AppAssets.confirm, // icon: AppAssets.confirm,
), ),
), ),
], ],
) )
:SizedBox.shrink(), : SizedBox.shrink(),
], ],
).center; ).center;
} }
@ -833,12 +834,16 @@ class Utils {
final iconH = height ?? 24.h; final iconH = height ?? 24.h;
final iconW = width ?? 24.w; final iconW = width ?? 24.w;
return Container( return Container(
width: iconW, height: iconH, width: iconW,
height: iconH,
decoration: BoxDecoration( decoration: BoxDecoration(
border: border != null ? Border.all(color: AppColors.whiteColor, width: border) : null, border: border != null ? Border.all(color: AppColors.whiteColor, width: border) : null,
borderRadius: borderRadius != null ? BorderRadius.circular(borderRadius ?? 12.r) : null, borderRadius: borderRadius != null ? BorderRadius.circular(borderRadius ?? 12.r) : null,
image: DecorationImage(image: AssetImage(icon,), fit: fit) image: DecorationImage(
), image: AssetImage(
icon,
),
fit: fit)),
); );
} }
@ -869,9 +874,8 @@ class Utils {
static Widget getPaymentMethods() { static Widget getPaymentMethods() {
return Row( return Row(
spacing: 6.w,
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
spacing: 5.w,
children: [ children: [
Image.asset(AppAssets.mada, width: 35.h, height: 35.h), Image.asset(AppAssets.mada, width: 35.h, height: 35.h),
Image.asset( Image.asset(
@ -1025,7 +1029,6 @@ class Utils {
isHMC: hospital.isHMC); isHMC: hospital.isHMC);
} }
static HospitalsModel? convertToHospitalsModel(PatientDoctorAppointmentList? item) { static HospitalsModel? convertToHospitalsModel(PatientDoctorAppointmentList? item) {
if (item == null) return null; if (item == null) return null;
return HospitalsModel( return HospitalsModel(

@ -115,12 +115,14 @@ extension EmailValidator on String {
bool isCenter = false, bool isCenter = false,
double? height, double? height,
double? letterSpacing, double? letterSpacing,
TextOverflow? textOverflow,
int maxLine = 0}) => int maxLine = 0}) =>
Text( Text(
this, this,
textAlign: isCenter ? TextAlign.center : textAlignment, textAlign: isCenter ? TextAlign.center : textAlignment,
maxLines: (maxLine > 0) ? maxLine : null, maxLines: (maxLine > 0) ? maxLine : null,
style: TextStyle( style: TextStyle(
overflow: textOverflow,
fontSize: 12.f, fontSize: 12.f,
fontWeight: fontWeight ?? (isBold ? FontWeight.bold : FontWeight.normal), fontWeight: fontWeight ?? (isBold ? FontWeight.bold : FontWeight.normal),
color: color ?? AppColors.blackColor, color: color ?? AppColors.blackColor,

@ -421,14 +421,14 @@ class BookAppointmentsViewModel extends ChangeNotifier {
}); });
} }
} else { } else {
for (var group in doctorsListGrouped) { // for (var group in doctorsListGrouped) {
group.sort((a, b) { // group.sort((a, b) {
var aSlot = a.decimalDoctorRate; // var aSlot = a.decimalDoctorRate;
var bSlot = b.decimalDoctorRate; // var bSlot = b.decimalDoctorRate;
if (aSlot == null || bSlot == null) return 0; // if (aSlot == null || bSlot == null) return 0;
return bSlot.compareTo(aSlot); // return bSlot.compareTo(aSlot);
}); // });
} // }
// doctorsList.sort((a, b) => b.decimalDoctorRate!.compareTo(a.decimalDoctorRate!)); // doctorsList.sort((a, b) => b.decimalDoctorRate!.compareTo(a.decimalDoctorRate!));
} }
@ -677,7 +677,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
doctorsList = apiResponse.data!; doctorsList = apiResponse.data!;
filteredDoctorList = doctorsList; filteredDoctorList = doctorsList;
isDoctorsListLoading = false; isDoctorsListLoading = false;
doctorsList.sort((a, b) => b.decimalDoctorRate!.compareTo(a.decimalDoctorRate!)); // doctorsList.sort((a, b) => b.decimalDoctorRate!.compareTo(a.decimalDoctorRate!));
initializeFilteredList(); initializeFilteredList();
clearSearchFilters(); clearSearchFilters();
getFiltersFromDoctorList(); getFiltersFromDoctorList();

@ -944,11 +944,11 @@ class HmgServicesRepoImp implements HmgServicesRepo {
final vitalSign = VitalSignResModel.fromJson(vitalSignJson); final vitalSign = VitalSignResModel.fromJson(vitalSignJson);
// Debug logging for blood pressure // Debug logging for blood pressure
print('=== Repository Blood Pressure Check ==='); // print('=== Repository Blood Pressure Check ===');
print('bloodPressureHigher: ${vitalSign.bloodPressureHigher}'); // print('bloodPressureHigher: ${vitalSign.bloodPressureHigher}');
print('bloodPressureLower: ${vitalSign.bloodPressureLower}'); // print('bloodPressureLower: ${vitalSign.bloodPressureLower}');
print('weightKg: ${vitalSign.weightKg}'); // print('weightKg: ${vitalSign.weightKg}');
print('heightCm: ${vitalSign.heightCm}'); // print('heightCm: ${vitalSign.heightCm}');
// Check if the record has at least one valid vital sign measurement // Check if the record has at least one valid vital sign measurement
final hasValidWeight = _isValidValue(vitalSign.weightKg); final hasValidWeight = _isValidValue(vitalSign.weightKg);

@ -3,7 +3,7 @@ import 'dart:math';
import 'package:hmg_patient_app_new/core/common_models/data_points.dart'; import 'package:hmg_patient_app_new/core/common_models/data_points.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'model/Vitals.dart'; import 'model/vitals_data_model.dart';
enum Durations { enum Durations {
daily("daily"), daily("daily"),

@ -1,18 +1,17 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:health/health.dart'; import 'package:health/health.dart';
import 'package:hmg_patient_app_new/core/common_models/data_points.dart';
import 'package:hmg_patient_app_new/core/common_models/smart_watch.dart'; import 'package:hmg_patient_app_new/core/common_models/smart_watch.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/loading_utils.dart';
import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_service.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_service.dart';
import 'package:hmg_patient_app_new/presentation/smartwatches/activity_detail.dart';
import 'package:hmg_patient_app_new/presentation/smartwatches/smart_watches_health_data_screen.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import '../../core/common_models/data_points.dart'; import 'health_data_transformations.dart';
import '../../core/dependencies.dart'; import 'model/vitals_data_model.dart';
import '../../presentation/smartwatches/activity_detail.dart' show ActivityDetails;
import '../../presentation/smartwatches/smart_watch_activity.dart' show SmartWatchActivity;
import '../../services/navigation_service.dart' show NavigationService;
import 'HealthDataTransformation.dart';
import 'model/Vitals.dart';
class HealthProvider with ChangeNotifier { class HealthProvider with ChangeNotifier {
final HealthService _healthService = HealthService(); final HealthService _healthService = HealthService();
@ -22,7 +21,8 @@ class HealthProvider with ChangeNotifier {
String selectedTimeRange = '7D'; String selectedTimeRange = '7D';
int selectedTabIndex = 0; int selectedTabIndex = 0;
SmartWatchTypes? selectedWatchType ; SmartWatchTypes? selectedWatchType;
String selectedWatchURL = 'assets/images/png/smartwatches/apple-watch-5.jpg'; String selectedWatchURL = 'assets/images/png/smartwatches/apple-watch-5.jpg';
HealthDataTransformation healthDataTransformation = HealthDataTransformation(); HealthDataTransformation healthDataTransformation = HealthDataTransformation();
@ -90,7 +90,7 @@ class HealthProvider with ChangeNotifier {
healthData[type] = data; healthData[type] = data;
notifyListeners(); notifyListeners();
} catch (e) { } catch (e) {
print('Error refreshing metric $type: $e'); debugPrint('Error refreshing metric $type: $e');
} }
} }
@ -129,14 +129,12 @@ class HealthProvider with ChangeNotifier {
await getVitals(); await getVitals();
// LoaderBottomSheet.hideLoader(); // LoaderBottomSheet.hideLoader();
// await Future.delayed(Duration(seconds: 5)); // await Future.delayed(Duration(seconds: 5));
getIt.get<NavigationService>().pushPage(page: SmartWatchActivity()); getIt.get<NavigationService>().pushPage(page: SmartWatchesHealthDataScreen());
print('Device initialized successfully');
} }
notifyListeners(); notifyListeners();
} }
Future<void> getVitals() async { Future<void> getVitals() async {
final result = await _healthService.getVitals(); final result = await _healthService.getVitals();
vitals = result; vitals = result;
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
@ -186,15 +184,17 @@ class HealthProvider with ChangeNotifier {
} }
selectedData = yearly = healthDataTransformation.transformVitalsToDataPoints(vitals!, Durations.yearly.value, selectedSection); selectedData = yearly = healthDataTransformation.transformVitalsToDataPoints(vitals!, Durations.yearly.value, selectedSection);
break; break;
default:
{}
;
} }
notifyListeners(); notifyListeners();
} }
void navigateToDetails(String value, {required String sectionName, required String uom}) { void navigateToDetails(String value, {required String sectionName, required String uom}) {
getIt.get<NavigationService>().pushPage(page: ActivityDetails(selectedActivity: value, sectionName:sectionName, uom: uom,)); getIt.get<NavigationService>().pushPage(
page: ActivityDetails(
selectedActivity: value,
sectionName: sectionName,
uom: uom,
));
} }
void saveSelectedSection(String value) { void saveSelectedSection(String value) {
@ -243,7 +243,6 @@ class HealthProvider with ChangeNotifier {
count++; count++;
} }
}); });
print("total count is $count and total is $total");
averageValue = count > 0 ? total / count : null; averageValue = count > 0 ? total / count : null;
notifyListeners(); notifyListeners();
} }
@ -261,7 +260,7 @@ class HealthProvider with ChangeNotifier {
String firstNonEmptyValue(List<Vitals> dataPoints) { String firstNonEmptyValue(List<Vitals> dataPoints) {
try { try {
return dataPoints.firstWhere((dp) => dp.value != null && dp.value!.trim().isNotEmpty).value; return dataPoints.firstWhere((dp) => dp.value.trim().isNotEmpty).value;
} catch (e) { } catch (e) {
return "0"; // no non-empty value found return "0"; // no non-empty value found
} }

@ -5,7 +5,7 @@ import 'dart:io';
import 'package:health/health.dart'; import 'package:health/health.dart';
import 'package:hmg_patient_app_new/core/common_models/smart_watch.dart'; import 'package:hmg_patient_app_new/core/common_models/smart_watch.dart';
import 'package:hmg_patient_app_new/features/smartwatch_health_data/model/Vitals.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/model/vitals_data_model.dart';
import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/create_watch_helper.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/create_watch_helper.dart';
import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart'; import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';

@ -1,5 +1,5 @@
class Vitals { class Vitals {
String value; String value;
final String timestamp; final String timestamp;
final String unitOfMeasure; final String unitOfMeasure;
@ -16,11 +16,6 @@ class Vitals {
unitOfMeasure: map['uom'] ?? "", unitOfMeasure: map['uom'] ?? "",
); );
} }
toString(){
return "{\"value\": \"$value\", \"timeStamp\": \"$timestamp\", \"uom\": \"$unitOfMeasure\"}";
}
} }
class VitalsWRTType { class VitalsWRTType {
@ -31,15 +26,21 @@ class VitalsWRTType {
final List<Vitals> activity; final List<Vitals> activity;
final List<Vitals> bodyOxygen; final List<Vitals> bodyOxygen;
final List<Vitals> bodyTemperature; final List<Vitals> bodyTemperature;
double maxHeartRate = double.negativeInfinity; double maxHeartRate = double.negativeInfinity;
double maxSleep = double.negativeInfinity; double maxSleep = double.negativeInfinity;
double maxStep= double.negativeInfinity; double maxStep = double.negativeInfinity;
double maxActivity = double.negativeInfinity; double maxActivity = double.negativeInfinity;
double maxBloodOxygen = double.negativeInfinity; double maxBloodOxygen = double.negativeInfinity;
double maxBodyTemperature = double.negativeInfinity; double maxBodyTemperature = double.negativeInfinity;
VitalsWRTType({required this.distance, required this.bodyOxygen, required this.bodyTemperature, required this.heartRate, required this.sleep, required this.step, required this.activity}); VitalsWRTType(
{required this.distance,
required this.bodyOxygen,
required this.bodyTemperature,
required this.heartRate,
required this.sleep,
required this.step,
required this.activity});
factory VitalsWRTType.fromMap(Map<dynamic, dynamic> map) { factory VitalsWRTType.fromMap(Map<dynamic, dynamic> map) {
List<Vitals> activity = []; List<Vitals> activity = [];
@ -82,16 +83,23 @@ class VitalsWRTType {
map["distance"].forEach((element) { map["distance"].forEach((element) {
element["uom"] = "km"; element["uom"] = "km";
var data = Vitals.fromMap(element); var data = Vitals.fromMap(element);
data.value = (double.parse(data.value)/1000).toStringAsFixed(2); data.value = (double.parse(data.value) / 1000).toStringAsFixed(2);
distance.add(data); distance.add(data);
}); });
return VitalsWRTType(bodyTemperature: bodyTemperature, bodyOxygen: bodyOxygen, heartRate: heartRate, sleep: sleeps, step: steps, activity: activity, distance: distance); return VitalsWRTType(
bodyTemperature: bodyTemperature,
bodyOxygen: bodyOxygen,
heartRate: heartRate,
sleep: sleeps,
step: steps,
activity: activity,
distance: distance);
} }
Map<String, List<Vitals>> getVitals() { Map<String, List<Vitals>> getVitals() {
return { return {
"heartRate": heartRate , "heartRate": heartRate,
"sleep": sleep, "sleep": sleep,
"steps": step, "steps": step,
"activity": activity, "activity": activity,

@ -6,7 +6,7 @@ import 'package:health/health.dart';
import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart' show WatchHelper; import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart' show WatchHelper;
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import '../model/Vitals.dart'; import '../model/vitals_data_model.dart';
class HealthConnectHelper extends WatchHelper { class HealthConnectHelper extends WatchHelper {
final Health health = Health(); final Health health = Health();

@ -0,0 +1,48 @@
class ScheduleAppointmentRequestModel {
final String generalId;
final String fileNo;
final String appointmentNo;
final String doctorId;
final String appointmentDate;
final String mobileNumber;
final int projectId;
final int clinicId;
ScheduleAppointmentRequestModel({
required this.generalId,
required this.fileNo,
required this.appointmentNo,
required this.doctorId,
required this.appointmentDate,
required this.mobileNumber,
required this.projectId,
required this.clinicId,
});
Map<String, dynamic> toJson() {
return {
'generalId': generalId,
'fileNo': fileNo,
'appointmentNo': appointmentNo,
'doctorId': doctorId,
'appointmentDate': appointmentDate,
'mobileNumber': mobileNumber,
'projectId': projectId,
'clinicId': clinicId,
};
}
factory ScheduleAppointmentRequestModel.fromJson(Map<String, dynamic> json) {
return ScheduleAppointmentRequestModel(
generalId: json['generalId'] ?? '',
fileNo: json['fileNo'] ?? '',
appointmentNo: json['appointmentNo'] ?? '',
doctorId: json['doctorId'] ?? '',
appointmentDate: json['appointmentDate'] ?? '',
mobileNumber: json['mobileNumber'] ?? '',
projectId: json['projectId'] ?? 0,
clinicId: json['clinicId'] ?? 0,
);
}
}

@ -0,0 +1,32 @@
class ScheduleAppointmentResponseModel {
final bool? success;
final String? message;
final String? appointmentId;
final dynamic data;
ScheduleAppointmentResponseModel({
this.success,
this.message,
this.appointmentId,
this.data,
});
factory ScheduleAppointmentResponseModel.fromJson(Map<String, dynamic> json) {
return ScheduleAppointmentResponseModel(
success: json['success'],
message: json['message'],
appointmentId: json['appointmentId'],
data: json['data'],
);
}
Map<String, dynamic> toJson() {
return {
'success': success,
'message': message,
'appointmentId': appointmentId,
'data': data,
};
}
}

@ -6,9 +6,11 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/req_models/schedule_appointment_request_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/schedule_appointment_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart';
@ -17,6 +19,7 @@ abstract class SymptomsCheckerRepo {
Future<Either<Failure, GenericApiModel<SymptomsUserDetailsResponseModel>>> getUserDetails({ Future<Either<Failure, GenericApiModel<SymptomsUserDetailsResponseModel>>> getUserDetails({
required String userName, required String userName,
required String password, required String password,
String? fileNo,
}); });
Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>> getBodySymptomsByName({ Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>> getBodySymptomsByName({
@ -58,6 +61,11 @@ abstract class SymptomsCheckerRepo {
required String language, required String language,
required String userSessionToken, required String userSessionToken,
}); });
Future<Either<Failure, GenericApiModel<ScheduleAppointmentResponseModel>>> saveAppointmentDetailsForSymptomsChecker({
required ScheduleAppointmentRequestModel request,
required String userSessionToken,
});
} }
class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
@ -70,8 +78,17 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
Future<Either<Failure, GenericApiModel<SymptomsUserDetailsResponseModel>>> getUserDetails({ Future<Either<Failure, GenericApiModel<SymptomsUserDetailsResponseModel>>> getUserDetails({
required String userName, required String userName,
required String password, required String password,
String? fileNo,
}) async { }) async {
Map<String, dynamic> body = {"userName": userName, "password": password}; Map<String, dynamic> body = {
"userName": userName,
"password": password,
};
// Add fileNo only if provided (user is logged in)
if (fileNo != null && fileNo.isNotEmpty) {
body["fileNo"] = fileNo;
}
try { try {
GenericApiModel<SymptomsUserDetailsResponseModel>? apiResponse; GenericApiModel<SymptomsUserDetailsResponseModel>? apiResponse;
@ -409,7 +426,6 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': 'Bearer $userSessionToken', 'Authorization': 'Bearer $userSessionToken',
}; };
Map<String, dynamic> body = {};
try { try {
GenericApiModel<List<GetClinicDetailsResponseModel>>? apiResponse; GenericApiModel<List<GetClinicDetailsResponseModel>>? apiResponse;
@ -456,4 +472,61 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
return Left(UnknownFailure(e.toString())); return Left(UnknownFailure(e.toString()));
} }
} }
@override
Future<Either<Failure, GenericApiModel<ScheduleAppointmentResponseModel>>> saveAppointmentDetailsForSymptomsChecker({
required ScheduleAppointmentRequestModel request,
required String userSessionToken,
}) async {
Map<String, String> headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer $userSessionToken',
};
final body = request.toJson();
try {
GenericApiModel<ScheduleAppointmentResponseModel>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.symptomsCheckerScheduleAppointment,
apiHeaders: headers,
body: body,
isExternal: true,
isAllowAny: true,
onFailure: (error, statusCode, {messageStatus, failureType}) {
loggerService.logError("ScheduleAppointment API Failed: $error");
failure = failureType ?? ServerFailure(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
// Parse response if it's a string
final Map<String, dynamic> responseData = response is String ? jsonDecode(response) : response;
ScheduleAppointmentResponseModel scheduleAppointmentResponse = ScheduleAppointmentResponseModel.fromJson(responseData);
apiResponse = GenericApiModel<ScheduleAppointmentResponseModel>(
messageStatus: messageStatus ?? 1,
statusCode: statusCode,
errorMessage: errorMessage,
data: scheduleAppointmentResponse,
);
} catch (e, stackTrace) {
loggerService.logError("Error parsing ScheduleAppointment response: $e");
loggerService.logError("StackTrace: $stackTrace");
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e, stackTrace) {
loggerService.logError("Exception in scheduleAppointment: $e");
loggerService.logError("StackTrace: $stackTrace");
return Left(UnknownFailure(e.toString()));
}
}
} }

@ -1,13 +1,16 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.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/enums.dart'; import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/data/organ_mapping_data.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/data/organ_mapping_data.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/req_models/schedule_appointment_request_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/schedule_appointment_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_repo.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_repo.dart';
@ -62,6 +65,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
bool isRiskFactorsLoading = false; bool isRiskFactorsLoading = false;
bool isSuggestionsLoading = false; bool isSuggestionsLoading = false;
bool isTriageDiagnosisLoading = false; bool isTriageDiagnosisLoading = false;
bool isScheduleAppointmentLoading = false;
// Flag to track if appointment is being booked via symptoms checker flow
bool isBookingFromSymptomsChecker = false;
// API data storage - using API models directly // API data storage - using API models directly
SymptomsUserDetailsResponseModel? symptomsUserDetailsResponseModel; SymptomsUserDetailsResponseModel? symptomsUserDetailsResponseModel;
@ -76,6 +83,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
final List<Map<String, String>> _triageEvidenceList = []; // Store triage evidence with proper format final List<Map<String, String>> _triageEvidenceList = []; // Store triage evidence with proper format
int _triageQuestionCount = 0; // Track number of triage questions answered int _triageQuestionCount = 0; // Track number of triage questions answered
// For type=1 questions: single selection across all items
String? _selectedSingleItemId; // Store which item was selected
int? _selectedSingleChoiceIndex; // Store which choice was selected
// Selected risk factors tracking // Selected risk factors tracking
final Set<String> _selectedRiskFactorIds = {}; final Set<String> _selectedRiskFactorIds = {};
@ -85,6 +96,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
// Selected symptoms tracking (organId -> Set of symptom IDs) // Selected symptoms tracking (organId -> Set of symptom IDs)
final Map<String, Set<String>> _selectedSymptomsByOrgan = {}; final Map<String, Set<String>> _selectedSymptomsByOrgan = {};
// Symptom search/filter state
String _symptomSearchQuery = '';
List<OrganSymptomResult> _filteredOrganSymptomsResults = [];
// User Info Flow State // User Info Flow State
int _userInfoCurrentPage = 0; int _userInfoCurrentPage = 0;
bool _isSinglePageEditMode = false; // Track if editing single page or full flow bool _isSinglePageEditMode = false; // Track if editing single page or full flow
@ -170,12 +185,43 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
return _selectedTriageChoicesByItemId[itemId]; return _selectedTriageChoicesByItemId[itemId];
} }
/// Check if current question type is single selection (type=1)
bool get isTriageQuestionSingleSelection => currentTriageQuestion?.type == 1;
/// Check if current question type is multi-item selection (type=2)
bool get isTriageQuestionMultiItem => currentTriageQuestion?.type == 2;
/// For type=1 questions: check if a specific item is selected (ignore choiceIndex)
bool isTriageSingleOptionSelected(String itemId, int choiceIndex) {
return _selectedSingleItemId == itemId;
}
/// Get selected item ID for type=1 questions
String? get selectedSingleItemId => _selectedSingleItemId;
/// Get selected choice index for type=1 questions
int? get selectedSingleChoiceIndex => _selectedSingleChoiceIndex;
/// Set flag for booking from symptoms checker
void setBookingFromSymptomsChecker(bool value) {
isBookingFromSymptomsChecker = value;
log("isBookingFromSymptomsChecker: $isBookingFromSymptomsChecker");
notifyListeners();
}
/// Check if all items in current question have been answered /// Check if all items in current question have been answered
bool get areAllTriageItemsAnswered { bool get areAllTriageItemsAnswered {
if (currentTriageQuestion?.items == null || currentTriageQuestion!.items!.isEmpty) { if (currentTriageQuestion?.items == null || currentTriageQuestion!.items!.isEmpty) {
return false; return false;
} }
// Type 1: Single selection mode - check if any option is selected
if (isTriageQuestionSingleSelection) {
return _selectedSingleItemId != null && _selectedSingleChoiceIndex != null;
}
// Type 2: Multi-item selection mode - check if all items have answers
// Check if we have an answer for each item // Check if we have an answer for each item
for (var item in currentTriageQuestion!.items!) { for (var item in currentTriageQuestion!.items!) {
if (item.id != null && !_selectedTriageChoicesByItemId.containsKey(item.id)) { if (item.id != null && !_selectedTriageChoicesByItemId.containsKey(item.id)) {
@ -207,6 +253,28 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
return bodySymptomResponse!.dataDetails!.result ?? []; return bodySymptomResponse!.dataDetails!.result ?? [];
} }
/// Get filtered organ symptoms results based on search query
List<OrganSymptomResult> get filteredOrganSymptomsResults {
if (_symptomSearchQuery.isEmpty) {
return organSymptomsResults;
}
return _filteredOrganSymptomsResults;
}
/// Get current search query
String get symptomSearchQuery => _symptomSearchQuery;
/// Get all symptoms from all organs (for search suggestions)
List<BodySymptom> get allSymptoms {
List<BodySymptom> symptoms = [];
for (var organResult in organSymptomsResults) {
if (organResult.bodySymptoms != null) {
symptoms.addAll(organResult.bodySymptoms!);
}
}
return symptoms;
}
int get totalSelectedSymptomsCount { int get totalSelectedSymptomsCount {
return _selectedSymptomsByOrgan.values.fold(0, (sum, symptomIds) => sum + symptomIds.length); return _selectedSymptomsByOrgan.values.fold(0, (sum, symptomIds) => sum + symptomIds.length);
} }
@ -277,7 +345,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
} }
} }
toggleZoomOut(){ toggleZoomOut() {
if (_currentZoomScale > _minZoomScale) { if (_currentZoomScale > _minZoomScale) {
_currentZoomScale = (_currentZoomScale - _zoomStep).clamp(_minZoomScale, _maxZoomScale); _currentZoomScale = (_currentZoomScale - _zoomStep).clamp(_minZoomScale, _maxZoomScale);
notifyListeners(); notifyListeners();
@ -427,6 +495,51 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
/// Filter symptoms based on search query
void filterSymptoms(String query, {bool isArabic = false}) {
_symptomSearchQuery = query;
if (query.isEmpty) {
_filteredOrganSymptomsResults.clear();
notifyListeners();
return;
}
final lowercaseQuery = query.toLowerCase();
_filteredOrganSymptomsResults = [];
for (var organResult in organSymptomsResults) {
if (organResult.bodySymptoms == null || organResult.bodySymptoms!.isEmpty) {
continue;
}
// Filter symptoms that match the query
final filteredSymptoms = organResult.bodySymptoms!.where((symptom) {
final displayName = symptom.getDisplayName(isArabic).toLowerCase();
return displayName.contains(lowercaseQuery);
}).toList();
// Only add organ result if it has matching symptoms
if (filteredSymptoms.isNotEmpty) {
_filteredOrganSymptomsResults.add(
OrganSymptomResult(
name: organResult.name,
bodySymptoms: filteredSymptoms,
),
);
}
}
notifyListeners();
}
/// Clear symptom search filter
void clearSymptomFilter() {
_symptomSearchQuery = '';
_filteredOrganSymptomsResults.clear();
notifyListeners();
}
// Risk Factors Methods // Risk Factors Methods
/// Toggle risk factor selection /// Toggle risk factor selection
@ -846,7 +959,22 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/// Select a choice for a specific item (for multi-item questions) /// Select a choice for a specific item (for multi-item questions)
void selectTriageChoiceForItem(String itemId, int choiceIndex) { void selectTriageChoiceForItem(String itemId, int choiceIndex) {
_selectedTriageChoicesByItemId[itemId] = choiceIndex; // Type 1: Single selection mode - only one ITEM can be selected (choice is always "Yes")
if (isTriageQuestionSingleSelection) {
// If same item clicked again, deselect it
if (_selectedSingleItemId == itemId) {
_selectedSingleItemId = null;
_selectedSingleChoiceIndex = null;
} else {
// Select new item, clear previous selection
_selectedSingleItemId = itemId;
// For type=1, we don't use choiceIndex from UI, we'll find "Yes" choice in the API call
_selectedSingleChoiceIndex = 0; // Placeholder, actual Yes choice will be found later
}
} else {
// Type 2: Multi-item selection mode - each item can have one selected option
_selectedTriageChoicesByItemId[itemId] = choiceIndex;
}
notifyListeners(); notifyListeners();
} }
@ -854,6 +982,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
void resetTriageChoice() { void resetTriageChoice() {
_selectedTriageChoiceIndex = null; _selectedTriageChoiceIndex = null;
_selectedTriageChoicesByItemId.clear(); _selectedTriageChoicesByItemId.clear();
_selectedSingleItemId = null;
_selectedSingleChoiceIndex = null;
_triageQuestionCount++; // Increment question count _triageQuestionCount++; // Increment question count
notifyListeners(); notifyListeners();
} }
@ -889,15 +1019,20 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
_selectedTriageChoicesByItemId.clear(); _selectedTriageChoicesByItemId.clear();
_triageQuestionCount = 0; // Reset question count _triageQuestionCount = 0; // Reset question count
_currentZoomScale = 1.0; // Reset zoom scale _currentZoomScale = 1.0; // Reset zoom scale
_symptomSearchQuery = ''; // Reset search query
_filteredOrganSymptomsResults.clear(); // Clear filtered results
bodySymptomResponse = null; bodySymptomResponse = null;
riskFactorsResponse = null; riskFactorsResponse = null;
suggestionsResponse = null; suggestionsResponse = null;
triageDataDetails = null; triageDataDetails = null;
isTriageDiagnosisLoading = false; isTriageDiagnosisLoading = false;
_selectedTriageChoiceIndex = null; _selectedTriageChoiceIndex = null;
_selectedSingleItemId = null;
_selectedSingleChoiceIndex = null;
_isBottomSheetExpanded = false; _isBottomSheetExpanded = false;
_tooltipTimer?.cancel(); _tooltipTimer?.cancel();
_tooltipOrganId = null; _tooltipOrganId = null;
isBookingFromSymptomsChecker = false; // Reset booking flag
// Reset user info flow // Reset user info flow
_userInfoCurrentPage = 0; _userInfoCurrentPage = 0;
_isSinglePageEditMode = false; _isSinglePageEditMode = false;
@ -911,6 +1046,26 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
/// Clear user selections (organs, symptoms, risk factors, questions) but keep results
/// This is useful when user reaches results page and we want to prevent going back to edit selections
void clearSelectionsKeepResults() {
_selectedOrganIds.clear();
_selectedSymptomsByOrgan.clear();
_selectedRiskFactorIds.clear();
_selectedSuggestionsIds.clear();
_triageEvidenceList.clear();
_selectedTriageChoicesByItemId.clear();
_selectedSingleItemId = null;
_selectedSingleChoiceIndex = null;
_symptomSearchQuery = '';
_filteredOrganSymptomsResults.clear();
_isBottomSheetExpanded = false;
_tooltipTimer?.cancel();
_tooltipOrganId = null;
// Keep: bodySymptomResponse, riskFactorsResponse, suggestionsResponse, triageDataDetails, user info, booking flag
notifyListeners();
}
// User Info Flow Methods // User Info Flow Methods
/// Set current page in user info flow /// Set current page in user info flow
@ -1018,12 +1173,17 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
Future<void> getSymptomsUserDetails({ Future<void> getSymptomsUserDetails({
required String userName, required String userName,
required String password, required String password,
String? fileNo,
Function()? onSuccess, Function()? onSuccess,
Function(String)? onError, Function(String)? onError,
}) async { }) async {
isBodySymptomsLoading = true; isBodySymptomsLoading = true;
notifyListeners(); notifyListeners();
final result = await symptomsCheckerRepo.getUserDetails(userName: userName, password: password); final result = await symptomsCheckerRepo.getUserDetails(
userName: userName,
password: password,
fileNo: fileNo,
);
result.fold( result.fold(
(failure) async { (failure) async {
@ -1131,6 +1291,67 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
); );
} }
/// Schedule appointment for symptoms checker
Future<void> saveAppointmentDetailsForSymptomsChecker({
required String fileNo,
required String appointmentNo,
required String doctorId,
required String appointmentDate,
required String mobileNumber,
required int projectId,
required int clinicId,
Function(ScheduleAppointmentResponseModel)? onSuccess,
Function(String)? onError,
}) async {
isScheduleAppointmentLoading = true;
notifyListeners();
// Import the request model at the top of the file
final request = ScheduleAppointmentRequestModel(
generalId: currentSessionId,
fileNo: fileNo,
appointmentNo: appointmentNo,
doctorId: doctorId,
appointmentDate: appointmentDate,
mobileNumber: mobileNumber,
projectId: projectId,
clinicId: clinicId,
);
final result = await symptomsCheckerRepo.saveAppointmentDetailsForSymptomsChecker(
request: request,
userSessionToken: currentSessionAuthToken,
);
result.fold(
(failure) async {
isScheduleAppointmentLoading = false;
isBookingFromSymptomsChecker = false; // Reset flag on error
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
isScheduleAppointmentLoading = false;
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
isBookingFromSymptomsChecker = false; // Reset flag on success
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse.data!);
}
} else {
isBookingFromSymptomsChecker = false; // Reset flag on error
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? 'Failed to schedule appointment');
}
}
},
);
}
@override @override
void dispose() { void dispose() {
_tooltipTimer?.cancel(); _tooltipTimer?.cancel();

@ -1834,5 +1834,6 @@ abstract class LocaleKeys {
static const liveCareNotificationPermissionsMessage = 'liveCareNotificationPermissionsMessage'; static const liveCareNotificationPermissionsMessage = 'liveCareNotificationPermissionsMessage';
static const weatherIndicators = 'weatherIndicators'; static const weatherIndicators = 'weatherIndicators';
static const submitRating = 'submitRating'; static const submitRating = 'submitRating';
static const completedPrescriptionOrder = 'completedPrescriptionOrder';
} }

@ -49,6 +49,8 @@ import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_mo
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart';
import 'package:hmg_patient_app_new/services/app_lifecycle_service.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/app_theme.dart'; import 'package:hmg_patient_app_new/theme/app_theme.dart';
@ -88,7 +90,9 @@ Future<void> callAppStateInitializations() async {
appState.setDeviceTypeID = deviceTypeId; appState.setDeviceTypeID = deviceTypeId;
// Pass all uncaught "fatal" errors from the framework to Crashlytics // Pass all uncaught "fatal" errors from the framework to Crashlytics
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError; if (!kDebugMode) {
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
}
// Pass all uncaught asynchronous errors that aren't handled by the Flutter framework to Crashlytics // Pass all uncaught asynchronous errors that aren't handled by the Flutter framework to Crashlytics
PlatformDispatcher.instance.onError = (error, stack) { PlatformDispatcher.instance.onError = (error, stack) {
@ -121,6 +125,9 @@ Future<void> callInitializations() async {
HttpOverrides.global = MyHttpOverrides(); HttpOverrides.global = MyHttpOverrides();
await callAppStateInitializations(); await callAppStateInitializations();
// Initialize App Lifecycle Service to monitor background/foreground transitions
getIt.get<AppLifecycleService>().initialize();
// Restore persisted dark-mode preference before the first frame. // Restore persisted dark-mode preference before the first frame.
getIt.get<ProfileSettingsViewModel>().loadDarkMode(); getIt.get<ProfileSettingsViewModel>().loadDarkMode();
} }

@ -262,7 +262,10 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
Expanded( Expanded(
child: CollapsingListView( child: CollapsingListView(
title: LocaleKeys.appointmentDetails.tr(context: context), title: LocaleKeys.appointmentDetails.tr(context: context),
report: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) && widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment==false && widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! ==true && widget.patientAppointmentHistoryResponseModel.isActiveDoctor! == true report: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) &&
widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment == false &&
widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! == true &&
widget.patientAppointmentHistoryResponseModel.isActiveDoctor! == true
? () { ? () {
contactUsViewModel.setSelectedFeedbackType(FeedbackType(id: 1, nameEN: "Complaint for appointment", nameAR: 'شكوى على موعد')); contactUsViewModel.setSelectedFeedbackType(FeedbackType(id: 1, nameEN: "Complaint for appointment", nameAR: 'شكوى على موعد'));
contactUsViewModel.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); contactUsViewModel.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel);
@ -280,8 +283,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
children: [ children: [
AppointmentDoctorCard( AppointmentDoctorCard(
// renderWidgetForERDisplay: ((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || // renderWidgetForERDisplay: ((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ||
renderWidgetForERDisplay: renderWidgetForERDisplay: ((widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) ||
((widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)), !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)),
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onAskDoctorTap: () async { onAskDoctorTap: () async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context));
@ -361,10 +364,6 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
isFullScreen: false, isFullScreen: false,
isCloseButtonVisible: true, isCloseButtonVisible: true,
); );
// var isEventAddedOrRemoved = await CalenderUtilsNew.instance.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", );
// setState(() {
// myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
// });
}, },
onRescheduleTap: () async { onRescheduleTap: () async {
openDoctorScheduleCalendar(); openDoctorScheduleCalendar();
@ -416,14 +415,17 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
Row( Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [
Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 40.w, height: 40.h, applyThemeColor: false), Utils.buildSvgWithAssets(
icon: AppAssets.prescription_reminder_icon, width: 40.w, height: 40.h, applyThemeColor: false),
SizedBox(width: 8.w), SizedBox(width: 8.w),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4.h, spacing: 4.h,
children: [ children: [
LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), LocaleKeys.setReminder.tr(context: context).toText13(isBold: true),
LocaleKeys.notifyMeBeforeAppointment.tr(context: context).toText11(color: AppColors.textColorLight, isBold: true), LocaleKeys.notifyMeBeforeAppointment
.tr(context: context)
.toText11(color: AppColors.textColorLight, isBold: true),
], ],
), ),
const Spacer(), const Spacer(),
@ -438,48 +440,53 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
inactiveCircleColor: AppColors.greyTextColor, inactiveCircleColor: AppColors.greyTextColor,
activeIconColor: AppColors.bgGreenColor, activeIconColor: AppColors.bgGreenColor,
inactiveIconColor: AppColors.greyTextColor, inactiveIconColor: AppColors.greyTextColor,
activeIcon: Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 12.w, height: 12.h), activeIcon:
inactiveIcon: Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 12.w, height: 12.h), Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 12.w, height: 12.h),
inactiveIcon:
Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 12.w, height: 12.h),
onChanged: (newValue) async { onChanged: (newValue) async {
CalenderUtilsNew calender = CalenderUtilsNew.instance; CalenderUtilsNew calender = CalenderUtilsNew.instance;
bool isEventAddedOrRemoved = false; bool isEventAddedOrRemoved = false;
if (newValue == true) { if (newValue == true) {
DateTime startDate = DateTime.now(); DateTime startDate = DateTime.now();
DateTime endDate = DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate); DateTime endDate =
DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate);
// Show reminder bottom sheet and check if permission was granted // Show reminder bottom sheet and check if permission was granted
bool permissionGranted = await BottomSheetUtils().showReminderBottomSheet( bool permissionGranted = await BottomSheetUtils().showReminderBottomSheet(
context, context,
endDate, endDate,
widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "", widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "",
"${widget.patientAppointmentHistoryResponseModel.appointmentNo}" ?? "", "${widget.patientAppointmentHistoryResponseModel.appointmentNo}" ?? "",
"", "",
"", "",
title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}", title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}",
description: description:
"${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}", "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}",
onSuccess: () { onSuccess: () {
setState(() { setState(() {
myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); myAppointmentsViewModel.setAppointmentReminder(
}); newValue, widget.patientAppointmentHistoryResponseModel);
}, });
isMultiAllowed: true, },
onMultiDateSuccess: (int selectedIndex) async { isMultiAllowed: true,
isEventAddedOrRemoved = await calender.createOrUpdateEvent( onMultiDateSuccess: (int selectedIndex) async {
title: isEventAddedOrRemoved = await calender.createOrUpdateEvent(
"Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}", title:
description: "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
"Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}", description:
scheduleDateTime: DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}",
eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", scheduleDateTime:
location: '', DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate),
reminderMinutes: selectedIndex); eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
setState(() { location: '',
myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); reminderMinutes: selectedIndex);
}); setState(() {
}, myAppointmentsViewModel.setAppointmentReminder(
isForAppointment: true isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
); });
},
isForAppointment: true);
// If permission was not granted, revert the switch back to OFF // If permission was not granted, revert the switch back to OFF
if (!permissionGranted) { if (!permissionGranted) {
@ -490,72 +497,17 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
id: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", id: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
); );
setState(() { setState(() {
myAppointmentsViewModel.setAppointmentReminder(!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); myAppointmentsViewModel.setAppointmentReminder(
!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
}); });
} }
}, },
) )
// Switch(
// activeThumbColor: AppColors.successColor,
// // activeTrackColor: AppColors.successColor.withValues(alpha: .15),
// value: widget.patientAppointmentHistoryResponseModel.hasReminder!,
// onChanged: (newValue) async {
// CalenderUtilsNew calender = CalenderUtilsNew.instance;
// bool isEventAddedOrRemoved = false;
// if(newValue == true){
// DateTime startDate = DateTime.now();
// DateTime endDate = DateUtil.convertStringToDate(widget
// .patientAppointmentHistoryResponseModel.appointmentDate);
// BottomSheetUtils().showReminderBottomSheet(
// context,
// endDate,
// widget.patientAppointmentHistoryResponseModel.doctorNameObj??"",
// "${widget.patientAppointmentHistoryResponseModel.appointmentNo}"??"",
// "",
// "",
// title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}",
// description:
// "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}",
// onSuccess: () {
// setState(() {
// myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel);
// });
// },
// isMultiAllowed: true,
// onMultiDateSuccess: (int selectedIndex) async {
// isEventAddedOrRemoved = await calender.createOrUpdateEvent(
// title:
// "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
// description:
// "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}",
// scheduleDateTime: DateUtil.convertStringToDate(widget
// .patientAppointmentHistoryResponseModel.appointmentDate),
// eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
// location: '',
// reminderMinutes: selectedIndex
// );
// setState(() {
// myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
// });
// },
// );
// }else {
// isEventAddedOrRemoved = await calender.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", );
// setState(() {
// myAppointmentsViewModel.setAppointmentReminder(!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
// });
// }
//
//
// },
// ),
], ],
).paddingSymmetrical(16.w, 0) ).paddingSymmetrical(16.w, 0)
], ],
), ),
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) !AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)
? Column( ? Column(
@ -581,53 +533,15 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
LocaleKeys.appointmentStatus.tr(context: context).toText16(isBold: true), LocaleKeys.appointmentStatus.tr(context: context).toText16(isBold: true),
SizedBox(height: 4.h), SizedBox(height: 4.h),
(!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel)
? LocaleKeys.notConfirmed.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true) ? LocaleKeys.notConfirmed
: LocaleKeys.confirmed.tr(context: context).toText12(color: AppColors.successColor, isBold: true)), .tr(context: context)
.toText12(color: AppColors.primaryRedColor, isBold: true)
: LocaleKeys.confirmed
.tr(context: context)
.toText12(color: AppColors.successColor, isBold: true)),
SizedBox(height: 16.h), SizedBox(height: 16.h),
], ],
), ),
// ((!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) && widget.patientAppointmentHistoryResponseModel.nextAction != 10)
// ? CustomButton(
// text: LocaleKeys.confirm.tr(),
// onPressed: () async {
// LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingAppointmentPleaseWait.tr(context: context));
// await myAppointmentsViewModel.confirmAppointment(
// patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
// onSuccess: (apiResponse) {
// LoaderBottomSheet.hideLoader();
// myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
// myAppointmentsViewModel.initAppointmentsViewModel();
// // myAppointmentsViewModel.getPatientAppointments(true, false);
// showCommonBottomSheetWithoutHeight(
// title: "",
// context,
// child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)),
// callBackFunc: () {
// Navigator.pushAndRemoveUntil(
// context,
// CustomPageRoute(
// page: LandingNavigation(),
// ),
// (r) => false);
// },
// isFullScreen: false,
// isCloseButtonVisible: false,
// isAutoDismiss: true
// );
// });
// },
// backgroundColor: AppColors.successColor,
// borderColor: AppColors.successColor,
// textColor: Colors.white,
// fontSize: 14.f,
// isBold: true,
// borderRadius: 12.r,
// height: 40.h,
// icon: AppAssets.confirm_appointment_icon,
// iconColor: Colors.white,
// iconSize: 16.h,
// )
// : SizedBox.shrink())
], ],
), ),
//TODO Add countdown timer in case of LiveCare Appointment //TODO Add countdown timer in case of LiveCare Appointment
@ -640,7 +554,9 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
LocaleKeys.doctorWillCallYou.tr(context: context).toText14(color: AppColors.greyTextColor, isBold: true), LocaleKeys.doctorWillCallYou
.tr(context: context)
.toText14(color: AppColors.greyTextColor, isBold: true),
], ],
), ),
), ),
@ -648,13 +564,17 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
) )
: Stack( : Stack(
children: [ children: [
ClipRRect( SizedBox(
clipBehavior: Clip.hardEdge, width: double.infinity,
borderRadius: BorderRadius.circular(24.r), child: ClipRRect(
// Todo: what is this???? Api Key??? 😲 clipBehavior: Clip.hardEdge,
child: Image.network( borderRadius: BorderRadius.circular(24.r),
"https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=${(MediaQuery.of(context).size.width * 1.5).toInt()}x${(MediaQuery.of(context).size.height * 0.35).toInt()}&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=${ApiKeyConstants.googleMapsApiKey}", // Todo: what is this???? Api Key??? 😲
fit: BoxFit.contain, child: Image.network(
"https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=${(MediaQuery.of(context).size.width * 1.5).toInt()}x${(MediaQuery.of(context).size.height * 0.35).toInt()}&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=${ApiKeyConstants.googleMapsApiKey}",
fit: BoxFit.cover,
width: double.infinity,
),
), ),
), ),
Positioned( Positioned(
@ -663,7 +583,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
width: MediaQuery.of(context).size.width - 85.w, width: MediaQuery.of(context).size.width - 85.w,
child: CustomButton( child: CustomButton(
onPressed: () async { onPressed: () async {
if (widget.patientAppointmentHistoryResponseModel.projectID == 130 || widget.patientAppointmentHistoryResponseModel.projectID == 120) { if (widget.patientAppointmentHistoryResponseModel.projectID == 130 ||
widget.patientAppointmentHistoryResponseModel.projectID == 120) {
showDirectionsBottomSheet(); showDirectionsBottomSheet();
} else { } else {
await MapLauncher.showMarker( await MapLauncher.showMarker(
@ -683,7 +604,9 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
}, },
text: LocaleKeys.getDirections.tr(context: context), text: LocaleKeys.getDirections.tr(context: context),
backgroundColor: AppColors.bookAppointment.withValues(alpha: 0.8), backgroundColor: AppColors.bookAppointment.withValues(alpha: 0.8),
borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), borderColor: AppointmentType.getNextActionButtonColor(
widget.patientAppointmentHistoryResponseModel.nextAction)
.withValues(alpha: 0.01),
textColor: Colors.white, textColor: Colors.white,
fontSize: 14.f, fontSize: 14.f,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -723,148 +646,6 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
); );
}), }),
SizedBox(height: 16.h), SizedBox(height: 16.h),
// Container(
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// color: AppColors.whiteColor,
// borderRadius: 20.r,
// hasShadow: false,
// ),
// child: Row(
// mainAxisSize: MainAxisSize.max,
// children: [
// Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 35.h, height: 35.h, applyThemeColor: false),
// SizedBox(width: 8.h),
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// LocaleKeys.setReminder.tr(context: context).toText13(isBold: true),
// LocaleKeys.notifyMeBeforeAppointment.tr(context: context).toText11(color: AppColors.textColorLight, isBold: true),
// ],
// ),
// const Spacer(),
// BellAnimatedSwitch(
// key: _bellSwitchKey,
// initialValue: widget.patientAppointmentHistoryResponseModel.hasReminder ?? false,
// activeColor: AppColors.successColor.withOpacity(0.2),
// inactiveColor: AppColors.lightGrayBGColor,
// activeCircleColor: AppColors.successColor,
// inactiveCircleColor: AppColors.greyTextColor,
// activeIconColor: AppColors.bgGreenColor,
// inactiveIconColor: AppColors.greyTextColor,
// activeIcon: Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 15.w, height: 15.h),
// inactiveIcon: Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 15.w, height: 15.h),
// onChanged: (newValue) async {
// CalenderUtilsNew calender = CalenderUtilsNew.instance;
// bool isEventAddedOrRemoved = false;
// if (newValue == true) {
// DateTime startDate = DateTime.now();
// DateTime endDate = DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate);
//
// // Show reminder bottom sheet and check if permission was granted
// bool permissionGranted = await BottomSheetUtils().showReminderBottomSheet(
// context,
// endDate,
// widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "",
// "${widget.patientAppointmentHistoryResponseModel.appointmentNo}" ?? "",
// "",
// "",
// title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}",
// description:
// "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}",
// onSuccess: () {
// setState(() {
// myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel);
// });
// },
// isMultiAllowed: true,
// onMultiDateSuccess: (int selectedIndex) async {
// isEventAddedOrRemoved = await calender.createOrUpdateEvent(
// title:
// "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
// description:
// "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}",
// scheduleDateTime: DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate),
// eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
// location: '',
// reminderMinutes: selectedIndex);
// setState(() {
// myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
// });
// },
// );
//
// // If permission was not granted, revert the switch back to OFF
// if (!permissionGranted) {
// _bellSwitchKey.currentState?.setSwitchValue(false);
// }
// } else {
// isEventAddedOrRemoved = await calender.checkAndRemove(
// id: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
// );
// setState(() {
// myAppointmentsViewModel.setAppointmentReminder(!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
// });
// }
// },
// )
//
// // Switch(
// // activeThumbColor: AppColors.successColor,
// // // activeTrackColor: AppColors.successColor.withValues(alpha: .15),
// // value: widget.patientAppointmentHistoryResponseModel.hasReminder!,
// // onChanged: (newValue) async {
// // CalenderUtilsNew calender = CalenderUtilsNew.instance;
// // bool isEventAddedOrRemoved = false;
// // if(newValue == true){
// // DateTime startDate = DateTime.now();
// // DateTime endDate = DateUtil.convertStringToDate(widget
// // .patientAppointmentHistoryResponseModel.appointmentDate);
// // BottomSheetUtils().showReminderBottomSheet(
// // context,
// // endDate,
// // widget.patientAppointmentHistoryResponseModel.doctorNameObj??"",
// // "${widget.patientAppointmentHistoryResponseModel.appointmentNo}"??"",
// // "",
// // "",
// // title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}",
// // description:
// // "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}",
// // onSuccess: () {
// // setState(() {
// // myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel);
// // });
// // },
// // isMultiAllowed: true,
// // onMultiDateSuccess: (int selectedIndex) async {
// // isEventAddedOrRemoved = await calender.createOrUpdateEvent(
// // title:
// // "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
// // description:
// // "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}",
// // scheduleDateTime: DateUtil.convertStringToDate(widget
// // .patientAppointmentHistoryResponseModel.appointmentDate),
// // eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
// // location: '',
// // reminderMinutes: selectedIndex
// // );
// // setState(() {
// // myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
// // });
// // },
// // );
// // }else {
// // isEventAddedOrRemoved = await calender.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", );
// // setState(() {
// // myAppointmentsViewModel.setAppointmentReminder(!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
// // });
// // }
// //
// //
// // },
// // ),
// ],
// ).paddingSymmetrical(16.h, 16.h),
// ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
], ],
) )
@ -875,7 +656,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
crossAxisCount: 3, crossAxisCount: 3,
crossAxisSpacing: 16.h, crossAxisSpacing: 16.h,
mainAxisSpacing: 16.w, mainAxisSpacing: 16.w,
mainAxisExtent: 115.h, childAspectRatio: isFoldable ? 1.2 : (isTablet ? 1.1 : 0.78),
), ),
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@ -974,7 +755,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
); );
Navigator.of(context).push( Navigator.of(context).push(
CustomPageRoute( CustomPageRoute(
page: PrescriptionDetailPage(isFromAppointments: true, prescriptionsResponseModel: patientPrescriptionsResponseModel), page: PrescriptionDetailPage(
isFromAppointments: true, prescriptionsResponseModel: patientPrescriptionsResponseModel),
), ),
); );
} else { } else {
@ -1093,213 +875,6 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
], ],
); );
}), }),
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// "Lab & Radiology".needTranslation.toText18(isBold: true),
// SizedBox(height: 16.h),
// Row(
// children: [
// Expanded(
// child: LabRadCard(
// icon: AppAssets.lab_result_icon,
// labelText: LocaleKeys.labResults.tr(context: context),
// // labOrderTests: ["Complete blood count", "Creatinine", "Blood Sugar"],
// // labOrderTests: labViewModel.isLabOrdersLoading ? [] : labViewModel.labOrderTests,
// labOrderTests: [],
// // isLoading: labViewModel.isLabOrdersLoading,
// isLoading: false,
// ).onPress(() {
// Navigator.of(context).push(
// CustomPageRoute(
// page: LabOrdersPage(),
// ),
// );
// }),
// ),
// SizedBox(width: 16.h),
// Expanded(
// child: LabRadCard(
// icon: AppAssets.radiology_icon,
// labelText: LocaleKeys.radiology.tr(context: context),
// // labOrderTests: ["Chest X-ray", "Abdominal Ultrasound", "Dental X-ray"],
// labOrderTests: [],
// isLoading: false,
// ).onPress(() {
// Navigator.of(context).push(
// CustomPageRoute(
// page: RadiologyOrdersPage(),
// ),
// );
// }),
// ),
// ],
// ),
// SizedBox(height: 16.h),
// LocaleKeys.prescriptions.tr(context: context).toText18(isBold: true),
// SizedBox(height: 16.h),
// Consumer<PrescriptionsViewModel>(builder: (context, prescriptionVM, child) {
// return prescriptionVM.isPrescriptionsDetailsLoading
// ? const MoviesShimmerWidget()
// : Container(
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// color: Colors.white,
// borderRadius: 20.r,
// ),
// padding: EdgeInsets.all(16.w),
// child: Column(
// children: [
// // ListView.separated(
// // itemCount: prescriptionVM.prescriptionDetailsList.length,
// // shrinkWrap: true,
// // padding: EdgeInsets.only(right: 8.w),
// // physics: NeverScrollableScrollPhysics(),
// // itemBuilder: (context, index) {
// // return AnimationConfiguration.staggeredList(
// // position: index,
// // duration: const Duration(milliseconds: 500),
// // child: SlideAnimation(
// // verticalOffset: 100.0,
// // child: FadeInAnimation(
// // child: Row(
// // children: [
// // Utils.buildSvgWithAssets(
// // icon: AppAssets.prescription_item_icon,
// // width: 40.h,
// // height: 40.h,
// // ),
// // SizedBox(width: 8.h),
// // Row(
// // mainAxisSize: MainAxisSize.max,
// // children: [
// // Column(
// // children: [
// // prescriptionVM.prescriptionDetailsList[index].itemDescription!
// // .toText12(isBold: true, maxLine: 1),
// // "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}"
// // .needTranslation
// // .toText10(
// // weight: FontWeight.w600,
// // color: AppColors.greyTextColor,
// // letterSpacing: -0.4),
// // ],
// // ),
// // SizedBox(width: 68.w),
// // Transform.flip(
// // flipX: appState.isArabic(),
// // child: Utils.buildSvgWithAssets(
// // icon: AppAssets.forward_arrow_icon,
// // iconColor: AppColors.blackColor,
// // width: 18.w,
// // height: 13.h,
// // fit: BoxFit.contain,
// // ),
// // ),
// // ],
// // ),
// // ],
// // ),
// // ),
// // ),
// // );
// // },
// // separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
// // ).onPress(() {
// // prescriptionVM.setPrescriptionsDetailsLoading();
// // Navigator.of(context).push(
// // CustomPageRoute(
// // page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()),
// // ),
// // );
// // }),
// SizedBox(height: 16.h),
// const Divider(color: AppColors.dividerColor),
// SizedBox(height: 16.h),
// // Wrap(
// // runSpacing: 6.w,
// // children: [
// // // Expanded(
// // // child: CustomButton(
// // // text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context),
// // // onPressed: () {},
// // // backgroundColor: AppColors.secondaryLightRedColor,
// // // borderColor: AppColors.secondaryLightRedColor,
// // // textColor: AppColors.primaryRedColor,
// // // fontSize: 14,
// // // isBold: true,
// // // borderRadius: 12.h,
// // // height: 40.h,
// // // icon: AppAssets.appointment_calendar_icon,
// // // iconColor: AppColors.primaryRedColor,
// // // iconSize: 16.h,
// // // ),
// // // ),
// // // SizedBox(width: 16.h),
// // Expanded(
// // child: CustomButton(
// // text: "Refill & Delivery".needTranslation,
// // onPressed: () {
// // Navigator.of(context)
// // .push(
// // CustomPageRoute(
// // page: PrescriptionsListPage(),
// // ),
// // )
// // .then((val) {
// // prescriptionsViewModel.setPrescriptionsDetailsLoading();
// // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel());
// // });
// // },
// // backgroundColor: AppColors.secondaryLightRedColor,
// // borderColor: AppColors.secondaryLightRedColor,
// // textColor: AppColors.primaryRedColor,
// // fontSize: 14.f,
// // isBold: true,
// // borderRadius: 12.r,
// // height: 40.h,
// // icon: AppAssets.requests,
// // iconColor: AppColors.primaryRedColor,
// // iconSize: 16.h,
// // ),
// // ),
// //
// // SizedBox(width: 16.w),
// // Expanded(
// // child: CustomButton(
// // text: "All Prescriptions".needTranslation,
// // onPressed: () {
// // Navigator.of(context)
// // .push(
// // CustomPageRoute(
// // page: PrescriptionsListPage(),
// // ),
// // )
// // .then((val) {
// // prescriptionsViewModel.setPrescriptionsDetailsLoading();
// // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel());
// // });
// // },
// // backgroundColor: AppColors.secondaryLightRedColor,
// // borderColor: AppColors.secondaryLightRedColor,
// // textColor: AppColors.primaryRedColor,
// // fontSize: 14.f,
// // isBold: true,
// // borderRadius: 12.r,
// // height: 40.h,
// // icon: AppAssets.requests,
// // iconColor: AppColors.primaryRedColor,
// // iconSize: 16.h,
// // ),
// // ),
// // ],
// // ),
// ],
// ),
// );
// }),
// ],
// ),
], ],
).paddingAll(24.w), ).paddingAll(24.w),
), ),
@ -1315,7 +890,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (widget.patientAppointmentHistoryResponseModel.nextAction == 15 || widget.patientAppointmentHistoryResponseModel.nextAction == 20) if (widget.patientAppointmentHistoryResponseModel.nextAction == 15 ||
widget.patientAppointmentHistoryResponseModel.nextAction == 20)
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -1323,7 +899,10 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
LocaleKeys.amountBeforeTax.tr(context: context).toText18(isBold: true), LocaleKeys.amountBeforeTax.tr(context: context).toText18(isBold: true),
Utils.getPaymentAmountWithSymbol(widget.patientAppointmentHistoryResponseModel.patientShare!.toString().toText16(isBold: true), AppColors.blackColor, 13, Utils.getPaymentAmountWithSymbol(
widget.patientAppointmentHistoryResponseModel.patientShare!.toString().toText16(isBold: true),
AppColors.blackColor,
13,
isSaudiCurrency: true), isSaudiCurrency: true),
], ],
), ),
@ -1331,22 +910,24 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded(child: LocaleKeys.upcomingPaymentNow.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor)), Expanded(
"VAT 15%(${widget.patientAppointmentHistoryResponseModel.patientTaxAmount})".toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -0.64), child: LocaleKeys.upcomingPaymentNow.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor)),
"VAT 15%(${widget.patientAppointmentHistoryResponseModel.patientTaxAmount})"
.toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -0.64),
], ],
), ),
SizedBox(height: 18.h), SizedBox(height: 18.h),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
SizedBox( Utils.getPaymentMethods(),
width: 200.h,
child: Utils.getPaymentMethods(),
),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Utils.getPaymentAmountWithSymbol(widget.patientAppointmentHistoryResponseModel.patientShareWithTax!.toString().toText24(isBold: true), AppColors.blackColor, 17, Utils.getPaymentAmountWithSymbol(
widget.patientAppointmentHistoryResponseModel.patientShareWithTax!.toString().toText24(isBold: true),
AppColors.blackColor,
17,
isSaudiCurrency: true), isSaudiCurrency: true),
], ],
), ),
@ -1384,7 +965,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction); handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction);
}, },
backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction),
borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction)
.withValues(alpha: 0.01),
textColor: widget.patientAppointmentHistoryResponseModel.nextAction == 15 ? AppColors.textColor : Colors.white, textColor: widget.patientAppointmentHistoryResponseModel.nextAction == 15 ? AppColors.textColor : Colors.white,
fontSize: 16.f, fontSize: 16.f,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -1424,7 +1006,10 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
text: LocaleKeys.insideHospital.tr(context: context), text: LocaleKeys.insideHospital.tr(context: context),
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);
initPenguinSDK(widget.patientAppointmentHistoryResponseModel.projectID == 130 ? 1 : (widget.patientAppointmentHistoryResponseModel.projectID == 120 ? 3 : -1), initPenguinSDK(
widget.patientAppointmentHistoryResponseModel.projectID == 130
? 1
: (widget.patientAppointmentHistoryResponseModel.projectID == 120 ? 3 : -1),
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID.toString()); clinicID: widget.patientAppointmentHistoryResponseModel.clinicID.toString());
}, },
backgroundColor: AppColors.primaryRedColor, backgroundColor: AppColors.primaryRedColor,
@ -1443,12 +1028,14 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
Navigator.pop(context); Navigator.pop(context);
await MapLauncher.showMarker( await MapLauncher.showMarker(
mapType: MapType.google, mapType: MapType.google,
coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!),
double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)),
title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital",
).catchError((err) { ).catchError((err) {
MapLauncher.showMarker( MapLauncher.showMarker(
mapType: Platform.isIOS ? MapType.apple : MapType.google, mapType: Platform.isIOS ? MapType.apple : MapType.google,
coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)), coords: Coords(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!),
double.parse(widget.patientAppointmentHistoryResponseModel.longitude!)),
title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital", title: widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital",
); );
}); });
@ -1482,7 +1069,9 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
Permission.bluetoothScan, Permission.bluetoothScan,
Permission.activityRecognition, Permission.activityRecognition,
].request().whenComplete(() { ].request().whenComplete(() {
PenguinMethodChannel().launch("penguin", getIt.get<AppState>().isArabic() ? "ar" : "en", getIt.get<AppState>().getAuthenticatedUser()?.patientId?.toString() ?? "", true, details: data); PenguinMethodChannel().launch("penguin", getIt.get<AppState>().isArabic() ? "ar" : "en",
getIt.get<AppState>().getAuthenticatedUser()?.patientId?.toString() ?? "", true,
details: data);
}); });
} }
} }
@ -1540,12 +1129,20 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.getPatientAppointments(true, false); myAppointmentsViewModel.getPatientAppointments(true, false);
showCommonBottomSheet(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)), callBackFunc: (str) { showCommonBottomSheet(context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)), callBackFunc: (str) {
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false); myAppointmentsViewModel.getPatientAppointments(true, false);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isAutoDismiss: true, isCloseButtonVisible: true, isDismissible: false, isFullScreen: false, isSuccessDialog: true); },
title: "",
height: ResponsiveExtension.screenHeight * 0.3,
isAutoDismiss: true,
isCloseButtonVisible: true,
isDismissible: false,
isFullScreen: false,
isSuccessDialog: true);
}); });
// LoaderBottomSheet.hideLoader(); // LoaderBottomSheet.hideLoader();
case 15: case 15:
@ -1565,7 +1162,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Lottie.asset(AppAnimations.warningAnimation, repeat: false, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), Lottie.asset(AppAnimations.warningAnimation,
repeat: false, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill),
SizedBox( SizedBox(
height: 12, height: 12,
), ),

@ -1,4 +1,6 @@
import 'dart:async'; import 'dart:async';
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/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -18,6 +20,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie
import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.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/appointments/appointment_details_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_details_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/appointment_payment_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/eye_measurement_details_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/eye_measurement_details_page.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
@ -26,8 +29,6 @@ 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:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'dart:ui' as ui;
import 'package:hmg_patient_app_new/presentation/appointments/appointment_payment_page.dart';
import 'package:lottie/lottie.dart'; import 'package:lottie/lottie.dart';
class AppointmentCard extends StatefulWidget { class AppointmentCard extends StatefulWidget {
@ -42,6 +43,7 @@ class AppointmentCard extends StatefulWidget {
final ContactUsViewModel? contactUsViewModel; final ContactUsViewModel? contactUsViewModel;
final BookAppointmentsViewModel bookAppointmentsViewModel; final BookAppointmentsViewModel bookAppointmentsViewModel;
final bool isForRate; final bool isForRate;
// bool isAppointmentWithin4Hours = false; // bool isAppointmentWithin4Hours = false;
const AppointmentCard( const AppointmentCard(
@ -189,20 +191,28 @@ class _AppointmentCardState extends State<AppointmentCard> {
runSpacing: 6.h, runSpacing: 6.h,
children: [ children: [
AppCustomChipWidget( AppCustomChipWidget(
icon: widget.isLoading ? AppAssets.walkin_appointment_icon : (isLiveCare ? AppAssets.small_livecare_icon : AppAssets.walkin_appointment_icon), icon:
widget.isLoading ? AppAssets.walkin_appointment_icon : (isLiveCare ? AppAssets.small_livecare_icon : AppAssets.walkin_appointment_icon),
iconColor: widget.isLoading ? AppColors.textColor : (isLiveCare ? Colors.white : AppColors.textColor), iconColor: widget.isLoading ? AppColors.textColor : (isLiveCare ? Colors.white : AppColors.textColor),
labelText: widget.isLoading ? LocaleKeys.walkin.tr(context: context) : (isLiveCare ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context)), labelText: widget.isLoading
? LocaleKeys.walkin.tr(context: context)
: (isLiveCare ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context)),
backgroundColor: widget.isLoading ? AppColors.greyColor : (isLiveCare ? AppColors.successColor : AppColors.greyColor), backgroundColor: widget.isLoading ? AppColors.greyColor : (isLiveCare ? AppColors.successColor : AppColors.greyColor),
textColor: widget.isLoading ? AppColors.textColor : (isLiveCare ? Colors.white : AppColors.textColor), textColor: widget.isLoading ? AppColors.textColor : (isLiveCare ? Colors.white : AppColors.textColor),
).toShimmer2(isShow: widget.isLoading), ).toShimmer2(isShow: widget.isLoading),
AppCustomChipWidget( AppCustomChipWidget(
labelText: labelText: widget.isLoading
widget.isLoading ? 'OutPatient' : (appState.isArabic() ? widget.patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! : widget.patientAppointmentHistoryResponseModel.isInOutPatientDescription!), ? 'OutPatient'
: (appState.isArabic()
? widget.patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN!
: widget.patientAppointmentHistoryResponseModel.isInOutPatientDescription!),
backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.1), backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.1),
textColor: AppColors.warningColorYellow, textColor: AppColors.warningColorYellow,
).toShimmer2(isShow: widget.isLoading), ).toShimmer2(isShow: widget.isLoading),
AppCustomChipWidget( AppCustomChipWidget(
labelText: widget.isLoading ? 'Booked' : AppointmentType.getAppointmentStatusType(widget.patientAppointmentHistoryResponseModel.patientStatusType!), labelText: widget.isLoading
? 'Booked'
: AppointmentType.getAppointmentStatusType(widget.patientAppointmentHistoryResponseModel.patientStatusType!),
backgroundColor: AppColors.successColor.withValues(alpha: 0.1), backgroundColor: AppColors.successColor.withValues(alpha: 0.1),
textColor: AppColors.successColor, textColor: AppColors.successColor,
).toShimmer2(isShow: widget.isLoading), ).toShimmer2(isShow: widget.isLoading),
@ -218,7 +228,9 @@ class _AppointmentCardState extends State<AppointmentCard> {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Image.network( Image.network(
widget.isLoading ? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png' : widget.patientAppointmentHistoryResponseModel.doctorImageURL!, widget.isLoading
? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png'
: widget.patientAppointmentHistoryResponseModel.doctorImageURL!,
width: 63.h, width: 63.h,
height: 63.h, height: 63.h,
fit: BoxFit.cover, fit: BoxFit.cover,
@ -239,11 +251,13 @@ class _AppointmentCardState extends State<AppointmentCard> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, iconColor: AppColors.ratingColorYellow), Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 14.h, height: 14.h, iconColor: AppColors.ratingColorYellow),
SizedBox(height: 2.h), SizedBox(height: 2.h),
(isFoldable || isTablet) (isFoldable || isTablet)
? "${widget.patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText9(isBold: true, color: AppColors.textColor, isEnglishOnly: true) ? "${widget.patientAppointmentHistoryResponseModel.decimalDoctorRate}"
: "${widget.patientAppointmentHistoryResponseModel.decimalDoctorRate ?? "0.0"}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true), .toText9(isBold: true, color: AppColors.textColor, isEnglishOnly: true)
: "${widget.patientAppointmentHistoryResponseModel.decimalDoctorRate ?? "0.0"}"
.toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true),
], ],
), ),
).circle(100).toShimmer2(isShow: widget.isLoading), ).circle(100).toShimmer2(isShow: widget.isLoading),
@ -256,12 +270,17 @@ class _AppointmentCardState extends State<AppointmentCard> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
(widget.isLoading ? 'Dr' : "${widget.patientAppointmentHistoryResponseModel.doctorTitle}").toText16(isBold: true, maxlines: 1), (widget.isLoading ? 'Dr' : "${widget.patientAppointmentHistoryResponseModel.doctorTitle}").toText16(isBold: true, maxlines: 1),
(widget.isLoading ? 'John Doe' : " ${widget.patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(20)}") Expanded(
.toText16(isBold: true, maxlines: 1, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")), child: (widget.isLoading ? 'John Doe' : " ${widget.patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(20)}")
.toText16(isBold: true, maxlines: 2,
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!.isNotEmpty) (widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null &&
widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty)
? Image.network( ? Image.network(
widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png", widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png",
width: 20.h, width: 20.h,
@ -275,7 +294,7 @@ class _AppointmentCardState extends State<AppointmentCard> {
Wrap( Wrap(
direction: Axis.horizontal, direction: Axis.horizontal,
spacing: 6.h, spacing: 6.h,
runSpacing: 4.h, runSpacing: 6.h,
children: [ children: [
AppCustomChipWidget( AppCustomChipWidget(
labelText: widget.isLoading labelText: widget.isLoading
@ -419,40 +438,40 @@ class _AppointmentCardState extends State<AppointmentCard> {
// } else { // } else {
return CustomButton( return CustomButton(
text: widget.isFromMedicalReport ? LocaleKeys.selectAppointment.tr(context: context) : LocaleKeys.viewDetails.tr(context: context), text: widget.isFromMedicalReport ? LocaleKeys.selectAppointment.tr(context: context) : LocaleKeys.viewDetails.tr(context: context),
onPressed: () { onPressed: () {
if (widget.isFromMedicalReport) { if (widget.isFromMedicalReport) {
if (widget.isForFeedback) { if (widget.isForFeedback) {
widget.contactUsViewModel!.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); widget.contactUsViewModel!.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel);
} else {
widget.medicalFileViewModel!.setSelectedMedicalReportAppointment(widget.patientAppointmentHistoryResponseModel);
}
Navigator.pop(context, false);
} else { } else {
Navigator.of(context) widget.medicalFileViewModel!.setSelectedMedicalReportAppointment(widget.patientAppointmentHistoryResponseModel);
.push(
CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
),
)
.then((_) {
widget.myAppointmentsViewModel.initAppointmentsViewModel();
widget.myAppointmentsViewModel.getPatientAppointments(true, false);
});
} }
}, Navigator.pop(context, false);
backgroundColor: AppColors.secondaryLightRedColor, } else {
borderColor: AppColors.secondaryLightRedColor, Navigator.of(context)
textColor: AppColors.primaryRedColor, .push(
fontSize: (isFoldable || isTablet) ? 12.f : 14.f, CustomPageRoute(
fontWeight: FontWeight.w600, page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
borderRadius: 12.r, ),
padding: EdgeInsets.symmetric(horizontal: 10.w), )
// height: isTablet || isFoldable ? 46.h : 40.h, .then((_) {
height: 40.h, widget.myAppointmentsViewModel.initAppointmentsViewModel();
icon: widget.isFromMedicalReport ? AppAssets.checkmark_icon : null, widget.myAppointmentsViewModel.getPatientAppointments(true, false);
iconColor: AppColors.primaryRedColor, });
iconSize: 16.h, }
); },
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
// height: isTablet || isFoldable ? 46.h : 40.h,
height: 40.h,
icon: widget.isFromMedicalReport ? AppAssets.checkmark_icon : null,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
);
// } // }
} else { } else {
if (widget.isFromMedicalReport) { if (widget.isFromMedicalReport) {
@ -486,7 +505,7 @@ class _AppointmentCardState extends State<AppointmentCard> {
} }
return (widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) return (widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true)
? (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) && ? (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) &&
widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false) widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false)
? // Show only View Details button without arrow when rebooking not allowed ? // Show only View Details button without arrow when rebooking not allowed
_getArrivedButton(context) _getArrivedButton(context)
: Row( : Row(
@ -498,8 +517,10 @@ class _AppointmentCardState extends State<AppointmentCard> {
: CustomButton( : CustomButton(
text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction),
onPressed: () => handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction, context), onPressed: () => handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction, context),
backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15), backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction)
borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), .withValues(alpha: 0.15),
borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction)
.withValues(alpha: 0.01),
textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction),
fontSize: (isFoldable || isTablet) ? 12.f : 14.f, fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -589,10 +610,10 @@ class _AppointmentCardState extends State<AppointmentCard> {
// Show Rebook button // Show Rebook button
return CustomButton( return CustomButton(
borderSide: BorderSide( borderSide: BorderSide(
color: AppColors.textColor, color: AppColors.textColor,
width: 1.2, width: 1.2,
), ),
text: LocaleKeys.rebookSameDoctor.tr(context: context), text: LocaleKeys.rebookSameDoctor.tr(context: context),
onPressed: () => openDoctorScheduleCalendar(context), onPressed: () => openDoctorScheduleCalendar(context),
backgroundColor: AppColors.transparent, backgroundColor: AppColors.transparent,
@ -620,7 +641,8 @@ class _AppointmentCardState extends State<AppointmentCard> {
); );
} else { } else {
if (!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { if (!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) {
widget.bookAppointmentsViewModel.getAppointmentNearestGate(projectID: widget.patientAppointmentHistoryResponseModel.projectID, clinicID: widget.patientAppointmentHistoryResponseModel.clinicID); widget.bookAppointmentsViewModel.getAppointmentNearestGate(
projectID: widget.patientAppointmentHistoryResponseModel.projectID, clinicID: widget.patientAppointmentHistoryResponseModel.clinicID);
} }
Navigator.of(context) Navigator.of(context)
.push( .push(
@ -708,46 +730,47 @@ class _AppointmentCardState extends State<AppointmentCard> {
children: [ children: [
Lottie.asset(AppAnimations.warningAnimation, Lottie.asset(AppAnimations.warningAnimation,
repeat: false, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), repeat: false, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill),
SizedBox(height: 12,), SizedBox(
LocaleKeys.upcomingPaymentPending.tr(context: context).toText14( height: 12,
color: AppColors.textColor,
isCenter: true,
), ),
LocaleKeys.upcomingPaymentPending.tr(context: context).toText14(
color: AppColors.textColor,
isCenter: true,
),
SizedBox(height: 24.h), SizedBox(height: 24.h),
// Countdown Timer - DD : HH : MM : SS format with labels // Countdown Timer - DD : HH : MM : SS format with labels
Directionality( Directionality(
textDirection: ui.TextDirection.ltr, textDirection: ui.TextDirection.ltr,
child:Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Days // Days
_buildTimeUnit( _buildTimeUnit(
_timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00', _timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00',
LocaleKeys.days.tr(context: context), LocaleKeys.days.tr(context: context),
), ),
_buildTimeSeparator(), _buildTimeSeparator(),
// Hours // Hours
_buildTimeUnit( _buildTimeUnit(
_timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00', _timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00',
LocaleKeys.hours.tr(context: context), LocaleKeys.hours.tr(context: context),
), ),
_buildTimeSeparator(), _buildTimeSeparator(),
// Minutes // Minutes
_buildTimeUnit( _buildTimeUnit(
_timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00', _timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00',
LocaleKeys.minutes.tr(context: context), LocaleKeys.minutes.tr(context: context),
), ),
_buildTimeSeparator(), _buildTimeSeparator(),
// Seconds // Seconds
_buildTimeUnit( _buildTimeUnit(
_timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00', _timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00',
LocaleKeys.seconds.tr(context: context), LocaleKeys.seconds.tr(context: context),
), ),
], ],
)), )),
SizedBox(height: 24.h), SizedBox(height: 24.h),
// Green Acknowledge button with checkmark icon // Green Acknowledge button with checkmark icon
CustomButton( CustomButton(
@ -800,4 +823,3 @@ class _AppointmentCardState extends State<AppointmentCard> {
} }
} }
} }

@ -1,3 +1,5 @@
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/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -17,8 +19,6 @@ 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 'dart:ui' as ui;
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';
@ -67,8 +67,8 @@ class AppointmentDoctorCard extends StatelessWidget {
Transform.translate( Transform.translate(
offset: Offset(0.0, -20.h), offset: Offset(0.0, -20.h),
child: Container( child: Container(
width: 40.w, width: 50.h,
height: 40.h, height: 50.h,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.whiteColor, color: AppColors.whiteColor,
shape: BoxShape.circle, // Makes the container circular shape: BoxShape.circle, // Makes the container circular
@ -80,9 +80,10 @@ class AppointmentDoctorCard extends StatelessWidget {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, iconColor: AppColors.ratingColorYellow), Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.h, height: 15.h, iconColor: AppColors.ratingColorYellow),
SizedBox(height: 2.h), SizedBox(height: 2.h),
"${patientAppointmentHistoryResponseModel.decimalDoctorRate ?? 0.0}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true), "${patientAppointmentHistoryResponseModel.decimalDoctorRate ?? 0.0}"
.toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true),
], ],
), ),
).circle(100), ).circle(100),
@ -97,9 +98,14 @@ class AppointmentDoctorCard extends StatelessWidget {
children: [ children: [
Row( Row(
children: [ children: [
patientAppointmentHistoryResponseModel.doctorNameObj!.toText16(isBold: true, isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? "")), patientAppointmentHistoryResponseModel.doctorNameObj!.toText16(
isBold: true,
isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? ""),
textOverflow: TextOverflow.ellipsis,
),
SizedBox(width: 12.w), SizedBox(width: 12.w),
(patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty) (patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null &&
patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty)
? Image.network( ? Image.network(
patientAppointmentHistoryResponseModel.doctorNationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png", patientAppointmentHistoryResponseModel.doctorNationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png",
width: 20.h, width: 20.h,
@ -130,19 +136,25 @@ class AppointmentDoctorCard extends StatelessWidget {
child: AppCustomChipWidget( child: AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w),
icon: AppAssets.doctor_calendar_icon, icon: AppAssets.doctor_calendar_icon,
richText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang( richText:
"${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(
DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate),
false, false,
)}" )}"
.toText10(isBold: true), .toText10(isBold: true),
), ),
), ),
AppCustomChipWidget( AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w),
icon: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon, icon: !patientAppointmentHistoryResponseModel.isLiveCareAppointment!
? AppAssets.walkin_appointment_icon
: AppAssets.small_livecare_icon,
iconColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : Colors.white, iconColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : Colors.white,
labelText: patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context), labelText: patientAppointmentHistoryResponseModel.isLiveCareAppointment!
backgroundColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor, ? LocaleKeys.livecare.tr(context: context)
: LocaleKeys.walkin.tr(context: context),
backgroundColor:
!patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor,
textColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : Colors.white, textColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : Colors.white,
), ),
], ],
@ -150,10 +162,12 @@ class AppointmentDoctorCard extends StatelessWidget {
], ],
), ),
), ),
patientAppointmentHistoryResponseModel.isLiveCareAppointment! ||
patientAppointmentHistoryResponseModel.isLiveCareAppointment! || patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! ==false || patientAppointmentHistoryResponseModel.isActiveDoctor! == false patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! == false ||
patientAppointmentHistoryResponseModel.isActiveDoctor! == false
? SizedBox.shrink() ? SizedBox.shrink()
: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).onPress(() async { : Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown)
.onPress(() async {
DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel(); DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel();
selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID; selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID;
selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL; selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL;
@ -197,8 +211,7 @@ class AppointmentDoctorCard extends StatelessWidget {
AppointmentType.isArrived(patientAppointmentHistoryResponseModel), AppointmentType.isArrived(patientAppointmentHistoryResponseModel),
), ),
), ),
if (timerWidget != null) if (timerWidget != null) timerWidget ?? SizedBox()
timerWidget ?? SizedBox()
], ],
), ),
), ),

@ -1,3 +1,5 @@
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/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -19,7 +21,6 @@ import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dar
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/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'dart:ui' as ui;
class SavedLogin extends StatefulWidget { class SavedLogin extends StatefulWidget {
const SavedLogin({super.key}); const SavedLogin({super.key});
@ -33,6 +34,7 @@ class _SavedLogin extends State<SavedLogin> {
late AuthenticationViewModel authVm; late AuthenticationViewModel authVm;
late AppState appState; late AppState appState;
bool? isOther; bool? isOther;
@override @override
void initState() { void initState() {
authVm = context.read<AuthenticationViewModel>(); authVm = context.read<AuthenticationViewModel>();
@ -90,11 +92,11 @@ class _SavedLogin extends State<SavedLogin> {
: SizedBox(), : SizedBox(),
SizedBox(height: 24.h), SizedBox(height: 24.h),
Container( Container(
padding: EdgeInsets.all(16.h), padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, isCustomShadow: [ decoration: RoundedRectangleBorder()
BoxShadow(color: Color(0x0D000000), blurRadius: 16.h, offset: Offset(0, 0), spreadRadius: 5.h), .toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, isCustomShadow: [
]), BoxShadow(color: Color(0x0D000000), blurRadius: 16.h, offset: Offset(0, 0), spreadRadius: 5.h),
]),
child: Column( child: Column(
children: [ children: [
// Last login info - show WhatsApp only if isOther AND loginType is SMS // Last login info - show WhatsApp only if isOther AND loginType is SMS
@ -105,7 +107,9 @@ class _SavedLogin extends State<SavedLogin> {
textDirection: ui.TextDirection.ltr, textDirection: ui.TextDirection.ltr,
child: appState.getSelectDeviceByImeiRespModelElement != null child: appState.getSelectDeviceByImeiRespModelElement != null
? (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null ? (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null
? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y 'at' HH:mm") ? DateUtil.getFormattedDate(
DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!),
"d MMMM, y 'at' HH:mm")
: '--') : '--')
.toText16(isBold: true, color: AppColors.textColor, isEnglishOnly: true) .toText16(isBold: true, color: AppColors.textColor, isEnglishOnly: true)
: SizedBox(), : SizedBox(),
@ -115,10 +119,14 @@ class _SavedLogin extends State<SavedLogin> {
? Container( ? Container(
margin: EdgeInsets.all(16.h), margin: EdgeInsets.all(16.h),
child: Utils.buildSvgWithAssets( child: Utils.buildSvgWithAssets(
icon: (isOther == true && loginType == LoginTypeEnum.sms) ? AppAssets.whatsapp : getTypeIcons(appState.getSelectDeviceByImeiRespModelElement!.logInType!), icon: (isOther == true && loginType == LoginTypeEnum.sms)
? AppAssets.whatsapp
: getTypeIcons(appState.getSelectDeviceByImeiRespModelElement!.logInType!),
height: 54.h, height: 54.h,
width: 54.w, width: 54.w,
iconColor: (isOther == true && loginType == LoginTypeEnum.sms) || loginType.toInt == 4 ? null : AppColors.primaryRedColor)) iconColor: (isOther == true && loginType == LoginTypeEnum.sms) || loginType.toInt == 4
? null
: AppColors.primaryRedColor))
: SizedBox(), : SizedBox(),
// Main login button - for isOther with SMS, show WhatsApp, otherwise keep original login type // Main login button - for isOther with SMS, show WhatsApp, otherwise keep original login type
CustomButton( CustomButton(
@ -126,7 +134,6 @@ class _SavedLogin extends State<SavedLogin> {
? "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}" ? "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}"
: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}", : "${LocaleKeys.loginBy.tr()} ${loginType.displayName}",
onPressed: () { onPressed: () {
if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) {
authVm.loginWithFingerPrintFace(() {}); authVm.loginWithFingerPrintFace(() {});
} else { } else {
@ -147,7 +154,8 @@ class _SavedLogin extends State<SavedLogin> {
height: 40.h, height: 40.h,
padding: EdgeInsets.symmetric(vertical: 10.h), padding: EdgeInsets.symmetric(vertical: 10.h),
icon: (isOther == true && loginType == LoginTypeEnum.sms) ? AppAssets.whatsapp : getTypeIcons(loginType.toInt), icon: (isOther == true && loginType == LoginTypeEnum.sms) ? AppAssets.whatsapp : getTypeIcons(loginType.toInt),
iconColor: (isOther == true && loginType == LoginTypeEnum.sms) || loginType == LoginTypeEnum.whatsapp ? null : Colors.white, iconColor:
(isOther == true && loginType == LoginTypeEnum.sms) || loginType == LoginTypeEnum.whatsapp ? null : Colors.white,
), ),
], ],
), ),
@ -159,120 +167,124 @@ class _SavedLogin extends State<SavedLogin> {
padding: EdgeInsets.symmetric(horizontal: 16.w), padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Text( child: Text(
LocaleKeys.oR.tr(), LocaleKeys.oR.tr(),
style: context.dynamicTextStyle(fontSize: 16.f, fontWeight: FontWeight.w600,), style: context.dynamicTextStyle(
fontSize: 16.f,
fontWeight: FontWeight.w600,
),
), ),
), ),
SizedBox(height: 24.h), SizedBox(height: 24.h),
// OTP login button // OTP login button
loginType.toInt != 1 loginType.toInt != 1
? Column( ? Column(
children: [ children: [
loginType.toInt != 1 loginType.toInt != 1
? CustomButton( ? CustomButton(
text: LocaleKeys.loginByOTP.tr(), text: LocaleKeys.loginByOTP.tr(),
onPressed: () { onPressed: () {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
isDismissible: false, isDismissible: false,
useSafeArea: true, useSafeArea: true,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
enableDrag: false, enableDrag: false,
// Prevent dragging to avoid focus conflicts // Prevent dragging to avoid focus conflicts
builder: (bottomSheetContext) => builder: (bottomSheetContext) =>
StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) { StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) {
return Padding( return Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom), padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom),
child: SingleChildScrollView( child: SingleChildScrollView(
child: GenericBottomSheet( child: GenericBottomSheet(
countryCode: "966", countryCode: "966",
initialPhoneNumber: "", initialPhoneNumber: "",
textController: TextEditingController(), textController: TextEditingController(),
isFromSavedLogin: true, isFromSavedLogin: true,
isEnableCountryDropdown: true, isEnableCountryDropdown: true,
onCountryChange: (value) {}, onCountryChange: (value) {},
onChange: (String? value) {}, onChange: (String? value) {},
buttons: [ buttons: [
Padding( Padding(
padding: EdgeInsets.only(bottom: 10.h), padding: EdgeInsets.only(bottom: 10.h),
child: CustomButton( child: CustomButton(
text: LocaleKeys.sendOTPSMS.tr(), text: LocaleKeys.sendOTPSMS.tr(),
onPressed: () {
Navigator.of(context).pop();
loginType = LoginTypeEnum.sms;
authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms);
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: Colors.white,
icon: AppAssets.sms),
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.h),
child: (LocaleKeys.oR.tr()).toText16(color: AppColors.textColor)),
],
),
Padding(
padding: EdgeInsets.only(bottom: 10.h, top: 10.h),
child: CustomButton(
text: LocaleKeys.sendOTPWHATSAPP.tr(),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
loginType = LoginTypeEnum.sms; loginType = LoginTypeEnum.whatsapp;
authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms); authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp);
}, },
backgroundColor: AppColors.primaryRedColor, backgroundColor: AppColors.transparent,
borderColor: AppColors.primaryRedColor, borderColor: AppColors.textColor,
textColor: Colors.white, textColor: AppColors.textColor,
icon: AppAssets.sms), icon: AppAssets.whatsapp,
), iconColor: null,
Row( applyThemeColor: false,
crossAxisAlignment: CrossAxisAlignment.center, ),
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.h),
child: (LocaleKeys.oR.tr()).toText16(color: AppColors.textColor)),
],
),
Padding(
padding: EdgeInsets.only(bottom: 10.h, top: 10.h),
child: CustomButton(
text: LocaleKeys.sendOTPWHATSAPP.tr(),
onPressed: () {
Navigator.of(context).pop();
loginType = LoginTypeEnum.whatsapp;
authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp);
},
backgroundColor: AppColors.transparent,
borderColor: AppColors.textColor,
textColor: AppColors.textColor,
icon: AppAssets.whatsapp,
iconColor: null,
applyThemeColor: false,
), ),
), ],
], ),
), ),
), );
); }),
}), );
); },
}, height: isFoldable ? 50.h : 40.h,
backgroundColor: AppColors.whiteColor, backgroundColor: AppColors.whiteColor,
borderColor: AppColors.borderOnlyColor, borderColor: AppColors.borderOnlyColor,
textColor: AppColors.textColor, textColor: AppColors.textColor,
borderWidth: 2, borderWidth: 2,
padding: EdgeInsets.fromLTRB(0, 14.h, 0, 14.h), padding: EdgeInsets.fromLTRB(0, 14.h, 0, 14.h),
icon: AppAssets.sms, icon: AppAssets.sms,
iconColor: AppColors.textColor, iconColor: AppColors.textColor,
) )
: Container(), : Container(),
SizedBox( SizedBox(
height: 20.h, height: 20.h,
), ),
], ],
) )
: CustomButton( : CustomButton(
text: "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}", text: "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}",
icon: AppAssets.whatsapp, icon: AppAssets.whatsapp,
iconColor: null, iconColor: null,
onPressed: () { onPressed: () {
if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) {
authVm.loginWithFingerPrintFace(() {}); authVm.loginWithFingerPrintFace(() {});
} else { } else {
loginType = LoginTypeEnum.whatsapp; loginType = LoginTypeEnum.whatsapp;
authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp); authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp);
} }
}, },
backgroundColor: AppColors.whiteColor, backgroundColor: AppColors.whiteColor,
borderColor: AppColors.textColor, borderColor: AppColors.textColor,
textColor: AppColors.textColor, textColor: AppColors.textColor,
borderWidth: 2.w, borderWidth: 2.w,
padding: EdgeInsets.fromLTRB(0, 14.h, 0, 14.h), padding: EdgeInsets.fromLTRB(0, 14.h, 0, 14.h),
applyThemeColor: false, applyThemeColor: false,
), ),
], ],
const Spacer(flex: 2), const Spacer(flex: 2),
@ -294,7 +306,7 @@ class _SavedLogin extends State<SavedLogin> {
CustomPageRoute( CustomPageRoute(
page: LandingNavigation(), page: LandingNavigation(),
), ),
(r) => false); (r) => false);
// Navigator.of(context).pushAndRemoveUntil( // Navigator.of(context).pushAndRemoveUntil(
// MaterialPageRoute(builder: (BuildContext context) => LandingNavigation()) // MaterialPageRoute(builder: (BuildContext context) => LandingNavigation())
// ); // );

@ -1,6 +1,5 @@
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:intl/intl.dart' show NumberFormat;
import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart';
@ -13,8 +12,8 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie
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/widgets/appointment_calendar.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/doctor_rating_details.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/doctor_rating_details.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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';
@ -56,7 +55,9 @@ class DoctorProfilePage extends StatelessWidget {
doctorID: viewModel.doctorsProfileResponseModel.doctorID ?? 0, doctorID: viewModel.doctorsProfileResponseModel.doctorID ?? 0,
isActive: viewModel.isFavouriteDoctor, isActive: viewModel.isFavouriteDoctor,
onSuccess: (response) { onSuccess: (response) {
Utils.showToast(viewModel.isFavouriteDoctor ? LocaleKeys.doctorAddedToFavourite.tr(context: context) : LocaleKeys.doctorRemovedFromFavourite.tr(context: context)); Utils.showToast(viewModel.isFavouriteDoctor
? LocaleKeys.doctorAddedToFavourite.tr(context: context)
: LocaleKeys.doctorRemovedFromFavourite.tr(context: context));
// Successfully added/removed favorite - refresh the favorites list // Successfully added/removed favorite - refresh the favorites list
getIt.get<MyAppointmentsViewModel>().refreshFavouriteDoctors(); getIt.get<MyAppointmentsViewModel>().refreshFavouriteDoctors();
}, },
@ -75,7 +76,7 @@ class DoctorProfilePage extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// SizedBox(height: 24.h), isFoldable ? SizedBox(height: 24.h) : SizedBox.shrink(),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -87,18 +88,21 @@ class DoctorProfilePage extends StatelessWidget {
width: 63.h, width: 63.h,
height: 63.h, height: 63.h,
fit: BoxFit.cover, fit: BoxFit.cover,
).circle(100), ).circle(100.r),
SizedBox(width: 8.h), SizedBox(width: 8.h),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox( SizedBox(
width: 220.h, width: isFoldable ? 250.w : 220.w,
child: ("${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorTitleForProfile} ${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName}") child:
.toString() ("${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorTitleForProfile} ${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName}")
.toText24(isBold: true), .toString()
.toText24(isBold: true),
), ),
(bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.isNotEmpty ? bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.first : "") (bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.isNotEmpty
? bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.first
: "")
.toString() .toString()
.toText18(isBold: true, color: AppColors.primaryRedColor), .toText18(isBold: true, color: AppColors.primaryRedColor),
], ],
@ -137,12 +141,16 @@ class DoctorProfilePage extends StatelessWidget {
children: [ children: [
Column( Column(
children: [ children: [
Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_rating_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false), Utils.buildSvgWithAssets(
icon: AppAssets.doctor_profile_rating_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false),
SizedBox(height: 16.h), SizedBox(height: 16.h),
LocaleKeys.ratings.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor), LocaleKeys.ratings.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor),
bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate.toString().toText16(
.toString() isBold: true,
.toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"), color: AppColors.textColor,
isUnderLine: true,
decorationColor: AppColors.textColor,
fontFamily: "Poppins"),
], ],
).onPress(() { ).onPress(() {
bookAppointmentsViewModel.getDoctorRatingDetails(); bookAppointmentsViewModel.getDoctorRatingDetails();
@ -158,11 +166,18 @@ class DoctorProfilePage extends StatelessWidget {
SizedBox(width: 36.w), SizedBox(width: 36.w),
Column( Column(
children: [ children: [
Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_reviews_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false), Utils.buildSvgWithAssets(
icon: AppAssets.doctor_profile_reviews_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false),
SizedBox(height: 16.h), SizedBox(height: 16.h),
LocaleKeys.reviews.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor), LocaleKeys.reviews.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor),
NumberFormat.decimalPattern().format(bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate ?? 0) NumberFormat.decimalPattern()
.toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"), .format(bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate ?? 0)
.toText16(
isBold: true,
color: AppColors.textColor,
isUnderLine: true,
decorationColor: AppColors.textColor,
fontFamily: "Poppins"),
], ],
).onPress(() { ).onPress(() {
bookAppointmentsViewModel.getDoctorRatingDetails(); bookAppointmentsViewModel.getDoctorRatingDetails();
@ -182,92 +197,97 @@ class DoctorProfilePage extends StatelessWidget {
SizedBox(height: 16.h), SizedBox(height: 16.h),
LocaleKeys.information.tr(context: context).toText14(isBold: true, color: AppColors.textColor), LocaleKeys.information.tr(context: context).toText14(isBold: true, color: AppColors.textColor),
SizedBox(height: 6.h), SizedBox(height: 6.h),
(bookAppointmentsViewModel.doctorsProfileResponseModel.doctorProfileInfo ?? "").trim().toText12(isBold: true, color: AppColors.greyTextColor), (bookAppointmentsViewModel.doctorsProfileResponseModel.doctorProfileInfo ?? "")
.trim()
.toText12(isBold: true, color: AppColors.greyTextColor),
SizedBox(height: 24.h), SizedBox(height: 24.h),
], ],
).paddingSymmetrical(24.h, 0.h), ).paddingSymmetrical(24.h, 0.h),
), ),
), ),
), ),
isDoctorAllowedToBook ? Container( isDoctorAllowedToBook
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( ? Container(
color: AppColors.whiteColor, decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
borderRadius: 24.h, color: AppColors.whiteColor,
hasShadow: true, borderRadius: 24.h,
), hasShadow: true,
child: CustomButton( ),
text: LocaleKeys.viewAvailableAppointments.tr(), child: CustomButton(
onPressed: () async { text: LocaleKeys.viewAvailableAppointments.tr(),
bookAppointmentsViewModel.selectedDoctor.speciality = bookAppointmentsViewModel.doctorsProfileResponseModel.specialty; onPressed: () async {
bookAppointmentsViewModel.selectedDoctor.specialityN = bookAppointmentsViewModel.doctorsProfileResponseModel.specialty; bookAppointmentsViewModel.selectedDoctor.speciality = bookAppointmentsViewModel.doctorsProfileResponseModel.specialty;
bookAppointmentsViewModel.selectedDoctor.name = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName; bookAppointmentsViewModel.selectedDoctor.specialityN = bookAppointmentsViewModel.doctorsProfileResponseModel.specialty;
bookAppointmentsViewModel.selectedDoctor.doctorImageURL = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorImageURL; bookAppointmentsViewModel.selectedDoctor.name = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName;
bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL = bookAppointmentsViewModel.doctorsProfileResponseModel.nationalityFlagURL; bookAppointmentsViewModel.selectedDoctor.doctorImageURL = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorImageURL;
bookAppointmentsViewModel.selectedDoctor.clinicName = bookAppointmentsViewModel.doctorsProfileResponseModel.clinicDescription; bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL =
bookAppointmentsViewModel.selectedDoctor.projectName = bookAppointmentsViewModel.doctorsProfileResponseModel.projectName; bookAppointmentsViewModel.doctorsProfileResponseModel.nationalityFlagURL;
bookAppointmentsViewModel.selectedDoctor.clinicName = bookAppointmentsViewModel.doctorsProfileResponseModel.clinicDescription;
bookAppointmentsViewModel.selectedDoctor.projectName = bookAppointmentsViewModel.doctorsProfileResponseModel.projectName;
LoaderBottomSheet.showLoader(); LoaderBottomSheet.showLoader();
bookAppointmentsViewModel.isLiveCareSchedule bookAppointmentsViewModel.isLiveCareSchedule
? await bookAppointmentsViewModel.getLiveCareDoctorFreeSlots( ? await bookAppointmentsViewModel.getLiveCareDoctorFreeSlots(
isBookingForLiveCare: true, isBookingForLiveCare: true,
onSuccess: (dynamic respData) async { onSuccess: (dynamic respData) async {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight( showCommonBottomSheetWithoutHeight(
title: LocaleKeys.pickADate.tr(), title: LocaleKeys.pickADate.tr(),
context, context,
child: AppointmentCalendar(), child: AppointmentCalendar(),
isFullScreen: false, isFullScreen: false,
isCloseButtonVisible: true, isCloseButtonVisible: true,
callBackFunc: () {}, callBackFunc: () {},
); );
}, },
onError: (err) { onError: (err) {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight( showCommonBottomSheetWithoutHeight(
context, context,
child: Utils.getErrorWidget(loadingText: err), child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {}, callBackFunc: () {},
isFullScreen: false, isFullScreen: false,
isCloseButtonVisible: true, isCloseButtonVisible: true,
); );
}) })
: await bookAppointmentsViewModel.getDoctorFreeSlots( : await bookAppointmentsViewModel.getDoctorFreeSlots(
isBookingForLiveCare: false, isBookingForLiveCare: false,
onSuccess: (dynamic respData) async { onSuccess: (dynamic respData) async {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight( showCommonBottomSheetWithoutHeight(
title: LocaleKeys.pickADate.tr() , title: LocaleKeys.pickADate.tr(),
context, context,
child: AppointmentCalendar(), child: AppointmentCalendar(),
isFullScreen: false, isFullScreen: false,
isCloseButtonVisible: true, isCloseButtonVisible: true,
callBackFunc: () {}, callBackFunc: () {},
); );
}, },
onError: (err) { onError: (err) {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight( showCommonBottomSheetWithoutHeight(
context, context,
child: Utils.getErrorWidget(loadingText: err), child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {}, callBackFunc: () {},
isFullScreen: false, isFullScreen: false,
isCloseButtonVisible: true, isCloseButtonVisible: true,
); );
}); });
}, },
backgroundColor: AppColors.primaryRedColor, backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor,
textColor: Colors.white, textColor: Colors.white,
fontSize: 16, fontSize: 16,
isBold: true, isBold: true,
borderRadius: 12, borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0), padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 50.h, height: 50.h,
icon: AppAssets.calendar, icon: AppAssets.calendar,
iconColor: Colors.white, iconColor: Colors.white,
iconSize: 20.h, iconSize: 20.h,
).paddingSymmetrical(24.h, 24.h), ).paddingSymmetrical(24.h, 24.h),
) : SizedBox.shrink(), )
: SizedBox.shrink(),
], ],
), ),
); );

@ -75,7 +75,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
"${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText16(isBold: true, isEnglishOnly: true), "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}"
.toText16(isBold: true, isEnglishOnly: true),
SizedBox(height: 8.h), SizedBox(height: 8.h),
Wrap( Wrap(
direction: Axis.horizontal, direction: Axis.horizontal,
@ -83,13 +84,16 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
runSpacing: 4.h, runSpacing: 4.h,
children: [ children: [
AppCustomChipWidget( AppCustomChipWidget(
richText: Row( richText: Row(
children: [ children: [
"${appState.getAuthenticatedUser()!.age} ".toText10(color: AppColors.blackColor, isEnglishOnly: true), "${appState.getAuthenticatedUser()!.age} ".toText10(color: AppColors.blackColor, isEnglishOnly: true),
LocaleKeys.yearsOld.tr(context: context).toText10(color: AppColors.blackColor), LocaleKeys.yearsOld.tr(context: context).toText10(color: AppColors.blackColor),
], ],
),), ),
AppCustomChipWidget(labelText: "${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"), ),
AppCustomChipWidget(
labelText:
"${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"),
], ],
), ),
], ],
@ -115,7 +119,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
children: [ children: [
AppCustomChipWidget( AppCustomChipWidget(
labelText: labelText:
"${LocaleKeys.clinic.tr()}: ${(appState.isArabic() ? immediateLiveCareVM.immediateLiveCareSelectedClinic.serviceNameN : immediateLiveCareVM.immediateLiveCareSelectedClinic.serviceName) ?? ""}"), "${LocaleKeys.clinic.tr()}: ${(appState.isArabic() ? immediateLiveCareVM.immediateLiveCareSelectedClinic.serviceNameN : immediateLiveCareVM.immediateLiveCareSelectedClinic.serviceName) ?? ""}"),
SizedBox(height: 16.h), SizedBox(height: 16.h),
1.divider, 1.divider,
SizedBox(height: 16.h), SizedBox(height: 16.h),
@ -124,7 +128,12 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
children: [ children: [
Row( Row(
children: [ children: [
Utils.buildSvgWithAssets(icon: getLiveCareTypeIcon(immediateLiveCareVM.liveCareSelectedCallType), width: 32.h, height: 32.h, fit: BoxFit.contain, applyThemeColor: false), Utils.buildSvgWithAssets(
icon: getLiveCareTypeIcon(immediateLiveCareVM.liveCareSelectedCallType),
width: 32.h,
height: 32.h,
fit: BoxFit.contain,
applyThemeColor: false),
SizedBox(width: 8.h), SizedBox(width: 8.h),
getLiveCareType(context, immediateLiveCareVM.liveCareSelectedCallType).toText16(isBold: true), getLiveCareType(context, immediateLiveCareVM.liveCareSelectedCallType).toText16(isBold: true),
], ],
@ -136,7 +145,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
), ),
), ),
).onPress(() { ).onPress(() {
showCommonBottomSheetWithoutHeight(context, child: SelectLiveCareCallType(immediateLiveCareViewModel: immediateLiveCareVM), callBackFunc: () async { showCommonBottomSheetWithoutHeight(context, child: SelectLiveCareCallType(immediateLiveCareViewModel: immediateLiveCareVM),
callBackFunc: () async {
debugPrint("Selected Call Type: ${immediateLiveCareVM.liveCareSelectedCallType}"); debugPrint("Selected Call Type: ${immediateLiveCareVM.liveCareSelectedCallType}");
}, title: LocaleKeys.selectLiveCareCallType.tr(context: context), isCloseButtonVisible: true, isFullScreen: false); }, title: LocaleKeys.selectLiveCareCallType.tr(context: context), isCloseButtonVisible: true, isFullScreen: false);
}); });
@ -169,11 +179,15 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true).paddingSymmetrical(24.h, 0.h), LocaleKeys.insuranceExpiredOrInactive
.tr(context: context)
.toText14(color: AppColors.primaryRedColor, isBold: true)
.paddingSymmetrical(24.h, 0.h),
CustomButton( CustomButton(
text: LocaleKeys.updateInsurance.tr(context: context), text: LocaleKeys.updateInsurance.tr(context: context),
onPressed: () { onPressed: () {
Navigator.of(context).push( Navigator.of(context)
.push(
CustomPageRoute( CustomPageRoute(
page: InsuranceHomePage(), page: InsuranceHomePage(),
), ),
@ -214,7 +228,10 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true),
Utils.getPaymentAmountWithSymbol((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.amount ?? "").toText16(isBold: true, isEnglishOnly: true), AppColors.blackColor, 13, Utils.getPaymentAmountWithSymbol(
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.amount ?? "").toText16(isBold: true, isEnglishOnly: true),
AppColors.blackColor,
13,
isSaudiCurrency: (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "sar").toLowerCase() == "sar" || isSaudiCurrency: (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "sar").toLowerCase() == "sar" ||
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ريال").toLowerCase() == "ريال"), (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ريال").toLowerCase() == "ريال"),
], ],
@ -224,7 +241,10 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
children: [ children: [
LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor),
Utils.getPaymentAmountWithSymbol( Utils.getPaymentAmountWithSymbol(
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.tax ?? "0.0").toText14(isBold: true, color: AppColors.greyTextColor, isEnglishOnly: true), AppColors.greyTextColor, 13, (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.tax ?? "0.0")
.toText14(isBold: true, color: AppColors.greyTextColor, isEnglishOnly: true),
AppColors.greyTextColor,
13,
isSaudiCurrency: ((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "sar").toLowerCase() == "sar" || isSaudiCurrency: ((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "sar").toLowerCase() == "sar" ||
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ريال").toLowerCase() == "ريال")), (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ريال").toLowerCase() == "ريال")),
], ],
@ -233,13 +253,17 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
SizedBox(width: 200.h, child: Utils.getPaymentMethods()), Utils.getPaymentMethods(),
Utils.getPaymentAmountWithSymbol((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total ?? "0.0").toText24(isBold: true, isEnglishOnly: true), AppColors.blackColor, 17, Utils.getPaymentAmountWithSymbol(
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total ?? "0.0").toText24(isBold: true, isEnglishOnly: true),
AppColors.blackColor,
17,
isSaudiCurrency: ((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "sar").toLowerCase() == "sar" || isSaudiCurrency: ((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "sar").toLowerCase() == "sar" ||
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ريال").toLowerCase() == "ريال")), (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ريال").toLowerCase() == "ريال")),
], ],
).paddingSymmetrical(24.h, 0.h), ).paddingSymmetrical(24.h, 0.h),
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" || immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0") (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" ||
immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0")
// (true) // (true)
? CustomButton( ? CustomButton(
text: LocaleKeys.confirmLiveCare.tr(context: context), text: LocaleKeys.confirmLiveCare.tr(context: context),
@ -248,7 +272,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
if (val) { if (val) {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingLiveCareRequest.tr(context: context)); LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingLiveCareRequest.tr(context: context));
await immediateLiveCareVM.addNewCallRequestForImmediateLiveCare("${appState.getAuthenticatedUser()!.patientId}${DateTime.now().millisecondsSinceEpoch}"); await immediateLiveCareVM.addNewCallRequestForImmediateLiveCare(
"${appState.getAuthenticatedUser()!.patientId}${DateTime.now().millisecondsSinceEpoch}");
await immediateLiveCareVM.getPatientLiveCareHistory(); await immediateLiveCareVM.getPatientLiveCareHistory();
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
if (immediateLiveCareVM.patientHasPendingLiveCareRequest) { if (immediateLiveCareVM.patientHasPendingLiveCareRequest) {
@ -296,7 +321,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
borderColor: AppColors.successColor, borderColor: AppColors.successColor,
textColor: AppColors.whiteColor, textColor: AppColors.whiteColor,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
borderRadius: 12, borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0), padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 50.h, height: 50.h,
@ -339,7 +364,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
borderColor: AppColors.infoColor, borderColor: AppColors.infoColor,
textColor: AppColors.whiteColor, textColor: AppColors.whiteColor,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
borderRadius: 12, borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0), padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 50.h, height: 50.h,
@ -425,8 +450,9 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
final newlyPermanent = missing.where((p) => (newStatuses[p]?.isPermanentlyDenied ?? false) || (newStatuses[p]?.isRestricted ?? false)).toList(); final newlyPermanent = missing.where((p) => (newStatuses[p]?.isPermanentlyDenied ?? false) || (newStatuses[p]?.isRestricted ?? false)).toList();
if (newlyPermanent.isNotEmpty) { if (newlyPermanent.isNotEmpty) {
final names = newlyPermanent.map((p) => LiveCarePermissionService.instance.friendlyName(p)).join(' and '); final names = newlyPermanent.map((p) => LiveCarePermissionService.instance.friendlyName(p)).join(' and ');
final message = final message = newlyPermanent.length == 1
newlyPermanent.length == 1 ? '$names permission is permanently denied. Open app settings to allow it.' : '$names permissions are permanently denied. Open app settings to allow them.'; ? '$names permission is permanently denied. Open app settings to allow it.'
: '$names permissions are permanently denied. Open app settings to allow them.';
await LiveCarePermissionService.instance.showOpenSettingsDialog( await LiveCarePermissionService.instance.showOpenSettingsDialog(
context, context,
title: "Permissions Required", title: "Permissions Required",

@ -230,7 +230,7 @@ class _ImmediateLiveCarePendingRequestPageState extends State<ImmediateLiveCareP
workGroup: "Live_Care_Chat", workGroup: "Live_Care_Chat",
onSuccess: (response) { onSuccess: (response) {
debugPrint("Chat Request ID received: ${getIt.get<ContactUsViewModel>().chatRequestID}"); debugPrint("Chat Request ID received: ${getIt.get<ContactUsViewModel>().chatRequestID}");
chatURL = "https://chat.hmg.com/Index.aspx?RequestedId=${getIt.get<ContactUsViewModel>().chatRequestID}"; chatURL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=${getIt.get<ContactUsViewModel>().chatRequestID}";
debugPrint("Chat URL: $chatURL"); debugPrint("Chat URL: $chatURL");
Uri uri = Uri.parse(chatURL); Uri uri = Uri.parse(chatURL);
launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: "");

@ -1,10 +1,11 @@
import 'dart:developer';
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/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/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/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/loading_utils.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -13,11 +14,12 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_view_
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/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/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/waiting_appointment/waiting_appointment_payment_page.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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/appbar/collapsing_list_view.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';
@ -37,6 +39,7 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
late BookAppointmentsViewModel bookAppointmentsViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel;
late AuthenticationViewModel authVM; late AuthenticationViewModel authVM;
late MyAppointmentsViewModel myAppointmentsViewModel; late MyAppointmentsViewModel myAppointmentsViewModel;
late SymptomsCheckerViewModel symptomsCheckerViewModel;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -44,6 +47,7 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false); myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false);
authVM = Provider.of<AuthenticationViewModel>(context, listen: false); authVM = Provider.of<AuthenticationViewModel>(context, listen: false);
appState = getIt.get<AppState>(); appState = getIt.get<AppState>();
symptomsCheckerViewModel = Provider.of<SymptomsCheckerViewModel>(context, listen: false);
return Scaffold( return Scaffold(
backgroundColor: AppColors.scaffoldBgColor, backgroundColor: AppColors.scaffoldBgColor,
body: Column( body: Column(
@ -74,7 +78,8 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
Row( Row(
children: [ children: [
Image.network( Image.network(
bookAppointmentsViewModel.selectedDoctor.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png", bookAppointmentsViewModel.selectedDoctor.doctorImageURL ??
"https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png",
width: 50.h, width: 50.h,
height: 50.h, height: 50.h,
fit: BoxFit.cover, fit: BoxFit.cover,
@ -90,9 +95,11 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
.toString() .toString()
.toText16(isBold: true, maxlines: 1), .toText16(isBold: true, maxlines: 1),
SizedBox(width: 12.w), SizedBox(width: 12.w),
(bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null && bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty) (bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null &&
bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty)
? Image.network( ? Image.network(
bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png", bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL ??
"https://hmgwebservices.com/Images/flag/SAU.png",
width: 20.h, width: 20.h,
height: 15.h, height: 15.h,
fit: BoxFit.cover, fit: BoxFit.cover,
@ -101,7 +108,9 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
], ],
), ),
SizedBox(height: 2.h), SizedBox(height: 2.h),
(bookAppointmentsViewModel.selectedDoctor.speciality!.isNotEmpty ? bookAppointmentsViewModel.selectedDoctor.speciality!.first : "") (bookAppointmentsViewModel.selectedDoctor.speciality!.isNotEmpty
? bookAppointmentsViewModel.selectedDoctor.speciality!.first
: "")
.toString() .toString()
.toText12(isBold: true, color: AppColors.greyTextColor, maxLine: 1), .toText12(isBold: true, color: AppColors.greyTextColor, maxLine: 1),
], ],
@ -163,7 +172,8 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
spacing: 4.h, spacing: 4.h,
runSpacing: 4.h, runSpacing: 4.h,
children: [ children: [
AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"), AppCustomChipWidget(
labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"),
AppCustomChipWidget( AppCustomChipWidget(
labelText: labelText:
"${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"), "${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"),
@ -300,7 +310,9 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.getPatientAppointments(true, false); myAppointmentsViewModel.getPatientAppointments(true, false);
showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: apiResponse.data["SuccessMsg"]), callBackFunc: () { showCommonBottomSheetWithoutHeight(context,
title: LocaleKeys.success.tr(context: context),
child: Utils.getSuccessWidget(loadingText: apiResponse.data["SuccessMsg"]), callBackFunc: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
Navigator.pushAndRemoveUntil( Navigator.pushAndRemoveUntil(
context, context,
@ -312,7 +324,8 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
}, },
onError: (error) { onError: (error) {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.error.tr(context: context), child: Utils.getErrorWidget(loadingText: error), callBackFunc: () { showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.error.tr(context: context), child: Utils.getErrorWidget(loadingText: error),
callBackFunc: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, isFullScreen: false); }, isFullScreen: false);
}, },
@ -322,7 +335,9 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
void initiateBookAppointment() async { void initiateBookAppointment() async {
// LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context)); // LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context));
LoaderBottomSheet.showLoader( LoaderBottomSheet.showLoader(
loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context)); loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment
? LocaleKeys.reschedulingAppo.tr(context: context)
: LocaleKeys.bookingYourAppointment.tr(context: context));
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
if (bookAppointmentsViewModel.isLiveCareSchedule) { if (bookAppointmentsViewModel.isLiveCareSchedule) {
@ -333,7 +348,8 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
await Future.delayed(Duration(milliseconds: 50)).then((value) async { await Future.delayed(Duration(milliseconds: 50)).then((value) async {
// LoaderBottomSheet.showLoader(loadingText: LocaleKeys.appointmentSuccess.tr()); // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.appointmentSuccess.tr());
showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()), callBackFunc: () { showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()),
callBackFunc: () {
bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false); bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false);
bookAppointmentsViewModel.setIsLiveCareSchedule(false); bookAppointmentsViewModel.setIsLiveCareSchedule(false);
Navigator.pushAndRemoveUntil( Navigator.pushAndRemoveUntil(
@ -363,9 +379,52 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
isCloseButtonVisible: true, isCloseButtonVisible: true,
); );
}, onSuccess: (apiResp) async { }, onSuccess: (apiResp) async {
log("AppointmentNo: ${symptomsCheckerViewModel.isBookingFromSymptomsChecker}");
// Check if booking is from symptoms checker and call the API
if (symptomsCheckerViewModel.isBookingFromSymptomsChecker) {
final appointmentNo = apiResp.data['AppointmentNo']?.toString() ?? '';
final doctorId = bookAppointmentsViewModel.selectedDoctor.doctorID?.toString() ?? '';
// Convert date and time to ISO 8601 format with timezone
DateTime? appointmentDateTime;
try {
// Combine date (YYYY-MM-DD) and time (HH:MM) into ISO format string
final dateTimeString = '${bookAppointmentsViewModel.selectedAppointmentDate}T${bookAppointmentsViewModel.selectedAppointmentTime}:00';
appointmentDateTime = DateTime.parse(dateTimeString);
} catch (e) {
log("Error parsing appointment date/time: $e");
}
// Convert to ISO 8601 format with UTC timezone (e.g., "2026-02-11T13:15:37.652Z")
final appointmentDate = appointmentDateTime != null ? DateUtil.getISODateFormat(appointmentDateTime.toUtc()) : '';
final mobileNumber = appState.getAuthenticatedUser()?.mobileNumber ?? '';
final fileNo = appState.getAuthenticatedUser()?.patientId?.toString() ?? '';
final projectId = bookAppointmentsViewModel.selectedDoctor.projectID ?? 0;
final clinicId = bookAppointmentsViewModel.selectedDoctor.clinicID ?? 0;
await symptomsCheckerViewModel.saveAppointmentDetailsForSymptomsChecker(
fileNo: fileNo,
appointmentNo: appointmentNo,
doctorId: doctorId,
appointmentDate: appointmentDate,
mobileNumber: mobileNumber,
projectId: projectId,
clinicId: clinicId,
onSuccess: (response) {
// Success - continue with normal flow
debugPrint("onSuccess called for saveAppointmentDetailsForSymptomsChecker: ${response.data}");
},
onError: (error) {
// Log error but don't block the user flow
debugPrint("Error saving symptoms checker appointment: $error");
},
);
}
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
await Future.delayed(Duration(milliseconds: 50)).then((value) async { await Future.delayed(Duration(milliseconds: 50)).then((value) async {
showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h), callBackFunc: () { showCommonBottomSheetWithoutHeight(context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h), callBackFunc: () {
bookAppointmentsViewModel.setIsLiveCareSchedule(false); bookAppointmentsViewModel.setIsLiveCareSchedule(false);
bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false); bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false);
Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false); Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false);

@ -92,7 +92,7 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
// ], // ],
// ), // ),
SizedBox( SizedBox(
height: 350.h, height: MediaQuery.of(context).size.height * 0.45,
child: Directionality( child: Directionality(
textDirection: isArabic ? ui.TextDirection.rtl : ui.TextDirection.ltr, textDirection: isArabic ? ui.TextDirection.rtl : ui.TextDirection.ltr,
child: Localizations.override( child: Localizations.override(
@ -102,7 +102,7 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
controller: _calendarController, controller: _calendarController,
minDate: DateTime.now(), minDate: DateTime.now(),
showNavigationArrow: true, showNavigationArrow: true,
headerHeight: 60.h, // headerHeight: 60.h,
headerStyle: CalendarHeaderStyle( headerStyle: CalendarHeaderStyle(
backgroundColor: AppColors.transparent, backgroundColor: AppColors.transparent,
textAlign: isArabic ? TextAlign.end : TextAlign.start, textAlign: isArabic ? TextAlign.end : TextAlign.start,
@ -157,8 +157,11 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
), ),
//TODO: Add Next Day Span here //TODO: Add Next Day Span here
dayEvents.isNotEmpty dayEvents.isNotEmpty
? SizedBox( ? ConstrainedBox(
height: 100.h, constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.15,
minHeight: 0,
),
child: Directionality( child: Directionality(
textDirection: isArabic ? ui.TextDirection.rtl : ui.TextDirection.ltr, textDirection: isArabic ? ui.TextDirection.rtl : ui.TextDirection.ltr,
child: SingleChildScrollView( child: SingleChildScrollView(
@ -169,7 +172,7 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
spacing: 6.h, spacing: 6.h,
runSpacing: 6.h, runSpacing: 6.h,
children: List.generate( children: List.generate(
dayEvents.length, // Generate a large number of items to ensure scrolling dayEvents.length,
(index) => TimeSlotChip( (index) => TimeSlotChip(
label: dayEvents[index].isoTime!, label: dayEvents[index].isoTime!,
isSelected: index == selectedButtonIndex, isSelected: index == selectedButtonIndex,
@ -204,7 +207,8 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
), ),
); );
} else { } else {
bookAppointmentsViewModel.getAppointmentNearestGate(projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!); bookAppointmentsViewModel.getAppointmentNearestGate(
projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!);
bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime, selectedDateDisplay); bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime, selectedDateDisplay);
Navigator.of(context).pop(); Navigator.of(context).pop();
Navigator.of(context).push( Navigator.of(context).push(
@ -221,7 +225,8 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), Lottie.asset(AppAnimations.errorAnimation,
repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill),
SizedBox(height: 8.h), SizedBox(height: 8.h),
(LocaleKeys.loginToUseService.tr(context: context)).toText16(color: AppColors.blackColor), (LocaleKeys.loginToUseService.tr(context: context)).toText16(color: AppColors.blackColor),
SizedBox(height: 16.h), SizedBox(height: 16.h),
@ -314,13 +319,18 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
if (bookAppointmentsViewModel.isWaitingAppointmentAvailable && DateUtils.isSameDay(dateStart, DateTime.now())) { if (bookAppointmentsViewModel.isWaitingAppointmentAvailable && DateUtils.isSameDay(dateStart, DateTime.now())) {
dayEvents.add(TimeSlot(isoTime: LocaleKeys.waitingAppointment.tr(context: context), start: DateTime.now(), end: DateTime.now(), vidaDate: "")); dayEvents.add(TimeSlot(isoTime: LocaleKeys.waitingAppointment.tr(context: context), start: DateTime.now(), end: DateTime.now(), vidaDate: ""));
} }
freeSlots.forEach((v) { for (var v in freeSlots) {
if (v.start == dateStartObj) dayEvents.add(v); if (v.start == dateStartObj) dayEvents.add(v);
}); }
selectedButtonIndex = 0; selectedButtonIndex = 0;
List<Map<String, dynamic>> timeList = []; List<Map<String, dynamic>> timeList = [];
for (var i = 0; i < dayEvents.length; i++) { for (var i = 0; i < dayEvents.length; i++) {
Map<String, dynamic> timeSlot = {"isoTime": dayEvents[i].isoTime, "start": dayEvents[i].start.toString(), "end": dayEvents[i].end.toString(), "vidaDate": dayEvents[i].vidaDate}; Map<String, dynamic> timeSlot = {
"isoTime": dayEvents[i].isoTime,
"start": dayEvents[i].start.toString(),
"end": dayEvents[i].end.toString(),
"vidaDate": dayEvents[i].vidaDate
};
timeList.add(timeSlot); timeList.add(timeSlot);
} }
if (dayEvents.isNotEmpty) { if (dayEvents.isNotEmpty) {
@ -333,9 +343,7 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
final DateFormat formatter = DateFormat('yyyy-MM-dd', "en-US"); final DateFormat formatter = DateFormat('yyyy-MM-dd', "en-US");
final isArabic = appState.isArabic(); final isArabic = appState.isArabic();
setState(() { setState(() {
selectedDateDisplay = isArabic selectedDateDisplay = isArabic ? DateUtil.getMonthDayYearDateFormattedAr(day) : DateUtil.getMonthDayYearDateFormatted(day);
? DateUtil.getMonthDayYearDateFormattedAr(day)
: DateUtil.getMonthDayYearDateFormatted(day);
selectedNextDate = DateUtil.getWeekDayMonthDayYearDateFormatted(day.add(Duration(days: 1)), isArabic ? "ar" : "en"); selectedNextDate = DateUtil.getWeekDayMonthDayYearDateFormatted(day.add(Duration(days: 1)), isArabic ? "ar" : "en");
_calendarController.selectedDate = day; _calendarController.selectedDate = day;
openTimeSlotsPickerForDate(day, bookAppointmentsViewModel.docFreeSlots); openTimeSlotsPickerForDate(day, bookAppointmentsViewModel.docFreeSlots);

@ -24,7 +24,7 @@ class RequestStatus extends StatelessWidget {
case 2: //processing case 2: //processing
return LocaleKeys.underProcessing.tr(); return LocaleKeys.underProcessing.tr();
case 3: case 3:
return LocaleKeys.completed.tr(); return LocaleKeys.completedPrescriptionOrder.tr();
case 4: //cancel case 4: //cancel
case 6: case 6:
case 7: case 7:

@ -1,9 +1,7 @@
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/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/enums.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/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.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/select_clinic_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart';
@ -19,7 +17,6 @@ import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ibw.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ibw.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ovulation.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/ovulation.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/triglycerides.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/triglycerides.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -27,11 +24,11 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class HealthCalculatorDetailedPage extends StatefulWidget { class HealthCalculatorDetailedPage extends StatefulWidget {
HealthCalculatorsTypeEnum calculatorType; final HealthCalculatorsTypeEnum calculatorType;
int? clinicID; final int? clinicID;
int? calculationID; final int? calculationID;
HealthCalculatorDetailedPage({super.key, required this.calculatorType, this.clinicID, this.calculationID}); const HealthCalculatorDetailedPage({super.key, required this.calculatorType, this.clinicID, this.calculationID});
@override @override
State<HealthCalculatorDetailedPage> createState() => _HealthCalculatorDetailedPageState(); State<HealthCalculatorDetailedPage> createState() => _HealthCalculatorDetailedPageState();
@ -52,8 +49,8 @@ class _HealthCalculatorDetailedPageState extends State<HealthCalculatorDetailedP
widget.calculatorType == HealthCalculatorsTypeEnum.triglycerides widget.calculatorType == HealthCalculatorsTypeEnum.triglycerides
? SizedBox() ? SizedBox()
: Container( : Container(
decoration: decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))), color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h), padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h),
child: CustomButton( child: CustomButton(
text: widget.calculatorType == HealthCalculatorsTypeEnum.bloodSugar || text: widget.calculatorType == HealthCalculatorsTypeEnum.bloodSugar ||
@ -131,36 +128,6 @@ class _HealthCalculatorDetailedPageState extends State<HealthCalculatorDetailedP
: {'result': provider.triglyceridesResult ?? result, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}; : {'result': provider.triglyceridesResult ?? result, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID};
break; break;
} }
// switch (widget.calculatorType) {
// case HealthCalculatorsTypeEnum.bmi:
// calculatedResult = provider.bmiResultMap ?? result;
// break;
// case HealthCalculatorsTypeEnum.calories:
// case HealthCalculatorsTypeEnum.bmr:
// calculatedResult = provider.caloriesResultMap ?? result;
// break;
// case HealthCalculatorsTypeEnum.idealBodyWeight:
// calculatedResult = provider.ibwResultMap ?? result;
// break;
// case HealthCalculatorsTypeEnum.bodyFat:
// calculatedResult = provider.bodyFatResultMap ?? result;
// break;
// case HealthCalculatorsTypeEnum.crabsProteinFat:
// calculatedResult = provider.macrosResultMap ?? result;
// break;
// case HealthCalculatorsTypeEnum.ovulation:
// calculatedResult = provider.ovulationResult ?? result;
// break;
// case HealthCalculatorsTypeEnum.deliveryDueDate:
// calculatedResult = provider.deliveryResult ?? result;
// break;
// case HealthCalculatorsTypeEnum.bloodSugar:
// calculatedResult = provider.bloodSugarResult ?? result;
// case HealthCalculatorsTypeEnum.bloodCholesterol:
// calculatedResult = provider.bloodCholesterolResult ?? result;
// case HealthCalculatorsTypeEnum.triglycerides:
// calculatedResult = provider.triglyceridesResult ?? result;
// }
}, },
).paddingSymmetrical(20.w, 24.h), ).paddingSymmetrical(20.w, 24.h),
); );
@ -172,58 +139,69 @@ class _HealthCalculatorDetailedPageState extends State<HealthCalculatorDetailedP
switch (widget.calculatorType) { switch (widget.calculatorType) {
case HealthCalculatorsTypeEnum.bmi: case HealthCalculatorsTypeEnum.bmi:
return Container( return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BMIWidget(onChange: onCalculate), child: BMIWidget(onChange: onCalculate),
); );
case HealthCalculatorsTypeEnum.calories: case HealthCalculatorsTypeEnum.calories:
return Container( return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: CaloriesWidget(onChange: onCalculate), child: CaloriesWidget(onChange: onCalculate),
); );
case HealthCalculatorsTypeEnum.bmr: case HealthCalculatorsTypeEnum.bmr:
return Container( return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BMRWidget(onChange: onCalculate), child: BMRWidget(onChange: onCalculate),
); );
case HealthCalculatorsTypeEnum.idealBodyWeight: case HealthCalculatorsTypeEnum.idealBodyWeight:
return Container( return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: IdealBodyWeightWidget(onChange: onCalculate), child: IdealBodyWeightWidget(onChange: onCalculate),
); );
case HealthCalculatorsTypeEnum.bodyFat: case HealthCalculatorsTypeEnum.bodyFat:
return Container( return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BodyFatWidget(onChange: onCalculate), child: BodyFatWidget(onChange: onCalculate),
); );
case HealthCalculatorsTypeEnum.crabsProteinFat: case HealthCalculatorsTypeEnum.crabsProteinFat:
return Container( return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: CrabsWidget(onChange: onCalculate), child: CrabsWidget(onChange: onCalculate),
); );
case HealthCalculatorsTypeEnum.ovulation: case HealthCalculatorsTypeEnum.ovulation:
return Container( return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: OvulationWidget(onChange: onCalculate), child: OvulationWidget(onChange: onCalculate),
); );
case HealthCalculatorsTypeEnum.deliveryDueDate: case HealthCalculatorsTypeEnum.deliveryDueDate:
return Container( return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: DeliveryDueDWidget(onChange: onCalculate), child: DeliveryDueDWidget(onChange: onCalculate),
); );
case HealthCalculatorsTypeEnum.bloodSugar: case HealthCalculatorsTypeEnum.bloodSugar:
return Container( return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BloodSugarWidget(onChange: onCalculate), child: BloodSugarWidget(onChange: onCalculate),
); );
case HealthCalculatorsTypeEnum.bloodCholesterol: case HealthCalculatorsTypeEnum.bloodCholesterol:
return Container( return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BloodCholesterolWidget(onChange: onCalculate), child: BloodCholesterolWidget(onChange: onCalculate),
); );
case HealthCalculatorsTypeEnum.triglycerides: case HealthCalculatorsTypeEnum.triglycerides:
return Container( return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: TriglyceridesWidget(onChange: onCalculate), child: TriglyceridesWidget(onChange: onCalculate),
); );
} }

@ -11,16 +11,15 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.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/health_calculators_and_converts/health_calculator_detailed_page.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/health_card.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/widgets/health_card.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/expandable_list_widget.dart'; import 'package:hmg_patient_app_new/widgets/expandable_list_widget.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';
class HealthCalculatorsPage extends StatefulWidget { class HealthCalculatorsPage extends StatefulWidget {
HealthCalConEnum type; final HealthCalConEnum type;
HealthCalculatorsPage({super.key, required this.type}); const HealthCalculatorsPage({super.key, required this.type});
@override @override
State<HealthCalculatorsPage> createState() => _HealthCalculatorsPageState(); State<HealthCalculatorsPage> createState() => _HealthCalculatorsPageState();
@ -34,9 +33,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
DialogService dialogService = getIt.get<DialogService>();
return CollapsingListView( return CollapsingListView(
isLeading: Navigator.canPop(context), isLeading: Navigator.canPop(context),
title: widget.type == HealthCalConEnum.calculator ? LocaleKeys.healthCalculators.tr(context: context) : LocaleKeys.healthConverters.tr(), title: widget.type == HealthCalConEnum.calculator ? LocaleKeys.healthCalculators.tr(context: context) : LocaleKeys.healthConverters.tr(),
child: widget.type == HealthCalConEnum.calculator child: widget.type == HealthCalConEnum.calculator
? CustomExpandableList( ? CustomExpandableList(
@ -58,7 +56,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
), ),
], ],
theme: ExpandableListTheme.custom( theme: ExpandableListTheme.custom(
defaultTrailingIcon: Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 22.h, width: 22.w, iconColor: AppColors.textColor), defaultTrailingIcon:
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 22.h, width: 22.w, iconColor: AppColors.textColor),
), ),
).paddingSymmetrical(16.w, 0.0) ).paddingSymmetrical(16.w, 0.0)
// ? Column( // ? Column(
@ -129,7 +128,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Utils.buildSvgWithAssets(icon: AppAssets.bloodSugar, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false), Utils.buildSvgWithAssets(
icon: AppAssets.bloodSugar, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false),
SizedBox(width: 12.w), SizedBox(width: 12.w),
Flexible( Flexible(
child: Column( child: Column(
@ -149,7 +149,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
), ),
Transform.flip( Transform.flip(
flipX: getIt.get<AppState>().isArabic(), flipX: getIt.get<AppState>().isArabic(),
child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), child: Utils.buildSvgWithAssets(
icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor),
), ),
], ],
).paddingAll(16.w)) ).paddingAll(16.w))
@ -166,7 +167,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Utils.buildSvgWithAssets(icon: AppAssets.bloodCholestrol, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false), Utils.buildSvgWithAssets(
icon: AppAssets.bloodCholestrol, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false),
SizedBox(width: 12.w), SizedBox(width: 12.w),
Flexible( Flexible(
child: Column( child: Column(
@ -181,7 +183,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
SizedBox(width: 12.w), SizedBox(width: 12.w),
Transform.flip( Transform.flip(
flipX: getIt.get<AppState>().isArabic(), flipX: getIt.get<AppState>().isArabic(),
child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), child: Utils.buildSvgWithAssets(
icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor),
), ),
], ],
).paddingAll(16.w)) ).paddingAll(16.w))
@ -198,7 +201,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Utils.buildSvgWithAssets(icon: AppAssets.triglycerides, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false), Utils.buildSvgWithAssets(
icon: AppAssets.triglycerides, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false),
SizedBox(width: 12.w), SizedBox(width: 12.w),
Flexible( Flexible(
child: Column( child: Column(
@ -213,7 +217,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
SizedBox(width: 12.w), SizedBox(width: 12.w),
Transform.flip( Transform.flip(
flipX: getIt.get<AppState>().isArabic(), flipX: getIt.get<AppState>().isArabic(),
child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), child: Utils.buildSvgWithAssets(
icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor),
), ),
], ],
).paddingAll(16.w)) ).paddingAll(16.w))
@ -247,7 +252,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
page: HealthCalculatorDetailedPage( page: HealthCalculatorDetailedPage(
calculatorType: type == HealthCalculatorEnum.general ? generalHealthServices[index].type : womenHealthServices[index].type, calculatorType: type == HealthCalculatorEnum.general ? generalHealthServices[index].type : womenHealthServices[index].type,
clinicID: type == HealthCalculatorEnum.general ? generalHealthServices[index].clinicID : womenHealthServices[index].clinicID, clinicID: type == HealthCalculatorEnum.general ? generalHealthServices[index].clinicID : womenHealthServices[index].clinicID,
calculationID: type == HealthCalculatorEnum.general ? generalHealthServices[index].calculationID : womenHealthServices[index].calculationID, calculationID:
type == HealthCalculatorEnum.general ? generalHealthServices[index].calculationID : womenHealthServices[index].calculationID,
), ),
), ),
); );
@ -341,5 +347,14 @@ class HealthComponentModel {
int? clinicID; int? clinicID;
int? calculationID; int? calculationID;
HealthComponentModel({required this.title, this.subTitle, required this.icon, this.iconColor, this.bgColor, this.textColor, required this.type, this.clinicID, this.calculationID}); HealthComponentModel(
{required this.title,
this.subTitle,
required this.icon,
this.iconColor,
this.bgColor,
this.textColor,
required this.type,
this.clinicID,
this.calculationID});
} }

@ -1,14 +1,14 @@
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/generated/locale_keys.g.dart';
import 'package:provider/provider.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:provider/provider.dart';
class BloodCholesterolWidget extends StatefulWidget { class BloodCholesterolWidget extends StatefulWidget {
final Function(dynamic result)? onChange; final Function(dynamic result)? onChange;
@ -107,7 +107,7 @@ class _BloodCholesterolWidgetState extends State<BloodCholesterolWidget> {
], ],
), ),
_buildInputField( _buildInputField(
label:LocaleKeys.mmol.tr(), label: LocaleKeys.mmol.tr(),
hint: "3.1", hint: "3.1",
controller: _mmolController, controller: _mmolController,
focusNode: _mmolFocus, focusNode: _mmolFocus,
@ -123,7 +123,7 @@ class _BloodCholesterolWidgetState extends State<BloodCholesterolWidget> {
Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w), Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w),
SizedBox(width: 12.w), SizedBox(width: 12.w),
Expanded( Expanded(
child:LocaleKeys.convertBloodcholesterolInfo.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), child: LocaleKeys.convertBloodcholesterolInfo.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor),
), ),
], ],
).paddingSymmetrical(0.w, 16.w), ).paddingSymmetrical(0.w, 16.w),
@ -149,29 +149,26 @@ class _BloodCholesterolWidgetState extends State<BloodCholesterolWidget> {
isBold: true, isBold: true,
color: AppColors.inputLabelTextColor, color: AppColors.inputLabelTextColor,
), ),
SizedBox( TextField(
height: 40.h, controller: controller,
child: TextField( focusNode: focusNode,
controller: controller, keyboardType: const TextInputType.numberWithOptions(decimal: true),
focusNode: focusNode, maxLines: 1,
keyboardType: const TextInputType.numberWithOptions(decimal: true), onChanged: onChanged,
maxLines: 1, cursorHeight: 35.h,
onChanged: onChanged, textAlignVertical: TextAlignVertical.center,
cursorHeight: 35.h, decoration: InputDecoration(
textAlignVertical: TextAlignVertical.center, border: InputBorder.none,
decoration: InputDecoration( contentPadding: EdgeInsets.zero,
border: InputBorder.none, isCollapsed: true,
contentPadding: EdgeInsets.zero, hintText: hint,
isCollapsed: true, hintStyle: const TextStyle(color: Colors.grey),
hintText: hint, ),
hintStyle: const TextStyle(color: Colors.grey), style: TextStyle(
), fontSize: 32.f,
style: TextStyle( fontWeight: FontWeight.bold,
fontSize: 32.f, color: Colors.black87,
fontWeight: FontWeight.bold, height: 1.h,
color: Colors.black87,
height: 1.h,
),
), ),
), ),
], ],

@ -1,14 +1,14 @@
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/generated/locale_keys.g.dart';
import 'package:provider/provider.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:provider/provider.dart';
class TriglyceridesWidget extends StatefulWidget { class TriglyceridesWidget extends StatefulWidget {
final Function(dynamic result)? onChange; final Function(dynamic result)? onChange;
@ -91,7 +91,6 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
provider.onTriglyceridesMgdlChanged(value); provider.onTriglyceridesMgdlChanged(value);
}, },
).paddingOnly(top: 16.h), ).paddingOnly(top: 16.h),
Row( Row(
children: [ children: [
const Expanded( const Expanded(
@ -111,7 +110,6 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
}), }),
], ],
), ),
_buildInputField( _buildInputField(
label: LocaleKeys.mmol, label: LocaleKeys.mmol,
hint: "1.7", hint: "1.7",
@ -122,9 +120,7 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
provider.onTriglyceridesMmolChanged(value); provider.onTriglyceridesMmolChanged(value);
}, },
).paddingOnly(bottom: 16.h), ).paddingOnly(bottom: 16.h),
const Divider(height: 1, color: Color(0xFFEEEEEE)), const Divider(height: 1, color: Color(0xFFEEEEEE)),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -135,12 +131,10 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
), ),
SizedBox(width: 12.w), SizedBox(width: 12.w),
Expanded( Expanded(
child: child: LocaleKeys.triglycerideInfo.tr().toText12(
LocaleKeys.triglycerideInfo.tr() isBold: true,
.toText12( color: AppColors.inputLabelTextColor,
isBold: true, ),
color: AppColors.inputLabelTextColor,
),
), ),
], ],
).paddingSymmetrical(0.w, 16.w), ).paddingSymmetrical(0.w, 16.w),
@ -165,27 +159,23 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
isBold: true, isBold: true,
color: AppColors.inputLabelTextColor, color: AppColors.inputLabelTextColor,
), ),
SizedBox( TextField(
height: 40.h, controller: controller,
child: TextField( focusNode: focusNode,
controller: controller, keyboardType: const TextInputType.numberWithOptions(decimal: true),
focusNode: focusNode, onChanged: onChanged,
keyboardType: cursorHeight: 35.h,
const TextInputType.numberWithOptions(decimal: true), decoration: InputDecoration(
onChanged: onChanged, border: InputBorder.none,
cursorHeight: 35.h, contentPadding: EdgeInsets.zero,
decoration: InputDecoration( isCollapsed: true,
border: InputBorder.none, hintText: hint,
contentPadding: EdgeInsets.zero, hintStyle: const TextStyle(color: Colors.grey),
isCollapsed: true, ),
hintText: hint, style: TextStyle(
hintStyle: const TextStyle(color: Colors.grey), fontSize: 32.f,
), fontWeight: FontWeight.bold,
style: TextStyle( color: Colors.black87,
fontSize: 32.f,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
), ),
), ),
], ],

@ -1,12 +1,10 @@
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_staggered_animations/flutter_staggered_animations.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_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/enums.dart'; import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/location_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -14,10 +12,8 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart'; import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_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/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_services_component_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_services_component_model.dart';
import 'package:hmg_patient_app_new/features/hospital/hospital_selection_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart'; import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart';
@ -29,13 +25,11 @@ import 'package:hmg_patient_app_new/presentation/emergency_services/emergency_se
import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart';
import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart';
import 'package:hmg_patient_app_new/presentation/hmg_services/services_view.dart'; import 'package:hmg_patient_app_new/presentation/hmg_services/services_view.dart';
import 'package:hmg_patient_app_new/presentation/hmg_services/widgets/weather_widget.dart';
import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart';
import 'package:hmg_patient_app_new/presentation/home/service_info_page.dart'; import 'package:hmg_patient_app_new/presentation/home/service_info_page.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart';
import 'package:hmg_patient_app_new/presentation/hmg_services/widgets/weather_widget.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart'; import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart';
import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart';
import 'package:hmg_patient_app_new/presentation/servicesPriceList/services_price_list_page.dart'; import 'package:hmg_patient_app_new/presentation/servicesPriceList/services_price_list_page.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart';
@ -49,8 +43,6 @@ import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../../core/dependencies.dart' show getIt; import '../../core/dependencies.dart' show getIt;
import '../../features/qr_parking/qr_parking_view_model.dart';
import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart';
class ServicesPage extends StatefulWidget { class ServicesPage extends StatefulWidget {
bool showBackIcon; bool showBackIcon;
@ -69,7 +61,14 @@ class _ServicesPageState extends State<ServicesPage> {
late WeatherMonitorViewModel weatherVM; late WeatherMonitorViewModel weatherVM;
late final List<HmgServicesComponentModel> hmgServices = [ late final List<HmgServicesComponentModel> hmgServices = [
HmgServicesComponentModel(11, LocaleKeys.emergencyServices.tr(), "", AppAssets.emergency_services_icon, bgColor: AppColors.primaryRedColor, true, route: null, onTap: () async { HmgServicesComponentModel(
11,
LocaleKeys.emergencyServices.tr(),
"",
AppAssets.emergency_services_icon,
bgColor: AppColors.primaryRedColor,
true,
route: null, onTap: () async {
// if (getIt.get<AppState>().isAuthenticated) { // if (getIt.get<AppState>().isAuthenticated) {
// getIt.get<EmergencyServicesViewModel>().flushData(); // getIt.get<EmergencyServicesViewModel>().flushData();
// getIt.get<EmergencyServicesViewModel>().getTransportationOrders( // getIt.get<EmergencyServicesViewModel>().getTransportationOrders(
@ -88,11 +87,19 @@ class _ServicesPageState extends State<ServicesPage> {
// await getIt.get<AuthenticationViewModel>().onLoginPressed(); // await getIt.get<AuthenticationViewModel>().onLoginPressed();
// } // }
}), }),
HmgServicesComponentModel(11, LocaleKeys.bookAppointmentService.tr(), "", AppAssets.appointment_calendar_icon, bgColor: AppColors.bookAppointment, true, route: null, onTap: () { HmgServicesComponentModel(
11,
LocaleKeys.bookAppointmentService.tr(),
"",
AppAssets.appointment_calendar_icon,
bgColor: AppColors.bookAppointment,
true,
route: null, onTap: () {
getIt.get<BookAppointmentsViewModel>().onTabChanged(0); getIt.get<BookAppointmentsViewModel>().onTabChanged(0);
Navigator.of(getIt<NavigationService>().navigatorKey.currentContext!).push(CustomPageRoute(page: BookAppointmentPage())); Navigator.of(getIt<NavigationService>().navigatorKey.currentContext!).push(CustomPageRoute(page: BookAppointmentPage()));
}), }),
HmgServicesComponentModel(5, LocaleKeys.completeCheckup.tr(), "", AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async { HmgServicesComponentModel(
5, LocaleKeys.completeCheckup.tr(), "", AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async {
if (getIt.get<AppState>().isAuthenticated) { if (getIt.get<AppState>().isAuthenticated) {
getIt.get<NavigationService>().pushPageRoute(AppRoutes.comprehensiveCheckupPage); getIt.get<NavigationService>().pushPageRoute(AppRoutes.comprehensiveCheckupPage);
} else { } else {
@ -152,7 +159,8 @@ class _ServicesPageState extends State<ServicesPage> {
// ); // );
// }, // },
// ), // ),
HmgServicesComponentModel(11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async { HmgServicesComponentModel(
11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async {
if (getIt.get<AppState>().isAuthenticated) { if (getIt.get<AppState>().isAuthenticated) {
getIt.get<NavigationService>().pushPageRoute(AppRoutes.eReferralPage); getIt.get<NavigationService>().pushPageRoute(AppRoutes.eReferralPage);
} else { } else {
@ -403,14 +411,13 @@ class _ServicesPageState extends State<ServicesPage> {
}, },
), ),
HmgServicesComponentModel( HmgServicesComponentModel(
103, 103,
LocaleKeys.watchUsOnYoutube.tr(), LocaleKeys.watchUsOnYoutube.tr(),
"", "",
AppAssets.youtube, AppAssets.youtube,
bgColor: AppColors.whiteColor, bgColor: AppColors.whiteColor,
true, true,
onTap:()=> launchUrl(Uri.parse("https://www.youtube.com/c/DrsulaimanAlhabibHospitals")) onTap: () => launchUrl(Uri.parse("https://www.youtube.com/c/DrsulaimanAlhabibHospitals"))),
),
HmgServicesComponentModel( HmgServicesComponentModel(
104, 104,
LocaleKeys.connectOnLinkedin.tr(), LocaleKeys.connectOnLinkedin.tr(),
@ -418,9 +425,8 @@ class _ServicesPageState extends State<ServicesPage> {
AppAssets.linkedin, AppAssets.linkedin,
bgColor: AppColors.whiteColor, bgColor: AppColors.whiteColor,
true, true,
onTap:()=> launchUrl(Uri.parse("https://www.linkedin.com/company/drsulaiman-alhabib-medical-group")), onTap: () => launchUrl(Uri.parse("https://www.linkedin.com/company/drsulaiman-alhabib-medical-group")),
), ),
]; ];
@override @override
@ -431,8 +437,6 @@ class _ServicesPageState extends State<ServicesPage> {
weatherVM.initiateFetchWeather(); weatherVM.initiateFetchWeather();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bloodDonationViewModel = Provider.of<BloodDonationViewModel>(context); bloodDonationViewModel = Provider.of<BloodDonationViewModel>(context);
@ -451,92 +455,111 @@ class _ServicesPageState extends State<ServicesPage> {
children: [ children: [
const WeatherWidget(), const WeatherWidget(),
SizedBox(height: 16.h), SizedBox(height: 16.h),
LocaleKeys.medicalAndCareServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), LocaleKeys.medicalAndCareServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0),
SizedBox(height: 16.h), SizedBox(height: 16.h),
GridView.builder( GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: (isFoldable || isTablet) ? 6 : 4, // 4 icons per row crossAxisCount: (isFoldable || isTablet) ? 5 : 4, // 4 icons per row
crossAxisSpacing: 21.w, ),
mainAxisSpacing: 18.h, physics: NeverScrollableScrollPhysics(),
childAspectRatio: 80 / 94),
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: hmgServices.length,
padding: EdgeInsets.zero,
itemBuilder: (BuildContext context, int index) {
return ServiceGridViewItem(hmgServices[index], index, false, isHealthToolIcon: false);
},
).paddingSymmetrical(24.w, 0),
SizedBox(height: 24.h),
LocaleKeys.hmgServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0),
SizedBox(height: 16.h),
SizedBox(
height: 350.h,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: LandingPageData.getServiceCardsList.length,
shrinkWrap: true, shrinkWrap: true,
padding: EdgeInsets.symmetric(horizontal: 24.w), itemCount: hmgServices.length,
itemBuilder: (context, index) { padding: EdgeInsets.zero,
return AnimationConfiguration.staggeredList( itemBuilder: (BuildContext context, int index) {
position: index, return ServiceGridViewItem(hmgServices[index], index, false, isHealthToolIcon: false);
duration: const Duration(milliseconds: 1000), },
child: SlideAnimation( ).paddingSymmetrical(24.w, 0),
horizontalOffset: 100.0, SizedBox(height: 24.h),
child: FadeInAnimation( LocaleKeys.hmgServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0),
child: LargeServiceCard( SizedBox(height: 16.h),
serviceCardData: LandingPageData.getServiceCardsList[index], ConstrainedBox(
image: LandingPageData.getServiceCardsList[index].icon, constraints: BoxConstraints(
title: LandingPageData.getServiceCardsList[index].title, minHeight: 320.h,
subtitle: LandingPageData.getServiceCardsList[index].subtitle, maxHeight: isFoldable ? 400.h : (isTablet ? 360.h : 340.h),
icon: LandingPageData.getServiceCardsList[index].largeCardIcon, ),
isPNG: LandingPageData.getServiceCardsList[index].isPNG, child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: LandingPageData.getServiceCardsList.length,
shrinkWrap: true,
padding: EdgeInsets.symmetric(horizontal: 24.w),
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 1000),
child: SlideAnimation(
horizontalOffset: 100.0,
child: FadeInAnimation(
child: LargeServiceCard(
serviceCardData: LandingPageData.getServiceCardsList[index],
image: LandingPageData.getServiceCardsList[index].icon,
title: LandingPageData.getServiceCardsList[index].title,
subtitle: LandingPageData.getServiceCardsList[index].subtitle,
icon: LandingPageData.getServiceCardsList[index].largeCardIcon,
isPNG: LandingPageData.getServiceCardsList[index].isPNG,
),
), ),
), ),
), );
); },
}, separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.w),
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.w), ),
), ),
), SizedBox(height: 24.h),
SizedBox(height: 24.h), getIt.get<AppState>().isAuthenticated
getIt.get<AppState>().isAuthenticated ? Column(
? Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ LocaleKeys.personalServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0),
LocaleKeys.personalServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h),
SizedBox(height: 16.h), Row(
Row( children: [
children: [ Expanded(
Expanded( child: Container(
child: Container( height: 183.h,
height: 183.h, width: 183.h,
width: 183.h, padding: EdgeInsets.all(16.w),
padding: EdgeInsets.all(16.w), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor,
color: AppColors.whiteColor, borderRadius: 20.r,
borderRadius: 20.r, hasShadow: false,
hasShadow: false, ),
), child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ Row(
Row( spacing: 8.w,
spacing: 8.w, crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, children: [
children: [ Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h, applyThemeColor: false),
Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h, applyThemeColor: false), LocaleKeys.habibWallet.tr().toText14(isBold: true, maxlines: 2).expanded,
LocaleKeys.habibWallet.tr().toText14(isBold: true, maxlines: 2).expanded, Utils.buildSvgWithAssets(
Utils.buildSvgWithAssets(icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward),
], ],
), ),
Spacer(), Spacer(),
getIt.get<AppState>().isAuthenticated getIt.get<AppState>().isAuthenticated
? Consumer<HabibWalletViewModel>(builder: (context, habibWalletVM, child) { ? Consumer<HabibWalletViewModel>(builder: (context, habibWalletVM, child) {
return Utils.getPaymentAmountWithSymbol2(num.parse(NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount)), return Row(
isExpanded: false, letterSpacing: -1) children: [
.toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); Utils.buildSvgWithAssets(
}) icon: AppAssets.saudi_riyal_icon,
iconColor: AppColors.inputLabelTextColor,
width: 24.h,
height: 24.h,
fit: BoxFit.contain,
),
SizedBox(width: 8.h),
NumberFormat.decimalPattern()
.format(habibWalletVM.habibWalletAmount)
.toString()
.toText28(isBold: true, isEnglishOnly: true)
.toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h),
],
);
// Utils.getPaymentAmountWithSymbol2(num.parse(NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount)),
// isExpanded: false, letterSpacing: -1)
// .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h);
})
: LocaleKeys.loginToViewWalletBalance.tr().toText12(isBold: true, maxLine: 2), : LocaleKeys.loginToViewWalletBalance.tr().toText12(isBold: true, maxLine: 2),
Spacer(), Spacer(),
getIt.get<AppState>().isAuthenticated getIt.get<AppState>().isAuthenticated
@ -586,321 +609,254 @@ class _ServicesPageState extends State<ServicesPage> {
spacing: 8.w, spacing: 8.w,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 40.w, height: 40.h, applyThemeColor: false), Utils.buildSvgWithAssets(
LocaleKeys.familyTitle.tr().toText16(isBold: true, maxlines: 2).expanded, icon: AppAssets.services_medical_file_icon, width: 40.w, height: 40.h, applyThemeColor: false),
Utils.buildSvgWithAssets(icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), LocaleKeys.familyTitle.tr().toText16(isBold: true, maxlines: 2).expanded,
], Utils.buildSvgWithAssets(
), icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward),
Spacer(), ],
getIt.get<AppState>().isAuthenticated
? Wrap(
spacing: -12.h,
// runSpacing: 0.h,
children: [
Utils.buildImgWithAssets(
icon: AppAssets.babyGirlImg,
height: 32.h,
width: 32.w,
border: 1,
fit: BoxFit.contain,
borderRadius: 50.r,
),
Utils.buildImgWithAssets(
icon: AppAssets.femaleImg,
height: 32.h,
width: 32.w,
border: 1,
borderRadius: 50.r,
fit: BoxFit.contain,
),
Utils.buildImgWithAssets(
icon: AppAssets.maleImg,
height: 32.h,
width: 32.w,
border: 1,
borderRadius: 50.r,
fit: BoxFit.contain,
),
],
)
: LocaleKeys.loginToViewMedicalFile.tr().toText12(isBold: true, maxLine: 2),
Spacer(),
getIt.get<AppState>().isAuthenticated
? CustomButton(
height: 40.h,
icon: AppAssets.add_icon,
iconSize: 24.h,
iconColor: AppColors.primaryRedColor,
textColor: AppColors.primaryRedColor,
text: getIt.get<AppState>().isArabic() ? LocaleKeys.add.tr() :LocaleKeys.addMember.tr(),
borderWidth: 0.w,
isBold: true,
borderColor: Colors.transparent,
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08),
padding: EdgeInsets.all(8.w),
fontSize: 14.f,
onPressed: () {
DialogService dialogService = getIt.get<DialogService>();
medicalFileViewModel.clearAuthValues();
dialogService.showAddFamilyFileSheet(
label: LocaleKeys.addFamilyMember.tr(),
message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(),
onVerificationPress: () {
medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms);
});
},
)
: SizedBox.shrink(),
],
).onPress(() async {
if (getIt.get<AppState>().isAuthenticated) {
// Navigator.of(context).push(
// CustomPageRoute(
// page: MedicalFilePage(),
// ),
// );
Navigator.of(context).push(
CustomPageRoute(
direction: AxisDirection.down,
page: FamilyMedicalScreen(),
), ),
); Spacer(),
} else { getIt.get<AppState>().isAuthenticated
await getIt.get<AuthenticationViewModel>().onLoginPressed(); ? Wrap(
} spacing: -12.h,
}), // runSpacing: 0.h,
children: [
Utils.buildImgWithAssets(
icon: AppAssets.babyGirlImg,
height: 32.h,
width: 32.w,
border: 1,
fit: BoxFit.contain,
borderRadius: 50.r,
),
Utils.buildImgWithAssets(
icon: AppAssets.femaleImg,
height: 32.h,
width: 32.w,
border: 1,
borderRadius: 50.r,
fit: BoxFit.contain,
),
Utils.buildImgWithAssets(
icon: AppAssets.maleImg,
height: 32.h,
width: 32.w,
border: 1,
borderRadius: 50.r,
fit: BoxFit.contain,
),
],
)
: LocaleKeys.loginToViewMedicalFile.tr().toText12(isBold: true, maxLine: 2),
Spacer(),
getIt.get<AppState>().isAuthenticated
? CustomButton(
height: 40.h,
icon: AppAssets.add_icon,
iconSize: 24.h,
iconColor: AppColors.primaryRedColor,
textColor: AppColors.primaryRedColor,
text: getIt.get<AppState>().isArabic() ? LocaleKeys.add.tr() : LocaleKeys.addMember.tr(),
borderWidth: 0.w,
isBold: true,
borderColor: Colors.transparent,
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08),
padding: EdgeInsets.all(8.w),
fontSize: 14.f,
onPressed: () {
DialogService dialogService = getIt.get<DialogService>();
medicalFileViewModel.clearAuthValues();
dialogService.showAddFamilyFileSheet(
label: LocaleKeys.addFamilyMember.tr(),
message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(),
onVerificationPress: () {
medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms);
});
},
)
: SizedBox.shrink(),
],
).onPress(() async {
if (getIt.get<AppState>().isAuthenticated) {
// Navigator.of(context).push(
// CustomPageRoute(
// page: MedicalFilePage(),
// ),
// );
Navigator.of(context).push(
CustomPageRoute(
direction: AxisDirection.down,
page: FamilyMedicalScreen(),
),
);
} else {
await getIt.get<AuthenticationViewModel>().onLoginPressed();
}
}),
),
),
],
).paddingSymmetrical(24.w, 0),
],
)
: SizedBox(),
SizedBox(height: 24.h),
LocaleKeys.healthTools.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0),
SizedBox(height: 16.h),
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: (isFoldable || isTablet) ? 5 : 4, // 4 icons per row
mainAxisSpacing: 18.h,
),
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: hmgHealthToolServices.length,
padding: EdgeInsets.zero,
itemBuilder: (BuildContext context, int index) {
return ServiceGridViewItem(
hmgHealthToolServices[index],
index,
false,
isHealthToolIcon: true,
);
},
).paddingSymmetrical(24.w, 0),
SizedBox(height: 24.h),
LocaleKeys.supportServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0),
SizedBox(height: 16.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.h,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Row(
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.latest_news_icon,
width: 32.h,
height: 32.h,
fit: BoxFit.contain,
),
SizedBox(width: 8.w),
LocaleKeys.latestNews.tr().toText14(isBold: true)
],
),
),
).onPress(() {
Utils.openWebView(
url: 'https://x.com/HMG',
);
}),
),
SizedBox(width: 16.w),
Expanded(
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.h,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Row(
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.hmg_contact_icon,
width: 32.h,
height: 32.h,
fit: BoxFit.contain,
),
SizedBox(width: 8.w),
Expanded(child: LocaleKeys.hmgContact.tr().toText14(isBold: true))
],
), ),
), ),
], ).onPress(() {
).paddingSymmetrical(24.w, 0), showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.contactUs.tr(),
child: ContactUs(),
callBackFunc: () {},
isFullScreen: false,
);
}),
)
], ],
) ),
: SizedBox(), SizedBox(height: 24.h),
SizedBox(height: 24.h), LocaleKeys.hmgPolicies.tr().toText18(weight: FontWeight.bold),
LocaleKeys.healthTools.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h),
SizedBox(height: 16.h), Row(
GridView.builder( children: [
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( Expanded(
crossAxisCount: (isFoldable || isTablet) ? 6 : 4, // 4 icons per row child: Container(
crossAxisSpacing: 21.w, decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
mainAxisSpacing: 18.h, color: AppColors.whiteColor,
childAspectRatio: 80.w / 94.h, borderRadius: 12.h,
), hasShadow: false,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: hmgHealthToolServices.length,
padding: EdgeInsets.zero,
itemBuilder: (BuildContext context, int index) {
return ServiceGridViewItem(
hmgHealthToolServices[index],
index,
false,
isHealthToolIcon: true,
);
},
).paddingSymmetrical(24.w, 0),
SizedBox(height: 24.h),
LocaleKeys.supportServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0),
SizedBox(height: 16.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Row(
// children: [
// // Expanded(
// // child: Container(
// // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// // color: AppColors.whiteColor,
// // borderRadius: 12.h,
// // hasShadow: false,
// // ),
// // child: Padding(
// // padding: EdgeInsets.all(16.h),
// // child: Row(
// // children: [
// // Utils.buildSvgWithAssets(
// // icon: AppAssets.virtual_tour_icon,
// // width: 32.w,
// // height: 32.h,
// // fit: BoxFit.contain,
// // ),
// // SizedBox(width: 8.w),
// // LocaleKeys.virtualTour.tr().toText14(isBold: true)
// // ],
// // ),
// // ),
// // ).onPress(() {
// // Utils.openWebView(
// // url: 'https://hmgwebservices.com/vt_mobile/html/index.html',
// // );
// // }),
// // ),
// SizedBox(width: 16.w),
// Expanded(
// child: Container(
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// color: AppColors.whiteColor,
// borderRadius: 12.h,
// hasShadow: false,
// ),
// child: Padding(
// padding: EdgeInsets.all(16.h),
// child: Row(
// children: [
// Utils.buildSvgWithAssets(
// icon: AppAssets.car_parking_icon,
// width: 32.w,
// height: 32.h,
// fit: BoxFit.contain,
// ),
// SizedBox(width: 8.w),
// LocaleKeys.carParking.tr().toText14(isBold: true)
// ],
// ).onPress(() {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (_) => ChangeNotifierProvider(
// create: (_) => getIt<QrParkingViewModel>(),
// child: const ParkingPage(),
// ),
// ),
// );
// }),
// ),
// ),
// ),
// ],
// ),
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.h,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Row(
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.latest_news_icon,
width: 32.w,
height: 32.h,
fit: BoxFit.contain,
),
SizedBox(width: 8.w),
LocaleKeys.latestNews.tr().toText14(isBold: true)
],
), ),
), child: Padding(
).onPress(() { padding: EdgeInsets.all(16.h),
Utils.openWebView( child: Row(
url: 'https://x.com/HMG', children: [
); Utils.buildSvgWithAssets(
}), icon: AppAssets.privacy_terms, width: 32.w, height: 32.h, fit: BoxFit.contain, iconColor: AppColors.blackColor),
), SizedBox(width: 8.w),
SizedBox(width: 16.w), Expanded(child: LocaleKeys.termsConditoins.tr().toText14(isBold: true))
Expanded( ],
child: Container( ),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.h,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Row(
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.hmg_contact_icon,
width: 32.w,
height: 32.h,
fit: BoxFit.contain,
),
SizedBox(width: 8.w),
Expanded(child: LocaleKeys.hmgContact.tr().toText14(isBold: true))
],
), ),
), ).onPress(() {
).onPress(() { Utils.openWebView(
showCommonBottomSheetWithoutHeight( url: 'https://hmg.com/en/Pages/Terms.aspx',
context, );
title: LocaleKeys.contactUs.tr(), }),
child: ContactUs(), ),
callBackFunc: () {}, SizedBox(width: 16.w),
isFullScreen: false, Expanded(
); child: Container(
}), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
) color: AppColors.whiteColor,
], borderRadius: 12.h,
), hasShadow: false,
SizedBox(height: 24.h),
LocaleKeys.hmgPolicies.tr().toText18(weight: FontWeight.bold),
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.h,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.privacy_terms, width: 32.w, height: 32.h, fit: BoxFit.contain, iconColor: AppColors.blackColor),
SizedBox(width: 8.w),
Expanded(child: LocaleKeys.termsConditoins.tr().toText14(isBold: true))
],
), ),
), child: Padding(
).onPress(() { padding: EdgeInsets.all(16.h),
Utils.openWebView( child: Row(
url: 'https://hmg.com/en/Pages/Terms.aspx', children: [
); Utils.buildSvgWithAssets(
}), icon: AppAssets.privacy_terms, width: 32.w, height: 32.h, fit: BoxFit.contain, iconColor: AppColors.blackColor),
), SizedBox(width: 8.w),
SizedBox(width: 16.w), Expanded(child: LocaleKeys.privacyPolicy.tr().toText14(isBold: true))
Expanded( ],
child: Container( ),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.h,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.privacy_terms, width: 32.w, height: 32.h, fit: BoxFit.contain, iconColor: AppColors.blackColor),
SizedBox(width: 8.w),
Expanded(child: LocaleKeys.privacyPolicy.tr().toText14(isBold: true))
],
), ),
), ).onPress(() {
).onPress(() { Utils.openWebView(
Utils.openWebView( url: 'https://hmg.com/en/Pages/Privacy.aspx',
url: 'https://hmg.com/en/Pages/Privacy.aspx', );
); }),
}), )
) ],
], )
) ],
], ).paddingSymmetrical(24.w, 0),
).paddingSymmetrical(24.w, 0),
SizedBox(height: 16.h), SizedBox(height: 16.h),
GridView.builder( GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: (isFoldable || isTablet) ? 6 : 4, // 4 icons per row crossAxisCount: (isFoldable || isTablet) ? 5 : 4, // 4 icons per row
crossAxisSpacing: 21.w,
mainAxisSpacing: 18.h, mainAxisSpacing: 18.h,
childAspectRatio: 80.w / 94.h,
), ),
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
shrinkWrap: true, shrinkWrap: true,
@ -915,11 +871,11 @@ class _ServicesPageState extends State<ServicesPage> {
); );
}, },
).paddingSymmetrical(24.w, 0), ).paddingSymmetrical(24.w, 0),
SizedBox(height: 24.h), SizedBox(height: 24.h),
], ],
),
), ),
), ),
),
); );
} }
} }

File diff suppressed because it is too large Load Diff

@ -47,7 +47,9 @@ class HabibWalletCard extends StatelessWidget {
child: Stack(children: [ child: Stack(children: [
Positioned( Positioned(
right: 0, right: 0,
child: ClipRRect(borderRadius: BorderRadius.circular(24.0), child: Utils.buildSvgWithAssets(icon: AppAssets.habib_background_icon, width: 150.h, height: 150.h, applyThemeColor: false)), child: ClipRRect(
borderRadius: BorderRadius.circular(24.0),
child: Utils.buildSvgWithAssets(icon: AppAssets.habib_background_icon, width: 150.h, height: 150.h, applyThemeColor: false)),
), ),
Padding( Padding(
padding: EdgeInsets.all(16.h), padding: EdgeInsets.all(16.h),
@ -57,25 +59,25 @@ class HabibWalletCard extends StatelessWidget {
// Row( // Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [ // children: [
LocaleKeys.habibWallet.tr(context: context).toText16(isBold: true, letterSpacing: -0.2), LocaleKeys.habibWallet.tr(context: context).toText16(isBold: true, letterSpacing: -0.2),
// Container( // Container(
// height: 40.h, // height: 40.h,
// width: 40.h, // width: 40.h,
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration( // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// color: AppColors.textColor, // color: AppColors.textColor,
// borderRadius: 8.h, // borderRadius: 8.h,
// ), // ),
// child: Padding( // child: Padding(
// padding: EdgeInsets.all(8.h), // padding: EdgeInsets.all(8.h),
// child: Utils.buildSvgWithAssets( // child: Utils.buildSvgWithAssets(
// icon: AppAssets.show_icon, // icon: AppAssets.show_icon,
// width: 12.h, // width: 12.h,
// height: 12.h, // height: 12.h,
// fit: BoxFit.contain, // fit: BoxFit.contain,
// ), // ),
// ), // ),
// ), // ),
// ], // ],
// ), // ),
SizedBox(height: 4.h), SizedBox(height: 4.h),
Column( Column(

@ -43,83 +43,87 @@ class LargeServiceCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
height: 350.h,
width: 230.w, width: 230.w,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.transparent, borderRadius: 24.r), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
child: Stack( color: AppColors.whiteColor,
borderRadius: 24.r,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(24.r), borderRadius: BorderRadius.only(
topLeft: Radius.circular(24.r),
topRight: Radius.circular(24.r),
),
child: Image.asset( child: Image.asset(
serviceCardData.largeCardIcon, serviceCardData.largeCardIcon,
fit: BoxFit.cover, fit: BoxFit.cover,
width: double.infinity,
height: isFoldable ? 190.h : (isTablet ? 200.h : 180.h),
), ),
), ),
Positioned( Container(
bottom: 0.0, // Positions the child 0 logical pixels from the bottom padding: EdgeInsets.all(16.w),
left: 0.0, child: Column(
right: 0.0, mainAxisSize: MainAxisSize.min,
child: Container( children: [
height: 180.h, Row(
padding: EdgeInsets.only(bottom: 16.h, top: 16.h), crossAxisAlignment: CrossAxisAlignment.start,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( children: [
color: AppColors.whiteColor, isPNG
customBorder: BorderRadius.only( ? Image.asset(serviceCardData.icon, width: 35.h, height: 35.h)
bottomLeft: Radius.circular(24.r), : Container(
bottomRight: Radius.circular(24.r), height: 48.h,
), width: 48.h,
), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
child: Column( color: serviceCardData.backgroundColor!,
children: [ borderRadius: 12.r,
Row( hasShadow: false,
crossAxisAlignment: CrossAxisAlignment.start, ),
children: [ child: Padding(
isPNG ? Image.asset(serviceCardData.icon, width: 35.h, height: 35.h) : Container( padding: EdgeInsets.all(12.h),
height: 48.h, child: Utils.buildSvgWithAssets(
width: 48.h, icon: serviceCardData.icon,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( iconColor: serviceCardData.iconColor,
color: serviceCardData.backgroundColor!, fit: BoxFit.contain,
borderRadius: 12.r, applyThemeColor: false,
hasShadow: false, ),
), ),
child: Padding(
padding: EdgeInsets.all(12.h),
child: Utils.buildSvgWithAssets(
icon: serviceCardData.icon,
iconColor: serviceCardData.iconColor,
fit: BoxFit.contain,
applyThemeColor: false,
), ),
), SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
serviceCardData.title.tr(context: context).toText14(
isBold: true,
color: AppColors.textColor,
maxlines: 1,
textOverflow: TextOverflow.ellipsis,
),
serviceCardData.subtitle.tr(context: context).toText12(isBold: true, color: AppColors.textColorLight, maxLine: 2),
],
), ),
SizedBox(width: 12.w), ),
Expanded( ],
child: Column( ),
crossAxisAlignment: CrossAxisAlignment.start, SizedBox(height: 24.h),
children: [ CustomButton(
serviceCardData.title.tr(context: context).toText14(isBold: true, color: AppColors.textColor), text: serviceCardData.isBold ? LocaleKeys.visitPharmacyOnline.tr(context: context) : LocaleKeys.bookNow.tr(context: context),
serviceCardData.subtitle.tr(context: context).toText12(isBold: true, color: AppColors.textColorLight, maxLine: 2), onPressed: () {
], handleOnTap();
), },
), padding: EdgeInsets.zero,
], backgroundColor: serviceCardData.isBold ? AppColors.successLightColor.withValues(alpha: 0.2) : AppColors.bgRedLightColor,
).paddingSymmetrical(8.w, 0.h).expanded, borderColor: serviceCardData.isBold ? AppColors.successLightColor.withValues(alpha: 0.01) : AppColors.bgRedLightColor,
CustomButton( textColor: serviceCardData.isBold ? AppColors.successColor : AppColors.primaryRedColor,
text: serviceCardData.isBold ? LocaleKeys.visitPharmacyOnline.tr(context: context) : LocaleKeys.bookNow.tr(context: context), fontSize: 14.f,
onPressed: () { fontWeight: FontWeight.w600,
handleOnTap(); borderRadius: 10.r,
}, height: 40.h,
padding: EdgeInsets.zero, ),
backgroundColor: serviceCardData.isBold ? AppColors.successLightColor.withValues(alpha: 0.2) : AppColors.bgRedLightColor, ],
borderColor: serviceCardData.isBold ? AppColors.successLightColor.withValues(alpha: 0.01) : AppColors.bgRedLightColor,
textColor: serviceCardData.isBold ? AppColors.successColor : AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 10.r,
height: 40.h,
).paddingSymmetrical(16.w, 0.h),
],
),
), ),
), ),
], ],
@ -229,27 +233,25 @@ class FadedLargeServiceCard extends StatelessWidget {
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
isPNG ? Image.asset(serviceCardData.icon, width: 32.h, height: 32.h).circle(100.h) : Container( isPNG
height: 32.h, ? Image.asset(serviceCardData.icon, width: 32.h, height: 32.h).circle(100.h)
width: 32.h, : Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( height: 32.h,
color: serviceCardData.backgroundColor!, width: 32.h,
borderRadius: 30.r, decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
hasShadow: false, color: serviceCardData.backgroundColor!,
), borderRadius: 30.r,
child: Padding( hasShadow: false,
padding: EdgeInsets.all(8.h), ),
child: Transform.flip( child: Padding(
flipX: getIt.get<AppState>().isArabic(), padding: EdgeInsets.all(8.h),
child: Utils.buildSvgWithAssets( child: Transform.flip(
icon: serviceCardData.icon, flipX: getIt.get<AppState>().isArabic(),
iconColor: serviceCardData.iconColor, child: Utils.buildSvgWithAssets(
fit: BoxFit.contain, icon: serviceCardData.icon, iconColor: serviceCardData.iconColor, fit: BoxFit.contain, applyThemeColor: false),
applyThemeColor: false ),
),
), ),
),
),
),
SizedBox(width: 12.w), SizedBox(width: 12.w),
serviceCardData.title.tr(context: context).toText18(isBold: true, color: AppColors.textColor).expanded, serviceCardData.title.tr(context: context).toText18(isBold: true, color: AppColors.textColor).expanded,
], ],

@ -108,10 +108,10 @@ class InsuranceApprovalDetailsPage extends StatelessWidget {
richText: Row( richText: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
"${LocaleKeys.receiptOn.tr(context: context)} ".toText10(), "${LocaleKeys.receiptOn.tr(context: context)} ".toText10(isBold: true),
Directionality( Directionality(
textDirection: ui.TextDirection.ltr, textDirection: ui.TextDirection.ltr,
child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceApprovalResponseModel.receiptOn), false).toText10(isEnglishOnly: true)), child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceApprovalResponseModel.receiptOn), false).toText10(isBold: true)),
], ],
), ),
isEnglishOnly: true, isEnglishOnly: true,
@ -121,15 +121,14 @@ class InsuranceApprovalDetailsPage extends StatelessWidget {
richText: Row( richText: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
"${LocaleKeys.expiryOn.tr(context: context)} ".toText10(), "${LocaleKeys.expiryOn.tr(context: context)} ".toText10(isBold: true),
Directionality( Directionality(
textDirection: ui.TextDirection.ltr, textDirection: ui.TextDirection.ltr,
child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceApprovalResponseModel.expiryDate), false).toText10(isEnglishOnly: true)), child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceApprovalResponseModel.expiryDate), false).toText10(isBold: true)),
], ],
), ),
isEnglishOnly: true, isEnglishOnly: true,
), ),
], ],
), ),
], ],

@ -162,10 +162,11 @@ class PatientInsuranceCard extends StatelessWidget {
AppCustomChipWidget( AppCustomChipWidget(
icon: AppAssets.doctor_calendar_icon, icon: AppAssets.doctor_calendar_icon,
// labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", // labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}",
richText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}".toText10(isEnglishOnly: true), richText:
"${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}".toText10(isBold: true),
labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h),
), ),
AppCustomChipWidget(richText: LocaleKeys.patientCardID.tr(namedArgs: {'id': insuranceCardDetailsModel.patientCardID ?? ''}, context: context).toText10(isEnglishOnly: true)), AppCustomChipWidget(richText: LocaleKeys.patientCardID.tr(namedArgs: {'id': insuranceCardDetailsModel.patientCardID ?? ''}, context: context).toText10(isBold: true)),
], ],
), ),
SizedBox(height: 10.h), SizedBox(height: 10.h),

@ -20,8 +20,9 @@ class LabResultList extends StatelessWidget {
selector: (_, model) => model.mainLabResultsByHospitals, selector: (_, model) => model.mainLabResultsByHospitals,
builder: (__, list, ___) { builder: (__, list, ___) {
if (list.isEmpty && context.read<LabViewModel>().labSpecialResult.isEmpty) { if (list.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();
} else { } else {
return ListView.builder( return ListView.builder(
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),

File diff suppressed because it is too large Load Diff

@ -1,4 +1,6 @@
import 'dart:async'; import 'dart:async';
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/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -18,10 +20,8 @@ import 'package:hmg_patient_app_new/presentation/appointments/appointment_paymen
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/routes/custom_page_route.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/routes/custom_page_route.dart';
import 'dart:ui' as ui;
class MedicalFileAppointmentCard extends StatefulWidget { class MedicalFileAppointmentCard extends StatefulWidget {
final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel;
@ -138,174 +138,231 @@ class _MedicalFileAppointmentCardState extends State<MedicalFileAppointmentCard>
richText: Directionality( richText: Directionality(
textDirection: ui.TextDirection.ltr, textDirection: ui.TextDirection.ltr,
child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), false) child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), false)
.toText12(color: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, isBold: true, isEnglishOnly: true) .toText12(
color: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor,
isBold: true,
isEnglishOnly: true)
.paddingSymmetrical(8.w, 0), .paddingSymmetrical(8.w, 0),
), ),
icon: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppAssets.appointment_calendar_icon : AppAssets.alarm_clock_icon, icon: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)
? AppAssets.appointment_calendar_icon
: AppAssets.alarm_clock_icon,
iconColor: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, iconColor: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor,
iconSize: 16.w, iconSize: 16.w,
backgroundColor: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.greyColor : AppColors.secondaryLightRedColor, backgroundColor:
AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.greyColor : AppColors.secondaryLightRedColor,
textColor: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, textColor: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor,
padding: EdgeInsets.only(top: 12.h, bottom: 12.h, left: 8.w, right: 8.w), padding: EdgeInsets.only(top: 12.h, left: 8.w, right: 8.w, bottom: 8.h),
).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading),
SizedBox(height: 16.h), SizedBox(height: 16.h),
Container( IntrinsicWidth(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false), child: Container(
width: 200.w, decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
children: [ children: [
Image.network( Image.network(
widget.patientAppointmentHistoryResponseModel.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", widget.patientAppointmentHistoryResponseModel.doctorImageURL ??
width: 25.w, "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
height: 27.h, width: 30.h,
fit: BoxFit.fill, height: 30.h,
).circle(100).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), fit: BoxFit.fill,
SizedBox(width: 8.w), ).circle(100.r).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading),
Expanded( SizedBox(width: 8.w),
child: Column( Expanded(
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText14(isBold: true, maxlines: 1, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "")).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), children: [
(widget.patientAppointmentHistoryResponseModel.clinicName ?? "") (widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "")
.toText12(maxLine: 1, isBold: true, color: AppColors.greyTextColor) .toText14(
.toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), isBold: true,
], maxlines: 1,
isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? ""))
.toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading),
(widget.patientAppointmentHistoryResponseModel.clinicName ?? "")
.toText12(maxLine: 1, textOverflow: TextOverflow.ellipsis, isBold: true, color: AppColors.greyTextColor)
.toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading),
],
),
), ),
), ],
], ),
), SizedBox(height: 8.h),
SizedBox(height: 12.h), _buildAppointmentActionButton(context, appState),
// Check if doctor is active - if not, show only View Details button ],
(widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) ).paddingAll(16.w),
? // Doctor is active - check rebooking logic ),
(AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) &&
widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false)
? // Show only the button without arrow when rebooking not allowed
widget.myAppointmentsViewModel.isMyAppointmentsLoading
? Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r)
: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)
? getArrivedAppointmentButton(context).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading)
: CustomButton(
text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction),
onPressed: () {
handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction, context);
},
backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15),
borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01),
textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction),
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction),
iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction),
iconSize: 14.h,
).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading)
: // Normal flow - show button with arrow
Row(
children: [
widget.myAppointmentsViewModel.isMyAppointmentsLoading
? Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r)
: Expanded(
flex: 7,
child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)
? getArrivedAppointmentButton(context).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading)
: CustomButton(
text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction),
onPressed: () {
handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction, context);
},
backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15),
borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01),
textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction),
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction),
iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction),
iconSize: 14.h,
).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading),
),
SizedBox(width: 8.w),
((((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ||
(widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) ||
!Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID ?? 0))) &&
AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel))
? SizedBox.shrink()
: Expanded(
flex: 2,
child: Container(
height: 40.h,
width: 40.w,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.textColor,
borderRadius: 10.r,
),
child: Padding(
padding: EdgeInsets.all(10.w),
child: Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
iconColor: AppColors.whiteColor,
icon: AppAssets.forward_arrow_icon_small,
width: 40.w,
height: 40.h,
fit: BoxFit.contain,
),
),
),
).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading).onPress(() {
Navigator.of(context)
.push(
CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
),
)
.then((val) {
// widget.myAppointmentsViewModel.initAppointmentsViewModel();
// widget.myAppointmentsViewModel.getPatientAppointments(true, false);
});
}),
),
],
)
: // Doctor is not active - show only View Details button
CustomButton(
text: LocaleKeys.viewDetails.tr(context: context),
onPressed: () {
Navigator.of(context)
.push(
CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
),
)
.then((_) {
widget.myAppointmentsViewModel.initAppointmentsViewModel();
widget.myAppointmentsViewModel.getPatientAppointments(true, false);
});
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading),
],
).paddingAll(16.w),
), ),
], ],
); );
} }
/// Builds the appropriate action button based on appointment state and doctor status
Widget _buildAppointmentActionButton(BuildContext context, AppState appState) {
final isLoading = widget.myAppointmentsViewModel.isMyAppointmentsLoading;
final appointment = widget.patientAppointmentHistoryResponseModel;
final isDoctorActive = appointment.isActiveDoctor ?? true;
// If doctor is not active, show only View Details button
if (!isDoctorActive) {
return _buildViewDetailsButton(context);
}
// Doctor is active - check rebooking logic
final isArrived = AppointmentType.isArrived(appointment);
final isRebookingNotAllowed = appointment.isClinicReBookingAllowed == false;
// If arrived and rebooking not allowed, show button without arrow
if (isArrived && isRebookingNotAllowed) {
return _buildSingleButton(context, isLoading);
}
// Normal flow - show button with arrow
return _buildButtonWithArrow(context, appState, isLoading);
}
/// Builds a single button without arrow (for no rebooking scenarios)
Widget _buildSingleButton(BuildContext context, bool isLoading) {
if (isLoading) {
return Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r);
}
final isArrived = AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel);
if (isArrived) {
return getArrivedAppointmentButton(context).toShimmer2(isShow: isLoading);
}
return _buildNextActionButton(context, isLoading);
}
/// Builds the next action button (Pay Now, Confirm, etc.)
Widget _buildNextActionButton(BuildContext context, bool isLoading) {
final appointment = widget.patientAppointmentHistoryResponseModel;
return CustomButton(
text: AppointmentType.getNextActionText(appointment.nextAction),
onPressed: () {
handleAppointmentNextAction(appointment.nextAction, context);
},
backgroundColor: AppointmentType.getNextActionButtonColor(appointment.nextAction).withValues(alpha: 0.15),
borderColor: AppointmentType.getNextActionButtonColor(appointment.nextAction).withValues(alpha: 0.01),
textColor: AppointmentType.getNextActionTextColor(appointment.nextAction),
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
icon: AppointmentType.getNextActionIcon(appointment.nextAction),
iconColor: AppointmentType.getNextActionTextColor(appointment.nextAction),
iconSize: 14.h,
).toShimmer2(isShow: isLoading);
}
/// Builds button with arrow (normal flow with navigation arrow)
Widget _buildButtonWithArrow(BuildContext context, AppState appState, bool isLoading) {
return Row(
children: [
_buildMainActionButton(context, isLoading),
SizedBox(width: 8.w),
_buildNavigationArrow(context, appState, isLoading),
],
);
}
/// Builds the main action button in the row (left side)
Widget _buildMainActionButton(BuildContext context, bool isLoading) {
if (isLoading) {
return Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r);
}
final isArrived = AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel);
return Expanded(
flex: 7,
child: isArrived ? getArrivedAppointmentButton(context).toShimmer2(isShow: isLoading) : _buildNextActionButton(context, isLoading),
);
}
/// Builds the navigation arrow button (right side)
Widget _buildNavigationArrow(BuildContext context, AppState appState, bool isLoading) {
final appointment = widget.patientAppointmentHistoryResponseModel;
final isArrived = AppointmentType.isArrived(appointment);
// Check if arrow should be hidden
final shouldHideArrow = (appointment.isLiveCareAppointment ?? false) ||
(appointment.isExecludeDoctor ?? false) ||
!Utils.isClinicAllowedForRebook(appointment.clinicID ?? 0);
if (shouldHideArrow && isArrived) {
return SizedBox.shrink();
}
return Expanded(
flex: 2,
child: Container(
height: 40.h,
width: 40.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.textColor,
borderRadius: 10.r,
),
child: Padding(
padding: EdgeInsets.all(10.w),
child: Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
iconColor: AppColors.whiteColor,
icon: AppAssets.forward_arrow_icon_small,
width: 40.h,
height: 40.h,
fit: BoxFit.contain,
),
),
),
).toShimmer2(isShow: isLoading).onPress(() {
Navigator.of(context)
.push(
CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: appointment),
),
)
.then((val) {
// Can refresh appointments here if needed
});
}),
);
}
/// Builds the View Details button (for inactive doctors)
Widget _buildViewDetailsButton(BuildContext context) {
return CustomButton(
text: LocaleKeys.viewDetails.tr(context: context),
onPressed: () {
Navigator.of(context)
.push(
CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
),
)
.then((_) {
widget.myAppointmentsViewModel.initAppointmentsViewModel();
widget.myAppointmentsViewModel.getPatientAppointments(true, false);
});
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: isFoldable ? 36.h : 40.h,
).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading);
}
Widget getArrivedAppointmentButton(BuildContext context) { Widget getArrivedAppointmentButton(BuildContext context) {
// Check if rebooking is not allowed - show View Details button // Check if rebooking is not allowed - show View Details button
if (widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false) { if (widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false) {
@ -393,16 +450,16 @@ class _MedicalFileAppointmentCardState extends State<MedicalFileAppointmentCard>
// No action needed - go to details // No action needed - go to details
Navigator.of(context) Navigator.of(context)
.push(CustomPageRoute( .push(CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
)) ))
.then((val) {}); .then((val) {});
break; break;
case 10: case 10:
// Confirm appointment - go to details // Confirm appointment - go to details
Navigator.of(context) Navigator.of(context)
.push(CustomPageRoute( .push(CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
)) ))
.then((val) {}); .then((val) {});
break; break;
case 15: case 15:
@ -425,41 +482,42 @@ class _MedicalFileAppointmentCardState extends State<MedicalFileAppointmentCard>
children: [ children: [
// Message text // Message text
LocaleKeys.upcomingPaymentPending.tr(context: context).toText14( LocaleKeys.upcomingPaymentPending.tr(context: context).toText14(
color: AppColors.textColor, color: AppColors.textColor,
isCenter: true, isCenter: true,
), ),
SizedBox(height: 24.h), SizedBox(height: 24.h),
// Countdown Timer - DD : HH : MM : SS format with labels // Countdown Timer - DD : HH : MM : SS format with labels
Directionality( Directionality(
textDirection: ui.TextDirection.ltr, child:Row( textDirection: ui.TextDirection.ltr,
mainAxisAlignment: MainAxisAlignment.center, child: Row(
crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center,
children: [ crossAxisAlignment: CrossAxisAlignment.start,
// Days children: [
_buildTimeUnit( // Days
_timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00', _buildTimeUnit(
LocaleKeys.days.tr(context: context), _timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00',
), LocaleKeys.days.tr(context: context),
_buildTimeSeparator(), ),
// Hours _buildTimeSeparator(),
_buildTimeUnit( // Hours
_timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00', _buildTimeUnit(
LocaleKeys.hours.tr(context: context), _timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00',
), LocaleKeys.hours.tr(context: context),
_buildTimeSeparator(), ),
// Minutes _buildTimeSeparator(),
_buildTimeUnit( // Minutes
_timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00', _buildTimeUnit(
LocaleKeys.minutes.tr(context: context), _timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00',
), LocaleKeys.minutes.tr(context: context),
_buildTimeSeparator(), ),
// Seconds _buildTimeSeparator(),
_buildTimeUnit( // Seconds
_timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00', _buildTimeUnit(
LocaleKeys.seconds.tr(context: context), _timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00',
), LocaleKeys.seconds.tr(context: context),
], ),
)), ],
)),
SizedBox(height: 24.h), SizedBox(height: 24.h),
// Green Acknowledge button with checkmark icon // Green Acknowledge button with checkmark icon
CustomButton( CustomButton(
@ -502,26 +560,25 @@ class _MedicalFileAppointmentCardState extends State<MedicalFileAppointmentCard>
// Confirm livecare - go to details // Confirm livecare - go to details
Navigator.of(context) Navigator.of(context)
.push(CustomPageRoute( .push(CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
)) ))
.then((val) {}); .then((val) {});
break; break;
case 90: case 90:
// Check-in - go to details // Check-in - go to details
Navigator.of(context) Navigator.of(context)
.push(CustomPageRoute( .push(CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
)) ))
.then((val) {}); .then((val) {});
break; break;
default: default:
// Default - go to details // Default - go to details
Navigator.of(context) Navigator.of(context)
.push(CustomPageRoute( .push(CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
)) ))
.then((val) {}); .then((val) {});
} }
} }
} }

@ -30,11 +30,7 @@ class MedicalFileCard extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final iconS = iconSize ?? 30.w; final iconS = iconSize ?? 30.w;
return Container( return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: backgroundColor, borderRadius: 20.r, hasShadow: false),
color: backgroundColor,
borderRadius: 20.r,
hasShadow: false
),
padding: EdgeInsets.all(12.w), padding: EdgeInsets.all(12.w),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -64,5 +60,3 @@ class MedicalFileCard extends StatelessWidget {
); );
} }
} }

@ -14,7 +14,7 @@ import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart';
import 'package:hmg_patient_app_new/widgets/graph/CustomBarGraph.dart'; import 'package:hmg_patient_app_new/widgets/graph/CustomBarGraph.dart';
import 'package:intl/intl.dart' show DateFormat; import 'package:intl/intl.dart' show DateFormat;
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:hmg_patient_app_new/features/smartwatch_health_data/HealthDataTransformation.dart' as durations; import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_data_transformations.dart' as durations;
import 'package:dartz/dartz.dart' show Tuple2; import 'package:dartz/dartz.dart' show Tuple2;
import '../../core/utils/date_util.dart'; import '../../core/utils/date_util.dart';

File diff suppressed because it is too large Load Diff

@ -1,252 +0,0 @@
import 'package:flutter/material.dart';
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/dependencies.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/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart';
import 'package:hmg_patient_app_new/presentation/smartwatches/activity_detail.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:provider/provider.dart';
import 'package:hmg_patient_app_new/features/smartwatch_health_data/HealthDataTransformation.dart' as durations;
import '../../core/utils/date_util.dart' show DateUtil;
class SmartWatchActivity extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: "All Health Data".needTranslation,
child: Column(
spacing: 16.h,
children: [
resultItem(
leadingIcon: AppAssets.watchActivity,
title: "Activity Calories".needTranslation,
description: "Activity rings give you a quick visual reference of how active you are each day. ".needTranslation,
trailingIcon: AppAssets.watchActivityTrailing,
result: context.read<HealthProvider>().sumOfNonEmptyData(context.read<HealthProvider>().vitals?.activity??[]),
unitsOfMeasure: "Kcal"
).onPress((){
// Map<String, List<Vitals>> getVitals() {
// return {
// "heartRate": heartRate ,
// "sleep": sleep,
// "steps": step,
// "activity": activity,
// "bodyOxygen": bodyOxygen,
// "bodyTemperature": bodyTemperature,
// };
// }
context.read<HealthProvider>().setDurations(durations.Durations.daily);
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("activity");
context.read<HealthProvider>().saveSelectedSection("activity");
context.read<HealthProvider>().fetchData();
context.read<HealthProvider>().navigateToDetails("activity", sectionName:"Activity Calories", uom: "Kcal");
}),
resultItem(
leadingIcon: AppAssets.watchSteps,
title: "Steps".needTranslation,
description: "Step count is the number of steps you take throughout the day.".needTranslation,
trailingIcon: AppAssets.watchStepsTrailing,
result: context.read<HealthProvider>().sumOfNonEmptyData(context.read<HealthProvider>().vitals?.step??[]),
unitsOfMeasure: "Steps"
).onPress((){
// Map<String, List<Vitals>> getVitals() {
// return {
// "heartRate": heartRate ,
// "sleep": sleep,
// "steps": step,
// "activity": activity,
// "bodyOxygen": bodyOxygen,
// "bodyTemperature": bodyTemperature,
// };
// }
context.read<HealthProvider>().setDurations(durations.Durations.daily);
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("steps");
context.read<HealthProvider>().saveSelectedSection("steps");
context.read<HealthProvider>().fetchData();
context.read<HealthProvider>().navigateToDetails("steps", sectionName: "Steps", uom: "Steps");
}),
resultItem(
leadingIcon: AppAssets.watchSteps,
title: "Distance Covered".needTranslation,
description: "Step count is the distance you take throughout the day.".needTranslation,
trailingIcon: AppAssets.watchStepsTrailing,
result: context.read<HealthProvider>().sumOfNonEmptyData(context.read<HealthProvider>().vitals?.distance??[]),
unitsOfMeasure: "Km"
).onPress((){
// Map<String, List<Vitals>> getVitals() {
// return {
// "heartRate": heartRate ,
// "sleep": sleep,
// "steps": step,
// "activity": activity,
// "bodyOxygen": bodyOxygen,
// "bodyTemperature": bodyTemperature,
// };
// }
context.read<HealthProvider>().setDurations(durations.Durations.daily);
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("distance");
context.read<HealthProvider>().saveSelectedSection("distance");
context.read<HealthProvider>().fetchData();
context.read<HealthProvider>().navigateToDetails("distance", sectionName: "Distance Covered", uom: "km");
}),
resultItem(
leadingIcon: AppAssets.watchSleep,
title: "Sleep Score".needTranslation,
description: "This will keep track of how much hours you sleep in a day".needTranslation,
trailingIcon: AppAssets.watchSleepTrailing,
result: DateUtil.millisToHourMin(int.parse(context.read<HealthProvider>().firstNonEmptyValue(context.read<HealthProvider>().vitals?.sleep??[]))).split(" ")[0],
unitsOfMeasure: "hr",
resultSecondValue: DateUtil.millisToHourMin(int.parse(context.read<HealthProvider>().firstNonEmptyValue(context.read<HealthProvider>().vitals?.sleep??[]))).split(" ")[2],
unitOfSecondMeasure: "min"
).onPress((){
// Map<String, List<Vitals>> getVitals() {
// return {
// "heartRate": heartRate ,
// "sleep": sleep,
// "steps": step,
// "activity": activity,
// "bodyOxygen": bodyOxygen,
// "bodyTemperature": bodyTemperature,
// };
// }
context.read<HealthProvider>().setDurations(durations.Durations.daily);
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("sleep");
context.read<HealthProvider>().saveSelectedSection("sleep");
context.read<HealthProvider>().fetchData();
context.read<HealthProvider>().navigateToDetails("sleep", sectionName:"Sleep Score",uom:"");
}),
resultItem(
leadingIcon: AppAssets.watchWeight,
title: "Blood Oxygen".needTranslation,
description: "This will calculate your Blood Oxygen to keep track and update history".needTranslation,
trailingIcon: AppAssets.watchWeightTrailing,
result: context.read<HealthProvider>().firstNonEmptyValue(context.read<HealthProvider>().vitals?.bodyOxygen??[], ),
unitsOfMeasure: "%"
).onPress((){
// Map<String, List<Vitals>> getVitals() {
// return {
// "heartRate": heartRate ,
// "sleep": sleep,
// "steps": step,
// "activity": activity,
// "bodyOxygen": bodyOxygen,
// "bodyTemperature": bodyTemperature,
// };
// }
context.read<HealthProvider>().setDurations(durations.Durations.daily);
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("bodyOxygen");
context.read<HealthProvider>().saveSelectedSection("bodyOxygen");
context.read<HealthProvider>().fetchData();
context.read<HealthProvider>().navigateToDetails("bodyOxygen", uom: "%", sectionName:"Blood Oxygen" );
}),
resultItem(
leadingIcon: AppAssets.watchWeight,
title: "Body temperature".needTranslation,
description: "This will calculate your Body temprerature to keep track and update history".needTranslation,
trailingIcon: AppAssets.watchWeightTrailing,
result: context.read<HealthProvider>().firstNonEmptyValue(context.read<HealthProvider>().vitals?.bodyTemperature??[]),
unitsOfMeasure: "C"
).onPress((){
// Map<String, List<Vitals>> getVitals() {
// return {
// "heartRate": heartRate ,
// "sleep": sleep,
// "steps": step,
// "activity": activity,
// "bodyOxygen": bodyOxygen,
// "bodyTemperature": bodyTemperature,
// };
// }
context.read<HealthProvider>().setDurations(durations.Durations.daily);
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("bodyTemperature");
context.read<HealthProvider>().saveSelectedSection("bodyTemperature");
context.read<HealthProvider>().fetchData();
context.read<HealthProvider>().navigateToDetails("bodyTemperature" , sectionName: "Body temperature".capitalizeFirstofEach, uom: "C");
}),
],
).paddingSymmetrical(24.w, 24.h),
));
}
Widget resultItem({
required String leadingIcon,
required String title,
required String description,
required String trailingIcon,
required String result,
required String unitsOfMeasure,
String? resultSecondValue,
String? unitOfSecondMeasure
}) {
return DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h),
child: Row(
spacing: 16.w,
children: [
Expanded(
child:Column(
spacing: 8.h,
children: [
Row(
spacing: 8.w,
children: [
Utils.buildSvgWithAssets(icon: leadingIcon, height: 16.h, width: 14.w),
title.toText16( weight: FontWeight.w600, color: AppColors.textColor),
],
),
description.toText12(isBold: true, color: AppColors.greyTextColor),
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
spacing: 2.h,
children: [
result.toText21(isBold: true, color: AppColors.textColor),
unitsOfMeasure.toText10(isBold: true, color:AppColors.greyTextColor ),
if(resultSecondValue != null)
Visibility(
visible: resultSecondValue != null ,
child: Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
spacing: 2.h,
children: [
SizedBox(width: 2.w,),
resultSecondValue.toText21(isBold: true, color: AppColors.textColor),
unitOfSecondMeasure!.toText10(isBold: true, color:AppColors.greyTextColor )
],
),
)
],
),
],
) ,
),
Utils.buildSvgWithAssets(icon: trailingIcon, width: 72.w, height: 72.h),
],
).paddingSymmetrical(16.w, 16.h)
);
}
}

@ -0,0 +1,188 @@
import 'package:flutter/material.dart';
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/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/features/smartwatch_health_data/health_data_transformations.dart' as durations;
import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:provider/provider.dart';
import '../../core/utils/date_util.dart' show DateUtil;
class SmartWatchesHealthDataScreen extends StatelessWidget {
const SmartWatchesHealthDataScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: "All Health Data".needTranslation,
child: Column(
spacing: 16.h,
children: [
resultItem(
leadingIcon: AppAssets.watchActivity,
title: "Activity Calories".needTranslation,
description: "Activity rings give you a quick visual reference of how active you are each day. ".needTranslation,
trailingIcon: AppAssets.watchActivityTrailing,
result: context.read<HealthProvider>().sumOfNonEmptyData(context.read<HealthProvider>().vitals?.activity ?? []),
unitsOfMeasure: "Kcal")
.onPress(() {
context.read<HealthProvider>().setDurations(durations.Durations.daily);
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("activity");
context.read<HealthProvider>().saveSelectedSection("activity");
context.read<HealthProvider>().fetchData();
context.read<HealthProvider>().navigateToDetails("activity", sectionName: "Activity Calories", uom: "Kcal");
}),
resultItem(
leadingIcon: AppAssets.watchSteps,
title: "Steps".needTranslation,
description: "Step count is the number of steps you take throughout the day.".needTranslation,
trailingIcon: AppAssets.watchStepsTrailing,
result: context.read<HealthProvider>().sumOfNonEmptyData(context.read<HealthProvider>().vitals?.step ?? []),
unitsOfMeasure: "Steps")
.onPress(() {
// Map<String, List<Vitals>> getVitals() {
// return {
// "heartRate": heartRate ,
// "sleep": sleep,
// "steps": step,
// "activity": activity,
// "bodyOxygen": bodyOxygen,
// "bodyTemperature": bodyTemperature,
// };
// }
context.read<HealthProvider>().setDurations(durations.Durations.daily);
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("steps");
context.read<HealthProvider>().saveSelectedSection("steps");
context.read<HealthProvider>().fetchData();
context.read<HealthProvider>().navigateToDetails("steps", sectionName: "Steps", uom: "Steps");
}),
resultItem(
leadingIcon: AppAssets.watchSteps,
title: "Distance Covered".needTranslation,
description: "Step count is the distance you take throughout the day.".needTranslation,
trailingIcon: AppAssets.watchStepsTrailing,
result: context.read<HealthProvider>().sumOfNonEmptyData(context.read<HealthProvider>().vitals?.distance ?? []),
unitsOfMeasure: "Km")
.onPress(() {
context.read<HealthProvider>().setDurations(durations.Durations.daily);
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("distance");
context.read<HealthProvider>().saveSelectedSection("distance");
context.read<HealthProvider>().fetchData();
context.read<HealthProvider>().navigateToDetails("distance", sectionName: "Distance Covered", uom: "km");
}),
resultItem(
leadingIcon: AppAssets.watchSleep,
title: "Sleep Score".needTranslation,
description: "This will keep track of how much hours you sleep in a day".needTranslation,
trailingIcon: AppAssets.watchSleepTrailing,
result: DateUtil.millisToHourMin(
int.parse(context.read<HealthProvider>().firstNonEmptyValue(context.read<HealthProvider>().vitals?.sleep ?? [])))
.split(" ")[0],
unitsOfMeasure: "hr",
resultSecondValue: DateUtil.millisToHourMin(
int.parse(context.read<HealthProvider>().firstNonEmptyValue(context.read<HealthProvider>().vitals?.sleep ?? [])))
.split(" ")[2],
unitOfSecondMeasure: "min")
.onPress(() {
context.read<HealthProvider>().setDurations(durations.Durations.daily);
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("sleep");
context.read<HealthProvider>().saveSelectedSection("sleep");
context.read<HealthProvider>().fetchData();
context.read<HealthProvider>().navigateToDetails("sleep", sectionName: "Sleep Score", uom: "");
}),
resultItem(
leadingIcon: AppAssets.watchWeight,
title: "Blood Oxygen".needTranslation,
description: "This will calculate your Blood Oxygen to keep track and update history".needTranslation,
trailingIcon: AppAssets.watchWeightTrailing,
result: context.read<HealthProvider>().firstNonEmptyValue(
context.read<HealthProvider>().vitals?.bodyOxygen ?? [],
),
unitsOfMeasure: "%")
.onPress(() {
context.read<HealthProvider>().setDurations(durations.Durations.daily);
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("bodyOxygen");
context.read<HealthProvider>().saveSelectedSection("bodyOxygen");
context.read<HealthProvider>().fetchData();
context.read<HealthProvider>().navigateToDetails("bodyOxygen", uom: "%", sectionName: "Blood Oxygen");
}),
resultItem(
leadingIcon: AppAssets.watchWeight,
title: "Body temperature".needTranslation,
description: "This will calculate your Body temprerature to keep track and update history".needTranslation,
trailingIcon: AppAssets.watchWeightTrailing,
result: context.read<HealthProvider>().firstNonEmptyValue(context.read<HealthProvider>().vitals?.bodyTemperature ?? []),
unitsOfMeasure: "C")
.onPress(() {
context.read<HealthProvider>().setDurations(durations.Durations.daily);
context.read<HealthProvider>().deleteDataIfSectionIsDifferent("bodyTemperature");
context.read<HealthProvider>().saveSelectedSection("bodyTemperature");
context.read<HealthProvider>().fetchData();
context.read<HealthProvider>().navigateToDetails("bodyTemperature", sectionName: "Body temperature".capitalizeFirstofEach, uom: "C");
}),
],
).paddingSymmetrical(24.w, 24.h),
));
}
Widget resultItem({
required String leadingIcon,
required String title,
required String description,
required String trailingIcon,
required String result,
required String unitsOfMeasure,
String? resultSecondValue,
String? unitOfSecondMeasure,
}) {
return DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h),
child: Row(
spacing: 16.w,
children: [
Expanded(
child: Column(
spacing: 8.h,
children: [
Row(
spacing: 8.w,
children: [
Utils.buildSvgWithAssets(icon: leadingIcon, height: 16.h, width: 14.w),
title.toText16(weight: FontWeight.w600, color: AppColors.textColor),
],
),
description.toText12(isBold: true, color: AppColors.greyTextColor),
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
spacing: 2.h,
children: [
result.toText21(isBold: true, color: AppColors.textColor),
unitsOfMeasure.toText10(isBold: true, color: AppColors.greyTextColor),
if (resultSecondValue != null)
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
spacing: 2.h,
children: [
SizedBox(width: 2.w),
resultSecondValue.toText21(isBold: true, color: AppColors.textColor),
unitOfSecondMeasure!.toText10(isBold: true, color: AppColors.greyTextColor)
],
),
],
),
],
),
),
Utils.buildSvgWithAssets(icon: trailingIcon, width: 72.w, height: 72.h),
],
).paddingSymmetrical(16.w, 16.h));
}
}

@ -1,5 +1,3 @@
import 'dart:io';
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/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -54,7 +52,6 @@ class SmartwatchHomePage extends StatelessWidget {
fontSize: 16.f, fontSize: 16.f,
isBold: true, isBold: true,
borderRadius: 12.r, borderRadius: 12.r,
height: 50.h, height: 50.h,
icon: AppAssets.ask_doctor_icon, icon: AppAssets.ask_doctor_icon,
iconColor: AppColors.infoColor, iconColor: AppColors.infoColor,
@ -69,11 +66,12 @@ class SmartwatchHomePage extends StatelessWidget {
child: GridView( child: GridView(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
shrinkWrap: true, shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, crossAxisCount: 2,
crossAxisSpacing: 16.h, crossAxisSpacing: 16.h,
mainAxisSpacing: 16.w, mainAxisSpacing: 16.w,
mainAxisExtent: 240.h, childAspectRatio: isFoldable ? 1.1 : (isTablet ? 1.3 : 0.7),
), ),
children: [ children: [
Container( Container(
@ -83,20 +81,24 @@ class SmartwatchHomePage extends StatelessWidget {
), ),
child: Column( child: Column(
children: [ children: [
Image.asset("assets/images/png/smartwatches/apple-watch-5.jpg", width: 136.w, height: 136.h).paddingSymmetrical(24.w, 8.h), Image.asset("assets/images/png/smartwatches/apple-watch-5.jpg", width: 136.h, height: 136.h).paddingSymmetrical(24.w, 8.h),
"Apple Watch".needTranslation.toText16(isBold: true), "Apple Watch".needTranslation.toText16(isBold: true),
CustomButton( CustomButton(
text: LocaleKeys.selectSmartWatch.tr(context: context), text: LocaleKeys.selectSmartWatch.tr(context: context),
onPressed: () { onPressed: () {
context.read<HealthProvider>().setSelectedWatchType(SmartWatchTypes.apple, "assets/images/png/smartwatches/apple-watch-5.jpg"); context
getIt.get<NavigationService>().pushPage(page: SmartwatchInstructionsPage( .read<HealthProvider>()
smartwatchDetails: SmartwatchDetails(SmartWatchTypes.apple, .setSelectedWatchType(SmartWatchTypes.apple, "assets/images/png/smartwatches/apple-watch-5.jpg");
"assets/images/png/smartwatches/apple-watch-5.jpg", getIt.get<NavigationService>().pushPage(
AppAssets.bluetooth, page: SmartwatchInstructionsPage(
LocaleKeys.applehealthapplicationshouldbeinstalledinyourphone.tr(context: context), smartwatchDetails: SmartwatchDetails(
LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), SmartWatchTypes.apple,
LocaleKeys.applewatchshouldbeconnected.tr(context: context)), "assets/images/png/smartwatches/apple-watch-5.jpg",
)); AppAssets.bluetooth,
LocaleKeys.applehealthapplicationshouldbeinstalledinyourphone.tr(context: context),
LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context),
LocaleKeys.applewatchshouldbeconnected.tr(context: context)),
));
}, },
backgroundColor: AppColors.primaryRedColor.withAlpha(40), backgroundColor: AppColors.primaryRedColor.withAlpha(40),
borderColor: AppColors.primaryRedColor.withAlpha(0), borderColor: AppColors.primaryRedColor.withAlpha(0),
@ -110,26 +112,29 @@ class SmartwatchHomePage extends StatelessWidget {
), ),
), ),
Container( Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
color: AppColors.whiteColor,
borderRadius: 24.r,
),
child: Column( child: Column(
children: [ children: [
Image.asset("assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg", fit: BoxFit.contain, width: 136.w, height: 136.h).paddingSymmetrical(24.w, 8.h), Image.asset("assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg", fit: BoxFit.contain, width: 136.w, height: 136.h)
.paddingSymmetrical(24.w, 8.h),
"Samsung Watch".needTranslation.toText16(isBold: true), "Samsung Watch".needTranslation.toText16(isBold: true),
CustomButton( CustomButton(
text: LocaleKeys.selectSmartWatch.tr(context: context), text: LocaleKeys.selectSmartWatch.tr(context: context),
onPressed: () { onPressed: () {
context.read<HealthProvider>().setSelectedWatchType(SmartWatchTypes.samsung, "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg"); context
getIt.get<NavigationService>().pushPage(page: SmartwatchInstructionsPage( .read<HealthProvider>()
smartwatchDetails: SmartwatchDetails(SmartWatchTypes.samsung, .setSelectedWatchType(SmartWatchTypes.samsung, "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg");
"assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg", getIt.get<NavigationService>().pushPage(
AppAssets.bluetooth, page: SmartwatchInstructionsPage(
LocaleKeys.samsunghealthapplicationshouldbeinstalledinyourphone.tr(context: context), smartwatchDetails: SmartwatchDetails(
LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context), SmartWatchTypes.samsung,
LocaleKeys.samsungwatchshouldbeconnected.tr(context: context)), "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg",
)); }, AppAssets.bluetooth,
LocaleKeys.samsunghealthapplicationshouldbeinstalledinyourphone.tr(context: context),
LocaleKeys.unabletodetectapplicationinstalledpleasecomebackonceinstalled.tr(context: context),
LocaleKeys.samsungwatchshouldbeconnected.tr(context: context)),
));
},
backgroundColor: AppColors.primaryRedColor.withAlpha(40), backgroundColor: AppColors.primaryRedColor.withAlpha(40),
borderColor: AppColors.primaryRedColor.withAlpha(0), borderColor: AppColors.primaryRedColor.withAlpha(0),
textColor: AppColors.primaryRedColor, textColor: AppColors.primaryRedColor,
@ -187,7 +192,6 @@ class SmartwatchHomePage extends StatelessWidget {
CustomButton( CustomButton(
text: LocaleKeys.selectSmartWatch.tr(context: context), text: LocaleKeys.selectSmartWatch.tr(context: context),
onPressed: () { onPressed: () {
showUnavailableDialog(context); showUnavailableDialog(context);
// context.read<HealthProvider>().setSelectedWatchType(SmartWatchTypes.whoop, "assets/images/png/smartwatches/Whoop_Watch.png"); // context.read<HealthProvider>().setSelectedWatchType(SmartWatchTypes.whoop, "assets/images/png/smartwatches/Whoop_Watch.png");
// getIt.get<NavigationService>().pushPage(page: SmartwatchInstructionsPage( // getIt.get<NavigationService>().pushPage(page: SmartwatchInstructionsPage(
@ -221,18 +225,16 @@ class SmartwatchHomePage extends StatelessWidget {
} }
void showUnavailableDialog(BuildContext context) { void showUnavailableDialog(BuildContext context) {
showCommonBottomSheetWithoutHeight( showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context), title: LocaleKeys.notice.tr(context: context),
context, context,
child: Utils.getWarningWidget( child: Utils.getWarningWidget(
loadingText: LocaleKeys.featureComingSoonDescription.tr(context: context), loadingText: LocaleKeys.featureComingSoonDescription.tr(context: context),
isShowActionButtons: false, isShowActionButtons: false,
showOkButton: true, showOkButton: true,
onConfirmTap: () async { onConfirmTap: () async {
context.pop(); context.pop();
} }),
),
callBackFunc: () {}, callBackFunc: () {},
isFullScreen: false, isFullScreen: false,
isCloseButtonVisible: true, isCloseButtonVisible: true,

@ -2,13 +2,10 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/common_models/smart_watch.dart'; import 'package:hmg_patient_app_new/core/common_models/smart_watch.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/smartwatches/smart_watch_activity.dart' show SmartWatchActivity;
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -26,62 +23,66 @@ class SmartwatchInstructionsPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: AppColors.bgScaffoldColor, backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView( body: CollapsingListView(
title: "How does it work".needTranslation, title: "How does it work".needTranslation,
bottomChild: Container( bottomChild: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor, color: AppColors.whiteColor,
borderRadius: 24.r, borderRadius: 24.r,
),
child: CustomButton(
text: LocaleKeys.getStarted.tr(context: context),
onPressed: () {
context.read<HealthProvider>().initDevice();
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 16.f,
isBold: true,
borderRadius: 12.r,
height: 50.h,
).paddingSymmetrical(24.w, 30.h),
), ),
child: Column( child: CustomButton(
mainAxisSize: MainAxisSize.max, text: LocaleKeys.getStarted.tr(context: context),
spacing: 18.h, onPressed: () {
children: [ context.read<HealthProvider>().initDevice();
Image.asset(smartwatchDetails.watchIcon, fit: BoxFit.contain, height: 280.h,width: 280.w,), },
DecoratedBox( backgroundColor: AppColors.primaryRedColor,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), borderColor: AppColors.primaryRedColor,
child: Column( textColor: AppColors.whiteColor,
children: [ fontSize: 16.f,
watchContentDetails( isBold: true,
title: smartwatchDetails.detailsTitle, borderRadius: 12.r,
description: smartwatchDetails.details, height: 50.h,
icon: smartwatchDetails.smallIcon, ).paddingSymmetrical(24.w, 30.h),
descriptionTextColor: AppColors.primaryRedColor
),
Divider(
color: AppColors.dividerColor,
thickness: 1.h,
).paddingOnly(top: 16.h, bottom: 16.h),
watchContentDetails(
title: smartwatchDetails.secondTitle,
description: LocaleKeys.updatetheinformation.tr(),
icon: AppAssets.bluetooth,
descriptionTextColor: AppColors.greyTextColor
),
],
).paddingSymmetrical(16.w, 16.h),
)
],
).paddingSymmetrical(24.w, 16.h),
), ),
child: Column(
mainAxisSize: MainAxisSize.max,
spacing: 18.h,
children: [
Image.asset(
smartwatchDetails.watchIcon,
fit: BoxFit.contain,
height: 280.h,
width: 280.w,
),
DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h),
child: Column(
children: [
watchContentDetails(
title: smartwatchDetails.detailsTitle,
description: smartwatchDetails.details,
icon: smartwatchDetails.smallIcon,
descriptionTextColor: AppColors.primaryRedColor,
),
Divider(
color: AppColors.dividerColor,
thickness: 1.h,
).paddingOnly(top: 16.h, bottom: 16.h),
watchContentDetails(
title: smartwatchDetails.secondTitle,
description: LocaleKeys.updatetheinformation.tr(),
icon: AppAssets.bluetooth,
descriptionTextColor: AppColors.greyTextColor,
),
],
).paddingSymmetrical(16.w, 16.h),
)
],
).paddingSymmetrical(24.w, 16.h),
),
); );
} }
Widget watchContentDetails({required String title, required String description, required String icon, required Color descriptionTextColor}) { Widget watchContentDetails({required String title, required String description, required String icon, required Color descriptionTextColor}) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -90,9 +91,7 @@ class SmartwatchInstructionsPage extends StatelessWidget {
DecoratedBox( DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h),
child: Utils.buildSvgWithAssets(icon: icon, width: 40.w, height: 40.h), child: Utils.buildSvgWithAssets(icon: icon, width: 40.w, height: 40.h),
), ),
title.toText16(isBold: true, color: AppColors.textColor), title.toText16(isBold: true, color: AppColors.textColor),
description.toText12(isBold: true, color: descriptionTextColor) description.toText12(isBold: true, color: descriptionTextColor)
], ],

@ -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:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_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';
@ -46,13 +47,13 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
Future<void> _checkAndShowTutorial() async { Future<void> _checkAndShowTutorial() async {
// final hasSeenTutorial = cacheService.getBool(key: CacheConst.organSelectorTutorialShown) ?? false; // final hasSeenTutorial = cacheService.getBool(key: CacheConst.organSelectorTutorialShown) ?? false;
// if (!hasSeenTutorial) { // if (!hasSeenTutorial) {
// Show tutorial after a short delay to ensure the screen is fully built // Show tutorial after a short delay to ensure the screen is fully built
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
if (mounted) { if (mounted) {
setState(() { setState(() {
_showTutorial = true; _showTutorial = true;
}); });
} }
// } // }
} }
@ -74,12 +75,17 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
loadingText: LocaleKeys.pleaseWait.tr(context: context), loadingText: LocaleKeys.pleaseWait.tr(context: context),
); );
final String userName = 'guest_user'; // Get fileNo if user is logged in
final String password = '123456'; String? fileNo;
if (_appState.isAuthenticated) {
final user = _appState.getAuthenticatedUser();
fileNo = user?.patientId.toString();
}
await viewModel.getSymptomsUserDetails( await viewModel.getSymptomsUserDetails(
userName: userName, userName: ApiConsts.symptomsCheckerUsername,
password: password, password: ApiConsts.symptomsCheckerPassword,
fileNo: fileNo,
onSuccess: () { onSuccess: () {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
context.navigateWithName(AppRoutes.symptomsSelectorPage); context.navigateWithName(AppRoutes.symptomsSelectorPage);

@ -1,3 +1,5 @@
import 'dart:developer';
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/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -17,6 +19,7 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/nearest_er_page.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/nearest_er_page.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/condition_card.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/condition_card.dart';
@ -29,17 +32,28 @@ import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart'; import 'package:shimmer/shimmer.dart';
import '../appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart'; class PossibleConditionsPage extends StatefulWidget {
const PossibleConditionsPage({super.key});
class PossibleConditionsPage extends StatelessWidget { @override
PossibleConditionsPage({super.key}); State<PossibleConditionsPage> createState() => _PossibleConditionsPageState();
}
class _PossibleConditionsPageState extends State<PossibleConditionsPage> {
late SymptomsCheckerViewModel symptomsCheckerViewModel; late SymptomsCheckerViewModel symptomsCheckerViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel;
late AppointmentViaRegionViewmodel regionalViewModel; late AppointmentViaRegionViewmodel regionalViewModel;
late AppState appState; late AppState appState;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// Clear selections once user reaches results page
symptomsCheckerViewModel.clearSelectionsKeepResults();
});
}
Widget _buildLoadingShimmer() { Widget _buildLoadingShimmer() {
return ListView.separated( return ListView.separated(
shrinkWrap: true, shrinkWrap: true,
@ -114,13 +128,16 @@ class PossibleConditionsPage extends StatelessWidget {
return; return;
} }
log("isBookingFromSymptomsChecker: ${symptomsCheckerViewModel.isBookingFromSymptomsChecker}");
// For non-emergency cases, continue with normal booking flow // For non-emergency cases, continue with normal booking flow
LoaderBottomSheet.showLoader(); LoaderBottomSheet.showLoader();
symptomsCheckerViewModel.getClinicConditionsFromCategory( symptomsCheckerViewModel.getClinicConditionsFromCategory(
categoryName: (condition.conditionDetails!.category!.name) ?? "Other", categoryName: (condition.conditionDetails!.category!.name) ?? "Other",
onSuccess: (value) { onSuccess: (value) {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
print(symptomsCheckerViewModel.clinicDetailsList.first.clinicID); debugPrint("isBookingFromSymptomsChecker:: ${symptomsCheckerViewModel.isBookingFromSymptomsChecker}");
debugPrint(symptomsCheckerViewModel.clinicDetailsList.first.clinicID.toString());
initiateBookAppointmentFlow(context); initiateBookAppointmentFlow(context);
}, },
onError: (err) { onError: (err) {
@ -282,6 +299,9 @@ class PossibleConditionsPage extends StatelessWidget {
} }
initiateBookAppointmentFlow(BuildContext context) { initiateBookAppointmentFlow(BuildContext context) {
// Set flag to indicate booking is from symptoms checker
symptomsCheckerViewModel.setBookingFromSymptomsChecker(true);
// bookAppointmentsViewModel.getLocation(); // bookAppointmentsViewModel.getLocation();
bookAppointmentsViewModel.setSelectedClinic(GetClinicsListResponseModel( bookAppointmentsViewModel.setSelectedClinic(GetClinicsListResponseModel(
clinicID: symptomsCheckerViewModel.clinicDetailsList.first.clinicID, clinicID: symptomsCheckerViewModel.clinicDetailsList.first.clinicID,
@ -307,6 +327,10 @@ class PossibleConditionsPage extends StatelessWidget {
), ),
).onPress(() { ).onPress(() {
data.handleBackPress(); data.handleBackPress();
// Reset flag if user goes back during symptoms checker booking
if (symptomsCheckerViewModel.isBookingFromSymptomsChecker) {
symptomsCheckerViewModel.setBookingFromSymptomsChecker(false);
}
}); });
} }
} }
@ -317,8 +341,10 @@ class PossibleConditionsPage extends StatelessWidget {
regionalViewModel.flush(); regionalViewModel.flush();
regionalViewModel.setBottomSheetType(type); regionalViewModel.setBottomSheetType(type);
// AppointmentViaRegionViewmodel? viewmodel = null; // AppointmentViaRegionViewmodel? viewmodel = null;
showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) => getTitle(data, context)), isDismissible: false, showCommonBottomSheetWithoutHeight(context,
child: Consumer<AppointmentViaRegionViewmodel>(builder: (context, data, __) { title: "",
titleWidget: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) => getTitle(data, context)),
isDismissible: false, child: Consumer<AppointmentViaRegionViewmodel>(builder: (context, data, __) {
return getRegionalSelectionWidget(data, context); return getRegionalSelectionWidget(data, context);
}), callBackFunc: () {}); }), callBackFunc: () {});
} }
@ -356,7 +382,7 @@ class PossibleConditionsPage extends StatelessWidget {
} }
}, },
onHospitalSearch: (value) { onHospitalSearch: (value) {
data.searchHospitals(value ?? ""); data.searchHospitals(value);
}, },
selectedFacility: data.selectedFacility, selectedFacility: data.selectedFacility,
hmcCount: data.hmcCount, hmcCount: data.hmcCount,
@ -378,7 +404,6 @@ class PossibleConditionsPage extends StatelessWidget {
} else { } else {
return SizedBox.shrink(); return SizedBox.shrink();
} }
return SizedBox.shrink();
} }
void _handleSortByLocationToggle(bool value, AppointmentViaRegionViewmodel regionVM) { void _handleSortByLocationToggle(bool value, AppointmentViaRegionViewmodel regionVM) {

@ -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: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';
@ -28,12 +29,20 @@ class SymptomsSelectorPage extends StatefulWidget {
class _SymptomsSelectorPageState extends State<SymptomsSelectorPage> { class _SymptomsSelectorPageState extends State<SymptomsSelectorPage> {
late DialogService dialogService; late DialogService dialogService;
late AppState _appState; late AppState _appState;
final TextEditingController _searchController = TextEditingController();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
dialogService = getIt<DialogService>(); dialogService = getIt<DialogService>();
_appState = getIt<AppState>(); _appState = getIt<AppState>();
// Listen to search input changes
_searchController.addListener(() {
final viewModel = context.read<SymptomsCheckerViewModel>();
viewModel.filterSymptoms(_searchController.text, isArabic: _appState.isArabic());
});
// Initialize symptom groups based on selected organs // Initialize symptom groups based on selected organs
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
final viewModel = context.read<SymptomsCheckerViewModel>(); final viewModel = context.read<SymptomsCheckerViewModel>();
@ -41,6 +50,12 @@ class _SymptomsSelectorPageState extends State<SymptomsSelectorPage> {
}); });
} }
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
void _onNextPressed(SymptomsCheckerViewModel viewModel) { void _onNextPressed(SymptomsCheckerViewModel viewModel) {
if (viewModel.hasSelectedSymptoms) { if (viewModel.hasSelectedSymptoms) {
// Navigate to triage screen // Navigate to triage screen
@ -100,7 +115,56 @@ class _SymptomsSelectorPageState extends State<SymptomsSelectorPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 16.h), SizedBox(height: 16.h),
...viewModel.organSymptomsResults.map((organResult) { // Inline search field
Padding(
padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.r,
),
child: TextField(
controller: _searchController,
style: TextStyle(
fontSize: 14.f,
color: AppColors.textColor,
fontFamily: _appState.isArabic() ? 'CairoArabic' : 'Poppins',
),
decoration: InputDecoration(
hintText: LocaleKeys.search.tr(context: context),
hintStyle: TextStyle(
color: AppColors.greyTextColor,
fontSize: 14.f,
),
prefixIcon: Icon(
Icons.search,
color: AppColors.greyTextColor,
size: 20.h,
),
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
icon: Icon(
Icons.clear,
color: AppColors.greyTextColor,
size: 20.h,
),
onPressed: () {
_searchController.clear();
viewModel.clearSymptomFilter();
},
)
: null,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 12.h,
),
),
),
),
),
SizedBox(height: 16.h),
...viewModel.filteredOrganSymptomsResults.map((organResult) {
// Find matching organ ID from selected organs // Find matching organ ID from selected organs
String? organId; String? organId;
String? organName; String? organName;

@ -1,5 +1,3 @@
import 'dart:developer';
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/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -175,20 +173,51 @@ class _TriagePageState extends State<TriagePage> {
return; return;
} }
// Collect all evidence from all items // Collect evidence based on question type
for (var item in currentQuestion.items!) { if (viewModel.isTriageQuestionSingleSelection) {
final itemId = item.id ?? ""; // Type 1: Single selection - only one evidence entry with "Yes" choice
if (itemId.isEmpty) continue; final selectedItemId = viewModel.selectedSingleItemId;
final selectedChoiceIndex = viewModel.getTriageChoiceForItem(itemId); if (selectedItemId != null) {
if (selectedChoiceIndex == null) continue; // Find the item and its "Yes" choice
for (var item in currentQuestion.items!) {
if (item.choices != null && selectedChoiceIndex < item.choices!.length) { if (item.id == selectedItemId) {
final selectedChoice = item.choices![selectedChoiceIndex]; // Find the "Yes" choice (case-insensitive)
final choiceId = selectedChoice.id ?? ""; String? yesChoiceId;
if (item.choices != null) {
if (choiceId.isNotEmpty) { for (var choice in item.choices!) {
viewModel.addTriageEvidence(itemId, choiceId); final label = choice.label?.toLowerCase() ?? '';
if (label == 'yes' || label == 'نعم') {
yesChoiceId = choice.id;
break;
}
}
}
// If "Yes" choice found, add evidence
if (yesChoiceId != null && yesChoiceId.isNotEmpty) {
viewModel.addTriageEvidence(selectedItemId, yesChoiceId);
}
break;
}
}
}
} else {
// Type 2: Multi-item selection - collect evidence from all items
for (var item in currentQuestion.items!) {
final itemId = item.id ?? "";
if (itemId.isEmpty) continue;
final selectedChoiceIndex = viewModel.getTriageChoiceForItem(itemId);
if (selectedChoiceIndex == null) continue;
if (item.choices != null && selectedChoiceIndex < item.choices!.length) {
final selectedChoice = item.choices![selectedChoiceIndex];
final choiceId = selectedChoice.id ?? "";
if (choiceId.isNotEmpty) {
viewModel.addTriageEvidence(itemId, choiceId);
}
} }
} }
} }
@ -197,9 +226,6 @@ class _TriagePageState extends State<TriagePage> {
List<String> initialEvidenceIds = viewModel.getAllEvidenceIds(); List<String> initialEvidenceIds = viewModel.getAllEvidenceIds();
List<Map<String, String>> triageEvidence = viewModel.getTriageEvidence(); List<Map<String, String>> triageEvidence = viewModel.getTriageEvidence();
log("initialEvidenceIds: ${initialEvidenceIds.toString()}");
log("triageEvidence: ${triageEvidence.toString()}");
// Call API with updated evidence // Call API with updated evidence
viewModel.getDiagnosisForTriage( viewModel.getDiagnosisForTriage(
age: viewModel.selectedAge!, age: viewModel.selectedAge!,
@ -376,33 +402,75 @@ class _TriagePageState extends State<TriagePage> {
(question.text ?? "").toText16(isBold: true, color: AppColors.textColor), (question.text ?? "").toText16(isBold: true, color: AppColors.textColor),
SizedBox(height: 24.h), SizedBox(height: 24.h),
// Show all items with dividers // Type 1: Show items as checkboxes only (no choices displayed)
...List.generate(question.items!.length, (itemIndex) { if (viewModel.isTriageQuestionSingleSelection) ...[
final item = question.items![itemIndex]; ...List.generate(question.items!.length, (itemIndex) {
final itemId = item.id ?? ""; final item = question.items![itemIndex];
final choices = item.choices ?? []; final itemId = item.id ?? "";
final itemName = item.name ?? "";
return Column( final isSelected = viewModel.selectedSingleItemId == itemId;
crossAxisAlignment: CrossAxisAlignment.start,
children: [ return GestureDetector(
// Item name (sub-question) onTap: () => _onOptionSelectedForItem(itemId, 0), // Pass 0 as placeholder
(item.name ?? "").toText14(isBold: true, color: AppColors.textColor), child: Container(
SizedBox(height: 8.h), margin: EdgeInsets.only(bottom: 12.h),
// Choices for this item child: Row(
...List.generate(choices.length, (choiceIndex) { crossAxisAlignment: CrossAxisAlignment.start,
bool selected = viewModel.getTriageChoiceForItem(itemId) == choiceIndex; children: [
return _buildOptionItem(itemId, choiceIndex, selected, choices[choiceIndex].label ?? ""); AnimatedContainer(
}), duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
// Add divider between items (but not after the last one) width: 24.w,
if (itemIndex < question.items!.length - 1) ...[ height: 24.w,
decoration: BoxDecoration(
color: isSelected ? AppColors.primaryRedColor : Colors.transparent,
borderRadius: BorderRadius.circular(5.r),
border: Border.all(
color: isSelected ? AppColors.primaryRedColor : AppColors.checkBoxBorderColor,
width: 1.w,
),
),
child: isSelected ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor) : null,
),
SizedBox(width: 12.w),
Expanded(child: itemName.toText13(isBold: true)),
],
),
),
);
}),
] else ...[
// Type 2: Show all items with their choices
...List.generate(question.items!.length, (itemIndex) {
final item = question.items![itemIndex];
final itemId = item.id ?? "";
final choices = item.choices ?? [];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Item name (sub-question)
(item.name ?? "").toText14(isBold: true, color: AppColors.textColor),
SizedBox(height: 8.h), SizedBox(height: 8.h),
Divider(color: AppColors.bottomNAVBorder, thickness: 1), // Choices for this item
SizedBox(height: 10.h), ...List.generate(choices.length, (choiceIndex) {
// Check selection based on question type
bool selected = viewModel.isTriageQuestionSingleSelection
? viewModel.isTriageSingleOptionSelected(itemId, choiceIndex)
: viewModel.getTriageChoiceForItem(itemId) == choiceIndex;
return _buildOptionItem(itemId, choiceIndex, selected, choices[choiceIndex].label ?? "");
}),
// Add divider between items (but not after the last one)
if (itemIndex < question.items!.length - 1) ...[
SizedBox(height: 8.h),
Divider(color: AppColors.bottomNAVBorder, thickness: 1),
SizedBox(height: 10.h),
],
], ],
], );
); }),
}), ],
], ],
), ),
), ),
@ -468,18 +536,12 @@ class _TriagePageState extends State<TriagePage> {
text: TextSpan( text: TextSpan(
text: "${LocaleKeys.possibleSymptom.tr(context: context)} ", text: "${LocaleKeys.possibleSymptom.tr(context: context)} ",
style: TextStyle( style: TextStyle(
color: AppColors.greyTextColor, fontWeight: FontWeight.w600, fontSize: 14.f, fontFamily: isArabic ? 'CairoArabic' : 'Poppins'), color: AppColors.greyTextColor, fontWeight: FontWeight.w600, fontSize: 14.f, fontFamily: isArabic ? 'CairoArabic' : 'Poppins'),
children: [ children: [
TextSpan( TextSpan(
text: suggestedCondition, text: suggestedCondition,
style: TextStyle( style: TextStyle(
color: AppColors.textColor, color: AppColors.textColor, fontWeight: FontWeight.w600, fontSize: 14.f, fontFamily: isArabic ? 'CairoArabic' : 'Poppins'),
fontWeight: FontWeight.w600,
fontSize: 14.f, fontFamily: isArabic ? 'CairoArabic' : 'Poppins'),
), ),
], ],
), ),

@ -99,9 +99,8 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
title.toText14(isBold: true), title.toText14(isBold: true),
subTitle subTitle.toText12(color: AppColors.primaryRedColor, isBold: true, isEnglishOnly: true).toShimmer2(
.toText12(color: AppColors.primaryRedColor, isBold: true, isEnglishOnly: true) isShow: (leadingIcon == AppAssets.rulerIcon || leadingIcon == AppAssets.weightScale) && hmgServicesVM.isVitalSignLoading),
.toShimmer2(isShow: (leadingIcon == AppAssets.rulerIcon || leadingIcon == AppAssets.weightScale) && hmgServicesVM.isVitalSignLoading),
], ],
), ),
], ],
@ -159,7 +158,9 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
} }
} }
} else { } else {
name = ""; /// as per the mahmooud instruction, if user is not authenticated, we will not show any name in the greeting message name = "";
/// as per the mahmooud instruction, if user is not authenticated, we will not show any name in the greeting message
} }
return Scaffold( return Scaffold(
@ -167,7 +168,10 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
body: Consumer2<SymptomsCheckerViewModel, HmgServicesViewModel>( body: Consumer2<SymptomsCheckerViewModel, HmgServicesViewModel>(
builder: (context, viewModel, hmgServicesVM, child) { builder: (context, viewModel, hmgServicesVM, child) {
// Check if any field is empty // Check if any field is empty
bool hasEmptyFields = viewModel.selectedGender == null || viewModel.selectedAge == null || viewModel.selectedHeight == null || viewModel.selectedWeight == null; bool hasEmptyFields = viewModel.selectedGender == null ||
viewModel.selectedAge == null ||
viewModel.selectedHeight == null ||
viewModel.selectedWeight == null;
// Get display values // Get display values
String genderText = _getLocalizedGender(viewModel.selectedGender, context); String genderText = _getLocalizedGender(viewModel.selectedGender, context);
@ -313,6 +317,7 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
), ),
], ],
), ),
SizedBox(height: 24.h),
], ],
).paddingSymmetrical(24.w, 0), ).paddingSymmetrical(24.w, 0),
), ),

@ -53,9 +53,10 @@ class HeightSelectionPage extends StatelessWidget {
child: Text( child: Text(
'CM', 'CM',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
fontSize: 14.f, fontSize: 14.f,
color: viewModel.isHeightCm ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.6), fontFamily: "Poppins"), color: viewModel.isHeightCm ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.6),
fontFamily: "Poppins"),
), ),
), ),
), ),
@ -74,9 +75,10 @@ class HeightSelectionPage extends StatelessWidget {
child: Text( child: Text(
'FT', 'FT',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
fontSize: 14.f, fontSize: 14.f,
color: !viewModel.isHeightCm ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.6), fontFamily: "Poppins"), color: !viewModel.isHeightCm ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.6),
fontFamily: "Poppins"),
), ),
), ),
), ),
@ -128,10 +130,7 @@ class HeightSelectionPage extends StatelessWidget {
TextSpan( TextSpan(
text: text:
viewModel.isHeightCm ? viewModel.selectedHeight?.round().toString() : viewModel.selectedHeight?.toStringAsFixed(1), viewModel.isHeightCm ? viewModel.selectedHeight?.round().toString() : viewModel.selectedHeight?.toStringAsFixed(1),
style: TextStyle( style: TextStyle(fontSize: 90.f, color: AppColors.textColor, height: 1, fontFamily: "Poppins"),
fontSize: 90.f,
color: AppColors.textColor,
height: 1, fontFamily: "Poppins"),
), ),
TextSpan( TextSpan(
text: viewModel.isHeightCm ? 'cm' : 'ft', text: viewModel.isHeightCm ? 'cm' : 'ft',
@ -142,7 +141,7 @@ class HeightSelectionPage extends StatelessWidget {
), ),
], ],
), ),
).paddingOnly(bottom: 100.h, left: 20.w); ).paddingOnly(bottom: 100.h, left: isFoldable ? 40.w : 20.w);
}, },
), ),
), ),

@ -181,7 +181,7 @@ class _UserInfoFlowManagerState extends State<UserInfoFlowManager> {
color: AppColors.whiteColor, color: AppColors.whiteColor,
borderRadius: BorderRadius.vertical(top: Radius.circular(24.r)), borderRadius: BorderRadius.vertical(top: Radius.circular(24.r)),
), ),
padding: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h), padding: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 24.h),
child: SafeArea( child: SafeArea(
top: false, top: false,
child: isSingleEdit child: isSingleEdit

@ -131,12 +131,11 @@ class _HeightScaleState extends State<HeightScale> {
child: Text( child: Text(
widget.isCm ? height.round().toString() : height.toStringAsFixed(1), widget.isCm ? height.round().toString() : height.toStringAsFixed(1),
style: TextStyle( style: TextStyle(
fontSize: 11.f, fontSize: 11.f,
color: AppColors.greyTextColor, color: AppColors.greyTextColor,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
height: 1, height: isFoldable ? 0.5 : 1,
fontFamily: "Poppins" fontFamily: "Poppins"),
),
textAlign: TextAlign.right, textAlign: TextAlign.right,
), ),
) )

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:ui' as ui;
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
@ -24,8 +25,6 @@ import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.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';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'dart:ui' as ui;
class AncillaryOrderDetailsList extends StatefulWidget { class AncillaryOrderDetailsList extends StatefulWidget {
final int appointmentNoVida; final int appointmentNoVida;
final int orderNo; final int orderNo;
@ -653,13 +652,15 @@ class _AncillaryOrderDetailsListState extends State<AncillaryOrderDetailsList> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
SizedBox( SizedBox(
width: 200.h, width: isFoldable ? 220.w : 200.w,
child: Utils.getPaymentMethods(), child: Utils.getPaymentMethods(),
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Utils.getPaymentAmountWithSymbol(NumberFormat.decimalPattern().format(_getTotalAmount()).toText24(isBold: true, isEnglishOnly: true), AppColors.blackColor, 17, isSaudiCurrency: true), Utils.getPaymentAmountWithSymbol(
NumberFormat.decimalPattern().format(_getTotalAmount()).toText24(isBold: true, isEnglishOnly: true), AppColors.blackColor, 17,
isSaudiCurrency: true),
], ],
), ),
], ],
@ -695,40 +696,5 @@ class _AncillaryOrderDetailsListState extends State<AncillaryOrderDetailsList> {
], ],
).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h), ).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h),
); );
// Column(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// SizedBox(height: 16.h),
// _buildSummarySection(orderData),
// SizedBox(height: 16.h),
// CustomButton(
// borderWidth: 0,
// backgroundColor: AppColors.infoLightColor,
// text: "Proceed to Payment".needTranslation,
// onPressed: () {
// // Navigate to payment page with selected procedures
// Navigator.of(context).push(
// CustomPageRoute(
// page: AncillaryOrderPaymentPage(
// appointmentNoVida: widget.appointmentNoVida,
// orderNo: widget.orderNo,
// projectID: widget.projectID,
// selectedProcedures: selectedProcedures,
// totalAmount: _getTotalAmount(),
// appointmentDate: orderData.appointmentDate,
// ),
// ),
// );
// },
// isDisabled: !isButtonEnabled,
// textColor: AppColors.whiteColor,
// borderRadius: 12.r,
// borderColor: Colors.transparent,
// padding: EdgeInsets.symmetric(vertical: 16.h),
// ),
// SizedBox(height: 22.h),
// ],
// ).paddingSymmetrical(24.w, 0);
} }
} }

@ -20,7 +20,6 @@ import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart'; import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart';
import 'package:hmg_patient_app_new/presentation/servicesPriceList/services_price_list_page.dart'; import 'package:hmg_patient_app_new/presentation/servicesPriceList/services_price_list_page.dart';
import 'package:hmg_patient_app_new/presentation/smartwatches/huawei_health_example.dart';
import 'package:hmg_patient_app_new/presentation/smartwatches/smartwatch_home_page.dart'; import 'package:hmg_patient_app_new/presentation/smartwatches/smartwatch_home_page.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/organ_selector_screen.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/organ_selector_screen.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/possible_conditions_screen.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/possible_conditions_screen.dart';
@ -43,7 +42,6 @@ import '../features/monthly_reports/monthly_reports_repo.dart';
import '../features/monthly_reports/monthly_reports_view_model.dart'; import '../features/monthly_reports/monthly_reports_view_model.dart';
import '../features/qr_parking/qr_parking_view_model.dart'; import '../features/qr_parking/qr_parking_view_model.dart';
import '../presentation/parking/paking_page.dart'; import '../presentation/parking/paking_page.dart';
import '../presentation/smartwatches/smartwatch_instructions_page.dart';
import '../services/error_handler_service.dart'; import '../services/error_handler_service.dart';
class AppRoutes { class AppRoutes {

@ -0,0 +1,78 @@
import 'dart:async';
import 'package:flutter/material.dart';
/// Service to monitor and track app lifecycle state changes
/// This helps detect when app goes to background/foreground
class AppLifecycleService with WidgetsBindingObserver {
final _stateController = StreamController<AppLifecycleState>.broadcast();
/// Stream of app lifecycle state changes
Stream<AppLifecycleState> get appStateStream => _stateController.stream;
AppLifecycleState _currentState = AppLifecycleState.resumed;
/// Current app lifecycle state
AppLifecycleState get currentState => _currentState;
/// Check if app is currently active/visible
bool get isAppActive => _currentState == AppLifecycleState.resumed;
/// Check if app is in background or inactive
bool get isAppInBackground =>
_currentState == AppLifecycleState.paused ||
_currentState == AppLifecycleState.inactive ||
_currentState == AppLifecycleState.detached;
/// Last time app was resumed
DateTime? _lastResumedTime;
DateTime? get lastResumedTime => _lastResumedTime;
/// Last time app went to background
DateTime? _lastPausedTime;
DateTime? get lastPausedTime => _lastPausedTime;
/// Duration app has been in current state
Duration get timeInCurrentState {
final referenceTime = _currentState == AppLifecycleState.resumed
? _lastResumedTime
: _lastPausedTime;
if (referenceTime == null) return Duration.zero;
return DateTime.now().difference(referenceTime);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
_currentState = state;
// Track timing
if (state == AppLifecycleState.resumed) {
_lastResumedTime = DateTime.now();
debugPrint('📱 App RESUMED');
} else if (state == AppLifecycleState.paused) {
_lastPausedTime = DateTime.now();
debugPrint('📱 App PAUSED/BACKGROUNDED');
} else if (state == AppLifecycleState.inactive) {
debugPrint('📱 App INACTIVE');
} else if (state == AppLifecycleState.detached) {
debugPrint('📱 App DETACHED');
}
_stateController.add(state);
}
/// Initialize the lifecycle observer
void initialize() {
WidgetsBinding.instance.addObserver(this);
debugPrint('📱 AppLifecycleService initialized');
}
/// Clean up resources
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_stateController.close();
debugPrint('📱 AppLifecycleService disposed');
}
}

@ -115,7 +115,8 @@ class CollapsingListView extends StatelessWidget {
margin: EdgeInsets.fromLTRB(24.w, 0, 24.w, 0), margin: EdgeInsets.fromLTRB(24.w, 0, 24.w, 0),
child: Transform.flip( child: Transform.flip(
flipX: appState.isArabic(), flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.close_bottom_nav_trans : AppAssets.arrow_back_new, width: 24.h, height: 24.h), child: Utils.buildSvgWithAssets(
icon: isClose ? AppAssets.close_bottom_nav_trans : AppAssets.arrow_back_new, width: 24.h, height: 24.h),
), ),
).onPress(() { ).onPress(() {
if (leadingCallback != null) { if (leadingCallback != null) {
@ -288,7 +289,8 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
return Container( return Container(
// height: (widget.preferredSize.height - _fontSize / 2).h, // height: (widget.preferredSize.height - _fontSize / 2).h,
height: 56.h, height: 56.h,
alignment: isRtl ? (widget.showBack ? Alignment.topRight : Alignment.centerRight) : (widget.showBack ? Alignment.topLeft : Alignment.centerLeft), alignment:
isRtl ? (widget.showBack ? Alignment.topRight : Alignment.centerRight) : (widget.showBack ? Alignment.topLeft : Alignment.centerLeft),
padding: EdgeInsets.fromLTRB(24.w, 0, 24.w, 0), padding: EdgeInsets.fromLTRB(24.w, 0, 24.w, 0),
child: Row( child: Row(
spacing: 4.h, spacing: 4.h,
@ -304,19 +306,38 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
), ),
).expanded, ).expanded,
...[ ...[
if (widget.logout != null) actionButton(context, t, title: LocaleKeys.logout.tr(context: context), icon: AppAssets.logout).onPress(widget.logout!), if (widget.logout != null)
if (widget.report != null) actionButton(context, t, title: LocaleKeys.feedback.tr(context: context), icon: AppAssets.report_icon).onPress(widget.report!), actionButton(context, t, title: LocaleKeys.logout.tr(context: context), icon: AppAssets.logout).onPress(widget.logout!),
if (widget.history != null) actionButton(context, t, title: LocaleKeys.history.tr(context: context), icon: AppAssets.insurance_history_icon).onPress(widget.history!), if (widget.report != null)
if (widget.instructions != null) actionButton(context, t, title: LocaleKeys.instructions.tr(context: context), icon: AppAssets.requests).onPress(widget.instructions!), actionButton(context, t, title: LocaleKeys.feedback.tr(context: context), icon: AppAssets.report_icon).onPress(widget.report!),
if (widget.requests != null) actionButton(context, t, title: LocaleKeys.requests.tr(context: context), icon: AppAssets.insurance_history_icon).onPress(widget.requests!), if (widget.history != null)
if (widget.sendEmail != null) actionButton(context, t, title: LocaleKeys.sendEmail.tr(context: context), icon: AppAssets.email).onPress(widget.sendEmail!), actionButton(context, t, title: LocaleKeys.history.tr(context: context), icon: AppAssets.insurance_history_icon)
if (widget.doctorResponse != null) actionButton(context, t, title: LocaleKeys.doctorResponses.tr(context: context), icon: AppAssets.doctorResponseIcon).onPress(widget.doctorResponse!), .onPress(widget.history!),
if (widget.instructions != null)
actionButton(context, t, title: LocaleKeys.instructions.tr(context: context), icon: AppAssets.requests).onPress(widget.instructions!),
if (widget.requests != null)
actionButton(context, t, title: LocaleKeys.requests.tr(context: context), icon: AppAssets.insurance_history_icon)
.onPress(widget.requests!),
if (widget.sendEmail != null)
actionButton(context, t, title: LocaleKeys.sendEmail.tr(context: context), icon: AppAssets.email).onPress(widget.sendEmail!),
if (widget.doctorResponse != null)
actionButton(context, t, title: LocaleKeys.doctorResponses.tr(context: context), icon: AppAssets.doctorResponseIcon)
.onPress(widget.doctorResponse!),
if (widget.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!), if (widget.search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(widget.search!),
if (widget.aiOverview != null) actionButton(context, t, title: LocaleKeys.aiOverView.tr(context: context), icon: AppAssets.aiOverView, isAiButton: true).onPress(widget.aiOverview!), if (widget.aiOverview != null)
if (widget.downloadReport != null) actionButton(context, t, title: LocaleKeys.downloadReport.tr(context: context), icon: AppAssets.download).onPress(widget.downloadReport!), actionButton(context, t, title: LocaleKeys.aiOverView.tr(context: context), icon: AppAssets.aiOverView, isAiButton: true)
if (widget.viewImage != null) actionButton(context, t, title: LocaleKeys.viewRadiologyImage.tr(context: context), icon: AppAssets.download).onPress(widget.viewImage!), .onPress(widget.aiOverview!),
if (widget.location != null) actionButton(context, t, title: LocaleKeys.sortByLocation.tr(context: context), icon: AppAssets.location).onPress(widget.location!), if (widget.downloadReport != null)
if (widget.downloadInvoice != null) actionButton(context, t, title: LocaleKeys.downloadInvoice.tr(context: context), icon: AppAssets.download).onPress(widget.downloadInvoice!), actionButton(context, t, title: LocaleKeys.downloadReport.tr(context: context), icon: AppAssets.download)
.onPress(widget.downloadReport!),
if (widget.viewImage != null)
actionButton(context, t, title: LocaleKeys.viewRadiologyImage.tr(context: context), icon: AppAssets.download)
.onPress(widget.viewImage!),
if (widget.location != null)
actionButton(context, t, title: LocaleKeys.sortByLocation.tr(context: context), icon: AppAssets.location).onPress(widget.location!),
if (widget.downloadInvoice != null)
actionButton(context, t, title: LocaleKeys.downloadInvoice.tr(context: context), icon: AppAssets.download)
.onPress(widget.downloadInvoice!),
if (widget.trailing != null) widget.trailing!, if (widget.trailing != null) widget.trailing!,
] ]
], ],
@ -330,9 +351,10 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
duration: Duration(milliseconds: 150), duration: Duration(milliseconds: 150),
child: Center( child: Center(
child: Container( child: Container(
height: 40.h, height: isFoldable ? 50.h : 40.h,
padding: EdgeInsets.all(8.w), padding: EdgeInsets.all(8.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 8.r, side: BorderSide(width: 1, color: AppColors.borderGrayColor)), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor, borderRadius: 8.r, side: BorderSide(width: 1, color: AppColors.borderGrayColor)),
child: ShaderMask( child: ShaderMask(
blendMode: BlendMode.srcIn, blendMode: BlendMode.srcIn,
shaderCallback: (bounds) => AppColors.aiLinearGradient.createShader(bounds), shaderCallback: (bounds) => AppColors.aiLinearGradient.createShader(bounds),
@ -365,7 +387,7 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
: AnimatedSize( : AnimatedSize(
duration: Duration(milliseconds: 150), duration: Duration(milliseconds: 150),
child: Container( child: Container(
height: 40.h, height: isFoldable ? 50.h : 40.h,
padding: EdgeInsets.all(8.w), padding: EdgeInsets.all(8.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.secondaryLightRedColor, color: AppColors.secondaryLightRedColor,

Loading…
Cancel
Save