Merge pull request 'Dynamic Theming' (#380) from national_day_theme into master

Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/380
pull/381/head
Haroon6138 3 weeks ago
commit 3704d2d9d5

@ -0,0 +1,12 @@
{
"key": "none",
"enabled": false,
"start_date": null,
"end_date": null,
"primary_color": null,
"secondary_color": null,
"background_opacity_image": null,
"background_image_opacity": 0.2,
"header_image": null
}

@ -20,7 +20,5 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>

@ -29,6 +29,7 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -90,6 +91,7 @@
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A83C76CFF9D3F92EDE3B22B4 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -97,6 +99,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
76B1D2512F91BF8000DBBD1B /* Penguin.xcframework in Frameworks */,
76B1D2552F91BF8000DBBD1B /* PenNavUI.xcframework in Frameworks */,
76B1D2532F91BF8000DBBD1B /* PenguinINRenderer.xcframework in Frameworks */,
@ -159,6 +162,7 @@
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
@ -230,6 +234,9 @@
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
packageProductDependencies = (
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
);
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
@ -256,6 +263,9 @@
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
);
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
@ -339,14 +349,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
@ -360,14 +366,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
@ -798,6 +800,18 @@
defaultConfigurationName = Debug;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;
productName = FlutterGeneratedPluginSwiftPackage;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

@ -5,6 +5,24 @@
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Prepare Flutter Framework Script"
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"

