* '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.activity:activity-ktx:1.10.1")
// val room_version = "2.6.1"
// implementation("androidx.room:room-runtime:$room_version")
// annotationProcessor("androidx.room:room-compiler:$room_version")
val room_version = "2.6.1"
implementation("androidx.room:room-runtime:$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.sdp:sdp-android:1.1.0")

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

@ -4,20 +4,21 @@ import 'dart:io';
import 'package:easy_localization/easy_localization.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/app_state.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/generated/locale_keys.g.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/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/widgets/routes/custom_page_route.dart';
import 'package:http/http.dart' as http;
import '../exceptions/api_failure.dart';
abstract class ApiClient {
static final NavigationService _navigationService = getIt.get<NavigationService>();
@ -88,10 +89,14 @@ abstract class ApiClient {
class ApiClientImp implements ApiClient {
final _analytics = getIt<GAnalytics>();
final AppState _appState;
final HttpClientManager _httpClient;
ApiClientImp({
required AppState appState,
}) : _appState = appState;
HttpClientManager? httpClient,
}) : _appState = appState,
_httpClient = httpClient ??
HttpClientManager(lifecycleService: getIt<AppLifecycleService>());
@override
post(
@ -209,7 +214,7 @@ class ApiClientImp implements ApiClient {
// body['PatientOutSA'] = 0;
// body['SessionID'] = "45786230487560q";
//VIP Patient: 1181868
//VIP Patient: 1181868body:
// body['IdentificationNo'] = "2235558844";
// body['MobileNo'] = "966533147722";
@ -243,7 +248,12 @@ class ApiClientImp implements ApiClient {
http.Response response;
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) {
final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr();
onFailure(message, -1, failureType: ConnectivityFailure(message));
@ -445,8 +455,8 @@ class ApiClientImp implements ApiClient {
if (await Utils.checkConnection(bypassConnectionCheck: true)) {
http.Response response;
try {
response = await http.get(
Uri.parse(url.trim()),
response = await _httpClient.get(
uri: Uri.parse(url.trim()),
headers: apiHeaders ?? {'Content-Type': 'application/json', 'Accept': 'application/json'},
);
} 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 rcBaseUrl = 'https://rc.hmg.com/'; // dRC API URL PROD
static String hmgPharmacyApiBaseUrl = 'https://hmgpharmacyapi.hmg.com/'; // dRC API URL PROD
static String symptomsCheckerApi = '${hmgPharmacyApiBaseUrl}symptomsapi/api/SymptomChecker'; // dRC API URL PROD
static String hmgPharmacyApiBaseUrl = 'https://hmgpharmacyapi.hmg.com/'; // symptoms 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 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 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 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=';
rcBaseUrl = 'https://rc.hmg.com/';
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=";
break;
case AppEnvironmentTypeEnum.dev:
@ -59,6 +72,10 @@ class ApiConsts {
rcBaseUrl = 'https://rc.hmg.com/uat/';
QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail";
CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=";
symptomsCheckerApi = symptomsCheckerApiUAT;
symptomsCheckerUsername = 'guest_user';
symptomsCheckerPassword = '123456';
break;
case AppEnvironmentTypeEnum.uat:
baseUrl = "https://uat.hmgwebservices.com/";
@ -71,6 +88,10 @@ class ApiConsts {
rcBaseUrl = 'https://rc.hmg.com/uat/';
QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail";
CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=";
symptomsCheckerApi = symptomsCheckerApiUAT;
symptomsCheckerUsername = 'guest_user';
symptomsCheckerPassword = '123456';
break;
case AppEnvironmentTypeEnum.preProd:
baseUrl = "https://webservices.hmg.com/";
@ -83,6 +104,10 @@ class ApiConsts {
rcBaseUrl = 'https://rc.hmg.com/';
QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail";
CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=";
symptomsCheckerApi = symptomsCheckerApiUAT;
symptomsCheckerUsername = 'guest_user';
symptomsCheckerPassword = '123456';
break;
case AppEnvironmentTypeEnum.qa:
baseUrl = "https://uat.hmgwebservices.com/";
@ -95,6 +120,10 @@ class ApiConsts {
rcBaseUrl = 'https://rc.hmg.com/uat/';
QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail";
CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=";
symptomsCheckerApi = symptomsCheckerApiUAT;
symptomsCheckerUsername = 'guest_user';
symptomsCheckerPassword = '123456';
break;
case AppEnvironmentTypeEnum.staging:
baseUrl = "https://uat.hmgwebservices.com/";
@ -107,6 +136,10 @@ class ApiConsts {
rcBaseUrl = 'https://rc.hmg.com/uat/';
QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail";
CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=";
symptomsCheckerApi = symptomsCheckerApiUAT;
symptomsCheckerUsername = 'guest_user';
symptomsCheckerPassword = '123456';
break;
}
}
@ -189,7 +222,6 @@ class ApiConsts {
static final createEReferral = "Services/Patients.svc/REST/CreateEReferral";
static final getEReferrals = "Services/Patients.svc/REST/GetEReferrals";
//WATER CONSUMPTION
static String h2oGetUserProgress = "Services/H2ORemainder.svc/REST/H2O_GetUserProgress";
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 getPatientBloodAgreement = "Services/PatientVarification.svc/REST/CheckUserAgreementForBloodDonation";
static String getPatientBloodTypeNew = "Services/Patients.svc/REST/HIS_GetPatientBloodType_New";
// static String getAiOverViewLabOrders = "Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_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 values for Api ****************
static final double appVersionID = 20.9;
static final double appVersionID = 21.0;
// static final double appVersionID = 50.7;
static final int appChannelId = 3;
@ -806,7 +839,8 @@ var GET_CUSTOMER_INFO = "VerifyCustomer";
//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_PRODUCTS = 'offerproducts/';
var GET_CATEGORISE_PARENT = 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id=';

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

@ -1,4 +1,3 @@
import 'dart:developer';
import 'dart:math' as math;
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
bool get _isFoldable {
double aspectRatio = _screenWidth / _screenHeight;
// Foldable devices typically have aspect ratios close to 1:1 when unfolded
return (aspectRatio > 0.9 && aspectRatio < 1.1) && (_screenWidth > 700 || _screenHeight > 700);
double shorterSide = _screenWidth < _screenHeight ? _screenWidth : _screenHeight;
// 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
@ -38,8 +43,13 @@ extension ResponsiveExtension on num {
// Enhanced clamping for different device types
double clamp;
if (SizeUtils.deviceType == DeviceType.tablet || _isFoldable) {
if (SizeUtils.deviceType != DeviceType.tablet && _isFoldable) {
// 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 {
// Original logic for phones
clamp = (aspectRatio > 1.3 || aspectRatio < 0.77) ? 1.6 : 1.2;
@ -225,10 +235,16 @@ class SizeUtils {
deviceType = DeviceType.mobile;
}
log("longerSide: $longerSide");
log("shorterSide: $shorterSide");
log("isTablet: $isTablet");
log("isFoldable: $isFoldable");
debugPrint("============ Device Detection ============");
debugPrint("longerSide: $longerSide");
debugPrint("shorterSide: $shorterSide");
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 {
double aspectRatio = SizeUtils.width / SizeUtils.height;
// Foldable devices typically have aspect ratios close to 1:1 when unfolded
return (aspectRatio > 0.9 && aspectRatio < 1.1) && (SizeUtils.width > 700 || SizeUtils.height > 700);
double shorterSide = SizeUtils.width < SizeUtils.height ? SizeUtils.width : SizeUtils.height;
// 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,
"UsingInDoctorApp": false,
"IsHMC": false
},{
},
{
"Desciption": "Jeddah Fayhaa Hospital",
"DesciptionN": "مستشفى جدة الفيحاء",
"ID": 3, // Campus ID
@ -539,8 +540,8 @@ class Utils {
),
],
)
: showOkButton?
Row(
: showOkButton
? Row(
children: [
Expanded(
child: CustomButton(
@ -833,12 +834,16 @@ class Utils {
final iconH = height ?? 24.h;
final iconW = width ?? 24.w;
return Container(
width: iconW, height: iconH,
width: iconW,
height: iconH,
decoration: BoxDecoration(
border: border != null ? Border.all(color: AppColors.whiteColor, width: border) : 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() {
return Row(
spacing: 6.w,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
spacing: 5.w,
children: [
Image.asset(AppAssets.mada, width: 35.h, height: 35.h),
Image.asset(
@ -1025,7 +1029,6 @@ class Utils {
isHMC: hospital.isHMC);
}
static HospitalsModel? convertToHospitalsModel(PatientDoctorAppointmentList? item) {
if (item == null) return null;
return HospitalsModel(

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

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

@ -944,11 +944,11 @@ class HmgServicesRepoImp implements HmgServicesRepo {
final vitalSign = VitalSignResModel.fromJson(vitalSignJson);
// Debug logging for blood pressure
print('=== Repository Blood Pressure Check ===');
print('bloodPressureHigher: ${vitalSign.bloodPressureHigher}');
print('bloodPressureLower: ${vitalSign.bloodPressureLower}');
print('weightKg: ${vitalSign.weightKg}');
print('heightCm: ${vitalSign.heightCm}');
// print('=== Repository Blood Pressure Check ===');
// print('bloodPressureHigher: ${vitalSign.bloodPressureHigher}');
// print('bloodPressureLower: ${vitalSign.bloodPressureLower}');
// print('weightKg: ${vitalSign.weightKg}');
// print('heightCm: ${vitalSign.heightCm}');
// Check if the record has at least one valid vital sign measurement
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:intl/intl.dart';
import 'model/Vitals.dart';
import 'model/vitals_data_model.dart';
enum Durations {
daily("daily"),

@ -1,18 +1,17 @@
import 'package:flutter/foundation.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/dependencies.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/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 '../../core/common_models/data_points.dart';
import '../../core/dependencies.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';
import 'health_data_transformations.dart';
import 'model/vitals_data_model.dart';
class HealthProvider with ChangeNotifier {
final HealthService _healthService = HealthService();
@ -23,6 +22,7 @@ class HealthProvider with ChangeNotifier {
int selectedTabIndex = 0;
SmartWatchTypes? selectedWatchType;
String selectedWatchURL = 'assets/images/png/smartwatches/apple-watch-5.jpg';
HealthDataTransformation healthDataTransformation = HealthDataTransformation();
@ -90,7 +90,7 @@ class HealthProvider with ChangeNotifier {
healthData[type] = data;
notifyListeners();
} catch (e) {
print('Error refreshing metric $type: $e');
debugPrint('Error refreshing metric $type: $e');
}
}
@ -129,14 +129,12 @@ class HealthProvider with ChangeNotifier {
await getVitals();
// LoaderBottomSheet.hideLoader();
// await Future.delayed(Duration(seconds: 5));
getIt.get<NavigationService>().pushPage(page: SmartWatchActivity());
print('Device initialized successfully');
getIt.get<NavigationService>().pushPage(page: SmartWatchesHealthDataScreen());
}
notifyListeners();
}
Future<void> getVitals() async {
final result = await _healthService.getVitals();
vitals = result;
LoaderBottomSheet.hideLoader();
@ -186,15 +184,17 @@ class HealthProvider with ChangeNotifier {
}
selectedData = yearly = healthDataTransformation.transformVitalsToDataPoints(vitals!, Durations.yearly.value, selectedSection);
break;
default:
{}
;
}
notifyListeners();
}
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) {
@ -243,7 +243,6 @@ class HealthProvider with ChangeNotifier {
count++;
}
});
print("total count is $count and total is $total");
averageValue = count > 0 ? total / count : null;
notifyListeners();
}
@ -261,7 +260,7 @@ class HealthProvider with ChangeNotifier {
String firstNonEmptyValue(List<Vitals> dataPoints) {
try {
return dataPoints.firstWhere((dp) => dp.value != null && dp.value!.trim().isNotEmpty).value;
return dataPoints.firstWhere((dp) => dp.value.trim().isNotEmpty).value;
} catch (e) {
return "0"; // no non-empty value found
}

@ -5,7 +5,7 @@ import 'dart:io';
import 'package:health/health.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/watch_helper.dart';
import 'package:permission_handler/permission_handler.dart';

@ -16,11 +16,6 @@ class Vitals {
unitOfMeasure: map['uom'] ?? "",
);
}
toString(){
return "{\"value\": \"$value\", \"timeStamp\": \"$timestamp\", \"uom\": \"$unitOfMeasure\"}";
}
}
class VitalsWRTType {
@ -38,8 +33,14 @@ class VitalsWRTType {
double maxBloodOxygen = 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) {
List<Vitals> activity = [];
@ -86,7 +87,14 @@ class VitalsWRTType {
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() {

@ -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:permission_handler/permission_handler.dart';
import '../model/Vitals.dart';
import '../model/vitals_data_model.dart';
class HealthConnectHelper extends WatchHelper {
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/common_models/generic_api_model.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/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/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/triage_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
@ -17,6 +19,7 @@ abstract class SymptomsCheckerRepo {
Future<Either<Failure, GenericApiModel<SymptomsUserDetailsResponseModel>>> getUserDetails({
required String userName,
required String password,
String? fileNo,
});
Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>> getBodySymptomsByName({
@ -58,6 +61,11 @@ abstract class SymptomsCheckerRepo {
required String language,
required String userSessionToken,
});
Future<Either<Failure, GenericApiModel<ScheduleAppointmentResponseModel>>> saveAppointmentDetailsForSymptomsChecker({
required ScheduleAppointmentRequestModel request,
required String userSessionToken,
});
}
class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
@ -70,8 +78,17 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
Future<Either<Failure, GenericApiModel<SymptomsUserDetailsResponseModel>>> getUserDetails({
required String userName,
required String password,
String? fileNo,
}) 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 {
GenericApiModel<SymptomsUserDetailsResponseModel>? apiResponse;
@ -409,7 +426,6 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
'Content-Type': 'application/json',
'Authorization': 'Bearer $userSessionToken',
};
Map<String, dynamic> body = {};
try {
GenericApiModel<List<GetClinicDetailsResponseModel>>? apiResponse;
@ -456,4 +472,61 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
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:developer';
import 'package:flutter/material.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/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/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/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/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/triage_response_model.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 isSuggestionsLoading = 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
SymptomsUserDetailsResponseModel? symptomsUserDetailsResponseModel;
@ -76,6 +83,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
final List<Map<String, String>> _triageEvidenceList = []; // Store triage evidence with proper format
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
final Set<String> _selectedRiskFactorIds = {};
@ -85,6 +96,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
// Selected symptoms tracking (organId -> Set of symptom IDs)
final Map<String, Set<String>> _selectedSymptomsByOrgan = {};
// Symptom search/filter state
String _symptomSearchQuery = '';
List<OrganSymptomResult> _filteredOrganSymptomsResults = [];
// User Info Flow State
int _userInfoCurrentPage = 0;
bool _isSinglePageEditMode = false; // Track if editing single page or full flow
@ -170,12 +185,43 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
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
bool get areAllTriageItemsAnswered {
if (currentTriageQuestion?.items == null || currentTriageQuestion!.items!.isEmpty) {
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
for (var item in currentTriageQuestion!.items!) {
if (item.id != null && !_selectedTriageChoicesByItemId.containsKey(item.id)) {
@ -207,6 +253,28 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
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 {
return _selectedSymptomsByOrgan.values.fold(0, (sum, symptomIds) => sum + symptomIds.length);
}
@ -427,6 +495,51 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
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
/// Toggle risk factor selection
@ -846,7 +959,22 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/// Select a choice for a specific item (for multi-item questions)
void selectTriageChoiceForItem(String itemId, int 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();
}
@ -854,6 +982,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
void resetTriageChoice() {
_selectedTriageChoiceIndex = null;
_selectedTriageChoicesByItemId.clear();
_selectedSingleItemId = null;
_selectedSingleChoiceIndex = null;
_triageQuestionCount++; // Increment question count
notifyListeners();
}
@ -889,15 +1019,20 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
_selectedTriageChoicesByItemId.clear();
_triageQuestionCount = 0; // Reset question count
_currentZoomScale = 1.0; // Reset zoom scale
_symptomSearchQuery = ''; // Reset search query
_filteredOrganSymptomsResults.clear(); // Clear filtered results
bodySymptomResponse = null;
riskFactorsResponse = null;
suggestionsResponse = null;
triageDataDetails = null;
isTriageDiagnosisLoading = false;
_selectedTriageChoiceIndex = null;
_selectedSingleItemId = null;
_selectedSingleChoiceIndex = null;
_isBottomSheetExpanded = false;
_tooltipTimer?.cancel();
_tooltipOrganId = null;
isBookingFromSymptomsChecker = false; // Reset booking flag
// Reset user info flow
_userInfoCurrentPage = 0;
_isSinglePageEditMode = false;
@ -911,6 +1046,26 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
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
/// Set current page in user info flow
@ -1018,12 +1173,17 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
Future<void> getSymptomsUserDetails({
required String userName,
required String password,
String? fileNo,
Function()? onSuccess,
Function(String)? onError,
}) async {
isBodySymptomsLoading = true;
notifyListeners();
final result = await symptomsCheckerRepo.getUserDetails(userName: userName, password: password);
final result = await symptomsCheckerRepo.getUserDetails(
userName: userName,
password: password,
fileNo: fileNo,
);
result.fold(
(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
void dispose() {
_tooltipTimer?.cancel();

@ -1834,5 +1834,6 @@ abstract class LocaleKeys {
static const liveCareNotificationPermissionsMessage = 'liveCareNotificationPermissionsMessage';
static const weatherIndicators = 'weatherIndicators';
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/features/health_trackers/health_trackers_view_model.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/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/app_theme.dart';
@ -88,7 +90,9 @@ Future<void> callAppStateInitializations() async {
appState.setDeviceTypeID = deviceTypeId;
// Pass all uncaught "fatal" errors from the framework to Crashlytics
if (!kDebugMode) {
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
}
// Pass all uncaught asynchronous errors that aren't handled by the Flutter framework to Crashlytics
PlatformDispatcher.instance.onError = (error, stack) {
@ -121,6 +125,9 @@ Future<void> callInitializations() async {
HttpOverrides.global = MyHttpOverrides();
await callAppStateInitializations();
// Initialize App Lifecycle Service to monitor background/foreground transitions
getIt.get<AppLifecycleService>().initialize();
// Restore persisted dark-mode preference before the first frame.
getIt.get<ProfileSettingsViewModel>().loadDarkMode();
}

@ -262,7 +262,10 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
Expanded(
child: CollapsingListView(
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.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel);
@ -280,8 +283,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
children: [
AppointmentDoctorCard(
// renderWidgetForERDisplay: ((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ||
renderWidgetForERDisplay:
((widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)),
renderWidgetForERDisplay: ((widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) ||
!Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)),
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onAskDoctorTap: () async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context));
@ -361,10 +364,6 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
isFullScreen: false,
isCloseButtonVisible: true,
);
// var isEventAddedOrRemoved = await CalenderUtilsNew.instance.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", );
// setState(() {
// myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
// });
},
onRescheduleTap: () async {
openDoctorScheduleCalendar();
@ -416,14 +415,17 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
Row(
mainAxisSize: MainAxisSize.max,
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),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4.h,
children: [
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(),
@ -438,14 +440,17 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
inactiveCircleColor: AppColors.greyTextColor,
activeIconColor: AppColors.bgGreenColor,
inactiveIconColor: AppColors.greyTextColor,
activeIcon: 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),
activeIcon:
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 {
CalenderUtilsNew calender = CalenderUtilsNew.instance;
bool isEventAddedOrRemoved = false;
if (newValue == true) {
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
bool permissionGranted = await BottomSheetUtils().showReminderBottomSheet(
@ -460,7 +465,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
"${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}",
onSuccess: () {
setState(() {
myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel);
myAppointmentsViewModel.setAppointmentReminder(
newValue, widget.patientAppointmentHistoryResponseModel);
});
},
isMultiAllowed: true,
@ -470,16 +476,17 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
"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),
scheduleDateTime:
DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate),
eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
location: '',
reminderMinutes: selectedIndex);
setState(() {
myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
myAppointmentsViewModel.setAppointmentReminder(
isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel);
});
},
isForAppointment: true
);
isForAppointment: true);
// If permission was not granted, revert the switch back to OFF
if (!permissionGranted) {
@ -490,72 +497,17 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
id: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}",
);
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)
],
),
),
SizedBox(height: 16.h),
!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)
? Column(
@ -581,53 +533,15 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
LocaleKeys.appointmentStatus.tr(context: context).toText16(isBold: true),
SizedBox(height: 4.h),
(!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel)
? LocaleKeys.notConfirmed.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true)
: LocaleKeys.confirmed.tr(context: context).toText12(color: AppColors.successColor, isBold: true)),
? LocaleKeys.notConfirmed
.tr(context: context)
.toText12(color: AppColors.primaryRedColor, isBold: true)
: LocaleKeys.confirmed
.tr(context: context)
.toText12(color: AppColors.successColor, isBold: true)),
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
@ -640,7 +554,9 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
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(
children: [
ClipRRect(
SizedBox(
width: double.infinity,
child: ClipRRect(
clipBehavior: Clip.hardEdge,
borderRadius: BorderRadius.circular(24.r),
// Todo: what is this???? Api Key??? 😲
child: Image.network(
"https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=${(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.contain,
fit: BoxFit.cover,
width: double.infinity,
),
),
),
Positioned(
@ -663,7 +583,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
width: MediaQuery.of(context).size.width - 85.w,
child: CustomButton(
onPressed: () async {
if (widget.patientAppointmentHistoryResponseModel.projectID == 130 || widget.patientAppointmentHistoryResponseModel.projectID == 120) {
if (widget.patientAppointmentHistoryResponseModel.projectID == 130 ||
widget.patientAppointmentHistoryResponseModel.projectID == 120) {
showDirectionsBottomSheet();
} else {
await MapLauncher.showMarker(
@ -683,7 +604,9 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
},
text: LocaleKeys.getDirections.tr(context: context),
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,
fontSize: 14.f,
fontWeight: FontWeight.w600,
@ -723,148 +646,6 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
);
}),
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),
],
)
@ -875,7 +656,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
crossAxisCount: 3,
crossAxisSpacing: 16.h,
mainAxisSpacing: 16.w,
mainAxisExtent: 115.h,
childAspectRatio: isFoldable ? 1.2 : (isTablet ? 1.1 : 0.78),
),
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
@ -974,7 +755,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
);
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionDetailPage(isFromAppointments: true, prescriptionsResponseModel: patientPrescriptionsResponseModel),
page: PrescriptionDetailPage(
isFromAppointments: true, prescriptionsResponseModel: patientPrescriptionsResponseModel),
),
);
} 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),
),
@ -1315,7 +890,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.patientAppointmentHistoryResponseModel.nextAction == 15 || widget.patientAppointmentHistoryResponseModel.nextAction == 20)
if (widget.patientAppointmentHistoryResponseModel.nextAction == 15 ||
widget.patientAppointmentHistoryResponseModel.nextAction == 20)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -1323,7 +899,10 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
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),
],
),
@ -1331,22 +910,24 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(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),
Expanded(
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),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: 200.h,
child: Utils.getPaymentMethods(),
),
Utils.getPaymentMethods(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
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),
],
),
@ -1384,7 +965,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
handleAppointmentNextAction(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,
fontSize: 16.f,
fontWeight: FontWeight.w600,
@ -1424,7 +1006,10 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
text: LocaleKeys.insideHospital.tr(context: context),
onPressed: () {
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());
},
backgroundColor: AppColors.primaryRedColor,
@ -1443,12 +1028,14 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
Navigator.pop(context);
await MapLauncher.showMarker(
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",
).catchError((err) {
MapLauncher.showMarker(
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",
);
});
@ -1482,7 +1069,9 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
Permission.bluetoothScan,
Permission.activityRecognition,
].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();
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
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.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
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();
case 15:
@ -1565,7 +1162,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
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(
height: 12,
),

@ -1,4 +1,6 @@
import 'dart:async';
import 'dart:ui' as ui;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -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/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_payment_page.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/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/loader/bottomsheet_loader.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';
class AppointmentCard extends StatefulWidget {
@ -42,6 +43,7 @@ class AppointmentCard extends StatefulWidget {
final ContactUsViewModel? contactUsViewModel;
final BookAppointmentsViewModel bookAppointmentsViewModel;
final bool isForRate;
// bool isAppointmentWithin4Hours = false;
const AppointmentCard(
@ -189,20 +191,28 @@ class _AppointmentCardState extends State<AppointmentCard> {
runSpacing: 6.h,
children: [
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),
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),
textColor: widget.isLoading ? AppColors.textColor : (isLiveCare ? Colors.white : AppColors.textColor),
).toShimmer2(isShow: widget.isLoading),
AppCustomChipWidget(
labelText:
widget.isLoading ? 'OutPatient' : (appState.isArabic() ? widget.patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! : widget.patientAppointmentHistoryResponseModel.isInOutPatientDescription!),
labelText: widget.isLoading
? 'OutPatient'
: (appState.isArabic()
? widget.patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN!
: widget.patientAppointmentHistoryResponseModel.isInOutPatientDescription!),
backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.1),
textColor: AppColors.warningColorYellow,
).toShimmer2(isShow: widget.isLoading),
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),
textColor: AppColors.successColor,
).toShimmer2(isShow: widget.isLoading),
@ -218,7 +228,9 @@ class _AppointmentCardState extends State<AppointmentCard> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
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,
height: 63.h,
fit: BoxFit.cover,
@ -239,11 +251,13 @@ class _AppointmentCardState extends State<AppointmentCard> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
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),
(isFoldable || isTablet)
? "${widget.patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText9(isBold: true, color: AppColors.textColor, isEnglishOnly: true)
: "${widget.patientAppointmentHistoryResponseModel.decimalDoctorRate ?? "0.0"}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true),
? "${widget.patientAppointmentHistoryResponseModel.decimalDoctorRate}"
.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),
@ -256,12 +270,17 @@ class _AppointmentCardState extends State<AppointmentCard> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(widget.isLoading ? 'Dr' : "${widget.patientAppointmentHistoryResponseModel.doctorTitle}").toText16(isBold: true, maxlines: 1),
(widget.isLoading ? 'John Doe' : " ${widget.patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(20)}")
.toText16(isBold: true, maxlines: 1, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")),
Expanded(
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),
(widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty)
(widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null &&
widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty)
? Image.network(
widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png",
width: 20.h,
@ -275,7 +294,7 @@ class _AppointmentCardState extends State<AppointmentCard> {
Wrap(
direction: Axis.horizontal,
spacing: 6.h,
runSpacing: 4.h,
runSpacing: 6.h,
children: [
AppCustomChipWidget(
labelText: widget.isLoading
@ -498,8 +517,10 @@ class _AppointmentCardState extends State<AppointmentCard> {
: 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),
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: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w600,
@ -620,7 +641,8 @@ class _AppointmentCardState extends State<AppointmentCard> {
);
} else {
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)
.push(
@ -708,14 +730,15 @@ class _AppointmentCardState extends State<AppointmentCard> {
children: [
Lottie.asset(AppAnimations.warningAnimation,
repeat: false, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill),
SizedBox(height: 12,),
SizedBox(
height: 12,
),
LocaleKeys.upcomingPaymentPending.tr(context: context).toText14(
color: AppColors.textColor,
isCenter: true,
),
SizedBox(height: 24.h),
// Countdown Timer - DD : HH : MM : SS format with labels
Directionality(
textDirection: ui.TextDirection.ltr,
@ -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:flutter/material.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/chip/app_custom_chip_widget.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/routes/custom_page_route.dart';
@ -67,8 +67,8 @@ class AppointmentDoctorCard extends StatelessWidget {
Transform.translate(
offset: Offset(0.0, -20.h),
child: Container(
width: 40.w,
height: 40.h,
width: 50.h,
height: 50.h,
decoration: BoxDecoration(
color: AppColors.whiteColor,
shape: BoxShape.circle, // Makes the container circular
@ -80,9 +80,10 @@ class AppointmentDoctorCard extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
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),
"${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),
@ -97,9 +98,14 @@ class AppointmentDoctorCard extends StatelessWidget {
children: [
Row(
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),
(patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty)
(patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null &&
patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty)
? Image.network(
patientAppointmentHistoryResponseModel.doctorNationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png",
width: 20.h,
@ -130,7 +136,8 @@ class AppointmentDoctorCard extends StatelessWidget {
child: AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w),
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),
false,
)}"
@ -139,10 +146,15 @@ class AppointmentDoctorCard extends StatelessWidget {
),
AppCustomChipWidget(
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,
labelText: patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context),
backgroundColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor,
labelText: patientAppointmentHistoryResponseModel.isLiveCareAppointment!
? LocaleKeys.livecare.tr(context: context)
: LocaleKeys.walkin.tr(context: context),
backgroundColor:
!patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor,
textColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : Colors.white,
),
],
@ -150,10 +162,12 @@ class AppointmentDoctorCard extends StatelessWidget {
],
),
),
patientAppointmentHistoryResponseModel.isLiveCareAppointment! || patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! ==false || patientAppointmentHistoryResponseModel.isActiveDoctor! == false
patientAppointmentHistoryResponseModel.isLiveCareAppointment! ||
patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! == false ||
patientAppointmentHistoryResponseModel.isActiveDoctor! == false
? 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();
selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID;
selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL;
@ -197,8 +211,7 @@ class AppointmentDoctorCard extends StatelessWidget {
AppointmentType.isArrived(patientAppointmentHistoryResponseModel),
),
),
if (timerWidget != null)
timerWidget ?? SizedBox()
if (timerWidget != null) timerWidget ?? SizedBox()
],
),
),

@ -1,3 +1,5 @@
import 'dart:ui' as ui;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -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/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
import 'dart:ui' as ui;
class SavedLogin extends StatefulWidget {
const SavedLogin({super.key});
@ -33,6 +34,7 @@ class _SavedLogin extends State<SavedLogin> {
late AuthenticationViewModel authVm;
late AppState appState;
bool? isOther;
@override
void initState() {
authVm = context.read<AuthenticationViewModel>();
@ -90,9 +92,9 @@ class _SavedLogin extends State<SavedLogin> {
: SizedBox(),
SizedBox(height: 24.h),
Container(
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, isCustomShadow: [
decoration: RoundedRectangleBorder()
.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(
@ -105,7 +107,9 @@ class _SavedLogin extends State<SavedLogin> {
textDirection: ui.TextDirection.ltr,
child: appState.getSelectDeviceByImeiRespModelElement != 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)
: SizedBox(),
@ -115,10 +119,14 @@ class _SavedLogin extends State<SavedLogin> {
? Container(
margin: EdgeInsets.all(16.h),
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,
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(),
// Main login button - for isOther with SMS, show WhatsApp, otherwise keep original login type
CustomButton(
@ -126,7 +134,6 @@ class _SavedLogin extends State<SavedLogin> {
? "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}"
: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}",
onPressed: () {
if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) {
authVm.loginWithFingerPrintFace(() {});
} else {
@ -147,7 +154,8 @@ class _SavedLogin extends State<SavedLogin> {
height: 40.h,
padding: EdgeInsets.symmetric(vertical: 10.h),
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,7 +167,10 @@ class _SavedLogin extends State<SavedLogin> {
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Text(
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),
@ -240,6 +251,7 @@ class _SavedLogin extends State<SavedLogin> {
}),
);
},
height: isFoldable ? 50.h : 40.h,
backgroundColor: AppColors.whiteColor,
borderColor: AppColors.borderOnlyColor,
textColor: AppColors.textColor,

@ -1,6 +1,5 @@
import 'package:easy_localization/easy_localization.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_state.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/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/widgets/appbar/collapsing_list_view.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/chip/app_custom_chip_widget.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,
isActive: viewModel.isFavouriteDoctor,
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
getIt.get<MyAppointmentsViewModel>().refreshFavouriteDoctors();
},
@ -75,7 +76,7 @@ class DoctorProfilePage extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// SizedBox(height: 24.h),
isFoldable ? SizedBox(height: 24.h) : SizedBox.shrink(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -87,18 +88,21 @@ class DoctorProfilePage extends StatelessWidget {
width: 63.h,
height: 63.h,
fit: BoxFit.cover,
).circle(100),
).circle(100.r),
SizedBox(width: 8.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 220.h,
child: ("${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorTitleForProfile} ${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName}")
width: isFoldable ? 250.w : 220.w,
child:
("${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorTitleForProfile} ${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName}")
.toString()
.toText24(isBold: true),
),
(bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.isNotEmpty ? bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.first : "")
(bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.isNotEmpty
? bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.first
: "")
.toString()
.toText18(isBold: true, color: AppColors.primaryRedColor),
],
@ -137,12 +141,16 @@ class DoctorProfilePage extends StatelessWidget {
children: [
Column(
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),
LocaleKeys.ratings.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor),
bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate
.toString()
.toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"),
bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate.toString().toText16(
isBold: true,
color: AppColors.textColor,
isUnderLine: true,
decorationColor: AppColors.textColor,
fontFamily: "Poppins"),
],
).onPress(() {
bookAppointmentsViewModel.getDoctorRatingDetails();
@ -158,11 +166,18 @@ class DoctorProfilePage extends StatelessWidget {
SizedBox(width: 36.w),
Column(
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),
LocaleKeys.reviews.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor),
NumberFormat.decimalPattern().format(bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate ?? 0)
.toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"),
NumberFormat.decimalPattern()
.format(bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate ?? 0)
.toText16(
isBold: true,
color: AppColors.textColor,
isUnderLine: true,
decorationColor: AppColors.textColor,
fontFamily: "Poppins"),
],
).onPress(() {
bookAppointmentsViewModel.getDoctorRatingDetails();
@ -182,14 +197,17 @@ class DoctorProfilePage extends StatelessWidget {
SizedBox(height: 16.h),
LocaleKeys.information.tr(context: context).toText14(isBold: true, color: AppColors.textColor),
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),
],
).paddingSymmetrical(24.h, 0.h),
),
),
),
isDoctorAllowedToBook ? Container(
isDoctorAllowedToBook
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
@ -202,7 +220,8 @@ class DoctorProfilePage extends StatelessWidget {
bookAppointmentsViewModel.selectedDoctor.specialityN = bookAppointmentsViewModel.doctorsProfileResponseModel.specialty;
bookAppointmentsViewModel.selectedDoctor.name = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName;
bookAppointmentsViewModel.selectedDoctor.doctorImageURL = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorImageURL;
bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL = bookAppointmentsViewModel.doctorsProfileResponseModel.nationalityFlagURL;
bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL =
bookAppointmentsViewModel.doctorsProfileResponseModel.nationalityFlagURL;
bookAppointmentsViewModel.selectedDoctor.clinicName = bookAppointmentsViewModel.doctorsProfileResponseModel.clinicDescription;
bookAppointmentsViewModel.selectedDoctor.projectName = bookAppointmentsViewModel.doctorsProfileResponseModel.projectName;
@ -267,7 +286,8 @@ class DoctorProfilePage extends StatelessWidget {
iconColor: Colors.white,
iconSize: 20.h,
).paddingSymmetrical(24.h, 24.h),
) : SizedBox.shrink(),
)
: SizedBox.shrink(),
],
),
);

@ -75,7 +75,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
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),
Wrap(
direction: Axis.horizontal,
@ -88,8 +89,11 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
"${appState.getAuthenticatedUser()!.age} ".toText10(color: AppColors.blackColor, isEnglishOnly: true),
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)}"),
],
),
],
@ -124,7 +128,12 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
children: [
Row(
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),
getLiveCareType(context, immediateLiveCareVM.liveCareSelectedCallType).toText16(isBold: true),
],
@ -136,7 +145,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
),
),
).onPress(() {
showCommonBottomSheetWithoutHeight(context, child: SelectLiveCareCallType(immediateLiveCareViewModel: immediateLiveCareVM), callBackFunc: () async {
showCommonBottomSheetWithoutHeight(context, child: SelectLiveCareCallType(immediateLiveCareViewModel: immediateLiveCareVM),
callBackFunc: () async {
debugPrint("Selected Call Type: ${immediateLiveCareVM.liveCareSelectedCallType}");
}, title: LocaleKeys.selectLiveCareCallType.tr(context: context), isCloseButtonVisible: true, isFullScreen: false);
});
@ -169,11 +179,15 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
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(
text: LocaleKeys.updateInsurance.tr(context: context),
onPressed: () {
Navigator.of(context).push(
Navigator.of(context)
.push(
CustomPageRoute(
page: InsuranceHomePage(),
),
@ -214,7 +228,10 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
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" ||
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ريال").toLowerCase() == "ريال"),
],
@ -224,7 +241,10 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
children: [
LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor),
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" ||
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ريال").toLowerCase() == "ريال")),
],
@ -233,13 +253,17 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(width: 200.h, child: Utils.getPaymentMethods()),
Utils.getPaymentAmountWithSymbol((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total ?? "0.0").toText24(isBold: true, isEnglishOnly: true), AppColors.blackColor, 17,
Utils.getPaymentMethods(),
Utils.getPaymentAmountWithSymbol(
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total ?? "0.0").toText24(isBold: true, isEnglishOnly: true),
AppColors.blackColor,
17,
isSaudiCurrency: ((immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "sar").toLowerCase() == "sar" ||
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ريال").toLowerCase() == "ريال")),
],
).paddingSymmetrical(24.h, 0.h),
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" || immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0")
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" ||
immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0")
// (true)
? CustomButton(
text: LocaleKeys.confirmLiveCare.tr(context: context),
@ -248,7 +272,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
if (val) {
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();
LoaderBottomSheet.hideLoader();
if (immediateLiveCareVM.patientHasPendingLiveCareRequest) {
@ -425,8 +450,9 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
final newlyPermanent = missing.where((p) => (newStatuses[p]?.isPermanentlyDenied ?? false) || (newStatuses[p]?.isRestricted ?? false)).toList();
if (newlyPermanent.isNotEmpty) {
final names = newlyPermanent.map((p) => LiveCarePermissionService.instance.friendlyName(p)).join(' and ');
final message =
newlyPermanent.length == 1 ? '$names permission is permanently denied. Open app settings to allow it.' : '$names permissions are permanently denied. Open app settings to allow them.';
final message = newlyPermanent.length == 1
? '$names permission is permanently denied. Open app settings to allow it.'
: '$names permissions are permanently denied. Open app settings to allow them.';
await LiveCarePermissionService.instance.showOpenSettingsDialog(
context,
title: "Permissions Required",

@ -230,7 +230,7 @@ class _ImmediateLiveCarePendingRequestPageState extends State<ImmediateLiveCareP
workGroup: "Live_Care_Chat",
onSuccess: (response) {
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");
Uri uri = Uri.parse(chatURL);
launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: "");

@ -1,10 +1,11 @@
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/dependencies.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/utils.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/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/symptoms_checker/symptoms_checker_view_model.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/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/widgets/appbar/collapsing_list_view.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/common_bottom_sheet.dart';
@ -37,6 +39,7 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
late BookAppointmentsViewModel bookAppointmentsViewModel;
late AuthenticationViewModel authVM;
late MyAppointmentsViewModel myAppointmentsViewModel;
late SymptomsCheckerViewModel symptomsCheckerViewModel;
@override
Widget build(BuildContext context) {
@ -44,6 +47,7 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false);
authVM = Provider.of<AuthenticationViewModel>(context, listen: false);
appState = getIt.get<AppState>();
symptomsCheckerViewModel = Provider.of<SymptomsCheckerViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.scaffoldBgColor,
body: Column(
@ -74,7 +78,8 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
Row(
children: [
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,
height: 50.h,
fit: BoxFit.cover,
@ -90,9 +95,11 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
.toString()
.toText16(isBold: true, maxlines: 1),
SizedBox(width: 12.w),
(bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null && bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty)
(bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null &&
bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty)
? 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,
height: 15.h,
fit: BoxFit.cover,
@ -101,7 +108,9 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
],
),
SizedBox(height: 2.h),
(bookAppointmentsViewModel.selectedDoctor.speciality!.isNotEmpty ? bookAppointmentsViewModel.selectedDoctor.speciality!.first : "")
(bookAppointmentsViewModel.selectedDoctor.speciality!.isNotEmpty
? bookAppointmentsViewModel.selectedDoctor.speciality!.first
: "")
.toString()
.toText12(isBold: true, color: AppColors.greyTextColor, maxLine: 1),
],
@ -163,7 +172,8 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
spacing: 4.h,
runSpacing: 4.h,
children: [
AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"),
AppCustomChipWidget(
labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"),
AppCustomChipWidget(
labelText:
"${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();
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
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.pushAndRemoveUntil(
context,
@ -312,7 +324,8 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
},
onError: (error) {
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();
}, isFullScreen: false);
},
@ -322,7 +335,9 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
void initiateBookAppointment() async {
// LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context));
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);
if (bookAppointmentsViewModel.isLiveCareSchedule) {
@ -333,7 +348,8 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
LoaderBottomSheet.hideLoader();
await Future.delayed(Duration(milliseconds: 50)).then((value) async {
// 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.setIsLiveCareSchedule(false);
Navigator.pushAndRemoveUntil(
@ -363,9 +379,52 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
isCloseButtonVisible: true,
);
}, 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();
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.setIsPatientRescheduleAppointment(false);
Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false);

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

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

@ -1,9 +1,7 @@
import 'package:easy_localization/easy_localization.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/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/generated/locale_keys.g.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/ovulation.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/widgets/appbar/collapsing_list_view.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';
class HealthCalculatorDetailedPage extends StatefulWidget {
HealthCalculatorsTypeEnum calculatorType;
int? clinicID;
int? calculationID;
final HealthCalculatorsTypeEnum calculatorType;
final int? clinicID;
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
State<HealthCalculatorDetailedPage> createState() => _HealthCalculatorDetailedPageState();
@ -52,8 +49,8 @@ class _HealthCalculatorDetailedPageState extends State<HealthCalculatorDetailedP
widget.calculatorType == HealthCalculatorsTypeEnum.triglycerides
? SizedBox()
: Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
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),
child: CustomButton(
text: widget.calculatorType == HealthCalculatorsTypeEnum.bloodSugar ||
@ -131,36 +128,6 @@ class _HealthCalculatorDetailedPageState extends State<HealthCalculatorDetailedP
: {'result': provider.triglyceridesResult ?? result, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID};
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),
);
@ -172,58 +139,69 @@ class _HealthCalculatorDetailedPageState extends State<HealthCalculatorDetailedP
switch (widget.calculatorType) {
case HealthCalculatorsTypeEnum.bmi:
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),
);
case HealthCalculatorsTypeEnum.calories:
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),
);
case HealthCalculatorsTypeEnum.bmr:
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),
);
case HealthCalculatorsTypeEnum.idealBodyWeight:
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),
);
case HealthCalculatorsTypeEnum.bodyFat:
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),
);
case HealthCalculatorsTypeEnum.crabsProteinFat:
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),
);
case HealthCalculatorsTypeEnum.ovulation:
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),
);
case HealthCalculatorsTypeEnum.deliveryDueDate:
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),
);
case HealthCalculatorsTypeEnum.bloodSugar:
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),
);
case HealthCalculatorsTypeEnum.bloodCholesterol:
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),
);
case HealthCalculatorsTypeEnum.triglycerides:
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),
);
}

@ -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/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/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/expandable_list_widget.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
class HealthCalculatorsPage extends StatefulWidget {
HealthCalConEnum type;
final HealthCalConEnum type;
HealthCalculatorsPage({super.key, required this.type});
const HealthCalculatorsPage({super.key, required this.type});
@override
State<HealthCalculatorsPage> createState() => _HealthCalculatorsPageState();
@ -34,7 +33,6 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
@override
Widget build(BuildContext context) {
DialogService dialogService = getIt.get<DialogService>();
return CollapsingListView(
isLeading: Navigator.canPop(context),
title: widget.type == HealthCalConEnum.calculator ? LocaleKeys.healthCalculators.tr(context: context) : LocaleKeys.healthConverters.tr(),
@ -58,7 +56,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
),
],
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)
// ? Column(
@ -129,7 +128,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
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),
Flexible(
child: Column(
@ -149,7 +149,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
),
Transform.flip(
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))
@ -166,7 +167,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
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),
Flexible(
child: Column(
@ -181,7 +183,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
SizedBox(width: 12.w),
Transform.flip(
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))
@ -198,7 +201,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
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),
Flexible(
child: Column(
@ -213,7 +217,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
SizedBox(width: 12.w),
Transform.flip(
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))
@ -247,7 +252,8 @@ class _HealthCalculatorsPageState extends State<HealthCalculatorsPage> {
page: HealthCalculatorDetailedPage(
calculatorType: type == HealthCalculatorEnum.general ? generalHealthServices[index].type : womenHealthServices[index].type,
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? 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: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/utils/size_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/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/theme/colors.dart';
import 'package:provider/provider.dart';
class BloodCholesterolWidget extends StatefulWidget {
final Function(dynamic result)? onChange;
@ -149,9 +149,7 @@ class _BloodCholesterolWidgetState extends State<BloodCholesterolWidget> {
isBold: true,
color: AppColors.inputLabelTextColor,
),
SizedBox(
height: 40.h,
child: TextField(
TextField(
controller: controller,
focusNode: focusNode,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
@ -173,7 +171,6 @@ class _BloodCholesterolWidgetState extends State<BloodCholesterolWidget> {
height: 1.h,
),
),
),
],
);
}

@ -1,14 +1,14 @@
import 'package:easy_localization/easy_localization.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/utils/size_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/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/theme/colors.dart';
import 'package:provider/provider.dart';
class TriglyceridesWidget extends StatefulWidget {
final Function(dynamic result)? onChange;
@ -91,7 +91,6 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
provider.onTriglyceridesMgdlChanged(value);
},
).paddingOnly(top: 16.h),
Row(
children: [
const Expanded(
@ -111,7 +110,6 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
}),
],
),
_buildInputField(
label: LocaleKeys.mmol,
hint: "1.7",
@ -122,9 +120,7 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
provider.onTriglyceridesMmolChanged(value);
},
).paddingOnly(bottom: 16.h),
const Divider(height: 1, color: Color(0xFFEEEEEE)),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -135,9 +131,7 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
),
SizedBox(width: 12.w),
Expanded(
child:
LocaleKeys.triglycerideInfo.tr()
.toText12(
child: LocaleKeys.triglycerideInfo.tr().toText12(
isBold: true,
color: AppColors.inputLabelTextColor,
),
@ -165,13 +159,10 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
isBold: true,
color: AppColors.inputLabelTextColor,
),
SizedBox(
height: 40.h,
child: TextField(
TextField(
controller: controller,
focusNode: focusNode,
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
onChanged: onChanged,
cursorHeight: 35.h,
decoration: InputDecoration(
@ -187,7 +178,6 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
color: Colors.black87,
),
),
),
],
);
}