@ -138,8 +138,7 @@ class ApiClientImp implements ApiClient {
} else {}
if (body.containsKey('isDentalAllowedBackend')) {
body['isDentalAllowedBackend'] =
body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND;
body['isDentalAllowedBackend'] = body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND;
}
if (!body.containsKey('IsPublicRequest')) {
@ -271,11 +270,11 @@ class ApiClientImp implements ApiClient {
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'POST');
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_socket_exception');
return;
@ -283,19 +282,19 @@ class ApiClientImp implements ApiClient {
final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr();
onFailure(message, -1, failureType: ConnectivityFailure(message));
_analytics.errorTracking.log(endPoint, error: "ClientException: $e");
// Enhanced Clarity logging
Clarity.setCustomTag('api_err_type', 'client_exception');
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'POST');
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_client_exception');
return;
} on TimeoutException catch (_) {
final message = LocaleKeys.networkTimeout.tr();
@ -307,36 +306,36 @@ class ApiClientImp implements ApiClient {
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'POST');
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_timeout');
return;
} catch (e) {
final message = LocaleKeys.networkUnknownError.tr();
onFailure(message, -1, failureType: ConnectivityFailure(message));
_analytics.errorTracking.log(endPoint, error: "UnknownException: $e");
// Enhanced Clarity logging for unknown errors
Clarity.setCustomTag('api_err_type', 'unknown_exception');
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'POST');
// Truncate exception message for Clarity
final exceptionMsg = e.toString();
final truncatedMsg = exceptionMsg.length > 252 ? '...${exceptionMsg.substring(exceptionMsg.length - 252)}' : exceptionMsg;
Clarity.setCustomTag('api_err_exception', truncatedMsg);
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_unknown_exception');
return;
}
@ -384,24 +383,19 @@ class ApiClientImp implements ApiClient {
onFailure(
parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'],
statusCode,
failureType:
UnAuthenticatedUserFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'] ?? "User is not Authenticated", url: url),
failureType: UnAuthenticatedUserFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'] ?? "User is not Authenticated", url: url),
);
// logApiEndpointError(endPoint, "session logged out", statusCode);
}
if (isAllowAny) {
onSuccess(parsed, statusCode,
messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
} else if (parsed['IsAuthenticated'] == null) {
if (parsed['isSMSSent'] == true) {
onSuccess(parsed, statusCode,
messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
} else if (parsed['MessageStatus'] == 1) {
onSuccess(parsed, statusCode,
messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
} else if (parsed['Result'] == 'OK') {
onSuccess(parsed, statusCode,
messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
} else {
onFailure(
parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'],
@ -411,8 +405,7 @@ class ApiClientImp implements ApiClient {
logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode);
}
} else if (parsed['MessageStatus'] == 1 || parsed['SMSLoginRequired'] == true) {
onSuccess(parsed, statusCode,
messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
}
// else if (parsed['IsAuthenticated'] == false) {
// onFailure(
@ -424,8 +417,7 @@ class ApiClientImp implements ApiClient {
// }
else if (parsed['MessageStatus'] == 2 && parsed['IsAuthenticated']) {
if (parsed['SameClinicApptList'] != null) {
onSuccess(parsed, statusCode,
messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
} else {
if (parsed['message'] == null && parsed['ErrorEndUserMessage'] == null) {
if (parsed['ErrorSearchMsg'] == null) {
@ -459,8 +451,7 @@ class ApiClientImp implements ApiClient {
}
} else {
if (parsed['SameClinicApptList'] != null) {
onSuccess(parsed, statusCode,
messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']);
} else {
if (parsed['message'] != null) {
onFailure(
@ -537,74 +528,74 @@ class ApiClientImp implements ApiClient {
final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr();
onFailure(message, -1);
_analytics.errorTracking.log(endPoint, error: "SocketException: $e");
// Clarity logging for GET requests
Clarity.setCustomTag('api_err_type', 'socket_exception');
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'GET');
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_get_socket_exception');
return;
} on http.ClientException catch (e) {
final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr();
onFailure(message, -1);
_analytics.errorTracking.log(endPoint, error: "ClientException: $e");
Clarity.setCustomTag('api_err_type', 'client_exception');
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'GET');
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_get_client_exception');
return;
} on TimeoutException catch (_) {
final message = LocaleKeys.networkTimeout.tr();
onFailure(message, -1);
_analytics.errorTracking.log(endPoint, error: "TimeoutException");
Clarity.setCustomTag('api_err_type', 'timeout');
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'GET');
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_get_timeout');
return;
} catch (e) {
final message = LocaleKeys.networkUnknownError.tr();
onFailure(message, -1);
_analytics.errorTracking.log(endPoint, error: "UnknownException: $e");
Clarity.setCustomTag('api_err_type', 'unknown_exception');
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'GET');
final exceptionMsg = e.toString();
final truncatedMsg = exceptionMsg.length > 252 ? '...${exceptionMsg.substring(exceptionMsg.length - 252)}' : exceptionMsg;
Clarity.setCustomTag('api_err_exception', truncatedMsg);
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_get_unknown_exception');
return;
}
@ -614,18 +605,18 @@ class ApiClientImp implements ApiClient {
if (statusCode < 200 || statusCode >= 400) {
onFailure(LocaleKeys.networkErrorWhileFetching.tr(), statusCode);
logApiEndpointError(endPoint, LocaleKeys.networkErrorWhileFetching.tr(), statusCode);
// Clarity logging for GET status code errors
Clarity.setCustomTag('api_err_type', 'status_code_error');
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'GET');
Clarity.setCustomTag('api_err_status', statusCode.toString());
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_get_status_error');
} else {
var parsed = json.decode(utf8.decode(response.bodyBytes));
@ -813,7 +804,7 @@ class ApiClientImp implements ApiClient {
// Extract the last part of the endpoint path for cleaner tracking
const int clarityLimit = 100;
final cleanEndpoint = endpoint.split('/').last;
if (cleanEndpoint.length <= clarityLimit) return cleanEndpoint;
return '...${cleanEndpoint.substring(cleanEndpoint.length - (clarityLimit - 3))}';
}

@ -879,8 +879,7 @@ 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=';

@ -85,7 +85,9 @@ import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/services/notification_service.dart';
import 'package:hmg_patient_app_new/services/permission_service.dart';
import 'package:hmg_patient_app_new/services/remote_config_service.dart';
import 'package:hmg_patient_app_new/services/security_service.dart';
import 'package:hmg_patient_app_new/features/occasion_theme/occasion_theme_view_model.dart';
import 'package:hmg_patient_app_new/core/services/turnstile_service.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart';
@ -390,5 +392,13 @@ class AppDependencies {
getIt.registerLazySingleton<ClarityService>(
() => ClarityService(appState: getIt.get<AppState>()),
);
// Remote-config-driven "occasion" theming (National Day, Eid, etc.).
getIt.registerLazySingleton<RemoteConfigService>(() => RemoteConfigServiceImp(loggerService: getIt()));
getIt.registerLazySingleton<OccasionThemeViewModel>(() => OccasionThemeViewModel(
remoteConfigService: getIt(),
cacheService: getIt(),
loggerService: getIt(),
));
}
}

@ -437,8 +437,8 @@ class Utils {
onCancelTap();
}
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
backgroundColor: AppColors.criticalActionColor,
borderColor: AppColors.criticalActionColor,
textColor: AppColors.whiteColor,
// icon: AppAssets.cancel,
// iconColor: AppColors.whiteColor,
@ -516,8 +516,8 @@ class Utils {
onCancelTap();
}
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
backgroundColor: AppColors.criticalActionColor,
borderColor: AppColors.criticalActionColor,
textColor: AppColors.whiteColor,
// icon: AppAssets.cancel,
// iconColor: AppColors.whiteColor,

@ -0,0 +1,123 @@
import 'package:flutter/material.dart';
/// Model representing a remotely-configurable "occasion" theme
/// (e.g. Saudi National Day, Founding Day, Eid, Ramadan, ...).
///
/// Everything here is fully data-driven — nothing about a specific
/// occasion (name, color, dates, banner asset) is hardcoded in the app.
/// All values are supplied via Firebase Remote Config (or the bundled
/// default JSON fallback used before the first successful fetch).
@immutable
class OccasionConfigModel {
/// Unique identifier of the occasion (e.g. "national_day"). Used only
/// for logging/analytics — no app logic branches on this value.
final String key;
/// Master kill-switch. When false, the app must look exactly like the
/// default/current theme, regardless of any other field below.
final bool enabled;
/// Optional activation window. When both are present, the theme is only
/// considered active if `DateTime.now()` falls within [startDate, endDate].
/// When null, the [enabled] flag alone controls activation.
final DateTime? startDate;
final DateTime? endDate;
/// Hex string (e.g. "#00894A") for the occasion's primary brand color.
/// When null, the app's default primary color is preserved.
final String? primaryColorHex;
/// Remote (or bundled asset path) image rendered as a decorative
/// background/watermark, drawn using [backgroundImageOpacity].
final String? backgroundOpacityImage;
/// Opacity (0.0 - 1.0) applied to [backgroundOpacityImage].
final double backgroundImageOpacity;
/// Remote (or bundled asset path) image rendered as the occasion's main
/// header/banner (e.g. the National Day greeting banner), drawn at full
/// opacity on top of [backgroundOpacityImage].
final String? headerImage;
const OccasionConfigModel({
required this.key,
required this.enabled,
this.startDate,
this.endDate,
this.primaryColorHex,
this.backgroundOpacityImage,
this.backgroundImageOpacity = 1.0,
this.headerImage,
});
/// Fully inert config — identical to the app's current/default look.
factory OccasionConfigModel.disabled() => const OccasionConfigModel(
key: 'none',
enabled: false,
startDate: null,
endDate: null,
primaryColorHex: null,
backgroundOpacityImage: null,
backgroundImageOpacity: 1.0,
headerImage: null,
);
factory OccasionConfigModel.fromJson(Map<String, dynamic> json) {
return OccasionConfigModel(
key: (json['key'] as String?) ?? 'none',
enabled: (json['enabled'] as bool?) ?? false,
startDate: _parseDate(json['start_date']),
endDate: _parseDate(json['end_date']),
primaryColorHex: json['primary_color'] as String?,
backgroundOpacityImage: json['background_opacity_image'] as String?,
backgroundImageOpacity: (json['background_image_opacity'] as num?)?.toDouble() ?? 1.0,
headerImage: json['header_image'] as String?,
);
}
static DateTime? _parseDate(dynamic value) {
if (value == null) return null;
if (value is String && value.isNotEmpty) {
return DateTime.tryParse(value);
}
return null;
}
/// Whether "now" falls inside the configured activation window.
/// If no window is configured, the [enabled] flag alone decides.
bool isWithinActiveWindow([DateTime? now]) {
final current = now ?? DateTime.now();
if (startDate != null && current.isBefore(startDate!)) return false;
if (endDate != null && current.isAfter(endDate!)) return false;
return true;
}
/// Parsed [Color] from [primaryColorHex], or null if not configured.
Color? get primaryColor => _hexToColor(primaryColorHex);
/// Lighter/translucent variant of [primaryColor] — 11% opacity of the same
/// hue (e.g. for chip/badge backgrounds). Derived automatically from
/// [primaryColor]; not an independent remote-config field. Null unless
/// [primaryColor] is configured.
Color? get secondaryColor => primaryColor?.withValues(alpha: 0.11);
static Color? _hexToColor(String? hex) {
if (hex == null || hex.isEmpty) return null;
var value = hex.trim().replaceFirst('#', '');
if (value.length == 6) value = 'FF$value';
final parsed = int.tryParse(value, radix: 16);
return parsed != null ? Color(parsed) : null;
}
Map<String, dynamic> toJson() => {
'key': key,
'enabled': enabled,
'start_date': startDate?.toIso8601String(),
'end_date': endDate?.toIso8601String(),
'primary_color': primaryColorHex,
'background_opacity_image': backgroundOpacityImage,
'background_image_opacity': backgroundImageOpacity,
'header_image': headerImage,
};
}

@ -0,0 +1,109 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart' show Color;
import 'package:hmg_patient_app_new/features/occasion_theme/occasion_config_model.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/remote_config_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
/// Drives the fully remote-config-based "occasion" theming (National Day,
/// Founding Day, Eid, Ramadan, ...). Nothing about a specific occasion is
/// hardcoded here — every color/asset/date comes from [OccasionConfigModel],
/// which is parsed from Firebase Remote Config.
///
/// Mirrors the existing dark-mode pattern in ProfileSettingsViewModel:
/// a static flag on [AppColors] is kept in sync so every widget using
/// AppColors.primaryRedColor (even ones with no BuildContext/Provider
/// access) reflects the active occasion instantly.
class OccasionThemeViewModel extends ChangeNotifier {
static const String _cachedConfigKey = 'cached_occasion_config_json';
final RemoteConfigService _remoteConfigService;
final CacheService _cacheService;
final LoggerService _loggerService;
OccasionThemeViewModel({
required RemoteConfigService remoteConfigService,
required CacheService cacheService,
required LoggerService loggerService,
}) : _remoteConfigService = remoteConfigService,
_cacheService = cacheService,
_loggerService = loggerService;
OccasionConfigModel _config = OccasionConfigModel.disabled();
OccasionConfigModel get config => _config;
/// True only when enabled AND (if a window is configured) within it.
bool get isOccasionActive => _config.enabled && _config.isWithinActiveWindow();
String? get backgroundOpacityImage => isOccasionActive ? _config.backgroundOpacityImage : null;
double get backgroundImageOpacity => _config.backgroundImageOpacity;
/// The occasion's main header/banner image (e.g. National Day greeting
/// banner), rendered at full opacity. Null unless an occasion is active.
String? get headerImage => isOccasionActive ? _config.headerImage : null;
/// Lighter/translucent variant of the active occasion's primary color
/// (11% opacity) — e.g. for chip/badge backgrounds. Null unless an
/// occasion is active.
Color? get secondaryColor => isOccasionActive ? _config.secondaryColor : null;
/// Call once at app startup (before the first frame) to restore the last
/// known-good config instantly (offline-safe), then kick off a remote
/// fetch in the background.
Future<void> initialize() async {
_restoreCachedConfig();
_applyToAppColors();
try {
await _remoteConfigService.initialize();
await refresh();
} catch (ex) {
_loggerService.logError('OccasionThemeViewModel.initialize error: $ex');
}
}
/// Fetches the latest occasion config from Firebase Remote Config and
/// applies it. Safe to call again later (e.g. on app resume) to pick up
/// changes without requiring an app update.
Future<void> refresh() async {
try {
await _remoteConfigService.fetchAndActivate();
final rawJson = _remoteConfigService.occasionConfigRawJson;
_config = OccasionConfigModel.fromJson(json.decode(rawJson) as Map<String, dynamic>);
await _cacheService.saveString(key: _cachedConfigKey, value: rawJson);
_applyToAppColors();
notifyListeners();
} catch (ex) {
_loggerService.logError('OccasionThemeViewModel.refresh error: $ex');
// Keep whatever config was already applied (cached/disabled) — never
// let a remote-config failure change the app's appearance unexpectedly.
}
}
void _restoreCachedConfig() {
try {
final cachedJson = _cacheService.getString(key: _cachedConfigKey);
if (cachedJson == null || cachedJson.isEmpty) {
_config = OccasionConfigModel.disabled();
return;
}
_config = OccasionConfigModel.fromJson(json.decode(cachedJson) as Map<String, dynamic>);
} catch (ex) {
_loggerService.logError('OccasionThemeViewModel._restoreCachedConfig error: $ex');
_config = OccasionConfigModel.disabled();
}
}
/// Keeps AppColors in sync so non-Provider call sites (AppColors.primaryRedColor)
/// pick up the occasion color exactly like the existing dark-mode flag does.
void _applyToAppColors() {
AppColors.isOccasionThemeActive = isOccasionActive;
AppColors.occasionPrimaryColor = _config.primaryColor;
AppColors.occasionSecondaryColor = _config.secondaryColor;
}
}

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
@ -37,6 +38,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
import 'package:hmg_patient_app_new/features/myfatoorah/myfatoorah_view_model.dart';
import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart';
import 'package:hmg_patient_app_new/features/occasion_theme/occasion_theme_view_model.dart';
import 'package:hmg_patient_app_new/features/offers_and_discounts/offers_and_discounts_view_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart';
@ -167,6 +169,14 @@ Future<void> callInitializations() async {
// Restore persisted accessibility settings before the first frame.
getIt.get<ProfileSettingsViewModel>().loadAccessibilitySettings();
// Restore last-known occasion theme instantly (offline-safe, synchronous)
// so the very first frame is already correct, then let the Remote Config
// fetch happen in the background without blocking app startup. Fully
// disabled by default — the app looks exactly as it does today until
// Remote Config enables an occasion (e.g. National Day) on the server side.
final occasionThemeViewModel = getIt.get<OccasionThemeViewModel>();
unawaited(occasionThemeViewModel.initialize());
}
void main() async {
@ -309,7 +319,10 @@ void main() async {
),
ChangeNotifierProvider<RefundRequestViewModel>(
create: (_) => getIt.get<RefundRequestViewModel>(),
)
),
ChangeNotifierProvider<OccasionThemeViewModel>(
create: (_) => getIt.get<OccasionThemeViewModel>(),
),
], child: MyApp()),
), // Wrap your app
);
@ -377,11 +390,11 @@ class MyApp extends StatelessWidget {
return Sizer(
builder: (context, orientation, deviceType) {
// SizeConfig().init(constraints, orientation);
return Consumer<ProfileSettingsViewModel>(
builder: (context, profileVm, _) {
return Consumer2<ProfileSettingsViewModel, OccasionThemeViewModel>(
builder: (context, profileVm, occasionThemeVm, _) {
final isArabic = EasyLocalization.of(context)?.locale.languageCode == "ar";
return MaterialApp(
key: ValueKey('app_theme_${profileVm.isDarkMode}'),
key: ValueKey('app_theme_${profileVm.isDarkMode}_${occasionThemeVm.isOccasionActive}_${occasionThemeVm.config.key}'),
// title: 'Dr. AlHabib',
title: 'Dr. AlHabib Beta',
builder: (context, mchild) {

@ -989,7 +989,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
openDoctorScheduleCalendar();
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
borderColor: Colors.transparent,
textColor: Colors.white,
fontSize: 16.f,
fontWeight: FontWeight.w600,
@ -1097,7 +1097,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
openDoctorScheduleCalendar();
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
borderColor: Colors.transparent,
textColor: Colors.white,
fontSize: 16.f,
fontWeight: FontWeight.w600,

@ -67,8 +67,8 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
SizedBox(height: 16.h),
CustomTabBar(
initialIndex: myAppointmentsVM.selectedTabIndex,
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
activeTextColor: AppColors.primaryRedColor,
activeBackgroundColor: AppColors.secondaryLightRedColor,
tabs: [
// CustomTabBarModel(null, LocaleKeys.allAppt.tr(context: context)),
CustomTabBarModel(null, LocaleKeys.upcoming.tr(context: context)),
@ -269,9 +269,9 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
),
);
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
isBold: true,
borderRadius: 12.r,

@ -84,8 +84,7 @@ class AppointmentDoctorCard extends StatelessWidget {
children: [
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),
@ -102,11 +101,7 @@ class AppointmentDoctorCard extends StatelessWidget {
children: [
Expanded(
child: (patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText16(
isBold: true,
isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? ""),
textOverflow: TextOverflow.ellipsis,
maxlines: 2
),
isBold: true, isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? ""), textOverflow: TextOverflow.ellipsis, maxlines: 2),
),
SizedBox(width: 12.w),
// (patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null &&
@ -142,18 +137,17 @@ 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)} ${(patientAppointmentHistoryResponseModel.startTime != null && patientAppointmentHistoryResponseModel.startTime!.isNotEmpty) ? patientAppointmentHistoryResponseModel.startTime!.substring(0, 5) : DateUtil.formatDateToTimeLang(DateUtil.convertStringToDateSaudiTimezone(patientAppointmentHistoryResponseModel.appointmentDate!, patientAppointmentHistoryResponseModel.projectID), false)}"
.toText10(isBold: true),
richText:
"${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${(patientAppointmentHistoryResponseModel.startTime != null && patientAppointmentHistoryResponseModel.startTime!.isNotEmpty) ? patientAppointmentHistoryResponseModel.startTime!.substring(0, 5) : DateUtil.formatDateToTimeLang(DateUtil.convertStringToDateSaudiTimezone(patientAppointmentHistoryResponseModel.appointmentDate!, patientAppointmentHistoryResponseModel.projectID), false)}"
.toText10(isBold: true),
),
),
),
AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w),
icon: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppAssets.walkin_appointment_icon
: AppAssets.small_livecare_icon,
icon: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon,
iconColor: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppColors.textColor : Colors.white,
labelText: (patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? LocaleKeys.livecare.tr(context: context)
: LocaleKeys.walkin.tr(context: context),
labelText: (patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context),
backgroundColor: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppColors.greyColor : AppColors.successColor,
textColor: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppColors.textColor : Colors.white,
),
@ -163,13 +157,12 @@ class AppointmentDoctorCard extends StatelessWidget {
),
),
(patientAppointmentHistoryResponseModel.isLiveCareAppointment! ||
patientAppointmentHistoryResponseModel.isExecludeDoctor! ||
patientAppointmentHistoryResponseModel.isExecludeDoctor! ||
patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! == false ||
patientAppointmentHistoryResponseModel.isActiveDoctor! == false ||
!Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID))
!Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID))
? 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;
@ -270,7 +263,7 @@ class AppointmentDoctorCard extends StatelessWidget {
onAskDoctorTap();
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
isBold: true,
@ -310,7 +303,7 @@ class AppointmentDoctorCard extends StatelessWidget {
onCancelTap();
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
borderColor: Colors.transparent,
textColor: AppColors.whiteColor,
fontSize: 14.f,
isBold: true,
@ -329,7 +322,7 @@ class AppointmentDoctorCard extends StatelessWidget {
onRescheduleTap();
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
isBold: true,

@ -142,9 +142,9 @@ class _AppointmentRatingWidgetState extends State<AppointmentRatingWidget> {
Expanded(
child: CustomButton(
text: LocaleKeys.later.tr(context: context),
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
onPressed: () {
Navigator.pop(context);
},

@ -94,17 +94,18 @@ class QuickLoginState extends State<QuickLogin> {
mainAxisAlignment: MainAxisAlignment.end,
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.enableQuickLoginBtn.tr(context: context),
onPressed: () {
widget.onPressed();
},
backgroundColor: Color(0xffED1C2B),
borderColor: Color(0xffED1C2B),
textColor: Colors.white,
icon: AppAssets.apple_finder,
height: 56.h,
)),
child: CustomButton(
text: LocaleKeys.enableQuickLoginBtn.tr(context: context),
onPressed: () {
widget.onPressed();
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: Colors.white,
icon: AppAssets.apple_finder,
height: 56.h,
),
),
],
),
SizedBox(
@ -120,9 +121,9 @@ class QuickLoginState extends State<QuickLogin> {
cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: false);
Navigator.pop(context, "true");
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Colors.red,
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
height: 56.h,
// icon: "assets/images/svg/apple-finder.svg",
)),

@ -327,9 +327,9 @@ class _SavedLogin extends State<SavedLogin> with RouteAware {
// MaterialPageRoute(builder: (BuildContext context) => LandingNavigation())
// );
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 16.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
@ -352,9 +352,9 @@ class _SavedLogin extends State<SavedLogin> with RouteAware {
MaterialPageRoute(builder: (BuildContext context) => LoginScreen()),
);
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 15.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,

@ -232,7 +232,7 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
Container(
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.bgRedLightColor.withValues(alpha: 0.1),
color: AppColors.secondaryLightRedColorBase.withValues(alpha: 0.1),
borderRadius: 12.r,
hasShadow: false,
),

@ -106,8 +106,8 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
children: [
SizedBox(height: 16.h),
CustomTabBar(
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
activeTextColor: AppColors.primaryRedColor,
activeBackgroundColor: AppColors.secondaryLightRedColor,
initialIndex: bookAppointmentsVM.selectedTabIndex,
tabs: [
CustomTabBarModel(null, LocaleKeys.general.tr(context: context)),
@ -326,9 +326,9 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
onPressed: () {
Navigator.of(context).push(CustomPageRoute(page: SearchDoctorByName()));
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
isBold: true,
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),

@ -60,9 +60,7 @@ 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();
},
@ -101,14 +99,11 @@ class DoctorProfilePage extends StatelessWidget {
children: [
SizedBox(
width: isFoldable ? 250.w : 220.w,
child:
("${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorTitleForProfile} ${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName}")
.toString()
.toText24(isBold: true),
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),
],
@ -148,16 +143,12 @@ 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();
@ -173,18 +164,12 @@ 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("en_US")
.format(bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate ?? 0)
.toText16(
isBold: true,
color: AppColors.textColor,
isUnderLine: true,
decorationColor: AppColors.textColor,
fontFamily: "Poppins"),
.toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"),
],
).onPress(() {
bookAppointmentsViewModel.getDoctorRatingDetails();
@ -215,9 +200,7 @@ 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),
@ -238,8 +221,7 @@ 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;

@ -80,8 +80,8 @@ class LaserAppointment extends StatelessWidget {
child: Column(
children: [
CustomTabBar(
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
activeTextColor: AppColors.primaryRedColor,
activeBackgroundColor: AppColors.secondaryLightRedColor,
tabs: [
CustomTabBarModel(null, LocaleKeys.malE.tr()),
CustomTabBarModel(null, LocaleKeys.femaleGender.tr()),

@ -311,9 +311,9 @@ class DoctorCard extends StatelessWidget {
});
}
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
isBold: true,
borderRadius: 10.r,

@ -9,192 +9,190 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
class LandingPageData {
static List<ServiceCardData> get getNotLoggedInServiceCardsList => [
ServiceCardData(
serviceName: "emergency",
icon: AppAssets.emergency_services_icon,
title: getIt.get<AppState>().isArabic()?LocaleKeys.services3 :LocaleKeys.emergency2,
subtitle: getIt.get<AppState>().isArabic()?LocaleKeys.emergency2:LocaleKeys.services3,
backgroundColor: AppColors.primaryRedColor,
iconColor: Colors.white,
textColor: Colors.white,
isBold: true,
),
ServiceCardData(
serviceName: "search_doctor",
icon: AppAssets.search_doctor_icon,
title: LocaleKeys.finda,
subtitle: LocaleKeys.doctor,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "health_calculators_and_converts",
icon: AppAssets.health_calculators_icon,
title: getIt.get<AppState>().isArabic()? LocaleKeys.calculators:LocaleKeys.health ,
subtitle: getIt.get<AppState>().isArabic()? LocaleKeys.health:LocaleKeys.calculators,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "health_converters",
icon: AppAssets.health_converters_icon,
title: getIt.get<AppState>().isArabic()? LocaleKeys.converters:LocaleKeys.health,
subtitle: getIt.get<AppState>().isArabic()? LocaleKeys.health:LocaleKeys.converters,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
// ServiceCardData(
// serviceName: "parking_guide",
// icon: AppAssets.car_parking_icon,
// title: getIt.get<AppState>().isArabic()? LocaleKeys.guide:LocaleKeys.parking2,
// subtitle: getIt.get<AppState>().isArabic()? LocaleKeys.parking2:LocaleKeys.guide,
// backgroundColor: AppColors.whiteColor,
// iconColor: AppColors.blackColor,
// textColor: AppColors.blackColor,
// isBold: false,
// ),
ServiceCardData(
serviceName: "indoor_navigation",
icon: AppAssets.indoor_nav_icon,
title: LocaleKeys.indoor,
subtitle: LocaleKeys.thehospital,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
];
ServiceCardData(
serviceName: "emergency",
icon: AppAssets.emergency_services_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.services3 : LocaleKeys.emergency2,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.emergency2 : LocaleKeys.services3,
backgroundColor: AppColors.primaryRedColorBase,
iconColor: Colors.white,
textColor: Colors.white,
isBold: true,
),
ServiceCardData(
serviceName: "search_doctor",
icon: AppAssets.search_doctor_icon,
title: LocaleKeys.finda,
subtitle: LocaleKeys.doctor,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "health_calculators_and_converts",
icon: AppAssets.health_calculators_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.calculators : LocaleKeys.health,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.health : LocaleKeys.calculators,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "health_converters",
icon: AppAssets.health_converters_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.converters : LocaleKeys.health,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.health : LocaleKeys.converters,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
// ServiceCardData(
// serviceName: "parking_guide",
// icon: AppAssets.car_parking_icon,
// title: getIt.get<AppState>().isArabic()? LocaleKeys.guide:LocaleKeys.parking2,
// subtitle: getIt.get<AppState>().isArabic()? LocaleKeys.parking2:LocaleKeys.guide,
// backgroundColor: AppColors.whiteColor,
// iconColor: AppColors.blackColor,
// textColor: AppColors.blackColor,
// isBold: false,
// ),
ServiceCardData(
serviceName: "indoor_navigation",
icon: AppAssets.indoor_nav_icon,
title: LocaleKeys.indoor,
subtitle: LocaleKeys.thehospital,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
];
List<ServiceCardData> get getLoggedInServiceCardsList => [
ServiceCardData(
serviceName: "emergency",
icon: AppAssets.emergency_services_icon,
title: getIt.get<AppState>().isArabic()?LocaleKeys.services3 :LocaleKeys.emergency2,
subtitle: getIt.get<AppState>().isArabic()?LocaleKeys.emergency2:LocaleKeys.services3,
backgroundColor: AppColors.primaryRedColor,
iconColor: Colors.white,
textColor: Colors.white,
isBold: true,
),
ServiceCardData(
serviceName: "lab_results",
icon: AppAssets.home_lab_result_icon,
title: getIt.get<AppState>().isArabic()?LocaleKeys.labSubtitle:LocaleKeys.lab,
subtitle: getIt.get<AppState>().isArabic()?LocaleKeys.lab:LocaleKeys.labSubtitle ,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "radiology_results",
icon: AppAssets.my_radiology_icon,
title: getIt.get<AppState>().isArabic()?LocaleKeys.labSubtitle:LocaleKeys.radiology,
subtitle: getIt.get<AppState>().isArabic()?LocaleKeys.radiology:LocaleKeys.labSubtitle ,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "prescriptions",
icon: AppAssets.my_prescription_icon,
title: getIt.get<AppState>().isArabic()?LocaleKeys.medicinesSubtitle:LocaleKeys.medicalKey,
subtitle: getIt.get<AppState>().isArabic()?LocaleKeys.medicalKey:LocaleKeys.medicinesSubtitle,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "insurance_update",
icon: AppAssets.insurance_update_icon,
title: getIt.get<AppState>().isArabic()?LocaleKeys.insurance :LocaleKeys.insurance,
subtitle: getIt.get<AppState>().isArabic()?LocaleKeys.medical: LocaleKeys.details2,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "my_doctors",
icon: AppAssets.my_doctors_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.myDoctorSubtitle2.tr() : "My",
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.myDoctor.tr() : "Doctors",
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "sick_leaves",
icon: AppAssets.my_sick_leave_icon,
title: getIt.get<AppState>().isArabic()?LocaleKeys.sickSubtitle:LocaleKeys.sick2,
subtitle: getIt.get<AppState>().isArabic()?LocaleKeys.sick2:LocaleKeys.sickSubtitle,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "my_invoice",
icon: AppAssets.invoiceListIcon,
title: getIt.get<AppState>().isArabic()?LocaleKeys.myDoctorSubtitle2:LocaleKeys.invoiceList2,
subtitle: getIt.get<AppState>().isArabic()?LocaleKeys.invoiceList2:LocaleKeys.myDoctorSubtitle2,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
];
ServiceCardData(
serviceName: "emergency",
icon: AppAssets.emergency_services_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.services3 : LocaleKeys.emergency2,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.emergency2 : LocaleKeys.services3,
backgroundColor: AppColors.primaryRedColorBase,
iconColor: Colors.white,
textColor: Colors.white,
isBold: true,
),
ServiceCardData(
serviceName: "lab_results",
icon: AppAssets.home_lab_result_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.labSubtitle : LocaleKeys.lab,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.lab : LocaleKeys.labSubtitle,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "radiology_results",
icon: AppAssets.my_radiology_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.labSubtitle : LocaleKeys.radiology,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.radiology : LocaleKeys.labSubtitle,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "prescriptions",
icon: AppAssets.my_prescription_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.medicinesSubtitle : LocaleKeys.medicalKey,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.medicalKey : LocaleKeys.medicinesSubtitle,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "insurance_update",
icon: AppAssets.insurance_update_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.insurance : LocaleKeys.insurance,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.medical : LocaleKeys.details2,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "my_doctors",
icon: AppAssets.my_doctors_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.myDoctorSubtitle2.tr() : "My",
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.myDoctor.tr() : "Doctors",
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "sick_leaves",
icon: AppAssets.my_sick_leave_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.sickSubtitle : LocaleKeys.sick2,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.sick2 : LocaleKeys.sickSubtitle,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "my_invoice",
icon: AppAssets.invoiceListIcon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.myDoctorSubtitle2 : LocaleKeys.invoiceList2,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.invoiceList2 : LocaleKeys.myDoctorSubtitle2,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
];
static List<ServiceCardData> get getServiceCardsList => [
ServiceCardData(
serviceName: "livecare",
icon: AppAssets.small_livecare_icon,
title: LocaleKeys.liveCare,
subtitle: LocaleKeys.liveCareServiceDesc,
largeCardIcon: AppAssets.liveCareService,
backgroundColor: AppColors.successColor,
iconColor: AppColors.whiteColor,
isBold: false,
isPNG: false
),
// ServiceCardData(
// icon: AppAssets.homeBottom,
// title: "Dermatology",
// subtitle: "Explore our app, View our services and offers",
// largeCardIcon: AppAssets.homeBottom,
// backgroundColor: AppColors.primaryRedColor,
// isBold: false,
// ),
ServiceCardData(
serviceName: "home_health_care",
icon: AppAssets.homeBottom,
title: LocaleKeys.homeHealthCare,
subtitle: LocaleKeys.homeHealthCareText,
largeCardIcon: AppAssets.homeHealthCareService,
backgroundColor: AppColors.primaryRedColor,
iconColor: AppColors.whiteColor,
isBold: false,
isPNG: false
),
ServiceCardData(
serviceName: "pharmacy",
icon: AppAssets.defaultMEP, //359846
title: LocaleKeys.hmgPharmacy,
subtitle: LocaleKeys.hmgPharmacyText,
largeCardIcon: AppAssets.pharmacyService,
backgroundColor: AppColors.pharmacyBGColor,
iconColor: null,
isBold: true,
isPNG: true
),
];
ServiceCardData(
serviceName: "livecare",
icon: AppAssets.small_livecare_icon,
title: LocaleKeys.liveCare,
subtitle: LocaleKeys.liveCareServiceDesc,
largeCardIcon: AppAssets.liveCareService,
backgroundColor: AppColors.successColor,
iconColor: AppColors.whiteColor,
isBold: false,
isPNG: false),
// ServiceCardData(
// icon: AppAssets.homeBottom,
// title: "Dermatology",
// subtitle: "Explore our app, View our services and offers",
// largeCardIcon: AppAssets.homeBottom,
// backgroundColor: AppColors.primaryRedColor,
// isBold: false,
// ),
ServiceCardData(
serviceName: "home_health_care",
icon: AppAssets.homeBottom,
title: LocaleKeys.homeHealthCare,
subtitle: LocaleKeys.homeHealthCareText,
largeCardIcon: AppAssets.homeHealthCareService,
backgroundColor: AppColors.primaryRedColor,
iconColor: AppColors.whiteColor,
isBold: false,
isPNG: false),
ServiceCardData(
serviceName: "pharmacy",
icon: AppAssets.defaultMEP,
//359846
title: LocaleKeys.hmgPharmacy,
subtitle: LocaleKeys.hmgPharmacyText,
largeCardIcon: AppAssets.pharmacyService,
backgroundColor: AppColors.pharmacyBGColor,
iconColor: null,
isBold: true,
isPNG: true),
];
}

@ -5,6 +5,7 @@ import 'dart:ui' as ui;
import 'package:clarity_flutter/clarity_flutter.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:flutter_swiper_view/flutter_swiper_view.dart';
import 'package:get_it/get_it.dart';
@ -31,6 +32,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart';
import 'package:hmg_patient_app_new/features/occasion_theme/occasion_theme_view_model.dart';
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
@ -49,6 +51,7 @@ import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dar
import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/lakum_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/occasion_banner_widget.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/widgets/insurance_update_details_card.dart';
@ -126,10 +129,7 @@ class _LandingPageState extends State<LandingPage> {
authVM.savePushTokenToAppState();
if (mounted) {
final user = appState.getAuthenticatedUser();
final hasUserQuickLoginData =
user != null &&
(user.mobileNumber?.isNotEmpty ?? false) &&
(user.patientIdentificationNo?.isNotEmpty ?? false);
final hasUserQuickLoginData = user != null && (user.mobileNumber?.isNotEmpty ?? false) && (user.patientIdentificationNo?.isNotEmpty ?? false);
if (hasUserQuickLoginData) {
authVM.checkLastLoginStatus(() {
@ -191,7 +191,7 @@ class _LandingPageState extends State<LandingPage> {
onSuccess: ((response) {
// Only open dialog if details are loaded AND widget is mounted
if (!mounted || appointmentRatingViewModel.appointmentDetails == null) return;
appointmentRatingViewModel.setClinicOrDoctor(false);
appointmentRatingViewModel.setTitle(LocaleKeys.rateDoctor.tr());
appointmentRatingViewModel.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr());
@ -229,9 +229,19 @@ class _LandingPageState extends State<LandingPage> {
body: Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) {
return Stack(
children: [
// Renders nothing unless a remote-config occasion theme (e.g.
// National Day) is currently active — fills the whole page,
// behind all other content below.
const OccasionBackgroundWidget(),
RefreshIndicator(
color: AppColors.primaryRedColor,
onRefresh: () async {
// Re-check Remote Config for occasion theme changes on
// pull-to-refresh (independent of auth state) — handy for
// testing without needing to restart the app.
unawaited(GetIt.instance<OccasionThemeViewModel>().refresh());
if (appState.isAuthenticated) {
// Refresh Appointments Data
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
@ -268,7 +278,6 @@ class _LandingPageState extends State<LandingPage> {
// }
// },
// );
}
},
child: SingleChildScrollView(
@ -280,8 +289,8 @@ class _LandingPageState extends State<LandingPage> {
: kToolbarHeight + 16.h,
bottom: 24.h),
child: Column(
spacing: 16.h,
children: [
const OccasionBannerWidget(),
Row(
spacing: 8.h,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -348,11 +357,11 @@ class _LandingPageState extends State<LandingPage> {
// ...existing commented code...
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w700,
borderRadius: 12.r,
borderRadius: 10.r,
padding: EdgeInsets.fromLTRB(12.h, 0, 12.h, 0),
height: 40.h,
),
@ -374,7 +383,7 @@ class _LandingPageState extends State<LandingPage> {
height: 18.h,
padding: EdgeInsets.all(2),
decoration: BoxDecoration(
color: AppColors.primaryRedColor,
color: AppColors.primaryRedColorBase,
borderRadius: BorderRadius.circular(20.r),
),
child: Text(
@ -440,6 +449,7 @@ class _LandingPageState extends State<LandingPage> {
}),
],
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 16.h),
!appState.isAuthenticated
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
@ -485,6 +495,7 @@ class _LandingPageState extends State<LandingPage> {
),
).paddingSymmetrical(24.w, 0.h)
: SizedBox.shrink(),
SizedBox(height: 16.h),
// Offers And Discounts Carousel - Auto-scrolling from right to left
// appState.isAuthenticated && appState.isEnabledOffersAndDiscountsCarousel
@ -673,9 +684,9 @@ class _LandingPageState extends State<LandingPage> {
getIt.get<BookAppointmentsViewModel>().onTabChanged(0);
Navigator.of(context).push(CustomPageRoute(page: BookAppointmentPage()));
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
@ -907,6 +918,7 @@ class _LandingPageState extends State<LandingPage> {
],
),
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -922,6 +934,7 @@ class _LandingPageState extends State<LandingPage> {
}),
],
).paddingSymmetrical(24.w, 0.h),
SizedBox(height: 16.h),
ConstrainedBox(
constraints: BoxConstraints(maxHeight: isFoldable ? 450.h : (isTablet ? 440.h : 431.h), minHeight: 411.h),
child: ListView.separated(
@ -951,6 +964,7 @@ class _LandingPageState extends State<LandingPage> {
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.w),
),
),
SizedBox(height: 16.h),
appState.isAuthenticated
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -994,13 +1008,13 @@ 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(
color: AppColors.secondaryLightRedBorderColor,
color: AppColors.secondaryLightRedColorBase,
shape: SmoothRectangleBorder(
side: BorderSide(width: 1, color: AppColors.primaryRedColor.withAlpha(20)),
side: BorderSide(width: 1, color: AppColors.primaryRedColorBase.withAlpha(20)),
// borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
smoothness: 1,
),
@ -1011,7 +1025,7 @@ 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.primaryRedColorBase, isBold: true).paddingSymmetrical(0.h, 0.h),
Row(
children: [
CustomButton(
@ -1023,8 +1037,8 @@ class _LandingPageState extends State<LandingPage> {
showCommonBottomSheetWithoutHeight(context,
child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false);
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.secondaryLightRedBorderColor,
backgroundColor: AppColors.primaryRedColorBase,
borderColor: AppColors.primaryRedColorBase,
textColor: Colors.white,
fontSize: 12.f,
fontWeight: FontWeight.bold,
@ -1032,7 +1046,7 @@ class _LandingPageState extends State<LandingPage> {
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
height: 36.h,
).paddingSymmetrical(12.h, 0.h),
Icon(Icons.close, color: AppColors.primaryRedColor).onPress(() {
Icon(Icons.close, color: AppColors.primaryRedColorBase).onPress(() {
insuranceVM.setIsInsuranceExpiryBannerShown(false);
}),
],

@ -0,0 +1,100 @@
import 'dart:developer';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/occasion_theme/occasion_theme_view_model.dart';
import 'package:provider/provider.dart';
/// Renders the occasion's main header/banner (`header_image`, e.g. the
/// National Day greeting banner) inline at the top of the landing page's
/// scrollable content, drawn at full opacity.
///
/// Fully data-driven: when no occasion is active (the default), this widget
/// renders nothing and takes zero space — the existing layout/UI is
/// completely unaffected.
///
/// See also [OccasionBackgroundWidget], which renders the decorative
/// backdrop image as a full-page background instead of inline content.
class OccasionBannerWidget extends StatelessWidget {
const OccasionBannerWidget({super.key});
@override
Widget build(BuildContext context) {
return Consumer<OccasionThemeViewModel>(
builder: (context, occasionThemeVm, _) {
final headerImageUrl = occasionThemeVm.headerImage;
final hasHeaderImage = occasionThemeVm.isOccasionActive && (headerImageUrl?.isNotEmpty ?? false);
if (!hasHeaderImage) {
return const SizedBox(height: 0);
}
return buildOccasionImage(headerImageUrl!).paddingOnly(bottom: 16.h);
},
);
}
}
/// Renders the occasion's decorative backdrop/watermark
/// (`background_opacity_image`) as a full-page background. Place this as
/// the first child of the landing page's outer [Stack] (behind the
/// scrollable content) so it fills the whole screen and sits behind
/// everything else, drawn at [OccasionThemeViewModel.backgroundImageOpacity].
///
/// Fully data-driven: when no occasion is active (the default), this widget
/// renders nothing — the existing layout/UI is completely unaffected.
class OccasionBackgroundWidget extends StatelessWidget {
const OccasionBackgroundWidget({super.key});
@override
Widget build(BuildContext context) {
return Consumer<OccasionThemeViewModel>(
builder: (context, occasionThemeVm, _) {
final backgroundImageUrl = occasionThemeVm.backgroundOpacityImage;
final hasBackgroundImage = occasionThemeVm.isOccasionActive && (backgroundImageUrl?.isNotEmpty ?? false);
log("occasionThemeVm.backgroundImageOpacity: ${occasionThemeVm.backgroundImageOpacity}");
if (!hasBackgroundImage) {
return const SizedBox.shrink();
}
return Positioned.fill(
child: Opacity(
opacity: occasionThemeVm.backgroundImageOpacity.clamp(0.0, 1.0),
child: buildOccasionImage(backgroundImageUrl!),
),
);
},
);
}
}
/// Remote-config image URLs may point to either an SVG (e.g. the National
/// Day banner) or a raster image (PNG/JPG). [CachedNetworkImage] cannot
/// decode SVG — it silently renders nothing — so SVG URLs are routed to
/// [SvgPicture.network] instead, based on the URL's file extension.
Widget buildOccasionImage(String url) {
final isSvg = url.split('?').first.toLowerCase().endsWith('.svg');
if (isSvg) {
return SvgPicture.network(
url,
width: double.infinity,
fit: BoxFit.cover,
placeholderBuilder: (_) => const SizedBox.shrink(),
);
}
return CachedNetworkImage(
imageUrl: url,
width: double.infinity,
fit: BoxFit.cover,
placeholder: (_, __) => const SizedBox.shrink(),
errorWidget: (_, __, error) {
log('OccasionImage: failed to load $url -> $error');
return const SizedBox.shrink();
},
);
}

@ -38,7 +38,7 @@ class SmallServiceCard extends StatelessWidget {
final Color? backgroundColor;
final bool isBold;
SmallServiceCard({
const SmallServiceCard({
super.key,
this.icon = "",
this.serviceName = "",

@ -69,8 +69,8 @@ class InsuranceHistory extends StatelessWidget {
CustomButton(
text: insuranceVM.patientInsuranceCardHistoryList[index].statusDescription ?? "",
onPressed: () {},
backgroundColor: AppColors.primaryRedColor.withOpacity(0.1),
borderColor: AppColors.primaryRedColor.withOpacity(0.0),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 10,
isBold: true,

@ -117,7 +117,7 @@ class PatientInsuranceCard extends StatelessWidget {
}
},
backgroundColor: isCurrentPatientInsuranceExpired(insuranceCardDetailsModel.cardValidTo!)
? AppColors.primaryRedColor.withOpacity(0.1)
? AppColors.secondaryLightRedColor
: insuranceViewModel.isInsuranceActive
? AppColors.successColor.withOpacity(0.1)
: AppColors.warningColorYellow.withOpacity(0.1),
@ -129,7 +129,7 @@ class PatientInsuranceCard extends StatelessWidget {
// iconColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor,
// textColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor,
// iconSize: 12,
// backgroundColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1),
// backgroundColor: isInsuranceExpired ? AppColors.primaryRedColorBase.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1),
// labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h),
// ),
],

@ -14,8 +14,8 @@ import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'lab_order_by_test.dart';
class AlphabeticScroll extends StatefulWidget{
final List<String> alpahbetsAvailable;
class AlphabeticScroll extends StatefulWidget {
final List<String> alpahbetsAvailable;
final List<TestDetails> details;
final AppState appState;
final LabViewModel labViewModel;
@ -38,13 +38,13 @@ class _AlphabetScrollPageState extends State<AlphabeticScroll> {
Map<String, num> density = {};
Map<String, double> _offsetMap = {};
Map<String, GlobalKey> _offsetKeys = {};
Map<String, GlobalKey> _offsetKeys = {};
int _activeIndex = 0; // <-- Highlighted letter
@override
void initState() {
super.initState();
scheduleMicrotask((){
scheduleMicrotask(() {
_rebuildData();
});
itemPositionsListener.itemPositions.addListener(_onPositionChanged);
@ -61,9 +61,7 @@ class _AlphabetScrollPageState extends State<AlphabeticScroll> {
void _rebuildData() {
data.clear();
for (var char in widget.alpahbetsAvailable) {
data[char] = widget.details
.where((element) => element.description?.toLowerCase().startsWith(char.toLowerCase()) == true)
.toList();
data[char] = widget.details.where((element) => element.description?.toLowerCase().startsWith(char.toLowerCase()) == true).toList();
}
if (_activeIndex >= widget.alpahbetsAvailable.length) {
_activeIndex = 0;
@ -79,15 +77,12 @@ class _AlphabetScrollPageState extends State<AlphabeticScroll> {
// Get FIRST visible item (top-most)
final firstVisible = positions
.where((p) => p.itemTrailingEdge > 0) // visible
.reduce((min, p) =>
p.itemLeadingEdge < min.itemLeadingEdge ? p : min);
.reduce((min, p) => p.itemLeadingEdge < min.itemLeadingEdge ? p : min);
if(_activeIndex == firstVisible.index) return ;
if (_activeIndex == firstVisible.index) return;
setState(() {
_activeIndex = firstVisible.index;
});
print("Active index = $_activeIndex");
}
@override
@ -97,12 +92,8 @@ class _AlphabetScrollPageState extends State<AlphabeticScroll> {
}
void _scrollToLetter(String letter) async {
// itemScrollController.jumpTo(index:density[letter]?.toInt()??0, );
itemScrollController.scrollTo(
index: data.keys.toList().indexOf(letter).toInt()??0,
duration: Duration(seconds: 2),
curve: Curves.easeInOutCubic);
itemScrollController.scrollTo(index: data.keys.toList().indexOf(letter).toInt() ?? 0, duration: Duration(seconds: 2), curve: Curves.easeInOutCubic);
// final key = _offsetKeys[letter];
// if (key == null) return;
//
@ -134,58 +125,52 @@ class _AlphabetScrollPageState extends State<AlphabeticScroll> {
@override
Widget build(BuildContext context) {
return SizedBox(
width: MediaQuery.sizeOf(context).width,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start, // Add this
children: [
Expanded(
child: Column(
children: [
SizedBox(
height: (MediaQuery.sizeOf(context).height),
child: _buildList(widget.alpahbetsAvailable)
),
],
),
width: MediaQuery.sizeOf(context).width,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start, // Add this
children: [
Expanded(
child: Column(
children: [
SizedBox(height: (MediaQuery.sizeOf(context).height), child: _buildList(widget.alpahbetsAvailable)),
],
),
SizedBox(
width: 24.w,
height: MediaQuery.sizeOf(context).height-(70.h+ kToolbarHeight+100.h),
child: Column(
mainAxisSize: MainAxisSize.max, // Add this
mainAxisAlignment: MainAxisAlignment.center, // Changed from center to start
crossAxisAlignment: CrossAxisAlignment.center,
children:List.generate(widget.alpahbetsAvailable.length, (i) {
final isActive = (i == _activeIndex);
return GestureDetector(
onTap: () {
setState(() => _activeIndex = i);
_scrollToLetter(widget.alpahbetsAvailable[i]);
},
child: TweenAnimationBuilder<double>(
tween: Tween(begin: 1.0, end: isActive ? 1.8 : 1.0),
duration: Duration(milliseconds: 120),
curve: Curves.easeOut,
builder: (_, scale, child) {
return Transform.scale(
scale: scale,
child: Opacity(
opacity: isActive ? 1.0 : 0.5,
child: widget.alpahbetsAvailable[i].toUpperCase().toText14(
color: !isActive ? AppColors.greyTextColor : AppColors.primaryRedColor, isEnglishOnly: true
),
),
);
},
),
);
}
),
SizedBox(
width: 24.w,
height: MediaQuery.sizeOf(context).height - (70.h + kToolbarHeight + 100.h),
child: Column(
mainAxisSize: MainAxisSize.max, // Add this
mainAxisAlignment: MainAxisAlignment.center, // Changed from center to start
crossAxisAlignment: CrossAxisAlignment.center,
children: List.generate(widget.alpahbetsAvailable.length, (i) {
final isActive = (i == _activeIndex);
return GestureDetector(
onTap: () {
setState(() => _activeIndex = i);
_scrollToLetter(widget.alpahbetsAvailable[i]);
},
child: TweenAnimationBuilder<double>(
tween: Tween(begin: 1.0, end: isActive ? 1.8 : 1.0),
duration: Duration(milliseconds: 120),
curve: Curves.easeOut,
builder: (_, scale, child) {
return Transform.scale(
scale: scale,
child: Opacity(
opacity: isActive ? 1.0 : 0.5,
child: widget.alpahbetsAvailable[i].toUpperCase().toText14(color: !isActive ? AppColors.greyTextColor : AppColors.primaryRedColor, isEnglishOnly: true),
),
);
},
),
),
),
],
),
);
);
}),
),
),
],
),
);
// );
}
@ -205,35 +190,35 @@ class _AlphabetScrollPageState extends State<AlphabeticScroll> {
key: _offsetKeys[letter],
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children:
List.generate(items.length,(itemIndex)=>
AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: LabOrderByTest(
appState: getIt<AppState>(),
onTap: () {
if (items[itemIndex].model != null) {
widget.rangeViewModel.flush();
widget.labViewModel.getPatientLabResult(items[itemIndex].model!, items[itemIndex].description!,
(widget.appState.isArabic() ? items[itemIndex].testDescriptionAr! : items[itemIndex].testDescriptionEn!),"");
}
},
tests: items[itemIndex],
index: itemIndex,
isExpanded: true)),
),
))
children: List.generate(
items.length,
(itemIndex) => AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: LabOrderByTest(
appState: getIt<AppState>(),
onTap: () {
if (items[itemIndex].model != null) {
widget.rangeViewModel.flush();
widget.labViewModel.getPatientLabResult(items[itemIndex].model!, items[itemIndex].description!,
(widget.appState.isArabic() ? items[itemIndex].testDescriptionAr! : items[itemIndex].testDescriptionEn!), "");
}
},
tests: items[itemIndex],
index: itemIndex,
isExpanded: true)),
),
))
// ...items.indexed((item) =>
//
//
//
// )
),
),
);
},
);

@ -26,64 +26,65 @@ class LabOrderByTest extends StatelessWidget {
@override
build(BuildContext context) {
return AnimatedContainer(
key:key,
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
margin: EdgeInsets.symmetric(vertical: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true),
child: Container(
key: ValueKey<int>(index),
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
'${tests!.description}'.toText16(isBold: true, isEnglishOnly: true),
SizedBox(height: 4.h),
(appState.isArabic() ? tests!.testDescriptionAr : tests!.testDescriptionEn)!.toText12(isBold: true),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppCustomChipWidget(
richText: Row(
children: [
"${LocaleKeys.lastTested.tr(context: context)}: ".toText12(isBold: true),
Directionality(
textDirection: ui.TextDirection.ltr,child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false).toText12(isBold: true, isEnglishOnly: true))
],
),
backgroundColor: AppColors.greyLightColor,
textColor: AppColors.textColor,
key: key,
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
margin: EdgeInsets.symmetric(vertical: 8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true),
child: Container(
key: ValueKey<int>(index),
padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
'${tests!.description}'.toText16(isBold: true, isEnglishOnly: true),
SizedBox(height: 4.h),
(appState.isArabic() ? tests!.testDescriptionAr : tests!.testDescriptionEn)!.toText12(isBold: true),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppCustomChipWidget(
richText: Row(
children: [
"${LocaleKeys.lastTested.tr(context: context)}: ".toText12(isBold: true),
Directionality(
textDirection: ui.TextDirection.ltr, child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(tests!.createdOn), false).toText12(isBold: true, isEnglishOnly: true))
],
),
],
),
SizedBox(height: 16.h),
Row(
children: [
Expanded(child: Container()),
Expanded(
child: CustomButton(
icon: AppAssets.view_report_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
text: LocaleKeys.viewResults.tr(context: context),
onPressed: () {
onTap();
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
backgroundColor: AppColors.greyLightColor,
textColor: AppColors.textColor,
),
],
),
SizedBox(height: 16.h),
Row(
children: [
Expanded(child: Container()),
Expanded(
child: CustomButton(
icon: AppAssets.view_report_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
text: LocaleKeys.viewResults.tr(context: context),
onPressed: () {
onTap();
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 10.r,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
],
),
],
),
));
),
],
),
],
),
),
);
}
}

File diff suppressed because one or more lines are too long

@ -340,7 +340,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
}
},
backgroundColor: insuranceVM.isInsuranceExpired
? AppColors.primaryRedColor.withOpacity(0.1)
? AppColors.secondaryLightRedColor
: insuranceVM.isInsuranceActive
? AppColors.successColor.withOpacity(0.1)
: AppColors.warningColorYellow.withOpacity(0.1),

@ -34,14 +34,13 @@ class _FamilyMedicalScreenState extends State<FamilyMedicalScreen> {
super.initState();
medicalVM = context.read<MedicalFileViewModel>();
WidgetsBinding.instance.addPostFrameCallback((_) async {
if(medicalVM!.isFamilyFilesLoading) {
if (medicalVM!.isFamilyFilesLoading) {
LoaderBottomSheet.showLoader();
} else {
LoaderBottomSheet.hideLoader();
}
medicalVM?.onFamilyFileTabChange(0);
medicalVM?.getAllPendingRecordsByResponseId();
});
}
@ -71,6 +70,8 @@ class _FamilyMedicalScreenState extends State<FamilyMedicalScreen> {
medicalVM!.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms);
});
},
borderColor: AppColors.primaryRedColor,
backgroundColor: AppColors.primaryRedColor,
icon: AppAssets.add_icon,
fontSize: 16.f,
borderRadius: 12.r,
@ -104,9 +105,7 @@ class _FamilyMedicalScreenState extends State<FamilyMedicalScreen> {
),
shouldRebuild: (previous, next) {
// Only rebuild if something actually changed
return previous.selectedIndex != next.selectedIndex ||
!identical(previous.patientFiles, next.patientFiles) ||
!identical(previous.pendingFiles, next.pendingFiles);
return previous.selectedIndex != next.selectedIndex || !identical(previous.patientFiles, next.patientFiles) || !identical(previous.pendingFiles, next.pendingFiles);
},
builder: (context, data, child) => getFamilyTabs(
index: data.selectedIndex,
@ -127,7 +126,6 @@ class _FamilyMedicalScreenState extends State<FamilyMedicalScreen> {
}) {
switch (index) {
case 0:
return FamilyCards(
profiles: patientFiles,
onSelect: (FamilyFileResponseModelLists profile) {

@ -165,67 +165,63 @@ class _FamilyCardsState extends State<FamilyCards> {
title: LocaleKeys.whoCanViewMyMedicalFile.tr(context: context).toText18(isBold: true),
expandedBackgroundColor: Colors.transparent,
initiallyExpanded: true,
children: [
SizedBox(height: 10.h),
manageFamily()
],
children: [SizedBox(height: 10.h), manageFamily()],
),
ExpandableListItem(title: LocaleKeys.notifications.tr(context: context).toText18(isBold: true), expandedBackgroundColor: Colors.transparent, initiallyExpanded: true, children: [
SizedBox(height: 10.h),
widget.profileViewList!.where((profile) => profile.isRequestFromMySide ?? false).isEmpty
? Utils.getNoDataWidget(context)
: ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: widget.profileViewList!.where((profile) => profile.isRequestFromMySide ?? false).length,
itemBuilder: (context, index) {
final mySideProfiles = widget.profileViewList!.where((profile) => profile.isRequestFromMySide ?? false).toList();
FamilyFileResponseModelLists profile = mySideProfiles[index];
return Container(
margin: EdgeInsets.only(bottom: 12.h),
padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 15.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24),
child: Opacity(
opacity: 1.0,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
AppCustomChipWidget(
labelText: profile.statusDescription ?? " N/A",
backgroundColor: profile.status == FamilyFileEnum.pending.toInt
? AppColors.alertLightColor.withValues(alpha: 0.20)
: profile.status == FamilyFileEnum.rejected.toInt
? AppColors.primaryRedColor.withValues(alpha: 0.20)
: profile.status == FamilyFileEnum.active.toInt
? AppColors.lightGreenColor
: AppColors.lightGrayBGColor,
textColor: profile.status == FamilyFileEnum.pending.toInt
? AppColors.alertLightColor
: profile.status == FamilyFileEnum.rejected.toInt
? AppColors.primaryRedColor
: profile.status == FamilyFileEnum.active.toInt
? AppColors.textGreenColor
: AppColors.alertColor
),
SizedBox(height: 8.h),
Wrap(alignment: WrapAlignment.start, crossAxisAlignment: WrapCrossAlignment.start, runAlignment: WrapAlignment.start, spacing: 0.h, children: [
(profile.patientName ?? "").toText14(isBold: true, isCenter: false, maxlines: 1),
(getStatusTextByRequest(FamilyFileEnum.values.firstWhere((e) => e.toInt == profile.status), profile.isRequestFromMySide ?? false))
.toText12(isCenter: false, maxLine: 1, isBold: true, color: AppColors.greyTextColor),
]),
SizedBox(height: 8.h),
AppCustomChipWidget(
labelText: "Medical File: ${profile.responseId ?? "N/A"}",
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: widget.profileViewList!.where((profile) => profile.isRequestFromMySide ?? false).length,
itemBuilder: (context, index) {
final mySideProfiles = widget.profileViewList!.where((profile) => profile.isRequestFromMySide ?? false).toList();
FamilyFileResponseModelLists profile = mySideProfiles[index];
return Container(
margin: EdgeInsets.only(bottom: 12.h),
padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 15.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24),
child: Opacity(
opacity: 1.0,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
AppCustomChipWidget(
labelText: profile.statusDescription ?? " N/A",
backgroundColor: profile.status == FamilyFileEnum.pending.toInt
? AppColors.alertLightColor.withValues(alpha: 0.20)
: profile.status == FamilyFileEnum.rejected.toInt
? AppColors.primaryRedColor.withValues(alpha: 0.20)
: profile.status == FamilyFileEnum.active.toInt
? AppColors.lightGreenColor
: AppColors.lightGrayBGColor,
textColor: profile.status == FamilyFileEnum.pending.toInt
? AppColors.alertLightColor
: profile.status == FamilyFileEnum.rejected.toInt
? AppColors.primaryRedColor
: profile.status == FamilyFileEnum.active.toInt
? AppColors.textGreenColor
: AppColors.alertColor),
SizedBox(height: 8.h),
Wrap(alignment: WrapAlignment.start, crossAxisAlignment: WrapCrossAlignment.start, runAlignment: WrapAlignment.start, spacing: 0.h, children: [
(profile.patientName ?? "").toText14(isBold: true, isCenter: false, maxlines: 1),
(getStatusTextByRequest(FamilyFileEnum.values.firstWhere((e) => e.toInt == profile.status), profile.isRequestFromMySide ?? false))
.toText12(isCenter: false, maxLine: 1, isBold: true, color: AppColors.greyTextColor),
]),
SizedBox(height: 8.h),
AppCustomChipWidget(
labelText: "Medical File: ${profile.responseId ?? "N/A"}",
),
],
),
),
],
),
);
},
),
);
},
),
])
],
theme: ExpandableListTheme.custom(
@ -517,7 +513,7 @@ class _FamilyCardsState extends State<FamilyCards> {
},
text: isActive ? LocaleKeys.active.tr(context: context) : LocaleKeys.switchLogin.tr(context: context),
backgroundColor: isActive || !canSwitch ? Colors.grey.shade200 : AppColors.secondaryLightRedColor,
borderColor: isActive || !canSwitch ? Colors.grey.shade200 : AppColors.secondaryLightRedColor,
borderColor: Colors.transparent,
textColor: isActive || !canSwitch ? AppColors.greyTextColor : AppColors.primaryRedColor,
fontSize: 13.h,
icon: isActive ? AppAssets.activeCheck : AppAssets.switch_user,
@ -527,13 +523,15 @@ class _FamilyCardsState extends State<FamilyCards> {
],
),
if (widget.isShowRemoveButton) ...[
isParentUser ? Positioned(
top: 0,
right: 0,
child: Utils.buildSvgWithAssets(icon: AppAssets.deleteIcon).onPress(() {
if (!isActive) widget.onRemove(profile);
}),
) : SizedBox(),
isParentUser
? Positioned(
top: 0,
right: 0,
child: Utils.buildSvgWithAssets(icon: AppAssets.deleteIcon).onPress(() {
if (!isActive) widget.onRemove(profile);
}),
)
: SizedBox(),
],
],
),

@ -122,8 +122,8 @@ class _MyInvoicesListState extends State<MyInvoicesList> {
children: [
SizedBox(height: 16.h),
CustomTabBar(
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
activeTextColor: AppColors.primaryRedColor,
activeBackgroundColor: AppColors.secondaryLightRedColor,
initialIndex: 0,
tabs: [
CustomTabBarModel(null, LocaleKeys.visitsOrders.tr(context: context)),
@ -149,13 +149,11 @@ class _MyInvoicesListState extends State<MyInvoicesList> {
myInvoicesViewModel.filterInvoices(InvoiceFilterType.all);
},
backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.all
? AppColors.primaryRedColor
: AppColors.textColor.withValues(alpha: 0.2),
borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
textColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontSize: 12.f,
fontWeight: FontWeight.w600,
borderRadius: 10,
borderRadius: 10.r,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
@ -166,15 +164,12 @@ class _MyInvoicesListState extends State<MyInvoicesList> {
if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return;
_showHospitalFilterBottomSheet(context);
},
backgroundColor:
myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital
? AppColors.primaryRedColor
: AppColors.textColor.withValues(alpha: 0.2),
backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
textColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontSize: 12.f,
fontWeight: FontWeight.w600,
borderRadius: 10,
borderRadius: 10.r,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
@ -185,15 +180,12 @@ class _MyInvoicesListState extends State<MyInvoicesList> {
if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return;
_showClinicFilterBottomSheet(context);
},
backgroundColor:
myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic
? AppColors.primaryRedColor
: AppColors.textColor.withValues(alpha: 0.2),
backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
textColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontSize: 12.f,
fontWeight: FontWeight.w600,
borderRadius: 10,
borderRadius: 10.r,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
@ -204,15 +196,12 @@ class _MyInvoicesListState extends State<MyInvoicesList> {
if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return;
_showDoctorFilterBottomSheet(context);
},
backgroundColor:
myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor
? AppColors.primaryRedColor
: AppColors.textColor.withValues(alpha: 0.2),
backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
textColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontSize: 12.f,
fontWeight: FontWeight.w600,
borderRadius: 10,
borderRadius: 10.r,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
@ -245,16 +234,14 @@ class _MyInvoicesListState extends State<MyInvoicesList> {
getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index],
onTap: () async {
if (Utils.isVidaPlusProject(myInvoicesVM.allInvoicesList[index].projectId ?? 0)) {
LoaderBottomSheet.showLoader(
loadingText: LocaleKeys.sendingEmailPleaseWait.tr(context: context));
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingEmailPleaseWait.tr(context: context));
await myInvoicesViewModel.sendInvoiceEmail(
appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!,
projectID: myInvoicesVM.allInvoicesList[index].projectId!,
onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(context,
child: Utils.getSuccessWidget(
loadingText: LocaleKeys.emailSentSuccessfullyMessage.tr(context: context)),
child: Utils.getSuccessWidget(loadingText: LocaleKeys.emailSentSuccessfullyMessage.tr(context: context)),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
@ -289,8 +276,7 @@ class _MyInvoicesListState extends State<MyInvoicesList> {
onSuccess: (value) async {
LoaderBottomSheet.hideLoader();
if (myInvoicesViewModel.downloadInvoicePDFBase64!.isNotEmpty) {
String path = await Utils.createFileFromString(
myInvoicesViewModel.downloadInvoicePDFBase64!, "pdf");
String path = await Utils.createFileFromString(myInvoicesViewModel.downloadInvoicePDFBase64!, "pdf");
try {
OpenFilex.open(path);
} catch (ex) {
@ -377,8 +363,7 @@ class _MyInvoicesListState extends State<MyInvoicesList> {
onSuccess: (value) async {
LoaderBottomSheet.hideLoader();
if (myInvoicesViewModel.downloadPharmacyInvoicePDFBase64!.isNotEmpty) {
String path = await Utils.createFileFromString(
myInvoicesViewModel.downloadPharmacyInvoicePDFBase64!, "pdf");
String path = await Utils.createFileFromString(myInvoicesViewModel.downloadPharmacyInvoicePDFBase64!, "pdf");
try {
OpenFilex.open(path);
} catch (ex) {

@ -111,7 +111,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
onPressed: () => goToHomePage(),
width: 86.w,
height: 56.h,
backgroundColor: Color(0xffFEE9EA),
backgroundColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
borderColor: Colors.transparent,
).paddingOnly(left: 24.w)

@ -164,7 +164,7 @@ class _AnimatedScreenState extends State<AnimatedScreen> with TickerProviderStat
width: 400.h,
height: 653.h,
decoration: BoxDecoration(
color: Color(0xffED1C2B),
color: AppColors.primaryRedColor,
borderRadius: BorderRadius.circular(330.h),
),
),
@ -273,7 +273,7 @@ class _MoveObjectDemoState extends State<MoveObjectDemo> with SingleTickerProvid
width: 200,
height: 375,
decoration: BoxDecoration(
color: const Color(0xffED1C2B),
color: AppColors.primaryRedColor,
borderRadius: BorderRadius.circular(330),
),
),
@ -367,7 +367,7 @@ class _MoveOnClickDemoState extends State<MoveOnClickDemo> with TickerProviderSt
width: 100,
height: 150,
decoration: BoxDecoration(
color: const Color(0xffED1C2B),
color: AppColors.primaryRedColor,
borderRadius: BorderRadius.circular(330),
),
),

@ -177,8 +177,8 @@ class PrescriptionItemView extends StatelessWidget {
// child: CustomButton(
// text: LocaleKeys.checkAvailability.tr(context: context),
// onPressed: () {},
// backgroundColor: AppColors.primaryRedColor.withOpacity(0.1),
// borderColor: AppColors.primaryRedColor.withOpacity(0.0),
// backgroundColor: AppColors.primaryRedColorBase.withOpacity(0.1),
// borderColor: AppColors.primaryRedColorBase.withOpacity(0.0),
// textColor: AppColors.primaryRedColor,
// fontSize: 14.f,
// isBold: true,

@ -52,14 +52,13 @@ class ProfileSettings extends StatefulWidget {
}
class ProfileSettingsState extends State<ProfileSettings> {
final InAppReview inAppReview = InAppReview.instance;
@override
void initState() {
super.initState();
_loadPermissions();
// Load data after first frame
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
@ -434,7 +433,15 @@ class ProfileSettingsState extends State<ProfileSettings> {
if (trailingLabel.isNotEmpty)
ConstrainedBox(
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.35),
child: isEnglishOnly ? Directionality(textDirection: ui.TextDirection.ltr, child: trailingLabel.toText14(color: AppColors.greyTextColor, isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, isEnglishOnly: isEnglishOnly).toShimmer2(isShow: isShowLoading)) : trailingLabel.toText14(color: AppColors.greyTextColor, isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, isEnglishOnly: isEnglishOnly).toShimmer2(isShow: isShowLoading),
child: isEnglishOnly
? Directionality(
textDirection: ui.TextDirection.ltr,
child: trailingLabel
.toText14(color: AppColors.greyTextColor, isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, isEnglishOnly: isEnglishOnly)
.toShimmer2(isShow: isShowLoading))
: trailingLabel
.toText14(color: AppColors.greyTextColor, isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, isEnglishOnly: isEnglishOnly)
.toShimmer2(isShow: isShowLoading),
),
switchValue != null
? Switch(
@ -452,8 +459,10 @@ class ProfileSettingsState extends State<ProfileSettings> {
);
}
Widget socialMedia(String icon, String label,
) {
Widget socialMedia(
String icon,
String label,
) {
return SizedBox(
height: 56.h,
child: Row(
@ -461,14 +470,13 @@ class ProfileSettingsState extends State<ProfileSettings> {
children: [
Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.greyTextColor),
label.toText14(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded,
Row(
spacing: 24.w,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.x2).onPress(()=> launchUrl(Uri.parse("https://twitter.com/HMG"))),
Utils.buildSvgWithAssets(icon: AppAssets.fb).onPress(()=> launchUrl(Uri.parse("https://www.facebook.com/DrSulaimanAlHabib?ref=tn_tnmn"))),
Utils.buildSvgWithAssets(icon: AppAssets.linkedin).onPress(()=> launchUrl(Uri.parse("https://www.linkedin.com/company/drsulaiman-alhabib-medical-group"))),
Utils.buildSvgWithAssets(icon: AppAssets.youtube).onPress(()=> launchUrl(Uri.parse("https://www.youtube.com/c/DrsulaimanAlhabibHospitals"))),
Utils.buildSvgWithAssets(icon: AppAssets.x2).onPress(() => launchUrl(Uri.parse("https://twitter.com/HMG"))),
Utils.buildSvgWithAssets(icon: AppAssets.fb).onPress(() => launchUrl(Uri.parse("https://www.facebook.com/DrSulaimanAlHabib?ref=tn_tnmn"))),
Utils.buildSvgWithAssets(icon: AppAssets.linkedin).onPress(() => launchUrl(Uri.parse("https://www.linkedin.com/company/drsulaiman-alhabib-medical-group"))),
Utils.buildSvgWithAssets(icon: AppAssets.youtube).onPress(() => launchUrl(Uri.parse("https://www.youtube.com/c/DrsulaimanAlhabibHospitals"))),
],
)
],
@ -546,9 +554,7 @@ class _FamilyCardWidgetState extends State<FamilyCardWidget> {
// final isParentUser = appState.getAuthenticatedUser()?.isParentUser ?? false;
// final canSwitch = isParentUser || (!isParentUser && widget.profile.responseId == appState.getSuperUserID);
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: Column(
children: [
Column(
@ -607,12 +613,14 @@ class _FamilyCardWidgetState extends State<FamilyCardWidget> {
labelText: "${LocaleKeys.fileno.tr(context: context)}: ${widget.profile.responseId}",
iconSize: 12.w,
),
isActive ? AppCustomChipWidget(
icon: AppAssets.checkmark_icon,
labelText: LocaleKeys.verified.tr(context: context),
iconColor: AppColors.successColor,
labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w),
) : SizedBox.shrink(),
isActive
? AppCustomChipWidget(
icon: AppAssets.checkmark_icon,
labelText: LocaleKeys.verified.tr(context: context),
iconColor: AppColors.successColor,
labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w),
)
: SizedBox.shrink(),
],
),
],
@ -639,73 +647,76 @@ class _FamilyCardWidgetState extends State<FamilyCardWidget> {
),
// isActive && appState.getAuthenticatedUser()!.bloodGroup != null
// ?
isActive ? AppCustomChipWidget(
icon: AppAssets.blood_icon,
isActive
? AppCustomChipWidget(
icon: AppAssets.blood_icon,
labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 8.w),
labelText: appState.getAuthenticatedUser()!.bloodGroup ?? "N/A",
iconColor: AppColors.primaryRedColor)
: SizedBox(),
labelText: appState.getAuthenticatedUser()!.bloodGroup ?? "N/A",
iconColor: AppColors.primaryRedColor)
: SizedBox(),
Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) {
return isActive
? AppCustomChipWidget(
icon: insuranceVM.isInsuranceExpired
? AppAssets.cancel_circle_icon
: insuranceVM.isInsuranceActive
? AppAssets.insurance_active_icon
: AppAssets.alertSquare,
labelText: insuranceVM.isInsuranceExpired
? LocaleKeys.insuranceExpired.tr(context: context)
: insuranceVM.isInsuranceActive
? LocaleKeys.insuranceActive.tr(context: context)
: LocaleKeys.insuranceInActive.tr(context: context),
iconColor: insuranceVM.isInsuranceExpired
? AppColors.primaryRedColor
: insuranceVM.isInsuranceActive
? AppColors.successColor
: AppColors.warningColorYellow,
textColor: insuranceVM.isInsuranceExpired
? AppColors.primaryRedColor
: insuranceVM.isInsuranceActive
? AppColors.successColor
: AppColors.warningColorYellow,
iconSize: 12.w,
deleteIcon: insuranceVM.isInsuranceActive ? null : AppAssets.forward_chevron_icon,
deleteIconColor: insuranceVM.isInsuranceExpired
? AppColors.primaryRedColor
: insuranceVM.isInsuranceActive
? AppColors.successColor
: AppColors.warningColorYellow,
deleteIconHasColor: true,
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);
// showCommonBottomSheetWithoutHeight(
// title: LocaleKeys.notice.tr(context: context),
// context,
// child: Utils.getWarningWidget(
// loadingText: LocaleKeys.insuranceInActiveContactSupport.tr(context: context),
// confirmText: LocaleKeys.contactUs.tr(context: context),
// isShowActionButtons: true,
// onCancelTap: () {
// Navigator.pop(context);
// },
// onConfirmTap: () async {
// launchUrl(Uri.parse("tel://" + "+966 92 006 6666"));
// }),
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// );
}
},
backgroundColor: insuranceVM.isInsuranceExpired
? AppColors.primaryRedColor.withValues(alpha: 0.1)
: insuranceVM.isInsuranceActive
? AppColors.successColor.withValues(alpha: 0.1)
: AppColors.warningColorYellow.withValues(alpha: 0.1),
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: insuranceVM.isInsuranceActive ? 6.w : 0.w),
? AppAssets.insurance_active_icon
: AppAssets.alertSquare,
labelText: insuranceVM.isInsuranceExpired
? LocaleKeys.insuranceExpired.tr(context: context)
: insuranceVM.isInsuranceActive
? LocaleKeys.insuranceActive.tr(context: context)
: LocaleKeys.insuranceInActive.tr(context: context),
iconColor: insuranceVM.isInsuranceExpired
? AppColors.primaryRedColor
: insuranceVM.isInsuranceActive
? AppColors.successColor
: AppColors.warningColorYellow,
textColor: insuranceVM.isInsuranceExpired
? AppColors.primaryRedColor
: insuranceVM.isInsuranceActive
? AppColors.successColor
: AppColors.warningColorYellow,
iconSize: 12.w,
deleteIcon: insuranceVM.isInsuranceActive ? null : AppAssets.forward_chevron_icon,
deleteIconColor: insuranceVM.isInsuranceExpired
? AppColors.primaryRedColor
: insuranceVM.isInsuranceActive
? AppColors.successColor
: AppColors.warningColorYellow,
deleteIconHasColor: true,
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);
// showCommonBottomSheetWithoutHeight(
// title: LocaleKeys.notice.tr(context: context),
// context,
// child: Utils.getWarningWidget(
// loadingText: LocaleKeys.insuranceInActiveContactSupport.tr(context: context),
// confirmText: LocaleKeys.contactUs.tr(context: context),
// isShowActionButtons: true,
// onCancelTap: () {
// Navigator.pop(context);
// },
// onConfirmTap: () async {
// launchUrl(Uri.parse("tel://" + "+966 92 006 6666"));
// }),
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// );
}
},
backgroundColor: insuranceVM.isInsuranceExpired
? AppColors.primaryRedColor.withValues(alpha: 0.1)
: insuranceVM.isInsuranceActive
? AppColors.successColor.withValues(alpha: 0.1)
: AppColors.warningColorYellow.withValues(alpha: 0.1),
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: insuranceVM.isInsuranceActive ? 6.w : 0.w),
).toShimmer2(isShow: insuranceVM.isInsuranceLoading)
: SizedBox.shrink();
}),
@ -747,7 +758,7 @@ class _FamilyCardWidgetState extends State<FamilyCardWidget> {
height: isFoldable ? 50.h : 45.h,
fontSize: 14.f,
).paddingOnly(top: 12.h, right: 16.w, left: 16.w, bottom: 16.h);
}
}
Widget _buildNonParentUserButton(int? superUserId) {
final canSwitchBack = superUserId != null && superUserId == widget.profile.responseId;

@ -159,9 +159,9 @@ class _RateAppointmentClinicState extends State<RateAppointmentClinic> {
Expanded(
child: CustomButton(
text: LocaleKeys.back.tr(context: context),
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
onPressed: () {
appointmentRatingViewModel!.setTitle(LocaleKeys.rateDoctor.tr(context: context));
appointmentRatingViewModel!.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context));

@ -181,9 +181,9 @@ class _RateAppointmentDoctorState extends State<RateAppointmentDoctor> {
Expanded(
child: CustomButton(
text: LocaleKeys.later.tr(context: context),
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
onPressed: () {
Navigator.pop(context);
},

@ -66,8 +66,8 @@ class _ActivityDetailsState extends State<ActivityDetails> {
children: [
Expanded(
child: CustomTabBar(
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
activeTextColor: AppColors.primaryRedColor,
activeBackgroundColor: AppColors.secondaryLightRedColor,
tabs: [
CustomTabBarModel(null, "D"),
CustomTabBarModel(null, "W"),

@ -56,8 +56,8 @@ class _HealthDashboardState extends State<HealthDashboard> with SingleTickerProv
children: [
SizedBox(height: 96.h),
CustomTabBar(
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
activeTextColor: AppColors.primaryRedColor,
activeBackgroundColor: AppColors.secondaryLightRedColor,
tabs: [
CustomTabBarModel(null, "Overview".needTranslation),
CustomTabBarModel(null, "Details".needTranslation),

@ -480,7 +480,7 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> with SingleTicker
backgroundColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
deleteIcon: AppAssets.cancel,
deleteIconColor: AppColors.primaryRedColor,
deleteIconColor: AppColors.criticalActionColor,
deleteIconHasColor: false,
onDeleteTap: () {
viewModel.removeOrgan(organ.id);
@ -500,6 +500,7 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> with SingleTicker
isDisabled: viewModel.selectedOrgans.isEmpty,
backgroundColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
borderColor: Colors.transparent,
),
);
}

@ -121,7 +121,7 @@ class _SelectedOrgansSectionState extends State<SelectedOrgansSection> {
backgroundColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
deleteIcon: AppAssets.cross_circle,
deleteIconColor: AppColors.primaryRedColor,
deleteIconColor: AppColors.criticalActionColor,
deleteIconHasColor: true,
onDeleteTap: () => widget.onRemoveOrgan(organ.id),
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),

@ -94,7 +94,7 @@ class _TriageLevelCardState extends State<TriageLevelCard> with SingleTickerProv
switch (triageLevel) {
case 'emergency_ambulance':
case 'emergency':
return AppColors.chipColorEmergency.withValues(alpha: 0.1);
return AppColors.primaryRedColor.withValues(alpha: 0.1);
case 'consultation_24':
case 'consultation':
return AppColors.chipColorSeekMedicalAdvice.withValues(alpha: 0.1);

@ -257,9 +257,9 @@ class AncillaryOrderCard extends StatelessWidget {
onViewDetails!();
}
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w600,
borderRadius: 10.r,

@ -252,9 +252,9 @@ class AncillaryOrderCard extends StatelessWidget {
onViewDetails!();
}
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
isBold: true,
borderRadius: 10.r,

@ -91,7 +91,7 @@ class SwitchCupBottomSheet extends StatelessWidget {
width: 60.w,
height: 60.w,
decoration: BoxDecoration(
color: isSelected ? AppColors.primaryRedColor.withOpacity(0.08) : Colors.transparent,
color: isSelected ? AppColors.secondaryLightRedColor : Colors.transparent,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: isSelected ? AppColors.primaryRedColor : AppColors.bgScaffoldColor,
@ -271,9 +271,9 @@ class _CustomizeCupBottomSheetState extends State<CustomizeCupBottomSheet> {
SliderTheme(
data: SliderTheme.of(context).copyWith(
activeTrackColor: AppColors.primaryRedColor,
inactiveTrackColor: AppColors.primaryRedColor.withOpacity(0.2),
inactiveTrackColor: AppColors.secondaryLightRedColor,
thumbColor: AppColors.primaryRedColor,
overlayColor: AppColors.primaryRedColor.withOpacity(0.2),
overlayColor: AppColors.secondaryLightRedColor,
trackHeight: 4.h,
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 10.w),
),

@ -62,7 +62,7 @@ class _WeatherDetailsPageState extends State<WeatherDetailsPage> {
case ColorName.ORANGE:
return AppColors.warningLightColor.withValues(alpha: 0.1);
case ColorName.RED:
return AppColors.errorLightColor.withValues(alpha: 0.1);
return AppColors.primaryRedColor.withValues(alpha: 0.1);
}
}
@ -120,7 +120,7 @@ class _WeatherDetailsPageState extends State<WeatherDetailsPage> {
}
// Red: Very High, At High Risk
else if (category.contains('very high') || category.contains('at high risk')) {
return AppColors.errorLightColor.withValues(alpha: 0.1);
return AppColors.primaryRedColor.withValues(alpha: 0.1);
}
// Orange: Risk, High (but not "very high" or "at high risk")
else if (category.contains('risk') || category.contains('high')) {

@ -0,0 +1,83 @@
import 'dart:convert';
import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/services.dart' show rootBundle;
import 'package:hmg_patient_app_new/services/logger_service.dart';
/// Firebase Remote Config wrapper. Fully generic — the app never hardcodes
/// occasion-specific values here; it only knows about the `occasion_config`
/// remote key and reads whatever JSON is published on the Firebase console.
abstract class RemoteConfigService {
/// Must be called once at startup, before the first frame if possible.
Future<void> initialize();
/// Fetches the latest values from Firebase (respecting the configured
/// minimum fetch interval) and activates them.
Future<bool> fetchAndActivate();
/// Raw JSON string currently held for the `occasion_config` key
/// (bundled default until the first successful fetch/activate).
String get occasionConfigRawJson;
}
class RemoteConfigServiceImp implements RemoteConfigService {
static const String occasionConfigKey = 'occasion_config';
static const String _defaultConfigAssetPath = 'assets/json/default_occasion_config.json';
final LoggerService loggerService;
final FirebaseRemoteConfig _remoteConfig;
RemoteConfigServiceImp({
required this.loggerService,
FirebaseRemoteConfig? remoteConfig,
}) : _remoteConfig = remoteConfig ?? FirebaseRemoteConfig.instance;
@override
Future<void> initialize() async {
try {
await _remoteConfig.setConfigSettings(
RemoteConfigSettings(
fetchTimeout: const Duration(seconds: 10),
// In debug builds, always fetch fresh values so you can test
// Remote Config changes instantly without waiting/throttling.
// Release builds keep a sane 1-hour minimum interval.
minimumFetchInterval: kDebugMode ? Duration.zero : const Duration(hours: 1),
),
);
// Bundled fallback so behaviour is deterministic (and identical to the
// current app look) before the very first successful remote fetch.
final defaultJson = await rootBundle.loadString(_defaultConfigAssetPath);
await _remoteConfig.setDefaults(<String, dynamic>{occasionConfigKey: defaultJson});
} catch (ex) {
loggerService.logError('RemoteConfigService.initialize error: $ex');
}
}
@override
Future<bool> fetchAndActivate() async {
try {
return await _remoteConfig.fetchAndActivate();
} catch (ex) {
loggerService.logError('RemoteConfigService.fetchAndActivate error: $ex');
return false;
}
}
@override
String get occasionConfigRawJson {
try {
final value = _remoteConfig.getString(occasionConfigKey);
if (value.isEmpty) return _fallbackDisabledJson;
// Validate JSON shape early; if malformed, fall back safely.
json.decode(value);
return value;
} catch (ex) {
loggerService.logError('RemoteConfigService.occasionConfigRawJson parse error: $ex');
return _fallbackDisabledJson;
}
}
static const String _fallbackDisabledJson = '{"key":"none","enabled":false}';
}

@ -5,41 +5,115 @@ class AppColors {
/// When true, every getter below returns the dark palette value.
static bool isDarkMode = false;
/// Global occasion-theme flag — toggled by OccasionThemeViewModel based on
/// Firebase Remote Config. When false (the default), the app looks exactly
/// like it does today — no colors are affected.
static bool isOccasionThemeActive = false;
/// Remotely-configured primary color for the currently active occasion
/// (e.g. National Day). Null unless an occasion theme is active.
static Color? occasionPrimaryColor;
/// Lighter/translucent (11% opacity) variant of [occasionPrimaryColor],
/// derived automatically by OccasionThemeViewModel. Null unless an
/// occasion theme is active.
static Color? occasionSecondaryColor;
static const transparent = Colors.transparent;
// ── Scaffold / Background ─────────────────────────────────────────────────
static Color get scaffoldBgColor => isDarkMode ? dark.scaffoldBgColor : const Color(0xFFF8F8F8);
static Color get bottomSheetBgColor => isDarkMode ? dark.bottomSheetBgColor : const Color(0xFFF8F8FA);
static Color get lightGreyEFColor => isDarkMode ? dark.lightGreyEFColor : const Color(0xffeaeaff);
static Color get greyF7Color => isDarkMode ? dark.greyF7Color : const Color(0xffF7F7F7);
static Color get greyInfoTextColor => isDarkMode ? dark.greyInfoTextColor : const Color(0xff777777);
static Color get lightGrayColor => isDarkMode ? dark.lightGrayColor : const Color(0xff808080);
static Color get greyTextColorLight => isDarkMode ? dark.greyTextColorLight : const Color(0xFFA2A2A2);
static Color get mainPurple => isDarkMode ? dark.mainPurple : const Color(0xFF7954F7);
// ── Core Colors ───────────────────────────────────────────────────────────
static Color get whiteColor => isDarkMode ? dark.whiteColor : const Color(0xFFffffff);
// static const Color primaryRedColor = Color(0xFFED1C2B);
static Color get primaryRedColor => isDarkMode ? dark.primaryRedColor : Color(0xFFED1C2B);
static Color get secondaryLightRedColor => isDarkMode ? dark.secondaryLightRedColor : const Color(0xFFFEE9EA);
/// Occasion-aware primary brand color. Resolves to the remotely-configured
/// occasion color when active (e.g. National Day green), otherwise falls
/// back to the original dark/light red — identical to current behavior.
///
/// NOTE: Do NOT use this getter for `.withOpacity()/.withValues()` derived
/// (translucent) colors — use [primaryRedColorBase] for those instead, so
/// occasion theming never affects translucent overlays/backgrounds.
static Color get primaryRedColor => (isOccasionThemeActive && occasionPrimaryColor != null) ? occasionPrimaryColor! : primaryRedColorBase;
/// The original, non-themed brand red (or its dark-mode variant). Always
/// stable regardless of any active occasion theme — intended for opacity
/// derived usages (e.g. `AppColors.primaryRedColorBase.withOpacity(0.1)`).
static Color get primaryRedColorBase => isDarkMode ? dark.primaryRedColor : const Color(0xFFED1C2B);
/// Semantic color for critical/negative actions (delete/deactivate account,
/// cancel appointment, clear/reset, emergency, etc.). Same light/dark
/// values as [primaryRedColorBase] — respects dark mode, but is never
/// affected by the active occasion/remote-config theme, so destructive
/// actions always read as "red" regardless of any occasion branding.
static Color get criticalActionColor => primaryRedColorBase;
/// Occasion-aware light/tint variant of [primaryRedColor] (e.g. chip and
/// badge backgrounds). Resolves to the remotely-configured occasion's
/// secondary color when active (e.g. National Day's light green),
/// otherwise falls back to the original dark/light tint — identical to
/// current behavior.
///
/// NOTE: Do NOT use this getter for `.withOpacity()/.withValues()` derived
/// colors — use [secondaryLightRedColorBase] for those instead, so
/// occasion theming never affects translucent overlays/backgrounds.
static Color get secondaryLightRedColor => (isOccasionThemeActive && occasionSecondaryColor != null) ? occasionSecondaryColor! : secondaryLightRedColorBase;
/// The original, non-themed light-red tint (or its dark-mode variant).
/// Always stable regardless of any active occasion theme — intended for
/// opacity derived usages.
static Color get secondaryLightRedColorBase => isDarkMode ? dark.secondaryLightRedColor : const Color(0xFFFEE9EA);
static const Color successColor = Color(0xFF18C273);
static Color get successLightBgColor => isDarkMode ? dark.successLightBgColor : const Color(0xffDDF6EA);
static Color get greyColor => isDarkMode ? dark.greyColor : const Color(0xFFEFEFF0);
static Color get chipBgColor => isDarkMode ? dark.chipBgColor : const Color(0xFFEFEFF0);
static Color get textColor => isDarkMode ? dark.textColor : const Color(0xFF2E3039);
static Color get iconColor => isDarkMode ? dark.iconColor : const Color(0xFF2E3039);
static Color get borderGrayColor => isDarkMode ? dark.borderGrayColor : const Color(0x332E3039);
static Color get textColorLight => isDarkMode ? dark.textColorLight : const Color(0xFF5E5E5E);
static Color get dividerColor => isDarkMode ? dark.dividerColor : const Color(0x40D2D2D2);
static Color get warningColorYellow => isDarkMode ? dark.warningColorYellow : const Color(0xFFF4A308);
static Color get inputLabelTextColor => isDarkMode ? dark.inputLabelTextColor : const Color(0xff898A8D);
static Color get greyTextColor => isDarkMode ? dark.greyTextColor : const Color(0xFF8F9AA3);
static Color get lightGrayBGColor => isDarkMode ? dark.lightGrayBGColor : const Color(0x142E3039);
static Color get checkBoxBorderColor => isDarkMode ? dark.checkBoxBorderColor : const Color(0xffD2D2D2);
static Color get pharmacyBGColor => isDarkMode ? dark.pharmacyBGColor : const Color(0xFF359846);
static Color get lightGreenColor => isDarkMode ? dark.lightGreenColor : const Color(0xFF0ccedde);
static Color get ratingColorYellow => isDarkMode ? dark.ratingColorYellow : const Color(0xFFFFAF15);
static Color get spacerLineColor => isDarkMode ? dark.spacerLineColor : const Color(0x2E30391A);
static Color get habibPharmacyColor => const Color(0xFF2d9137);
// ── Doctor Rating Colors ──────────────────────────────────────────────────
@ -52,92 +126,147 @@ class AppColors {
static const Color ratingBadgeFiveStarColor = Color(0xFF5BBD8E); // Green badge for ≥4.0
static const Color ratingPercentageTextColor = Color(0xFF4A4A4A); // Dark grey for percentage text
// ── Semantic / Status Colors ──────────────────────────────────────────────
static Color get errorColor => isDarkMode ? dark.errorColor : const Color(0xFFED1C2B);
static Color get alertColor => isDarkMode ? dark.alertColor : const Color(0xFFD48D05);
static Color get infoColor => isDarkMode ? dark.infoColor : const Color(0xFF0B85F7);
static Color get warningColor => isDarkMode ? dark.warningColor : const Color(0xFFFFCC00);
static Color get switchBackgroundColor => isDarkMode ? dark.switchBackgroundColor : const Color(0x2618C273);
static Color get bottomNAVBorder => isDarkMode ? dark.bottomNAVBorder : const Color(0xFFEEEEEE);
static Color get quickLoginColor => isDarkMode ? dark.quickLoginColor : const Color(0xFF666666);
static Color get tooltipTextColor => isDarkMode ? dark.tooltipTextColor : const Color(0xFF414D55);
static Color get graphGridColor => isDarkMode ? dark.graphGridColor : const Color(0x4D18C273);
static Color get highAndLow => isDarkMode ? dark.highAndLow : const Color(0xFFFFAF15);
static Color get labelTextColor => isDarkMode ? dark.labelTextColor : const Color(0xFF838383);
static Color get calenderTextColor => isDarkMode ? dark.calenderTextColor : const Color(0xFFD0D0D0);
static Color get lightGreenButtonColor => isDarkMode ? dark.lightGreenButtonColor : const Color(0x2618C273);
static Color get lightRedButtonColor => isDarkMode ? dark.lightRedButtonColor : const Color(0x1AED1C2B);
// ── Status Colors ─────────────────────────────────────────────────────────
static Color get statusPendingColor => isDarkMode ? dark.statusPendingColor : const Color(0xffCC9B14);
static Color get statusProcessingColor => isDarkMode ? dark.statusProcessingColor : const Color(0xff2E303A);
static Color get statusCompletedColor => isDarkMode ? dark.statusCompletedColor : const Color(0xff359846);
static Color get statusRejectedColor => isDarkMode ? dark.statusRejectedColor : const Color(0xffD02127);
// ── Info Banner Colors ────────────────────────────────────────────────────
static Color get infoBannerBgColor => isDarkMode ? dark.infoBannerBgColor : const Color(0xFFFFF4E6);
static Color get infoBannerBorderColor => isDarkMode ? dark.infoBannerBorderColor : const Color(0xFFFFE5B4);
static Color get infoBannerIconColor => isDarkMode ? dark.infoBannerIconColor : const Color(0xFFCC9B14);
static Color get infoBannerTextColor => isDarkMode ? dark.infoBannerTextColor : const Color(0xFF856404);
// ── Symptoms Checker ─────────────────────────────────────────────<EFBFBD><EFBFBD>────────
static Color get chipColorSeekMedicalAdvice => isDarkMode ? dark.chipColorSeekMedicalAdvice : const Color(0xFFFFAF15);
static Color get chipTextColorSeekMedicalAdvice => isDarkMode ? dark.chipTextColorSeekMedicalAdvice : const Color(0xFFD48D05);
// ── Services Page Colors ──────────────────────────────────────────────────
static Color get eReferralCardColor => isDarkMode ? dark.eReferralCardColor : const Color(0xFFFF8012);
static Color get bloodDonationCardColor => isDarkMode ? dark.bloodDonationCardColor : const Color(0xFFFF5662);
static Color get myChildVaccineCardColor => isDarkMode ? dark.myChildVaccineCardColor : const Color(0xFFFF2D78);
static Color get bookAppointment => isDarkMode ? dark.bookAppointment : const Color(0xFF415364);
// ── Health Calculators Colors ─────────────────────────────────────────────
static Color get bmiCalculatorColor => isDarkMode ? dark.bmiCalculatorColor : const Color(0xFFF43333);
static Color get caloriesCalculatorColor => isDarkMode ? dark.caloriesCalculatorColor : const Color(0xFF0B85F7);
static Color get bmrCalculatorColor => isDarkMode ? dark.bmrCalculatorColor : const Color(0xFF18C273);
static Color get idealBodyWeightColor => isDarkMode ? dark.idealBodyWeightColor : const Color(0xFFFF8012);
static Color get bodyFatCalculatorColor => isDarkMode ? dark.bodyFatCalculatorColor : const Color(0xFFFF5662);
static Color get carbsProteinFatColor => isDarkMode ? dark.carbsProteinFatColor : const Color(0xFF18C273);
static Color get ovulationPeriodColor => isDarkMode ? dark.ovulationPeriodColor : const Color(0xFFFF47A9);
static Color get deliveryDueDateColor => isDarkMode ? dark.deliveryDueDateColor : const Color(0xFFFF2D78);
// ── Water Monitor ─────────────────────────────────────────────────────────
static Color get blueColor => isDarkMode ? dark.blueColor : const Color(0xFF4EB5FF);
static Color get blueGradientColorOne => isDarkMode ? dark.blueGradientColorOne : const Color(0xFFF1F7FD);
static Color get blueGradientColorTwo => isDarkMode ? dark.blueGradientColorTwo : const Color(0xFFD9EFFF);
// ── Shimmer ───────────────────────────────────────────────────────────────
static Color get shimmerBaseColor => isDarkMode ? dark.shimmerBaseColor : const Color(0xFFE0E0E0);
static Color get shimmerHighlightColor => isDarkMode ? dark.shimmerHighlightColor : const Color(0xFFF5F5F5);
static Color get covid29Color => isDarkMode ? dark.covid29Color : const Color(0xff2563EB);
static Color get lightGreyTextColor => isDarkMode ? dark.lightGreyTextColor : const Color(0xFF959595);
static Color get labelColorYellow => isDarkMode ? dark.labelColorYellow : const Color(0xFFFBCB6E);
static const LinearGradient aiLinearGradient =
LinearGradient(colors: [Color(0xFF8A38F5), Color(0xFFE20BBB)], begin: Alignment.topLeft, end: Alignment.bottomRight);
static const LinearGradient aiLinearGradient = LinearGradient(colors: [Color(0xFF8A38F5), Color(0xFFE20BBB)], begin: Alignment.topLeft, end: Alignment.bottomRight);
// ── Aliases ───────────────────────────────────────────────────────────────
static Color get bgScaffoldColor => scaffoldBgColor;
static Color get primaryRedBorderColor => primaryRedColor;
static Color get chipPrimaryRedBorderColor => primaryRedColor;
static Color get criticalLowAndHigh => primaryRedColor;
static Color get chipColorEmergency => primaryRedColor;
static Color get errorLightColor => primaryRedColor;
static Color get secondaryLightRedBorderColor => secondaryLightRedColor;
static Color get bgRedLightColor => secondaryLightRedColor;
static Color get chipSecondaryLightRedColor => secondaryLightRedColor;
static Color get blackBgColor => textColor;
static Color get blackColor => textColor;
static Color get borderOnlyColor => textColor;
static Color get chipBorderColorOpacity20 => borderGrayColor;
static Color get bgGreenColor => successColor;
static Color get textGreenColor => successColor;
static Color get successLightColor => successColor;
static Color get thumbColor => successColor;
static Color get chipColorMonitor => successColor;
static Color get greyLightColor => greyColor;
static Color get alertLightColor => alertColor;
static Color get infoLightColor => infoColor;
static Color get warningLightColor => warningColor;
// ── Dark Mode Palette ─────────────────────────────────────────────────────
@ -152,11 +281,17 @@ class AppColorsDark {
// Scaffold / Background
Color get scaffoldBgColor => const Color(0xFF191919);
Color get bottomSheetBgColor => const Color(0xFF1C1C1E);
Color get lightGreyEFColor => const Color(0xFF2A2A3A);
Color get greyF7Color => const Color(0xFF1E1E1E);
Color get greyInfoTextColor => const Color(0xFF9E9E9E);
Color get lightGrayColor => const Color(0xFF9E9E9E);
Color get greyTextColorLight => const Color(0xFF757575);
// ⚠️ NEW — was missing from AppColorsDark
@ -164,114 +299,192 @@ class AppColorsDark {
// Core
Color get baseColor => const Color(0xFF1E1E1E);
// ⚠️ NEW — maps whiteColor → dark surface color
Color get whiteColor => const Color(0xFF1E1E1E); // surface replacement for white
Color get primaryRedColor => const Color(0xFFDE5C5D);
Color get secondaryLightRedColor => const Color(0xFF3A1015);
Color get successColor => const Color(0xFF18C273);
Color get successLightBgColor => const Color(0xFF0D2E1F);
Color get textColor => const Color(0xFFFFFFFF);
Color get iconColor => const Color(0xFFFFFFFF);
Color get borderGrayColor => const Color(0x55ECECEC);
Color get textColorLight => const Color(0xFFB0B0B0);
Color get dividerColor => const Color(0x33FFFFFF);
Color get warningColorYellow => const Color(0xFFF4A308);
Color get inputLabelTextColor => const Color(0xFF9E9E9E);
Color get greyTextColor => const Color(0xFF8F9AA3);
Color get lightGrayBGColor => const Color(0x22FFFFFF);
Color get checkBoxBorderColor => const Color(0xFF555555);
Color get pharmacyBGColor => const Color(0xFF359846);
// ⚠️ NEW — was missing from AppColorsDark
Color get lightGreenColor => const Color(0xFF0ccedde); // same teal accent — works on dark
Color get ratingColorYellow => const Color(0xFFFFAF15);
Color get spacerLineColor => const Color(0x22FFFFFF);
// Semantic / Status
Color get errorColor => const Color(0xFFD63D48);
Color get alertColor => const Color(0xFFD48D05);
Color get infoColor => const Color(0xFF4DA6FF);
Color get warningColor => const Color(0xFFFFCC00);
Color get greyColor => const Color(0xFF2C2C2E);
Color get chipBgColor => const Color(0xFF3E3E3E);
Color get cardDarkBorderColor => const Color(0x0DEAEAEA);
Color get serviceCardButtonColor => const Color(0xFF3E3E3E);
Color get switchBackgroundColor => const Color(0x2618C273);
Color get bottomNAVBorder => const Color(0xFF2C2C2E);
Color get quickLoginColor => const Color(0xFF9E9E9E);
Color get tooltipTextColor => const Color(0xFFCFD8DC);
Color get graphGridColor => const Color(0x4D18C273);
Color get highAndLow => const Color(0xFFFFAF15);
Color get labelTextColor => const Color(0xFF9E9E9E);
Color get calenderTextColor => const Color(0xFF555555);
Color get lightGreenButtonColor => const Color(0x2618C273);
Color get lightRedButtonColor => const Color(0x1AED1C2B);
// Status
Color get statusPendingColor => const Color(0xFFCC9B14);
Color get statusProcessingColor => const Color(0xFFECECEC);
Color get statusCompletedColor => const Color(0xFF359846);
Color get statusRejectedColor => const Color(0xFFD02127);
// Info Banner
Color get infoBannerBgColor => const Color(0xFF2A1F0A);
Color get infoBannerBorderColor => const Color(0xFF5A3D0A);
Color get infoBannerIconColor => const Color(0xFFCC9B14);
Color get infoBannerTextColor => const Color(0xFFFFCB6B);
// Symptoms Checker
Color get chipColorSeekMedicalAdvice => const Color(0xFFFFAF15);
Color get chipTextColorSeekMedicalAdvice => const Color(0xFFD48D05);
// Services Page
Color get eReferralCardColor => const Color(0xFFFF8012);
Color get bloodDonationCardColor => const Color(0xFFFF5662);
Color get myChildVaccineCardColor => const Color(0xFFFF2D78);
Color get bookAppointment => const Color(0xFF415364);
// Health Calculators
Color get bmiCalculatorColor => const Color(0xFFF43333);
Color get caloriesCalculatorColor => const Color(0xFF0B85F7);
Color get bmrCalculatorColor => const Color(0xFF18C273);
Color get idealBodyWeightColor => const Color(0xFFFF8012);
Color get bodyFatCalculatorColor => const Color(0xFFFF5662);
Color get carbsProteinFatColor => const Color(0xFF18C273);
Color get ovulationPeriodColor => const Color(0xFFFF47A9);
Color get deliveryDueDateColor => const Color(0xFFFF2D78);
// Water Monitor
Color get blueColor => const Color(0xFF4EB5FF);
Color get blueGradientColorOne => const Color(0xFF101C2A);
Color get blueGradientColorTwo => const Color(0xFF0D2235);
// Shimmer
Color get shimmerBaseColor => const Color(0xFF2C2C2C);
Color get shimmerHighlightColor => const Color(0xFF3D3D3D);
Color get covid29Color => const Color(0xFF2563EB);
Color get lightGreyTextColor => const Color(0xFF757575);
Color get labelColorYellow => const Color(0xFFFBCB6E);
// Aliases
Color get bgScaffoldColor => scaffoldBgColor;
Color get primaryRedBorderColor => primaryRedColor;
Color get chipPrimaryRedBorderColor => primaryRedColor;
Color get criticalLowAndHigh => primaryRedColor;
Color get chipColorEmergency => primaryRedColor;
Color get errorLightColor => primaryRedColor;
Color get secondaryLightRedBorderColor => secondaryLightRedColor;
Color get bgRedLightColor => secondaryLightRedColor;
Color get chipSecondaryLightRedColor => secondaryLightRedColor;
Color get blackBgColor => textColor;
Color get blackColor => textColor;
Color get borderOnlyColor => textColor;
Color get chipBorderColorOpacity20 => borderGrayColor;
Color get bgGreenColor => successColor;
Color get textGreenColor => successColor;
Color get successLightColor => successColor;
Color get thumbColor => successColor;
Color get chipColorMonitor => successColor;
Color get greyLightColor => greyColor;
Color get alertLightColor => alertColor;
Color get infoLightColor => infoColor;
Color get warningLightColor => warningColor;
}
@ -283,61 +496,99 @@ extension AppColorsContext on BuildContext {
// Scaffold / Background
Color get scaffoldBgColor => _isDark ? AppColors.dark.scaffoldBgColor : const Color(0xFFF8F8F8);
Color get bottomSheetBgColor => _isDark ? AppColors.dark.bottomSheetBgColor : const Color(0xFFF8F8FA);
Color get greyF7Color => _isDark ? AppColors.dark.greyF7Color : const Color(0xffF7F7F7);
Color get greyInfoTextColor => _isDark ? AppColors.dark.greyInfoTextColor : const Color(0xff777777);
Color get lightGrayColor => _isDark ? AppColors.dark.lightGrayColor : const Color(0xff808080);
// Core
Color get cardBaseColor => _isDark ? AppColors.dark.baseColor : const Color(0xFFFFFFFF);
Color get primaryRedColor => _isDark ? AppColors.dark.primaryRedColor : AppColors.primaryRedColor;
Color get secondaryLightRedColor => _isDark ? AppColors.dark.serviceCardButtonColor : const Color(0xFFFEE9EA);
Color get secondaryLightRedForegroundColor => _isDark ? const Color(0xFFFFFFFF) : AppColors.primaryRedColor;
Color get successColor => AppColors.successColor;
Color get successLightBgColor => _isDark ? AppColors.dark.successLightBgColor : const Color(0xffDDF6EA);
Color get textColor => _isDark ? AppColors.dark.textColor : const Color(0xFF2E3039);
Color get iconColor => _isDark ? AppColors.dark.iconColor : const Color(0xFF2E3039);
Color get borderGrayColor => _isDark ? AppColors.dark.borderGrayColor : const Color(0x332E3039);
Color get textColorLight => _isDark ? AppColors.dark.textColorLight : const Color(0xFF5E5E5E);
Color get dividerColor => _isDark ? AppColors.dark.dividerColor : const Color(0x40D2D2D2);
Color get inputLabelTextColor => _isDark ? AppColors.dark.inputLabelTextColor : const Color(0xff898A8D);
Color get greyTextColor => AppColors.greyTextColor;
Color get lightGrayBGColor => _isDark ? AppColors.dark.lightGrayBGColor : const Color(0x142E3039);
Color get checkBoxBorderColor => _isDark ? AppColors.dark.checkBoxBorderColor : const Color(0xffD2D2D2);
// Status / Semantic
Color get errorColor => _isDark ? AppColors.dark.errorColor : const Color(0xFFED1C2B);
Color get alertColor => AppColors.alertColor;
Color get infoColor => _isDark ? AppColors.dark.infoColor : const Color(0xFF0B85F7);
Color get warningColor => AppColors.warningColor;
Color get greyColor => _isDark ? AppColors.dark.greyColor : const Color(0xFFEFEFF0);
Color get chipBgColor => _isDark ? AppColors.dark.chipBgColor : const Color(0xFFEFEFF0);
Color get cardDarkBorderColor => _isDark ? AppColors.dark.cardDarkBorderColor : Colors.transparent;
Color get serviceCardButtonColor => _isDark ? AppColors.dark.serviceCardButtonColor : const Color(0xFF2E3039);
Color get bottomNAVBorder => _isDark ? AppColors.dark.bottomNAVBorder : const Color(0xFFEEEEEE);
Color get labelTextColor => _isDark ? AppColors.dark.labelTextColor : const Color(0xFF838383);
Color get lightGreyTextColor => _isDark ? AppColors.dark.lightGreyTextColor : const Color(0xFF959595);
// Info Banner
Color get infoBannerBgColor => _isDark ? AppColors.dark.infoBannerBgColor : const Color(0xFFFFF4E6);
Color get infoBannerBorderColor => _isDark ? AppColors.dark.infoBannerBorderColor : const Color(0xFFFFE5B4);
Color get infoBannerIconColor => AppColors.infoBannerIconColor;
Color get infoBannerTextColor => _isDark ? AppColors.dark.infoBannerTextColor : const Color(0xFF856404);
// Water Monitor
Color get blueColor => AppColors.blueColor;
Color get blueGradientColorOne => _isDark ? AppColors.dark.blueGradientColorOne : const Color(0xFFF1F7FD);
Color get blueGradientColorTwo => _isDark ? AppColors.dark.blueGradientColorTwo : const Color(0xFFD9EFFF);
// Shimmer
Color get shimmerBaseColor => _isDark ? AppColors.dark.shimmerBaseColor : const Color(0xFFE0E0E0);
Color get shimmerHighlightColor => _isDark ? AppColors.dark.shimmerHighlightColor : const Color(0xFFF5F5F5);
static const Color tooltipColor = Color(0xFF1AACACAC);
// Aliases
Color get bgScaffoldColor => scaffoldBgColor;
Color get bgGreenColor => successColor;
Color get bgRedLightColor => secondaryLightRedColor;
Color get blackColor => textColor;
Color get blackBgColor => textColor;
}

@ -115,8 +115,7 @@ 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) {
@ -289,8 +288,7 @@ 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,
@ -306,38 +304,19 @@ 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!,
]
],
@ -353,8 +332,7 @@ class _ScrollAnimatedTitleState extends State<ScrollAnimatedTitle> {
child: Container(
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),

@ -28,7 +28,13 @@ class BottomNavigation extends StatelessWidget {
icon: AppAssets.bookAppoBottom,
fillIcon: AppAssets.bookAppoBottom,
label: LocaleKeys.appointments.tr(context: context),
iconSize: 32, isSpecial: true),
iconSize: 32,
isSpecial: true,
// Explicit dynamic color — the SVG asset itself is a fixed red, so
// without this override the Appointments icon would never react to
// dark mode or the active occasion theme (e.g. National Day green).
color: AppColors.primaryRedColor,
),
appState.isAuthenticated
// ? BottomNavItem(icon: AppAssets.toDoBottom, label: LocaleKeys.todoList.tr(context: context))
? BottomNavItem(icon: AppAssets.symptomCheckerBottomIcon, fillIcon: AppAssets.symptomCheckerBottomFillIcon, label: LocaleKeys.symptoms.tr(context: context))
@ -75,7 +81,8 @@ class BottomNavigation extends StatelessWidget {
icon: isSelected ? item.fillIcon : item.icon,
height: item.iconSize.h,
width: item.iconSize.w,
applyThemeColor: !item.isSpecial
applyThemeColor: !item.isSpecial,
iconColor: item.color,
// iconColor: isSelected ? Colors.black87 : Colors.black87,
),
// const SizedBox(height: 10),

@ -10,8 +10,8 @@ class CustomButton extends StatefulWidget {
final String text;
final String? icon;
final VoidCallback? onPressed;
final Color backgroundColor;
final Color borderColor;
final Color? backgroundColor;
final Color? borderColor;
final Color textColor;
final double? borderRadius;
final double borderWidth;
@ -33,8 +33,8 @@ class CustomButton extends StatefulWidget {
super.key,
required this.text,
required this.onPressed,
this.backgroundColor = const Color(0xFFED1C2B),
this.borderColor = const Color(0xFFED1C2B),
this.backgroundColor,
this.borderColor,
this.textColor = Colors.white,
this.borderRadius,
this.borderWidth = 2,
@ -93,6 +93,8 @@ class _CustomButtonState extends State<CustomButton> with SingleTickerProviderSt
final iconS = widget.iconSize ?? (24.h);
final fontS = widget.fontSize ?? (16.f);
final isButtonDisabled = widget.isDisabled || widget.onPressed == null;
final resolvedBackgroundColor = widget.backgroundColor ?? AppColors.primaryRedColor;
final resolvedBorderColor = widget.borderColor ?? AppColors.primaryRedColor;
return Semantics(
button: true,
@ -134,10 +136,10 @@ class _CustomButtonState extends State<CustomButton> with SingleTickerProviderSt
width: widget.width,
padding: widget.padding,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: isButtonDisabled ? AppColors.inputLabelTextColor.withValues(alpha: 0.3) : widget.backgroundColor,
color: isButtonDisabled ? AppColors.inputLabelTextColor.withValues(alpha: 0.3) : resolvedBackgroundColor,
borderRadius: radius,
customBorder: BorderRadius.circular(radius),
side: widget.borderSide ?? BorderSide(width: widget.borderWidth.h, color: isButtonDisabled ? Colors.transparent : widget.borderColor)),
side: widget.borderSide ?? BorderSide(width: widget.borderWidth.h, color: isButtonDisabled ? Colors.transparent : resolvedBorderColor)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,

@ -163,7 +163,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
),
startRangeSelectionColor: AppColors.primaryRedColor,
endRangeSelectionColor: AppColors.primaryRedColor,
rangeSelectionColor: AppColors.primaryRedColor.withOpacity(0.1),
rangeSelectionColor: AppColors.secondaryLightRedColor,
rangeTextStyle: TextStyle(
fontFamily: "Poppins",
),
@ -963,7 +963,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
children: [
AppCustomChipWidget(
labelText: LocaleKeys.thisWeek.tr(context: context),
backgroundColor: model.currentlySelectedRange == Range.WEEKLY ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor,
backgroundColor: model.currentlySelectedRange == Range.WEEKLY ? AppColors.secondaryLightRedColor : AppColors.whiteColor,
shape: RoundedRectangleBorder(
side: BorderSide(
color: model.currentlySelectedRange == Range.WEEKLY ? AppColors.primaryRedBorderColor : AppColors.chipBorderColorOpacity20,
@ -977,7 +977,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
}),
AppCustomChipWidget(
labelText: LocaleKeys.lastMonth.tr(context: context),
backgroundColor: model.currentlySelectedRange == Range.LAST_MONTH ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor,
backgroundColor: model.currentlySelectedRange == Range.LAST_MONTH ? AppColors.secondaryLightRedColor : AppColors.whiteColor,
shape: RoundedRectangleBorder(
side: BorderSide(
color: model.currentlySelectedRange == Range.LAST_MONTH ? AppColors.primaryRedBorderColor : AppColors.chipBorderColorOpacity20,
@ -991,7 +991,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
}),
AppCustomChipWidget(
labelText: LocaleKeys.lastSixMonths.tr(context: context),
backgroundColor: model.currentlySelectedRange == Range.LAST_6MONTH ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor,
backgroundColor: model.currentlySelectedRange == Range.LAST_6MONTH ? AppColors.secondaryLightRedColor : AppColors.whiteColor,
shape: RoundedRectangleBorder(
side: BorderSide(
color: model.currentlySelectedRange == Range.LAST_6MONTH ? AppColors.primaryRedBorderColor : AppColors.chipBorderColorOpacity20,
@ -1012,7 +1012,7 @@ class _DateRangeSelectorState extends State<DateRangeSelector> {
],
),
labelText: "${LocaleKeys.year.tr(context: context)} ${model.getCurrentYear}",
backgroundColor: model.currentlySelectedRange == Range.THIS_YEAR ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor,
backgroundColor: model.currentlySelectedRange == Range.THIS_YEAR ? AppColors.secondaryLightRedColor : AppColors.whiteColor,
shape: RoundedRectangleBorder(
side: BorderSide(
color: model.currentlySelectedRange == Range.THIS_YEAR ? AppColors.primaryRedBorderColor : AppColors.chipBorderColorOpacity20,

@ -56,21 +56,22 @@ class _LanguageSelectorState extends State<LanguageSelector> {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
// currentLangData['name']?.toUpperCase() ?? 'EN',
widget.currentLanguage == 'ar' ? "ENGLISH" : "العربية",
style: context.dynamicTextStyle(
fontWeight: FontWeight.w600,
fontSize: 14.f,
color: AppColors.primaryRedColor,
letterSpacing: 0.1,
isLanguageSwitcher: true,
fontSize: 14.f,
color: AppColors.primaryRedColor,
letterSpacing: 0.1,
isLanguageSwitcher: true,
fontFamily: widget.currentLanguage == 'ar' ? "Poppins" : "CairoArabic"),
),
SizedBox(width: 6.h),
Utils.buildSvgWithAssets(icon: AppAssets.language),
Utils.buildSvgWithAssets(
icon: AppAssets.language,
iconColor: AppColors.primaryRedColor,
),
],
),
),

@ -220,7 +220,7 @@ class _MapTypeItem extends StatelessWidget {
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.h),
decoration: BoxDecoration(
color: isSelected
? AppColors.primaryRedColor.withOpacity(0.1)
? AppColors.secondaryLightRedColor
: Colors.transparent,
borderRadius: BorderRadius.circular(8.r),
),

@ -372,7 +372,7 @@ class _MapTypeItem extends StatelessWidget {
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.h),
decoration: BoxDecoration(
color: isSelected
? AppColors.primaryRedColor.withOpacity(0.1)
? AppColors.secondaryLightRedColor
: Colors.transparent,
borderRadius: BorderRadius.circular(8.r),
),

@ -486,6 +486,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.10.10"
firebase_remote_config:
dependency: "direct main"
description:
name: firebase_remote_config
sha256: e1635b1e8713f4a823920ec3a56a14034b90ce455d47746ab0da994857f370cf
url: "https://pub.dev"
source: hosted
version: "5.5.0"
firebase_remote_config_platform_interface:
dependency: transitive
description:
name: firebase_remote_config_platform_interface
sha256: ce836c5c62056edbe23ef501e6876691ee32476afd12fe95b76e57bba9d25485
url: "https://pub.dev"
source: hosted
version: "2.0.0"
firebase_remote_config_web:
dependency: transitive
description:
name: firebase_remote_config_web
sha256: "9dbd75024bfcd47c05046c95f9cf648a8fc862b096bcf8ea1e4b855d50ea19ad"
url: "https://pub.dev"
source: hosted
version: "1.8.9"
fixnum:
dependency: transitive
description:
@ -797,10 +821,10 @@ packages:
dependency: "direct main"
description:
name: flutter_zoom_videosdk
sha256: "46a4dea664b1c969099328a499c198a1755adf9ac333dea28bea5187910b3bf9"
sha256: "87caad07b994f09b9d8c4b8054c0dff5659029abae38dd6ec223460c3f1bcb7d"
url: "https://pub.dev"
source: hosted
version: "2.1.10"
version: "2.5.10"
fluttertoast:
dependency: "direct main"
description:
@ -809,6 +833,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.2.14"
freerasp:
dependency: "direct main"
description:
name: freerasp
sha256: a3d019fa2c1a8f4bb15504d86997f583d1066fe0832172fd68ea6cd521e6fdcc
url: "https://pub.dev"
source: hosted
version: "8.2.2"
fwfh_cached_network_image:
dependency: transitive
description:
@ -1657,6 +1689,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.28.0"
safe_device:
dependency: "direct main"
description:
name: safe_device
sha256: ad1b32a564484c710f6d52b5a526838c8a7d999c8ab2e14890ff55c06a018248
url: "https://pub.dev"
source: hosted
version: "1.4.1"
sanitize_html:
dependency: transitive
description:

@ -71,6 +71,7 @@ dependencies:
google_api_availability: ^5.0.1
firebase_analytics: ^11.5.1
firebase_crashlytics: ^4.3.8
firebase_remote_config: ^5.5.0
jiffy: ^6.4.3
# hijri_gregorian_calendar:
# path: /Users/aamir/StudioProjects/hijri_gregorian_calendar

Loading…
Cancel
Save