@ -1,12 +1,10 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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_export.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/location_util.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/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/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/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/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/water_monitor/water_monitor_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/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/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/service_info_page.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/parking/paking_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/navigation_service.dart';
@ -49,8 +43,6 @@ import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
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 {
bool showBackIcon;
@ -69,7 +61,14 @@ class _ServicesPageState extends State<ServicesPage> {
late WeatherMonitorViewModel weatherVM;
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) {
// getIt.get<EmergencyServicesViewModel>().flushData();
// getIt.get<EmergencyServicesViewModel>().getTransportationOrders(
@ -88,11 +87,19 @@ class _ServicesPageState extends State<ServicesPage> {
// 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);
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) {
getIt.get<NavigationService>().pushPageRoute(AppRoutes.comprehensiveCheckupPage);
} 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) {
getIt.get<NavigationService>().pushPageRoute(AppRoutes.eReferralPage);
} else {
@ -409,8 +417,7 @@ class _ServicesPageState extends State<ServicesPage> {
AppAssets.youtube,
bgColor: AppColors.whiteColor,
true,
onTap:()=> launchUrl(Uri.parse("https://www.youtube.com/c/DrsulaimanAlhabibHospitals"))
),
onTap: () => launchUrl(Uri.parse("https://www.youtube.com/c/DrsulaimanAlhabibHospitals"))),
HmgServicesComponentModel(
104,
LocaleKeys.connectOnLinkedin.tr(),
@ -420,7 +427,6 @@ class _ServicesPageState extends State<ServicesPage> {
true,
onTap: () => launchUrl(Uri.parse("https://www.linkedin.com/company/drsulaiman-alhabib-medical-group")),
),
];
@override
@ -431,8 +437,6 @@ class _ServicesPageState extends State<ServicesPage> {
weatherVM.initiateFetchWeather();
}
@override
Widget build(BuildContext context) {
bloodDonationViewModel = Provider.of<BloodDonationViewModel>(context);
@ -455,10 +459,8 @@ class _ServicesPageState extends State<ServicesPage> {
SizedBox(height: 16.h),
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: (isFoldable || isTablet) ? 6 : 4, // 4 icons per row
crossAxisSpacing: 21.w,
mainAxisSpacing: 18.h,
childAspectRatio: 80 / 94),
crossAxisCount: (isFoldable || isTablet) ? 5 : 4, // 4 icons per row
),
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: hmgServices.length,
@ -470,8 +472,11 @@ class _ServicesPageState extends State<ServicesPage> {
SizedBox(height: 24.h),
LocaleKeys.hmgServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0),
SizedBox(height: 16.h),
SizedBox(
height: 350.h,
ConstrainedBox(
constraints: BoxConstraints(
minHeight: 320.h,
maxHeight: isFoldable ? 400.h : (isTablet ? 360.h : 340.h),
),
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: LandingPageData.getServiceCardsList.length,
@ -527,15 +532,33 @@ class _ServicesPageState extends State<ServicesPage> {
children: [
Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h, applyThemeColor: false),
LocaleKeys.habibWallet.tr().toText14(isBold: true, maxlines: 2).expanded,
Utils.buildSvgWithAssets(icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward),
Utils.buildSvgWithAssets(
icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward),
],
),
Spacer(),
getIt.get<AppState>().isAuthenticated
? Consumer<HabibWalletViewModel>(builder: (context, habibWalletVM, child) {
return 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);
return Row(
children: [
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),
Spacer(),
@ -586,9 +609,11 @@ class _ServicesPageState extends State<ServicesPage> {
spacing: 8.w,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 40.w, height: 40.h, applyThemeColor: false),
Utils.buildSvgWithAssets(
icon: AppAssets.services_medical_file_icon, width: 40.w, height: 40.h, applyThemeColor: false),
LocaleKeys.familyTitle.tr().toText16(isBold: true, maxlines: 2).expanded,
Utils.buildSvgWithAssets(icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward),
Utils.buildSvgWithAssets(
icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward),
],
),
Spacer(),
@ -681,10 +706,8 @@ class _ServicesPageState extends State<ServicesPage> {
SizedBox(height: 16.h),
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: (isFoldable || isTablet) ? 6 : 4, // 4 icons per row
crossAxisSpacing: 21.w,
crossAxisCount: (isFoldable || isTablet) ? 5 : 4, // 4 icons per row
mainAxisSpacing: 18.h,
childAspectRatio: 80.w / 94.h,
),
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
@ -705,73 +728,6 @@ class _ServicesPageState extends State<ServicesPage> {
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: [
@ -788,7 +744,7 @@ class _ServicesPageState extends State<ServicesPage> {
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.latest_news_icon,
width: 32.w,
width: 32.h,
height: 32.h,
fit: BoxFit.contain,
),
@ -817,7 +773,7 @@ class _ServicesPageState extends State<ServicesPage> {
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.hmg_contact_icon,
width: 32.w,
width: 32.h,
height: 32.h,
fit: BoxFit.contain,
),
@ -854,7 +810,8 @@ class _ServicesPageState extends State<ServicesPage> {
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),
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))
],
@ -878,7 +835,8 @@ class _ServicesPageState extends State<ServicesPage> {
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),
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))
],
@ -897,10 +855,8 @@ class _ServicesPageState extends State<ServicesPage> {
SizedBox(height: 16.h),
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: (isFoldable || isTablet) ? 6 : 4, // 4 icons per row
crossAxisSpacing: 21.w,
crossAxisCount: (isFoldable || isTablet) ? 5 : 4, // 4 icons per row
mainAxisSpacing: 18.h,
childAspectRatio: 80.w / 94.h,
),
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,

@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:developer';
import 'dart:ui' as ui;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -10,7 +11,6 @@ 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/cache_consts.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
@ -25,8 +25,6 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode
import 'package:hmg_patient_app_new/features/hospital/hospital_selection_view_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_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/models/family_file_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_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/my_appointments_view_model.dart';
@ -40,44 +38,35 @@ import 'package:hmg_patient_app_new/presentation/authentication/quick_login.dart
import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/contact_us.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart';
import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart';
import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart';
import 'package:hmg_patient_app_new/widgets/user_avatar_widget.dart';
import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.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/notifications/notifications_list_page.dart';
import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart';
import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointment_doctor.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_procedures_details_page.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart';
import 'package:hmg_patient_app_new/routes/app_routes.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/zoom_service.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/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/countdown_timer.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/spring_page_route_builder.dart';
import 'package:hmg_patient_app_new/widgets/user_avatar_widget.dart';
import 'package:lottie/lottie.dart';
import 'package:provider/provider.dart';
import 'package:smooth_corner/smooth_corner.dart';
import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart';
import 'dart:ui' as ui;
class LandingPage extends StatefulWidget {
const LandingPage({super.key});
@ -220,15 +209,16 @@ class _LandingPageState extends State<LandingPage> {
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.only(
top: (appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown)
top: (appState.isAuthenticated &&
!insuranceVM.isInsuranceLoading &&
insuranceVM.isInsuranceExpired &&
insuranceVM.isInsuranceExpiryBannerShown)
? (MediaQuery.paddingOf(context).top + 70.h)
: kToolbarHeight + 0.h,
bottom: 24),
child: Column(
spacing: 16.h,
children: [
Row(
spacing: 8.h,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -273,8 +263,6 @@ class _LandingPageState extends State<LandingPage> {
mainAxisSize: MainAxisSize.min,
// spacing: 18.h,
children: [
Stack(clipBehavior: Clip.none, children: [
if (appState.isAuthenticated)
Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 24.h, width: 24.h).onPress(() async {
@ -319,7 +307,9 @@ class _LandingPageState extends State<LandingPage> {
)
: SizedBox.shrink(),
]),
SizedBox(width: 24.w,),
SizedBox(
width: 24.w,
),
Utils.buildSvgWithAssets(icon: AppAssets.location, height: 24.h, width: 24.w).onPress(() {
// openIndoorNavigationBottomSheet(context);
showCommonBottomSheetWithoutHeight(
@ -341,9 +331,16 @@ class _LandingPageState extends State<LandingPage> {
// );
// }),
!appState.isAuthenticated
?Row(children: [ SizedBox(width: 24.w,), Utils.buildSvgWithAssets(icon: appState.isArabic() ? AppAssets.enLangIcon : AppAssets.arLangIcon, height: 24.h, width: 24.h).onPress(() {
? Row(children: [
SizedBox(
width: 24.w,
),
Utils.buildSvgWithAssets(
icon: appState.isArabic() ? AppAssets.enLangIcon : AppAssets.arLangIcon, height: 24.h, width: 24.h)
.onPress(() {
context.setLocale(appState.isArabic() ? Locale('en', 'US') : Locale('ar', 'SA'));
})])
})
])
: SizedBox()
],
);
@ -451,7 +448,7 @@ class _LandingPageState extends State<LandingPage> {
).paddingSymmetrical(24.h, 0.h)
: isTablet
? SizedBox(
height: isFoldable ? 290.h : 255.h,
height: isTablet ? 290.h : 255.h,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: 3,
@ -459,7 +456,7 @@ class _LandingPageState extends State<LandingPage> {
padding: EdgeInsets.only(left: 16.h, right: 16.h),
itemBuilder: (context, index) {
return SizedBox(
height: 255.h,
height: isTablet ? 290.h : 255.h,
width: 250.w,
child: getIndexSwiperCard(index),
);
@ -478,14 +475,15 @@ class _LandingPageState extends State<LandingPage> {
// ),
// );
},
separatorBuilder: (BuildContext cxt, int index) =>
SizedBox(
separatorBuilder: (BuildContext cxt, int index) => SizedBox(
width: 10.w,
),
),
)
: SizedBox(
height: 255.h + 20 + 30, // itemHeight + shadow padding (10 top + 10 bottom) + pagination dots space
height: 255.h +
20 +
30, // itemHeight + shadow padding (10 top + 10 bottom) + pagination dots space
child: Builder(
builder: (context) {
final int swiperItemCount = myAppointmentsVM.isMyAppointmentsLoading
@ -530,7 +528,8 @@ class _LandingPageState extends State<LandingPage> {
? _buildLiveCareRequestCard().paddingSymmetrical(24.h, 16.h)
: Container(
width: double.infinity,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
@ -630,7 +629,9 @@ class _LandingPageState extends State<LandingPage> {
LocaleKeys.quickLinks.tr(context: context).toText16(isBold: true),
Row(
children: [
LocaleKeys.viewMedicalFileLandingPage.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true),
LocaleKeys.viewMedicalFileLandingPage
.tr(context: context)
.toText14(color: AppColors.primaryRedColor, isBold: true),
SizedBox(width: 2.h),
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h),
],
@ -642,7 +643,6 @@ class _LandingPageState extends State<LandingPage> {
SizedBox(height: 16.h),
Consumer(builder: (BuildContext context, TodoSectionViewModel todoSectionVM, Widget? child) {
return Container(
// height: 121.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
child: Column(
children: [
@ -659,16 +659,15 @@ class _LandingPageState extends State<LandingPage> {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.pendingAncillaryOrders.tr(context: context).toText14(color: AppColors.eReferralCardColor, isBold: true).paddingSymmetrical(24.h, 0.h),
LocaleKeys.pendingAncillaryOrders
.tr(context: context)
.toText14(color: AppColors.eReferralCardColor, isBold: true)
.paddingSymmetrical(24.h, 0.h),
CustomButton(
text: LocaleKeys.view.tr(context: context),
onPressed: () {
todoSectionVM.setIsAncillaryOrdersNeedReloading(true);
Navigator.of(context).push(
CustomPageRoute(
page: ToDoPage(),
),
);
Navigator.of(context).push(CustomPageRoute(page: ToDoPage()));
},
backgroundColor: AppColors.eReferralCardColor,
borderColor: AppColors.eReferralCardColor,
@ -694,11 +693,10 @@ class _LandingPageState extends State<LandingPage> {
trackColor: Color(0xffD9D9D9),
trackBorderColor: Colors.transparent,
trackRadius: Radius.circular(10.0),
padding: EdgeInsets.only(top: 92.h + 32.h, left: MediaQuery
.sizeOf(context)
.width / 2.5 - 10, right: MediaQuery
.sizeOf(context)
.width / 2.5 - 10),
padding: EdgeInsets.only(
top: 92.h + 32.h,
left: MediaQuery.sizeOf(context).width / 2.5 - 10,
right: MediaQuery.sizeOf(context).width / 2.5 - 10),
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: LandingPageData().getLoggedInServiceCardsList.length,
@ -752,11 +750,10 @@ class _LandingPageState extends State<LandingPage> {
trackColor: Color(0xffD9D9D9),
trackBorderColor: Colors.transparent,
trackRadius: Radius.circular(10.0),
padding: EdgeInsets.only(top: 92.h + 32.h, left: MediaQuery
.sizeOf(context)
.width / 2.5 - 10, right: MediaQuery
.sizeOf(context)
.width / 2.5 - 10),
padding: EdgeInsets.only(
top: 92.h + 32.h,
left: MediaQuery.sizeOf(context).width / 2.5 - 10,
right: MediaQuery.sizeOf(context).width / 2.5 - 10),
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: LandingPageData.getNotLoggedInServiceCardsList.length,
@ -807,8 +804,8 @@ class _LandingPageState extends State<LandingPage> {
}),
],
).paddingSymmetrical(24.w, 0.h),
SizedBox(
height: 431.h,
ConstrainedBox(
constraints: BoxConstraints(maxHeight: isFoldable ? 450.h : (isTablet ? 440.h : 431.h), minHeight: 411.h),
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: LandingPageData.getServiceCardsList.length,
@ -860,7 +857,10 @@ class _LandingPageState extends State<LandingPage> {
),
),
),
(appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown)
(appState.isAuthenticated &&
!insuranceVM.isInsuranceLoading &&
insuranceVM.isInsuranceExpired &&
insuranceVM.isInsuranceExpiryBannerShown)
? Container(
height: MediaQuery.paddingOf(context).top + 50.h,
decoration: ShapeDecoration(
@ -877,17 +877,24 @@ class _LandingPageState extends State<LandingPage> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true).paddingSymmetrical(0.h, 0.h),
LocaleKeys.insuranceExpiredOrInactive
.tr(context: context)
.toText14(color: AppColors.primaryRedColor, isBold: true)
.paddingSymmetrical(0.h, 0.h),
Row(
children: [
CustomButton(
text: LocaleKeys.updateInsurance.tr(context: context),
onPressed: () {
insuranceVM.setIsInsuranceUpdateDetailsLoading(true);
insuranceVM.getPatientInsuranceDetailsForUpdate(
appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString());
insuranceVM.getPatientInsuranceDetailsForUpdate(appState.getAuthenticatedUser()!.patientId.toString(),
appState.getAuthenticatedUser()!.patientIdentificationNo.toString());
showCommonBottomSheetWithoutHeight(context,
child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false);
child: PatientInsuranceCardUpdateCard(),
callBackFunc: () {},
title: "",
isCloseButtonVisible: false,
isFullScreen: false);
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.secondaryLightRedBorderColor,
@ -1027,7 +1034,8 @@ class _LandingPageState extends State<LandingPage> {
SizedBox(height: 6.h),
_buildServingNowSection(),
SizedBox(height: 5.h),
_buildQueueActionButton(currentStatus, currentQueue.roomNo ?? "").toShimmer2(isShow: myAppointmentsViewModel.patientQueueDetailsList.isEmpty),
_buildQueueActionButton(currentStatus, currentQueue.roomNo ?? "")
.toShimmer2(isShow: myAppointmentsViewModel.patientQueueDetailsList.isEmpty),
],
),
),
@ -1056,7 +1064,8 @@ class _LandingPageState extends State<LandingPage> {
hasShadow: false,
),
padding: EdgeInsets.all(6.h),
child: Lottie.asset(AppAnimations.hourGlass, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)),
child: Lottie.asset(AppAnimations.hourGlass,
repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)),
],
);
}
@ -1169,7 +1178,6 @@ class _LandingPageState extends State<LandingPage> {
height: 40.h,
iconColor: AppColors.whiteColor,
iconSize: 18.h,
),
// _buildLiveCareWaitingTime(),
],
@ -1236,7 +1244,8 @@ class _LandingPageState extends State<LandingPage> {
hasShadow: false,
),
padding: EdgeInsets.all(6.h),
child: Lottie.asset(AppAnimations.hourGlass, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)),
child: Lottie.asset(AppAnimations.hourGlass,
repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.fill)),
// Utils.buildSvgWithAssets(
// icon: AppAssets.waiting_icon,
// width: 24.h,
@ -1284,12 +1293,8 @@ class _LandingPageState extends State<LandingPage> {
// Appointment Card Wrapper (reusable)
Widget _buildAppointmentCardWrapper(appointment) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
hasDenseShadow: true
),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true, hasDenseShadow: true),
child: AppointmentCard(
patientAppointmentHistoryResponseModel: appointment,
myAppointmentsViewModel: myAppointmentsViewModel,
@ -1463,4 +1468,3 @@ class _DirectionalDotPaginationBuilder extends SwiperPlugin {
);
}
}

@ -47,7 +47,9 @@ class HabibWalletCard extends StatelessWidget {
child: Stack(children: [
Positioned(
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: EdgeInsets.all(16.h),

@ -43,38 +43,37 @@ class LargeServiceCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 350.h,
width: 230.w,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.transparent, borderRadius: 24.r),
child: Stack(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(24.r),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(24.r),
topRight: Radius.circular(24.r),
),
child: Image.asset(
serviceCardData.largeCardIcon,
fit: BoxFit.cover,
width: double.infinity,
height: isFoldable ? 190.h : (isTablet ? 200.h : 180.h),
),
),
Positioned(
bottom: 0.0, // Positions the child 0 logical pixels from the bottom
left: 0.0,
right: 0.0,
child: Container(
height: 180.h,
padding: EdgeInsets.only(bottom: 16.h, top: 16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
customBorder: BorderRadius.only(
bottomLeft: Radius.circular(24.r),
bottomRight: Radius.circular(24.r),
),
),
Container(
padding: EdgeInsets.all(16.w),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
isPNG ? Image.asset(serviceCardData.icon, width: 35.h, height: 35.h) : Container(
isPNG
? Image.asset(serviceCardData.icon, width: 35.h, height: 35.h)
: Container(
height: 48.h,
width: 48.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
@ -97,13 +96,19 @@ class LargeServiceCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
serviceCardData.title.tr(context: context).toText14(isBold: true, color: AppColors.textColor),
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),
],
),
),
],
).paddingSymmetrical(8.w, 0.h).expanded,
),
SizedBox(height: 24.h),
CustomButton(
text: serviceCardData.isBold ? LocaleKeys.visitPharmacyOnline.tr(context: context) : LocaleKeys.bookNow.tr(context: context),
onPressed: () {
@ -117,9 +122,8 @@ class LargeServiceCard extends StatelessWidget {
fontWeight: FontWeight.w600,
borderRadius: 10.r,
height: 40.h,
).paddingSymmetrical(16.w, 0.h),
],
),
],
),
),
],
@ -229,7 +233,9 @@ class FadedLargeServiceCard extends StatelessWidget {
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
isPNG ? Image.asset(serviceCardData.icon, width: 32.h, height: 32.h).circle(100.h) : Container(
isPNG
? Image.asset(serviceCardData.icon, width: 32.h, height: 32.h).circle(100.h)
: Container(
height: 32.h,
width: 32.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
@ -242,11 +248,7 @@ class FadedLargeServiceCard extends StatelessWidget {
child: Transform.flip(
flipX: getIt.get<AppState>().isArabic(),
child: Utils.buildSvgWithAssets(
icon: serviceCardData.icon,
iconColor: serviceCardData.iconColor,
fit: BoxFit.contain,
applyThemeColor: false
),
icon: serviceCardData.icon, iconColor: serviceCardData.iconColor, fit: BoxFit.contain, applyThemeColor: false),
),
),
),

@ -108,10 +108,10 @@ class InsuranceApprovalDetailsPage extends StatelessWidget {
richText: Row(
mainAxisSize: MainAxisSize.min,
children: [
"${LocaleKeys.receiptOn.tr(context: context)} ".toText10(),
"${LocaleKeys.receiptOn.tr(context: context)} ".toText10(isBold: true),
Directionality(
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,
@ -121,15 +121,14 @@ class InsuranceApprovalDetailsPage extends StatelessWidget {
richText: Row(
mainAxisSize: MainAxisSize.min,
children: [
"${LocaleKeys.expiryOn.tr(context: context)} ".toText10(),
"${LocaleKeys.expiryOn.tr(context: context)} ".toText10(isBold: true),
Directionality(
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,
),
],
),
],

@ -162,10 +162,11 @@ class PatientInsuranceCard extends StatelessWidget {
AppCustomChipWidget(
icon: AppAssets.doctor_calendar_icon,
// 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),
),
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),

@ -20,8 +20,9 @@ class LabResultList extends StatelessWidget {
selector: (_, model) => model.mainLabResultsByHospitals,
builder: (__, list, ___) {
if (list.isEmpty && context.read<LabViewModel>().labSpecialResult.isEmpty) {
return Utils.getNoDataWidget(context,
noDataText: LocaleKeys.noLabResults.tr(context: context));
// return Utils.getNoDataWidget(context,
// noDataText: LocaleKeys.noLabResults.tr(context: context));
return Container();
} else {
return ListView.builder(
physics: NeverScrollableScrollPhysics(),

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:ui' as ui;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -7,17 +8,13 @@ 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_export.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_config.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/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/active_prescriptions/models/active_prescriptions_response_model.dart';
import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart';
@ -26,7 +23,6 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/vital
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/lab_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/models/family_file_response_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart';
import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_view_model.dart';
@ -37,12 +33,10 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/active_medication/active_medication_page.dart';
import 'package:hmg_patient_app_new/presentation/allergies/allergies_list_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart';
import 'package:hmg_patient_app_new/presentation/ask_doctor/ask_doctor_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart';
@ -58,14 +52,11 @@ import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/eye_measurements_appointments_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_list_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/health_tracker_menu_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/health_tools_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_report_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_page.dart';
import 'package:hmg_patient_app_new/presentation/monthly_report/monthly_report.dart';
import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart';
import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_list.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart';
@ -73,7 +64,6 @@ import 'package:hmg_patient_app_new/presentation/radiology/radiology_orders_page
import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart';
import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.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/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
@ -81,20 +71,16 @@ 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/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/expandable_list_widget.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.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/shimmer/common_shimmer_widget.dart';
import 'package:hmg_patient_app_new/widgets/user_avatar_widget.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../features/active_prescriptions/active_prescriptions_view_model.dart';
import '../prescriptions/prescription_detail_page.dart';
import 'widgets/medical_file_appointment_card.dart';
import 'dart:ui' as ui;
class MedicalFilePage extends StatefulWidget {
bool showBackIcon;
@ -121,10 +107,6 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
int currentIndex = 0;
// Used to make the PageView height follow the card's intrinsic height
final GlobalKey _vitalSignMeasureKey = GlobalKey();
double? _vitalSignMeasuredHeight;
@override
void initState() {
appState = getIt.get<AppState>();
@ -145,22 +127,6 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
super.initState();
}
void _scheduleVitalSignMeasure() {
WidgetsBinding.instance.addPostFrameCallback((_) {
final ctx = _vitalSignMeasureKey.currentContext;
if (ctx == null) return;
final box = ctx.findRenderObject();
if (box is RenderBox) {
final h = box.size.height;
if (h > 0 && h != _vitalSignMeasuredHeight) {
setState(() {
_vitalSignMeasuredHeight = h;
});
}
}
});
}
@override
Widget build(BuildContext context) {
labViewModel = Provider.of<LabViewModel>(context, listen: false);
@ -253,7 +219,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
UserAvatarWidget(
width: 56.w,
width: 56.h,
height: 56.h,
fit: BoxFit.cover,
isCircular: true,
@ -275,7 +241,8 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
children: [
AppCustomChipWidget(
icon: AppAssets.file_icon,
richText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText10(isEnglishOnly: true),
richText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}"
.toText10(isEnglishOnly: true),
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
),
AppCustomChipWidget(
@ -299,7 +266,9 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
runSpacing: 4.h,
children: [
AppCustomChipWidget(
labelText: LocaleKeys.ageYearsOld.tr(namedArgs: {'age': '${appState.getAuthenticatedUser()!.age}', 'yearsOld': LocaleKeys.yearsOld.tr(context: context)}, context: context),
labelText: LocaleKeys.ageYearsOld.tr(
namedArgs: {'age': '${appState.getAuthenticatedUser()!.age}', 'yearsOld': LocaleKeys.yearsOld.tr(context: context)},
context: context),
labelPadding: EdgeInsetsDirectional.only(start: 8.w, end: 8.w),
),
AppCustomChipWidget(
@ -342,9 +311,14 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
onChipTap: () {
if (!insuranceVM.isInsuranceActive) {
insuranceVM.setIsInsuranceUpdateDetailsLoading(true);
insuranceVM.getPatientInsuranceDetailsForUpdate(
appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString());
showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false);
insuranceVM.getPatientInsuranceDetailsForUpdate(appState.getAuthenticatedUser()!.patientId.toString(),
appState.getAuthenticatedUser()!.patientIdentificationNo.toString());
showCommonBottomSheetWithoutHeight(context,
child: PatientInsuranceCardUpdateCard(),
callBackFunc: () {},
title: "",
isCloseButtonVisible: false,
isFullScreen: false);
// showCommonBottomSheetWithoutHeight(
// title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!),
// navigationService.navigatorKey.currentContext!,
@ -440,17 +414,16 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
);
}
// The cards define their own height; measure the first rendered page once
_scheduleVitalSignMeasure();
final double hostHeight = _vitalSignMeasuredHeight ?? (135.h);
return SizedBox(
height: hostHeight,
// Responsive PageView with dynamic height constraint
return ConstrainedBox(
constraints: BoxConstraints(
minHeight: 135.h,
maxHeight: isFoldable ? 160.h : (isTablet ? 165.h : 135.h),
),
child: PageView(
controller: hmgServicesVM.vitalSignPageController,
onPageChanged: (index) {
hmgServicesVM.setVitalSignCurrentPage(index);
_scheduleVitalSignMeasure();
},
children: _buildVitalSignPages(
vitalSign: hmgServicesVM.vitalSignList.first,
@ -461,7 +434,6 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
),
);
},
measureKey: _vitalSignMeasureKey,
currentPageIndex: hmgServicesVM.vitalSignCurrentPage,
),
),
@ -519,7 +491,9 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
getSelectedTabData(0),
],
),
ExpandableListItem(title: LocaleKeys.medicalReports.tr(context: context).toText18(isBold: true), expandedBackgroundColor: Colors.transparent,
ExpandableListItem(
title: LocaleKeys.medicalReports.tr(context: context).toText18(isBold: true),
expandedBackgroundColor: Colors.transparent,
children: [
SizedBox(height: 10.h),
getSelectedTabData(2),
@ -590,10 +564,137 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
});
}
Widget getSelectedTabData(int index) {
switch (index) {
case 0:
//General Tab Data
Widget buildInsuranceTab(int index) {
return Column(
children: [
Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) {
return insuranceVM.isInsuranceLoading
? LabResultItemView(
onTap: () {},
labOrder: null,
index: index,
isLoading: true,
).paddingSymmetrical(0.w, 0.0)
: insuranceVM.patientInsuranceList.isNotEmpty
? PatientInsuranceCard(
insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first,
isInsuranceExpired: DateTime.now().isAfter(
DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo),
),
)
: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.bgRedLightColor,
borderRadius: 12.r,
hasShadow: false,
),
child: Utils.getNoDataWidget(
context,
noDataText: LocaleKeys.noInsuranceWithHMG.tr(context: context),
isSmallWidget: true,
width: 62.w,
height: 62.h,
callToActionButton: CustomButton(
icon: AppAssets.update_insurance_card_icon,
iconColor: AppColors.successColor,
iconSize: 15.h,
text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}",
onPressed: () {
insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true);
insuranceViewModel.getPatientInsuranceDetailsForUpdate(appState.getAuthenticatedUser()!.patientId.toString(),
appState.getAuthenticatedUser()!.patientIdentificationNo.toString());
showCommonBottomSheetWithoutHeight(context,
child: PatientInsuranceCardUpdateCard(),
callBackFunc: () {},
title: "",
isCloseButtonVisible: false,
isFullScreen: false);
},
backgroundColor: AppColors.bgGreenColor.withOpacity(0.20),
borderColor: AppColors.bgGreenColor.withOpacity(0.0),
textColor: AppColors.bgGreenColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0),
height: isFoldable ? 50.h : 40.h,
).paddingOnly(left: 12.w, right: 12.w, bottom: 12.h),
),
).paddingSymmetrical(0.w, 0.h);
}),
SizedBox(height: 10.h),
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 10.h,
mainAxisSpacing: 16.w,
// mainAxisExtent: 120.h,
),
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.only(top: 12.h),
shrinkWrap: true,
children: [
MedicalFileCard(
label: LocaleKeys.updateInsuranceInfo.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.update_insurance_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() {
Navigator.of(context).push(CustomPageRoute(page: InsuranceHomePage()));
}),
MedicalFileCard(
label: "${LocaleKeys.approvals1.tr(context: context)} ${LocaleKeys.insurance.tr(context: context)}",
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.insurance_approval_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: InsuranceApprovalsPage(),
),
);
}),
MedicalFileCard(
label: LocaleKeys.myInvoicesList.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.invoices_list_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: MyInvoicesList(),
),
);
}),
MedicalFileCard(
label: LocaleKeys.ancillaryOrdersListNew.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.ancillary_orders_list_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() {
getIt.get<TodoSectionViewModel>().setIsAncillaryOrdersNeedReloading(true);
Navigator.of(context).push(
CustomPageRoute(
page: ToDoPage(),
),
);
}),
],
).paddingSymmetrical(0.w, 0.0),
SizedBox(height: 16.h),
],
);
}
Widget buildMedicalServicesTab() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -610,17 +711,16 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
),
],
).paddingSymmetrical(0.w, 0.h).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: MyAppointmentsPage(),
),
);
Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage()));
}),
SizedBox(height: 16.h),
Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
// Provide an explicit height so the horizontal ListView has a bounded height
return SizedBox(
height: 192.h,
// Dynamic height that adapts to device and content
return ConstrainedBox(
constraints: BoxConstraints(
minHeight: 150.h,
maxHeight: isFoldable ? 230.h : (isTablet ? 240.h : 180.h),
),
child: myAppointmentsVM.isMyAppointmentsLoading
? MedicalFileAppointmentCard(
patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(),
@ -632,7 +732,8 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
? Container(
padding: EdgeInsets.all(12.w),
width: MediaQuery.of(context).size.width,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: false),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: false),
child: Column(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h),
@ -791,7 +892,8 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
? const CommonShimmerWidget().paddingSymmetrical(0.w, 0.h)
: prescriptionVM.patientPrescriptionOrders.isNotEmpty
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r, hasShadow: false),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r, hasShadow: false),
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
@ -812,7 +914,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
children: [
Image.network(
prescriptionVM.patientPrescriptionOrders[index].doctorImageURL!,
width: 40.w,
width: 40.h,
height: 40.h,
fit: BoxFit.cover,
).circle(100.r),
@ -828,13 +930,15 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
spacing: 3.w,
runSpacing: 4.w,
children: [
AppCustomChipWidget(labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!),
AppCustomChipWidget(
labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!),
Directionality(
textDirection: ui.TextDirection.ltr,
child: AppCustomChipWidget(
icon: AppAssets.doctor_calendar_icon,
labelText: DateUtil.formatDateToDate(
DateUtil.convertStringToDate(prescriptionVM.patientPrescriptionOrders[index].appointmentDate),
DateUtil.convertStringToDate(
prescriptionVM.patientPrescriptionOrders[index].appointmentDate),
false,
),
isEnglishOnly: true,
@ -849,13 +953,19 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small, width: 15.w, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)),
icon: AppAssets.forward_arrow_icon_small,
width: 15.w,
height: 15.h,
fit: BoxFit.contain,
iconColor: AppColors.textColor)),
],
).onPress(() {
prescriptionVM.setPrescriptionsDetailsLoading();
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionDetailPage(isFromAppointments: false, prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]),
page: PrescriptionDetailPage(
isFromAppointments: false,
prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]),
),
);
}),
@ -963,7 +1073,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
children: [
Image.network(
"https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
width: 64.w,
width: 64.h,
height: 64.h,
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: true, radius: 50.r),
@ -987,8 +1097,11 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
height: 62.h,
),
).paddingSymmetrical(0.w, 0.h)
: SizedBox(
height: 110.h,
: ConstrainedBox(
constraints: BoxConstraints(
minHeight: 100.h,
maxHeight: isFoldable ? 130.h : (isTablet ? 140.h : 115.h),
),
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: myAppointmentsVM.patientMyDoctorsList.length,
@ -1007,7 +1120,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
children: [
Image.network(
myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!,
width: 64.w,
width: 64.h,
height: 64.h,
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: false, radius: 50.r),
@ -1015,7 +1128,9 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
SizedBox(
width: 80.w,
child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName)
.toString().toText12(isBold: true, isCenter: true, maxLine: 2).toShimmer2(isShow: false),
.toString()
.toText12(isBold: true, isCenter: true, maxLine: 2)
.toShimmer2(isShow: false),
),
],
),
@ -1030,7 +1145,8 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(isDoctorAllowedToBook: !(myAppointmentsVM.patientMyDoctorsList[index].isLiveCareClinic ?? false)),
page: DoctorProfilePage(
isDoctorAllowedToBook: !(myAppointmentsVM.patientMyDoctorsList[index].isLiveCareClinic ?? false)),
),
);
}, onError: (err) {
@ -1057,9 +1173,8 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 1,
crossAxisSpacing: 10.h,
mainAxisSpacing: 16.w,
mainAxisExtent: 115.h,
),
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
@ -1116,142 +1231,9 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
SizedBox(height: 24.h),
],
);
case 1:
//Insurance Tab Data
return Column(
children: [
Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) {
return insuranceVM.isInsuranceLoading
? LabResultItemView(
onTap: () {},
labOrder: null,
index: index,
isLoading: true,
).paddingSymmetrical(0.w, 0.0)
: insuranceVM.patientInsuranceList.isNotEmpty
? PatientInsuranceCard(
insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first,
isInsuranceExpired: DateTime.now().isAfter(
DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo),
),
)
: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.r,
hasShadow: false,
),
child: Utils.getNoDataWidget(
context,
noDataText: LocaleKeys.noInsuranceWithHMG.tr(context: context),
isSmallWidget: true,
width: 62.w,
height: 62.h,
callToActionButton: CustomButton(
icon: AppAssets.update_insurance_card_icon,
iconColor: AppColors.successColor,
iconSize: 15.h,
text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}",
onPressed: () {
insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true);
insuranceViewModel.getPatientInsuranceDetailsForUpdate(
appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString());
showCommonBottomSheetWithoutHeight(context, child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false);
},
backgroundColor: AppColors.bgGreenColor.withOpacity(0.20),
borderColor: AppColors.bgGreenColor.withOpacity(0.0),
textColor: AppColors.bgGreenColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0),
height: isFoldable ? 50.h : 40.h,
).paddingOnly(left: 12.w, right: 12.w, bottom: 12.h),
),
).paddingSymmetrical(0.w, 0.h);
}),
SizedBox(height: 10.h),
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 10.h,
mainAxisSpacing: 16.w,
mainAxisExtent: 120.h,
),
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.only(top: 12.h),
shrinkWrap: true,
children: [
MedicalFileCard(
label: LocaleKeys.updateInsuranceInfo.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.update_insurance_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() {
Navigator.of(context).push(CustomPageRoute(page: InsuranceHomePage()));
}),
MedicalFileCard(
label: "${LocaleKeys.approvals1.tr(context: context)} ${LocaleKeys.insurance.tr(context: context)}",
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.insurance_approval_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: InsuranceApprovalsPage(),
),
);
}),
MedicalFileCard(
label: LocaleKeys.myInvoicesList.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.invoices_list_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: MyInvoicesList(),
),
);
}),
MedicalFileCard(
label: LocaleKeys.ancillaryOrdersListNew.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.ancillary_orders_list_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() {
getIt.get<TodoSectionViewModel>().setIsAncillaryOrdersNeedReloading(true);
Navigator.of(context).push(
CustomPageRoute(
page: ToDoPage(),
),
);
}),
MedicalFileCard(
label: LocaleKeys.habibWallet.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.wallet,
isLargeText: true,
iconSize: 36.w,
).onPress(() {
Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage()));
}),
],
).paddingSymmetrical(0.w, 0.0),
SizedBox(height: 16.h),
],
);
case 2:
// Requests Tab Data
}
Widget buildRequestsTab() {
return Column(
children: [
Row(
@ -1301,79 +1283,23 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
).paddingSymmetrical(0.w, 0.h);
}),
SizedBox(height: 16.h),
Selector<MedicalFileViewModel,({bool isLoading, List<PatientMedicalReportResponseModel> listRequest, List<PatientMedicalReportResponseModel> listReady})>(
selector: (context, vm) => (isLoading: vm.isPatientMedicalReportsListLoading, listRequest: vm.patientMedicalReportRequestedList, listReady: vm.patientMedicalReportReadyList),
Selector<MedicalFileViewModel,
({bool isLoading, List<PatientMedicalReportResponseModel> listRequest, List<PatientMedicalReportResponseModel> listReady})>(
selector: (context, vm) => (
isLoading: vm.isPatientMedicalReportsListLoading,
listRequest: vm.patientMedicalReportRequestedList,
listReady: vm.patientMedicalReportReadyList
),
builder: (context, data, _) {
return MedicalReportCard(
isLoading: data.isLoading, listRequest: data.listRequest, listReady: data.listReady
);
return MedicalReportCard(isLoading: data.isLoading, listRequest: data.listRequest, listReady: data.listReady);
},
)
// GridView(
// gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
// crossAxisCount: 3,
// crossAxisSpacing: 10.h,
// mainAxisSpacing: 16.w,
// mainAxisExtent: 110.h,
// ),
// physics: NeverScrollableScrollPhysics(),
// padding: EdgeInsets.zero,
// shrinkWrap: true,
// children: [
// // MedicalFileCard(
// // label: LocaleKeys.monthlyReports.tr(context: context),
// // textColor: AppColors.blackColor,
// // backgroundColor: AppColors.whiteColor,
// // svgIcon: AppAssets.monthly_reports_icon,
// // isLargeText: true,
// // iconSize: 36.h,
// // ).onPress(() {
// // monthlyReportViewModel.setHealthSummaryEnabled(cacheService.getBool(key: CacheConst.isMonthlyReportEnabled) ?? false);
// // Navigator.of(context).push(
// // CustomPageRoute(
// // page: MonthlyReport(),
// // ),
// // );
// // }),
// ///todo te changes of the medical report should be displayed here.
// ///
//
// // MedicalFileCard(
// // label: LocaleKeys.medicalReports.tr(context: context),
// // textColor: AppColors.blackColor,
// // backgroundColor: AppColors.whiteColor,
// // svgIcon: AppAssets.medical_reports_icon,
// // isLargeText: true,
// // iconSize: 36.w,
// // ).onPress(() {
// //
// // Navigator.of(context).push(
// // CustomPageRoute(
// // page: MedicalReportsPage(),
// // ),
// // );
// // }),
// // MedicalFileCard(
// // label: LocaleKeys.sickLeaveReport.tr(context: context),
// // textColor: AppColors.blackColor,
// // backgroundColor: AppColors.whiteColor,
// // svgIcon: AppAssets.sick_leave_report_icon,
// // isLargeText: true,
// // iconSize: 36.h,
// // ).onPress(() {
// // Navigator.of(context).push(
// // CustomPageRoute(
// // page: PatientSickleavesListPage(),
// // ),
// // );
// // }),
// ],
// ).paddingSymmetrical(0.w, 0.0),
,SizedBox(height: 24.h),
),
SizedBox(height: 24.h),
],
);
case 3:
// Health Tools Tab Data
}
Widget buildHealthToolsTab() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -1382,7 +1308,6 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
crossAxisCount: 3,
crossAxisSpacing: 10.h,
mainAxisSpacing: 16.w,
mainAxisExtent: 120.h,
),
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.only(top: 12.h),
@ -1465,6 +1390,22 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
SizedBox(height: 24.h),
],
);
}
Widget getSelectedTabData(int index) {
switch (index) {
case 0:
//General Tab Data
return buildMedicalServicesTab();
case 1:
//Insurance Tab Data
return buildInsuranceTab(index);
case 2:
// Requests Tab Data
return buildRequestsTab();
case 3:
// Health Tools Tab Data
return buildHealthToolsTab();
default:
return Container();
}
@ -1550,13 +1491,12 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
List<Widget> _buildVitalSignPages({
required VitalSignResModel vitalSign,
required VoidCallback onTap,
required GlobalKey measureKey,
required int currentPageIndex,
}) {
return [
// Page 1: BMI + Height
Padding(
padding: EdgeInsets.only(left: 24.w),
padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Row(
children: [
Expanded(
@ -1586,7 +1526,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
),
// Page 2: Weight + Blood Pressure
Padding(
padding: EdgeInsets.symmetric(horizontal: 12.w),
padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Row(
children: [
Expanded(
@ -1604,13 +1544,17 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
child: _buildVitalSignCard(
icon: AppAssets.bloodPressure,
label: LocaleKeys.bloodPressure.tr(context: context),
value: (vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null &&
vitalSign.bloodPressureLower != 0 && vitalSign.bloodPressureHigher != 0)
value: (vitalSign.bloodPressureLower != null &&
vitalSign.bloodPressureHigher != null &&
vitalSign.bloodPressureLower != 0 &&
vitalSign.bloodPressureHigher != 0)
? "${vitalSign.bloodPressureHigher}/${vitalSign.bloodPressureLower}"
: '--',
unit: '',
status: (vitalSign.bloodPressureLower != null && vitalSign.bloodPressureHigher != null &&
vitalSign.bloodPressureLower != 0 && vitalSign.bloodPressureHigher != 0)
status: (vitalSign.bloodPressureLower != null &&
vitalSign.bloodPressureHigher != null &&
vitalSign.bloodPressureLower != 0 &&
vitalSign.bloodPressureHigher != 0)
? _getBloodPressureStatus(
systolic: vitalSign.bloodPressureHigher,
diastolic: vitalSign.bloodPressureLower,
@ -1694,7 +1638,8 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
weight: FontWeight.w600,
),
),
Utils.buildSvgWithAssets(icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward, width: 18.w, height: 18.h),
Utils.buildSvgWithAssets(
icon: getIt.get<AppState>().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward, width: 18.w, height: 18.h),
],
),
SizedBox(height: 14.h),
@ -1714,11 +1659,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: value.toText17(
isBold: true,
color: AppColors.textColor,
isEnglishOnly: true
),
child: value.toText17(isBold: true, color: AppColors.textColor, isEnglishOnly: true),
),
if (unit.isNotEmpty && value != '--' && value != '0') ...[
SizedBox(width: 3.w),

@ -1,4 +1,6 @@
import 'dart:async';
import 'dart:ui' as ui;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -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/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/routes/custom_page_route.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'dart:ui' as ui;
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
class MedicalFileAppointmentCard extends StatefulWidget {
final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel;
@ -138,111 +138,173 @@ class _MedicalFileAppointmentCardState extends State<MedicalFileAppointmentCard>
richText: Directionality(
textDirection: ui.TextDirection.ltr,
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),
),
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,
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,
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),
SizedBox(height: 16.h),
Container(
IntrinsicWidth(
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false),
width: 200.w,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Image.network(
widget.patientAppointmentHistoryResponseModel.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
width: 25.w,
height: 27.h,
widget.patientAppointmentHistoryResponseModel.doctorImageURL ??
"https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
width: 30.h,
height: 30.h,
fit: BoxFit.fill,
).circle(100).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading),
).circle(100.r).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading),
SizedBox(width: 8.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText14(isBold: true, maxlines: 1, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "")).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading),
(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "")
.toText14(
isBold: true,
maxlines: 1,
isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? ""))
.toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading),
(widget.patientAppointmentHistoryResponseModel.clinicName ?? "")
.toText12(maxLine: 1, isBold: true, color: AppColors.greyTextColor)
.toText12(maxLine: 1, textOverflow: TextOverflow.ellipsis, isBold: true, color: AppColors.greyTextColor)
.toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading),
],
),
),
],
),
SizedBox(height: 12.h),
// Check if doctor is active - if not, show only View Details button
(widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true)
? // 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),
SizedBox(height: 8.h),
_buildAppointmentActionButton(context, appState),
],
).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(widget.patientAppointmentHistoryResponseModel.nextAction, context);
handleAppointmentNextAction(appointment.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),
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(widget.patientAppointmentHistoryResponseModel.nextAction),
iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction),
icon: AppointmentType.getNextActionIcon(appointment.nextAction),
iconColor: AppointmentType.getNextActionTextColor(appointment.nextAction),
iconSize: 14.h,
).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading)
: // Normal flow - show button with arrow
Row(
).toShimmer2(isShow: isLoading);
}
/// Builds button with arrow (normal flow with navigation arrow)
Widget _buildButtonWithArrow(BuildContext context, AppState appState, bool isLoading) {
return 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),
),
_buildMainActionButton(context, isLoading),
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(
_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.w,
width: 40.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.textColor,
borderRadius: 10.r,
@ -254,29 +316,29 @@ class _MedicalFileAppointmentCardState extends State<MedicalFileAppointmentCard>
child: Utils.buildSvgWithAssets(
iconColor: AppColors.whiteColor,
icon: AppAssets.forward_arrow_icon_small,
width: 40.w,
width: 40.h,
height: 40.h,
fit: BoxFit.contain,
),
),
),
).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading).onPress(() {
).toShimmer2(isShow: isLoading).onPress(() {
Navigator.of(context)
.push(
CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel),
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: appointment),
),
)
.then((val) {
// widget.myAppointmentsViewModel.initAppointmentsViewModel();
// widget.myAppointmentsViewModel.getPatientAppointments(true, false);
// Can refresh appointments here if needed
});
}),
),
],
)
: // Doctor is not active - show only View Details button
CustomButton(
);
}
/// Builds the View Details button (for inactive doctors)
Widget _buildViewDetailsButton(BuildContext context) {
return CustomButton(
text: LocaleKeys.viewDetails.tr(context: context),
onPressed: () {
Navigator.of(context)
@ -297,13 +359,8 @@ class _MedicalFileAppointmentCardState extends State<MedicalFileAppointmentCard>
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading),
],
).paddingAll(16.w),
),
],
);
height: isFoldable ? 36.h : 40.h,
).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading);
}
Widget getArrivedAppointmentButton(BuildContext context) {
@ -431,7 +488,8 @@ class _MedicalFileAppointmentCardState extends State<MedicalFileAppointmentCard>
SizedBox(height: 24.h),
// Countdown Timer - DD : HH : MM : SS format with labels
Directionality(
textDirection: ui.TextDirection.ltr, child:Row(
textDirection: ui.TextDirection.ltr,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -524,4 +582,3 @@ class _MedicalFileAppointmentCardState extends State<MedicalFileAppointmentCard>
}
}
}

@ -30,11 +30,7 @@ class MedicalFileCard extends StatelessWidget {
Widget build(BuildContext context) {
final iconS = iconSize ?? 30.w;
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: backgroundColor,
borderRadius: 20.r,
hasShadow: false
),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: backgroundColor, borderRadius: 20.r, hasShadow: false),
padding: EdgeInsets.all(12.w),
child: Column(
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:intl/intl.dart' show DateFormat;
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 '../../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:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -54,7 +52,6 @@ class SmartwatchHomePage extends StatelessWidget {
fontSize: 16.f,
isBold: true,
borderRadius: 12.r,
height: 50.h,
icon: AppAssets.ask_doctor_icon,
iconColor: AppColors.infoColor,
@ -69,11 +66,12 @@ class SmartwatchHomePage extends StatelessWidget {
child: GridView(
padding: EdgeInsets.zero,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 16.h,
mainAxisSpacing: 16.w,
mainAxisExtent: 240.h,
childAspectRatio: isFoldable ? 1.1 : (isTablet ? 1.3 : 0.7),
),
children: [
Container(
@ -83,14 +81,18 @@ class SmartwatchHomePage extends StatelessWidget {
),
child: Column(
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),
CustomButton(
text: LocaleKeys.selectSmartWatch.tr(context: context),
onPressed: () {
context.read<HealthProvider>().setSelectedWatchType(SmartWatchTypes.apple, "assets/images/png/smartwatches/apple-watch-5.jpg");
getIt.get<NavigationService>().pushPage(page: SmartwatchInstructionsPage(
smartwatchDetails: SmartwatchDetails(SmartWatchTypes.apple,
context
.read<HealthProvider>()
.setSelectedWatchType(SmartWatchTypes.apple, "assets/images/png/smartwatches/apple-watch-5.jpg");
getIt.get<NavigationService>().pushPage(
page: SmartwatchInstructionsPage(
smartwatchDetails: SmartwatchDetails(
SmartWatchTypes.apple,
"assets/images/png/smartwatches/apple-watch-5.jpg",
AppAssets.bluetooth,
LocaleKeys.applehealthapplicationshouldbeinstalledinyourphone.tr(context: context),
@ -110,26 +112,29 @@ class SmartwatchHomePage extends StatelessWidget {
),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
child: Column(
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),
CustomButton(
text: LocaleKeys.selectSmartWatch.tr(context: context),
onPressed: () {
context.read<HealthProvider>().setSelectedWatchType(SmartWatchTypes.samsung, "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg");
getIt.get<NavigationService>().pushPage(page: SmartwatchInstructionsPage(
smartwatchDetails: SmartwatchDetails(SmartWatchTypes.samsung,
context
.read<HealthProvider>()
.setSelectedWatchType(SmartWatchTypes.samsung, "assets/images/png/smartwatches/galaxy_watch_8_classic.jpeg");
getIt.get<NavigationService>().pushPage(
page: SmartwatchInstructionsPage(
smartwatchDetails: SmartwatchDetails(
SmartWatchTypes.samsung,
"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),
borderColor: AppColors.primaryRedColor.withAlpha(0),
textColor: AppColors.primaryRedColor,
@ -187,7 +192,6 @@ class SmartwatchHomePage extends StatelessWidget {
CustomButton(
text: LocaleKeys.selectSmartWatch.tr(context: context),
onPressed: () {
showUnavailableDialog(context);
// context.read<HealthProvider>().setSelectedWatchType(SmartWatchTypes.whoop, "assets/images/png/smartwatches/Whoop_Watch.png");
// getIt.get<NavigationService>().pushPage(page: SmartwatchInstructionsPage(
@ -221,7 +225,6 @@ class SmartwatchHomePage extends StatelessWidget {
}
void showUnavailableDialog(BuildContext context) {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
@ -231,8 +234,7 @@ class SmartwatchHomePage extends StatelessWidget {
showOkButton: true,
onConfirmTap: () async {
context.pop();
}
),
}),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,

@ -2,13 +2,10 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/dependencies.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/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/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -51,7 +48,12 @@ class SmartwatchInstructionsPage extends StatelessWidget {
mainAxisSize: MainAxisSize.max,
spacing: 18.h,
children: [
Image.asset(smartwatchDetails.watchIcon, fit: BoxFit.contain, height: 280.h,width: 280.w,),
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(
@ -60,7 +62,7 @@ class SmartwatchInstructionsPage extends StatelessWidget {
title: smartwatchDetails.detailsTitle,
description: smartwatchDetails.details,
icon: smartwatchDetails.smallIcon,
descriptionTextColor: AppColors.primaryRedColor
descriptionTextColor: AppColors.primaryRedColor,
),
Divider(
color: AppColors.dividerColor,
@ -70,7 +72,7 @@ class SmartwatchInstructionsPage extends StatelessWidget {
title: smartwatchDetails.secondTitle,
description: LocaleKeys.updatetheinformation.tr(),
icon: AppAssets.bluetooth,
descriptionTextColor: AppColors.greyTextColor
descriptionTextColor: AppColors.greyTextColor,
),
],
).paddingSymmetrical(16.w, 16.h),
@ -81,7 +83,6 @@ class SmartwatchInstructionsPage extends StatelessWidget {
);
}
Widget watchContentDetails({required String title, required String description, required String icon, required Color descriptionTextColor}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -90,9 +91,7 @@ class SmartwatchInstructionsPage extends StatelessWidget {
DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h),
child: Utils.buildSvgWithAssets(icon: icon, width: 40.w, height: 40.h),
),
title.toText16(isBold: true, color: AppColors.textColor),
description.toText12(isBold: true, color: descriptionTextColor)
],

@ -1,5 +1,6 @@
import 'package:easy_localization/easy_localization.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_export.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
@ -74,12 +75,17 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
loadingText: LocaleKeys.pleaseWait.tr(context: context),
);
final String userName = 'guest_user';
final String password = '123456';
// Get fileNo if user is logged in
String? fileNo;
if (_appState.isAuthenticated) {
final user = _appState.getAuthenticatedUser();
fileNo = user?.patientId.toString();
}
await viewModel.getSymptomsUserDetails(
userName: userName,
password: password,
userName: ApiConsts.symptomsCheckerUsername,
password: ApiConsts.symptomsCheckerPassword,
fileNo: fileNo,
onSuccess: () {
LoaderBottomSheet.hideLoader();
context.navigateWithName(AppRoutes.symptomsSelectorPage);

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/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/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/emergency_services/nearest_er_page.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: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 {
PossibleConditionsPage({super.key});
@override
State<PossibleConditionsPage> createState() => _PossibleConditionsPageState();
}
class _PossibleConditionsPageState extends State<PossibleConditionsPage> {
late SymptomsCheckerViewModel symptomsCheckerViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel;
late AppointmentViaRegionViewmodel regionalViewModel;
late AppState appState;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// Clear selections once user reaches results page
symptomsCheckerViewModel.clearSelectionsKeepResults();
});
}
Widget _buildLoadingShimmer() {
return ListView.separated(
shrinkWrap: true,
@ -114,13 +128,16 @@ class PossibleConditionsPage extends StatelessWidget {
return;
}
log("isBookingFromSymptomsChecker: ${symptomsCheckerViewModel.isBookingFromSymptomsChecker}");
// For non-emergency cases, continue with normal booking flow
LoaderBottomSheet.showLoader();
symptomsCheckerViewModel.getClinicConditionsFromCategory(
categoryName: (condition.conditionDetails!.category!.name) ?? "Other",
onSuccess: (value) {
LoaderBottomSheet.hideLoader();
print(symptomsCheckerViewModel.clinicDetailsList.first.clinicID);
debugPrint("isBookingFromSymptomsChecker:: ${symptomsCheckerViewModel.isBookingFromSymptomsChecker}");
debugPrint(symptomsCheckerViewModel.clinicDetailsList.first.clinicID.toString());
initiateBookAppointmentFlow(context);
},
onError: (err) {
@ -282,6 +299,9 @@ class PossibleConditionsPage extends StatelessWidget {
}
initiateBookAppointmentFlow(BuildContext context) {
// Set flag to indicate booking is from symptoms checker
symptomsCheckerViewModel.setBookingFromSymptomsChecker(true);
// bookAppointmentsViewModel.getLocation();
bookAppointmentsViewModel.setSelectedClinic(GetClinicsListResponseModel(
clinicID: symptomsCheckerViewModel.clinicDetailsList.first.clinicID,
@ -307,6 +327,10 @@ class PossibleConditionsPage extends StatelessWidget {
),
).onPress(() {
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.setBottomSheetType(type);
// AppointmentViaRegionViewmodel? viewmodel = null;
showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) => getTitle(data, context)), isDismissible: false,
child: Consumer<AppointmentViaRegionViewmodel>(builder: (context, data, __) {
showCommonBottomSheetWithoutHeight(context,
title: "",
titleWidget: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) => getTitle(data, context)),
isDismissible: false, child: Consumer<AppointmentViaRegionViewmodel>(builder: (context, data, __) {
return getRegionalSelectionWidget(data, context);
}), callBackFunc: () {});
}
@ -356,7 +382,7 @@ class PossibleConditionsPage extends StatelessWidget {
}
},
onHospitalSearch: (value) {
data.searchHospitals(value ?? "");
data.searchHospitals(value);
},
selectedFacility: data.selectedFacility,
hmcCount: data.hmcCount,
@ -378,7 +404,6 @@ class PossibleConditionsPage extends StatelessWidget {
} else {
return SizedBox.shrink();
}
return SizedBox.shrink();
}
void _handleSortByLocationToggle(bool value, AppointmentViaRegionViewmodel regionVM) {

@ -1,5 +1,6 @@
import 'package:easy_localization/easy_localization.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_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
@ -28,12 +29,20 @@ class SymptomsSelectorPage extends StatefulWidget {
class _SymptomsSelectorPageState extends State<SymptomsSelectorPage> {
late DialogService dialogService;
late AppState _appState;
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
dialogService = getIt<DialogService>();
_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
WidgetsBinding.instance.addPostFrameCallback((_) {
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) {
if (viewModel.hasSelectedSymptoms) {
// Navigate to triage screen
@ -100,7 +115,56 @@ class _SymptomsSelectorPageState extends State<SymptomsSelectorPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
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
String? organId;
String? organName;

@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -175,7 +173,37 @@ class _TriagePageState extends State<TriagePage> {
return;
}
// Collect all evidence from all items
// Collect evidence based on question type
if (viewModel.isTriageQuestionSingleSelection) {
// Type 1: Single selection - only one evidence entry with "Yes" choice
final selectedItemId = viewModel.selectedSingleItemId;
if (selectedItemId != null) {
// Find the item and its "Yes" choice
for (var item in currentQuestion.items!) {
if (item.id == selectedItemId) {
// Find the "Yes" choice (case-insensitive)
String? yesChoiceId;
if (item.choices != null) {
for (var choice in item.choices!) {
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;
@ -192,14 +220,12 @@ class _TriagePageState extends State<TriagePage> {
}
}
}
}
// Get all evidence: initial symptoms + risk factors + suggestions + triage evidence
List<String> initialEvidenceIds = viewModel.getAllEvidenceIds();
List<Map<String, String>> triageEvidence = viewModel.getTriageEvidence();
log("initialEvidenceIds: ${initialEvidenceIds.toString()}");
log("triageEvidence: ${triageEvidence.toString()}");
// Call API with updated evidence
viewModel.getDiagnosisForTriage(
age: viewModel.selectedAge!,
@ -376,7 +402,45 @@ class _TriagePageState extends State<TriagePage> {
(question.text ?? "").toText16(isBold: true, color: AppColors.textColor),
SizedBox(height: 24.h),
// Show all items with dividers
// Type 1: Show items as checkboxes only (no choices displayed)
if (viewModel.isTriageQuestionSingleSelection) ...[
...List.generate(question.items!.length, (itemIndex) {
final item = question.items![itemIndex];
final itemId = item.id ?? "";
final itemName = item.name ?? "";
final isSelected = viewModel.selectedSingleItemId == itemId;
return GestureDetector(
onTap: () => _onOptionSelectedForItem(itemId, 0), // Pass 0 as placeholder
child: Container(
margin: EdgeInsets.only(bottom: 12.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: 24.w,
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 ?? "";
@ -390,7 +454,10 @@ class _TriagePageState extends State<TriagePage> {
SizedBox(height: 8.h),
// Choices for this item
...List.generate(choices.length, (choiceIndex) {
bool selected = viewModel.getTriageChoiceForItem(itemId) == 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 ?? "");
}),
@ -404,6 +471,7 @@ class _TriagePageState extends State<TriagePage> {
);
}),
],
],
),
),
);
@ -472,14 +540,8 @@ class _TriagePageState extends State<TriagePage> {
children: [
TextSpan(
text: suggestedCondition,
style: TextStyle(
color: AppColors.textColor,
fontWeight: FontWeight.w600,
fontSize: 14.f, fontFamily: isArabic ? 'CairoArabic' : 'Poppins'),
color: AppColors.textColor, fontWeight: FontWeight.w600, fontSize: 14.f, fontFamily: isArabic ? 'CairoArabic' : 'Poppins'),
),
],
),

@ -99,9 +99,8 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title.toText14(isBold: true),
subTitle
.toText12(color: AppColors.primaryRedColor, isBold: true, isEnglishOnly: true)
.toShimmer2(isShow: (leadingIcon == AppAssets.rulerIcon || leadingIcon == AppAssets.weightScale) && hmgServicesVM.isVitalSignLoading),
subTitle.toText12(color: AppColors.primaryRedColor, isBold: true, isEnglishOnly: true).toShimmer2(
isShow: (leadingIcon == AppAssets.rulerIcon || leadingIcon == AppAssets.weightScale) && hmgServicesVM.isVitalSignLoading),
],
),
],
@ -159,7 +158,9 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
}
}
} 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(
@ -167,7 +168,10 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
body: Consumer2<SymptomsCheckerViewModel, HmgServicesViewModel>(
builder: (context, viewModel, hmgServicesVM, child) {
// 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
String genderText = _getLocalizedGender(viewModel.selectedGender, context);
@ -313,6 +317,7 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
),
],
),
SizedBox(height: 24.h),
],
).paddingSymmetrical(24.w, 0),
),

@ -55,7 +55,8 @@ class HeightSelectionPage extends StatelessWidget {
style: TextStyle(
fontWeight: FontWeight.w700,
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"),
),
),
),
@ -76,7 +77,8 @@ class HeightSelectionPage extends StatelessWidget {
style: TextStyle(
fontWeight: FontWeight.w700,
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(
text:
viewModel.isHeightCm ? viewModel.selectedHeight?.round().toString() : viewModel.selectedHeight?.toStringAsFixed(1),
style: TextStyle(
fontSize: 90.f,
color: AppColors.textColor,
height: 1, fontFamily: "Poppins"),
style: TextStyle(fontSize: 90.f, color: AppColors.textColor, height: 1, fontFamily: "Poppins"),
),
TextSpan(
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,
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(
top: false,
child: isSingleEdit

@ -134,9 +134,8 @@ class _HeightScaleState extends State<HeightScale> {
fontSize: 11.f,
color: AppColors.greyTextColor,
fontWeight: FontWeight.w600,
height: 1,
fontFamily: "Poppins"
),
height: isFoldable ? 0.5 : 1,
fontFamily: "Poppins"),
textAlign: TextAlign.right,
),
)

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:ui' as ui;
import 'package:collection/collection.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:provider/provider.dart';
import 'dart:ui' as ui;
class AncillaryOrderDetailsList extends StatefulWidget {
final int appointmentNoVida;
final int orderNo;
@ -653,13 +652,15 @@ class _AncillaryOrderDetailsListState extends State<AncillaryOrderDetailsList> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: 200.h,
width: isFoldable ? 220.w : 200.w,
child: Utils.getPaymentMethods(),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
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),
);
// 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/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/smartwatches/huawei_health_example.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/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/qr_parking/qr_parking_view_model.dart';
import '../presentation/parking/paking_page.dart';
import '../presentation/smartwatches/smartwatch_instructions_page.dart';
import '../services/error_handler_service.dart';
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),
child: Transform.flip(
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(() {
if (leadingCallback != null) {
@ -288,7 +289,8 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
return Container(
// height: (widget.preferredSize.height - _fontSize / 2).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),
child: Row(
spacing: 4.h,
@ -304,19 +306,38 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
),
).expanded,
...[
if (widget.logout != null) actionButton(context, t, title: LocaleKeys.logout.tr(context: context), icon: AppAssets.logout).onPress(widget.logout!),
if (widget.report != null) actionButton(context, t, title: LocaleKeys.feedback.tr(context: context), icon: AppAssets.report_icon).onPress(widget.report!),
if (widget.history != null) actionButton(context, t, title: LocaleKeys.history.tr(context: context), icon: AppAssets.insurance_history_icon).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.logout != null)
actionButton(context, t, title: LocaleKeys.logout.tr(context: context), icon: AppAssets.logout).onPress(widget.logout!),
if (widget.report != null)
actionButton(context, t, title: LocaleKeys.feedback.tr(context: context), icon: AppAssets.report_icon).onPress(widget.report!),
if (widget.history != null)
actionButton(context, t, title: LocaleKeys.history.tr(context: context), icon: AppAssets.insurance_history_icon)
.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.aiOverview != null) actionButton(context, t, title: LocaleKeys.aiOverView.tr(context: context), icon: AppAssets.aiOverView, isAiButton: true).onPress(widget.aiOverview!),
if (widget.downloadReport != null) 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.aiOverview != null)
actionButton(context, t, title: LocaleKeys.aiOverView.tr(context: context), icon: AppAssets.aiOverView, isAiButton: true)
.onPress(widget.aiOverview!),
if (widget.downloadReport != null)
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!,
]
],
@ -330,9 +351,10 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
duration: Duration(milliseconds: 150),
child: Center(
child: Container(
height: 40.h,
height: isFoldable ? 50.h : 40.h,
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(
blendMode: BlendMode.srcIn,
shaderCallback: (bounds) => AppColors.aiLinearGradient.createShader(bounds),
@ -365,7 +387,7 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
: AnimatedSize(
duration: Duration(milliseconds: 150),
child: Container(
height: 40.h,
height: isFoldable ? 50.h : 40.h,
padding: EdgeInsets.all(8.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.secondaryLightRedColor,

Loading…
Cancel
Save