diff --git a/PROJECT_ARCHITECTURE.md b/PROJECT_ARCHITECTURE.md new file mode 100644 index 00000000..00ee9672 --- /dev/null +++ b/PROJECT_ARCHITECTURE.md @@ -0,0 +1,1768 @@ + +You are a senior Flutter developer. You will be given this entire document as your specification. +Your task: generate a complete, compilable Flutter project from scratch following every instruction below. +Do NOT skip any file. Do NOT summarize. Generate FULL, working Dart code for every file listed. +When you see placeholder names like `{{APP_NAME}}`, `{{PACKAGE_NAME}}`, `{{BASE_URL}}`, replace them with the values the user provides. If no values are provided, use the defaults in Section 1. + + +# Flutter Project Generator — Architecture Specification + +> **What this document is:** A precise, machine-readable specification for an AI to generate a greenfield Flutter project. Every file path, every class, every method signature, and every pattern is defined below with copy-ready code. Follow it top-to-bottom. + +--- + +## 1. DEFAULTS — Replace These for Your Project + +``` +APP_NAME = "My App" +DART_PACKAGE = "my_app" +ORG_IDENTIFIER = "com.example.myapp" +BASE_URL = "https://api.example.com/" +PRIMARY_COLOR = 0xFFED1C2B (Red) +FIGMA_WIDTH = 430 +FIGMA_HEIGHT = 927 +SUPPORTED_LOCALES = [en-US, ar-SA] +PRIMARY_FONT = "Poppins" +SECONDARY_FONT = "GESSTwo" (Arabic font) +``` + +When the user asks you to generate this project, first ask them for these values. If they say "use defaults," use the values above. + +--- + +## 2. COMMAND — Create the Flutter Project + +Run this first: +```bash +flutter create --org {{ORG_IDENTIFIER}} --project-name {{DART_PACKAGE}} {{DART_PACKAGE}} +cd {{DART_PACKAGE}} +``` + +--- + +## 3. FILE: `pubspec.yaml` + +Generate this file EXACTLY. Do not add or remove packages. + +```yaml +name: {{DART_PACKAGE}} +description: "{{APP_NAME}}" +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: ">=3.6.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + + # === Core Architecture === + provider: ^6.1.5+1 + get_it: ^8.2.0 + dartz: ^0.10.1 + equatable: ^2.0.7 + + # === Networking === + http: ^1.5.0 + connectivity_plus: ^6.1.5 + + # === Firebase === + firebase_core: any + firebase_messaging: ^15.2.10 + firebase_analytics: ^11.5.1 + firebase_crashlytics: ^4.3.8 + + # === UI === + cupertino_icons: ^1.0.8 + flutter_svg: ^2.2.0 + cached_network_image: ^3.4.1 + auto_size_text: ^3.0.0 + shimmer: ^3.0.0 + sizer: ^3.1.3 + lottie: ^3.3.1 + smooth_corner: ^1.1.1 + flutter_staggered_animations: ^1.1.1 + fl_chart: 1.0.0 + flutter_rating_bar: ^4.0.1 + + # === Localization === + easy_localization: ^3.0.8 + intl: ^0.20.2 + + # === Storage === + shared_preferences: ^2.5.3 + path_provider: ^2.0.8 + + # === Device === + permission_handler: ^12.0.1 + local_auth: ^2.3.0 + device_info_plus: ^11.5.0 + image_picker: ^1.2.0 + url_launcher: ^6.3.2 + share_plus: ^11.1.0 + + # === Notifications === + flutter_local_notifications: ^19.4.1 + timezone: ^0.10.0 + fluttertoast: ^8.2.12 + + # === Logging === + logger: ^2.6.1 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: + uses-material-design: true + assets: + - assets/ + - assets/json/ + - assets/fonts/ + - assets/langs/ + - assets/images/ + - assets/images/svg/ + - assets/images/png/ + - assets/animations/ + - assets/animations/lottie/ + + fonts: + - family: Poppins + fonts: + - asset: assets/fonts/poppins/Poppins-SemiBold.ttf + weight: 600 + - asset: assets/fonts/poppins/Poppins-Medium.ttf + weight: 500 + - asset: assets/fonts/poppins/Poppins-Regular.ttf + weight: 400 + - asset: assets/fonts/poppins/Poppins-Light.ttf + weight: 300 +``` + +> **Note:** If the user provides a SECONDARY_FONT, add its font family block here too. + +--- + +## 4. FILE: `analysis_options.yaml` + +```yaml +include: package:flutter_lints/flutter.yaml + +linter: + rules: + # prefer_single_quotes: true +``` + +--- + +## 5. FOLDER STRUCTURE — Create ALL These Directories + +```bash +mkdir -p lib/core/api +mkdir -p lib/core/common_models +mkdir -p lib/core/exceptions +mkdir -p lib/core/utils +mkdir -p lib/services/analytics +mkdir -p lib/features +mkdir -p lib/presentation/home/widgets +mkdir -p lib/presentation/authentication +mkdir -p lib/presentation/onboarding +mkdir -p lib/routes +mkdir -p lib/theme +mkdir -p lib/extensions +mkdir -p lib/widgets/buttons +mkdir -p lib/widgets/loader +mkdir -p lib/widgets/bottomsheet +mkdir -p lib/widgets/bottom_navigation +mkdir -p lib/widgets/shimmer +mkdir -p lib/widgets/routes +mkdir -p lib/generated +mkdir -p assets/fonts/poppins +mkdir -p assets/images/svg +mkdir -p assets/images/png +mkdir -p assets/animations/lottie +mkdir -p assets/json +mkdir -p assets/langs +mkdir -p assets/sounds +``` + +--- + +## 6. CORE LAYER — Generate Each File Exactly + +### 6.1 FILE: `lib/core/enums.dart` + +```dart +enum ViewStateEnum { hide, idle, busy, error, busyLocal, errorLocal } + +enum AppEnvironmentTypeEnum { dev, uat, preProd, qa, staging, prod } + +enum GenderTypeEnum { male, female } + +enum ChipTypeEnum { success, error, alert, info, warning, lightBg, primaryRed } + +enum LoginTypeEnum { sms, whatsapp, face, fingerprint } + +enum OTPTypeEnum { sms, whatsapp, faceIDFingerprint } +``` + +### 6.2 FILE: `lib/core/exceptions/api_failure.dart` + +```dart +import 'package:equatable/equatable.dart'; + +abstract class Failure extends Equatable implements Exception { + final String message; + const Failure(this.message); +} + +class ServerFailure extends Failure { + final String url; + const ServerFailure(super.message, {this.url = ""}); + @override + List get props => [message]; +} + +class DataParsingFailure extends Failure { + const DataParsingFailure(super.message); + @override + List get props => [message]; +} + +class ConnectivityFailure extends Failure { + const ConnectivityFailure(super.message); + @override + List get props => [message]; +} + +class UnAuthenticatedUserFailure extends Failure { + final String url; + const UnAuthenticatedUserFailure(super.message, {this.url = ""}); + @override + List get props => [message]; +} + +class AppUpdateFailure extends Failure { + const AppUpdateFailure(super.message); + @override + List get props => [message]; +} + +class StatusCodeFailure extends Failure { + const StatusCodeFailure(super.message); + @override + List get props => [message]; +} + +class UnknownFailure extends Failure { + final String url; + const UnknownFailure(super.message, {this.url = ""}); + @override + List get props => [message]; +} + +class UserIntimationFailure extends Failure { + const UserIntimationFailure(super.message); + @override + List get props => [message]; +} + +class MessageStatusFailure extends Failure { + const MessageStatusFailure(super.message); + @override + List get props => [message]; +} + +class InvalidCredentials extends Failure { + const InvalidCredentials(String? message) : super(message ?? ''); + @override + List get props => [message]; +} + +class LocalStorageFailure extends Failure { + const LocalStorageFailure(super.message); + @override + List get props => [message]; +} +``` + +### 6.3 FILE: `lib/core/common_models/generic_api_model.dart` + +```dart +class GenericApiModel { + final int? messageStatus; + final String? errorMessage; + final int? statusCode; + final T? data; + + GenericApiModel({this.messageStatus, this.errorMessage, this.statusCode, this.data}); + + factory GenericApiModel.fromJson( + Map json, + T Function(Object? json)? fromJsonT, + ) { + return GenericApiModel( + messageStatus: json['messageStatus'] as int?, + errorMessage: json['errorMessage'] as String?, + statusCode: json['statusCode'] as int?, + data: fromJsonT != null ? fromJsonT(json['data']) : json['data'] as T?, + ); + } + + Map toJson(Object Function(T value)? toJsonT) { + return { + 'messageStatus': messageStatus, + 'errorMessage': errorMessage, + 'statusCode': statusCode, + 'data': toJsonT != null && data != null ? toJsonT(data as T) : data, + }; + } +} +``` + +### 6.4 FILE: `lib/core/api_consts.dart` + +```dart +import 'package:{{DART_PACKAGE}}/core/enums.dart'; + +const int VERSION_ID = 1; +const int CHANNEL = 3; +const String IP_ADDRESS = "10.20.10.20"; +const String GENERAL_ID = "Aborkan"; +const int PATIENT_TYPE = 1; +const int PATIENT_TYPE_ID = 1; +const String SETUP_ID = "010266"; +const bool IS_DENTAL_ALLOWED_BACKEND = false; + +class ApiConsts { + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static String baseUrl = '{{BASE_URL}}'; + + static int appVersionID = VERSION_ID; + static int appChannelId = CHANNEL; + static String appIpAddress = IP_ADDRESS; + static String appGeneralId = GENERAL_ID; + static String sessionID = ""; + + static void setBackendURLs() { + switch (appEnvironmentType) { + case AppEnvironmentTypeEnum.prod: + baseUrl = '{{BASE_URL}}'; + break; + case AppEnvironmentTypeEnum.dev: + case AppEnvironmentTypeEnum.uat: + case AppEnvironmentTypeEnum.preProd: + case AppEnvironmentTypeEnum.qa: + case AppEnvironmentTypeEnum.staging: + baseUrl = '{{BASE_URL}}'; + break; + } + } + + // ── Add endpoint paths here as you build features ── + // static const String login = "api/auth/login"; +} +``` + +### 6.5 FILE: `lib/core/cache_consts.dart` + +```dart +class CacheConst { + static const String firstLaunch = "firstLaunch"; + static const String appAuthToken = "app_auth_token"; + static const String loggedInUserObj = "logged_in_user_obj"; + static const String pushToken = "push_token"; + static const String appLanguage = 'language'; + static const String isDarkMode = 'is_dark_mode'; + + // Add more cache keys as features are built +} +``` + +### 6.6 FILE: `lib/core/app_state.dart` + +```dart +import 'dart:io'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:{{DART_PACKAGE}}/services/navigation_service.dart'; + +class AppState { + final NavigationService navigationService; + + AppState({required this.navigationService}); + + double userLat = 0.0; + double userLong = 0.0; + + bool isArabic() { + final ctx = navigationService.navigatorKey.currentContext; + if (ctx == null) return false; + return EasyLocalization.of(ctx)?.locale.languageCode == "ar"; + } + + int getLanguageID() => isArabic() ? 1 : 2; + String? getLanguageCode() { + final ctx = navigationService.navigatorKey.currentContext; + if (ctx == null) return "en"; + return EasyLocalization.of(ctx)?.locale.languageCode; + } + int getDeviceTypeID() => Platform.isIOS ? 1 : 2; + + bool isAuthenticated = false; + String appAuthToken = ""; + String deviceToken = ""; + String voipToken = ""; + String sessionId = ""; + String deviceTypeID = ""; + + Map? authenticatedUser; + + void setAuthenticatedUser(Map? user) { + authenticatedUser = user; + isAuthenticated = user != null; + } + + void resetLocation() { + userLat = 0.0; + userLong = 0.0; + } +} +``` + +### 6.7 FILE: `lib/core/app_assets.dart` + +```dart +class AppAssets { + static const String svgBasePath = 'assets/images/svg'; + static const String pngBasePath = 'assets/images/png'; + static const String lottieBasePath = 'assets/animations/lottie'; + + // Add asset constants here as you add files. Examples: + // static const String logo = '$svgBasePath/logo.svg'; + // static const String placeholder = '$pngBasePath/placeholder.png'; +} +``` + +### 6.8 FILE: `lib/core/app_export.dart` + +```dart +export '../routes/app_routes.dart'; +export 'utils/size_utils.dart'; +``` + +### 6.9 FILE: `lib/core/utils/size_utils.dart` + +```dart +import 'package:flutter/material.dart'; + +const num figmaDesignWidth = {{FIGMA_WIDTH}}; +const num figmaDesignHeight = {{FIGMA_HEIGHT}}; + +class SizeUtils { + static late double width; + static late double height; + static late DeviceType deviceType; + + static void init(BoxConstraints constraints, Orientation orientation) { + width = constraints.maxWidth; + height = constraints.maxHeight; + deviceType = width > 600 ? DeviceType.tablet : DeviceType.phone; + } +} + +enum DeviceType { phone, tablet } + +bool get isTablet => SizeUtils.deviceType == DeviceType.tablet; + +extension ResponsiveExtension on num { + double get _width => SizeUtils.width; + double get _height => SizeUtils.height; + + double get f { + double scale = (_width < _height ? _width : _height) / figmaDesignWidth; + double clamp = isTablet ? 1.4 : 1.2; + if (scale > clamp) scale = clamp; + return this * scale; + } + + double get w => (this * _width) / figmaDesignWidth; + double get h => (this * _height) / figmaDesignHeight; + double get r => (this * _width) / figmaDesignWidth; +} +``` + +### 6.10 FILE: `lib/core/utils/loading_utils.dart` + +```dart +import 'package:flutter/material.dart'; +import 'package:{{DART_PACKAGE}}/core/dependencies.dart'; +import 'package:{{DART_PACKAGE}}/services/navigation_service.dart'; + +class LoadingUtils { + static bool isLoading = false; + static OverlayEntry? _overlayEntry; + + static void showFullScreenLoader() { + final context = getIt.get().navigatorKey.currentContext; + if (context == null || isLoading) return; + isLoading = true; + _overlayEntry = OverlayEntry( + builder: (_) => Container( + color: Colors.black38, + child: const Center(child: CircularProgressIndicator()), + ), + ); + Overlay.of(context).insert(_overlayEntry!); + } + + static void hideFullScreenLoader() { + _overlayEntry?.remove(); + _overlayEntry = null; + isLoading = false; + } +} +``` + +### 6.11 FILE: `lib/core/api/api_client.dart` + +```dart +import 'dart:convert'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart' as http; +import 'package:{{DART_PACKAGE}}/core/api_consts.dart'; +import 'package:{{DART_PACKAGE}}/core/app_state.dart'; +import 'package:{{DART_PACKAGE}}/core/exceptions/api_failure.dart'; + +abstract class ApiClient { + Future post( + String endPoint, { + required Map body, + required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess, + required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, + bool isExternal = false, + Map? apiHeaders, + }); + + Future get( + String endPoint, { + required Function(dynamic response, int statusCode) onSuccess, + required Function(String error, int statusCode) onFailure, + Map? queryParams, + Map? apiHeaders, + bool isExternal = false, + }); +} + +class ApiClientImp implements ApiClient { + final AppState _appState; + + ApiClientImp({required AppState appState}) : _appState = appState; + + Map _defaultHeaders() => {'Content-Type': 'application/json', 'Accept': 'application/json'}; + + void _injectCommonFields(Map body) { + body['VersionID'] = ApiConsts.appVersionID.toString(); + body['Channel'] = ApiConsts.appChannelId.toString(); + body['IPAdress'] = ApiConsts.appIpAddress; + body['generalid'] = ApiConsts.appGeneralId; + body['LanguageID'] = _appState.getLanguageID().toString(); + body['Latitude'] = _appState.userLat.toString(); + body['Longitude'] = _appState.userLong.toString(); + body['DeviceTypeID'] = _appState.deviceTypeID; + if (_appState.appAuthToken.isNotEmpty) { + body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; + } + } + + @override + Future post( + String endPoint, { + required Map body, + required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess, + required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, + bool isExternal = false, + Map? apiHeaders, + }) async { + final url = isExternal ? endPoint : ApiConsts.baseUrl + endPoint; + final headers = apiHeaders ?? _defaultHeaders(); + if (!isExternal) _injectCommonFields(body); + + try { + final response = await http.post(Uri.parse(url), headers: headers, body: jsonEncode(body)); + final statusCode = response.statusCode; + + if (statusCode == 401) { + onFailure('Unauthenticated', statusCode, failureType: UnAuthenticatedUserFailure('Session expired', url: url)); + return; + } + if (statusCode == 426) { + onFailure('App update required', statusCode, failureType: const AppUpdateFailure('Please update the app')); + return; + } + + if (statusCode >= 200 && statusCode < 300) { + final decoded = jsonDecode(response.body); + final int? messageStatus = decoded['MessageStatus']; + final String? errorMessage = decoded['ErrorEndUserMessage'] ?? decoded['ErrorMessage']; + + if (messageStatus == 1 || messageStatus == null) { + onSuccess(decoded, statusCode, messageStatus: messageStatus, errorMessage: errorMessage); + } else { + onFailure(errorMessage ?? 'Request failed', statusCode, + messageStatus: messageStatus, + failureType: MessageStatusFailure(errorMessage ?? 'Request failed with status $messageStatus')); + } + } else { + onFailure('Server error: $statusCode', statusCode, failureType: ServerFailure('Server error: $statusCode', url: url)); + } + } catch (e) { + if (kDebugMode) print('API POST error ($url): $e'); + onFailure(e.toString(), 0, failureType: UnknownFailure(e.toString(), url: url)); + } + } + + @override + Future get( + String endPoint, { + required Function(dynamic response, int statusCode) onSuccess, + required Function(String error, int statusCode) onFailure, + Map? queryParams, + Map? apiHeaders, + bool isExternal = false, + }) async { + final baseUrl = isExternal ? endPoint : ApiConsts.baseUrl + endPoint; + final uri = Uri.parse(baseUrl).replace(queryParameters: queryParams?.map((k, v) => MapEntry(k, v.toString()))); + final headers = apiHeaders ?? _defaultHeaders(); + + try { + final response = await http.get(uri, headers: headers); + if (response.statusCode >= 200 && response.statusCode < 300) { + onSuccess(jsonDecode(response.body), response.statusCode); + } else { + onFailure('GET failed: ${response.statusCode}', response.statusCode); + } + } catch (e) { + onFailure(e.toString(), 0); + } + } +} +``` + +--- + +## 7. SERVICES LAYER — Generate Each File Exactly + +### 7.1 FILE: `lib/services/logger_service.dart` + +```dart +import 'package:logger/logger.dart'; + +abstract class LoggerService { + void logError(String message); + void logInfo(String message); +} + +class LoggerServiceImp implements LoggerService { + final Logger logger; + LoggerServiceImp({required this.logger}); + + @override + void logError(String message) => logger.e(message); + @override + void logInfo(String message) => logger.d(message); +} +``` + +### 7.2 FILE: `lib/services/navigation_service.dart` + +```dart +import 'package:flutter/material.dart'; + +class NavigationService { + final GlobalKey navigatorKey = GlobalKey(); + + BuildContext? get context => navigatorKey.currentContext; + + Future push(Route route) => navigatorKey.currentState!.push(route); + + Future pushAndRemoveUntil(Route route, RoutePredicate predicate) => + navigatorKey.currentState!.pushAndRemoveUntil(route, predicate); + + void pop([T? result]) => navigatorKey.currentState!.pop(result); + + void popUntilNamed(String routeName) => + navigatorKey.currentState?.popUntil(ModalRoute.withName(routeName)); + + void replaceAllRoutesAndNavigateTo(String routeName) { + navigatorKey.currentState?.pushNamedAndRemoveUntil(routeName, (route) => false); + } + + void pushAndReplace(String routeName) => + navigatorKey.currentState?.pushReplacementNamed(routeName); + + void pushNamed(String routeName, {Object? arguments}) => + navigatorKey.currentState?.pushNamed(routeName, arguments: arguments); + + Future pushPage({required Widget page, bool fullscreenDialog = false}) => + navigatorKey.currentState!.push( + MaterialPageRoute(builder: (_) => page, fullscreenDialog: fullscreenDialog), + ); +} +``` + +### 7.3 FILE: `lib/services/cache_service.dart` + +```dart +import 'dart:convert'; +import 'package:{{DART_PACKAGE}}/services/logger_service.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +abstract class CacheService { + Future saveString({required String key, required String value}); + Future saveInt({required String key, required int value}); + Future saveBool({required String key, required bool value}); + String? getString({required String key}); + int? getInt({required String key}); + bool? getBool({required String key}); + Future getObject({required String key}); + Future saveObject({required String key, required dynamic value}); + Future remove({required String key}); + Future clear(); +} + +class CacheServiceImp implements CacheService { + final SharedPreferences sharedPreferences; + final LoggerService loggerService; + + CacheServiceImp({required this.sharedPreferences, required this.loggerService}); + + @override + Future saveString({required String key, required String value}) async => await sharedPreferences.setString(key, value); + @override + Future saveInt({required String key, required int value}) async => await sharedPreferences.setInt(key, value); + @override + Future saveBool({required String key, required bool value}) async => await sharedPreferences.setBool(key, value); + @override + String? getString({required String key}) => sharedPreferences.getString(key); + @override + int? getInt({required String key}) => sharedPreferences.getInt(key); + @override + bool? getBool({required String key}) => sharedPreferences.getBool(key); + + @override + Future getObject({required String key}) async { + try { + await sharedPreferences.reload(); + final s = sharedPreferences.getString(key); + return s == null ? null : json.decode(s); + } catch (e) { + loggerService.logError(e.toString()); + return null; + } + } + + @override + Future saveObject({required String key, required dynamic value}) async => + await sharedPreferences.setString(key, json.encode(value)); + @override + Future remove({required String key}) async => await sharedPreferences.remove(key); + @override + Future clear() async => await sharedPreferences.clear(); +} +``` + +### 7.4 FILE: `lib/services/dialog_service.dart` + +```dart +import 'package:flutter/material.dart'; +import 'package:{{DART_PACKAGE}}/services/navigation_service.dart'; +import 'package:{{DART_PACKAGE}}/theme/colors.dart'; + +abstract class DialogService { + Future showErrorBottomSheet({String title, required String message, Function()? onOkPressed}); + Future showConfirmBottomSheet({required String message, required Function() onOkPressed, Function()? onCancelPressed}); +} + +class DialogServiceImp implements DialogService { + final NavigationService navigationService; + bool _isSheetShowing = false; + + DialogServiceImp({required this.navigationService}); + + @override + Future showErrorBottomSheet({String title = "", required String message, Function()? onOkPressed}) async { + if (_isSheetShowing) return; + final context = navigationService.navigatorKey.currentContext; + if (context == null) return; + _isSheetShowing = true; + await showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), + builder: (_) => Padding( + padding: const EdgeInsets.all(24), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + if (title.isNotEmpty) ...[Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), const SizedBox(height: 12)], + Text(message, textAlign: TextAlign.center), + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ElevatedButton.styleFrom(backgroundColor: AppColors.primaryRedColor), + onPressed: () { Navigator.pop(context); onOkPressed?.call(); }, + child: const Text('OK', style: TextStyle(color: Colors.white)), + ), + ), + ]), + ), + ); + _isSheetShowing = false; + } + + @override + Future showConfirmBottomSheet({required String message, required Function() onOkPressed, Function()? onCancelPressed}) async { + final context = navigationService.navigatorKey.currentContext; + if (context == null) return; + await showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))), + builder: (_) => Padding( + padding: const EdgeInsets.all(24), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + Text(message, textAlign: TextAlign.center), + const SizedBox(height: 24), + Row(children: [ + Expanded(child: OutlinedButton(onPressed: () { Navigator.pop(context); onCancelPressed?.call(); }, child: const Text('Cancel'))), + const SizedBox(width: 12), + Expanded(child: ElevatedButton( + style: ElevatedButton.styleFrom(backgroundColor: AppColors.primaryRedColor), + onPressed: () { Navigator.pop(context); onOkPressed(); }, + child: const Text('OK', style: TextStyle(color: Colors.white)), + )), + ]), + ]), + ), + ); + } +} +``` + +### 7.5 FILE: `lib/services/error_handler_service.dart` + +```dart +import 'package:{{DART_PACKAGE}}/core/exceptions/api_failure.dart'; +import 'package:{{DART_PACKAGE}}/core/utils/loading_utils.dart'; +import 'package:{{DART_PACKAGE}}/services/dialog_service.dart'; +import 'package:{{DART_PACKAGE}}/services/logger_service.dart'; +import 'package:{{DART_PACKAGE}}/services/navigation_service.dart'; +import 'package:{{DART_PACKAGE}}/routes/app_routes.dart'; + +abstract class ErrorHandlerService { + Future handleError({required Failure failure, Function()? onOkPressed, Function(Failure)? onUnHandledFailure, Function(Failure)? onMessageStatusFailure}); +} + +class ErrorHandlerServiceImp implements ErrorHandlerService { + final DialogService dialogService; + final LoggerService loggerService; + final NavigationService navigationService; + + ErrorHandlerServiceImp({required this.dialogService, required this.loggerService, required this.navigationService}); + + @override + Future handleError({required Failure failure, Function()? onOkPressed, Function(Failure)? onUnHandledFailure, Function(Failure)? onMessageStatusFailure}) async { + if (LoadingUtils.isLoading) LoadingUtils.hideFullScreenLoader(); + + if (failure is ServerFailure) { + loggerService.logError("Server: ${failure.message}"); + await _show(failure); + } else if (failure is ConnectivityFailure) { + loggerService.logError("Connectivity: ${failure.message}"); + await _show(failure, title: "No Internet"); + } else if (failure is UnAuthenticatedUserFailure) { + loggerService.logError("Unauth: ${failure.message}"); + await _show(failure, title: "Session Expired", onOk: () => navigationService.replaceAllRoutesAndNavigateTo(AppRoutes.landingScreen)); + } else if (failure is AppUpdateFailure) { + await _show(failure, title: "Update Required"); + } else if (failure is DataParsingFailure) { + loggerService.logError("Parse: ${failure.message}"); + await _show(failure, title: "Data Error"); + } else if (failure is UserIntimationFailure) { + onUnHandledFailure != null ? onUnHandledFailure(failure) : await _show(failure, onOk: onOkPressed); + } else if (failure is MessageStatusFailure) { + onMessageStatusFailure != null ? onMessageStatusFailure(failure) : await _show(failure, onOk: onOkPressed); + } else { + loggerService.logError("Unhandled: $failure"); + await _show(failure, onOk: onOkPressed); + } + } + + Future _show(Failure f, {String? title, Function()? onOk}) async => + await dialogService.showErrorBottomSheet(title: title ?? "", message: f.message, onOkPressed: onOk); +} +``` + +### 7.6 FILE: `lib/services/firebase_service.dart` + +```dart +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:{{DART_PACKAGE}}/core/app_state.dart'; +import 'package:{{DART_PACKAGE}}/services/logger_service.dart'; + +abstract class FirebaseService { + Future getDeviceToken(); +} + +class FirebaseServiceImpl implements FirebaseService { + final FirebaseMessaging firebaseMessaging; + final LoggerService loggerService; + final AppState appState; + + FirebaseServiceImpl({required this.firebaseMessaging, required this.loggerService, required this.appState}); + + @override + Future getDeviceToken() async { + try { + final token = await firebaseMessaging.getToken(); + appState.deviceToken = token ?? ""; + return appState.deviceToken; + } catch (e) { + loggerService.logError(e.toString()); + return ""; + } + } +} +``` + +### 7.7 FILE: `lib/services/notification_service.dart` + +```dart +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:{{DART_PACKAGE}}/services/logger_service.dart'; +import 'package:timezone/data/latest_all.dart' as tz; + +abstract class NotificationService { + Future initialize({Function(String payload)? onNotificationClick}); + Future showNotification({required String title, required String body, String? payload}); + Future cancelAllNotifications(); +} + +class NotificationServiceImp implements NotificationService { + final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; + final LoggerService loggerService; + + NotificationServiceImp({required this.flutterLocalNotificationsPlugin, required this.loggerService}); + + @override + Future initialize({Function(String payload)? onNotificationClick}) async { + tz.initializeTimeZones(); + const android = AndroidInitializationSettings('app_icon'); + const ios = DarwinInitializationSettings(requestAlertPermission: true, requestBadgePermission: true, requestSoundPermission: true); + await flutterLocalNotificationsPlugin.initialize( + const InitializationSettings(android: android, iOS: ios), + onDidReceiveNotificationResponse: (r) => onNotificationClick?.call(r.payload ?? ''), + ); + } + + @override + Future showNotification({required String title, required String body, String? payload}) async { + const details = NotificationDetails( + android: AndroidNotificationDetails('general', 'General', importance: Importance.high, priority: Priority.high), + iOS: DarwinNotificationDetails(), + ); + await flutterLocalNotificationsPlugin.show(DateTime.now().millisecondsSinceEpoch ~/ 1000, title, body, details, payload: payload); + } + + @override + Future cancelAllNotifications() async => await flutterLocalNotificationsPlugin.cancelAll(); +} +``` + +### 7.8 FILE: `lib/services/analytics/analytics_service.dart` + +```dart +import 'package:firebase_analytics/firebase_analytics.dart'; + +class GAnalytics { + final _analytics = FirebaseAnalytics.instance; + + Future logEvent(String name, {Map? parameters}) async { + try { + await _analytics.logEvent(name: name.trim().toLowerCase(), parameters: parameters?.map((k, v) => MapEntry(k, v as Object))); + } catch (_) {} + } + + Future setUser({String? userId, String? language}) async { + if (userId != null) _analytics.setUserProperty(name: 'userid', value: userId); + if (language != null) _analytics.setUserProperty(name: 'user_language', value: language); + } +} +``` + +--- + +## 8. THEME LAYER + +### 8.1 FILE: `lib/theme/colors.dart` + +```dart +import 'package:flutter/material.dart'; + +class AppColors { + static bool isDarkMode = false; + static const transparent = Colors.transparent; + + static Color get scaffoldBgColor => isDarkMode ? dark.scaffoldBgColor : const Color(0xFFF8F8F8); + static Color get whiteColor => isDarkMode ? dark.whiteColor : const Color(0xFFFFFFFF); + static Color get primaryRedColor => isDarkMode ? dark.primaryRedColor : const Color({{PRIMARY_COLOR}}); + static Color get secondaryLightRedColor => isDarkMode ? dark.secondaryLightRedColor : const Color(0xFFFEE9EA); + static const Color successColor = Color(0xFF18C273); + static Color get textColor => isDarkMode ? dark.textColor : const Color(0xFF2E3039); + static Color get iconColor => isDarkMode ? dark.iconColor : const Color(0xFF2E3039); + static Color get greyColor => isDarkMode ? dark.greyColor : const Color(0xFFEFEFF0); + static Color get textColorLight => isDarkMode ? dark.textColorLight : const Color(0xFF5E5E5E); + static Color get dividerColor => isDarkMode ? dark.dividerColor : const Color(0x40D2D2D2); + static Color get borderGrayColor => isDarkMode ? dark.borderGrayColor : const Color(0x332E3039); + static Color get greyTextColor => isDarkMode ? dark.greyTextColor : const Color(0xFF8F9AA3); + static Color get inputLabelTextColor => isDarkMode ? dark.inputLabelTextColor : const Color(0xFF898A8D); + static Color get errorColor => isDarkMode ? dark.errorColor : const Color(0xFFED1C2B); + static Color get warningColor => isDarkMode ? dark.warningColor : const Color(0xFFFFCC00); + static Color get infoColor => isDarkMode ? dark.infoColor : const Color(0xFF0B85F7); + static Color get shimmerBaseColor => isDarkMode ? dark.shimmerBaseColor : const Color(0xFFE0E0E0); + static Color get shimmerHighlightColor => isDarkMode ? dark.shimmerHighlightColor : const Color(0xFFF5F5F5); + + static Color get blackColor => textColor; + static Color get bgScaffoldColor => scaffoldBgColor; + + static const AppColorsDark dark = AppColorsDark(); +} + +class AppColorsDark { + const AppColorsDark(); + Color get scaffoldBgColor => const Color(0xFF191919); + Color get whiteColor => const Color(0xFF1E1E1E); + Color get primaryRedColor => const Color(0xFFDE5C5D); + Color get secondaryLightRedColor => const Color(0xFF3A1015); + Color get textColor => const Color(0xFFFFFFFF); + Color get iconColor => const Color(0xFFFFFFFF); + Color get greyColor => const Color(0xFF2C2C2E); + Color get textColorLight => const Color(0xFFB0B0B0); + Color get dividerColor => const Color(0x33FFFFFF); + Color get borderGrayColor => const Color(0x55ECECEC); + Color get greyTextColor => const Color(0xFF8F9AA3); + Color get inputLabelTextColor => const Color(0xFF9E9E9E); + Color get errorColor => const Color(0xFFD63D48); + Color get warningColor => const Color(0xFFFFCC00); + Color get infoColor => const Color(0xFF4DA6FF); + Color get shimmerBaseColor => const Color(0xFF2C2C2E); + Color get shimmerHighlightColor => const Color(0xFF3A3A3C); +} +``` + +### 8.2 FILE: `lib/theme/app_theme.dart` + +```dart +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:{{DART_PACKAGE}}/theme/colors.dart'; + +class AppTheme { + static ThemeData getTheme(bool isArabic) => ThemeData( + fontFamily: isArabic ? '{{SECONDARY_FONT}}' : '{{PRIMARY_FONT}}', + primarySwatch: Colors.red, + brightness: Brightness.light, + visualDensity: VisualDensity.adaptivePlatformDensity, + pageTransitionsTheme: const PageTransitionsTheme(builders: { + TargetPlatform.android: ZoomPageTransitionsBuilder(), + TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), + }), + canvasColor: Colors.white, + splashColor: Colors.transparent, + appBarTheme: const AppBarTheme(elevation: 0, systemOverlayStyle: SystemUiOverlayStyle.light, surfaceTintColor: Colors.transparent), + ); + + static ThemeData getDarkTheme(bool isArabic) => ThemeData( + fontFamily: isArabic ? '{{SECONDARY_FONT}}' : '{{PRIMARY_FONT}}', + primarySwatch: Colors.red, + brightness: Brightness.dark, + visualDensity: VisualDensity.adaptivePlatformDensity, + pageTransitionsTheme: const PageTransitionsTheme(builders: { + TargetPlatform.android: ZoomPageTransitionsBuilder(), + TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), + }), + canvasColor: AppColors.dark.scaffoldBgColor, + scaffoldBackgroundColor: AppColors.dark.scaffoldBgColor, + splashColor: Colors.transparent, + appBarTheme: AppBarTheme(color: AppColors.dark.scaffoldBgColor, elevation: 0, systemOverlayStyle: SystemUiOverlayStyle.light, surfaceTintColor: Colors.transparent), + ); +} +``` + +--- + +## 9. EXTENSIONS + +### 9.1 FILE: `lib/extensions/int_extensions.dart` + +```dart +import 'package:flutter/material.dart'; + +extension IntExtensions on int { + Widget get height => SizedBox(height: toDouble()); + Widget get width => SizedBox(width: toDouble()); + Widget get divider => Divider(height: toDouble(), thickness: toDouble(), color: const Color(0x30D2D2D2)); +} +``` + +### 9.2 FILE: `lib/extensions/string_extensions.dart` + +```dart +import 'package:flutter/material.dart'; +import 'package:{{DART_PACKAGE}}/core/utils/size_utils.dart'; +import 'package:{{DART_PACKAGE}}/theme/colors.dart'; + +extension CapExtension on String { + String get inCaps => isEmpty ? '' : '${this[0].toUpperCase()}${substring(1)}'; + String get capitalizeFirstOfEach => trim().isEmpty ? '' : trim().toLowerCase().split(' ').map((s) => s.inCaps).join(' '); +} + +extension StringToWidget on String { + Widget _text(double size, {Color? color, FontWeight? fontWeight, bool isBold = false, bool isCenter = false, bool isUnderLine = false, int? maxLines, TextOverflow? overflow, double letterSpacing = 0}) { + return Text(this, + textAlign: isCenter ? TextAlign.center : null, + maxLines: maxLines, + overflow: overflow, + style: TextStyle(fontSize: size.f, fontWeight: fontWeight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: letterSpacing, decoration: isUnderLine ? TextDecoration.underline : null, decorationColor: color ?? AppColors.blackColor), + ); + } + + Widget toText8({Color? color, FontWeight? fontWeight, bool isBold = false, int? maxLines, TextOverflow? overflow}) => _text(8, color: color, fontWeight: fontWeight, isBold: isBold, maxLines: maxLines, overflow: overflow); + Widget toText10({Color? color, FontWeight? fontWeight, bool isBold = false, bool isCenter = false, int? maxLines, TextOverflow? overflow}) => _text(10, color: color, fontWeight: fontWeight, isBold: isBold, isCenter: isCenter, maxLines: maxLines, overflow: overflow); + Widget toText11({Color? color, FontWeight? fontWeight, bool isBold = false, bool isCenter = false, int? maxLines}) => _text(11, color: color, fontWeight: fontWeight, isBold: isBold, isCenter: isCenter, maxLines: maxLines); + Widget toText12({Color? color, FontWeight? fontWeight, bool isBold = false, bool isCenter = false, int? maxLines, TextOverflow? overflow}) => _text(12, color: color, fontWeight: fontWeight, isBold: isBold, isCenter: isCenter, maxLines: maxLines, overflow: overflow); + Widget toText13({Color? color, FontWeight? fontWeight, bool isBold = false, bool isCenter = false, int? maxLines, TextOverflow? overflow}) => _text(13, color: color, fontWeight: fontWeight, isBold: isBold, isCenter: isCenter, maxLines: maxLines, overflow: overflow); + Widget toText14({Color? color, FontWeight? fontWeight, bool isBold = false, bool isCenter = false, int? maxLines, TextOverflow? overflow}) => _text(14, color: color, fontWeight: fontWeight, isBold: isBold, isCenter: isCenter, maxLines: maxLines, overflow: overflow); + Widget toText16({Color? color, FontWeight? fontWeight, bool isBold = false, bool isCenter = false, int? maxLines, TextOverflow? overflow}) => _text(16, color: color, fontWeight: fontWeight, isBold: isBold, isCenter: isCenter, maxLines: maxLines, overflow: overflow); + Widget toText18({Color? color, FontWeight? fontWeight, bool isBold = false, bool isCenter = false, int? maxLines}) => _text(18, color: color, fontWeight: fontWeight, isBold: isBold, isCenter: isCenter, maxLines: maxLines); + Widget toText20({Color? color, FontWeight? fontWeight, bool isBold = false, bool isCenter = false}) => _text(20, color: color, fontWeight: fontWeight, isBold: isBold, isCenter: isCenter); + Widget toText24({Color? color, FontWeight? fontWeight, bool isBold = false, bool isCenter = false}) => _text(24, color: color, fontWeight: fontWeight, isBold: isBold, isCenter: isCenter); + Widget toText30({Color? color, FontWeight? fontWeight, bool isBold = false, bool isCenter = false}) => _text(30, color: color, fontWeight: fontWeight, isBold: isBold, isCenter: isCenter); +} +``` + +### 9.3 FILE: `lib/extensions/widget_extensions.dart` + +```dart +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:{{DART_PACKAGE}}/theme/colors.dart'; + +extension WidgetExtensions on Widget { + Widget onPress(VoidCallback onTap) => InkWell(onTap: onTap, child: this); + Widget get expanded => Expanded(child: this); + Widget get center => Center(child: this); + Widget circle(double v) => ClipRRect(borderRadius: BorderRadius.circular(v), child: this); + Widget paddingAll(double v) => Padding(padding: EdgeInsets.all(v), child: this); + Widget paddingSymmetrical(double h, double v) => Padding(padding: EdgeInsets.symmetric(horizontal: h, vertical: v), child: this); + Widget paddingOnly({double left = 0, double right = 0, double top = 0, double bottom = 0}) => Padding(padding: EdgeInsetsDirectional.only(start: left, end: right, top: top, bottom: bottom), child: this); + Widget toShimmer({bool isShow = true}) => isShow ? Shimmer.fromColors(baseColor: AppColors.shimmerBaseColor, highlightColor: AppColors.shimmerHighlightColor, child: this) : this; + Widget objectContainerView({double radius = 15}) => Container( + padding: const EdgeInsets.all(15), + decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(radius), boxShadow: [BoxShadow(color: const Color(0xff000000).withOpacity(.15), blurRadius: 26, offset: const Offset(0, -3))]), + child: this, + ); +} +``` + +### 9.4 FILE: `lib/extensions/context_extensions.dart` + +```dart +import 'package:flutter/material.dart'; + +extension ContextUtils on BuildContext { + double get screenHeight => MediaQuery.of(this).size.height; + double get screenWidth => MediaQuery.of(this).size.width; + ThemeData get theme => Theme.of(this); + TextTheme get textTheme => theme.textTheme; +} + +extension ShowBottomSheetExt on BuildContext { + Future showSheet({required Widget child, bool isScrollControlled = true, bool isDismissible = true}) { + return showModalBottomSheet(context: this, isScrollControlled: isScrollControlled, isDismissible: isDismissible, backgroundColor: Colors.transparent, builder: (_) => child); + } +} +``` + +### 9.5 FILE: `lib/extensions/route_extensions.dart` + +```dart +import 'package:flutter/material.dart'; + +extension NavigationExtensions on BuildContext { + void navigateWithName(String routeName, {Object? arguments}) => Navigator.pushNamed(this, routeName, arguments: arguments); + Future navigateReplaceWithName(String routeName, {Object? arguments}) async => await Navigator.pushReplacementNamed(this, routeName, arguments: arguments); + void navigateAndRemoveAll(String routeName) => Navigator.pushNamedAndRemoveUntil(this, routeName, (route) => false); + void pop() => Navigator.of(this).pop(); + void pushTo(Widget page) => Navigator.push(this, MaterialPageRoute(builder: (_) => page)); +} +``` + +--- + +## 10. WIDGETS — Starter Widgets + +### 10.1 FILE: `lib/widgets/buttons/custom_button.dart` + +```dart +import 'package:flutter/material.dart'; +import 'package:{{DART_PACKAGE}}/core/utils/size_utils.dart'; +import 'package:{{DART_PACKAGE}}/theme/colors.dart'; + +class CustomButton extends StatelessWidget { + final String label; + final VoidCallback? onPressed; + final Color? color; + final Color? textColor; + final double? width; + final bool isOutlined; + final bool isLoading; + + const CustomButton({super.key, required this.label, this.onPressed, this.color, this.textColor, this.width, this.isOutlined = false, this.isLoading = false}); + + @override + Widget build(BuildContext context) { + final bg = color ?? AppColors.primaryRedColor; + return SizedBox( + width: width ?? double.infinity, + height: 48.h, + child: isOutlined + ? OutlinedButton(onPressed: isLoading ? null : onPressed, style: OutlinedButton.styleFrom(side: BorderSide(color: bg), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r))), child: _child(bg)) + : ElevatedButton(onPressed: isLoading ? null : onPressed, style: ElevatedButton.styleFrom(backgroundColor: bg, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r))), child: _child(textColor ?? Colors.white)), + ); + } + + Widget _child(Color c) => isLoading ? SizedBox(height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2, color: c)) : Text(label, style: TextStyle(fontSize: 14.f, fontWeight: FontWeight.w600, color: c)); +} +``` + +### 10.2 FILE: `lib/widgets/routes/fade_page.dart` + +```dart +import 'package:flutter/material.dart'; + +class FadePage extends PageRouteBuilder { + final Widget page; + FadePage({required this.page}) : super( + pageBuilder: (_, __, ___) => page, + transitionsBuilder: (_, a, __, child) => FadeTransition(opacity: a, child: child), + transitionDuration: const Duration(milliseconds: 300), + ); +} +``` + +--- + +## 11. DEPENDENCY INJECTION — `lib/core/dependencies.dart` + +```dart +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:get_it/get_it.dart'; +import 'package:logger/logger.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:{{DART_PACKAGE}}/core/api/api_client.dart'; +import 'package:{{DART_PACKAGE}}/core/app_state.dart'; +import 'package:{{DART_PACKAGE}}/services/analytics/analytics_service.dart'; +import 'package:{{DART_PACKAGE}}/services/cache_service.dart'; +import 'package:{{DART_PACKAGE}}/services/dialog_service.dart'; +import 'package:{{DART_PACKAGE}}/services/error_handler_service.dart'; +import 'package:{{DART_PACKAGE}}/services/firebase_service.dart'; +import 'package:{{DART_PACKAGE}}/services/logger_service.dart'; +import 'package:{{DART_PACKAGE}}/services/navigation_service.dart'; +import 'package:{{DART_PACKAGE}}/services/notification_service.dart'; + +final GetIt getIt = GetIt.instance; + +class AppDependencies { + static Future addDependencies() async { + final logger = Logger(printer: PrettyPrinter(methodCount: 2, errorMethodCount: 5, lineLength: 1000, colors: true, printEmojis: true)); + + getIt.registerLazySingleton(() => LoggerServiceImp(logger: logger)); + getIt.registerLazySingleton(() => NavigationService()); + getIt.registerLazySingleton(() => AppState(navigationService: getIt())); + getIt.registerLazySingleton(() => GAnalytics()); + getIt.registerLazySingleton(() => DialogServiceImp(navigationService: getIt())); + getIt.registerLazySingleton(() => ErrorHandlerServiceImp(dialogService: getIt(), loggerService: getIt(), navigationService: getIt())); + + final sp = await SharedPreferences.getInstance(); + getIt.registerLazySingleton(() => CacheServiceImp(sharedPreferences: sp, loggerService: getIt())); + getIt.registerLazySingleton(() => ApiClientImp(appState: getIt())); + getIt.registerLazySingleton(() => FirebaseServiceImpl(firebaseMessaging: FirebaseMessaging.instance, loggerService: getIt(), appState: getIt())); + + final flnp = FlutterLocalNotificationsPlugin(); + getIt.registerLazySingleton(() => NotificationServiceImp(flutterLocalNotificationsPlugin: flnp, loggerService: getIt())); + + // ═══ Register feature repos & ViewModels below as you add them ═══ + } +} +``` + +--- + +## 12. ROUTING — `lib/routes/app_routes.dart` + +```dart +import 'package:flutter/material.dart'; +import 'package:{{DART_PACKAGE}}/presentation/home/navigation_screen.dart'; +import 'package:{{DART_PACKAGE}}/splash_page.dart'; + +class AppRoutes { + static const String initialRoute = '/'; + static const String landingScreen = '/landingScreen'; + + // Add route names as you build features: + // static const String loginScreen = '/loginScreen'; + + static Map get routes => { + initialRoute: (_) => const SplashPage(), + landingScreen: (_) => const LandingNavigation(), + }; +} +``` + +--- + +## 13. LOCALIZATION FILES + +### FILE: `assets/langs/en-US.json` + +```json +{ + "appName": "{{APP_NAME}}", + "home": "Home", + "services": "Services", + "medicalFile": "Medical File", + "bookAppointment": "Book Appointment", + "login": "Login", + "register": "Register", + "cancel": "Cancel", + "confirm": "Confirm", + "done": "Done", + "ok": "OK", + "loading": "Loading...", + "noDataAvailable": "No data available", + "noInternetConnection": "No internet connection", + "settings": "Settings", + "language": "Language", + "darkMode": "Dark Mode", + "logout": "Logout", + "search": "Search" +} +``` + +### FILE: `assets/langs/ar-SA.json` + +```json +{ + "appName": "{{APP_NAME}}", + "home": "الرئيسية", + "services": "الخدمات", + "medicalFile": "الملف الطبي", + "bookAppointment": "حجز موعد", + "login": "تسجيل الدخول", + "register": "التسجيل", + "cancel": "إلغاء", + "confirm": "تأكيد", + "done": "تم", + "ok": "حسنا", + "loading": "جاري التحميل...", + "noDataAvailable": "لا توجد بيانات", + "noInternetConnection": "لا يوجد اتصال بالإنترنت", + "settings": "الإعدادات", + "language": "اللغة", + "darkMode": "الوضع الداكن", + "logout": "تسجيل الخروج", + "search": "بحث" +} +``` + +--- + +## 14. PRESENTATION — Starter Screens + +### FILE: `lib/splash_page.dart` + +```dart +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:{{DART_PACKAGE}}/core/api_consts.dart'; +import 'package:{{DART_PACKAGE}}/core/dependencies.dart'; +import 'package:{{DART_PACKAGE}}/presentation/home/navigation_screen.dart'; +import 'package:{{DART_PACKAGE}}/services/notification_service.dart'; +import 'package:{{DART_PACKAGE}}/theme/colors.dart'; +import 'package:{{DART_PACKAGE}}/widgets/routes/fade_page.dart'; + +class SplashPage extends StatefulWidget { + const SplashPage({super.key}); + @override + State createState() => _SplashPageState(); +} + +class _SplashPageState extends State { + @override + void initState() { + super.initState(); + _init(); + } + + Future _init() async { + ApiConsts.setBackendURLs(); + await getIt.get().initialize(); + Timer(const Duration(seconds: 2), () { + Navigator.of(context).pushReplacement(FadePage(page: const LandingNavigation())); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.whiteColor, + body: Center(child: Text('{{APP_NAME}}', style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: AppColors.primaryRedColor))), + ); + } +} +``` + +### FILE: `lib/presentation/home/navigation_screen.dart` + +```dart +import 'package:flutter/material.dart'; +import 'package:{{DART_PACKAGE}}/presentation/home/landing_page.dart'; +import 'package:{{DART_PACKAGE}}/theme/colors.dart'; + +class LandingNavigation extends StatefulWidget { + const LandingNavigation({super.key}); + @override + State createState() => _LandingNavigationState(); +} + +class _LandingNavigationState extends State { + int _idx = 0; + final _pc = PageController(); + + final _pages = const [ + LandingPage(), + Center(child: Text('Medical File')), + Center(child: Text('Book Appointment')), + Center(child: Text('Services')), + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: PageView(controller: _pc, physics: const NeverScrollableScrollPhysics(), children: _pages), + bottomNavigationBar: BottomNavigationBar( + currentIndex: _idx, + type: BottomNavigationBarType.fixed, + selectedItemColor: AppColors.primaryRedColor, + unselectedItemColor: AppColors.greyTextColor, + onTap: (i) { setState(() => _idx = i); _pc.jumpToPage(i); }, + items: const [ + BottomNavigationBarItem(icon: Icon(Icons.home_outlined), activeIcon: Icon(Icons.home), label: 'Home'), + BottomNavigationBarItem(icon: Icon(Icons.folder_outlined), activeIcon: Icon(Icons.folder), label: 'File'), + BottomNavigationBarItem(icon: Icon(Icons.calendar_today_outlined), activeIcon: Icon(Icons.calendar_today), label: 'Book'), + BottomNavigationBarItem(icon: Icon(Icons.grid_view_outlined), activeIcon: Icon(Icons.grid_view), label: 'Services'), + ], + ), + ); + } +} +``` + +### FILE: `lib/presentation/home/landing_page.dart` + +```dart +import 'package:flutter/material.dart'; +import 'package:{{DART_PACKAGE}}/extensions/int_extensions.dart'; +import 'package:{{DART_PACKAGE}}/extensions/string_extensions.dart'; +import 'package:{{DART_PACKAGE}}/theme/colors.dart'; + +class LandingPage extends StatelessWidget { + const LandingPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.scaffoldBgColor, + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + 24.height, + 'Welcome'.toText24(isBold: true), + 8.height, + 'What would you like to do today?'.toText14(color: AppColors.greyTextColor), + 24.height, + Expanded(child: Center(child: 'Home content goes here'.toText16(color: AppColors.textColorLight, isCenter: true))), + ]), + ), + ), + ); + } +} +``` + +--- + +## 15. ENTRY POINT — `lib/main.dart` + +```dart +import 'dart:io'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; +import 'package:sizer/sizer.dart'; +import 'package:{{DART_PACKAGE}}/core/dependencies.dart'; +import 'package:{{DART_PACKAGE}}/routes/app_routes.dart'; +import 'package:{{DART_PACKAGE}}/services/navigation_service.dart'; +import 'package:{{DART_PACKAGE}}/theme/app_theme.dart'; +import 'package:{{DART_PACKAGE}}/theme/colors.dart'; + +class MyHttpOverrides extends HttpOverrides { + @override + HttpClient createHttpClient(SecurityContext? context) => + super.createHttpClient(context)..badCertificateCallback = (cert, host, port) => true; +} + +Future _init() async { + WidgetsFlutterBinding.ensureInitialized(); + await EasyLocalization.ensureInitialized(); + // Uncomment when Firebase is configured: + // await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + await AppDependencies.addDependencies(); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + HttpOverrides.global = MyHttpOverrides(); +} + +void main() async { + await _init(); + runApp( + EasyLocalization( + supportedLocales: const [Locale('en', 'US'), Locale('ar', 'SA')], + path: 'assets/langs', + fallbackLocale: const Locale('en', 'US'), + child: MultiProvider( + providers: [ + // ═══ Add ChangeNotifierProviders as you build features ═══ + ], + child: const MyApp(), + ), + ), + ); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + bottom: !Platform.isIOS, + child: LayoutBuilder(builder: (context, constraints) { + return Sizer(builder: (context, orientation, deviceType) { + final isArabic = EasyLocalization.of(context)?.locale.languageCode == "ar"; + return MaterialApp( + title: '{{APP_NAME}}', + debugShowCheckedModeBanner: false, + localizationsDelegates: context.localizationDelegates, + supportedLocales: context.supportedLocales, + locale: context.locale, + initialRoute: AppRoutes.initialRoute, + routes: AppRoutes.routes, + theme: AppTheme.getTheme(isArabic), + darkTheme: AppTheme.getDarkTheme(isArabic), + themeMode: AppColors.isDarkMode ? ThemeMode.dark : ThemeMode.light, + navigatorKey: getIt.get().navigatorKey, + builder: (context, child) => MediaQuery( + data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling, alwaysUse24HourFormat: true), + child: child!, + ), + ); + }); + }), + ); + } +} +``` + +--- + +## 16. FEATURE MODULE TEMPLATE + +When asked to **add a new feature**, create these four files. Replace `{{feature}}` (snake_case) and `{{Feature}}` (PascalCase). + +### `lib/features/{{feature}}/{{feature}}_repo.dart` + +```dart +import 'package:dartz/dartz.dart'; +import 'package:{{DART_PACKAGE}}/core/api/api_client.dart'; +import 'package:{{DART_PACKAGE}}/core/common_models/generic_api_model.dart'; +import 'package:{{DART_PACKAGE}}/core/exceptions/api_failure.dart'; +import 'package:{{DART_PACKAGE}}/services/logger_service.dart'; + +abstract class {{Feature}}Repo { + // Future>>> getItems(); +} + +class {{Feature}}RepoImp implements {{Feature}}Repo { + final ApiClient apiClient; + final LoggerService loggerService; + {{Feature}}RepoImp({required this.loggerService, required this.apiClient}); + + // @override + // Future>>> getItems() async { + // Map body = {}; + // try { + // GenericApiModel>? result; + // Failure? failure; + // await apiClient.post('endpoint', body: body, + // onFailure: (e, s, {messageStatus, failureType}) => failure = failureType, + // onSuccess: (r, s, {messageStatus, errorMessage}) { + // try { result = GenericApiModel(data: (r['Key'] as List).map((e) => YourModel.fromJson(e)).toList()); } + // catch (e) { failure = DataParsingFailure(e.toString()); } + // }); + // if (failure != null) return Left(failure!); + // return Right(result!); + // } catch (e) { return Left(UnknownFailure(e.toString())); } + // } +} +``` + +### `lib/features/{{feature}}/{{feature}}_view_model.dart` + +```dart +import 'package:flutter/material.dart'; +import 'package:{{DART_PACKAGE}}/features/{{feature}}/{{feature}}_repo.dart'; +import 'package:{{DART_PACKAGE}}/services/error_handler_service.dart'; +import 'package:{{DART_PACKAGE}}/services/navigation_service.dart'; + +class {{Feature}}ViewModel extends ChangeNotifier { + final {{Feature}}Repo _repo; + final ErrorHandlerService _errorHandler; + final NavigationService _nav; + + {{Feature}}ViewModel({required {{Feature}}Repo repo, required ErrorHandlerService errorHandler, required NavigationService nav}) + : _repo = repo, _errorHandler = errorHandler, _nav = nav; + + bool isLoading = false; + + // void init() { isLoading = true; notifyListeners(); _fetch(); } + // void _fetch() async { + // final r = await _repo.getItems(); + // r.fold((f) { isLoading = false; _errorHandler.handleError(failure: f); notifyListeners(); }, + // (d) { /* items = d.data ?? []; */ isLoading = false; notifyListeners(); }); + // } +} +``` + +### `lib/presentation/{{feature}}/{{feature}}_page.dart` + +```dart +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:{{DART_PACKAGE}}/features/{{feature}}/{{feature}}_view_model.dart'; +import 'package:{{DART_PACKAGE}}/theme/colors.dart'; + +class {{Feature}}Page extends StatefulWidget { + const {{Feature}}Page({super.key}); + @override + State<{{Feature}}Page> createState() => _{{Feature}}PageState(); +} + +class _{{Feature}}PageState extends State<{{Feature}}Page> { + @override + void initState() { + super.initState(); + // Provider.of<{{Feature}}ViewModel>(context, listen: false).init(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.scaffoldBgColor, + appBar: AppBar(title: const Text('{{Feature}}')), + body: Consumer<{{Feature}}ViewModel>( + builder: (_, vm, __) { + if (vm.isLoading) return const Center(child: CircularProgressIndicator()); + return const Center(child: Text('Content here')); + }, + ), + ); + } +} +``` + +### After creating, REGISTER: + +**`dependencies.dart`:** +```dart +getIt.registerLazySingleton<{{Feature}}Repo>(() => {{Feature}}RepoImp(loggerService: getIt(), apiClient: getIt())); +getIt.registerLazySingleton<{{Feature}}ViewModel>(() => {{Feature}}ViewModel(repo: getIt(), errorHandler: getIt(), nav: getIt())); +``` + +**`main.dart` providers list:** +```dart +ChangeNotifierProvider<{{Feature}}ViewModel>(create: (_) => getIt.get<{{Feature}}ViewModel>()), +``` + +**`app_routes.dart`:** +```dart +static const String {{feature}}Page = '/{{feature}}'; +// in routes map: {{feature}}Page: (_) => const {{Feature}}Page(), +``` + +--- + +## 17. RULES — ALWAYS FOLLOW + +1. **Every service/repo = abstract class + implementation.** Never use concrete directly. +2. **All DI in `dependencies.dart`.** Only use `getIt.get()` elsewhere. +3. **All repos return `Future>>`.** +4. **All ViewModels extend `ChangeNotifier`.** Call `notifyListeners()` after state changes. +5. **ViewModels NEVER import widgets.** Use `NavigationService` for navigation. +6. **Pages use `Consumer` or `Provider.of` to read ViewModel state.** +7. **Colors from `AppColors`** — never hardcode hex in widgets. +8. **Text via string extensions** — `'Hello'.toText16()` not `Text('Hello')`. +9. **Spacing via int extensions** — `16.height` not `SizedBox(height: 16)`. +10. **Sizes via responsive extensions** — `16.f`, `16.w`, `16.h`, `16.r`. +11. **Assets via `AppAssets`** — never hardcode paths. +12. **Cache keys in `CacheConst`** — never raw strings for SharedPreferences. +13. **Endpoints in `ApiConsts`** — never hardcode URLs. +14. **Errors through `ErrorHandlerService`** — VMs never show dialogs. +15. **User-facing strings use `.tr()`** from easy_localization. + +--- + +## 18. CHECKLIST — Verify Before Delivering + +- [ ] `flutter pub get` succeeds +- [ ] All imports use `package:{{DART_PACKAGE}}/...` +- [ ] Every file in this spec exists +- [ ] `dependencies.dart` registers all existing services/repos/VMs +- [ ] `main.dart` providers list includes all registered VMs +- [ ] `app_routes.dart` includes all existing pages +- [ ] `pubspec.yaml` declares all existing asset directories +- [ ] Both `en-US.json` and `ar-SA.json` exist +- [ ] No circular imports +- [ ] `AppColors` used everywhere (no raw `Color()` in widgets) +- [ ] Extensions used for text, spacing, widget wrapping + +--- + +*End of specification. Generate all files above. When asked to add a feature, use Section 16.* + diff --git a/assets/images/svg/biometric_lock_icon.svg b/assets/images/svg/biometric_lock_icon.svg new file mode 100644 index 00000000..dd123157 --- /dev/null +++ b/assets/images/svg/biometric_lock_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/globe_other.svg b/assets/images/svg/globe_other.svg new file mode 100644 index 00000000..1b9734b3 --- /dev/null +++ b/assets/images/svg/globe_other.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/image_icon.svg b/assets/images/svg/image_icon.svg new file mode 100644 index 00000000..73945847 --- /dev/null +++ b/assets/images/svg/image_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index f929b1ae..b78b8545 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -435,7 +435,6 @@ "serviceInformation": "معلومات الخدمة", "homeHealthCare": "الرعاية الصحية المنزلية", "noAppointmentAvailable": "لا توجد مواعيد متاحة", - "homeHealthCareText": "تقدم هذه الخدمة مجموعة من خدمات الرعاية الصحية المنزلية، والمتابعة المستمرة والشاملة في أماكن إقامتهم لأولئك الذين لا يمكنهم الوصول إلى المرافق الصحية، مثل (تحليلات المختبر - الأشعة - التطعيمات - العلاج الطبيعي)، إلخ.", "loginRegister": "تسجيل الدخول / التسجيل", "orderLog": "سجل الطلب", "infoLab": "تتيح لك هذه الخدمة عرض نتائج جميع الفحوصات المخبرية التي أجريت في مجموعة الحبيب الطبية بالإضافة إلى إرسال التقرير عبر البريد الإلكتروني.", @@ -448,7 +447,7 @@ "lakumPoint": "نقطة", "wishlist": "قائمة الرغبات", "products": "المنتجات", - "reviews": "التقييمات", + "reviews": "التعليقات", "brands": "العلامات التجارية", "productDetails": "تفاصيل المنتج", "medicationRefill": "إعادة تعبئة الدواء", @@ -812,7 +811,7 @@ "awaitingApproval": "انتظر القبول", "news": "أخبار", "ready": "جاهز", - "enterValidNationalId": "الرجاء إدخال رقم الهوية الوطنية أو رقم الملف الصحيح", + "enterValidNationalId": "رقم الهوية أو رقم الملف غير صحيح", "enterValidPhoneNumber": "الرجاء إدخال رقم هاتف صالح", "cannotEnterSaudiOrUAENumber": "لا يمكنك إدخال أرقام هواتف السعودية (00966) أو الإمارات (00971) عند اختيار دولة 'أخرى'", "medicalCentersWithCount": "{count} مراكز طبية", @@ -1275,7 +1274,7 @@ "noVitalSignsRecordedYet": "لا توجد علامات حيوية مسجلة بعد", "appointmentsAndVisits": "المواعيد والزيارات", "labAndRadiology": "المختبر والأشعة", - "activeMedicationsAndPrescriptions": "الأدوية النشطة والوصفات الطبية", + "activeMedicationsAndPrescriptions": "الوصفات الطبية", "allPrescriptions": "جميع الوصفات", "allMedications": "جميع الأدوية", "youDontHaveAnyPrescriptionsYet": "ليس لديك أي وصفات طبية بعد.", @@ -1583,5 +1582,14 @@ "continueCash": "متابعة الدفع نقدًا", "timeFor": "الوقت", "hmgPolicies": "سياسات مجموعة الحبيب الطبية", - "darkMode": "المظهر الداكن" + "darkMode": "المظهر الداكن", + "generateAiAnalysisResult": "قم بإجراء تحليل لهذا المختبر AI", + "ratings": "التقييمات", + "hmgPharmacyText": "صيدلية الحبيب، المتجر الصيدلاني الإلكتروني المتكامل الذي تقدمه لكم مجموعة خدمات الدكتور سليمان الحبيب الطبية.", + "insuranceRequestSubmittedSuccessfully": "تم إرسال طلب تحديث بيانات التأمين بنجاح. سيتم إعلامك بمجرد الانتهاء.", + "updatingEmailAddress": "جارٍ تحديث عنوان البريد الإلكتروني، يرجى الانتظار...", + "verifyInsurance": "التحقق من التأمين", + "tests": "تحليل", + "calendarPermissionAlert": "يرجى منح إذن الوصول إلى التقويم من إعدادات التطبيق لضبط تذكير تناول الدواء.", + "sortByLocation": "الترتيب حسب الموقع" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index b1cc5b08..cbea7b94 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -802,7 +802,7 @@ "notNow": "Not Now", "pendingActivation": "Pending Activation", "awaitingApproval": "Awaiting Approval", - "enterValidNationalId": "Please enter a valid national ID or file number", + "enterValidNationalId": "Invalid Identification or file number", "enterValidPhoneNumber": "Please enter a valid phone number", "cannotEnterSaudiOrUAENumber": "You cannot enter Saudi Arabia (00966) or UAE (00971) phone numbers when 'Others' country is selected", "ready": "Ready", @@ -1266,7 +1266,7 @@ "noVitalSignsRecordedYet": "No vital signs recorded yet", "appointmentsAndVisits": "Appointments & visits", "labAndRadiology": "Lab & Radiology", - "activeMedicationsAndPrescriptions": "Active Medications & Prescriptions", + "activeMedicationsAndPrescriptions": "Recent Prescriptions", "allPrescriptions": "All Prescriptions", "allMedications": "All Medications", "youDontHaveAnyPrescriptionsYet": "You don't have any prescriptions yet.", @@ -1576,5 +1576,14 @@ "continueCash": "Continue as cash", "timeFor": "Time For", "hmgPolicies": "HMG Policies", - "darkMode": "Dark Mode" + "darkMode": "Dark Mode", + "generateAiAnalysisResult": "Generate AI analysis for this result", + "ratings": "Ratings", + "hmgPharmacyText": "Al Habib Pharmacy, the complete online Pharmaceutical store brought to you by Dr. Sulaiman Al Habib Medical Services Group.", + "insuranceRequestSubmittedSuccessfully": "Your insurance update request has been successfully submitted. You will be notified once completed.", + "updatingEmailAddress": "Updating email address, Please wait...", + "verifyInsurance": "Verify Insurance", + "tests": "tests", + "calendarPermissionAlert": "Please grant calendar access permission from app settings to set medication reminder.", + "sortByLocation": "Sort by location" } diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index e031a976..9f1f6de9 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -11,6 +11,12 @@ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 478CFA942E638C8E0064F3D7 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */; }; + 47C1AAC72F425ACF00DA1231 /* Penguin.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47C1AAC12F425AC800DA1231 /* Penguin.xcframework */; }; + 47C1AAC82F425ACF00DA1231 /* Penguin.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 47C1AAC12F425AC800DA1231 /* Penguin.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 47C1AAC92F425AD000DA1231 /* PenguinINRenderer.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47C1AAC22F425AC800DA1231 /* PenguinINRenderer.xcframework */; }; + 47C1AACA2F425AD000DA1231 /* PenguinINRenderer.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 47C1AAC22F425AC800DA1231 /* PenguinINRenderer.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 47C1AACB2F425AD100DA1231 /* PenNavUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 47C1AAC32F425AC800DA1231 /* PenNavUI.xcframework */; }; + 47C1AACC2F425AD100DA1231 /* PenNavUI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 47C1AAC32F425AC800DA1231 /* PenNavUI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 61243B492EC5FA3700D46FA0 /* PenguinModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B422EC5FA3700D46FA0 /* PenguinModel.swift */; }; 61243B4C2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B3D2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift */; }; 61243B502EC5FA3700D46FA0 /* PenguinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B452EC5FA3700D46FA0 /* PenguinView.swift */; }; @@ -18,12 +24,6 @@ 61243B562EC5FA3700D46FA0 /* PenguinNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B432EC5FA3700D46FA0 /* PenguinNavigator.swift */; }; 61243B572EC5FA3700D46FA0 /* PenguinViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B462EC5FA3700D46FA0 /* PenguinViewFactory.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 765A5A8C2F35CD8B0003FF7D /* Penguin.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A802F35CD730003FF7D /* Penguin.xcframework */; }; - 765A5A8D2F35CD8B0003FF7D /* Penguin.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A802F35CD730003FF7D /* Penguin.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 765A5A8E2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */; }; - 765A5A8F2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 765A5A902F35CD8B0003FF7D /* PenNavUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */; }; - 765A5A912F35CD8B0003FF7D /* PenNavUI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 76AA18AE2F3B2A4D00DC8DFC /* ring_30Sec.caf in Resources */ = {isa = PBXBuildFile; fileRef = 76AA18AC2F3B2A4D00DC8DFC /* ring_30Sec.caf */; }; 76AA18AF2F3B2A4D00DC8DFC /* ring_30Sec.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 76AA18AD2F3B2A4D00DC8DFC /* ring_30Sec.mp3 */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; @@ -49,9 +49,9 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 765A5A8F2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Embed Frameworks */, - 765A5A8D2F35CD8B0003FF7D /* Penguin.xcframework in Embed Frameworks */, - 765A5A912F35CD8B0003FF7D /* PenNavUI.xcframework in Embed Frameworks */, + 47C1AACA2F425AD000DA1231 /* PenguinINRenderer.xcframework in Embed Frameworks */, + 47C1AAC82F425ACF00DA1231 /* Penguin.xcframework in Embed Frameworks */, + 47C1AACC2F425AD100DA1231 /* PenNavUI.xcframework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -66,6 +66,9 @@ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 478CFA952E6E20A60064F3D7 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 47C1AAC12F425AC800DA1231 /* Penguin.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Penguin.xcframework; sourceTree = ""; }; + 47C1AAC22F425AC800DA1231 /* PenguinINRenderer.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = PenguinINRenderer.xcframework; sourceTree = ""; }; + 47C1AAC32F425AC800DA1231 /* PenNavUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = PenNavUI.xcframework; sourceTree = ""; }; 61243B3D2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HMGPenguinInPlatformBridge.swift; sourceTree = ""; }; 61243B422EC5FA3700D46FA0 /* PenguinModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinModel.swift; sourceTree = ""; }; 61243B432EC5FA3700D46FA0 /* PenguinNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinNavigator.swift; sourceTree = ""; }; @@ -75,9 +78,6 @@ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7595037DD52211B91157B0F3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 765A5A802F35CD730003FF7D /* Penguin.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Penguin.xcframework; sourceTree = ""; }; - 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = PenguinINRenderer.xcframework; sourceTree = ""; }; - 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = PenNavUI.xcframework; sourceTree = ""; }; 769C9BF82E6F106D009F68A9 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; 76AA18AC2F3B2A4D00DC8DFC /* ring_30Sec.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = ring_30Sec.caf; sourceTree = ""; }; 76AA18AD2F3B2A4D00DC8DFC /* ring_30Sec.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ring_30Sec.mp3; sourceTree = ""; }; @@ -99,9 +99,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 765A5A8C2F35CD8B0003FF7D /* Penguin.xcframework in Frameworks */, - 765A5A902F35CD8B0003FF7D /* PenNavUI.xcframework in Frameworks */, - 765A5A8E2F35CD8B0003FF7D /* PenguinINRenderer.xcframework in Frameworks */, + 47C1AAC92F425AD000DA1231 /* PenguinINRenderer.xcframework in Frameworks */, + 47C1AACB2F425AD100DA1231 /* PenNavUI.xcframework in Frameworks */, + 47C1AAC72F425ACF00DA1231 /* Penguin.xcframework in Frameworks */, DE1EF78253E64BE86845D2CC /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -140,9 +140,9 @@ 766D8CB22EC60BE600D05E07 /* Frameworks */ = { isa = PBXGroup; children = ( - 765A5A802F35CD730003FF7D /* Penguin.xcframework */, - 765A5A812F35CD730003FF7D /* PenguinINRenderer.xcframework */, - 765A5A822F35CD730003FF7D /* PenNavUI.xcframework */, + 47C1AAC12F425AC800DA1231 /* Penguin.xcframework */, + 47C1AAC22F425AC800DA1231 /* PenguinINRenderer.xcframework */, + 47C1AAC32F425AC800DA1231 /* PenNavUI.xcframework */, D562310E31D1DDEFA02A6C12 /* Pods_Runner.framework */, ); name = Frameworks; @@ -523,10 +523,11 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 15; DEVELOPMENT_TEAM = 3A359E86ZF; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Dr. Alhabib"; IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -709,10 +710,11 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 15; DEVELOPMENT_TEAM = 3A359E86ZF; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Dr. Alhabib"; IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -738,10 +740,11 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 15; DEVELOPMENT_TEAM = 3A359E86ZF; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Dr. Alhabib"; IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index 1eb27a20..65b74d7e 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,128 +1 @@ -{ - "images" : [ - { - "filename" : "Icon-App-20x20@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "Icon-App-20x20@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "filename" : "Icon-App-29x29@1x.png", - "idiom" : "iphone", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-29x29@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-29x29@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-40x40@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "Icon-App-40x40@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "filename" : "Icon-App-60x60@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "filename" : "Icon-App-60x60@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "filename" : "Icon-App-20x20@1x.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "filename" : "Icon-App-20x20@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "Icon-App-29x29@1x.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-29x29@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "Icon-App-40x40@1x.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "filename" : "Icon-App-40x40@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "Icon-App-76x76@1x.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "filename" : "Icon-App-76x76@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "filename" : "Icon-App-83.5x83.5@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "filename" : "icon.jpg", - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - }, - { - "filename" : "Icon-App-76x76@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "76x76" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} +{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]} \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 9c805ac5..bbe54c9a 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -13,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - Dr. Alhabib Beta + Dr. Alhabib CFBundlePackageType APPL CFBundleShortVersionString @@ -101,7 +101,6 @@ audio fetch - location remote-notification voip diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 116e0733..2de4ce5c 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -184,15 +184,15 @@ class ApiClientImp implements ApiClient { body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } - if (url.contains("HMGAI_Lab_Analyze_Orders_API")) { - url = "https://uat.hmgwebservices.com/Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API"; - body['TokenID'] = "@dm!n"; - } - - if (url.contains("HMGAI_Lab_Analyzer_API")) { - url = "https://uat.hmgwebservices.com/Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; - body['TokenID'] = "@dm!n"; - } + // if (url.contains("HMGAI_Lab_Analyze_Orders_API")) { + // url = "https://uat.hmgwebservices.com/Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API"; + // body['TokenID'] = "@dm!n"; + // } + // + // if (url.contains("HMGAI_Lab_Analyzer_API")) { + // url = "https://uat.hmgwebservices.com/Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; + // body['TokenID'] = "@dm!n"; + // } if (url == 'https://uat.hmgwebservices.com/Services/NHIC.svc/REST/GetPatientInfo') { url = "https://hmgwebservices.com/Services/NHIC.svc/REST/GetPatientInfo"; @@ -200,7 +200,7 @@ class ApiClientImp implements ApiClient { } // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 809289; + // body['PatientID'] = 1231755; // body['PatientTypeID'] = 1; // body['PatientOutSA'] = 0; // body['SessionID'] = "45786230487560q"; @@ -359,7 +359,8 @@ class ApiClientImp implements ApiClient { onFailure( parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, - failureType: ServerFailure("Error While Fetching data"), + // failureType: ServerFailure("Error While Fetching data"), + failureType: ServerFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']), ); logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index a529c740..5fae22b1 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -233,7 +233,8 @@ class ApiConsts { static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; // ************ static values for Api **************** - static final double appVersionID = 20.2; + static final double appVersionID = 20.5; + // static final double appVersionID = 50.7; static final int appChannelId = 3; static final String appIpAddress = "10.20.10.20"; @@ -777,8 +778,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='; @@ -920,6 +920,10 @@ const DASHBOARD = 'Services/Patients.svc/REST/PatientDashboard'; const SEND_PATIENT_IMMEDIATE_UPDATE_INSURANCE_REQUEST = 'Services/OUTPs.svc/REST/PatientCompanyUpdate'; +const PROFILE_SETTING = 'Services/Patients.svc/REST/GetPateintInfoForUpdate'; + +const SAVE_SETTING = 'Services/Patients.svc/REST/UpdatePateintInfo'; + class ApiKeyConstants { static final String googleMapsApiKey = 'AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng'; } diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index fdd32814..86fbb07e 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -17,6 +17,8 @@ class AppAssets { static const String email = '$svgBasePath/email.svg'; static const String globe = '$svgBasePath/globe.svg'; static const String globeOther = '$svgBasePath/globe_other.svg'; + + // static const String globeOther = '$svgBasePath/globe_other.svg'; static const String cancel = '$svgBasePath/cancel.svg'; static const String bell = '$svgBasePath/bell.svg'; static const String login1 = '$svgBasePath/login1.svg'; @@ -333,6 +335,8 @@ class AppAssets { static const String changeLanguageHomePageIcon = '$svgBasePath/change_language_home_page.svg'; static const String aiOverView = '$svgBasePath/ai_overview.svg'; static const String darkModeIcon = '$svgBasePath/dark_mode_icon.svg'; + static const String biometricLockIcon = '$svgBasePath/biometric_lock_icon.svg'; + static const String imageIcon = '$svgBasePath/image_icon.svg'; // PNGS // static const String hmgLogo = '$pngBasePath/hmg_logo.png'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 4266aa3b..657d03ea 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -50,6 +50,7 @@ import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_repo.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; +import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_repo.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/features/qr_parking/qr_parking_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; @@ -172,6 +173,7 @@ class AppDependencies { getIt.registerLazySingleton(() => NotificationsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => AskDoctorRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ServicesPriceListRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => ProfileSettingsRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -224,7 +226,11 @@ class AppDependencies { authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()), ); - getIt.registerLazySingleton(() => ProfileSettingsViewModel(cacheService: getIt())); + getIt.registerLazySingleton(() => ProfileSettingsViewModel( + cacheService: getIt(), + profileSettingsRepo: getIt(), + errorHandlerService: getIt(), + )); getIt.registerLazySingleton(() => DateRangeSelectorRangeViewModel()); diff --git a/lib/core/location_util.dart b/lib/core/location_util.dart index cf26d24e..95b154ba 100644 --- a/lib/core/location_util.dart +++ b/lib/core/location_util.dart @@ -94,7 +94,27 @@ class LocationUtils { permissionGranted = await Geolocator.requestPermission(); if (permissionGranted != LocationPermission.whileInUse && permissionGranted != LocationPermission.always) { appState.resetLocation(); - onFailure?.call(); + if (onFailure == null && isShowConfirmDialog) { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!), + navigationService.navigatorKey.currentContext!, + child: Utils.getWarningWidget( + loadingText: "Please grant location permission from app settings to see better results", + isShowActionButtons: true, + onCancelTap: () { + navigationService.pop(); + }, + onConfirmTap: () async { + navigationService.pop(); + openAppSettings(); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } else { + onFailure?.call(); + } return; } } else if (permissionGranted == LocationPermission.deniedForever) { diff --git a/lib/core/utils/calender_utils_new.dart b/lib/core/utils/calender_utils_new.dart index 9edfdced..331cf6d5 100644 --- a/lib/core/utils/calender_utils_new.dart +++ b/lib/core/utils/calender_utils_new.dart @@ -1,9 +1,16 @@ import 'dart:async'; import 'package:device_calendar_plus/device_calendar_plus.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:jiffy/jiffy.dart' show Jiffy; import 'package:manage_calendar_events/manage_calendar_events.dart' hide Calendar; +import 'package:permission_handler/permission_handler.dart'; class CalenderUtilsNew { final DeviceCalendar calender = DeviceCalendar.instance; @@ -17,7 +24,26 @@ class CalenderUtilsNew { Future getCalenders() async { CalendarPermissionStatus result = await DeviceCalendar.instance.hasPermissions(); - if (result != CalendarPermissionStatus.granted) await DeviceCalendar.instance.requestPermissions(); + if (result != CalendarPermissionStatus.granted) { + // await DeviceCalendar.instance.requestPermissions(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: GetIt.instance().navigatorKey.currentContext!), + GetIt.instance().navigatorKey.currentContext!, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.calendarPermissionAlert.tr(), + isShowActionButtons: true, + onCancelTap: () { + GetIt.instance().pop(); + }, + onConfirmTap: () async { + GetIt.instance().pop(); + openAppSettings(); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } var calenders = await calender.listCalendars(); calenders.forEach((calender) { if (!calender.readOnly) { diff --git a/lib/core/utils/validation_utils.dart b/lib/core/utils/validation_utils.dart index a8bc03d3..b5651648 100644 --- a/lib/core/utils/validation_utils.dart +++ b/lib/core/utils/validation_utils.dart @@ -4,6 +4,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; @@ -32,6 +33,13 @@ class ValidationUtils { isCorrectID = false; } + if(nationalId!.length == 10) { + if (Utils.isSAUDIIDValid(nationalId!) == false) { + _dialogService.showExceptionBottomSheet(message: LocaleKeys.enterValidNationalId.tr(), onOkPressed: onOkPress); + isCorrectID = false; + } + } + if (nationalId != null && nationalId.isNotEmpty && selectedCountry != null) { if (selectedCountry == CountryEnum.saudiArabia) { if (!validateIqama(nationalId)) { diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 3f9327fc..6e28ffda 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -258,12 +258,12 @@ extension EmailValidator on String { style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'), ); - Widget toText18({Color? color, FontWeight? weight, bool isBold = false, bool isCenter = false, int? maxlines, TextOverflow? textOverflow}) => Text( + Widget toText18({Color? color, FontWeight? weight, bool isBold = false, bool isCenter = false, int? maxlines, TextOverflow? textOverflow, bool isEnglishOnly = false,}) => Text( maxLines: maxlines, textAlign: isCenter ? TextAlign.center : null, this, overflow: textOverflow, - style: TextStyle(fontSize: 18.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4), + style: TextStyle(fontSize: 18.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4, fontFamily: (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'),), ); Widget toText19({Color? color, bool isBold = false}) => Text( @@ -320,9 +320,9 @@ extension EmailValidator on String { height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.f, letterSpacing: -1, fontFamily: isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins', fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal), ); - Widget toText44({Color? color, bool isBold = false}) => Text( + Widget toText44({Color? color, bool isBold = false, bool isEnglishOnly = false,}) => Text( this, - style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, fontFamily: (isEnglishOnly ? "Poppins" : getIt.get().getLanguageCode() == "ar" ? 'GESSTwo' : 'Poppins'),), ); Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) { diff --git a/lib/extensions/widget_extensions.dart b/lib/extensions/widget_extensions.dart index 64672f5f..866c26a4 100644 --- a/lib/extensions/widget_extensions.dart +++ b/lib/extensions/widget_extensions.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:shimmer/shimmer.dart'; import 'package:sizer/sizer.dart'; @@ -152,6 +154,7 @@ extension SmoothContainerExtension on ShapeBorder { BorderSide? side, BorderRadius? customBorder, bool hasShadow = false, + bool hasDenseShadow = false, }) { final bgColor = backgroundColor ?? color; return ShapeDecoration( @@ -161,17 +164,17 @@ extension SmoothContainerExtension on ShapeBorder { smoothness: 1, side: side ?? BorderSide.none, ), - shadows: - // hasShadow - // ? [ - // BoxShadow( - // color: const Color(0xff000000).withOpacity(.05), - // blurRadius: 32, - // offset: const Offset(0, 0), - // ) - // ] - // : - [], + shadows: hasShadow + ? [ + BoxShadow( + // color: hasDenseShadow ? const Color(0xff000000).withOpacity(.06) : const Color(0xff000000).withOpacity(.1), + color: getIt.get().isDarkMode ? Color(0xff3a3a3a).withOpacity(1.0) : Color(0xffE1E1E1).withOpacity(1.0), + blurRadius: 0, + spreadRadius: 0, + offset: const Offset(1, 0), + ) + ] + : [], ); } } diff --git a/lib/features/ask_doctor/ask_doctor_repo.dart b/lib/features/ask_doctor/ask_doctor_repo.dart index 4e4a4006..bd6af5b8 100644 --- a/lib/features/ask_doctor/ask_doctor_repo.dart +++ b/lib/features/ask_doctor/ask_doctor_repo.dart @@ -47,7 +47,7 @@ class AskDoctorRepoImp implements AskDoctorRepo { try { final list = response['PatientDoctorAppointmentResultList']; - final clinicsList = list.map((item) => AskDoctorAppointmentHistoryList.fromJson(item as Map)).toList().cast(); + final clinicsList = list != null ? list.map((item) => AskDoctorAppointmentHistoryList.fromJson(item as Map)).toList().cast() : []; apiResponse = GenericApiModel>( messageStatus: messageStatus, diff --git a/lib/features/ask_doctor/models/ask_doctor_appointments_list.dart b/lib/features/ask_doctor/models/ask_doctor_appointments_list.dart index 7f40d71c..6b337662 100644 --- a/lib/features/ask_doctor/models/ask_doctor_appointments_list.dart +++ b/lib/features/ask_doctor/models/ask_doctor_appointments_list.dart @@ -143,7 +143,7 @@ class AskDoctorAppointmentHistoryList { noOfPatientsRate = json['NoOfPatientsRate']; projectName = json['ProjectName']; qR = json['QR']; - speciality = json['Speciality'].cast(); + // speciality = json['Speciality'].cast(); } Map toJson() { diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index ce54c69f..336b35ae 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -28,6 +28,7 @@ import 'package:hmg_patient_app_new/features/authentication/models/resp_models/a import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_activation_code_resp_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_user_staus_nhic_response_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; @@ -619,11 +620,12 @@ class AuthenticationViewModel extends ChangeNotifier { } else { activation.list!.first.isParentUser = true; } - activation.list!.first.bloodGroup = activation.patientBlodType; + activation.list!.first.bloodGroup = activation.patientBloodType; activation.list!.first.zipCode = selectedCountrySignup == CountryEnum.others ? '0' : selectedCountrySignup.countryCode; _appState.setAuthenticatedUser(activation.list!.first); _appState.setPrivilegeModelList(activation.list!.first.listPrivilege!); - _appState.setUserBloodGroup = activation.patientBlodType ?? "N/A"; + // _appState.setUserBloodGroup = activation.patientBlodType ?? "N/A"; + _appState.setUserBloodGroup = activation.patientBloodType ?? "N/A"; } // _appState.setUserBloodGroup = (activation.patientBlodType ?? ""); _appState.setAppAuthToken = activation.authenticationTokenId; @@ -646,6 +648,7 @@ class AuthenticationViewModel extends ChangeNotifier { await clearDefaultInputValues(); myAppointmentsVM.setIsAppointmentDataToBeLoaded(true); getIt.get().setIsInsuranceDataToBeLoaded(true); + getIt.get().setHasVitalSignDataLoaded(false); if (isUserAgreedBefore) { LoaderBottomSheet.hideLoader(); navigateToHomeScreen(); diff --git a/lib/features/authentication/widgets/otp_verification_screen.dart b/lib/features/authentication/widgets/otp_verification_screen.dart index 7ddccda8..9e80dbb9 100644 --- a/lib/features/authentication/widgets/otp_verification_screen.dart +++ b/lib/features/authentication/widgets/otp_verification_screen.dart @@ -20,6 +20,8 @@ import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; import 'package:provider/provider.dart'; import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart'; +import 'dart:ui' as ui; + typedef OnDone = void Function(String text); class ProvidedPinBoxTextAnimation { @@ -564,23 +566,27 @@ class _OTPVerificationScreenState extends State { SizedBox(height: 16.h), Center( - child: OTPWidget( - maxLength: _otpLength, - controller: _otpController, - pinBoxWidth: 70.h, - pinBoxHeight: 100.h, - pinBoxRadius: 16, - pinBoxBorderWidth: 0, - pinBoxOuterPadding: EdgeInsets.symmetric(horizontal: 4.h), - defaultBorderColor: Colors.transparent, - textBorderColor: Colors.transparent, - pinBoxColor: AppColors.whiteColor, - autoFocus: true, - onTextChanged: _onOtpChanged, - pinTextStyle: TextStyle( - fontSize: 40.f, - fontWeight: FontWeight.bold, - color: AppColors.whiteColor, + child: Directionality( + textDirection: ui.TextDirection.ltr, + child: OTPWidget( + maxLength: _otpLength, + controller: _otpController, + pinBoxWidth: 70.h, + pinBoxHeight: 100.h, + pinBoxRadius: 16, + pinBoxBorderWidth: 0, + pinBoxOuterPadding: EdgeInsets.symmetric(horizontal: 4.h), + defaultBorderColor: Colors.transparent, + textBorderColor: Colors.transparent, + pinBoxColor: AppColors.whiteColor, + autoFocus: true, + onTextChanged: _onOtpChanged, + pinTextStyle: TextStyle( + fontSize: 40.f, + fontWeight: FontWeight.bold, + color: AppColors.whiteColor, + fontFamily: "Poppins" + ), ), ), ), @@ -601,7 +607,7 @@ class _OTPVerificationScreenState extends State { children: [ LocaleKeys.resendIn.tr(context: context).toText16(color: AppColors.inputLabelTextColor), SizedBox(width: 2.h), - ' ($minutes:$seconds). '.toText16(color: AppColors.inputLabelTextColor) + ' ($minutes:$seconds). '.toText16(color: AppColors.inputLabelTextColor, isEnglishOnly: true) ], ); }, diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 4dd103b8..0d2a8f82 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -49,6 +49,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { bool isDoctorSearchByNameStarted = false; bool isAppointmentNearestGateLoading = false; + bool isLiveCareSelectedFromHomePage = false; bool isLiveCareSchedule = false; bool isGetDocForHealthCal = false; bool showSortFilterButtons = false; @@ -318,6 +319,11 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + setIsLiveCareSelectedFromHomePage(bool isLiveCareSelectedFromHomePage) { + this.isLiveCareSelectedFromHomePage = isLiveCareSelectedFromHomePage; + notifyListeners(); + } + setIsWaitingAppointmentSelected(bool isWaitingAppointmentSelected) { this.isWaitingAppointmentSelected = isWaitingAppointmentSelected; notifyListeners(); @@ -423,9 +429,25 @@ class BookAppointmentsViewModel extends ChangeNotifier { calculationID = null; isGetDocForHealthCal = false; selectedTabIndex = index; + checkLiveCareSymptomCheckerStatus(); notifyListeners(); } + bool checkLiveCareSymptomCheckerStatus() { + bool isAllowed = false; + + if (selectedTabIndex == 1) { + if (_appState.isAuthenticated) { + isAllowed = true; + } else { + isAllowed = false; + } + } else { + isAllowed = true; + } + return isAllowed; + } + /// this function will decide which clinic api to be called /// either api for region flow or the select clinic api Future getClinics() async { @@ -536,6 +558,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { clearSearchFilters(); getFiltersFromDoctorList(); _groupDoctorsList(); + setIsNearestAppointmentSelected(true); notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/features/contact_us/contact_us_view_model.dart b/lib/features/contact_us/contact_us_view_model.dart index d627c55e..34657a85 100644 --- a/lib/features/contact_us/contact_us_view_model.dart +++ b/lib/features/contact_us/contact_us_view_model.dart @@ -12,6 +12,7 @@ import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_p import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_status_coc_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; +import 'package:permission_handler/permission_handler.dart'; class ContactUsViewModel extends ChangeNotifier { ContactUsRepo contactUsRepo; @@ -22,6 +23,7 @@ class ContactUsViewModel extends ChangeNotifier { bool isHMGHospitalsListSelected = true; bool isLiveChatProjectsListLoading = false; bool isSendFeedbackTabSelected = true; + bool hasLocationEnabled = false; List hmgHospitalsLocationsList = []; List hmgPharmacyLocationsList = []; @@ -52,11 +54,12 @@ class ContactUsViewModel extends ChangeNotifier { ContactUsViewModel({required this.contactUsRepo, required this.errorHandlerService, required this.appState}); - initContactUsViewModel() { + initContactUsViewModel() async { isHMGLocationsListLoading = true; isHMGHospitalsListSelected = true; isLiveChatProjectsListLoading = true; isCOCItemsListLoading = true; + hasLocationEnabled = false; hmgHospitalsLocationsList.clear(); hmgPharmacyLocationsList.clear(); liveChatProjectsList.clear(); @@ -65,6 +68,18 @@ class ContactUsViewModel extends ChangeNotifier { selectedFeedbackType = FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'); setPatientFeedbackSelectedAppointment(null); getHMGLocations(); + + if (await Permission.location.isGranted) { + setHasLocationEnabled(true); + } else { + setHasLocationEnabled(false); + } + + notifyListeners(); + } + + setHasLocationEnabled(bool hasLocationEnabled) { + this.hasLocationEnabled = hasLocationEnabled; notifyListeners(); } @@ -128,6 +143,8 @@ class ContactUsViewModel extends ChangeNotifier { hmgPharmacyLocationsList.add(location); } } + + sortHMGLocations(hasLocationEnabled); isHMGLocationsListLoading = false; notifyListeners(); if (onSuccess != null) { @@ -138,6 +155,17 @@ class ContactUsViewModel extends ChangeNotifier { ); } + sortHMGLocations(bool isByLocation) { + if (isByLocation) { + hmgHospitalsLocationsList.sort((a, b) => a.distanceInKilometers.compareTo(b.distanceInKilometers)); + hmgPharmacyLocationsList.sort((a, b) => a.distanceInKilometers.compareTo(b.distanceInKilometers)); + } else { + hmgHospitalsLocationsList.sort((a, b) => a.locationName!.compareTo(b.locationName!)); + hmgPharmacyLocationsList.sort((a, b) => a.locationName!.compareTo(b.locationName!)); + } + notifyListeners(); + } + Future getLiveChatProjectsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { isLiveChatProjectsListLoading = true; liveChatProjectsList.clear(); diff --git a/lib/features/habib_wallet/habib_wallet_repo.dart b/lib/features/habib_wallet/habib_wallet_repo.dart index 659510de..c89a85be 100644 --- a/lib/features/habib_wallet/habib_wallet_repo.dart +++ b/lib/features/habib_wallet/habib_wallet_repo.dart @@ -3,6 +3,7 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/models/patient_advance_balance_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -40,18 +41,22 @@ class HabibWalletRepoImp implements HabibWalletRepo { }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { - // final list = response['ListPLO']; + final list = response['List_PatientAdvanceBalanceAmount']; // if (list == null || list.isEmpty) { // throw Exception("lab list is empty"); // } - // final labOrders = list.map((item) => PatientLabOrdersResponseModel.fromJson(item as Map)).toList().cast(); + final List balanceAmountList = list.map((item) => PatientAdvanceBalanceResponseModel.fromJson(item as Map)).toList().cast(); + + for (var element in balanceAmountList) { + element.totalAmount = response['TotalAdvanceBalanceAmount']; + } apiResponse = GenericApiModel( messageStatus: messageStatus, statusCode: statusCode, errorMessage: null, - data: response["TotalAdvanceBalanceAmount"], + data: balanceAmountList, ); } catch (e) { failure = DataParsingFailure(e.toString()); diff --git a/lib/features/habib_wallet/habib_wallet_view_model.dart b/lib/features/habib_wallet/habib_wallet_view_model.dart index 2f338957..3d72f651 100644 --- a/lib/features/habib_wallet/habib_wallet_view_model.dart +++ b/lib/features/habib_wallet/habib_wallet_view_model.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/models/patient_advance_balance_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; @@ -30,6 +31,8 @@ class HabibWalletViewModel extends ChangeNotifier { num selectedRechargeType = 1; + List habibWalletBalanceList = []; + HabibWalletViewModel({required this.habibWalletRepo, required this.errorHandlerService}); initHabibWalletProvider() { @@ -39,6 +42,7 @@ class HabibWalletViewModel extends ChangeNotifier { walletRechargeAmount = 0; selectedRechargeType = 1; advancePaymentHospitals.clear(); + habibWalletBalanceList.clear(); selectedHospital = null; fileNumber = ''; depositorName = ''; @@ -103,7 +107,13 @@ class HabibWalletViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { - habibWalletAmount = apiResponse.data!; + habibWalletBalanceList = apiResponse.data; + habibWalletAmount = habibWalletBalanceList.first.totalAmount ?? 0.0; + + habibWalletBalanceList.sort((a, b) => b.patientAdvanceBalanceAmount!.compareTo(a.patientAdvanceBalanceAmount!)); + + habibWalletBalanceList.removeWhere((element) => element.patientAdvanceBalanceAmount == 0); + isWalletAmountLoading = false; notifyListeners(); if (onSuccess != null) { diff --git a/lib/features/habib_wallet/models/patient_advance_balance_response_model.dart b/lib/features/habib_wallet/models/patient_advance_balance_response_model.dart new file mode 100644 index 00000000..55594555 --- /dev/null +++ b/lib/features/habib_wallet/models/patient_advance_balance_response_model.dart @@ -0,0 +1,27 @@ +class PatientAdvanceBalanceResponseModel { + num? distanceInKilometers; + num? patientAdvanceBalanceAmount; + String? projectDescription; + int? projectID; + num? totalAmount; + + PatientAdvanceBalanceResponseModel({this.distanceInKilometers, this.patientAdvanceBalanceAmount, this.projectDescription, this.projectID, this.totalAmount}); + + PatientAdvanceBalanceResponseModel.fromJson(Map json) { + distanceInKilometers = json['DistanceInKilometers']; + patientAdvanceBalanceAmount = json['PatientAdvanceBalanceAmount']; + projectDescription = json['ProjectDescription']; + projectID = json['ProjectID']; + totalAmount = json['TotalAmount']; + } + + Map toJson() { + final Map data = Map(); + data['DistanceInKilometers'] = distanceInKilometers; + data['PatientAdvanceBalanceAmount'] = patientAdvanceBalanceAmount; + data['ProjectDescription'] = projectDescription; + data['ProjectID'] = projectID; + data['TotalAmount'] = totalAmount; + return data; + } +} diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart index 5bf450ba..7b32cc6e 100644 --- a/lib/features/hmg_services/hmg_services_view_model.dart +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -85,6 +85,11 @@ class HmgServicesViewModel extends ChangeNotifier { List covidTestProcedureList = []; Covid19GetPaymentInfo? covidPaymentInfo; + void setHasVitalSignDataLoaded(bool hasVitalSignDataLoaded) { + this.hasVitalSignDataLoaded = hasVitalSignDataLoaded; + notifyListeners(); + } + Future getOrdersList() async {} // HHC multiple services selection diff --git a/lib/features/insurance/insurance_view_model.dart b/lib/features/insurance/insurance_view_model.dart index 515743c6..28210d39 100644 --- a/lib/features/insurance/insurance_view_model.dart +++ b/lib/features/insurance/insurance_view_model.dart @@ -13,6 +13,7 @@ class InsuranceViewModel extends ChangeNotifier { bool isInsuranceHistoryLoading = false; bool isInsuranceDetailsLoading = false; bool isInsuranceUpdateDetailsLoading = false; + bool isInsuranceExpiryBannerShown = false; bool isInsuranceDataToBeLoaded = true; bool isInsuranceApprovalsLoading = false; @@ -49,6 +50,11 @@ class InsuranceViewModel extends ChangeNotifier { notifyListeners(); } + setIsInsuranceExpiryBannerShown(bool isInsuranceExpiryBannerShown) { + this.isInsuranceExpiryBannerShown = isInsuranceExpiryBannerShown; + notifyListeners(); + } + setIsInsuranceHistoryLoading(bool val) { isInsuranceHistoryLoading = val; notifyListeners(); @@ -88,9 +94,10 @@ class InsuranceViewModel extends ChangeNotifier { (failure) async { debugPrint("InsuranceViewModel: API call failed - ${failure.toString()}"); isInsuranceLoading = false; - isInsuranceDataToBeLoaded = false; + isInsuranceDataToBeLoaded = true; isInsuranceExpired = false; isInsuranceActive = false; + isInsuranceExpiryBannerShown = false; notifyListeners(); }, (apiResponse) { @@ -111,6 +118,8 @@ class InsuranceViewModel extends ChangeNotifier { debugPrint("InsuranceViewModel: Insurance card expired: $isInsuranceExpired"); } + isInsuranceExpiryBannerShown = isInsuranceExpired; + isInsuranceActive = patientInsuranceList.first.isActive ?? false; // isInsuranceActive = true; @@ -148,6 +157,9 @@ class InsuranceViewModel extends ChangeNotifier { } Future getPatientInsuranceDetailsForUpdate(String patientID, String identificationNo, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + patientInsuranceUpdateResponseModel = null; + notifyListeners(); + final result = await insuranceRepo.getPatientInsuranceDetailsForUpdate(patientId: patientID, identificationNo: identificationNo); result.fold( @@ -214,7 +226,7 @@ class InsuranceViewModel extends ChangeNotifier { (failure) async { notifyListeners(); if (onError != null) { - onError(failure.toString()); + onError(failure.message.toString()); } }, (apiResponse) { diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index a787c6b6..0e7efdb3 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -106,6 +106,11 @@ class LabViewModel extends ChangeNotifier { notifyListeners(); } + closeAILabResultAnalysis() { + labOrderResponseByAi = null; + notifyListeners(); + } + void setIsSortByClinic(bool value) { isSortByClinic = value; patientLabOrdersViewList = isSortByClinic ? patientLabOrdersByClinic : patientLabOrdersByHospital; @@ -144,21 +149,26 @@ class LabViewModel extends ChangeNotifier { isLabOrdersLoading = false; isLabResultsLoading = false; - // --- Build groups by clinic and by hospital (projectName) --- - final clinicMap = >{}; - final hospitalMap = >{}; - for (var order in patientLabOrders) { - final clinicKey = (order.clinicDescription ?? 'Unknown').trim(); - clinicMap.putIfAbsent(clinicKey, () => []).add(order); - - final hospitalKey = (order.projectName ?? order.projectID ?? 'Unknown').toString().trim(); - hospitalMap.putIfAbsent(hospitalKey, () => []).add(order); + order.testDetails!.sort((a, b) => a.description!.compareTo(b.description!)); } - patientLabOrdersByClinic = clinicMap.values.toList(); - patientLabOrdersByHospital = hospitalMap.values.toList(); - patientLabOrdersViewList = isSortByClinic ? patientLabOrdersByClinic : patientLabOrdersByHospital; + // --- Build groups by clinic and by hospital (projectName) --- + // final clinicMap = >{}; + // final hospitalMap = >{}; + // + // for (var order in patientLabOrders) { + // final clinicKey = (order.clinicDescription ?? 'Unknown').trim(); + // clinicMap.putIfAbsent(clinicKey, () => []).add(order); + // + // final hospitalKey = (order.projectName ?? order.projectID ?? 'Unknown').toString().trim(); + // hospitalMap.putIfAbsent(hospitalKey, () => []).add(order); + // } + + // patientLabOrdersByClinic = clinicMap.values.toList(); + // patientLabOrdersByHospital = hospitalMap.values.toList(); + // patientLabOrdersViewList = isSortByClinic ? patientLabOrdersByClinic : patientLabOrdersByHospital; + // patientLabOrdersViewList = patientLabOrdersByClinic; filterSuggestions(); getUniqueTestDescription(); diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index d2296a66..1584f891 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -634,6 +634,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { Future getPatientMyDoctors({Function(dynamic)? onSuccess, Function(String)? onError}) async { // if (!isAppointmentDataToBeLoaded) return; isPatientMyDoctorsLoading = true; + patientMyDoctorsList.clear(); notifyListeners(); final result = await myAppointmentsRepo.getPatientDoctorsList(); diff --git a/lib/features/my_invoices/my_invoices_view_model.dart b/lib/features/my_invoices/my_invoices_view_model.dart index 20d7869f..f3c716ae 100644 --- a/lib/features/my_invoices/my_invoices_view_model.dart +++ b/lib/features/my_invoices/my_invoices_view_model.dart @@ -60,6 +60,9 @@ class MyInvoicesViewModel extends ChangeNotifier { (failure) async { isInvoiceDetailsLoading = false; notifyListeners(); + if (onError != null) { + onError(failure.message); + } }, (apiResponse) { if (apiResponse.messageStatus == 2) { diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index c0f9c7b5..4baf6882 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -19,6 +19,7 @@ import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/map/map_utility_screen.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:permission_handler/permission_handler.dart'; class PrescriptionsViewModel extends ChangeNotifier { bool isPrescriptionsOrdersLoading = false; @@ -68,9 +69,9 @@ class PrescriptionsViewModel extends ChangeNotifier { notifyListeners(); } - - checkIfReminderExistForPrescription(int index) async { + Future checkIfReminderExistForPrescription(int index) async { prescriptionDetailsList[index].hasReminder = await CalenderUtilsNew.instance.checkIfEventExist(prescriptionDetailsList[index].itemID?.toString() ?? ""); + return prescriptionDetailsList[index].hasReminder ?? false; } setPrescriptionsDetailsLoading() { @@ -157,14 +158,16 @@ class PrescriptionsViewModel extends ChangeNotifier { (failure) async { onError!(failure.message); }, - (apiResponse) { + (apiResponse) async { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { prescriptionDetailsList = apiResponse.data!; - prescriptionDetailsList.forEach((element) async { - await checkIfReminderExistForPrescription(prescriptionDetailsList.indexOf(element)); - }); + if (await Permission.calendarFullAccess.isGranted && await Permission.calendarWriteOnly.isGranted) { + prescriptionDetailsList.forEach((element) async { + await checkIfReminderExistForPrescription(prescriptionDetailsList.indexOf(element)); + }); + } isPrescriptionsDetailsLoading = false; notifyListeners(); if (onSuccess != null) { diff --git a/lib/features/profile_settings/profile_settings_repo.dart b/lib/features/profile_settings/profile_settings_repo.dart new file mode 100644 index 00000000..253bf505 --- /dev/null +++ b/lib/features/profile_settings/profile_settings_repo.dart @@ -0,0 +1,100 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class ProfileSettingsRepo { + /// Updates general patient info (name, phone, etc.). + Future>> updatePatientInfo({ + required Map patientInfo, + }); + + /// Deactivates (deletes) the patient's account. + Future>> deactivateAccount(); +} + +class ProfileSettingsRepoImp implements ProfileSettingsRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + ProfileSettingsRepoImp({ + required this.loggerService, + required this.apiClient, + }); + + @override + Future>> updatePatientInfo({ + required Map patientInfo, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + SAVE_SETTING, + body: patientInfo, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> deactivateAccount() async { + final Map body = {}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + // TODO: Replace with actual deactivate-account endpoint once available + 'Services/Patients.svc/REST/Patient_DeactivateAccount', + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } +} + diff --git a/lib/features/profile_settings/profile_settings_view_model.dart b/lib/features/profile_settings/profile_settings_view_model.dart index e001d1bd..92f11528 100644 --- a/lib/features/profile_settings/profile_settings_view_model.dart +++ b/lib/features/profile_settings/profile_settings_view_model.dart @@ -1,18 +1,42 @@ import 'package:flutter/foundation.dart'; +import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_repo.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; class ProfileSettingsViewModel extends ChangeNotifier { static const String _darkModeKey = 'is_dark_mode'; final CacheService _cacheService; + final ProfileSettingsRepo profileSettingsRepo; + final ErrorHandlerService errorHandlerService; + // ── Dark-mode state ────────────────────────────────────────────────── bool _isDarkMode = false; - bool get isDarkMode => _isDarkMode; - ProfileSettingsViewModel({required CacheService cacheService}) - : _cacheService = cacheService; + // ── Update email state ─────────────────────────────────────────────── + bool isUpdateEmailLoading = false; + bool isUpdateEmailSuccess = false; + String? updateEmailError; + + // ── Update patient info state ──────────────────────────────────────── + bool isUpdatePatientInfoLoading = false; + bool isUpdatePatientInfoSuccess = false; + String? updatePatientInfoError; + + // ── Deactivate account state ───────────────────────────────────────── + bool isDeactivateAccountLoading = false; + bool isDeactivateAccountSuccess = false; + String? deactivateAccountError; + + ProfileSettingsViewModel({ + required CacheService cacheService, + required this.profileSettingsRepo, + required this.errorHandlerService, + }) : _cacheService = cacheService; + + // ── Dark mode ──────────────────────────────────────────────────────── /// Call once at app startup (before the first frame) to restore the /// persisted dark-mode preference. @@ -30,6 +54,75 @@ class ProfileSettingsViewModel extends ChangeNotifier { notifyListeners(); } + // ── Update patient info ────────────────────────────────────────────── + + Future updatePatientInfo({ + required Map patientInfo, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isUpdatePatientInfoLoading = true; + isUpdatePatientInfoSuccess = false; + updatePatientInfoError = null; + notifyListeners(); + + final result = await profileSettingsRepo.updatePatientInfo(patientInfo: patientInfo); + + result.fold( + (failure) { + isUpdatePatientInfoLoading = false; + updatePatientInfoError = failure.message; + notifyListeners(); + if (onError != null) { + onError(failure.message); + } else { + errorHandlerService.handleError(failure: failure); + } + }, + (response) { + isUpdatePatientInfoLoading = false; + isUpdatePatientInfoSuccess = true; + notifyListeners(); + onSuccess?.call(response.data); + }, + ); + } + + // ── Deactivate account ─────────────────────────────────────────────── + + Future deactivateAccount({ + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isDeactivateAccountLoading = true; + isDeactivateAccountSuccess = false; + deactivateAccountError = null; + notifyListeners(); + + final result = await profileSettingsRepo.deactivateAccount(); + + result.fold( + (failure) { + isDeactivateAccountLoading = false; + deactivateAccountError = failure.message; + notifyListeners(); + if (onError != null) { + onError(failure.message); + } else { + errorHandlerService.handleError(failure: failure); + } + }, + (response) { + isDeactivateAccountLoading = false; + isDeactivateAccountSuccess = true; + notifyListeners(); + onSuccess?.call(response.data); + }, + ); + } + + // ── Helpers ────────────────────────────────────────────────────────── + void notify() { notifyListeners(); } diff --git a/lib/features/radiology/radiology_view_model.dart b/lib/features/radiology/radiology_view_model.dart index ee5c970f..fa43a96d 100644 --- a/lib/features/radiology/radiology_view_model.dart +++ b/lib/features/radiology/radiology_view_model.dart @@ -68,17 +68,17 @@ class RadiologyViewModel extends ChangeNotifier { filteredRadiologyOrders = List.from(patientRadiologyOrders); tempRadiologyOrders = [...patientRadiologyOrders]; - final clinicMap = >{}; - final hospitalMap = >{}; - for (var order in patientRadiologyOrders) { - final clinicKey = (order.clinicDescription ?? 'Unknown').trim(); - clinicMap.putIfAbsent(clinicKey, () => []).add(order); - final hospitalKey = (order.projectName ?? order.projectID ?? 'Unknown').toString().trim(); - hospitalMap.putIfAbsent(hospitalKey, () => []).add(order); - } - patientRadiologyOrdersByClinic = clinicMap.values.toList(); - patientRadiologyOrdersByHospital = hospitalMap.values.toList(); - patientRadiologyOrdersViewList = isSortByClinic ? patientRadiologyOrdersByClinic : patientRadiologyOrdersByHospital; + // final clinicMap = >{}; + // final hospitalMap = >{}; + // for (var order in patientRadiologyOrders) { + // final clinicKey = (order.clinicDescription ?? 'Unknown').trim(); + // clinicMap.putIfAbsent(clinicKey, () => []).add(order); + // final hospitalKey = (order.projectName ?? order.projectID ?? 'Unknown').toString().trim(); + // hospitalMap.putIfAbsent(hospitalKey, () => []).add(order); + // } + // patientRadiologyOrdersByClinic = clinicMap.values.toList(); + // patientRadiologyOrdersByHospital = hospitalMap.values.toList(); + // patientRadiologyOrdersViewList = isSortByClinic ? patientRadiologyOrdersByClinic : patientRadiologyOrdersByHospital; isRadiologyOrdersLoading = false; filterSuggestions(); @@ -173,7 +173,7 @@ class RadiologyViewModel extends ChangeNotifier { } filterSuggestions() { - final List labels = patientRadiologyOrders.map((detail) => detail.description).whereType().toList(); + final List labels = patientRadiologyOrders.map((detail) => detail.procedureName.toString().trim()).whereType().toList(); _radiologySuggestionsList = labels.toSet().toList(); notifyListeners(); } @@ -193,7 +193,7 @@ class RadiologyViewModel extends ChangeNotifier { patientRadiologyOrdersByHospital = hospitalMap.values.toList(); patientRadiologyOrdersViewList = isSortByClinic ? patientRadiologyOrdersByClinic : patientRadiologyOrdersByHospital; } else { - filteredRadiologyOrders = filteredRadiologyOrders.where((desc) => (desc.description ?? "").toLowerCase().contains(query.toLowerCase())).toList(); + filteredRadiologyOrders = filteredRadiologyOrders.where((desc) => (desc.procedureName ?? "").toLowerCase().contains(query.toLowerCase())).toList(); final clinicMap = >{}; final hospitalMap = >{}; @@ -206,6 +206,8 @@ class RadiologyViewModel extends ChangeNotifier { patientRadiologyOrdersByClinic = clinicMap.values.toList(); patientRadiologyOrdersByHospital = hospitalMap.values.toList(); patientRadiologyOrdersViewList = isSortByClinic ? patientRadiologyOrdersByClinic : patientRadiologyOrdersByHospital; + + patientRadiologyOrders = filteredRadiologyOrders; } notifyListeners(); } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 1a33df93..4f66d29c 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -436,7 +436,6 @@ abstract class LocaleKeys { static const serviceInformation = 'serviceInformation'; static const homeHealthCare = 'homeHealthCare'; static const noAppointmentAvailable = 'noAppointmentAvailable'; - static const homeHealthCareText = 'homeHealthCareText'; static const loginRegister = 'loginRegister'; static const orderLog = 'orderLog'; static const infoLab = 'infoLab'; @@ -746,6 +745,7 @@ abstract class LocaleKeys { static const infoTodo = 'infoTodo'; static const familyInfo = 'familyInfo'; static const rrtdDetails = 'rrtdDetails'; + static const homeHealthCareText = 'homeHealthCareText'; static const onlineCheckInAgreement = 'onlineCheckInAgreement'; static const infoEreferral = 'infoEreferral'; static const erConsultation = 'erConsultation'; @@ -1577,5 +1577,14 @@ abstract class LocaleKeys { static const timeFor = 'timeFor'; static const hmgPolicies = 'hmgPolicies'; static const darkMode = 'darkMode'; + static const generateAiAnalysisResult = 'generateAiAnalysisResult'; + static const ratings = 'ratings'; + static const hmgPharmacyText = 'hmgPharmacyText'; + static const insuranceRequestSubmittedSuccessfully = 'insuranceRequestSubmittedSuccessfully'; + static const updatingEmailAddress = 'updatingEmailAddress'; + static const verifyInsurance = 'verifyInsurance'; + static const tests = 'tests'; + static const calendarPermissionAlert = 'calendarPermissionAlert'; + static const sortByLocation = 'sortByLocation'; } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index cafb740e..072cb04a 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; @@ -31,6 +32,8 @@ import 'package:hmg_patient_app_new/presentation/appointments/appointment_paymen import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart'; +import 'package:hmg_patient_app_new/presentation/ask_doctor/ask_doctor_page.dart'; +import 'package:hmg_patient_app_new/presentation/ask_doctor/doctor_response_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart'; @@ -69,12 +72,12 @@ class _AppointmentDetailsPageState extends State { @override void initState() { scheduleMicrotask(() async { - CalenderUtilsNew calendarUtils = await CalenderUtilsNew.instance; - var doesExist = await calendarUtils.checkIfEventExist("${widget.patientAppointmentHistoryResponseModel.appointmentNo}"); - myAppointmentsViewModel.setAppointmentReminder(doesExist, widget.patientAppointmentHistoryResponseModel); - setState((){ - - }); + if (!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { + CalenderUtilsNew calendarUtils = await CalenderUtilsNew.instance; + var doesExist = await calendarUtils.checkIfEventExist("${widget.patientAppointmentHistoryResponseModel.appointmentNo}"); + myAppointmentsViewModel.setAppointmentReminder(doesExist, widget.patientAppointmentHistoryResponseModel); + setState(() {}); + } }); super.initState(); @@ -556,6 +559,22 @@ class _AppointmentDetailsPageState extends State { // ), // ); }), + MedicalFileCard( + label: LocaleKeys.doctorResponses.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.ask_doctor_medical_file_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() { + getIt.get().initAskDoctorViewModel(); + getIt.get().getDoctorResponses(); + Navigator.of(context).push( + CustomPageRoute( + page: DoctorResponsePage(), + ), + ); + }), ], ), // Column( @@ -839,8 +858,8 @@ class _AppointmentDetailsPageState extends State { onPressed: () { openDoctorScheduleCalendar(); }, - backgroundColor: AppColors.successColor, - borderColor: AppColors.successColor, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, textColor: Colors.white, fontSize: 16.f, fontWeight: FontWeight.w500, diff --git a/lib/presentation/appointments/my_doctors_page.dart b/lib/presentation/appointments/my_doctors_page.dart index d1ea0e98..002f537c 100644 --- a/lib/presentation/appointments/my_doctors_page.dart +++ b/lib/presentation/appointments/my_doctors_page.dart @@ -93,8 +93,6 @@ class _MyDoctorsPageState extends State { borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, - - ), SizedBox(width: 8.h), CustomButton( @@ -280,7 +278,7 @@ class _MyDoctorsPageState extends State { runSpacing: 4.h, children: [ AppCustomChipWidget( - labelText: isSortByClinic ? (doctor?.clinicName ?? "") : (doctor?.projectName ?? ""), + labelText: isSortByClinic ? (doctor?.projectName ?? "") : (doctor?.clinicName ?? ""), ), ], ), diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 89a18bf5..52a1c219 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -104,8 +104,8 @@ class AppointmentCard extends StatelessWidget { AppCustomChipWidget( labelText: isLoading ? 'OutPatient' : (appState.isArabic() ? patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! : patientAppointmentHistoryResponseModel.isInOutPatientDescription!), - backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1), - textColor: AppColors.primaryRedColor, + backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.1), + textColor: AppColors.warningColorYellow, ).toShimmer2(isShow: isLoading), AppCustomChipWidget( labelText: isLoading ? 'Booked' : AppointmentType.getAppointmentStatusType(patientAppointmentHistoryResponseModel.patientStatusType!), @@ -161,9 +161,13 @@ class AppointmentCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (isLoading ? 'John Doe' : "${patientAppointmentHistoryResponseModel.doctorTitle} ${patientAppointmentHistoryResponseModel.doctorNameObj!}") - .toText16(isBold: true, maxlines: 1) - .toShimmer2(isShow: isLoading), + Row( + children: [ + (isLoading ? 'Dr' : "${patientAppointmentHistoryResponseModel.doctorTitle}").toText16(isBold: true, maxlines: 1), + (isLoading ? 'John Doe' : " ${patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(20)}") + .toText16(isBold: true, maxlines: 1, isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")) + ], + ).toShimmer2(isShow: isLoading), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, @@ -325,12 +329,19 @@ class AppointmentCard extends StatelessWidget { flex: 1, child: Container( height: (isFoldable || isTablet) ? 50.h : 40.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.textColor, borderRadius: 10.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.transparent, + borderRadius: 10.h, + side: BorderSide( + color: AppColors.textColor, + width: 1.2, + ), + ), child: Transform.flip( flipX: appState.isArabic(), child: Utils.buildSvgWithAssets( icon: AppAssets.forward_arrow_icon, - iconColor: AppColors.whiteColor, + iconColor: AppColors.textColor, width: 24.w, height: 24.h, fit: BoxFit.contain, @@ -397,9 +408,10 @@ class AppointmentCard extends StatelessWidget { return CustomButton( text: LocaleKeys.rebookSameDoctor.tr(context: context), onPressed: () => openDoctorScheduleCalendar(context), - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, + backgroundColor: AppColors.transparent, + borderColor: AppColors.textColor, textColor: AppColors.blackColor, + borderWidth: 1.h, fontSize: (isFoldable || isTablet) ? 12.f : 14.f, fontWeight: FontWeight.w500, borderRadius: 12.r, @@ -420,7 +432,9 @@ class AppointmentCard extends StatelessWidget { ), ); } else { - bookAppointmentsViewModel.getAppointmentNearestGate(projectID: patientAppointmentHistoryResponseModel.projectID, clinicID: patientAppointmentHistoryResponseModel.clinicID); + if (!AppointmentType.isArrived(patientAppointmentHistoryResponseModel)) { + bookAppointmentsViewModel.getAppointmentNearestGate(projectID: patientAppointmentHistoryResponseModel.projectID, clinicID: patientAppointmentHistoryResponseModel.clinicID); + } Navigator.of(context) .push( CustomPageRoute( diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 298e8732..5a47708e 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -16,11 +16,7 @@ import 'dart:ui' as ui; class AppointmentDoctorCard extends StatelessWidget { const AppointmentDoctorCard( - {super.key, - required this.patientAppointmentHistoryResponseModel, - required this.onRescheduleTap, - required this.onCancelTap, - required this.onAskDoctorTap, this.renderWidgetForERDisplay = false}); + {super.key, required this.patientAppointmentHistoryResponseModel, required this.onRescheduleTap, required this.onCancelTap, required this.onAskDoctorTap, this.renderWidgetForERDisplay = false}); final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; final VoidCallback onRescheduleTap; @@ -82,7 +78,7 @@ class AppointmentDoctorCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - patientAppointmentHistoryResponseModel.doctorNameObj!.toText16(isBold: true), + patientAppointmentHistoryResponseModel.doctorNameObj!.toText16(isBold: true, isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? "")), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, @@ -107,20 +103,16 @@ class AppointmentDoctorCard extends StatelessWidget { richText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang( DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false, - )}".toText10(isEnglishOnly: true), + )}" + .toText10(isEnglishOnly: true), ), ), AppCustomChipWidget( labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), - icon: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! - ? AppAssets.walkin_appointment_icon - : AppAssets.small_livecare_icon, + icon: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon, iconColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : Colors.white, - labelText: patientAppointmentHistoryResponseModel.isLiveCareAppointment! - ? LocaleKeys.livecare.tr(context: context) - : LocaleKeys.walkin.tr(context: context), - backgroundColor: - !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor, + labelText: patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context), + backgroundColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor, textColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : Colors.white, ), ], @@ -163,25 +155,27 @@ class AppointmentDoctorCard extends StatelessWidget { icon: AppAssets.ask_doctor_icon, iconColor: AppColors.primaryRedColor, ) - : !patientAppointmentHistoryResponseModel.isLiveCareAppointment! - ? CustomButton( - text: LocaleKeys.rebookSameDoctor.tr(), - onPressed: () { - onRescheduleTap(); - }, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), - height: 40.h, - icon: AppAssets.rebook_appointment_icon, - iconColor: AppColors.blackColor, - iconSize: 14.h, - ) - : SizedBox.shrink(); + : + // !patientAppointmentHistoryResponseModel.isLiveCareAppointment! + // ? CustomButton( + // text: LocaleKeys.rebookSameDoctor.tr(), + // onPressed: () { + // onRescheduleTap(); + // }, + // backgroundColor: AppColors.greyColor, + // borderColor: AppColors.greyColor, + // textColor: AppColors.blackColor, + // fontSize: 12.f, + // fontWeight: FontWeight.w500, + // borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), + // height: 40.h, + // icon: AppAssets.rebook_appointment_icon, + // iconColor: AppColors.blackColor, + // iconSize: 14.h, + // ) + // : + SizedBox.shrink(); } else { return patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? CustomButton( diff --git a/lib/presentation/authentication/quick_login.dart b/lib/presentation/authentication/quick_login.dart index fbcb17d8..19e90d64 100644 --- a/lib/presentation/authentication/quick_login.dart +++ b/lib/presentation/authentication/quick_login.dart @@ -65,7 +65,8 @@ class QuickLoginState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.asset(AppAssets.lockIcon, height: 100), + // Image.asset(AppAssets.lockIcon, height: 100), + Utils.buildSvgWithAssets(icon: AppAssets.biometricLockIcon, iconColor: AppColors.textColor, width: 100.h, height: 100.h), SizedBox(height: 10.h), LocaleKeys.enableQuickLogin.tr(context: context).toText26(isBold: true), // Text( diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index e9f201ad..96db5187 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -206,7 +206,7 @@ class _SavedLogin extends State { }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, + textColor: AppColors.textColor, icon: AppAssets.sms), ), Row( @@ -232,6 +232,7 @@ class _SavedLogin extends State { textColor: AppColors.textColor, icon: AppAssets.whatsapp, iconColor: null, + applyThemeColor: false, ), ), ], diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index ab548449..4a8ada6a 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -59,9 +59,19 @@ class _BookAppointmentPageState extends State { bookAppointmentsViewModel.initBookAppointmentViewModel(); bookAppointmentsViewModel.getLocation(); immediateLiveCareViewModel.initImmediateLiveCare(); + if (appState.isAuthenticated) { + getIt.get().getPatientMyDoctors(); + } }); WidgetsBinding.instance.addPostFrameCallback((_) { - showUnKnownClinicBottomSheet(); + if (bookAppointmentsViewModel.selectedTabIndex == 1) { + if (appState.isAuthenticated) { + getIt.get().getPatientMyDoctors(); + showUnKnownClinicBottomSheet(); + } + } else { + showUnKnownClinicBottomSheet(); + } }); super.initState(); } @@ -203,7 +213,9 @@ class _BookAppointmentPageState extends State { ), ), ), - _buildSymptomsBottomCard(), + Consumer(builder: (context, bookAppointmentsVM, child) { + return _buildSymptomsBottomCard(); + }), ], ), ); @@ -414,12 +426,13 @@ class _BookAppointmentPageState extends State { } Widget _buildSymptomsBottomCard() { - return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), - child: Row( - children: [ - Expanded( - child: Column( + return bookAppointmentsViewModel.checkLiveCareSymptomCheckerStatus() + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + child: Row( + children: [ + Expanded( + child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -442,7 +455,8 @@ class _BookAppointmentPageState extends State { ) ], ).paddingAll(24.w), - ); + ) + : SizedBox.shrink(); } void openRegionListBottomSheet(BuildContext context, RegionBottomSheetType type) { diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart index 549b8b0d..fdd1af0f 100644 --- a/lib/presentation/book_appointment/doctor_profile_page.dart +++ b/lib/presentation/book_appointment/doctor_profile_page.dart @@ -1,4 +1,3 @@ - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -101,18 +100,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), - "Ratings".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + LocaleKeys.ratings.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate .toString() - .toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor), + .toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"), ], ).onPress(() { bookAppointmentsViewModel.getDoctorRatingDetails(); @@ -128,18 +121,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), - "Reviews".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + LocaleKeys.reviews.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate .toString() - .toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor), + .toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"), ], ).onPress(() { bookAppointmentsViewModel.getDoctorRatingDetails(); diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index 1fc9d9ad..7947fa05 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -46,7 +46,7 @@ class _SelectDoctorPageState extends State { void initState() { _scrollController = ScrollController(); scheduleMicrotask(() { - bookAppointmentsViewModel.setIsNearestAppointmentSelected(false); + bookAppointmentsViewModel.setIsNearestAppointmentSelected(true); if (bookAppointmentsViewModel.isLiveCareSchedule) { bookAppointmentsViewModel.getLiveCareDoctorsList(); } else { diff --git a/lib/presentation/book_appointment/widgets/appointment_calendar.dart b/lib/presentation/book_appointment/widgets/appointment_calendar.dart index 9124a16b..57a045cc 100644 --- a/lib/presentation/book_appointment/widgets/appointment_calendar.dart +++ b/lib/presentation/book_appointment/widgets/appointment_calendar.dart @@ -90,59 +90,64 @@ class _AppointmentCalendarState extends State { // ), SizedBox( height: 350.h, - child: SfCalendar( - controller: _calendarController, - minDate: DateTime.now(), - showNavigationArrow: true, - headerHeight: 60.h, - headerStyle: CalendarHeaderStyle( - backgroundColor: AppColors.scaffoldBgColor, - textAlign: TextAlign.start, - textStyle: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.primaryRedColor, fontFamily: "Poppins"), - ), - viewHeaderStyle: ViewHeaderStyle( - backgroundColor: AppColors.scaffoldBgColor, - dayTextStyle: TextStyle(fontSize: 14.f, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.textColor), - ), - view: CalendarView.month, - todayHighlightColor: Colors.transparent, - todayTextStyle: TextStyle(color: AppColors.textColor, fontWeight: FontWeight.bold), - selectionDecoration: ShapeDecoration( - color: AppColors.transparent, - shape: SmoothRectangleBorder( - borderRadius: BorderRadius.circular(10.r), - smoothness: 1, - side: BorderSide(color: AppColors.primaryRedColor, width: 1.5), + child: Localizations.override( + context: context, + locale: const Locale('en'), + child: SfCalendar( + controller: _calendarController, + minDate: DateTime.now(), + showNavigationArrow: true, + headerHeight: 60.h, + headerStyle: CalendarHeaderStyle( + backgroundColor: AppColors.scaffoldBgColor, + textAlign: TextAlign.start, + textStyle: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.primaryRedColor, fontFamily: "Poppins"), ), - ), - cellBorderColor: AppColors.transparent, - dataSource: MeetingDataSource(_getDataSource()), - monthCellBuilder: (context, details) => Padding( - padding: EdgeInsets.all(12.h), - child: details.date.day.toString().toText14( - isCenter: true, - color: details.date == _calendarController.selectedDate ? AppColors.primaryRedColor : AppColors.textColor, - ), - ), - monthViewSettings: MonthViewSettings( - dayFormat: "EEE", - appointmentDisplayMode: MonthAppointmentDisplayMode.indicator, - showTrailingAndLeadingDates: false, - appointmentDisplayCount: 1, - monthCellStyle: MonthCellStyle( - textStyle: TextStyle(fontSize: 19.f), + viewHeaderStyle: ViewHeaderStyle( + backgroundColor: AppColors.scaffoldBgColor, + dayTextStyle: TextStyle(fontSize: 14.f, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.textColor, fontFamily: "Poppins"), ), + view: CalendarView.month, + todayHighlightColor: Colors.transparent, + todayTextStyle: TextStyle(color: AppColors.textColor, fontWeight: FontWeight.bold, fontFamily: "Poppins"), + selectionDecoration: ShapeDecoration( + color: AppColors.transparent, + shape: SmoothRectangleBorder( + borderRadius: BorderRadius.circular(10.r), + smoothness: 1, + side: BorderSide(color: AppColors.primaryRedColor, width: 1.5), + ), + ), + cellBorderColor: AppColors.transparent, + dataSource: MeetingDataSource(_getDataSource()), + monthCellBuilder: (context, details) => Padding( + padding: EdgeInsets.all(12.h), + child: details.date.day.toString().toText14( + isCenter: true, + color: details.date == _calendarController.selectedDate ? AppColors.primaryRedColor : AppColors.textColor, + isEnglishOnly: true, + ), + ), + monthViewSettings: MonthViewSettings( + dayFormat: "EEE", + appointmentDisplayMode: MonthAppointmentDisplayMode.indicator, + showTrailingAndLeadingDates: false, + appointmentDisplayCount: 1, + monthCellStyle: MonthCellStyle( + textStyle: TextStyle(fontSize: 19.f), + ), + ), + onTap: (CalendarTapDetails details) { + _calendarController.selectedDate = details.date; + _onDaySelected(details.date!); + }, ), - onTap: (CalendarTapDetails details) { - _calendarController.selectedDate = details.date; - _onDaySelected(details.date!); - }, ), ), SizedBox(height: 10.h), Transform.translate( offset: const Offset(0.0, -10.0), - child: selectedDateDisplay.toText16(weight: FontWeight.w500), + child: selectedDateDisplay.toText16(weight: FontWeight.w500, isEnglishOnly: true), ), //TODO: Add Next Day Span here dayEvents.isNotEmpty @@ -153,23 +158,23 @@ class _AppointmentCalendarState extends State { child: Wrap( direction: Axis.horizontal, alignment: WrapAlignment.start, - spacing: 6.h, - runSpacing: 6.h, - children: List.generate( - dayEvents.length, // Generate a large number of items to ensure scrolling - (index) => TimeSlotChip( - label: dayEvents[index].isoTime!, - isSelected: index == selectedButtonIndex, - onTap: () { - setState(() { - selectedButtonIndex = index; - selectedTime = dayEvents[index].isoTime!; - }); - }, + spacing: 6.h, + runSpacing: 6.h, + children: List.generate( + dayEvents.length, // Generate a large number of items to ensure scrolling + (index) => TimeSlotChip( + label: dayEvents[index].isoTime!, + isSelected: index == selectedButtonIndex, + onTap: () { + setState(() { + selectedButtonIndex = index; + selectedTime = dayEvents[index].isoTime!; + }); + }, + ), + ), + ), ), - ), - ), - ), ) : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noFreeSlot.tr(context: context)), @@ -179,7 +184,7 @@ class _AppointmentCalendarState extends State { isDisabled: dayEvents.isEmpty, onPressed: () async { if (appState.isAuthenticated) { - if(selectedTime == LocaleKeys.waitingAppointment.tr(context: context)){ + if (selectedTime == LocaleKeys.waitingAppointment.tr(context: context)) { bookAppointmentsViewModel.setWaitingAppointmentProjectID(bookAppointmentsViewModel.selectedDoctor.projectID!); bookAppointmentsViewModel.setWaitingAppointmentDoctor(bookAppointmentsViewModel.selectedDoctor); @@ -351,26 +356,20 @@ class TimeSlotChip extends StatelessWidget { side: BorderSide(color: isSelected ? AppColors.warningColorYellow : AppColors.borderOnlyColor.withOpacity(0.2), width: 1), ), ), - child: label.toText12( - color: isSelected ? AppColors.whiteColor : Colors.black87, - fontWeight: FontWeight.w500, - ), + child: label.toText12(color: isSelected ? AppColors.whiteColor : Colors.black87, fontWeight: FontWeight.w500, isEnglishOnly: true), ) : Container( padding: EdgeInsets.symmetric(horizontal: 14.h, vertical: 8.h), decoration: ShapeDecoration( - color: AppColors.whiteColor, - shape: SmoothRectangleBorder( - borderRadius: BorderRadius.circular(8.h), - smoothness: 1, - side: BorderSide(color: isSelected ? AppColors.primaryRedColor : AppColors.borderOnlyColor.withOpacity(0.2), width: 1), - ), - ), - child: label.toText12( - color: isSelected ? AppColors.primaryRedColor : AppColors.textColor, - fontWeight: FontWeight.w500, - ), - ), + color: AppColors.whiteColor, + shape: SmoothRectangleBorder( + borderRadius: BorderRadius.circular(8.h), + smoothness: 1, + side: BorderSide(color: isSelected ? AppColors.primaryRedColor : AppColors.borderOnlyColor.withOpacity(0.2), width: 1), + ), + ), + child: label.toText12(color: isSelected ? AppColors.primaryRedColor : AppColors.textColor, fontWeight: FontWeight.w500, isEnglishOnly: true), + ), ); } } diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index 6eecf5f8..02b2e55d 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -109,7 +109,7 @@ class DoctorCard extends StatelessWidget { : doctorsListResponseModel.speciality!.first) : "") .toString() - .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 2) + .toText12(fontWeight: FontWeight.w500, color: AppColors.textColor, maxLine: 2, isEnglishOnly: true) .toShimmer2(isShow: isLoading), ), SizedBox(width: 6.w), diff --git a/lib/presentation/book_appointment/widgets/doctor_rating_details.dart b/lib/presentation/book_appointment/widgets/doctor_rating_details.dart index 6d9e1cb2..1a5eedfe 100644 --- a/lib/presentation/book_appointment/widgets/doctor_rating_details.dart +++ b/lib/presentation/book_appointment/widgets/doctor_rating_details.dart @@ -20,13 +20,20 @@ class DoctorRatingDetails extends StatelessWidget { : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - bookAppointmentsVM.doctorsProfileResponseModel.actualDoctorRate!.ceilToDouble().toString().toText44(isBold: true), + bookAppointmentsVM.doctorsProfileResponseModel.actualDoctorRate!.ceilToDouble().toString().toText44(isBold: true, isEnglishOnly: true), SizedBox(height: 4.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "${bookAppointmentsVM.doctorsProfileResponseModel.noOfPatientsRate} ${LocaleKeys.reviews.tr(context: context)}" - .toText16(weight: FontWeight.w500, color: AppColors.greyInfoTextColor), + Row( + children: [ + "${bookAppointmentsVM.doctorsProfileResponseModel.noOfPatientsRate} " + .toText16(weight: FontWeight.w500, color: AppColors.greyInfoTextColor, isEnglishOnly: true), + LocaleKeys.reviews.tr(context: context) + .toText16(weight: FontWeight.w500, color: AppColors.greyInfoTextColor,), + ], + ), + RatingBar( initialRating: bookAppointmentsVM.doctorsProfileResponseModel.actualDoctorRate!.toDouble(), direction: Axis.horizontal, @@ -75,7 +82,7 @@ class DoctorRatingDetails extends StatelessWidget { ), Container( margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), - child: Text("${getRatingWidth(bookAppointmentsVM.doctorDetailsList[0].ratio).round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600)), + child: Text("${getRatingWidth(bookAppointmentsVM.doctorDetailsList[0].ratio).round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600, fontFamily: "Poppins")), ), ], ), @@ -95,7 +102,7 @@ class DoctorRatingDetails extends StatelessWidget { ), Container( margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), - child: Text("${bookAppointmentsVM.doctorDetailsList[1].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600)), + child: Text("${bookAppointmentsVM.doctorDetailsList[1].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600, fontFamily: "Poppins")), ), ], ), @@ -115,7 +122,7 @@ class DoctorRatingDetails extends StatelessWidget { ), Container( margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), - child: Text("${bookAppointmentsVM.doctorDetailsList[2].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600)), + child: Text("${bookAppointmentsVM.doctorDetailsList[2].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600, fontFamily: "Poppins")), ), ], ), @@ -135,7 +142,7 @@ class DoctorRatingDetails extends StatelessWidget { ), Container( margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), - child: Text("${bookAppointmentsVM.doctorDetailsList[3].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600)), + child: Text("${bookAppointmentsVM.doctorDetailsList[3].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600, fontFamily: "Poppins")), ), ], ), @@ -156,7 +163,7 @@ class DoctorRatingDetails extends StatelessWidget { ), Container( margin: EdgeInsets.only(top: 10.0, left: 10.0, right: 10.0), - child: Text("${bookAppointmentsVM.doctorDetailsList[4].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600)), + child: Text("${bookAppointmentsVM.doctorDetailsList[4].ratio.round()}%", style: TextStyle(fontSize: 14.0, color: AppColors.textColor, fontWeight: FontWeight.w600, fontFamily: "Poppins")), ), ], ), diff --git a/lib/presentation/contact_us/contact_us.dart b/lib/presentation/contact_us/contact_us.dart index fd0a439b..70ef6e3f 100644 --- a/lib/presentation/contact_us/contact_us.dart +++ b/lib/presentation/contact_us/contact_us.dart @@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/presentation/contact_us/live_chat_page.dart' import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; class ContactUs extends StatelessWidget { ContactUs({super.key}); @@ -33,6 +34,15 @@ class ContactUs extends StatelessWidget { contactUsViewModel = Provider.of(context); return Column( children: [ + checkInOptionCard( + AppAssets.call_fill, + LocaleKeys.callNow.tr(), + // LocaleKeys.viewNearestHMGLocationsviewNearestHMGLocations.tr(), + "Call for immediate assistance", + ).onPress(() { + launchUrl(Uri.parse("tel://" + "+966 92 006 6666")); + }), + SizedBox(height: 16.h), checkInOptionCard( AppAssets.location, LocaleKeys.findUs.tr(), @@ -46,26 +56,25 @@ class ContactUs extends StatelessWidget { page: FindUsPage(), ), ); + }, onFailure: () { + contactUsViewModel.initContactUsViewModel(); + Navigator.pop(context); + Navigator.of(context).push( + CustomPageRoute( + page: FindUsPage(), + ), + ); + }, onLocationDeniedForever: () { + contactUsViewModel.initContactUsViewModel(); + Navigator.pop(context); + Navigator.of(context).push( + CustomPageRoute( + page: FindUsPage(), + ), + ); }); }), SizedBox(height: 16.h), - checkInOptionCard( - AppAssets.feedbackFill, - LocaleKeys.feedback.tr(), - LocaleKeys.provideFeedbackOnServices.tr(), - ).onPress(() { - contactUsViewModel.setSelectedFeedbackType( - FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'), - ); - contactUsViewModel.setIsSendFeedbackTabSelected(true); - Navigator.pop(context); - Navigator.of(context).push( - CustomPageRoute( - page: FeedbackPage(), - ), - ); - }), - SizedBox(height: 16.h), checkInOptionCard( AppAssets.ask_doctor_icon, LocaleKeys.liveChat.tr(), diff --git a/lib/presentation/contact_us/find_us_page.dart b/lib/presentation/contact_us/find_us_page.dart index 5957bb83..9091f74d 100644 --- a/lib/presentation/contact_us/find_us_page.dart +++ b/lib/presentation/contact_us/find_us_page.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/location_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; @@ -20,11 +21,14 @@ class FindUsPage extends StatelessWidget { late AppState appState; late ContactUsViewModel contactUsViewModel; + late LocationUtils locationUtils; @override Widget build(BuildContext context) { contactUsViewModel = Provider.of(context); appState = getIt.get(); + locationUtils = getIt.get(); + locationUtils.isShowConfirmDialog = true; return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( @@ -47,8 +51,48 @@ class FindUsPage extends StatelessWidget { contactUsVM.setHMGHospitalsListSelected(index == 0); }, ).paddingSymmetrical(24.h, 0.h), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.sortByLocation.tr(context: context).toText14(isBold: true), + SizedBox(height: 4.h), + "Sort the locations by nearest to your location".toText11(color: AppColors.textColorLight, weight: FontWeight.w500), + ], + ), + const Spacer(), + Switch( + activeThumbColor: AppColors.successColor, + activeTrackColor: AppColors.successColor.withValues(alpha: .15), + value: contactUsVM.hasLocationEnabled, + onChanged: (newValue) async { + if (newValue) { + locationUtils.getCurrentLocation( + onSuccess: (value) { + // if (contactUsVM.hmgHospitalsLocationsList.isNotEmpty) { + // contactUsVM.sortHMGLocations(true); + // contactUsVM.setHasLocationEnabled(newValue); + // } else { + contactUsVM.initContactUsViewModel(); + contactUsVM.setHasLocationEnabled(newValue); + contactUsVM.sortHMGLocations(true); + // } + }, + onFailure: () {}, + ); + } else { + contactUsVM.sortHMGLocations(false); + contactUsVM.setHasLocationEnabled(newValue); + } + // bookAppointmentsVM.setIsNearestAppointmentSelected(newValue); + }, + ), + ], + ).paddingSymmetrical(24.h, 12.h), ListView.separated( - padding: EdgeInsets.only(top: 16.h), + padding: EdgeInsets.only(top: 4.h), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), itemCount: contactUsVM.isHMGLocationsListLoading diff --git a/lib/presentation/contact_us/widgets/find_us_item_card.dart b/lib/presentation/contact_us/widgets/find_us_item_card.dart index 4738a63a..521775bc 100644 --- a/lib/presentation/contact_us/widgets/find_us_item_card.dart +++ b/lib/presentation/contact_us/widgets/find_us_item_card.dart @@ -6,24 +6,30 @@ import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_hmg_locations.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:map_launcher/map_launcher.dart'; +import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; class FindUsItemCard extends StatelessWidget { FindUsItemCard({super.key, required this.getHMGLocationsModel}); late AppState appState; + late ContactUsViewModel contactUsViewModel; GetHMGLocationsModel getHMGLocationsModel; @override Widget build(BuildContext context) { appState = getIt.get(); + contactUsViewModel = getIt.get(); return DecoratedBox( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -45,71 +51,108 @@ class FindUsItemCard extends StatelessWidget { ); } - Widget get hospitalName => Row( + Widget get hospitalName => Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.network( - getHMGLocationsModel.projectImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", - width: 40.h, - height: 40.h, - fit: BoxFit.cover, - ).circle(100).toShimmer2(isShow: false).paddingOnly(right: 10), - Expanded( - child: Text( - getHMGLocationsModel.locationName!, - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - color: AppColors.blackColor, - ), - ), - ) + (getHMGLocationsModel.distanceInKilometers != 0 && contactUsViewModel.hasLocationEnabled) + ? Column( + children: [ + AppCustomChipWidget( + labelText: "${getHMGLocationsModel.distanceInKilometers ?? ""} km", + labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.w), + icon: AppAssets.location_red, + // iconColor: AppColors.primaryRedColor, + // backgroundColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.errorColor, + ), + SizedBox( + height: 16.h, + ), + ], + ) + : SizedBox.shrink(), + Row( + children: [ + Image.network( + getHMGLocationsModel.projectImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + width: 40.h, + height: 40.h, + fit: BoxFit.cover, + ).circle(100).toShimmer2(isShow: false).paddingOnly(right: 10), + Expanded( + child: Text( + getHMGLocationsModel.locationName!, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: AppColors.blackColor, + ), + ), + ) + ], + ), ], ); Widget get distanceInfo => Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - AppCustomChipWidget( - labelText: "${getHMGLocationsModel.distanceInKilometers ?? ""} km", - icon: AppAssets.location_red, - iconColor: AppColors.primaryRedColor, - backgroundColor: AppColors.secondaryLightRedColor, - textColor: AppColors.errorColor, - ), - Row( - children: [ - AppCustomChipWidget( - labelText: LocaleKeys.getDirections.tr(), - icon: AppAssets.directions_icon, - iconColor: AppColors.whiteColor, - backgroundColor: AppColors.textColor.withValues(alpha: 0.8), - textColor: AppColors.whiteColor, - onChipTap: () async { - await MapLauncher.showMarker( - mapType: MapType.google, + Expanded( + flex: 7, + child: CustomButton( + text: LocaleKeys.getDirections.tr(), + onPressed: () async { + await MapLauncher.showMarker( + mapType: MapType.google, + coords: Coords(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0")), + title: getHMGLocationsModel.locationName ?? "Hospital", + ).catchError((err) { + MapLauncher.showMarker( + mapType: Platform.isIOS ? MapType.apple : MapType.google, coords: Coords(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0")), title: getHMGLocationsModel.locationName ?? "Hospital", - ).catchError((err) { - MapLauncher.showMarker( - mapType: Platform.isIOS ? MapType.apple : MapType.google, - coords: Coords(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0")), - title: getHMGLocationsModel.locationName ?? "Hospital", - ); - }); - }, + ); + }); + }, + backgroundColor: AppColors.transparent, + borderColor: AppColors.textColor, + textColor: AppColors.blackColor, + borderWidth: 1.h, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + icon: AppAssets.directions_icon, + iconColor: AppColors.blackColor, + iconSize: 16.h, + ), + ), + SizedBox(width: 8.w), + Expanded( + flex: 1, + child: Container( + height: (isFoldable || isTablet) ? 50.h : 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.transparent, + borderRadius: 10.h, + side: BorderSide( + color: AppColors.textColor, + width: 1.2, + ), ), - SizedBox(width: 4.w), - AppCustomChipWidget( - labelText: LocaleKeys.callNow.tr(), - icon: AppAssets.call_fill, - iconColor: Colors.white, - backgroundColor: AppColors.primaryRedColor.withValues(alpha: 1.0), - textColor: Colors.white, - onChipTap: () { - launchUrl(Uri.parse("tel://" + "${getHMGLocationsModel.phoneNumber}")); - }, + child: Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.call_fill, + iconColor: AppColors.textColor, + width: 5.w, + height: 5.h, + fit: BoxFit.scaleDown, + ), ), - ], + ).onPress(() { + launchUrl(Uri.parse("tel://" + "${getHMGLocationsModel.phoneNumber}")); + }), ), ], ); diff --git a/lib/presentation/emergency_services/emergency_services_page.dart b/lib/presentation/emergency_services/emergency_services_page.dart index 41597e40..7970d819 100644 --- a/lib/presentation/emergency_services/emergency_services_page.dart +++ b/lib/presentation/emergency_services/emergency_services_page.dart @@ -35,7 +35,7 @@ class EmergencyServicesPage extends StatelessWidget { return CollapsingListView( title: LocaleKeys.emergencyServices.tr(), - requests: () { + history: () { emergencyServicesViewModel.changeOrderDisplayItems(OrderDislpay.ALL); Navigator.of(context).push(CustomPageRoute(page: ErHistoryListing(), direction: AxisDirection.up)); }, diff --git a/lib/presentation/habib_wallet/habib_wallet_page.dart b/lib/presentation/habib_wallet/habib_wallet_page.dart index c90003a8..2a3da660 100644 --- a/lib/presentation/habib_wallet/habib_wallet_page.dart +++ b/lib/presentation/habib_wallet/habib_wallet_page.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -26,6 +28,16 @@ class HabibWalletPage extends StatefulWidget { class _HabibWalletState extends State { late HabibWalletViewModel habibWalletVM; + @override + void initState() { + scheduleMicrotask(() async { + habibWalletVM.initHabibWalletProvider(); + habibWalletVM.getPatientBalanceAmount(); + }); + + super.initState(); + } + @override Widget build(BuildContext context) { habibWalletVM = Provider.of(context, listen: false); @@ -44,8 +56,10 @@ class _HabibWalletState extends State { width: double.infinity, height: 180.h, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.blackBgColor, - borderRadius: 24, + // color: AppColors.blackBgColor, + color: Color(0xFF2E3039), + borderRadius: 24.r, + hasShadow: true ), child: Padding( padding: EdgeInsets.all(16.h), @@ -59,18 +73,18 @@ class _HabibWalletState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "${_appState.getAuthenticatedUser()!.firstName!} ${_appState.getAuthenticatedUser()!.lastName!}".toText19(isBold: true, color: AppColors.whiteColor), + "${_appState.getAuthenticatedUser()!.firstName!} ${_appState.getAuthenticatedUser()!.lastName!}".toText19(isBold: true, color: Colors.white), "MRN: ${_appState.getAuthenticatedUser()!.patientId!}".toText14(weight: FontWeight.w500, color: AppColors.greyTextColor), ], ).expanded, - Utils.buildSvgWithAssets(icon: AppAssets.habiblogo, width: 24.h, height: 24.h), + Utils.buildSvgWithAssets(icon: AppAssets.habiblogo, width: 24.h, height: 24.h, applyThemeColor: false), ], ), Spacer(), - LocaleKeys.balanceAmount.tr(context: context).toText14(weight: FontWeight.w500, color: AppColors.whiteColor), + LocaleKeys.balanceAmount.tr(context: context).toText14(weight: FontWeight.w500, color: Colors.white), SizedBox(height: 4.h), Consumer(builder: (context, habibWalletVM, child) { - return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount, textColor: AppColors.whiteColor, iconColor: AppColors.whiteColor, iconSize: 13, isExpanded: false) + return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount, textColor: Colors.white, iconColor: Colors.white, iconSize: 16, isExpanded: false) .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 24.h); }), ], @@ -86,7 +100,7 @@ class _HabibWalletState extends State { icon: AppAssets.recharge_icon, iconSize: 24.w, backgroundColor: AppColors.infoColor, - textColor: AppColors.whiteColor, + textColor: Colors.white, text: LocaleKeys.recharge.tr(context: context), borderWidth: 0.w, fontWeight: FontWeight.w500, @@ -99,12 +113,42 @@ class _HabibWalletState extends State { page: RechargeWalletPage(), )) .then((val) { + habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); }); }, ), ], ), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, + ), + child: Consumer(builder: (context, habibWalletVM, child) { + return ListView.separated( + itemCount: habibWalletVM.habibWalletBalanceList.length, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(top: 8, bottom: 8), + shrinkWrap: true, + itemBuilder: (context, index) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: habibWalletVM.habibWalletBalanceList[index].projectDescription!.toText16(weight: FontWeight.w500, color: AppColors.textColor)), + Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletBalanceList[index].patientAdvanceBalanceAmount!, textColor: AppColors.textColor, iconColor: AppColors.textColor, iconSize: 18.h, isExpanded: false, fontSize: 28.f, fontWeight: FontWeight.w500), + ], + ).paddingSymmetrical(0, 12.h); + }, + separatorBuilder: (BuildContext context, int index) { + return Divider(height: 1, color: AppColors.textColor.withAlpha(20)); + }, + ).paddingSymmetrical(16.h, 24.h).toShimmer2(isShow: habibWalletVM.isWalletAmountLoading); + }), + ), + SizedBox(height: 24.h), ], ), ), diff --git a/lib/presentation/home/app_update_page.dart b/lib/presentation/home/app_update_page.dart index 5e6481bd..18382838 100644 --- a/lib/presentation/home/app_update_page.dart +++ b/lib/presentation/home/app_update_page.dart @@ -83,13 +83,15 @@ class AppUpdatePage extends StatelessWidget { }).catchError((e) { print(e.toString()); Utils.openWebView( - url: "https://play.google.com/store/apps/details?id=com.ejada.hmg", + // url: "https://play.google.com/store/apps/details?id=com.ejada.hmg", + url: "https://play.google.com/store/apps/details?id=com.cloudsolutions.HMGPatientApp", ); }); } if (Platform.isIOS) { Utils.openWebView( - url: "https://itunes.apple.com/app/id733503978", + // url: "https://itunes.apple.com/app/id733503978", + url: "https://itunes.apple.com/app/id6758851027", ); } } diff --git a/lib/presentation/home/data/landing_page_data.dart b/lib/presentation/home/data/landing_page_data.dart index b74e57f3..4606c800 100644 --- a/lib/presentation/home/data/landing_page_data.dart +++ b/lib/presentation/home/data/landing_page_data.dart @@ -165,8 +165,8 @@ class LandingPageData { serviceName: "home_health_care", icon: AppAssets.homeBottom, title: LocaleKeys.homeHealthCare, - subtitle: LocaleKeys.liveCareServiceDesc, - largeCardIcon: AppAssets.homeHealthCareService, + subtitle: LocaleKeys.homeHealthCareText, + largeCardIcon: AppAssets.homeHealthCareService, backgroundColor: AppColors.primaryRedColor, iconColor: AppColors.whiteColor, isBold: false, @@ -175,8 +175,8 @@ class LandingPageData { serviceName: "pharmacy", icon: AppAssets.pharmacy_icon, //359846 title: LocaleKeys.hmgPharmacy, - subtitle: LocaleKeys.liveCareServiceDesc, - largeCardIcon: AppAssets.pharmacyService, + subtitle: LocaleKeys.hmgPharmacyText, + largeCardIcon: AppAssets.pharmacyService, backgroundColor: AppColors.pharmacyBGColor, iconColor: null, isBold: true, diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index a7ae78b9..14684c0c 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -25,6 +25,8 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode import 'package:hmg_patient_app_new/features/hospital/hospital_selection_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; @@ -45,7 +47,10 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card. import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; +import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart'; import 'package:hmg_patient_app_new/presentation/notifications/notifications_list_page.dart'; import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart'; import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointment_doctor.dart'; @@ -53,6 +58,7 @@ import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_procedur import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/zoom_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -62,6 +68,7 @@ import 'package:hmg_patient_app_new/widgets/countdown_timer.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dart'; import 'package:provider/provider.dart'; +import 'package:smooth_corner/smooth_corner.dart'; import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart'; @@ -122,6 +129,7 @@ class _LandingPageState extends State { emergencyServicesViewModel.checkPatientERAdvanceBalance(); // myAppointmentsViewModel.getPatientAppointmentQueueDetails(); notificationsViewModel.initNotificationsViewModel(); + insuranceViewModel.initInsuranceProvider(); // Commented as per new requirement to remove rating popup from the app @@ -160,29 +168,50 @@ class _LandingPageState extends State { canPop: false, child: Scaffold( backgroundColor: AppColors.bgScaffoldColor, - body: SingleChildScrollView( - padding: EdgeInsets.only(top: kToolbarHeight + 0.h, bottom: 24), - child: Column( - spacing: 16.h, + body: Consumer(builder: (context, insuranceVM, child) { + return Stack( children: [ - Row( - spacing: 8.h, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - appState.isAuthenticated - ? WelcomeWidget( - onTap: () { - Navigator.of(context).push(springPageRoute(ProfileSettings())); - }, - name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), - imageUrl: appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, - ).expanded - : CustomButton( - text: LocaleKeys.loginOrRegister.tr(context: context), - onPressed: () async { - await authVM.onLoginPressed(); + SingleChildScrollView( + padding: EdgeInsets.only( + top: (!insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown) + ? (MediaQuery.paddingOf(context).top + 70.h) + : kToolbarHeight + 0.h, + bottom: 24), + child: Column( + spacing: 16.h, + children: [ + Row( + spacing: 8.h, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + appState.isAuthenticated + ? WelcomeWidget( + onTap: () { + // DialogService dialogService = getIt.get(); + // dialogService.showFamilyBottomSheetWithoutH( + // label: LocaleKeys.familyTitle.tr(context: context), + // message: "", + // isShowManageButton: true, + // onSwitchPress: (FamilyFileResponseModelLists profile) { + // getIt.get().switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); + // }, + // profiles: getIt.get().patientFamilyFiles); + + Navigator.of(context).push( + CustomPageRoute( + page: FamilyMedicalScreen(), + ), + ); + }, + name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), + imageUrl: appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, + ).expanded + : CustomButton( + text: LocaleKeys.loginOrRegister.tr(context: context), + onPressed: () async { + await authVM.onLoginPressed(); - // Navigator.pushReplacementNamed( + // Navigator.pushReplacementNamed( // // context, // context, // AppRoutes.zoomCallPage, @@ -223,470 +252,542 @@ class _LandingPageState extends State { }), (appState.isAuthenticated && (int.parse(todoSectionVM.notificationsCount ?? "0") > 0)) ? Positioned( - right: 0, - top: 0, - child: Container( - width: 8.w, - height: 8.h, - padding: EdgeInsets.all(4), - decoration: BoxDecoration( - color: AppColors.primaryRedColor, - borderRadius: BorderRadius.circular(20.r), - ), - child: Text( - "", - style: TextStyle( - color: Colors.white, - fontSize: 8.f, - ), - textAlign: TextAlign.center, - ), - ), - ) - : SizedBox.shrink(), - ]), - Utils.buildSvgWithAssets(icon: AppAssets.indoor_nav_icon, height: 24.h, width: 24.w).onPress(() { - openIndoorNavigationBottomSheet(context); - }), - Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 24.h, width: 24.h).onPress(() { - showCommonBottomSheetWithoutHeight( - context, + right: 0, + top: 0, + child: Container( + width: 8.w, + height: 8.h, + padding: EdgeInsets.all(4), + decoration: BoxDecoration( + color: AppColors.primaryRedColor, + borderRadius: BorderRadius.circular(20.r), + ), + child: Text( + "", + style: TextStyle( + color: Colors.white, + fontSize: 8.f, + ), + textAlign: TextAlign.center, + ), + ), + ) + : SizedBox.shrink(), + ]), + Utils.buildSvgWithAssets(icon: AppAssets.location, height: 24.h, width: 24.w).onPress(() { + // openIndoorNavigationBottomSheet(context); + showCommonBottomSheetWithoutHeight( + context, title: LocaleKeys.contactUs.tr(), child: ContactUs(), callBackFunc: () {}, isFullScreen: false, ); }), - !appState.isAuthenticated - ? Utils.buildSvgWithAssets(icon: AppAssets.changeLanguageHomePageIcon, height: 24.h, width: 24.h).onPress(() { - context.setLocale(appState.isArabic() ? Locale('en', 'US') : Locale('ar', 'SA')); - }) - : SizedBox.shrink() - ], + // Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 24.h, width: 24.h).onPress(() { + // showCommonBottomSheetWithoutHeight( + // context, + // title: LocaleKeys.contactUs.tr(), + // child: ContactUs(), + // callBackFunc: () {}, + // isFullScreen: false, + // ); + // }), + !appState.isAuthenticated + ? Utils.buildSvgWithAssets(icon: appState.isArabic() ? AppAssets.enLangIcon : AppAssets.arLangIcon, height: 24.h, width: 24.h).onPress(() { + context.setLocale(appState.isArabic() ? Locale('en', 'US') : Locale('ar', 'SA')); + }) + : SizedBox.shrink() + ], ); }), ], ).paddingSymmetrical(24.h, 0.h), !appState.isAuthenticated ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Row( - children: [ - Utils.buildSvgWithAssets( - width: 50.w, - height: 60.h, - icon: AppAssets.symptomCheckerIcon, - fit: BoxFit.contain, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, ), - SizedBox(width: 12.w), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.howAreYouFeelingToday.tr(context: context).toText14(weight: FontWeight.w600), - LocaleKeys.checkYourSymptomsWithScale.tr(context: context).toText12(fontWeight: FontWeight.w500), - SizedBox(height: 14.h), - CustomButton( - text: LocaleKeys.checkYourSymptoms.tr(context: context), - onPressed: () async { - context.navigateWithName(AppRoutes.userInfoSelection); - }, - padding: EdgeInsetsGeometry.zero, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: Colors.white, - fontSize: 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - height: 40.h, - ), - ], - ).expanded - ], - ), - ), - ).paddingSymmetrical(24.w, 0.h) - : SizedBox.shrink(), - appState.isAuthenticated - ? Column( - children: [ - SizedBox(height: 12.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.appointmentsAndVisits.tr(context: context).toText16(weight: FontWeight.w600), - Row( - children: [ - LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), - ], + child: Padding( + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets( + width: 50.w, + height: 60.h, + icon: AppAssets.symptomCheckerIcon, + fit: BoxFit.contain, + ), + SizedBox(width: 12.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.howAreYouFeelingToday.tr(context: context).toText14(weight: FontWeight.w600), + LocaleKeys.checkYourSymptomsWithScale.tr(context: context).toText12(fontWeight: FontWeight.w500), + SizedBox(height: 14.h), + CustomButton( + text: LocaleKeys.checkYourSymptoms.tr(context: context), + onPressed: () async { + context.navigateWithName(AppRoutes.userInfoSelection); + }, + padding: EdgeInsetsGeometry.zero, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: Colors.white, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + height: 40.h, + ), + ], + ).expanded + ], + ), ), - ], - ).paddingSymmetrical(24.h, 0.h).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage())); - }), - SizedBox(height: 16.h), - Consumer3( - builder: (context, myAppointmentsVM, immediateLiveCareVM, todoSectionVM, child) { - return myAppointmentsVM.isMyAppointmentsLoading - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: true, - isFromHomePage: true, - ), - ).paddingSymmetrical(24.h, 0.h) - : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty - ? myAppointmentsVM.patientAppointmentsHistoryList.length == 1 - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList.first, - myAppointmentsViewModel: myAppointmentsViewModel, - bookAppointmentsViewModel: bookAppointmentsViewModel, - isLoading: false, - isFromHomePage: true, - ), - ).paddingSymmetrical(24.h, 0.h) - : isTablet - ? SizedBox( - height: isFoldable ? 290.h : 255.h, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: 3, - shrinkWrap: true, - padding: EdgeInsets.only(left: 16.h, right: 16.h), - itemBuilder: (context, index) { - return SizedBox( - height: 255.h, - width: 250.w, - child: getIndexSwiperCard(index), - ); - // return AnimationConfiguration.staggeredList( - // position: index, - // duration: const Duration(milliseconds: 1000), - // child: SlideAnimation( - // horizontalOffset: 100.0, - // child: FadeInAnimation( - // child: SizedBox( - // height: 255.h, - // width: 250.w, - // child: getIndexSwiperCard(index), - // ), - // ), - // ), - // ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox( - width: 10.w, + ).paddingSymmetrical(24.w, 0.h) + : SizedBox.shrink(), + appState.isAuthenticated + ? Column( + children: [ + SizedBox(height: 12.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.appointmentsAndVisits.tr(context: context).toText16(weight: FontWeight.w600), + Row( + children: [ + LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), + ], + ), + ], + ).paddingSymmetrical(24.h, 0.h).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage())); + }), + Consumer3( + builder: (context, myAppointmentsVM, immediateLiveCareVM, todoSectionVM, child) { + return myAppointmentsVM.isMyAppointmentsLoading + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: true, + isFromHomePage: true, + ), + ).paddingSymmetrical(24.h, 16.h) + : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty + ? myAppointmentsVM.patientAppointmentsHistoryList.length == 1 + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, ), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList.first, + myAppointmentsViewModel: myAppointmentsViewModel, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: true, + ), + ).paddingSymmetrical(24.h, 0.h) + : isTablet + ? SizedBox( + height: isFoldable ? 290.h : 255.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: 3, + shrinkWrap: true, + padding: EdgeInsets.only(left: 16.h, right: 16.h), + itemBuilder: (context, index) { + return SizedBox( + height: 255.h, + width: 250.w, + child: getIndexSwiperCard(index), + ); + // return AnimationConfiguration.staggeredList( + // position: index, + // duration: const Duration(milliseconds: 1000), + // child: SlideAnimation( + // horizontalOffset: 100.0, + // child: FadeInAnimation( + // child: SizedBox( + // height: 255.h, + // width: 250.w, + // child: getIndexSwiperCard(index), + // ), + // ), + // ), + // ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox( + width: 10.w, + ), + ), + ) + : SizedBox( + height: 255.h + 20 + 30, // itemHeight + shadow padding (10 top + 10 bottom) + pagination dots space + child: Swiper( + itemCount: myAppointmentsVM.isMyAppointmentsLoading + ? 3 + : myAppointmentsVM.patientAppointmentsHistoryList.length < 3 + ? myAppointmentsVM.patientAppointmentsHistoryList.length + : 3, + layout: SwiperLayout.STACK, + loop: false, + itemWidth: MediaQuery.of(context).size.width - 48.h, + indicatorLayout: PageIndicatorLayout.COLOR, + axisDirection: getIt.get().isArabic() ? AxisDirection.left : AxisDirection.right, + controller: _controller, + itemHeight: 255.h + 20, + // extra space for shadow + pagination: SwiperPagination( + alignment: Alignment.bottomCenter, + margin: EdgeInsets.only(top: 220.h + 20 + 8 + 24), + builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), + ), + itemBuilder: (BuildContext context, int index) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: getIndexSwiperCard(index), + ); + }, + ), + ) + : Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), + SizedBox(height: 12.h), + LocaleKeys.noUpcomingAppointmentPleaseBook.tr(context: context).toText12(isCenter: true), + SizedBox(height: 12.h), + CustomButton( + text: LocaleKeys.bookAppo.tr(context: context), + onPressed: () { + getIt.get().onTabChanged(0); + Navigator.of(context).push(CustomPageRoute(page: BookAppointmentPage())); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 14.f, + fontWeight: FontWeight.w500, + padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), + icon: AppAssets.add_icon, + iconColor: AppColors.primaryRedColor, + height: 40.h, + ), + ], ), - ) - : Swiper( - itemCount: myAppointmentsVM.isMyAppointmentsLoading - ? 3 - : myAppointmentsVM.patientAppointmentsHistoryList.length < 3 - ? myAppointmentsVM.patientAppointmentsHistoryList.length - : 3, - layout: SwiperLayout.STACK, - loop: true, - itemWidth: MediaQuery.of(context).size.width - 48.h, - indicatorLayout: PageIndicatorLayout.COLOR, - axisDirection: AxisDirection.right, - controller: _controller, - itemHeight: 255.h, - pagination: SwiperPagination( - alignment: Alignment.bottomCenter, - margin: EdgeInsets.only(top: 240.h + 8 + 24), - builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), - ), - itemBuilder: (BuildContext context, int index) { - return getIndexSwiperCard(index); - }, - ) - : Container( - width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), - SizedBox(height: 12.h), - LocaleKeys.noUpcomingAppointmentPleaseBook.tr(context: context).toText12(isCenter: true), - SizedBox(height: 12.h), - CustomButton( - text: LocaleKeys.bookAppo.tr(context: context), - onPressed: () { - getIt.get().onTabChanged(0); - Navigator.of(context).push(CustomPageRoute(page: BookAppointmentPage())); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 14.f, - fontWeight: FontWeight.w500, - padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), - icon: AppAssets.add_icon, - iconColor: AppColors.primaryRedColor, - height: 40.h, ), - ], - ), - ), - ).paddingSymmetrical(24.h, 0.h); - }, - ), + ).paddingSymmetrical(24.h, 16.h); + }, + ), - // Consumer for ER Online Check-In pending request - Consumer( - builder: (context, emergencyServicesVM, child) { - return emergencyServicesVM.patientHasAdvanceERBalance - ? Column( + // Consumer for ER Online Check-In pending request + Consumer( + builder: (context, emergencyServicesVM, child) { + return emergencyServicesVM.patientHasAdvanceERBalance + ? Column( + children: [ + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, + side: BorderSide(color: AppColors.primaryRedColor, width: 3.h), + ), + width: double.infinity, + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // AppCustomChipWidget( + // labelText: LocaleKeys.erOnlineCheckInRequest.tr(context: context), + // backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.10), + // textColor: AppColors.primaryRedColor, + // ), + // Utils.buildSvgWithAssets(icon: AppAssets.appointment_checkin_icon, width: 24.h, height: 24.h, iconColor: AppColors.primaryRedColor), + // ], + // ), + SizedBox(height: 8.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: true, - side: BorderSide(color: AppColors.primaryRedColor, width: 3.h), - ), - width: double.infinity, - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - AppCustomChipWidget( - labelText: LocaleKeys.erOnlineCheckInRequest.tr(context: context), - backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.10), - textColor: AppColors.primaryRedColor, - ), - Utils.buildSvgWithAssets(icon: AppAssets.appointment_checkin_icon, width: 24.h, height: 24.h, iconColor: AppColors.primaryRedColor), - ], - ), - SizedBox(height: 8.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.youHaveEROnlineCheckInRequest.tr(context: context).toText12(isBold: true), - Transform.flip( - flipX: getIt.get().isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon_small, - iconColor: AppColors.blackColor, - width: 20.h, - height: 15.h, - fit: BoxFit.contain, - ), - ), - ], - ), - ], - ), + LocaleKeys.youHaveEROnlineCheckInRequest.tr(context: context).toText12(isBold: true), + Transform.flip( + flipX: getIt.get().isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, + width: 20.h, + height: 15.h, + fit: BoxFit.contain, ), - ).paddingSymmetrical(24.h, 0.h).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: ErOnlineCheckinHome())); - // context.read().navigateToEROnlineCheckIn(); - }), - SizedBox(height: 12.h), + ), ], - ) - : SizedBox(height: 0.h); - }, + ), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: ErOnlineCheckinHome())); + // context.read().navigateToEROnlineCheckIn(); + }), + SizedBox(height: 12.h), + ], + ) + : SizedBox(height: 0.h); + }, + ), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.quickLinks.tr(context: context).toText16(weight: FontWeight.w600), + Row( + children: [ + LocaleKeys.viewMedicalFile.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), + ], + ), + ], + ).paddingSymmetrical(24.h, 0.h).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: MedicalFilePage())); + }), + SizedBox(height: 16.h), + Container( + // height: 121.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + child: Column( + children: [ + SizedBox( + height: 92.h + 32.h - 4.h, + child: RawScrollbar( + controller: _horizontalScrollController, + thumbVisibility: true, + radius: Radius.circular(10.0), + thumbColor: AppColors.primaryRedColor, + trackVisibility: true, + trackColor: Color(0xffD9D9D9), + trackBorderColor: Colors.transparent, + trackRadius: Radius.circular(10.0), + padding: EdgeInsets.only(top: 92.h + 32.h, left: MediaQuery + .sizeOf(context) + .width / 2 - 35, right: MediaQuery + .sizeOf(context) + .width / 2 - 35), + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getLoggedInServiceCardsList.length, + shrinkWrap: true, + controller: _horizontalScrollController, + padding: EdgeInsets.only(left: 16.h, right: 16.h, top: 16.h, bottom: 12.h), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SmallServiceCard( + icon: LandingPageData.getLoggedInServiceCardsList[index].icon, + title: LandingPageData.getLoggedInServiceCardsList[index].title, + subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle, + iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor!, + textColor: LandingPageData.getLoggedInServiceCardsList[index].textColor, + backgroundColor: LandingPageData.getLoggedInServiceCardsList[index].backgroundColor, + isBold: LandingPageData.getLoggedInServiceCardsList[index].isBold, + serviceName: LandingPageData.getLoggedInServiceCardsList[index].serviceName, + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => 10.width, + ), + ), ), SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.quickLinks.tr(context: context).toText16(weight: FontWeight.w600), - Row( + ], + ), + ).paddingSymmetrical(24.h, 0.h), + ], + ) + : Container( + // height: 141.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + child: Column( children: [ - LocaleKeys.viewMedicalFile.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), - ], - ), - ], - ).paddingSymmetrical(24.h, 0.h).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: MedicalFilePage())); - }), - SizedBox(height: 16.h), - Container( - // height: 121.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), - child: Column( - children: [ - SizedBox( - height: 92.h + 32.h - 4.h, - child: RawScrollbar( - controller: _horizontalScrollController, - thumbVisibility: true, - radius: Radius.circular(10.0), - thumbColor: AppColors.primaryRedColor, - trackVisibility: true, - trackColor: Color(0xffD9D9D9), - trackBorderColor: Colors.transparent, - trackRadius: Radius.circular(10.0), - padding: EdgeInsets.only(top: 92.h + 32.h, left: MediaQuery.sizeOf(context).width / 2 - 35, right: MediaQuery.sizeOf(context).width / 2 - 35), - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getLoggedInServiceCardsList.length, - shrinkWrap: true, + SizedBox( + height: 92.h + 32.h - 4.h, + child: RawScrollbar( controller: _horizontalScrollController, - padding: EdgeInsets.only(left: 16.h, right: 16.h, top: 16.h, bottom: 12.h), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SmallServiceCard( - icon: LandingPageData.getLoggedInServiceCardsList[index].icon, - title: LandingPageData.getLoggedInServiceCardsList[index].title, - subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor!, - textColor: LandingPageData.getLoggedInServiceCardsList[index].textColor, - backgroundColor: LandingPageData.getLoggedInServiceCardsList[index].backgroundColor, - isBold: LandingPageData.getLoggedInServiceCardsList[index].isBold, - serviceName: LandingPageData.getLoggedInServiceCardsList[index].serviceName, + thumbVisibility: true, + radius: Radius.circular(10.0), + thumbColor: AppColors.primaryRedColor, + trackVisibility: true, + trackColor: Color(0xffD9D9D9), + trackBorderColor: Colors.transparent, + trackRadius: Radius.circular(10.0), + padding: EdgeInsets.only(top: 92.h + 32.h, left: MediaQuery.sizeOf(context).width / 2 - 35, right: MediaQuery.sizeOf(context).width / 2 - 35), + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, + shrinkWrap: true, + controller: _horizontalScrollController, + padding: EdgeInsets.only(left: 16.h, right: 16.h, top: 16.h, bottom: 12.h), + // padding: EdgeInsets.zero, + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SmallServiceCard( + serviceName: LandingPageData.getNotLoggedInServiceCardsList[index].serviceName, + icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon, + title: LandingPageData.getNotLoggedInServiceCardsList[index].title, + subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle, + iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor!, + textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor, + backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor, + isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold, + ), ), ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => 10.width, + ); + }, + separatorBuilder: (BuildContext cxt, int index) => 0.width, + ), ), ), - ), - SizedBox(height: 16.h), - ], - ), - ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + ], + ), + ).paddingSymmetrical(24.h, 0.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.services2.tr(context: context).toText18(weight: FontWeight.w600), + Row( + children: [ + LocaleKeys.viewAllServices.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), + ], + ).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: ServicesPage())); + }), ], - ) - : Container( - // height: 141.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), - child: Column( - children: [ - SizedBox( - height: 92.h + 32.h - 4.h, - child: RawScrollbar( - controller: _horizontalScrollController, - thumbVisibility: true, - radius: Radius.circular(10.0), - thumbColor: AppColors.primaryRedColor, - trackVisibility: true, - trackColor: Color(0xffD9D9D9), - trackBorderColor: Colors.transparent, - trackRadius: Radius.circular(10.0), - padding: EdgeInsets.only(top: 92.h + 32.h, left: MediaQuery.sizeOf(context).width / 2 - 35, right: MediaQuery.sizeOf(context).width / 2 - 35), - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getNotLoggedInServiceCardsList.length, - shrinkWrap: true, - controller: _horizontalScrollController, - padding: EdgeInsets.only(left: 16.h, right: 16.h, top: 16.h, bottom: 12.h), - // padding: EdgeInsets.zero, - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: SmallServiceCard( - serviceName: LandingPageData.getNotLoggedInServiceCardsList[index].serviceName, - icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon, - title: LandingPageData.getNotLoggedInServiceCardsList[index].title, - subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData.getNotLoggedInServiceCardsList[index].iconColor!, - textColor: LandingPageData.getNotLoggedInServiceCardsList[index].textColor, - backgroundColor: LandingPageData.getNotLoggedInServiceCardsList[index].backgroundColor, - isBold: LandingPageData.getNotLoggedInServiceCardsList[index].isBold, - ), - ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => 0.width, + ).paddingSymmetrical(24.w, 0.h), + SizedBox( + height: 431.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: LandingPageData.getServiceCardsList.length, + shrinkWrap: true, + padding: EdgeInsets.only(left: 24.w, right: 24.w), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: FadedLargeServiceCard( + serviceCardData: LandingPageData.getServiceCardsList[index], + image: LandingPageData.getServiceCardsList[index].icon, + title: LandingPageData.getServiceCardsList[index].title, + subtitle: LandingPageData.getServiceCardsList[index].subtitle, + icon: LandingPageData.getServiceCardsList[index].largeCardIcon, + ), ), ), - ), - SizedBox(height: 16.h), - ], - ), - ).paddingSymmetrical(24.h, 0.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.services2.tr(context: context).toText18(weight: FontWeight.w600), - Row( - children: [ - LocaleKeys.viewAllServices.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500), - SizedBox(width: 2.h), - Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), - ], - ).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: ServicesPage())); - }), - ], - ).paddingSymmetrical(24.w, 0.h), - SizedBox( - height: 431.h, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getServiceCardsList.length, - shrinkWrap: true, - padding: EdgeInsets.only(left: 24.w, right: 24.w), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 1000), - child: SlideAnimation( - horizontalOffset: 100.0, - child: FadeInAnimation( - child: FadedLargeServiceCard( - serviceCardData: LandingPageData.getServiceCardsList[index], - image: LandingPageData.getServiceCardsList[index].icon, - title: LandingPageData.getServiceCardsList[index].title, - subtitle: LandingPageData.getServiceCardsList[index].subtitle, - icon: LandingPageData.getServiceCardsList[index].largeCardIcon, - ), - ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.w), ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.w), + ), + appState.isAuthenticated ? HabibWalletCard() : SizedBox(), + ], ), ), - appState.isAuthenticated ? HabibWalletCard() : SizedBox(), + (!insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown) + ? Container( + height: MediaQuery.paddingOf(context).top + 50.h, + decoration: ShapeDecoration( + color: AppColors.secondaryLightRedBorderColor, + shape: SmoothRectangleBorder( + side: BorderSide(width: 1, color: AppColors.primaryRedColor.withAlpha(20)), + // borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)), + smoothness: 1, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(0.h, 0.h), + Row( + children: [ + CustomButton( + text: LocaleKeys.updateInsurance.tr(context: context), + onPressed: () { + insuranceVM.setIsInsuranceUpdateDetailsLoading(true); + insuranceVM.getPatientInsuranceDetailsForUpdate( + appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); + showCommonBottomSheetWithoutHeight(context, + child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.secondaryLightRedBorderColor, + textColor: Colors.white, + fontSize: 12.f, + fontWeight: FontWeight.bold, + borderRadius: 8.r, + padding: EdgeInsets.fromLTRB(15, 0, 15, 0), + height: 36.h, + ).paddingSymmetrical(12.h, 0.h), + Icon(Icons.close, color: AppColors.primaryRedColor).onPress(() { + insuranceVM.setIsInsuranceExpiryBannerShown(false); + }), + ], + ), + ], + ), + SizedBox( + height: 10.h, + ) + ], + ).paddingSymmetrical(24.h, 0.h), + ) + : SizedBox.shrink() ], - ), - ), + ); + }), ), ); } @@ -775,7 +876,7 @@ class _LandingPageState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 20.h, - hasShadow: false, + hasShadow: true, side: BorderSide( color: Utils.getCardBorderColor(currentStatus), width: 2.w, @@ -1006,6 +1107,7 @@ class _LandingPageState extends State { color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true, + hasDenseShadow: true ), child: AppointmentCard( patientAppointmentHistoryResponseModel: appointment, diff --git a/lib/presentation/home/widgets/large_service_card.dart b/lib/presentation/home/widgets/large_service_card.dart index 7c86dd09..b89e58c5 100644 --- a/lib/presentation/home/widgets/large_service_card.dart +++ b/lib/presentation/home/widgets/large_service_card.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -92,12 +93,12 @@ class LargeServiceCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ serviceCardData.title.tr(context: context).toText14(isBold: true, color: AppColors.textColor), - serviceCardData.subtitle.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + serviceCardData.subtitle.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight, maxLine: 2), ], ), ), ], - ).paddingSymmetrical(16.w, 0.h).expanded, + ).paddingSymmetrical(8.w, 0.h).expanded, CustomButton( text: serviceCardData.isBold ? LocaleKeys.visitPharmacyOnline.tr(context: context) : LocaleKeys.bookNow.tr(context: context), onPressed: () { @@ -177,7 +178,9 @@ class FadedLargeServiceCard extends StatelessWidget { children: [ ClipRRect( borderRadius: BorderRadius.circular(24.r), - child: Image.asset(serviceCardData.largeCardIcon, fit: BoxFit.cover, width: 520.w, height: 250.h), + child: Transform.flip( + flipX: getIt.get().isArabic(), + child: Image.asset(serviceCardData.largeCardIcon, fit: BoxFit.cover, width: 520.w, height: 250.h)), ), Positioned( top: 0, @@ -216,11 +219,14 @@ class FadedLargeServiceCard extends StatelessWidget { ), child: Padding( padding: EdgeInsets.all(8.h), - child: Utils.buildSvgWithAssets( - icon: serviceCardData.icon, - iconColor: serviceCardData.iconColor, - fit: BoxFit.contain, - applyThemeColor: false + child: Transform.flip( + flipX: getIt.get().isArabic(), + child: Utils.buildSvgWithAssets( + icon: serviceCardData.icon, + iconColor: serviceCardData.iconColor, + fit: BoxFit.contain, + applyThemeColor: false + ), ), ), ), @@ -229,10 +235,10 @@ class FadedLargeServiceCard extends StatelessWidget { ], ), SizedBox(height: 10.h), - serviceCardData.subtitle.tr(context: context).toText14(weight: FontWeight.w500, color: AppColors.blackBgColor, letterSpacing: 0), + serviceCardData.subtitle.tr(context: context).toText14(weight: FontWeight.w500, color: AppColors.blackBgColor, letterSpacing: 0, maxlines: 2), SizedBox(height: 12.h), CustomButton( - text: serviceCardData.isBold ? "Visit Pharmacy Online".needTranslation : LocaleKeys.bookNow.tr(context: context), + text: serviceCardData.isBold ? LocaleKeys.visitPharmacyOnline.tr(context: context) : LocaleKeys.bookNow.tr(context: context), onPressed: () { handleOnTap(); }, @@ -255,6 +261,7 @@ class FadedLargeServiceCard extends StatelessWidget { case "livecare": { getIt.get().onTabChanged(1); + getIt.get().setIsLiveCareSelectedFromHomePage(true); Navigator.of(getIt.get().navigatorKey.currentContext!).push( CustomPageRoute( page: BookAppointmentPage(), diff --git a/lib/presentation/home/widgets/welcome_widget.dart b/lib/presentation/home/widgets/welcome_widget.dart index 4ccc126a..9b613e64 100644 --- a/lib/presentation/home/widgets/welcome_widget.dart +++ b/lib/presentation/home/widgets/welcome_widget.dart @@ -1,10 +1,20 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; +import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dart'; +import 'package:smooth_corner/smooth_corner.dart'; class WelcomeWidget extends StatelessWidget { final String name; @@ -20,33 +30,41 @@ class WelcomeWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(30), - child: Row( - mainAxisSize: MainAxisSize.min, - spacing: 8.h, - children: [ - Image.asset(imageUrl, width: 40, height: 40), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 4.h, + return Column( + children: [ + InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(30), + child: Row( mainAxisSize: MainAxisSize.min, + spacing: 8.h, children: [ - LocaleKeys.welcome.tr(context: context).toText14(color: AppColors.greyTextColor, height: 1, weight: FontWeight.w500), - Row( + Icon(Icons.menu, color: AppColors.textColor).onPress(() { + Navigator.of(context).push(springPageRoute(ProfileSettings())); + }), + Image.asset(imageUrl, width: 40, height: 40), + Column( + crossAxisAlignment: CrossAxisAlignment.start, spacing: 4.h, - crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - Flexible(child: name.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1, height: 1)), - Icon(Icons.keyboard_arrow_down, size: 20, color: AppColors.greyTextColor), + LocaleKeys.welcome.tr(context: context).toText14(color: AppColors.greyTextColor, height: 1, weight: FontWeight.w500), + Row( + spacing: 4.h, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Flexible(child: name.toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1, height: 1, isEnglishOnly: true)), + // Icon(Icons.keyboard_arrow_down, size: 20, color: AppColors.greyTextColor), + Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, height: 22.h, width: 22.w) + ], + ), ], - ), + ).expanded, ], - ).expanded, - ], - ), + ), + ), + ], ); } } diff --git a/lib/presentation/home_health_care/hhc_procedures_page.dart b/lib/presentation/home_health_care/hhc_procedures_page.dart index c420742c..88766ea0 100644 --- a/lib/presentation/home_health_care/hhc_procedures_page.dart +++ b/lib/presentation/home_health_care/hhc_procedures_page.dart @@ -8,6 +8,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; @@ -33,11 +34,13 @@ class HhcProceduresPage extends StatefulWidget { } class _HhcProceduresPageState extends State { + + late AppState appState; + @override void initState() { super.initState(); final HmgServicesViewModel hmgServicesViewModel = context.read(); - final AppState appState = getIt.get(); scheduleMicrotask(() async { final user = appState.getAuthenticatedUser(); @@ -445,11 +448,12 @@ class _HhcProceduresPageState extends State { @override Widget build(BuildContext context) { + appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.homeHealthCare.tr(context: context), - history: () => Navigator.of(context).push(CustomPageRoute(page: HhcOrderDetailPage(), direction: AxisDirection.up)), + history: () => appState.isAuthenticated ? Navigator.of(context).push(CustomPageRoute(page: HhcOrderDetailPage(), direction: AxisDirection.up)) : null, bottomChild: Consumer( builder: (BuildContext context, HmgServicesViewModel hmgServicesViewModel, Widget? child) { if (hmgServicesViewModel.isHhcOrdersLoading || hmgServicesViewModel.isHhcServicesLoading) { @@ -467,8 +471,14 @@ class _HhcProceduresPageState extends State { padding: EdgeInsets.all(24.w), child: CustomButton( borderWidth: 0, - text: LocaleKeys.createNewRequest.tr(context: context), - onPressed: () => _buildServicesListBottomsSheet(hmgServicesViewModel.hhcServicesList), + text: appState.isAuthenticated ? LocaleKeys.createNewRequest.tr(context: context) : LocaleKeys.loginToUseService.tr(context: context), + onPressed: () { + if(appState.isAuthenticated) { + _buildServicesListBottomsSheet(hmgServicesViewModel.hhcServicesList); + } else { + getIt().onLoginPressed(); + } + }, textColor: AppColors.whiteColor, borderRadius: 12.r, borderColor: Colors.transparent, @@ -492,12 +502,13 @@ class _HhcProceduresPageState extends State { } else { return Column( children: [ + appState.isAuthenticated ? Center( child: Utils.getNoDataWidget( context, noDataText: LocaleKeys.youHaveNoPendingRequests.tr(context: context), ), - ), + ) : LocaleKeys.homeHealthCareText.tr(context: context).toText18(weight: FontWeight.w500).paddingSymmetrical(24.h, 24.h), ], ); } diff --git a/lib/presentation/insurance/insurance_approval_details_page.dart b/lib/presentation/insurance/insurance_approval_details_page.dart index a074a8d8..7a69e088 100644 --- a/lib/presentation/insurance/insurance_approval_details_page.dart +++ b/lib/presentation/insurance/insurance_approval_details_page.dart @@ -63,8 +63,8 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { ), AppCustomChipWidget( labelText: appState.isArabic() ? insuranceApprovalResponseModel.isInOutPatientDescriptionN! : insuranceApprovalResponseModel.isInOutPatientDescription!, - backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - textColor: AppColors.primaryRedColor, + backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), + textColor: AppColors.warningColorYellow, ), ], ), diff --git a/lib/presentation/insurance/insurance_approvals_page.dart b/lib/presentation/insurance/insurance_approvals_page.dart index 52f8b1f6..feac2e3a 100644 --- a/lib/presentation/insurance/insurance_approvals_page.dart +++ b/lib/presentation/insurance/insurance_approvals_page.dart @@ -51,7 +51,7 @@ class _InsuranceApprovalsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ ListView.separated( - padding: EdgeInsets.only(top: 24.h), + padding: EdgeInsets.only(top: 12.h), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), itemCount: insuranceVM.isInsuranceApprovalsLoading diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart index 519d8306..35e3acb8 100644 --- a/lib/presentation/insurance/insurance_home_page.dart +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -48,11 +48,11 @@ class _InsuranceHomePageState extends State { insuranceViewModel = Provider.of(context, listen: false); return CollapsingListView( title: "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}", - history: () { - insuranceViewModel.setIsInsuranceHistoryLoading(true); - insuranceViewModel.getPatientInsuranceCardHistory(); - showCommonBottomSheetWithoutHeight(context, child: InsuranceHistory(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); - }, + // history: () { + // // insuranceViewModel.setIsInsuranceHistoryLoading(true); + // // insuranceViewModel.getPatientInsuranceCardHistory(); + // showCommonBottomSheetWithoutHeight(context, child: InsuranceHistory(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); + // }, child: SingleChildScrollView( child: Consumer(builder: (context, insuranceVM, child) { return Column( @@ -66,12 +66,23 @@ class _InsuranceHomePageState extends State { isLoading: true, ).paddingSymmetrical(24.h, 24.h) : insuranceVM.patientInsuranceList.isNotEmpty - ? Padding( - padding: EdgeInsets.only(top: 24.h), - child: PatientInsuranceCard( - insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, - isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))) - .paddingSymmetrical(24.w, 0.h), + ? ListView.builder( + shrinkWrap: true, + padding: EdgeInsets.all(16.h), + physics: const BouncingScrollPhysics(), + itemBuilder: (context, index) { + return Column( + children: [ + PatientInsuranceCard( + insuranceCardDetailsModel: insuranceVM.patientInsuranceList[index], + isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))), + SizedBox( + height: 12.h, + ) + ], + ); + }, + itemCount: insuranceVM.patientInsuranceList.length, ) : Padding( padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.12), diff --git a/lib/presentation/insurance/widgets/insurance_approval_card.dart b/lib/presentation/insurance/widgets/insurance_approval_card.dart index 588f9887..8f5f7cfb 100644 --- a/lib/presentation/insurance/widgets/insurance_approval_card.dart +++ b/lib/presentation/insurance/widgets/insurance_approval_card.dart @@ -9,8 +9,11 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_approval_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/insurance_approval_details_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; class InsuranceApprovalCard extends StatelessWidget { InsuranceApprovalCard({super.key, required this.insuranceApprovalResponseModel, required this.isLoading, required this.appState}); @@ -64,8 +67,8 @@ class InsuranceApprovalCard extends StatelessWidget { : appState.isArabic() ? insuranceApprovalResponseModel.isInOutPatientDescriptionN! : insuranceApprovalResponseModel.isInOutPatientDescription!, - backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - textColor: AppColors.primaryRedColor, + backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), + textColor: AppColors.warningColorYellow, ).toShimmer2(isShow: isLoading), ], ).toShimmer2(isShow: isLoading), @@ -111,12 +114,30 @@ class InsuranceApprovalCard extends StatelessWidget { ), ], ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Transform.flip( - flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon_small, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)), - ], + SizedBox( + height: 12.h, + ), + CustomButton( + text: LocaleKeys.viewDetails.tr(context: context), + onPressed: () async { + Navigator.of(context).push( + CustomPageRoute( + page: InsuranceApprovalDetailsPage(insuranceApprovalResponseModel: insuranceApprovalResponseModel), + ), + ); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: isTablet || isFoldable ? 46.h : 40.h, + // height: 40.h, + // icon: AppAssets.insurance, + // iconColor: AppColors.primaryRedColor, + iconSize: 16.h, ).toShimmer2(isShow: isLoading), ], ), diff --git a/lib/presentation/insurance/widgets/insurance_update_details_card.dart b/lib/presentation/insurance/widgets/insurance_update_details_card.dart index 35be7f9e..770f4da2 100644 --- a/lib/presentation/insurance/widgets/insurance_update_details_card.dart +++ b/lib/presentation/insurance/widgets/insurance_update_details_card.dart @@ -3,11 +3,13 @@ import 'package:flutter/cupertino.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -75,21 +77,18 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { ], ), SizedBox(height: 8.h), - Row( + Wrap( + direction: Axis.horizontal, + spacing: 4.h, + runSpacing: 4.h, children: [ - Wrap( - direction: Axis.horizontal, - spacing: 4.h, - runSpacing: 4.h, - children: [ - AppCustomChipWidget( - icon: AppAssets.doctor_calendar_icon, - labelText: "${LocaleKeys.expiryOn.tr(context: context)} ${insuranceViewModel.patientInsuranceUpdateResponseModel!.effectiveTo}", - ), - AppCustomChipWidget( - labelText: "Member ID: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.memberID!}", - ), - ], + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: + "${LocaleKeys.expiryOn.tr(context: context)} ${DateUtil.formatDateToDate(DateTime.parse(insuranceViewModel.patientInsuranceUpdateResponseModel!.effectiveTo!), false)}", + ), + AppCustomChipWidget( + labelText: "Member ID: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.memberID!}", ), ], ), @@ -106,39 +105,51 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { iconSize: 20.w, text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", onPressed: () { - LoaderBottomSheet.showLoader(); - insuranceViewModel.updatePatientInsuranceCard( - patientID: appState.getAuthenticatedUser()!.patientId!, - patientType: appState.getAuthenticatedUser()!.patientType!, - patientIdentificationID: appState.getAuthenticatedUser()!.patientIdentificationNo!, - mobileNo: appState.getAuthenticatedUser()!.mobileNumber!, - insuranceCardImage: "", - onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - title: LocaleKeys.success.tr(context: context), - context, - child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr(context: context)), - callBackFunc: () { - Navigator.pop(context); - }, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - title: LocaleKeys.notice.tr(context: context), - context, - child: Utils.getErrorWidget(loadingText: err.toString()), - callBackFunc: () { - Navigator.pop(context); + if (insuranceViewModel.patientInsuranceUpdateResponseModel != null) { + LoaderBottomSheet.showLoader(); + getIt().sendPatientUpdateRequest(onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + insuranceViewModel.setIsInsuranceDataToBeLoaded(true); + insuranceViewModel.initInsuranceProvider(); + Navigator.pop(context); + }, onError: (err) { + insuranceViewModel.updatePatientInsuranceCard( + patientID: appState.getAuthenticatedUser()!.patientId!, + patientType: appState.getAuthenticatedUser()!.patientType!, + patientIdentificationID: appState.getAuthenticatedUser()!.patientIdentificationNo!, + mobileNo: appState.getAuthenticatedUser()!.mobileNumber!, + insuranceCardImage: "", + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.success.tr(context: context), + context, + child: Utils.getSuccessWidget(loadingText: LocaleKeys.insuranceRequestSubmittedSuccessfully.tr(context: context)), + callBackFunc: () { + Navigator.pop(context); + }, + isFullScreen: false, + isCloseButtonVisible: false, + ); + // Future.delayed(Duration(milliseconds: 2000)).then((value) async { + // Navigator.pop(context); + // }); }, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () { + Navigator.pop(context); + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }); + } }, backgroundColor: insuranceViewModel.patientInsuranceUpdateResponseModel != null ? AppColors.successColor : AppColors.lightGrayBGColor, borderColor: AppColors.successColor.withOpacity(0.01), diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index 531341b5..1607a7c6 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -52,57 +52,69 @@ class PatientInsuranceCard extends StatelessWidget { children: [ SizedBox( width: MediaQuery.of(context).size.width * 0.4, - child: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText18(isBold: true, textOverflow: TextOverflow.clip)), - LocaleKeys.policyNumber.tr(namedArgs: {'number': insuranceCardDetailsModel.insurancePolicyNo ?? ''}, context: context).toText12(isBold: true, color: AppColors.lightGrayColor), + child: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText18(isBold: true, textOverflow: TextOverflow.clip, isEnglishOnly: true)), + Row( + children: [ + "${LocaleKeys.policyNumber.tr(context: context)}${insuranceCardDetailsModel.insurancePolicyNo}".toText12(isBold: true, color: AppColors.lightGrayColor), + ], + ), ], ), AppCustomChipWidget( - icon: insuranceViewModel.isInsuranceExpired + icon: isCurrentPatientInsuranceExpired(insuranceCardDetailsModel.cardValidTo!) ? AppAssets.cancel_circle_icon : insuranceViewModel.isInsuranceActive ? AppAssets.insurance_active_icon : AppAssets.alertSquare, - labelText: insuranceViewModel.isInsuranceExpired + labelText: isCurrentPatientInsuranceExpired(insuranceCardDetailsModel.cardValidTo!) ? LocaleKeys.insuranceExpired.tr(context: context) : insuranceViewModel.isInsuranceActive ? LocaleKeys.insuranceActive.tr(context: context) : LocaleKeys.insuranceInActive.tr(context: context), - iconColor: insuranceViewModel.isInsuranceExpired + iconColor: isCurrentPatientInsuranceExpired(insuranceCardDetailsModel.cardValidTo!) ? AppColors.primaryRedColor : insuranceViewModel.isInsuranceActive ? AppColors.successColor : AppColors.warningColorYellow, - textColor: insuranceViewModel.isInsuranceExpired + textColor: isCurrentPatientInsuranceExpired(insuranceCardDetailsModel.cardValidTo!) ? AppColors.primaryRedColor : insuranceViewModel.isInsuranceActive ? AppColors.successColor : AppColors.warningColorYellow, iconSize: 12.w, deleteIcon: insuranceViewModel.isInsuranceActive ? null : AppAssets.forward_chevron_icon, - deleteIconColor: AppColors.warningColorYellow, + deleteIconColor: insuranceViewModel.isInsuranceExpired + ? AppColors.primaryRedColor + : insuranceViewModel.isInsuranceActive + ? AppColors.successColor + : AppColors.warningColorYellow, deleteIconHasColor: true, onChipTap: () { if (!insuranceViewModel.isInsuranceActive) { - 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, - ); + insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true); + insuranceViewModel.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: insuranceViewModel.isInsuranceExpired + backgroundColor: isCurrentPatientInsuranceExpired(insuranceCardDetailsModel.cardValidTo!) ? AppColors.primaryRedColor.withOpacity(0.1) : insuranceViewModel.isInsuranceActive ? AppColors.successColor.withOpacity(0.1) @@ -122,7 +134,23 @@ class PatientInsuranceCard extends StatelessWidget { ), SizedBox(height: 12.h), insuranceCardDetailsModel.groupName!.toText12(isBold: true), - insuranceCardDetailsModel.companyName!.toText12(isBold: true), + Row( + children: [ + insuranceCardDetailsModel.companyName!.toText12(isBold: true), + SizedBox( + width: 6.h, + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 6.h, vertical: 4.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.infoColor, + borderRadius: 50.r, + ), + child: (insuranceCardDetailsModel.subCategoryDesc!.length > 5 ? insuranceCardDetailsModel.subCategoryDesc!.substring(0, 12) : insuranceCardDetailsModel.subCategoryDesc!) + .toText8(isBold: true, color: AppColors.whiteColor), + ), + ], + ), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, @@ -142,9 +170,10 @@ class PatientInsuranceCard extends StatelessWidget { isInsuranceExpired ? CustomButton( icon: AppAssets.update_insurance_card_icon, - iconColor: AppColors.successColor, + iconColor: AppColors.warningColorYellow, iconSize: 15.h, - text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", + // text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", + text: LocaleKeys.verifyInsurance.tr(context: context), onPressed: () { insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true); insuranceViewModel.getPatientInsuranceDetailsForUpdate( @@ -157,9 +186,9 @@ class PatientInsuranceCard extends StatelessWidget { isCloseButtonVisible: false, isFullScreen: false); }, - backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), - borderColor: AppColors.bgGreenColor.withOpacity(0.0), - textColor: AppColors.bgGreenColor, + backgroundColor: AppColors.warningColorYellow.withOpacity(0.20), + borderColor: AppColors.warningColorYellow.withOpacity(0.0), + textColor: AppColors.warningColorYellow, fontSize: 14, fontWeight: FontWeight.w500, borderRadius: 12, @@ -172,4 +201,10 @@ class PatientInsuranceCard extends StatelessWidget { ), ).paddingSymmetrical(0.h, 0.h); } + + bool isCurrentPatientInsuranceExpired(String cardValidTo) { + return DateTime.now().isAfter( + DateUtil.convertStringToDate(cardValidTo), + ); + } } diff --git a/lib/presentation/lab/lab_order_by_test.dart b/lib/presentation/lab/lab_order_by_test.dart index 1b5db920..ac6f0f3b 100644 --- a/lib/presentation/lab/lab_order_by_test.dart +++ b/lib/presentation/lab/lab_order_by_test.dart @@ -59,7 +59,7 @@ class LabOrderByTest extends StatelessWidget { icon: AppAssets.view_report_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, - text: LocaleKeys.viewReport.tr(context: context), + text: LocaleKeys.viewResults.tr(context: context), onPressed: () { onTap(); }, diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 15461659..900477e4 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1 +1 @@ -import 'dart:async'; import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_toolbar.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import 'alphabeticScroll.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return CollapsingListView( title: LocaleKeys.labResults.tr(context: context), search: () async { if (labProvider.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: labProvider.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; labProvider.filterLabReports(value); } } }, child: Consumer( builder: (context, labViewModel, child) { return SingleChildScrollView( physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.all(24.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, LocaleKeys.byVisit.tr()), CustomTabBarModel(null, LocaleKeys.byTest.tr()), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), ), ], ), if (activeIndex == 0) Padding( padding: EdgeInsets.symmetric(vertical: 10.h), child: Row( children: [ CustomButton( text: LocaleKeys.byClinic.tr(context: context), onPressed: () { labViewModel.setIsSortByClinic(true); }, backgroundColor: labViewModel.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, borderColor: labViewModel.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), textColor: labViewModel.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, fontSize: 12.f, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), SizedBox(width: 8.h), CustomButton( text: LocaleKeys.byHospital.tr(context: context), onPressed: () { labViewModel.setIsSortByClinic(false); }, backgroundColor: labViewModel.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, borderColor: labViewModel.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, textColor: labViewModel.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 10, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), ], ), ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? Column( children: [ AppCustomChipWidget( labelText: selectedFilterText!, backgroundColor: AppColors.alertColor, textColor: AppColors.whiteColor, deleteIcon: AppAssets.close_bottom_sheet_icon, deleteIconColor: AppColors.whiteColor, deleteIconHasColor: true, onDeleteTap: () { selectedFilterText = ""; labProvider.filterLabReports(""); }, ), SizedBox(height: 8.h), ], ) : SizedBox(), activeIndex == 0 ? // By Visit - show grouped view when available labViewModel.isLabOrdersLoading ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: 5, itemBuilder: (context, index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ), ) : (labViewModel.patientLabOrdersViewList.isNotEmpty ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: labViewModel.patientLabOrdersViewList.length, itemBuilder: (context, index) { final group = labViewModel.patientLabOrdersViewList[index]; final isExpanded = expandedIndex == index; return AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: AnimatedContainer( 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: InkWell( onTap: () { setState(() { expandedIndex = isExpanded ? null : index; }); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ AppCustomChipWidget(labelText: "${group.length} ${LocaleKeys.results.tr(context: context)}"), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), Text( labViewModel.isSortByClinic ? (group.first.clinicDescription ?? 'Unknown') : (group.first.projectName ?? 'Unknown'), style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis, ), ], ), ), AnimatedSwitcher( duration: Duration(milliseconds: 500), switchInCurve: Curves.easeIn, switchOutCurve: Curves.easeOut, transitionBuilder: (Widget child, Animation animation) { return FadeTransition( opacity: animation, child: SizeTransition( sizeFactor: animation, axisAlignment: 0.0, child: child, ), ); }, child: isExpanded ? Container( key: ValueKey(index), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 0.h), child: ListView.separated( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemBuilder: (cxt, index) { PatientLabOrdersResponseModel order = group[index]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisSize: MainAxisSize.min, children: [ Image.network( order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", width: 24.w, height: 24.h, fit: BoxFit.cover, ).circle(100), SizedBox(width: 8.h), Expanded(child: (order.doctorName ?? order.doctorNameEnglish ?? "").toString().toText14(weight: FontWeight.w500)), ], ), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( labelText: ("${LocaleKeys.orderNo.tr(context: context)}: ${order.orderNo!}"), ), AppCustomChipWidget( labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), ), AppCustomChipWidget( labelText: labViewModel.isSortByClinic ? (order.projectName ?? "") : (order.clinicDescription ?? ""), ), ], ), // Row( // children: [ // CustomButton( // text: ("Order No: ".needTranslation + order.orderNo!), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // SizedBox(width: 8.h), // CustomButton( // text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // ], // ), // SizedBox(height: 8.h), // Row( // children: [ // CustomButton( // text: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""), // onPressed: () {}, // backgroundColor: AppColors.greyColor, // borderColor: AppColors.greyColor, // textColor: AppColors.blackColor, // fontSize: 10, // fontWeight: FontWeight.w500, // borderRadius: 8, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 24.h, // ), // ], // ), SizedBox(height: 12.h), Row( children: [ Expanded(flex: 2, child: SizedBox()), // Expanded( // flex: 1, // child: Container( // height: 40.h, // width: 40.w, // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( // color: AppColors.textColor, // borderRadius: 12, // ), // child: Padding( // padding: EdgeInsets.all(12.h), // child: Transform.flip( // flipX: _appState.isArabic(), // child: Utils.buildSvgWithAssets( // icon: AppAssets.forward_arrow_icon_small, // iconColor: AppColors.whiteColor, // fit: BoxFit.contain, // ), // ), // ), // ).onPress(() { // model.currentlySelectedPatientOrder = order; // labProvider.getPatientLabResultByHospital(order); // labProvider.getPatientSpecialResult(order); // Navigator.of(context).push( // CustomPageRoute(page: LabResultByClinic(labOrder: order)), // ); // }), // ) Expanded( flex: 2, child: CustomButton( icon: AppAssets.view_report_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, text: LocaleKeys.viewResults.tr(context: context), onPressed: () { labViewModel.currentlySelectedPatientOrder = order; labProvider.getPatientLabResultByHospital(order); labProvider.getPatientSpecialResult(order); Navigator.of(context).push( CustomPageRoute(page: LabResultByClinic(labOrder: order)), ); }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, fontSize: 14, fontWeight: FontWeight.w500, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 40.h, ), ) ], ), // SizedBox(height: 12.h), // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), // SizedBox(height: 12.h), ], ).paddingOnly(top: 16, bottom: 16); }, separatorBuilder: (cxt, index) => Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), itemCount: group.length)) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noLabResults.tr(context: context))) : // By Test or other tabs keep existing behavior (labViewModel.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) : AlphabeticScroll( alpahbetsAvailable: labViewModel.indexedCharacterForUniqueTest, details: labViewModel.uniqueTestsList, labViewModel: labViewModel, rangeViewModel: rangeViewModel, appState: _appState, ) ], )); }, ), ); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file +import 'dart:async'; import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_toolbar.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import 'alphabeticScroll.dart'; import 'dart:ui' as ui; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return CollapsingListView( title: LocaleKeys.labResults.tr(context: context), search: () async { if (labProvider.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: labProvider.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; labProvider.filterLabReports(value); } } }, child: Consumer( builder: (context, labViewModel, child) { return SingleChildScrollView( physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.all(24.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, LocaleKeys.byVisit.tr()), CustomTabBarModel(null, LocaleKeys.byTest.tr()), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), ), ], ), if (activeIndex == 0) Padding( padding: EdgeInsets.symmetric(vertical: 10.h), child: Row( children: [ // CustomButton( // text: LocaleKeys.byClinic.tr(context: context), // onPressed: () { // labViewModel.setIsSortByClinic(true); // }, // backgroundColor: labViewModel.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, // borderColor: labViewModel.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), // textColor: labViewModel.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, // fontSize: 12.f, // fontWeight: FontWeight.w500, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, // ), // SizedBox(width: 8.h), // CustomButton( // text: LocaleKeys.byHospital.tr(context: context), // onPressed: () { // labViewModel.setIsSortByClinic(false); // }, // backgroundColor: labViewModel.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, // borderColor: labViewModel.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, // textColor: labViewModel.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, // fontSize: 12, // fontWeight: FontWeight.w500, // borderRadius: 10, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, // ), ], ), ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? Column( children: [ AppCustomChipWidget( labelText: selectedFilterText!, backgroundColor: AppColors.alertColor, textColor: AppColors.whiteColor, deleteIcon: AppAssets.close_bottom_sheet_icon, deleteIconColor: AppColors.whiteColor, deleteIconHasColor: true, onDeleteTap: () { selectedFilterText = ""; labProvider.filterLabReports(""); }, ), SizedBox(height: 8.h), ], ) : SizedBox(), activeIndex == 0 ? // By Visit - show grouped view when available labViewModel.isLabOrdersLoading ? ListView.builder( shrinkWrap: true, physics: AlwaysScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: 5, itemBuilder: (context, index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ), ) : (labViewModel.patientLabOrders.isNotEmpty ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: labViewModel.patientLabOrders.length, itemBuilder: (context, index) { final group = labViewModel.patientLabOrders[index]; final isExpanded = expandedIndex == index; return AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: AnimatedContainer( 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: InkWell( onTap: () { setState(() { expandedIndex = isExpanded ? null : index; }); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ AppCustomChipWidget( labelText: "${group.testDetails!.length} ${LocaleKeys.tests.tr(context: context)}", backgroundColor: AppColors.successColor.withOpacity(0.1), textColor: AppColors.successColor, ), AppCustomChipWidget( labelText: "${_appState.isArabic() ? group.isInOutPatientDescriptionN : group.isInOutPatientDescription}", backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), textColor: AppColors.warningColorYellow, ) ], ), Icon(isExpanded ? Icons.expand_less : Icons.expand_more), ], ), SizedBox(height: 8.h), Row( mainAxisSize: MainAxisSize.min, children: [ Image.network( group.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", width: 24.w, height: 24.h, fit: BoxFit.cover, ).circle(100), SizedBox(width: 8.h), Expanded(child: (group.doctorName ?? group.doctorNameEnglish ?? "").toString().toText14(weight: FontWeight.w500)), ], ), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, spacing: 4.h, runSpacing: 4.h, children: [ Directionality( textDirection: ui.TextDirection.ltr, child: AppCustomChipWidget( labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(group.orderDate ?? ""), false), isEnglishOnly: true, )), AppCustomChipWidget( labelText: (group.projectName ?? ""), ), AppCustomChipWidget( labelText: (group.clinicDescription ?? ""), ), ], ), ], ), ), AnimatedSwitcher( duration: Duration(milliseconds: 500), switchInCurve: Curves.easeIn, switchOutCurve: Curves.easeOut, transitionBuilder: (Widget child, Animation animation) { return FadeTransition( opacity: animation, child: SizeTransition( sizeFactor: animation, axisAlignment: 0.0, child: child, ), ); }, child: isExpanded ? Container( key: ValueKey(index), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 0.h), child: Column( children: [ ListView.separated( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemBuilder: (cxt, index) { PatientLabOrdersResponseModel order = group; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ "• ${order.testDetails![index].description!}".toText14(weight: FontWeight.w500), SizedBox(height: 4.h), order.testDetails![index].testDescriptionEn!.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), // Row( // mainAxisSize: MainAxisSize.min, // children: [ // Image.network( // order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", // width: 24.w, // height: 24.h, // fit: BoxFit.cover, // ).circle(100), // SizedBox(width: 8.h), // Expanded(child: (order.doctorName ?? order.doctorNameEnglish ?? "").toString().toText14(weight: FontWeight.w500)), // ], // ), // SizedBox(height: 8.h), // Wrap( // direction: Axis.horizontal, // spacing: 4.h, // runSpacing: 4.h, // children: [ // AppCustomChipWidget( // labelText: ("${LocaleKeys.orderNo.tr(context: context)}: ${order.orderNo!}"), isEnglishOnly: true, // ), // Directionality( // textDirection: ui.TextDirection.ltr, // child: AppCustomChipWidget( // labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), // isEnglishOnly: true, // )), // AppCustomChipWidget( // labelText: labViewModel.isSortByClinic ? (order.projectName ?? "") : (order.clinicDescription ?? ""), // ), // ], // ), // // Row( // // children: [ // // CustomButton( // // text: ("Order No: ".needTranslation + order.orderNo!), // // onPressed: () {}, // // backgroundColor: AppColors.greyColor, // // borderColor: AppColors.greyColor, // // textColor: AppColors.blackColor, // // fontSize: 10, // // fontWeight: FontWeight.w500, // // borderRadius: 8, // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // // height: 24.h, // // ), // // SizedBox(width: 8.h), // // CustomButton( // // text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), // // onPressed: () {}, // // backgroundColor: AppColors.greyColor, // // borderColor: AppColors.greyColor, // // textColor: AppColors.blackColor, // // fontSize: 10, // // fontWeight: FontWeight.w500, // // borderRadius: 8, // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // // height: 24.h, // // ), // // ], // // ), // // SizedBox(height: 8.h), // // Row( // // children: [ // // CustomButton( // // text: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""), // // onPressed: () {}, // // backgroundColor: AppColors.greyColor, // // borderColor: AppColors.greyColor, // // textColor: AppColors.blackColor, // // fontSize: 10, // // fontWeight: FontWeight.w500, // // borderRadius: 8, // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // // height: 24.h, // // ), // // ], // // ), // SizedBox(height: 12.h), // Row( // children: [ // Expanded(flex: 2, child: SizedBox()), // // Expanded( // // flex: 1, // // child: Container( // // height: 40.h, // // width: 40.w, // // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( // // color: AppColors.textColor, // // borderRadius: 12, // // ), // // child: Padding( // // padding: EdgeInsets.all(12.h), // // child: Transform.flip( // // flipX: _appState.isArabic(), // // child: Utils.buildSvgWithAssets( // // icon: AppAssets.forward_arrow_icon_small, // // iconColor: AppColors.whiteColor, // // fit: BoxFit.contain, // // ), // // ), // // ), // // ).onPress(() { // // model.currentlySelectedPatientOrder = order; // // labProvider.getPatientLabResultByHospital(order); // // labProvider.getPatientSpecialResult(order); // // Navigator.of(context).push( // // CustomPageRoute(page: LabResultByClinic(labOrder: order)), // // ); // // }), // // ) // // Expanded( // flex: 2, // child: CustomButton( // icon: AppAssets.view_report_icon, // iconColor: AppColors.primaryRedColor, // iconSize: 16.h, // text: LocaleKeys.viewResults.tr(context: context), // onPressed: () { // labViewModel.currentlySelectedPatientOrder = order; // labProvider.getPatientLabResultByHospital(order); // labProvider.getPatientSpecialResult(order); // Navigator.of(context).push( // CustomPageRoute(page: LabResultByClinic(labOrder: order)), // ); // }, // backgroundColor: AppColors.secondaryLightRedColor, // borderColor: AppColors.secondaryLightRedColor, // textColor: AppColors.primaryRedColor, // fontSize: 14, // fontWeight: FontWeight.w500, // borderRadius: 12, // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), // height: 40.h, // ), // ) // ], // ), // SizedBox(height: 12.h), // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), // SizedBox(height: 12.h), ], ).paddingOnly(top: 16, bottom: 16); }, separatorBuilder: (cxt, index) => Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), itemCount: group.testDetails!.length > 3 ? 3 : group.testDetails!.length), SizedBox(height: 16.h), CustomButton( text: "${LocaleKeys.viewResults.tr()} (${group.testDetails!.length})", onPressed: () { labProvider.currentlySelectedPatientOrder = group; labProvider.getPatientLabResultByHospital(group); labProvider.getPatientSpecialResult(group); Navigator.of(context).push( CustomPageRoute( page: LabResultByClinic(labOrder: group), ), ); }, backgroundColor: AppColors.infoColor.withAlpha(20), borderColor: AppColors.infoColor.withAlpha(0), textColor: AppColors.infoColor, fontSize: (isFoldable || isTablet) ? 12.f : 14.f, fontWeight: FontWeight.w500, borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), height: 40.h, iconSize: 14.h, icon: AppAssets.view_report_icon, iconColor: AppColors.infoColor, ), SizedBox(height: 16.h), ], ), ) : SizedBox.shrink(), ), ], ), ), ), ), )); }, ) : Utils.getNoDataWidget(context, noDataText: LocaleKeys.noLabResults.tr(context: context))) : // By Test or other tabs keep existing behavior (labViewModel.isLabOrdersLoading) ? Column( children: List.generate( 5, (index) => LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, )), ) : AlphabeticScroll( alpahbetsAvailable: labViewModel.indexedCharacterForUniqueTest, details: labViewModel.uniqueTestsList, labViewModel: labViewModel, rangeViewModel: rangeViewModel, appState: _appState, ) ], )); }, ), ); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file diff --git a/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart b/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart index af6c97f8..c883c863 100644 --- a/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart +++ b/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart @@ -164,7 +164,7 @@ class LabResultByClinic extends StatelessWidget { padding: EdgeInsets.only(right: 4.w, left: 4.w), child: Utils.buildSvgWithAssets(icon: AppAssets.aiOverView, width: 16.h, height: 16.h, iconColor: Colors.white), ), - LocaleKeys.generateAiAnalysis.tr(context: context).toText16(isBold: true) + LocaleKeys.generateAiAnalysis.tr(context: context).toText16(isBold: true, color: Colors.white) ], ), ).paddingSymmetrical(24.h, 24.h).onPress(() async { diff --git a/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart b/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart index 7a477761..ebf802df 100644 --- a/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart +++ b/lib/presentation/lab/lab_result_via_clinic/ai_analysis_widget.dart @@ -2,11 +2,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_order_response_by_ai_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; class AiAnalysisWidget extends StatelessWidget { final LabOrderResponseByAi data; @@ -71,6 +74,16 @@ class AiAnalysisWidget extends StatelessWidget { ), ], ), + SizedBox(height: 16.h), + CustomButton( + height: 50.h, + text: LocaleKeys.close.tr(context: context), + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + onPressed: () { + getIt.get().closeAILabResultAnalysis(); + }, + ), ], ), ), diff --git a/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart b/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart index cf6ce97b..1a97d48d 100644 --- a/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart +++ b/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart @@ -114,15 +114,15 @@ class LabOrderResultItem extends StatelessWidget { ), CustomButton( icon: AppAssets.view_report_icon, - iconColor: AppColors.primaryRedColor, + iconColor: AppColors.infoColor, iconSize: 16.h, text: LocaleKeys.viewReport.tr(context: context), onPressed: () { onTap(); }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, + backgroundColor: AppColors.infoColor.withAlpha(20), + borderColor: AppColors.infoColor.withAlpha(0), + textColor: AppColors.infoColor, fontSize: 14, fontWeight: FontWeight.w500, borderRadius: 12, diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index d1ed1470..ded1e693 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -35,97 +35,99 @@ class LabResultDetails extends StatelessWidget { LabViewModel labViewModel = Provider.of(context, listen: false); final appState = getIt.get(); return Scaffold( - body: Column( - children: [ - Expanded( - child: CollapsingListView( - title: LocaleKeys.labResultDetails.tr(context: context), - // aiOverview: () async { - // final _dialogService = getIt.get(); - // await _dialogService.showCommonBottomSheetWithoutH( - // message: LocaleKeys.aiDisclaimer.tr(), - // label: LocaleKeys.consent.tr(), - // okLabel: LocaleKeys.acceptLbl.tr(), - // cancelLabel: LocaleKeys.rejectView.tr(), - // onOkPressed: () { - // context.pop(); - // labViewModel.getAiOverviewSingleLabResult(langId: appState.getLanguageID().toString(), recentLabResult: recentLabResult, loadingText: LocaleKeys.loadingAIAnalysis.tr(context: context)); - // }, - // onCancelPressed: () { - // context.pop(); - // }); - // }, - child: SingleChildScrollView( - child: Column( - spacing: 16.h, - children: [ - LabNameAndStatus(context), - getLabDescription(context), - LabGraph(context), - Selector( - selector: (_, model) => model.labOrderResponseByAi, - builder: (_, aiData, __) { - if (aiData != null) { - return AiAnalysisWidget(data: aiData).paddingOnly(bottom: 16.h); - } - return const SizedBox.shrink(); - }, - ), - ], - ).paddingAll(24.h), + body: Consumer(builder: (context, labVM, child) { + return Column( + children: [ + Expanded( + child: CollapsingListView( + title: LocaleKeys.labResultDetails.tr(context: context), + // aiOverview: () async { + // final _dialogService = getIt.get(); + // await _dialogService.showCommonBottomSheetWithoutH( + // message: LocaleKeys.aiDisclaimer.tr(), + // label: LocaleKeys.consent.tr(), + // okLabel: LocaleKeys.acceptLbl.tr(), + // cancelLabel: LocaleKeys.rejectView.tr(), + // onOkPressed: () { + // context.pop(); + // labViewModel.getAiOverviewSingleLabResult(langId: appState.getLanguageID().toString(), recentLabResult: recentLabResult, loadingText: LocaleKeys.loadingAIAnalysis.tr(context: context)); + // }, + // onCancelPressed: () { + // context.pop(); + // }); + // }, + child: SingleChildScrollView( + child: Column( + spacing: 16.h, + children: [ + LabNameAndStatus(context), + getLabDescription(context), + LabGraph(context), + Selector( + selector: (_, model) => model.labOrderResponseByAi, + builder: (_, aiData, __) { + if (aiData != null) { + return AiAnalysisWidget(data: aiData).paddingOnly(bottom: 16.h); + } + return const SizedBox.shrink(); + }, + ), + ], + ).paddingAll(24.h), + ), ), ), - ), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: true, - ), - child: Container( - height: 56.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12.r), - gradient: LinearGradient( - begin: Alignment.centerLeft, - end: Alignment.centerRight, - stops: [0.236, 1.0], // 53.6% and 100% - colors: [ - Color(0xFF8A38F5), // Transparent - Color(0xFFE20BBB), // Solid #F8F8F8 - ], - ), + labVM.labOrderResponseByAi == null ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: EdgeInsets.only(right: 4.w, left: 4.w), - child: Utils.buildSvgWithAssets(icon: AppAssets.aiOverView, width: 16.h, height: 16.h, iconColor: Colors.white), + child: Container( + height: 56.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.r), + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + stops: [0.236, 1.0], // 53.6% and 100% + colors: [ + Color(0xFF8A38F5), // Transparent + Color(0xFFE20BBB), // Solid #F8F8F8 + ], ), - LocaleKeys.generateAiAnalysis.tr(context: context).toText16(isBold: true) - ], - ), - ).paddingSymmetrical(24.h, 24.h).onPress(() async { - final _dialogService = getIt.get(); - await _dialogService.showCommonBottomSheetWithoutH( - message: LocaleKeys.aiDisclaimer.tr(), - label: LocaleKeys.consent.tr(), - okLabel: LocaleKeys.acceptLbl.tr(), - cancelLabel: LocaleKeys.rejectView.tr(), - onOkPressed: () { - context.pop(); - labViewModel.getAiOverviewSingleLabResult( - langId: appState.getLanguageID().toString(), recentLabResult: recentLabResult, loadingText: LocaleKeys.loadingAIAnalysis.tr(context: context)); - }, - onCancelPressed: () { - context.pop(); - }); - }), - ), - ], - ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.only(right: 4.w, left: 4.w), + child: Utils.buildSvgWithAssets(icon: AppAssets.aiOverView, width: 16.h, height: 16.h, iconColor: Colors.white), + ), + LocaleKeys.generateAiAnalysisResult.tr(context: context).toText16(isBold: true, color: Colors.white) + ], + ), + ).paddingSymmetrical(24.h, 24.h).onPress(() async { + final _dialogService = getIt.get(); + await _dialogService.showCommonBottomSheetWithoutH( + message: LocaleKeys.aiDisclaimer.tr(), + label: LocaleKeys.consent.tr(), + okLabel: LocaleKeys.acceptLbl.tr(), + cancelLabel: LocaleKeys.rejectView.tr(), + onOkPressed: () { + context.pop(); + labViewModel.getAiOverviewSingleLabResult( + langId: appState.getLanguageID().toString(), recentLabResult: recentLabResult, loadingText: LocaleKeys.loadingAIAnalysis.tr(context: context)); + }, + onCancelPressed: () { + context.pop(); + }); + }), + ) : SizedBox.shrink(), + ], + ); + }), ); } diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index f288af66..cddc4757 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -85,6 +85,8 @@ import '../../features/active_prescriptions/active_prescriptions_view_model.dart import '../prescriptions/prescription_detail_page.dart'; import 'widgets/medical_file_appointment_card.dart'; +import 'dart:ui' as ui; + class MedicalFilePage extends StatefulWidget { bool showBackIcon; @@ -197,18 +199,23 @@ class _MedicalFilePageState extends State { ], ), SizedBox(width: 4.h), - Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 22.h, width: 22.w) + Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, height: 22.h, width: 22.w) ], ).onPress(() { - DialogService dialogService = getIt.get(); - dialogService.showFamilyBottomSheetWithoutH( - label: LocaleKeys.familyTitle.tr(context: context), - message: "", - isShowManageButton: true, - onSwitchPress: (FamilyFileResponseModelLists profile) { - medicalFileViewModel.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); - }, - profiles: medicalFileViewModel.patientFamilyFiles); + Navigator.of(context).push( + CustomPageRoute( + page: FamilyMedicalScreen(), + ), + ); + // DialogService dialogService = getIt.get(); + // dialogService.showFamilyBottomSheetWithoutH( + // label: LocaleKeys.familyTitle.tr(context: context), + // message: "", + // isShowManageButton: true, + // onSwitchPress: (FamilyFileResponseModelLists profile) { + // medicalFileViewModel.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); + // }, + // profiles: medicalFileViewModel.patientFamilyFiles); }), isLeading: widget.showBackIcon, // leadingCallback: () { @@ -281,9 +288,9 @@ class _MedicalFilePageState extends State { ), AppCustomChipWidget( icon: AppAssets.blood_icon, - labelText: LocaleKeys.bloodGroup.tr(namedArgs: {'bloodType': appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup}, context: context), + labelText: appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup, iconColor: AppColors.primaryRedColor, - labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), padding: EdgeInsets.zero, ), Consumer(builder: (context, insuranceVM, child) { @@ -310,27 +317,35 @@ class _MedicalFilePageState extends State { : AppColors.warningColorYellow, iconSize: 12.w, deleteIcon: insuranceVM.isInsuranceActive ? null : AppAssets.forward_chevron_icon, - deleteIconColor: AppColors.warningColorYellow, + deleteIconColor: insuranceVM.isInsuranceExpired + ? AppColors.primaryRedColor + : insuranceVM.isInsuranceActive + ? AppColors.successColor + : AppColors.warningColorYellow, deleteIconHasColor: true, onChipTap: () { if (!insuranceVM.isInsuranceActive) { - showCommonBottomSheetWithoutHeight( - title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!), - navigationService.navigatorKey.currentContext!, - child: Utils.getWarningWidget( - loadingText: LocaleKeys.insuranceInActiveContactSupport.tr(context: context), - confirmText: LocaleKeys.contactUs.tr(context: context), - isShowActionButtons: true, - onCancelTap: () { - navigationService.pop(); - }, - onConfirmTap: () async { - launchUrl(Uri.parse("tel://" + "+966 92 006 6666")); - }), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); + 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: navigationService.navigatorKey.currentContext!), + // navigationService.navigatorKey.currentContext!, + // child: Utils.getWarningWidget( + // loadingText: LocaleKeys.insuranceInActiveContactSupport.tr(context: context), + // confirmText: LocaleKeys.contactUs.tr(context: context), + // isShowActionButtons: true, + // onCancelTap: () { + // navigationService.pop(); + // }, + // onConfirmTap: () async { + // launchUrl(Uri.parse("tel://" + "+966 92 006 6666")); + // }), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); } }, backgroundColor: insuranceVM.isInsuranceExpired @@ -507,7 +522,7 @@ class _MedicalFilePageState extends State { ], ), ExpandableListItem( - title: LocaleKeys.trackerAndOthers.tr(context: context).toText18(weight: FontWeight.w600), + title: LocaleKeys.healthTrackers.tr(context: context).toText18(weight: FontWeight.w600), expandedBackgroundColor: Colors.transparent, children: [ SizedBox(height: 10.h), @@ -739,7 +754,26 @@ class _MedicalFilePageState extends State { ], ).paddingSymmetrical(0.w, 0.h), SizedBox(height: 24.h), - LocaleKeys.activeMedicationsAndPrescriptions.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.activeMedicationsAndPrescriptions.tr(context: context).toText16(weight: FontWeight.w500, letterSpacing: -0.2), + Row( + children: [ + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + SizedBox(width: 2.w), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], + ).onPress(() { + // myAppointmentsViewModel.getPatientMyDoctors(); + Navigator.of(context).push( + CustomPageRoute( + page: PrescriptionsListPage(), + ), + ); + }), + ], + ), SizedBox(height: 16.h), Consumer(builder: (context, prescriptionVM, child) { return prescriptionVM.isPrescriptionsOrdersLoading @@ -784,11 +818,15 @@ class _MedicalFilePageState extends State { runSpacing: 4.w, children: [ AppCustomChipWidget(labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!), - AppCustomChipWidget( - icon: AppAssets.doctor_calendar_icon, - labelText: DateUtil.formatDateToDate( - DateUtil.convertStringToDate(prescriptionVM.patientPrescriptionOrders[index].appointmentDate), - false, + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate( + DateUtil.convertStringToDate(prescriptionVM.patientPrescriptionOrders[index].appointmentDate), + false, + ), + isEnglishOnly: true, ), ), ], @@ -816,55 +854,55 @@ class _MedicalFilePageState extends State { separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), SizedBox(height: 16.h), - Divider(color: AppColors.dividerColor), - SizedBox(height: 16.h), - Row( - children: [ - Expanded( - child: CustomButton( - text: LocaleKeys.allPrescriptions.tr(context: context), - onPressed: () { - Navigator.of(context).push( - CustomPageRoute( - page: PrescriptionsListPage(), - ), - ); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 12.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - height: 40.h, - icon: AppAssets.requests, - iconColor: AppColors.primaryRedColor, - iconSize: 16.w, - ), - ), - SizedBox(width: 6.w), - Expanded( - child: CustomButton( - text: LocaleKeys.allMedications.tr(context: context), - onPressed: () { Navigator.of(context).push( - CustomPageRoute( - page: ActiveMedicationPage(), - ), - );}, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 12.f, - fontWeight: FontWeight.w500, - borderRadius: 12.h, - height: 40.h, - icon: AppAssets.all_medications_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ), - ), - ], - ), + // Divider(color: AppColors.dividerColor), + // SizedBox(height: 16.h), + // Row( + // children: [ + // Expanded( + // child: CustomButton( + // text: LocaleKeys.allPrescriptions.tr(context: context), + // onPressed: () { + // Navigator.of(context).push( + // CustomPageRoute( + // page: PrescriptionsListPage(), + // ), + // ); + // }, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: 12.f, + // fontWeight: FontWeight.w500, + // borderRadius: 12.r, + // height: 40.h, + // icon: AppAssets.requests, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.w, + // ), + // ), + // SizedBox(width: 6.w), + // Expanded( + // child: CustomButton( + // text: LocaleKeys.allMedications.tr(context: context), + // onPressed: () { Navigator.of(context).push( + // CustomPageRoute( + // page: ActiveMedicationPage(), + // ), + // );}, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: 12.f, + // fontWeight: FontWeight.w500, + // borderRadius: 12.h, + // height: 40.h, + // icon: AppAssets.all_medications_icon, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.h, + // ), + // ), + // ], + // ), ], ), ), @@ -897,7 +935,7 @@ class _MedicalFilePageState extends State { Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], ).onPress(() { - myAppointmentsViewModel.getPatientMyDoctors(); + // myAppointmentsViewModel.getPatientMyDoctors(); Navigator.of(context).push( CustomPageRoute( page: MyDoctorsPage(), @@ -1196,6 +1234,26 @@ class _MedicalFilePageState extends State { // Requests Tab Data return Column( children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "${LocaleKeys.sick.tr(context: context)} ${LocaleKeys.sickSubtitle.tr(context: context)}".toText16(isBold: true), + Row( + children: [ + LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], + ), + ], + ).onPress(() { + Navigator.of(context).push( + CustomPageRoute( + page: PatientSickleavesListPage(), + ), + ); + }), + SizedBox(height: 16.h), Consumer(builder: (context, medicalFileVM, child) { return medicalFileVM.isPatientSickLeaveListLoading ? PatientSickLeaveCard( @@ -1265,20 +1323,20 @@ class _MedicalFilePageState extends State { ), ); }), - MedicalFileCard( - label: LocaleKeys.sickLeaveReport.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.sick_leave_report_icon, - isLargeText: true, - iconSize: 36.h, - ).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: PatientSickleavesListPage(), - ), - ); - }), + // MedicalFileCard( + // label: LocaleKeys.sickLeaveReport.tr(context: context), + // textColor: AppColors.blackColor, + // backgroundColor: AppColors.whiteColor, + // svgIcon: AppAssets.sick_leave_report_icon, + // isLargeText: true, + // iconSize: 36.h, + // ).onPress(() { + // Navigator.of(context).push( + // CustomPageRoute( + // page: PatientSickleavesListPage(), + // ), + // ); + // }), ], ).paddingSymmetrical(0.w, 0.0), SizedBox(height: 24.h), @@ -1289,12 +1347,12 @@ class _MedicalFilePageState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - LocaleKeys.healthTrackers.tr(context: context).toText16(weight: FontWeight.w500, color: AppColors.textColor), - ], - ), - SizedBox(height: 16.h), + // Row( + // children: [ + // LocaleKeys.healthTrackers.tr(context: context).toText16(weight: FontWeight.w500, color: AppColors.textColor), + // ], + // ), + // SizedBox(height: 16.h), GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, @@ -1333,11 +1391,11 @@ class _MedicalFilePageState extends State { ], ).paddingSymmetrical(0.w, 0.0), SizedBox(height: 16.h), - Row( - children: [ - LocaleKeys.others.tr(context: context).toText16(weight: FontWeight.w500, color: AppColors.textColor), - ], - ), + // Row( + // children: [ + // LocaleKeys.others.tr(context: context).toText16(weight: FontWeight.w500, color: AppColors.textColor), + // ], + // ), SizedBox(height: 16.h), GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( @@ -1350,21 +1408,22 @@ class _MedicalFilePageState extends State { padding: EdgeInsets.zero, shrinkWrap: true, children: [ - MedicalFileCard( - label: LocaleKeys.askYourDoctor.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.ask_doctor_medical_file_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() { - getIt.get().initAskDoctorViewModel(); - Navigator.of(context).push( - CustomPageRoute( - page: AskDoctorPage(), - ), - ); - }), + // MedicalFileCard( + // label: LocaleKeys.askYourDoctor.tr(context: context), + // textColor: AppColors.blackColor, + // backgroundColor: AppColors.whiteColor, + // svgIcon: AppAssets.ask_doctor_medical_file_icon, + // isLargeText: true, + // iconSize: 36.w, + // ).onPress(() { + // getIt.get().initAskDoctorViewModel(); + // Navigator.of(context).push( + // CustomPageRoute( + // page: AskDoctorPage(), + // ), + // ); + // }), + // MedicalFileCard( // label: LocaleKeys.internetPairing.tr(context: context), // textColor: AppColors.blackColor, diff --git a/lib/presentation/medical_file/patient_sickleaves_list_page.dart b/lib/presentation/medical_file/patient_sickleaves_list_page.dart index 93cf17ff..4518eca5 100644 --- a/lib/presentation/medical_file/patient_sickleaves_list_page.dart +++ b/lib/presentation/medical_file/patient_sickleaves_list_page.dart @@ -57,48 +57,48 @@ class _PatientSickleavesListPageState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(height: 16.h), + // SizedBox(height: 16.h), // Clinic & Hospital Sort - Row( - children: [ - CustomButton( - text: LocaleKeys.byClinic.tr(context: context), - onPressed: () { - model.setIsSickLeavesSortByClinic(true); - }, - backgroundColor: model.isSickLeavesSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: model.isSickLeavesSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), - textColor: model.isSickLeavesSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - SizedBox(width: 8.h), - CustomButton( - text: LocaleKeys.byHospital.tr(context: context), - onPressed: () { - model.setIsSickLeavesSortByClinic(false); - }, - backgroundColor: model.isSickLeavesSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, - borderColor: model.isSickLeavesSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, - textColor: model.isSickLeavesSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - ], - ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 20.h), + // Row( + // children: [ + // CustomButton( + // text: LocaleKeys.byClinic.tr(context: context), + // onPressed: () { + // model.setIsSickLeavesSortByClinic(true); + // }, + // backgroundColor: model.isSickLeavesSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, + // borderColor: model.isSickLeavesSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), + // textColor: model.isSickLeavesSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, + // fontSize: 12, + // fontWeight: FontWeight.w500, + // borderRadius: 10, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // ), + // SizedBox(width: 8.h), + // CustomButton( + // text: LocaleKeys.byHospital.tr(context: context), + // onPressed: () { + // model.setIsSickLeavesSortByClinic(false); + // }, + // backgroundColor: model.isSickLeavesSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, + // borderColor: model.isSickLeavesSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, + // textColor: model.isSickLeavesSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, + // fontSize: 12, + // fontWeight: FontWeight.w500, + // borderRadius: 10, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // ), + // ], + // ).paddingSymmetrical(24.h, 0.h), + // SizedBox(height: 20.h), // Expandable list ListView.builder( itemCount: model.isPatientSickLeaveListLoading ? 4 : model.patientSickLeaveList.isNotEmpty - ? model.patientSickLeavesViewList.length + ? model.patientSickLeaveList.length : 1, physics: NeverScrollableScrollPhysics(), shrinkWrap: true, @@ -122,155 +122,322 @@ class _PatientSickleavesListPageState extends State { curve: Curves.easeInOut, margin: EdgeInsets.symmetric(vertical: 8.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), - child: InkWell( - onTap: () { - setState(() { - expandedIndex = isExpanded ? null : index; - }); - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppCustomChipWidget( + labelText: + "${getIt.get().isArabic() ? model.patientSickLeaveList[index].isInOutPatientDescriptionN : model.patientSickLeaveList[index].isInOutPatientDescription}", + backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), + textColor: AppColors.warningColorYellow, + ), + SizedBox(height: 16.h), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.network( + model.patientSickLeaveList[index].doctorImageURL!, + width: 24.h, + height: 24.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Expanded(child: model.patientSickLeaveList[index].doctorName!.toText14(weight: FontWeight.w500)), + ], + ), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CustomButton( - text: "${model.patientSickLeavesViewList[index].sickLeavesList!.length} ${LocaleKeys.sickSubtitle.tr(context: context)} Available", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - Icon(isExpanded ? Icons.expand_less : Icons.expand_more), - ], + AppCustomChipWidget( + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(model.patientSickLeaveList[index].appointmentDate), false), + ), + AppCustomChipWidget( + labelText: model.isSickLeavesSortByClinic ? model.patientSickLeaveList[index].projectName! : model.patientSickLeaveList[index].clinicName!, + ), + AppCustomChipWidget( + labelText: "${model.patientSickLeaveList[index].sickLeaveDays} Days", ), - SizedBox(height: 8.h), - model.patientSickLeavesViewList[index].filterName!.toText16(isBold: true) ], ), - ), - AnimatedSwitcher( - duration: Duration(milliseconds: 500), - switchInCurve: Curves.easeIn, - switchOutCurve: Curves.easeOut, - transitionBuilder: (Widget child, Animation animation) { - return FadeTransition( - opacity: animation, - child: SizeTransition( - sizeFactor: animation, - axisAlignment: 0.0, - child: child, + SizedBox(height: 12.h), + Row( + children: [ + Expanded( + flex: 6, + child: CustomButton( + text: LocaleKeys.downloadReport.tr(context: context), + onPressed: () async { + LoaderBottomSheet.showLoader(); + await medicalFileViewModel.getPatientSickLeavePDF(model.patientSickLeaveList[index], appState.getAuthenticatedUser()!).then((val) async { + LoaderBottomSheet.hideLoader(); + if (medicalFileViewModel.patientSickLeavePDFBase64.isNotEmpty) { + String path = await Utils.createFileFromString(medicalFileViewModel.patientSickLeavePDFBase64, "pdf"); + try { + OpenFilex.open(path); + } catch (ex) { + debugPrint("Error opening file: $ex"); + } + } + }); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.download, + iconColor: AppColors.primaryRedColor, + iconSize: 14.h, + ), ), - ); - }, - child: isExpanded - ? Container( - key: ValueKey(index), - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...model.patientSickLeavesViewList[index].sickLeavesList!.map((sickLeave) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Image.network( - sickLeave.doctorImageURL!, - width: 24.h, - height: 24.h, - fit: BoxFit.fill, - ).circle(100), - SizedBox(width: 8.h), - Expanded(child: sickLeave.doctorName!.toText14(weight: FontWeight.w500)), - ], - ), - SizedBox(height: 8.h), - Wrap( - direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.h, - children: [ - AppCustomChipWidget( - labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(sickLeave.appointmentDate), false), - ), - AppCustomChipWidget( - labelText: model.isSickLeavesSortByClinic ? sickLeave.projectName! : sickLeave.clinicName!, - ), - AppCustomChipWidget( - labelText: "${sickLeave.sickLeaveDays} Days", - ), - ], - ), - SizedBox(height: 12.h), - Row( - children: [ - Expanded( - flex: 6, - child: CustomButton( - text: LocaleKeys.downloadReport.tr(context: context), - onPressed: () async { - LoaderBottomSheet.showLoader(); - await medicalFileViewModel.getPatientSickLeavePDF(sickLeave, appState.getAuthenticatedUser()!).then((val) async { - LoaderBottomSheet.hideLoader(); - if (medicalFileViewModel.patientSickLeavePDFBase64.isNotEmpty) { - String path = await Utils.createFileFromString(medicalFileViewModel.patientSickLeavePDFBase64, "pdf"); - try { - OpenFilex.open(path); - } catch (ex) { - debugPrint("Error opening file: $ex"); - } - } - }); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppAssets.download, - iconColor: AppColors.primaryRedColor, - iconSize: 14.h, - ), - ), - ], - ), - SizedBox(height: 12.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), - SizedBox(height: 12.h), - ], - ); - }), - ], - ), - ) - : SizedBox.shrink(), - ), - ], + ], + ), + // SizedBox(height: 12.h), + // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + // SizedBox(height: 12.h), + ], + ), + // Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // ...model.patientSickLeaveList[index].sickLeavesList!.map((sickLeave) { + // return Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Image.network( + // sickLeave.doctorImageURL!, + // width: 24.h, + // height: 24.h, + // fit: BoxFit.fill, + // ).circle(100), + // SizedBox(width: 8.h), + // Expanded(child: sickLeave.doctorName!.toText14(weight: FontWeight.w500)), + // ], + // ), + // SizedBox(height: 8.h), + // Wrap( + // direction: Axis.horizontal, + // spacing: 6.h, + // runSpacing: 6.h, + // children: [ + // AppCustomChipWidget( + // labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(sickLeave.appointmentDate), false), + // ), + // AppCustomChipWidget( + // labelText: model.isSickLeavesSortByClinic ? sickLeave.projectName! : sickLeave.clinicName!, + // ), + // AppCustomChipWidget( + // labelText: "${sickLeave.sickLeaveDays} Days", + // ), + // ], + // ), + // SizedBox(height: 12.h), + // Row( + // children: [ + // Expanded( + // flex: 6, + // child: CustomButton( + // text: LocaleKeys.downloadReport.tr(context: context), + // onPressed: () async { + // LoaderBottomSheet.showLoader(); + // await medicalFileViewModel.getPatientSickLeavePDF(sickLeave, appState.getAuthenticatedUser()!).then((val) async { + // LoaderBottomSheet.hideLoader(); + // if (medicalFileViewModel.patientSickLeavePDFBase64.isNotEmpty) { + // String path = await Utils.createFileFromString(medicalFileViewModel.patientSickLeavePDFBase64, "pdf"); + // try { + // OpenFilex.open(path); + // } catch (ex) { + // debugPrint("Error opening file: $ex"); + // } + // } + // }); + // }, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: 14, + // fontWeight: FontWeight.w500, + // borderRadius: 12, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // icon: AppAssets.download, + // iconColor: AppColors.primaryRedColor, + // iconSize: 14.h, + // ), + // ), + // ], + // ), + // SizedBox(height: 12.h), + // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + // SizedBox(height: 12.h), + // ], + // ); + // }), + // ], + // ), + ) + // InkWell( + // onTap: () { + // setState(() { + // expandedIndex = isExpanded ? null : index; + // }); + // }, + // child: + // Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Padding( + // padding: EdgeInsets.all(16.h), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // CustomButton( + // text: "${model.patientSickLeavesViewList[index].sickLeavesList!.length} ${LocaleKeys.sickSubtitle.tr(context: context)} Available", + // onPressed: () {}, + // backgroundColor: AppColors.greyColor, + // borderColor: AppColors.greyColor, + // textColor: AppColors.blackColor, + // fontSize: 10, + // fontWeight: FontWeight.w500, + // borderRadius: 8, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 30.h, + // ), + // Icon(isExpanded ? Icons.expand_less : Icons.expand_more), + // ], + // ), + // SizedBox(height: 8.h), + // model.patientSickLeavesViewList[index].filterName!.toText16(isBold: true) + // ], + // ), + // ), + // // AnimatedSwitcher( + // // duration: Duration(milliseconds: 500), + // // switchInCurve: Curves.easeIn, + // // switchOutCurve: Curves.easeOut, + // // transitionBuilder: (Widget child, Animation animation) { + // // return FadeTransition( + // // opacity: animation, + // // child: SizeTransition( + // // sizeFactor: animation, + // // axisAlignment: 0.0, + // // child: child, + // // ), + // // ); + // // }, + // // child: isExpanded + // // ? Container( + // // key: ValueKey(index), + // // padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + // // child: Column( + // // crossAxisAlignment: CrossAxisAlignment.start, + // // children: [ + // // ...model.patientSickLeavesViewList[index].sickLeavesList!.map((sickLeave) { + // // return Column( + // // crossAxisAlignment: CrossAxisAlignment.start, + // // children: [ + // // Row( + // // mainAxisSize: MainAxisSize.min, + // // children: [ + // // Image.network( + // // sickLeave.doctorImageURL!, + // // width: 24.h, + // // height: 24.h, + // // fit: BoxFit.fill, + // // ).circle(100), + // // SizedBox(width: 8.h), + // // Expanded(child: sickLeave.doctorName!.toText14(weight: FontWeight.w500)), + // // ], + // // ), + // // SizedBox(height: 8.h), + // // Wrap( + // // direction: Axis.horizontal, + // // spacing: 6.h, + // // runSpacing: 6.h, + // // children: [ + // // AppCustomChipWidget( + // // labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(sickLeave.appointmentDate), false), + // // ), + // // AppCustomChipWidget( + // // labelText: model.isSickLeavesSortByClinic ? sickLeave.projectName! : sickLeave.clinicName!, + // // ), + // // AppCustomChipWidget( + // // labelText: "${sickLeave.sickLeaveDays} Days", + // // ), + // // ], + // // ), + // // SizedBox(height: 12.h), + // // Row( + // // children: [ + // // Expanded( + // // flex: 6, + // // child: CustomButton( + // // text: LocaleKeys.downloadReport.tr(context: context), + // // onPressed: () async { + // // LoaderBottomSheet.showLoader(); + // // await medicalFileViewModel.getPatientSickLeavePDF(sickLeave, appState.getAuthenticatedUser()!).then((val) async { + // // LoaderBottomSheet.hideLoader(); + // // if (medicalFileViewModel.patientSickLeavePDFBase64.isNotEmpty) { + // // String path = await Utils.createFileFromString(medicalFileViewModel.patientSickLeavePDFBase64, "pdf"); + // // try { + // // OpenFilex.open(path); + // // } catch (ex) { + // // debugPrint("Error opening file: $ex"); + // // } + // // } + // // }); + // // }, + // // backgroundColor: AppColors.secondaryLightRedColor, + // // borderColor: AppColors.secondaryLightRedColor, + // // textColor: AppColors.primaryRedColor, + // // fontSize: 14, + // // fontWeight: FontWeight.w500, + // // borderRadius: 12, + // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // // height: 40.h, + // // icon: AppAssets.download, + // // iconColor: AppColors.primaryRedColor, + // // iconSize: 14.h, + // // ), + // // ), + // // ], + // // ), + // // SizedBox(height: 12.h), + // // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + // // SizedBox(height: 12.h), + // // ], + // // ); + // // }), + // // ], + // // ), + // // ) + // // : SizedBox.shrink(), + // // ), + // ], + // ), + // ), ), - ), - ), ), ), ) : Utils.getNoDataWidget(context, noDataText: LocaleKeys.youDontHaveAnySickLeavesYet.tr(context: context)); }, ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 24.h), ], ); }), diff --git a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart index 73dd1652..4c1a72b7 100644 --- a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart @@ -74,7 +74,7 @@ class MedicalFileAppointmentCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText14(isBold: true, maxlines: 1).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + (patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText14(isBold: true, maxlines: 1, isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? "")).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), (patientAppointmentHistoryResponseModel.clinicName ?? "") .toText12(maxLine: 1, fontWeight: FontWeight.w500, color: AppColors.greyTextColor) .toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), diff --git a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart index a0c54e68..18bd2e69 100644 --- a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart +++ b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart @@ -40,18 +40,17 @@ class PatientSickLeaveCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "${LocaleKeys.sick.tr(context: context)} ${LocaleKeys.sickSubtitle.tr(context: context)}".toText16(isBold: true).toShimmer2(isShow: isLoading), - AppCustomChipWidget( - labelText: isLoading ? "" : getStatusText(context), - backgroundColor: getStatusColor().withOpacity(0.15), - textColor: getStatusColor(), - ).toShimmer2(isShow: isLoading, width: 100.h), - ], - ), - SizedBox(height: 16.h), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // AppCustomChipWidget( + // labelText: isLoading ? "" : getStatusText(context), + // backgroundColor: getStatusColor().withOpacity(0.15), + // textColor: getStatusColor(), + // ).toShimmer2(isShow: isLoading, width: 100.h), + // ], + // ), + // SizedBox(height: 16.h), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index 826b1741..a621168f 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -9,16 +11,22 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:provider/provider.dart'; class FamilyCards extends StatefulWidget { final List profiles; + late List? profileViewList; final Function(FamilyFileResponseModelLists) onSelect; final Function(FamilyFileResponseModelLists) onRemove; final bool isShowDetails; @@ -28,11 +36,12 @@ class FamilyCards extends StatefulWidget { final bool isShowRemoveButton; final bool isForWalletRecharge; - const FamilyCards( + FamilyCards( {super.key, required this.profiles, required this.onSelect, required this.onRemove, + this.profileViewList, this.isShowDetails = false, this.isBottomSheet = false, this.isRequestDesign = false, @@ -46,9 +55,22 @@ class FamilyCards extends StatefulWidget { class _FamilyCardsState extends State { AppState appState = getIt(); + late InsuranceViewModel insuranceViewModel; + + @override + void initState() { + scheduleMicrotask(() { + insuranceViewModel.initInsuranceProvider(); + }); + super.initState(); + } @override Widget build(BuildContext context) { + widget.profileViewList = []; + widget.profileViewList!.addAll(widget.profiles); + widget.profileViewList!.removeWhere((element) => element.responseId == appState.getAuthenticatedUser()?.patientId); + insuranceViewModel = Provider.of(context, listen: false); DialogService dialogService = getIt.get(); if (widget.isRequestDesign) { return Column( @@ -71,15 +93,15 @@ class _FamilyCardsState extends State { ], ), SizedBox(height: 24.h), - widget.profiles.where((profile) => profile.isRequestFromMySide ?? false).isEmpty + widget.profileViewList!.where((profile) => profile.isRequestFromMySide ?? false).isEmpty ? Utils.getNoDataWidget(context) : ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, - itemCount: widget.profiles.where((profile) => profile.isRequestFromMySide ?? false).length, + itemCount: widget.profileViewList!.where((profile) => profile.isRequestFromMySide ?? false).length, itemBuilder: (context, index) { - final mySideProfiles = widget.profiles.where((profile) => profile.isRequestFromMySide ?? false).toList(); + final mySideProfiles = widget.profileViewList!.where((profile) => profile.isRequestFromMySide ?? false).toList(); FamilyFileResponseModelLists profile = mySideProfiles[index]; return Container( margin: EdgeInsets.only(bottom: 12.h), @@ -102,7 +124,7 @@ class _FamilyCardsState extends State { : profile.status == FamilyFileEnum.active.toInt ? AppColors.lightGreenColor : AppColors.lightGrayBGColor, - chipText: profile.statusDescription ?? "N/A", + chipText: profile.statusDescription ?? " N/A", iconAsset: null, isShowBorder: false, borderRadius: 8.h, @@ -146,113 +168,252 @@ class _FamilyCardsState extends State { ], ); } else { - return GridView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: widget.profiles.length, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: 10.w, - mainAxisSpacing: 10.h, - childAspectRatio: widget.isShowDetails ? 0.56.h : 0.64.h, - ), - padding: EdgeInsets.only(bottom: 20.h), - itemBuilder: (context, index) { - final profile = widget.profiles[index]; - final isActive = (profile.responseId == appState.getAuthenticatedUser()?.patientId); - final isParentUser = appState.getAuthenticatedUser()?.isParentUser ?? false; - final canSwitch = isParentUser || (!isParentUser && profile.responseId == appState.getSuperUserID); - return Container( - padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 15.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), - child: Opacity( - opacity: isActive || profile.status == FamilyFileEnum.pending.toInt || !canSwitch ? 0.4 : 1.0, // Fade all content if active - child: Stack( + return Column( + children: [ + Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r), + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Column( - mainAxisSize: MainAxisSize.min, + Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Utils.buildImgWithAssets( - icon: profile.gender == null - ? AppAssets.dummyUser - : profile.gender == 1 - ? ((profile.age ?? 0) < 7 ? AppAssets.babyBoyImg : AppAssets.maleImg) - : (profile.age! < 7 ? AppAssets.babyGirlImg : AppAssets.femaleImg), - width: 72.h, - height: 70.h, + Image.asset(appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg, width: 56.w, height: 56.h), + SizedBox(width: 8.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width * 0.6, + child: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" + .toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 2), + ), + SizedBox(height: 4.h), + Wrap( + direction: Axis.horizontal, + spacing: 4.w, + runSpacing: 6.w, + children: [ + AppCustomChipWidget( + icon: AppAssets.file_icon, + richText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText10(isEnglishOnly: true), + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + ), + AppCustomChipWidget( + icon: AppAssets.checkmark_icon, + labelText: LocaleKeys.verified.tr(context: context), + iconColor: AppColors.successColor, + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), + ), + ], + ), + ], + ) + ], + ), + SizedBox(height: 16.h), + Divider(color: AppColors.dividerColor, height: 1.h), + SizedBox(height: 16.h), + Wrap( + direction: Axis.horizontal, + spacing: 4.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + labelText: LocaleKeys.ageYearsOld.tr(namedArgs: {'age': '${appState.getAuthenticatedUser()!.age}', 'yearsOld': LocaleKeys.yearsOld.tr(context: context)}, context: context), + labelPadding: EdgeInsetsDirectional.only(start: 8.w, end: 8.w), ), - SizedBox(height: 8.h), - (profile.patientName ?? "Unknown").toText14(isBold: false, isCenter: true, maxlines: 1, weight: FontWeight.w600), - SizedBox(height: 8.h), - CustomChipWidget( - chipType: ChipTypeEnum.alert, - backgroundColor: AppColors.lightGrayBGColor, - chipText: "Relation:${profile.relationship ?? " N/A"}", - iconAsset: AppAssets.heart, - isShowBorder: false, - borderRadius: 8.h, - textColor: AppColors.textColor), - widget.isShowDetails ? SizedBox(height: 4.h) : SizedBox(), - widget.isShowDetails - ? CustomChipWidget( + AppCustomChipWidget( + icon: AppAssets.blood_icon, + labelText: appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup, + iconColor: AppColors.primaryRedColor, + labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), + padding: EdgeInsets.zero, + ), + Consumer(builder: (context, insuranceVM, child) { + return 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: navigationService.navigatorKey.currentContext!), + // navigationService.navigatorKey.currentContext!, + // child: Utils.getWarningWidget( + // loadingText: LocaleKeys.insuranceInActiveContactSupport.tr(context: context), + // confirmText: LocaleKeys.contactUs.tr(context: context), + // isShowActionButtons: true, + // onCancelTap: () { + // navigationService.pop(); + // }, + // onConfirmTap: () async { + // launchUrl(Uri.parse("tel://" + "+966 92 006 6666")); + // }), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + } + }, + backgroundColor: insuranceVM.isInsuranceExpired + ? AppColors.primaryRedColor.withOpacity(0.1) + : insuranceVM.isInsuranceActive + ? AppColors.successColor.withOpacity(0.1) + : AppColors.warningColorYellow.withOpacity(0.1), + labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: insuranceVM.isInsuranceActive ? 6.w : 0.w), + ).toShimmer2(isShow: insuranceVM.isInsuranceLoading); + }), + ], + ), + ], + ), + ), + ).paddingSymmetrical(0.w, 0.0), + SizedBox(height: 16.h), + GridView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: widget.profileViewList!.length, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10.w, + mainAxisSpacing: 10.h, + childAspectRatio: widget.isShowDetails ? 0.56.h : 0.74.h, + ), + padding: EdgeInsets.only(bottom: 20.h), + itemBuilder: (context, index) { + final profile = widget.profileViewList![index]; + final isActive = (profile.responseId == appState.getAuthenticatedUser()?.patientId); + final isParentUser = appState.getAuthenticatedUser()?.isParentUser ?? false; + final canSwitch = isParentUser || (!isParentUser && profile.responseId == appState.getSuperUserID); + return Container( + padding: EdgeInsets.symmetric(vertical: 15.h, horizontal: 15.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + child: Opacity( + opacity: isActive || profile.status == FamilyFileEnum.pending.toInt || !canSwitch ? 0.4 : 1.0, // Fade all content if active + child: Stack( + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Utils.buildImgWithAssets( + icon: profile.gender == null + ? AppAssets.dummyUser + : profile.gender == 1 + ? ((profile.age ?? 0) < 7 ? AppAssets.babyBoyImg : AppAssets.maleImg) + : (profile.age! < 7 ? AppAssets.babyGirlImg : AppAssets.femaleImg), + width: 72.h, + height: 70.h, + ), + SizedBox(height: 8.h), + (profile.patientName ?? "Unknown").toText14(isBold: false, isCenter: true, maxlines: 1, weight: FontWeight.w600), + SizedBox(height: 8.h), + CustomChipWidget( chipType: ChipTypeEnum.alert, backgroundColor: AppColors.lightGrayBGColor, - chipText: "Age:${profile.age ?? "N/A"} Years", + chipText: "Relation: ${profile.relationship ?? " N/A"}", + iconAsset: AppAssets.heart, isShowBorder: false, borderRadius: 8.h, - textColor: AppColors.textColor, - ) - : SizedBox(), - widget.isShowDetails - ? SizedBox(height: 8.h) - : SizedBox( - height: 4.h, - ), - Spacer(), - widget.isForWalletRecharge ? CustomButton( - height: 40.h, - onPressed: () { - widget.onSelect(profile); - // if (canSwitch) widget.onSelect(profile); - }, - text: LocaleKeys.select.tr(context: context), - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 13.h, - icon: AppAssets.activeCheck, - iconColor: isActive || !canSwitch ? (isActive ? null : AppColors.greyTextColor) : AppColors.primaryRedColor, - padding: EdgeInsets.symmetric(vertical: 0, horizontal: 0), - ).paddingOnly(top: 0, bottom: 0) : CustomButton( - height: 40.h, - onPressed: () { - if (canSwitch) widget.onSelect(profile); - }, - 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, - textColor: isActive || !canSwitch ? AppColors.greyTextColor : AppColors.primaryRedColor, - fontSize: 13.h, - icon: isActive ? AppAssets.activeCheck : AppAssets.switch_user, - iconColor: isActive || !canSwitch ? (isActive ? null : AppColors.greyTextColor) : AppColors.primaryRedColor, - padding: EdgeInsets.symmetric(vertical: 0, horizontal: 0), - ).paddingOnly(top: 0, bottom: 0), + textColor: AppColors.textColor), + widget.isShowDetails ? SizedBox(height: 4.h) : SizedBox(), + widget.isShowDetails + ? CustomChipWidget( + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: "Age: ${profile.age ?? "N/A"} Years", + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor, + ) + : SizedBox(), + widget.isShowDetails + ? SizedBox(height: 8.h) + : SizedBox( + height: 4.h, + ), + Spacer(), + widget.isForWalletRecharge + ? CustomButton( + height: 40.h, + onPressed: () { + widget.onSelect(profile); + // if (canSwitch) widget.onSelect(profile); + }, + text: LocaleKeys.select.tr(context: context), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 13.h, + icon: AppAssets.activeCheck, + iconColor: isActive || !canSwitch ? (isActive ? null : AppColors.greyTextColor) : AppColors.primaryRedColor, + padding: EdgeInsets.symmetric(vertical: 0, horizontal: 0), + ).paddingOnly(top: 0, bottom: 0) + : CustomButton( + height: 40.h, + onPressed: () { + if (canSwitch) widget.onSelect(profile); + }, + 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, + textColor: isActive || !canSwitch ? AppColors.greyTextColor : AppColors.primaryRedColor, + fontSize: 13.h, + icon: isActive ? AppAssets.activeCheck : AppAssets.switch_user, + iconColor: isActive || !canSwitch ? (isActive ? null : AppColors.greyTextColor) : AppColors.primaryRedColor, + padding: EdgeInsets.symmetric(vertical: 0, horizontal: 0), + ).paddingOnly(top: 0, bottom: 0), + ], + ), + if (widget.isShowRemoveButton) ...[ + Positioned( + top: 0, + right: 0, + child: Utils.buildSvgWithAssets(icon: AppAssets.deleteIcon).onPress(() { + if (!isActive) widget.onRemove(profile); + }), + ), + ], ], ), - if (widget.isShowRemoveButton) ...[ - Positioned( - top: 0, - right: 0, - child: Utils.buildSvgWithAssets(icon: AppAssets.deleteIcon).onPress(() { - if (!isActive) widget.onRemove(profile); - }), - ), - ], - ], - ), - ), - ); - }, + ), + ); + }, + ), + ], ); } } @@ -260,15 +421,15 @@ class _FamilyCardsState extends State { Widget manageFamily() { NavigationService navigationService = getIt(); - return widget.profiles.where((profile) => !(profile.isRequestFromMySide ?? false)).isEmpty + return widget.profileViewList!.where((profile) => !(profile.isRequestFromMySide ?? false)).isEmpty ? Utils.getNoDataWidget(context) : ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsetsGeometry.zero, - itemCount: widget.profiles.where((profile) => !(profile.isRequestFromMySide ?? false)).length, + itemCount: widget.profileViewList!.where((profile) => !(profile.isRequestFromMySide ?? false)).length, itemBuilder: (context, index) { - final otherProfiles = widget.profiles.where((profile) => !(profile.isRequestFromMySide ?? false)).toList(); + final otherProfiles = widget.profileViewList!.where((profile) => !(profile.isRequestFromMySide ?? false)).toList(); FamilyFileResponseModelLists profile = otherProfiles[index]; return Container( margin: EdgeInsets.only(bottom: 12.h), diff --git a/lib/presentation/my_invoices/my_invoices_details_page.dart b/lib/presentation/my_invoices/my_invoices_details_page.dart index a38194fe..79e1adc3 100644 --- a/lib/presentation/my_invoices/my_invoices_details_page.dart +++ b/lib/presentation/my_invoices/my_invoices_details_page.dart @@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoice_details_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart'; import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -20,8 +21,9 @@ import 'package:provider/provider.dart'; class MyInvoicesDetailsPage extends StatefulWidget { GetInvoiceDetailsResponseModel getInvoiceDetailsResponseModel; + GetInvoicesListResponseModel getInvoicesListResponseModel; - MyInvoicesDetailsPage({super.key, required this.getInvoiceDetailsResponseModel}); + MyInvoicesDetailsPage({super.key, required this.getInvoiceDetailsResponseModel, required this.getInvoicesListResponseModel}); @override State createState() => _MyInvoicesDetailsPageState(); @@ -82,6 +84,26 @@ class _MyInvoicesDetailsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + Wrap( + alignment: WrapAlignment.start, + direction: Axis.horizontal, + spacing: 6.w, + runSpacing: 6.h, + children: [ + AppCustomChipWidget( + icon: AppAssets.walkin_appointment_icon, + iconColor: AppColors.textColor, + labelText: LocaleKeys.walkin.tr(context: context), + textColor: AppColors.textColor, + ), + AppCustomChipWidget( + labelText: LocaleKeys.outPatient.tr(context: context), + backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.1), + textColor: AppColors.warningColorYellow, + ), + ], + ), + SizedBox(height: 16.h), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -93,6 +115,29 @@ class _MyInvoicesDetailsPageState extends State { height: 63.h, fit: BoxFit.cover, ).circle(100.r), + Transform.translate( + offset: Offset(0.0, -20.h), + child: Container( + width: 40.w, + height: 40.h, + decoration: BoxDecoration( + color: AppColors.whiteColor, + shape: BoxShape.circle, // Makes the container circular + border: Border.all( + color: AppColors.scaffoldBgColor, // Color of the border + width: 1.5.w, // Width of the border + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, applyThemeColor: false), + SizedBox(height: 2.h), + "${widget.getInvoicesListResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor), + ], + ), + ).circle(100), + ), ], ), SizedBox(width: 16.w), @@ -138,41 +183,46 @@ class _MyInvoicesDetailsPageState extends State { ), ), SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: true, - ), - child: Padding( - padding: EdgeInsets.all(14.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - widget.getInvoiceDetailsResponseModel.listConsultation!.first.procedureName!.toText16(isBold: true), - SizedBox(height: 16.h), - Wrap( - direction: Axis.horizontal, - spacing: 6.w, - runSpacing: 6.h, + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - AppCustomChipWidget( - labelText: "${LocaleKeys.quantity.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.quantity!}", - labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), - ), - AppCustomChipWidget( - labelText: "${LocaleKeys.price.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.price!} ${LocaleKeys.sar.tr()}", - labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), - ), - AppCustomChipWidget( - labelText: "${LocaleKeys.total.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.total!} ${LocaleKeys.sar.tr()}", - labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + widget.getInvoiceDetailsResponseModel.listConsultation!.first.procedureName!.toText16(isBold: true), + SizedBox(height: 16.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.w, + runSpacing: 6.h, + children: [ + AppCustomChipWidget( + labelText: "${LocaleKeys.quantity.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.quantity!}", + labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + ), + AppCustomChipWidget( + labelText: "${LocaleKeys.price.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.price!} ${LocaleKeys.sar.tr()}", + labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + ), + AppCustomChipWidget( + labelText: "${LocaleKeys.total.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.total!} ${LocaleKeys.sar.tr()}", + labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + ), + ], ), ], ), - ], + ), ), - ), + ], ), SizedBox(height: 16.h), Container( diff --git a/lib/presentation/my_invoices/my_invoices_list.dart b/lib/presentation/my_invoices/my_invoices_list.dart index 177dde84..dddb0a15 100644 --- a/lib/presentation/my_invoices/my_invoices_list.dart +++ b/lib/presentation/my_invoices/my_invoices_list.dart @@ -86,7 +86,7 @@ class _MyInvoicesListState extends State { LoaderBottomSheet.hideLoader(); Navigator.of(context).push( CustomPageRoute( - page: MyInvoicesDetailsPage(getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel), + page: MyInvoicesDetailsPage(getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel, getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index],), ), ); }, diff --git a/lib/presentation/my_invoices/widgets/invoice_list_card.dart b/lib/presentation/my_invoices/widgets/invoice_list_card.dart index 6e0fa0e4..e5ae7de8 100644 --- a/lib/presentation/my_invoices/widgets/invoice_list_card.dart +++ b/lib/presentation/my_invoices/widgets/invoice_list_card.dart @@ -47,8 +47,8 @@ class InvoiceListCard extends StatelessWidget { ), AppCustomChipWidget( labelText: LocaleKeys.outPatient.tr(context: context), - backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1), - textColor: AppColors.primaryRedColor, + backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.1), + textColor: AppColors.warningColorYellow, ), ], ), diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index b9b5b789..0ac9ce40 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -4,6 +4,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; @@ -23,6 +25,8 @@ import 'package:open_filex/open_filex.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'dart:ui' as ui; + class PrescriptionDetailPage extends StatefulWidget { PrescriptionDetailPage({super.key, required this.prescriptionsResponseModel, required this.isFromAppointments}); @@ -61,8 +65,9 @@ class _PrescriptionDetailPageState extends State { Expanded( child: CollapsingListView( title: LocaleKeys.prescriptions.tr(context: context), - instructions: () async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionPDFPleaseWait.tr(context: context)); + instructions: widget.prescriptionsResponseModel.isInOutPatient! + ? () async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionPDFPleaseWait.tr(context: context)); await prescriptionsViewModel.getPrescriptionInstructionsPDF(widget.prescriptionsResponseModel, onSuccess: (val) { LoaderBottomSheet.hideLoader(); if (prescriptionsViewModel.prescriptionInstructionsPDFLink.isNotEmpty) { @@ -87,7 +92,8 @@ class _PrescriptionDetailPageState extends State { isCloseButtonVisible: true, ); }); - }, + } + : null, child: SingleChildScrollView( child: Consumer(builder: (context, prescriptionVM, child) { return Column( @@ -105,6 +111,13 @@ class _PrescriptionDetailPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + AppCustomChipWidget( + labelText: + "${getIt.get().isArabic() ? widget.prescriptionsResponseModel.isInOutPatientDescriptionN : widget.prescriptionsResponseModel.isInOutPatientDescription}", + backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), + textColor: AppColors.warningColorYellow, + ), + SizedBox(height: 16.h), Row( mainAxisSize: MainAxisSize.min, children: [ @@ -124,11 +137,15 @@ class _PrescriptionDetailPageState extends State { spacing: 4.h, runSpacing: 4.h, children: [ - AppCustomChipWidget( - icon: AppAssets.doctor_calendar_icon, - labelText: DateUtil.formatDateToDate( - DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), - labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), + isEnglishOnly: true + , + labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), + ), ), AppCustomChipWidget( labelText: widget.prescriptionsResponseModel.clinicDescription!, @@ -225,9 +242,7 @@ class _PrescriptionDetailPageState extends State { hasShadow: true, ), child: CustomButton( - text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! - ? LocaleKeys.resendOrder.tr(context: context) - : LocaleKeys.prescriptionDeliveryError.tr(context: context), + text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), onPressed: () async { if (widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported!) { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionDetails.tr(context: context)); @@ -237,18 +252,16 @@ class _PrescriptionDetailPageState extends State { }); } }, - backgroundColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.greyF7Color, + backgroundColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.15), borderColor: AppColors.successColor.withOpacity(0.01), - textColor: - widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? Colors.white : AppColors.textColor.withOpacity(0.35), + textColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? Colors.white : AppColors.textColor.withOpacity(0.35), fontSize: 16, fontWeight: FontWeight.w500, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, icon: AppAssets.prescription_refill_icon, - iconColor: - widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? Colors.white : AppColors.textColor.withOpacity(0.35), + iconColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? Colors.white : AppColors.textColor.withOpacity(0.35), iconSize: 20.h, ).paddingSymmetrical(24.h, 24.h), ), diff --git a/lib/presentation/prescriptions/prescription_item_view.dart b/lib/presentation/prescriptions/prescription_item_view.dart index 38b96d37..7f969b21 100644 --- a/lib/presentation/prescriptions/prescription_item_view.dart +++ b/lib/presentation/prescriptions/prescription_item_view.dart @@ -47,7 +47,7 @@ class PrescriptionItemView extends StatelessWidget { fit: BoxFit.fill, ).toShimmer2(isShow: isLoading).circle(100), Expanded( - child: (isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].itemDescription!).toText16(isBold: true, maxlines: 2).toShimmer2(isShow: isLoading), + child: (isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].itemDescription!).toText16(isBold: true, maxlines: 2, isEnglishOnly: true).toShimmer2(isShow: isLoading), ), ], ).paddingSymmetrical(16.h, 0.h), @@ -78,7 +78,9 @@ class PrescriptionItemView extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.h, height: 18.h), SizedBox(width: 9.h), - Expanded(child: "${LocaleKeys.remarks.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].remarks!}".toText10(isBold: true)), + Expanded( + child: "${LocaleKeys.remarks.tr(context: context)} ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].remarks!}".toText10(isBold: true, isEnglishOnly: true), + ), ], ).paddingSymmetrical(16.h, 0.h), SizedBox(height: 14.h), @@ -95,61 +97,65 @@ class PrescriptionItemView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), - "Notify me before the consumption time".toText10(color: AppColors.textColorLight), + "Notify me before the consumption time".toText10(color: AppColors.textColorLight, isEnglishOnly: true), ], ).toShimmer2(isShow: isLoading).expanded, Switch( activeColor: AppColors.successColor, activeTrackColor: AppColors.successColor.withValues(alpha: .15), value: isLoading ? false : prescriptionVM.prescriptionDetailsList[index].hasReminder!, + // value: prescriptionVM.prescriptionDetailsList[index].hasReminder ?? false, onChanged: (newValue) async { CalenderUtilsNew calender = CalenderUtilsNew.instance; - if (prescriptionVM.prescriptionDetailsList[index].hasReminder ?? false) { - LoaderBottomSheet.showLoader(loadingText: "Removing Reminders"); - bool resultValue = await calender.checkAndRemoveMultipleItems(id: prescriptionVM.prescriptionDetailsList[index].itemID.toString()); + if (await prescriptionVM.checkIfReminderExistForPrescription(index)) { + prescriptionVM.prescriptionDetailsList[index].hasReminder = true; + if (prescriptionVM.prescriptionDetailsList[index].hasReminder ?? false) { + LoaderBottomSheet.showLoader(loadingText: "Removing Reminders"); + bool resultValue = await calender.checkAndRemoveMultipleItems(id: prescriptionVM.prescriptionDetailsList[index].itemID.toString()); - prescriptionVM.setPrescriptionItemReminder(newValue, prescriptionVM.prescriptionDetailsList[index]); - LoaderBottomSheet.hideLoader(); - return; + prescriptionVM.setPrescriptionItemReminder(newValue, prescriptionVM.prescriptionDetailsList[index]); + LoaderBottomSheet.hideLoader(); + return; + } + } else { + DateTime startDate = DateTime.now(); + DateTime endDate = DateTime(startDate.year, startDate.month, startDate.day + prescriptionVM.prescriptionDetailsList[index].days!.toInt()); + BottomSheetUtils().showReminderBottomSheet( + context, + endDate, + "", + prescriptionVM.prescriptionDetailsList[index].itemID.toString(), + "", + "", + title: "${prescriptionVM.prescriptionDetailsList[index].itemDescription} Prescription Reminder", + description: + "${prescriptionVM.prescriptionDetailsList[index].itemDescription} ${prescriptionVM.prescriptionDetailsList[index].frequency} ${prescriptionVM.prescriptionDetailsList[index].route} ", + onSuccess: () {}, + isMultiAllowed: true, + onMultiDateSuccess: (int selectedIndex) async { + bool isEventAdded = await calender.createMultipleEvents( + reminderMinutes: selectedIndex, + frequencyNumber: prescriptionVM.prescriptionDetailsList[index].frequencyNumber?.toInt(), + days: prescriptionVM.prescriptionDetailsList[index].days!.toInt(), + orderDate: prescriptionVM.prescriptionDetailsList[index].orderDate!, + itemDescriptionN: prescriptionVM.prescriptionDetailsList[index].itemDescription!, + route: prescriptionVM.prescriptionDetailsList[index].route!, + onFailure: (errorMessage) => prescriptionVM.showError(errorMessage), + prescriptionNumber: prescriptionVM.prescriptionDetailsList[index].itemID.toString(), + ); + prescriptionVM.setPrescriptionItemReminder(isEventAdded, prescriptionVM.prescriptionDetailsList[index]); + // setCalender(context, + // eventId: prescriptionVM.prescriptionDetailsList[index].itemID.toString(), + // selectedMinutes: selectedIndex, + // frequencyNumber: prescriptionVM.prescriptionDetailsList[index].frequencyNumber?.toInt(), + // days: prescriptionVM.prescriptionDetailsList[index].days!.toInt(), + // orderDate: prescriptionVM.prescriptionDetailsList[index].orderDate!, + // itemDescriptionN: prescriptionVM.prescriptionDetailsList[index].itemDescription!, + // route: prescriptionVM.prescriptionDetailsList[index].route!); + }, + ); } - - DateTime startDate = DateTime.now(); - DateTime endDate = DateTime(startDate.year, startDate.month, startDate.day + prescriptionVM.prescriptionDetailsList[index].days!.toInt()); - BottomSheetUtils().showReminderBottomSheet( - context, - endDate, - "", - prescriptionVM.prescriptionDetailsList[index].itemID.toString(), - "", - "", - title: "${prescriptionVM.prescriptionDetailsList[index].itemDescription} Prescription Reminder", - description: - "${prescriptionVM.prescriptionDetailsList[index].itemDescription} ${prescriptionVM.prescriptionDetailsList[index].frequency} ${prescriptionVM.prescriptionDetailsList[index].route} ", - onSuccess: () {}, - isMultiAllowed: true, - onMultiDateSuccess: (int selectedIndex) async { - bool isEventAdded = await calender.createMultipleEvents( - reminderMinutes: selectedIndex, - frequencyNumber: prescriptionVM.prescriptionDetailsList[index].frequencyNumber?.toInt(), - days: prescriptionVM.prescriptionDetailsList[index].days!.toInt(), - orderDate: prescriptionVM.prescriptionDetailsList[index].orderDate!, - itemDescriptionN: prescriptionVM.prescriptionDetailsList[index].itemDescription!, - route: prescriptionVM.prescriptionDetailsList[index].route!, - onFailure: (errorMessage) => prescriptionVM.showError(errorMessage), - prescriptionNumber: prescriptionVM.prescriptionDetailsList[index].itemID.toString(), - ); - prescriptionVM.setPrescriptionItemReminder(isEventAdded, prescriptionVM.prescriptionDetailsList[index]); - // setCalender(context, - // eventId: prescriptionVM.prescriptionDetailsList[index].itemID.toString(), - // selectedMinutes: selectedIndex, - // frequencyNumber: prescriptionVM.prescriptionDetailsList[index].frequencyNumber?.toInt(), - // days: prescriptionVM.prescriptionDetailsList[index].days!.toInt(), - // orderDate: prescriptionVM.prescriptionDetailsList[index].orderDate!, - // itemDescriptionN: prescriptionVM.prescriptionDetailsList[index].itemDescription!, - // route: prescriptionVM.prescriptionDetailsList[index].route!); - }, - ); }, ).toShimmer2(isShow: isLoading), ], diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index cd507d41..3a54926f 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -24,6 +24,8 @@ import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; +import 'dart:ui' as ui; + class PrescriptionsListPage extends StatefulWidget { const PrescriptionsListPage({super.key}); @@ -67,52 +69,52 @@ class _PrescriptionsListPageState extends State { children: [ SizedBox(height: 16.h), // Clinic & Hospital Sort - Row( - children: [ - CustomButton( - text: LocaleKeys.byClinic.tr(context: context), - onPressed: () { - model.setIsSortByClinic(true); - }, - backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), - textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - SizedBox(width: 8.h), - CustomButton( - text: LocaleKeys.byHospital.tr(context: context), - onPressed: () { - model.setIsSortByClinic(false); - }, - backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, - borderColor: model.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor, - textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - ], - ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 20.h), + // Row( + // children: [ + // CustomButton( + // text: LocaleKeys.byClinic.tr(context: context), + // onPressed: () { + // model.setIsSortByClinic(true); + // }, + // backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, + // borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + // textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, + // fontSize: 12, + // fontWeight: FontWeight.w500, + // borderRadius: 10, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // ), + // SizedBox(width: 8.h), + // CustomButton( + // text: LocaleKeys.byHospital.tr(context: context), + // onPressed: () { + // model.setIsSortByClinic(false); + // }, + // backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, + // borderColor: model.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor, + // textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, + // fontSize: 12, + // fontWeight: FontWeight.w500, + // borderRadius: 10, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // ), + // ], + // ).paddingSymmetrical(24.h, 0.h), + // SizedBox(height: 20.h), // Expandable list ListView.builder( itemCount: model.isPrescriptionsOrdersLoading ? 4 : model.patientPrescriptionOrders.isNotEmpty - ? model.patientPrescriptionOrdersViewList.length + ? model.patientPrescriptionOrders.length : 1, physics: NeverScrollableScrollPhysics(), shrinkWrap: true, padding: const EdgeInsets.only(left: 0, right: 8), itemBuilder: (context, index) { - final isExpanded = expandedIndex == index; + // final isExpanded = expandedIndex == index; return model.isPrescriptionsOrdersLoading ? LabResultItemView( onTap: () {}, @@ -132,177 +134,415 @@ class _PrescriptionsListPageState extends State { curve: Curves.easeInOut, margin: EdgeInsets.symmetric(vertical: 8.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), - child: InkWell( - onTap: () { - setState(() { - expandedIndex = isExpanded ? null : index; - }); - }, + child: Container( + key: ValueKey(index), + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CustomButton( - text: "${model.patientPrescriptionOrdersViewList[index].prescriptionsList!.length} Prescriptions Available", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - Icon(isExpanded ? Icons.expand_less : Icons.expand_more), - ], + AppCustomChipWidget( + labelText: + "${getIt.get().isArabic() ? model.patientPrescriptionOrders[index].isInOutPatientDescriptionN : model.patientPrescriptionOrders[index].isInOutPatientDescription}", + backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), + textColor: AppColors.warningColorYellow, + ), + SizedBox(height: 16.h), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.network( + model.patientPrescriptionOrders[index].doctorImageURL!, + width: 24.h, + height: 24.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Expanded(child: model.patientPrescriptionOrders[index].doctorName!.toText14(weight: FontWeight.w500)), + ], + ), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, + children: [ + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(model.patientPrescriptionOrders[index].appointmentDate), false), + isEnglishOnly: true, ), - SizedBox(height: 8.h), - (model.patientPrescriptionOrdersViewList[index].filterName ?? "").toText16(isBold: true) - ], - ), + ), + AppCustomChipWidget( + labelText: model.patientPrescriptionOrders[index].name, + ), + AppCustomChipWidget( + labelText: model.patientPrescriptionOrders[index].clinicDescription!, + ), + ], ), - AnimatedSwitcher( - duration: Duration(milliseconds: 500), - switchInCurve: Curves.easeIn, - switchOutCurve: Curves.easeOut, - transitionBuilder: (Widget child, Animation animation) { - return FadeTransition( - opacity: animation, - child: SizeTransition( - sizeFactor: animation, - axisAlignment: 0.0, - child: child, + SizedBox(height: 8.h), + Row( + children: [ + Expanded( + flex: 6, + child: CustomButton( + text: model.patientPrescriptionOrders[index].isHomeMedicineDeliverySupported! + ? LocaleKeys.resendOrder.tr(context: context) + : LocaleKeys.prescriptionDeliveryError.tr(context: context), + onPressed: () async { + if (model.patientPrescriptionOrders[index].isHomeMedicineDeliverySupported!) { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionDetails.tr(context: context)); + await prescriptionsViewModel.getPrescriptionDetails(prescriptionsViewModel.patientPrescriptionOrders[index], onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + prescriptionsViewModel.initiatePrescriptionDelivery(); + }); + } + }, + backgroundColor: model.patientPrescriptionOrders[index].isHomeMedicineDeliverySupported! + ? AppColors.successColor.withOpacity(0.15) + : AppColors.textColor.withOpacity(0.15), + borderColor: AppColors.successColor.withOpacity(0.01), + textColor: model.patientPrescriptionOrders[index].isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), + fontSize: model.patientPrescriptionOrders[index].isHomeMedicineDeliverySupported! ? 14.f : 12.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.prescription_refill_icon, + iconColor: model.patientPrescriptionOrders[index].isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), + iconSize: 16.h, ), - ); - }, - child: isExpanded - ? Container( - key: ValueKey(index), - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...model.patientPrescriptionOrdersViewList[index].prescriptionsList!.map((prescription) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Image.network( - prescription.doctorImageURL!, - width: 24.h, - height: 24.h, - fit: BoxFit.fill, - ).circle(100), - SizedBox(width: 8.h), - Expanded(child: prescription.doctorName!.toText14(weight: FontWeight.w500)), - ], - ), - SizedBox(height: 8.h), - Wrap( - direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.h, - children: [ - AppCustomChipWidget( - labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(prescription.appointmentDate), false), - ), - AppCustomChipWidget( - labelText: model.isSortByClinic ? prescription.name ?? "" : prescription.clinicDescription!, - ), - ], - ), - SizedBox(height: 8.h), - Row( - children: [ - Expanded( - flex: 6, - child: CustomButton( - text: prescription.isHomeMedicineDeliverySupported! - ? LocaleKeys.resendOrder.tr(context: context) - : LocaleKeys.prescriptionDeliveryError.tr(context: context), - onPressed: () async { - if (prescription.isHomeMedicineDeliverySupported!) { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionDetails.tr(context: context)); - await prescriptionsViewModel.getPrescriptionDetails(prescriptionsViewModel.patientPrescriptionOrders[index], - onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - prescriptionsViewModel.initiatePrescriptionDelivery(); - }); - } - }, - backgroundColor: - prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor.withOpacity(0.15) : AppColors.greyF7Color, - borderColor: AppColors.successColor.withOpacity(0.01), - textColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), - fontSize: prescription.isHomeMedicineDeliverySupported! ? 14.f : 12.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppAssets.prescription_refill_icon, - iconColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), - iconSize: 16.h, - ), - ), - SizedBox(width: 8.h), - Expanded( - flex: 1, - child: Container( - height: 40.h, - width: 40.w, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.textColor, - borderRadius: 12, - ), - child: Padding( - padding: EdgeInsets.all(12.h), - child: Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon_small, - iconColor: AppColors.whiteColor, - fit: BoxFit.contain, - ), - ), - ), - ).onPress(() { - model.setPrescriptionsDetailsLoading(); - Navigator.of(context).push( - CustomPageRoute( - page: PrescriptionDetailPage( - prescriptionsResponseModel: prescription, - isFromAppointments: false, - ), - ), - ); - }), - ), - ], - ), - SizedBox(height: 12.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), - SizedBox(height: 12.h), - ], - ); - }).toList(), - ], + ), + SizedBox(width: 8.h), + Expanded( + flex: 1, + child: Container( + height: 40.h, + width: 40.w, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.textColor, + borderRadius: 12, + ), + child: Padding( + padding: EdgeInsets.all(12.h), + child: Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.whiteColor, + fit: BoxFit.contain, + ), + ), + ), + ).onPress(() { + model.setPrescriptionsDetailsLoading(); + Navigator.of(context).push( + CustomPageRoute( + page: PrescriptionDetailPage( + prescriptionsResponseModel: model.patientPrescriptionOrders[index], + isFromAppointments: false, + ), ), - ) - : SizedBox.shrink(), + ); + }), + ), + ], ), + // SizedBox(height: 12.h), + // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + // SizedBox(height: 12.h), ], ), + // Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // ...model.patientPrescriptionOrders.map((prescription) { + // return Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Image.network( + // prescription.doctorImageURL!, + // width: 24.h, + // height: 24.h, + // fit: BoxFit.fill, + // ).circle(100), + // SizedBox(width: 8.h), + // Expanded(child: prescription.doctorName!.toText14(weight: FontWeight.w500)), + // ], + // ), + // SizedBox(height: 8.h), + // Wrap( + // direction: Axis.horizontal, + // spacing: 6.h, + // runSpacing: 6.h, + // children: [ + // Directionality( + // textDirection: ui.TextDirection.ltr, + // child: AppCustomChipWidget( + // labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(prescription.appointmentDate), false), isEnglishOnly: true, + // ), + // ), + // AppCustomChipWidget( + // labelText: model.isSortByClinic ? prescription.name ?? "" : prescription.clinicDescription!, + // ), + // ], + // ), + // SizedBox(height: 8.h), + // Row( + // children: [ + // Expanded( + // flex: 6, + // child: CustomButton( + // text: prescription.isHomeMedicineDeliverySupported! + // ? LocaleKeys.resendOrder.tr(context: context) + // : LocaleKeys.prescriptionDeliveryError.tr(context: context), + // onPressed: () async { + // if (prescription.isHomeMedicineDeliverySupported!) { + // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionDetails.tr(context: context)); + // await prescriptionsViewModel.getPrescriptionDetails(prescriptionsViewModel.patientPrescriptionOrders[index], + // onSuccess: (val) { + // LoaderBottomSheet.hideLoader(); + // prescriptionsViewModel.initiatePrescriptionDelivery(); + // }); + // } + // }, + // backgroundColor: + // prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor.withOpacity(0.15) : AppColors.greyF7Color, + // borderColor: AppColors.successColor.withOpacity(0.01), + // textColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), + // fontSize: prescription.isHomeMedicineDeliverySupported! ? 14.f : 12.f, + // fontWeight: FontWeight.w500, + // borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // icon: AppAssets.prescription_refill_icon, + // iconColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), + // iconSize: 16.h, + // ), + // ), + // SizedBox(width: 8.h), + // Expanded( + // flex: 1, + // child: Container( + // height: 40.h, + // width: 40.w, + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.textColor, + // borderRadius: 12, + // ), + // child: Padding( + // padding: EdgeInsets.all(12.h), + // child: Transform.flip( + // flipX: appState.isArabic(), + // child: Utils.buildSvgWithAssets( + // icon: AppAssets.forward_arrow_icon_small, + // iconColor: AppColors.whiteColor, + // fit: BoxFit.contain, + // ), + // ), + // ), + // ).onPress(() { + // model.setPrescriptionsDetailsLoading(); + // Navigator.of(context).push( + // CustomPageRoute( + // page: PrescriptionDetailPage( + // prescriptionsResponseModel: prescription, + // isFromAppointments: false, + // ), + // ), + // ); + // }), + // ), + // ], + // ), + // SizedBox(height: 12.h), + // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + // SizedBox(height: 12.h), + // ], + // ); + // }).toList(), + // ], + // ), ), + + // InkWell( + // onTap: () { + // setState(() { + // expandedIndex = isExpanded ? null : index; + // }); + // }, + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Padding( + // padding: EdgeInsets.all(16.h), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // CustomButton( + // text: "${model.patientPrescriptionOrdersViewList[index].prescriptionsList!.length} Prescriptions Available", + // onPressed: () {}, + // backgroundColor: AppColors.greyColor, + // borderColor: AppColors.greyColor, + // textColor: AppColors.blackColor, + // fontSize: 10, + // fontWeight: FontWeight.w500, + // borderRadius: 8, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 30.h, + // ), + // Icon(isExpanded ? Icons.expand_less : Icons.expand_more), + // ], + // ), + // SizedBox(height: 8.h), + // (model.patientPrescriptionOrdersViewList[index].filterName ?? "").toText16(isBold: true) + // ], + // ), + // ), + // AnimatedSwitcher( + // duration: Duration(milliseconds: 500), + // switchInCurve: Curves.easeIn, + // switchOutCurve: Curves.easeOut, + // transitionBuilder: (Widget child, Animation animation) { + // return FadeTransition( + // opacity: animation, + // child: SizeTransition( + // sizeFactor: animation, + // axisAlignment: 0.0, + // child: child, + // ), + // ); + // }, + // // child: isExpanded + // // ? Container( + // // key: ValueKey(index), + // // padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + // // child: Column( + // // crossAxisAlignment: CrossAxisAlignment.start, + // // children: [ + // // ...model.patientPrescriptionOrdersViewList[index].prescriptionsList!.map((prescription) { + // // return Column( + // // crossAxisAlignment: CrossAxisAlignment.start, + // // children: [ + // // Row( + // // mainAxisSize: MainAxisSize.min, + // // children: [ + // // Image.network( + // // prescription.doctorImageURL!, + // // width: 24.h, + // // height: 24.h, + // // fit: BoxFit.fill, + // // ).circle(100), + // // SizedBox(width: 8.h), + // // Expanded(child: prescription.doctorName!.toText14(weight: FontWeight.w500)), + // // ], + // // ), + // // SizedBox(height: 8.h), + // // Wrap( + // // direction: Axis.horizontal, + // // spacing: 6.h, + // // runSpacing: 6.h, + // // children: [ + // // Directionality( + // // textDirection: ui.TextDirection.ltr, + // // child: AppCustomChipWidget( + // // labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(prescription.appointmentDate), false), isEnglishOnly: true, + // // ), + // // ), + // // AppCustomChipWidget( + // // labelText: model.isSortByClinic ? prescription.name ?? "" : prescription.clinicDescription!, + // // ), + // // ], + // // ), + // // SizedBox(height: 8.h), + // // Row( + // // children: [ + // // Expanded( + // // flex: 6, + // // child: CustomButton( + // // text: prescription.isHomeMedicineDeliverySupported! + // // ? LocaleKeys.resendOrder.tr(context: context) + // // : LocaleKeys.prescriptionDeliveryError.tr(context: context), + // // onPressed: () async { + // // if (prescription.isHomeMedicineDeliverySupported!) { + // // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionDetails.tr(context: context)); + // // await prescriptionsViewModel.getPrescriptionDetails(prescriptionsViewModel.patientPrescriptionOrders[index], + // // onSuccess: (val) { + // // LoaderBottomSheet.hideLoader(); + // // prescriptionsViewModel.initiatePrescriptionDelivery(); + // // }); + // // } + // // }, + // // backgroundColor: + // // prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor.withOpacity(0.15) : AppColors.greyF7Color, + // // borderColor: AppColors.successColor.withOpacity(0.01), + // // textColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), + // // fontSize: prescription.isHomeMedicineDeliverySupported! ? 14.f : 12.f, + // // fontWeight: FontWeight.w500, + // // borderRadius: 12.r, + // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // // height: 40.h, + // // icon: AppAssets.prescription_refill_icon, + // // iconColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), + // // iconSize: 16.h, + // // ), + // // ), + // // SizedBox(width: 8.h), + // // Expanded( + // // flex: 1, + // // child: Container( + // // height: 40.h, + // // width: 40.w, + // // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // // color: AppColors.textColor, + // // borderRadius: 12, + // // ), + // // child: Padding( + // // padding: EdgeInsets.all(12.h), + // // child: Transform.flip( + // // flipX: appState.isArabic(), + // // child: Utils.buildSvgWithAssets( + // // icon: AppAssets.forward_arrow_icon_small, + // // iconColor: AppColors.whiteColor, + // // fit: BoxFit.contain, + // // ), + // // ), + // // ), + // // ).onPress(() { + // // model.setPrescriptionsDetailsLoading(); + // // Navigator.of(context).push( + // // CustomPageRoute( + // // page: PrescriptionDetailPage( + // // prescriptionsResponseModel: prescription, + // // isFromAppointments: false, + // // ), + // // ), + // // ); + // // }), + // // ), + // // ], + // // ), + // // SizedBox(height: 12.h), + // // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + // // SizedBox(height: 12.h), + // // ], + // // ); + // // }).toList(), + // // ], + // // ), + // // ) + // // : SizedBox.shrink(), + // ), + // ], + // ), + // ), ), ), ), diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index 2bd41f06..1a10e0de 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -15,14 +15,19 @@ import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart'; +import 'package:hmg_patient_app_new/presentation/profile_settings/widgets/update_email_widget.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/app_language_change.dart'; @@ -48,6 +53,7 @@ class ProfileSettingsState extends State { scheduleMicrotask(() { insuranceViewModel.initInsuranceProvider(); }); + _loadPermissions(); super.initState(); } @@ -86,10 +92,13 @@ class ProfileSettingsState extends State { int length = 3; final SwiperController _controller = SwiperController(); late InsuranceViewModel insuranceViewModel; + late ContactUsViewModel contactUsViewModel; + String _permissionsLabel = ""; @override Widget build(BuildContext context) { insuranceViewModel = Provider.of(context, listen: false); + contactUsViewModel = Provider.of(context, listen: false); return CollapsingListView( title: LocaleKeys.profileAndSettings.tr(context: context), logout: () { @@ -97,102 +106,109 @@ class ProfileSettingsState extends State { }, isClose: true, child: SingleChildScrollView( - padding: EdgeInsets.only(top: 24.h, bottom: 24.h), + padding: EdgeInsets.only(top: 0.h, bottom: 24.h), physics: NeverScrollableScrollPhysics(), child: Consumer2( builder: (context, profileVm, medicalVm, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Swiper( - itemCount: medicalVm.patientFamilyFiles.length, - layout: SwiperLayout.STACK, - loop: true, - itemHeight: dynamicItemHeight(context), - itemWidth: SizeUtils.width - 30.w, - indicatorLayout: PageIndicatorLayout.COLOR, - axisDirection: AxisDirection.right, - controller: _controller, - pagination: SwiperPagination( - alignment: Alignment.bottomCenter, - margin: EdgeInsets.only(top: (210.h + 8.h + 24.h)), - builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), - ), - itemBuilder: (BuildContext context, int index) { - return FamilyCardWidget( - profile: medicalVm.patientFamilyFiles[index], - onAddFamilyMemberPress: () { - DialogService dialogService = getIt.get(); - dialogService.showAddFamilyFileSheet( - label: LocaleKeys.addFamilyMember.tr(context: context), - message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(context: context), - onVerificationPress: () { - medicalVm.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); - }); - }, - onFamilySwitchPress: (FamilyFileResponseModelLists profile) { - medicalVm.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); - }, - ).paddingOnly(right: 16.w, left: 8.w); - }, - ), - SizedBox(height: 5.h), - GridView( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: isTablet ? 3 : 2), - physics: const NeverScrollableScrollPhysics(), - padding: EdgeInsets.only(left: 24.w, right: 24.w, bottom: 24.h), - shrinkWrap: true, - children: [ - Container( - padding: EdgeInsets.all(16.w), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: true, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - // spacing: 4.h, - children: [ - Row( - spacing: 8.w, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h, applyThemeColor: false), - LocaleKeys.habibWallet.tr(context: context).toText16(weight: FontWeight.w600, maxlines: 2).expanded, - Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), - ], - ), - Spacer(), - Consumer(builder: (context, habibWalletVM, child) { - return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount, isExpanded: false) - .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); - }), - Spacer(), - CustomButton( - height: 40.h, - icon: AppAssets.recharge_icon, - iconSize: 22.w, - iconColor: AppColors.infoColor, - textColor: AppColors.infoColor, - text: LocaleKeys.recharge.tr(context: context), - borderWidth: 0.w, - fontWeight: FontWeight.w500, - borderColor: Colors.transparent, - backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), - padding: EdgeInsets.all(8.w), - fontSize: 14.f, - onPressed: () { - Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); - }, - ), - ], - ).onPress(() { - Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage())); - }), - ), - ], - ), + // SizedBox( + // height: dynamicItemHeight(context) + 20 + 30, // itemHeight + shadow padding (10 top + 10 bottom) + pagination dots space + // child: Swiper( + // itemCount: medicalVm.patientFamilyFiles.length, + // layout: SwiperLayout.STACK, + // loop: true, + // itemHeight: dynamicItemHeight(context) + 20, + // // extra space for shadow + // itemWidth: SizeUtils.width - 30.w, + // indicatorLayout: PageIndicatorLayout.COLOR, + // axisDirection: getIt.get().isArabic() ? AxisDirection.left : AxisDirection.right, + // controller: _controller, + // pagination: SwiperPagination( + // alignment: Alignment.bottomCenter, + // margin: EdgeInsets.only(top: (180.h + 20 + 8.h + 24.h)), + // builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), + // ), + // itemBuilder: (BuildContext context, int index) { + // return Padding( + // padding: const EdgeInsets.symmetric(vertical: 10), + // child: FamilyCardWidget( + // profile: medicalVm.patientFamilyFiles[index], + // onAddFamilyMemberPress: () { + // DialogService dialogService = getIt.get(); + // dialogService.showAddFamilyFileSheet( + // label: LocaleKeys.addFamilyMember.tr(context: context), + // message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(context: context), + // onVerificationPress: () { + // medicalVm.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); + // }); + // }, + // onFamilySwitchPress: (FamilyFileResponseModelLists profile) { + // medicalVm.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); + // }, + // ).paddingOnly(right: 16.w, left: 8.w), + // ); + // }, + // ), + // ), + // SizedBox(height: 16.h), + // GridView( + // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: isTablet ? 3 : 2), + // physics: const NeverScrollableScrollPhysics(), + // padding: EdgeInsets.only(left: 24.w, right: 24.w, bottom: 24.h), + // shrinkWrap: true, + // children: [ + // Container( + // padding: EdgeInsets.all(16.w), + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.whiteColor, + // borderRadius: 20.r, + // hasShadow: true, + // ), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // // spacing: 4.h, + // children: [ + // Row( + // spacing: 8.w, + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h, applyThemeColor: false), + // LocaleKeys.habibWallet.tr(context: context).toText16(weight: FontWeight.w600, maxlines: 2).expanded, + // Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), + // ], + // ), + // Spacer(), + // Consumer(builder: (context, habibWalletVM, child) { + // return Utils.getPaymentAmountWithSymbol2(habibWalletVM.habibWalletAmount, isExpanded: false) + // .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); + // }), + // Spacer(), + // CustomButton( + // height: 40.h, + // icon: AppAssets.recharge_icon, + // iconSize: 22.w, + // iconColor: AppColors.infoColor, + // textColor: AppColors.infoColor, + // text: LocaleKeys.recharge.tr(context: context), + // borderWidth: 0.w, + // fontWeight: FontWeight.w500, + // borderColor: Colors.transparent, + // backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), + // padding: EdgeInsets.all(8.w), + // fontSize: 14.f, + // onPressed: () { + // Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); + // }, + // ), + // ], + // ).onPress(() { + // Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage())); + // }), + // ), + // ], + // ), LocaleKeys.quickActions.tr(context: context).toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24.w, right: 24.w), Container( margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 24.h), @@ -226,41 +242,49 @@ class ProfileSettingsState extends State { ], ), ), - Container( - margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 24.h), - padding: EdgeInsets.only(top: 4.h, bottom: 4.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), - child: Column( - children: [ - actionItem(AppAssets.language_change, LocaleKeys.language.tr(context: context), () { - showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.language.tr(context: context), child: AppLanguageChange(), callBackFunc: () {}, isFullScreen: false); - }, trailingLabel: Utils.appState.isArabic() ? "العربية" : "English"), - 1.divider, - actionItem(AppAssets.bell, LocaleKeys.notificationsSettings.tr(context: context), () { - openAppSettings(); - }), - // 1.divider, - // actionItem(AppAssets.touch_face_id, LocaleKeys.touchIDFaceIDServices.tr(context: context), () {}, switchValue: true), - ], - ), - ), - LocaleKeys.personalInformation.tr(context: context).toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24.w, right: 24.w), - Container( - margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 24.h), - padding: EdgeInsets.only(top: 4.h, bottom: 4.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), - child: Column( - children: [ - actionItem(AppAssets.email_transparent, LocaleKeys.updateEmailAddress.tr(context: context), () {}), - // 1.divider, - // actionItem(AppAssets.smart_phone_fill, "Phone Number".needTranslation, () {}), - // 1.divider, - // actionItem(AppAssets.my_address, "My Addresses".needTranslation, () {}), - // 1.divider, - // actionItem(AppAssets.emergency, "Emergency Contact".needTranslation, () {}), - ], - ), - ), + // Container( + // margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 24.h), + // padding: EdgeInsets.only(top: 4.h, bottom: 4.h), + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), + // child: Column( + // children: [ + // actionItem(AppAssets.language_change, LocaleKeys.language.tr(context: context), () { + // showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.language.tr(context: context), child: AppLanguageChange(), callBackFunc: () {}, isFullScreen: false); + // }, trailingLabel: Utils.appState.isArabic() ? "العربية" : "English"), + // 1.divider, + // actionItem(AppAssets.bell, LocaleKeys.notificationsSettings.tr(context: context), () { + // openAppSettings(); + // }), + // // 1.divider, + // // actionItem(AppAssets.touch_face_id, LocaleKeys.touchIDFaceIDServices.tr(context: context), () {}, switchValue: true), + // ], + // ), + // ), + // LocaleKeys.personalInformation.tr(context: context).toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24.w, right: 24.w), + // Container( + // margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 24.h), + // padding: EdgeInsets.only(top: 4.h, bottom: 4.h), + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), + // child: Column( + // children: [ + // actionItem(AppAssets.email_transparent, LocaleKeys.updateEmailAddress.tr(context: context), () { + // showCommonBottomSheetWithoutHeight( + // context, + // title: LocaleKeys.updateEmailAddress.tr(context: context), + // child: UpdateEmailDialog(), + // callBackFunc: () {}, + // isFullScreen: false, + // ); + // }), + // // 1.divider, + // // actionItem(AppAssets.smart_phone_fill, "Phone Number".needTranslation, () {}), + // // 1.divider, + // // actionItem(AppAssets.my_address, "My Addresses".needTranslation, () {}), + // // 1.divider, + // // actionItem(AppAssets.emergency, "Emergency Contact".needTranslation, () {}), + // ], + // ), + // ), LocaleKeys.helpAndSupport.tr(context: context).toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24.w, right: 24.w), Container( margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h), @@ -272,6 +296,22 @@ class ProfileSettingsState extends State { launchUrl(Uri.parse("tel://" + "+966 92 006 6666")); }, trailingLabel: "92 006 6666"), 1.divider, + actionItem(AppAssets.permission, LocaleKeys.permissionsProfile.tr(context: context), () { + openAppSettings(); + }, trailingLabel: getCurrentPermissions()), + actionItem(AppAssets.feedbackFill, LocaleKeys.feedback.tr(context: context), () { + contactUsViewModel.setSelectedFeedbackType( + FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'), + ); + contactUsViewModel.setIsSendFeedbackTabSelected(true); + Navigator.pop(context); + Navigator.of(context).push( + CustomPageRoute( + page: FeedbackPage(), + ), + ); + }, trailingLabel: ""), + 1.divider, // actionItem(AppAssets.permission, LocaleKeys.permissions.tr(context: context), () {}, trailingLabel: "Location, Camera"), // 1.divider, actionItem(AppAssets.rate, LocaleKeys.rateApp.tr(context: context), () { @@ -314,6 +354,34 @@ class ProfileSettingsState extends State { ); } + Future _loadPermissions() async { + final Map permissionMap = { + 'Camera': Permission.camera, + 'Microphone': Permission.microphone, + 'Location': Permission.location, + 'Notifications': Permission.notification, + 'Calendar': Permission.calendarFullAccess, + }; + + final List granted = []; + + for (final entry in permissionMap.entries) { + if (await entry.value.isGranted) { + granted.add(entry.key); + } + } + + if (mounted) { + setState(() { + _permissionsLabel = granted.isEmpty ? 'No permissions granted' : granted.join(', '); + }); + } + } + + String getCurrentPermissions() { + return _permissionsLabel; + } + Widget actionItem(String icon, String label, VoidCallback onPress, {String trailingLabel = "", bool? switchValue, ValueChanged? onSwitchChanged, bool isExternalLink = false}) { return SizedBox( height: 56.h, @@ -361,9 +429,7 @@ class FamilyCardWidget extends StatelessWidget { return Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), + borderRadius: 24.r, hasShadow: true, hasDenseShadow: true), child: Column( children: [ Column( @@ -378,11 +444,25 @@ class FamilyCardWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - "${profile.patientName}".toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1), - AppCustomChipWidget( - icon: AppAssets.file_icon, - labelText: "${LocaleKeys.fileNo.tr(context: context)}: ${profile.responseId}", - iconSize: 12.w, + "${profile.patientName}".toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1, isEnglishOnly: true), + Wrap( + direction: Axis.horizontal, + spacing: 4.w, + runSpacing: 6.w, + children: [ + AppCustomChipWidget( + labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), + icon: AppAssets.file_icon, + labelText: "${LocaleKeys.fileno.tr(context: context)}: ${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(), + ], ), ], ).expanded, @@ -400,18 +480,20 @@ class FamilyCardWidget extends StatelessWidget { AppCustomChipWidget( labelText: LocaleKeys.ageYearsOld.tr(namedArgs: {'age': profile.age.toString(), 'yearsOld': LocaleKeys.yearsOld.tr(context: context)}), ), - isActive && appState.getAuthenticatedUser()!.bloodGroup != null - ? AppCustomChipWidget( - icon: AppAssets.blood_icon, + // isActive && appState.getAuthenticatedUser()!.bloodGroup != null + // ? + isActive ? AppCustomChipWidget( + icon: AppAssets.blood_icon, labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 8.w), - labelText: "Blood: ${appState.getAuthenticatedUser()!.bloodGroup ?? ""}", - iconColor: AppColors.primaryRedColor) - : SizedBox(), + labelText: appState.getAuthenticatedUser()!.bloodGroup ?? "N/A", + iconColor: AppColors.primaryRedColor) + : SizedBox(), Consumer(builder: (context, insuranceVM, child) { - return AppCustomChipWidget( - icon: insuranceVM.isInsuranceExpired - ? AppAssets.cancel_circle_icon - : insuranceVM.isInsuranceActive + return isActive + ? AppCustomChipWidget( + icon: insuranceVM.isInsuranceExpired + ? AppAssets.cancel_circle_icon + : insuranceVM.isInsuranceActive ? AppAssets.insurance_active_icon : AppAssets.alertSquare, labelText: insuranceVM.isInsuranceExpired @@ -431,27 +513,34 @@ class FamilyCardWidget extends StatelessWidget { : AppColors.warningColorYellow, iconSize: 12.w, deleteIcon: insuranceVM.isInsuranceActive ? null : AppAssets.forward_chevron_icon, - deleteIconColor: AppColors.warningColorYellow, + deleteIconColor: insuranceVM.isInsuranceExpired + ? AppColors.primaryRedColor + : insuranceVM.isInsuranceActive + ? AppColors.successColor + : AppColors.warningColorYellow, deleteIconHasColor: true, onChipTap: () { if (!insuranceVM.isInsuranceActive) { - 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, - ); + 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 @@ -460,7 +549,8 @@ class FamilyCardWidget extends StatelessWidget { ? AppColors.successColor.withOpacity(0.1) : AppColors.warningColorYellow.withOpacity(0.1), labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: insuranceVM.isInsuranceActive ? 6.w : 0.w), - ).toShimmer2(isShow: insuranceVM.isInsuranceLoading); + ).toShimmer2(isShow: insuranceVM.isInsuranceLoading) + : SizedBox.shrink(); }), ], ), diff --git a/lib/presentation/profile_settings/widgets/update_email_widget.dart b/lib/presentation/profile_settings/widgets/update_email_widget.dart new file mode 100644 index 00000000..ad9b9d5a --- /dev/null +++ b/lib/presentation/profile_settings/widgets/update_email_widget.dart @@ -0,0 +1,106 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:provider/provider.dart'; + +class UpdateEmailDialog extends StatefulWidget { + UpdateEmailDialog({super.key}); + + @override + State createState() => _UpdateEmailDialogState(); +} + +class _UpdateEmailDialogState extends State { + late FocusNode _textFieldFocusNode; + late TextEditingController? textController; + ProfileSettingsViewModel? profileSettingsViewModel; + + @override + void initState() { + _textFieldFocusNode = FocusNode(); + textController = TextEditingController(); + textController!.text = getIt.get().getAuthenticatedUser()!.emailAddress ?? ""; + super.initState(); + } + + @override + void dispose() { + _textFieldFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + profileSettingsViewModel = Provider.of(context); + return GestureDetector( + onTap: () { + _textFieldFocusNode.unfocus(); + FocusScope.of(context).unfocus(); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Enter the new email address to be updated in your HMG File: ".toText16(textAlign: TextAlign.start, weight: FontWeight.w500), + SizedBox(height: 12.h), + TextInputWidget( + labelText: LocaleKeys.email.tr(), + hintText: "demo@gmail.com", + controller: textController, + focusNode: _textFieldFocusNode, + autoFocus: true, + padding: EdgeInsets.all(8.h), + keyboardType: TextInputType.emailAddress, + isEnable: true, + isReadOnly: false, + prefix: null, + isBorderAllowed: false, + isAllowLeadingIcon: true, + fontSize: 14.f, + isCountryDropDown: false, + leadingIcon: AppAssets.email, + fontFamily: "Poppins", + ), + SizedBox(height: 12.h), + CustomButton( + text: LocaleKeys.submit.tr(context: context), + onPressed: () { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.updatingEmailAddress.tr(context: context)); + profileSettingsViewModel!.updatePatientInfo( + patientInfo: {"EmailAddress": textController!.text}, + onSuccess: (response) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), + callBackFunc: () async { + Navigator.of(context).pop(); + }, isFullScreen: false); + }, + onError: (error) { + LoaderBottomSheet.hideLoader(); + // Show error message + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(error)), + ); + }, + ); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: const Color(0xFFffffff), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart index a07face1..1a3ab448 100644 --- a/lib/presentation/radiology/radiology_orders_page.dart +++ b/lib/presentation/radiology/radiology_orders_page.dart @@ -3,6 +3,8 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; @@ -24,6 +26,8 @@ import 'package:provider/provider.dart'; import '../../features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'dart:ui' as ui; + class RadiologyOrdersPage extends StatefulWidget { const RadiologyOrdersPage({super.key}); @@ -94,40 +98,40 @@ class _RadiologyOrdersPageState extends State { children: [ // Clinic / Hospital toggle SizedBox(height: 16.h), - Row( - children: [ - CustomButton( - text: LocaleKeys.byClinic.tr(context: context), - onPressed: () { - model.setIsSortByClinic(true); - }, - backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), - textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - SizedBox(width: 8.h), - CustomButton( - text: LocaleKeys.byHospital.tr(context: context), - onPressed: () { - model.setIsSortByClinic(false); - }, - backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, - borderColor: model.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, - textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - ], - ), - SizedBox(height: 8.h), + // Row( + // children: [ + // CustomButton( + // text: LocaleKeys.byClinic.tr(context: context), + // onPressed: () { + // model.setIsSortByClinic(true); + // }, + // backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, + // borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2), + // textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, + // fontSize: 12, + // fontWeight: FontWeight.w500, + // borderRadius: 10, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // ), + // SizedBox(width: 8.h), + // CustomButton( + // text: LocaleKeys.byHospital.tr(context: context), + // onPressed: () { + // model.setIsSortByClinic(false); + // }, + // backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, + // borderColor: model.isSortByClinic ? AppColors.textColor.withValues(alpha: 0.2) : AppColors.primaryRedColor, + // textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, + // fontSize: 12, + // fontWeight: FontWeight.w500, + // borderRadius: 10, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // ), + // ], + // ), + // SizedBox(height: 8.h), selectedFilterText.isNotEmpty ? AppCustomChipWidget( padding: EdgeInsets.symmetric(horizontal: 5.h), @@ -165,199 +169,525 @@ class _RadiologyOrdersPageState extends State { ); } - if (model.patientRadiologyOrdersViewList.isEmpty) { + if (model.patientRadiologyOrders.isEmpty) { return Utils.getNoDataWidget(ctx, noDataText: LocaleKeys.youDontHaveRadiologyOrders.tr(context: context)); } - return ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, - itemCount: model.patientRadiologyOrdersViewList.length, - itemBuilder: (context, index) { - final group = model.patientRadiologyOrdersViewList[index]; - final displayName = model.isSortByClinic ? (group.first.clinicDescription ?? 'Unknown') : (group.first.projectName ?? 'Unknown'); - final isExpanded = expandedIndex == index; - return AnimationConfiguration.staggeredList( + // return ListView.builder( + // shrinkWrap: true, + // physics: NeverScrollableScrollPhysics(), + // padding: EdgeInsets.zero, + // itemCount: model.patientRadiologyOrdersViewList.length, + // itemBuilder: (context, index) { + // final group = model.patientRadiologyOrdersViewList[index]; + // final displayName = model.isSortByClinic ? (group.first.clinicDescription ?? 'Unknown') : (group.first.projectName ?? 'Unknown'); + // final isExpanded = expandedIndex == index; + // return AnimationConfiguration.staggeredList( + // position: index, + // duration: const Duration(milliseconds: 400), + // child: SlideAnimation( + // verticalOffset: 50.0, + // child: FadeInAnimation( + // child: AnimatedContainer( + // duration: const Duration(milliseconds: 300), + // curve: Curves.easeInOut, + // margin: EdgeInsets.symmetric(vertical: 8.h), + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.whiteColor, + // borderRadius: 20.h, + // hasShadow: true, + // ), + // child: InkWell( + // onTap: () { + // setState(() { + // expandedIndex = isExpanded ? null : index; + // }); + // WidgetsBinding.instance.addPostFrameCallback((_) { + // final key = _groupKeys.putIfAbsent(index, () => GlobalKey()); + // if (key.currentContext != null && expandedIndex == index) { + // Future.delayed(const Duration(milliseconds: 450), () { + // if (key.currentContext != null) { + // Scrollable.ensureVisible( + // key.currentContext!, + // duration: const Duration(milliseconds: 350), + // curve: Curves.easeInOut, + // alignment: 0.0, + // ); + // } + // }); + // } + // }); + // }, + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Padding( + // key: _groupKeys.putIfAbsent(index, () => GlobalKey()), + // padding: EdgeInsets.all(16.h), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // AppCustomChipWidget(labelText: "${group.length} ${LocaleKeys.results.tr(context: context)}"), + // Icon(isExpanded ? Icons.expand_less : Icons.expand_more), + // ], + // ), + // SizedBox(height: 8.h), + // Text( + // displayName, + // style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600), + // overflow: TextOverflow.ellipsis, + // ), + // ], + // ), + // ), + // AnimatedSwitcher( + // duration: const Duration(milliseconds: 500), + // switchInCurve: Curves.easeIn, + // switchOutCurve: Curves.easeOut, + // transitionBuilder: (Widget child, Animation animation) { + // return FadeTransition( + // opacity: animation, + // child: SizeTransition( + // sizeFactor: animation, + // axisAlignment: 0.0, + // child: child, + // ), + // ); + // }, + // child: isExpanded + // ? Container( + // key: ValueKey(index), + // padding: EdgeInsets.symmetric(horizontal: 16.w), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // ...group.map((order) { + // return Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Image.network( + // order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + // width: 24.w, + // height: 24.h, + // fit: BoxFit.cover, + // ).circle(100), + // SizedBox(width: 8.h), + // Expanded( + // child: (order.doctorName ?? '').toString().toText14(weight: FontWeight.w500), + // ), + // ], + // ), + // SizedBox(height: 8.h), + // Wrap( + // direction: Axis.horizontal, + // spacing: 4.h, + // runSpacing: 4.h, + // children: [ + // if ((order.description ?? '').isNotEmpty) + // AppCustomChipWidget( + // labelText: (order.description ?? '').toString(), + // ), + // Directionality( + // textDirection: ui.TextDirection.ltr, + // child: AppCustomChipWidget( + // labelText: DateUtil.formatDateToDate( + // (order.orderDate ?? order.appointmentDate), + // false, + // ), isEnglishOnly: true, + // ), + // ), + // AppCustomChipWidget( + // labelText: model.isSortByClinic ? (order.projectName ?? '') : (order.clinicDescription ?? ''), + // ), + // ], + // ), + // SizedBox(height: 12.h), + // Row( + // children: [ + // Expanded(flex: 2, child: const SizedBox()), + // Expanded( + // flex: 2, + // child: CustomButton( + // icon: AppAssets.view_report_icon, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.h, + // text: LocaleKeys.viewResults.tr(context: context), + // onPressed: () { + // model.navigationService.push( + // CustomPageRoute( + // page: RadiologyResultPage(patientRadiologyResponseModel: order), + // ), + // ); + // }, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: 14, + // fontWeight: FontWeight.w500, + // borderRadius: 12, + // padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // ), + // ), + // ], + // ), + // SizedBox(height: 12.h), + // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + // SizedBox(height: 12.h), + // ], + // ); + // }).toList(), + // ], + // ), + // ) + // : const SizedBox.shrink(), + // ), + // ], + // ), + // ), + // ), + // ), + // ), + // ); + // }, + // ); + + return ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + itemCount: model.patientRadiologyOrders.length, + itemBuilder: (context, index) { + final group = model.patientRadiologyOrders[index]; + final isExpanded = expandedIndex == index; + return AnimationConfiguration.staggeredList( position: index, - duration: const Duration(milliseconds: 400), + duration: const Duration(milliseconds: 500), child: SlideAnimation( - verticalOffset: 50.0, + verticalOffset: 100.0, child: FadeInAnimation( child: AnimatedContainer( - duration: const Duration(milliseconds: 300), + duration: Duration(milliseconds: 300), curve: Curves.easeInOut, margin: EdgeInsets.symmetric(vertical: 8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: true, - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), child: InkWell( onTap: () { setState(() { expandedIndex = isExpanded ? null : index; }); - WidgetsBinding.instance.addPostFrameCallback((_) { - final key = _groupKeys.putIfAbsent(index, () => GlobalKey()); - if (key.currentContext != null && expandedIndex == index) { - Future.delayed(const Duration(milliseconds: 450), () { - if (key.currentContext != null) { - Scrollable.ensureVisible( - key.currentContext!, - duration: const Duration(milliseconds: 350), - curve: Curves.easeInOut, - alignment: 0.0, - ); - } - }); - } - }); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - key: _groupKeys.putIfAbsent(index, () => GlobalKey()), padding: EdgeInsets.all(16.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + AppCustomChipWidget( + labelText: "${getIt.get().isArabic() ? group.isInOutPatientDescriptionN : group.isInOutPatientDescription}", + backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), + textColor: AppColors.warningColorYellow, + ), + SizedBox(height: 16.h), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, children: [ - AppCustomChipWidget(labelText: "${group.length} ${LocaleKeys.results.tr(context: context)}"), - Icon(isExpanded ? Icons.expand_less : Icons.expand_more), + Image.network( + group.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + width: 24.w, + height: 24.h, + fit: BoxFit.cover, + ).circle(100), + SizedBox(width: 8.h), + Expanded(child: (group.doctorName ?? "").toString().toText14(weight: FontWeight.w500)), ], ), SizedBox(height: 8.h), - Text( - displayName, - style: TextStyle(fontSize: 16.h, fontWeight: FontWeight.w600), - overflow: TextOverflow.ellipsis, + Wrap( + direction: Axis.horizontal, + spacing: 4.h, + runSpacing: 4.h, + children: [ + Directionality( + textDirection: ui.TextDirection.ltr, + child: AppCustomChipWidget( + labelText: DateUtil.formatDateToDate(group.orderDate!, false), + isEnglishOnly: true, + )), + AppCustomChipWidget( + labelText: (group.projectName ?? ""), + ), + AppCustomChipWidget( + labelText: (group.clinicDescription ?? ""), + ), + ], ), + SizedBox(height: 16.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "• ${group.procedureName.toString().trim() ?? ""}".toText14(weight: FontWeight.w500), + // "Lorem ipsum text".toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + SizedBox(height: 16.h), + CustomButton( + text: LocaleKeys.viewReport.tr(), + onPressed: () { + model.navigationService.push( + CustomPageRoute( + page: RadiologyResultPage(patientRadiologyResponseModel: group), + ), + ); + }, + backgroundColor: AppColors.infoColor.withAlpha(20), + borderColor: AppColors.infoColor.withAlpha(0), + textColor: AppColors.infoColor, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), + height: 40.h, + iconSize: 14.h, + icon: AppAssets.view_report_icon, + iconColor: AppColors.infoColor, + ), + ], + ) ], ), ), - AnimatedSwitcher( - duration: const Duration(milliseconds: 500), - switchInCurve: Curves.easeIn, - switchOutCurve: Curves.easeOut, - transitionBuilder: (Widget child, Animation animation) { - return FadeTransition( - opacity: animation, - child: SizeTransition( - sizeFactor: animation, - axisAlignment: 0.0, - child: child, - ), - ); - }, - child: isExpanded - ? Container( - key: ValueKey(index), - padding: EdgeInsets.symmetric(horizontal: 16.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...group.map((order) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Image.network( - order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", - width: 24.w, - height: 24.h, - fit: BoxFit.cover, - ).circle(100), - SizedBox(width: 8.h), - Expanded( - child: (order.doctorName ?? '').toString().toText14(weight: FontWeight.w500), - ), - ], - ), - SizedBox(height: 8.h), - Wrap( - direction: Axis.horizontal, - spacing: 4.h, - runSpacing: 4.h, - children: [ - if ((order.description ?? '').isNotEmpty) - AppCustomChipWidget( - labelText: (order.description ?? '').toString(), - ), - AppCustomChipWidget( - labelText: DateUtil.formatDateToDate( - (order.orderDate ?? order.appointmentDate), - false, - ), - ), - AppCustomChipWidget( - labelText: model.isSortByClinic ? (order.projectName ?? '') : (order.clinicDescription ?? ''), - ), - ], - ), - SizedBox(height: 12.h), - Row( - children: [ - Expanded(flex: 2, child: const SizedBox()), - Expanded( - flex: 2, - child: CustomButton( - icon: AppAssets.view_report_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - text: LocaleKeys.viewResults.tr(context: context), - onPressed: () { - model.navigationService.push( - CustomPageRoute( - page: RadiologyResultPage(patientRadiologyResponseModel: order), - ), - ); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - ), - ], - ), - SizedBox(height: 12.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), - SizedBox(height: 12.h), - ], - ); - }).toList(), - ], - ), - ) - : const SizedBox.shrink(), - ), + // AnimatedSwitcher( + // duration: Duration(milliseconds: 500), + // switchInCurve: Curves.easeIn, + // switchOutCurve: Curves.easeOut, + // transitionBuilder: (Widget child, Animation animation) { + // return FadeTransition( + // opacity: animation, + // child: SizeTransition( + // sizeFactor: animation, + // axisAlignment: 0.0, + // child: child, + // ), + // ); + // }, + // child: isExpanded + // ? Container( + // key: ValueKey(index), + // padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 0.h), + // child: Column( + // children: [ + // ListView.separated( + // shrinkWrap: true, + // physics: NeverScrollableScrollPhysics(), + // padding: EdgeInsets.zero, + // itemBuilder: (cxt, index) { + // PatientRadiologyResponseModel order = group; + // return Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // "• ${order.procedureName ?? ""}".toText14(weight: FontWeight.w500), + // "Lorem ipsum text".toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + // // SizedBox(height: 4.h), + // // order.testDetails![index].testDescriptionEn!.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + // // Row( + // // mainAxisSize: MainAxisSize.min, + // // children: [ + // // Image.network( + // // order.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + // // width: 24.w, + // // height: 24.h, + // // fit: BoxFit.cover, + // // ).circle(100), + // // SizedBox(width: 8.h), + // // Expanded(child: (order.doctorName ?? order.doctorNameEnglish ?? "").toString().toText14(weight: FontWeight.w500)), + // // ], + // // ), + // // SizedBox(height: 8.h), + // // Wrap( + // // direction: Axis.horizontal, + // // spacing: 4.h, + // // runSpacing: 4.h, + // // children: [ + // // AppCustomChipWidget( + // // labelText: ("${LocaleKeys.orderNo.tr(context: context)}: ${order.orderNo!}"), isEnglishOnly: true, + // // ), + // // Directionality( + // // textDirection: ui.TextDirection.ltr, + // // child: AppCustomChipWidget( + // // labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), + // // isEnglishOnly: true, + // // )), + // // AppCustomChipWidget( + // // labelText: labViewModel.isSortByClinic ? (order.projectName ?? "") : (order.clinicDescription ?? ""), + // // ), + // // ], + // // ), + // // // Row( + // // // children: [ + // // // CustomButton( + // // // text: ("Order No: ".needTranslation + order.orderNo!), + // // // onPressed: () {}, + // // // backgroundColor: AppColors.greyColor, + // // // borderColor: AppColors.greyColor, + // // // textColor: AppColors.blackColor, + // // // fontSize: 10, + // // // fontWeight: FontWeight.w500, + // // // borderRadius: 8, + // // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // // // height: 24.h, + // // // ), + // // // SizedBox(width: 8.h), + // // // CustomButton( + // // // text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(order.orderDate ?? ""), false), + // // // onPressed: () {}, + // // // backgroundColor: AppColors.greyColor, + // // // borderColor: AppColors.greyColor, + // // // textColor: AppColors.blackColor, + // // // fontSize: 10, + // // // fontWeight: FontWeight.w500, + // // // borderRadius: 8, + // // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // // // height: 24.h, + // // // ), + // // // ], + // // // ), + // // // SizedBox(height: 8.h), + // // // Row( + // // // children: [ + // // // CustomButton( + // // // text: model.isSortByClinic ? (order.clinicDescription ?? "") : (order.projectName ?? ""), + // // // onPressed: () {}, + // // // backgroundColor: AppColors.greyColor, + // // // borderColor: AppColors.greyColor, + // // // textColor: AppColors.blackColor, + // // // fontSize: 10, + // // // fontWeight: FontWeight.w500, + // // // borderRadius: 8, + // // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // // // height: 24.h, + // // // ), + // // // ], + // // // ), + // // SizedBox(height: 12.h), + // // Row( + // // children: [ + // // Expanded(flex: 2, child: SizedBox()), + // // // Expanded( + // // // flex: 1, + // // // child: Container( + // // // height: 40.h, + // // // width: 40.w, + // // // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // // // color: AppColors.textColor, + // // // borderRadius: 12, + // // // ), + // // // child: Padding( + // // // padding: EdgeInsets.all(12.h), + // // // child: Transform.flip( + // // // flipX: _appState.isArabic(), + // // // child: Utils.buildSvgWithAssets( + // // // icon: AppAssets.forward_arrow_icon_small, + // // // iconColor: AppColors.whiteColor, + // // // fit: BoxFit.contain, + // // // ), + // // // ), + // // // ), + // // // ).onPress(() { + // // // model.currentlySelectedPatientOrder = order; + // // // labProvider.getPatientLabResultByHospital(order); + // // // labProvider.getPatientSpecialResult(order); + // // // Navigator.of(context).push( + // // // CustomPageRoute(page: LabResultByClinic(labOrder: order)), + // // // ); + // // // }), + // // // ) + // // + // // Expanded( + // // flex: 2, + // // child: CustomButton( + // // icon: AppAssets.view_report_icon, + // // iconColor: AppColors.primaryRedColor, + // // iconSize: 16.h, + // // text: LocaleKeys.viewResults.tr(context: context), + // // onPressed: () { + // // labViewModel.currentlySelectedPatientOrder = order; + // // labProvider.getPatientLabResultByHospital(order); + // // labProvider.getPatientSpecialResult(order); + // // Navigator.of(context).push( + // // CustomPageRoute(page: LabResultByClinic(labOrder: order)), + // // ); + // // }, + // // backgroundColor: AppColors.secondaryLightRedColor, + // // borderColor: AppColors.secondaryLightRedColor, + // // textColor: AppColors.primaryRedColor, + // // fontSize: 14, + // // fontWeight: FontWeight.w500, + // // borderRadius: 12, + // // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // // height: 40.h, + // // ), + // // ) + // // ], + // // ), + // // SizedBox(height: 12.h), + // // Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + // // SizedBox(height: 12.h), + // ], + // ).paddingOnly(top: 16, bottom: 16); + // }, + // separatorBuilder: (cxt, index) => Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + // itemCount: 1), + // SizedBox(height: 16.h), + // CustomButton( + // text: LocaleKeys.viewReport.tr(), + // onPressed: () { + // model.navigationService.push( + // CustomPageRoute( + // page: RadiologyResultPage(patientRadiologyResponseModel: group), + // ), + // ); + // }, + // backgroundColor: AppColors.infoColor.withAlpha(20), + // borderColor: AppColors.infoColor.withAlpha(0), + // textColor: AppColors.infoColor, + // fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + // fontWeight: FontWeight.w500, + // borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), + // height: 40.h, + // iconSize: 14.h, + // icon: AppAssets.view_report_icon, + // iconColor: AppColors.infoColor, + // ), + // SizedBox(height: 16.h), + // ], + // ), + // ) + // : SizedBox.shrink(), + // ), ], ), ), ), ), - ), - ); - }, - ); - }), - ], - ), - ); - }, - ), + )); + }, + ); + }), + ], + ), + ); + }, ), ), + ), ); } diff --git a/lib/presentation/radiology/radiology_result_page.dart b/lib/presentation/radiology/radiology_result_page.dart index 7b3f11d4..05f23530 100644 --- a/lib/presentation/radiology/radiology_result_page.dart +++ b/lib/presentation/radiology/radiology_result_page.dart @@ -20,6 +20,7 @@ import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:open_filex/open_filex.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'dart:ui' as ui; class RadiologyResultPage extends StatefulWidget { RadiologyResultPage({super.key, required this.patientRadiologyResponseModel}); @@ -52,6 +53,48 @@ class _RadiologyResultPageState extends State { Expanded( child: CollapsingListView( title: LocaleKeys.radiologyResult.tr(context: context), + downloadReport: () async { + LoaderBottomSheet.showLoader(); + await radiologyViewModel + .getRadiologyPDF( + patientRadiologyResponseModel: widget.patientRadiologyResponseModel, + authenticatedUser: _appState.getAuthenticatedUser()!, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }) + .then((val) async { + LoaderBottomSheet.hideLoader(); + if (radiologyViewModel.patientRadiologyReportPDFBase64.isNotEmpty) { + String path = await Utils.createFileFromString(radiologyViewModel.patientRadiologyReportPDFBase64, "pdf"); + try { + OpenFilex.open(path); + } catch (ex) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Cannot open file"), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + } + }); + }, + // viewImage: () { + // if (radiologyViewModel.radiologyImageURL.isNotEmpty) { + // Uri uri = Uri.parse(radiologyViewModel.radiologyImageURL); + // launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); + // } else { + // Utils.showToast("Radiology image not available"); + // } + // }, child: SingleChildScrollView( child: Padding( padding: EdgeInsets.symmetric(horizontal: 24.h), @@ -71,31 +114,34 @@ class _RadiologyResultPageState extends State { SizedBox(height: 16.h), // widget.patientRadiologyResponseModel.description!.toText16(isBold: true), SizedBox(height: 8.h), - widget.patientRadiologyResponseModel.reportData!.trim().toText12(isBold: true, color: AppColors.textColorLight), - SizedBox(height: 16.h), - CustomButton( - text: LocaleKeys.viewRadiologyImage.tr(context: context), - onPressed: () async { - if (radiologyViewModel.radiologyImageURL.isNotEmpty) { - Uri uri = Uri.parse(radiologyViewModel.radiologyImageURL); - launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); - } else { - Utils.showToast("Radiology image not available"); - } - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: Colors.white, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppAssets.download, - iconColor: Colors.white, - iconSize: 20.h, + Directionality( + textDirection: ui.TextDirection.ltr, + child: widget.patientRadiologyResponseModel.reportData!.trim().toText12(isBold: true, color: AppColors.textColorLight, isEnglishOnly: true), ), SizedBox(height: 16.h), + // CustomButton( + // text: LocaleKeys.viewRadiologyImage.tr(context: context), + // onPressed: () async { + // if (radiologyViewModel.radiologyImageURL.isNotEmpty) { + // Uri uri = Uri.parse(radiologyViewModel.radiologyImageURL); + // launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); + // } else { + // Utils.showToast("Radiology image not available"); + // } + // }, + // backgroundColor: AppColors.primaryRedColor, + // borderColor: AppColors.primaryRedColor, + // textColor: Colors.white, + // fontSize: 14, + // fontWeight: FontWeight.w500, + // borderRadius: 12, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // icon: AppAssets.download, + // iconColor: Colors.white, + // iconSize: 20.h, + // ), + // SizedBox(height: 16.h), ], ).paddingSymmetrical(16.h, 0.h), ), @@ -112,49 +158,28 @@ class _RadiologyResultPageState extends State { borderRadius: 24.h, hasShadow: true, ), - child: CustomButton( - text: LocaleKeys.downloadReport.tr(context: context), + child: widget.patientRadiologyResponseModel.dIAPACSURL != "" ? CustomButton( + text: LocaleKeys.openRad.tr(context: context), onPressed: () async { - LoaderBottomSheet.showLoader(); - await radiologyViewModel.getRadiologyPDF(patientRadiologyResponseModel: widget.patientRadiologyResponseModel, authenticatedUser: _appState.getAuthenticatedUser()!, onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }).then((val) async { - LoaderBottomSheet.hideLoader(); - if (radiologyViewModel.patientRadiologyReportPDFBase64.isNotEmpty) { - String path = await Utils.createFileFromString(radiologyViewModel.patientRadiologyReportPDFBase64, "pdf"); - try { - OpenFilex.open(path); - } catch (ex) { - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: "Cannot open file"), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - } - } - }); + if (radiologyViewModel.radiologyImageURL.isNotEmpty) { + Uri uri = Uri.parse(radiologyViewModel.radiologyImageURL); + launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); + } else { + Utils.showToast("Radiology image not available"); + } }, - backgroundColor: AppColors.successColor, - borderColor: AppColors.successColor, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, textColor: Colors.white, fontSize: 16, fontWeight: FontWeight.w500, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 45.h, - icon: AppAssets.download, + icon: AppAssets.imageIcon, iconColor: Colors.white, iconSize: 20.h, - ).paddingSymmetrical(24.h, 24.h), + ).paddingSymmetrical(24.h, 24.h) : SizedBox.shrink(), ), ], ), diff --git a/lib/presentation/symptoms_checker/user_info_selection.dart b/lib/presentation/symptoms_checker/user_info_selection.dart index f1e8413d..b245c014 100644 --- a/lib/presentation/symptoms_checker/user_info_selection.dart +++ b/lib/presentation/symptoms_checker/user_info_selection.dart @@ -100,7 +100,7 @@ class _UserInfoSelectionPageState extends State { children: [ title.toText14(weight: FontWeight.w500), subTitle - .toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) + .toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500, isEnglishOnly: true) .toShimmer2(isShow: (leadingIcon == AppAssets.rulerIcon || leadingIcon == AppAssets.weightScale) && hmgServicesVM.isVitalSignLoading), ], ), diff --git a/lib/presentation/symptoms_checker/widgets/condition_card.dart b/lib/presentation/symptoms_checker/widgets/condition_card.dart index e48f41ea..d753b780 100644 --- a/lib/presentation/symptoms_checker/widgets/condition_card.dart +++ b/lib/presentation/symptoms_checker/widgets/condition_card.dart @@ -168,7 +168,7 @@ class ConditionCard extends StatelessWidget { crossAxisAlignment: WrapCrossAlignment.center, children: [ for (int i = 0; i < symptoms.length; i++) ...[ - "● ${symptoms[i]}".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + "● ${symptoms[i]}".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, isEnglishOnly: true), if (i != symptoms.length - 1) Padding( padding: EdgeInsets.symmetric(horizontal: 2.w), @@ -228,7 +228,7 @@ class ConditionCard extends StatelessWidget { backgroundColor: AppColors.scaffoldBgColor, titleWidget: Row( children: [ - "$percentage%".toText12(fontWeight: FontWeight.bold, color: getChipColorBySeverityEnum(severityEnum)), + "$percentage%".toText12(fontWeight: FontWeight.bold, color: getChipColorBySeverityEnum(severityEnum), isEnglishOnly: true), ], ).paddingSymmetrical(0, 4.h), ), diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart index c4dfb7c4..d2ba2723 100644 --- a/lib/services/dialog_service.dart +++ b/lib/services/dialog_service.dart @@ -166,7 +166,7 @@ class DialogServiceImp implements DialogService { mainAxisAlignment: MainAxisAlignment.start, children: [ if (message != null) (message).toText16(isBold: false, color: AppColors.textColor), - SizedBox(height: 24.h), + // SizedBox(height: 24.h), FamilyCards( profiles: profiles, onSelect: (FamilyFileResponseModelLists profile) { @@ -190,6 +190,7 @@ class DialogServiceImp implements DialogService { }) ], ), + useSafeArea: true, callBackFunc: () {}); } diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 514acfb4..467edd92 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -8,7 +8,7 @@ class AppColors { static const transparent = Colors.transparent; // ── Scaffold / Background ───────────────────────────────────────────────── - static Color get scaffoldBgColor => isDarkMode ? dark.scaffoldBgColor : const Color(0xFFF8F8F8); + static Color get scaffoldBgColor => isDarkMode ? dark.scaffoldBgColor : const Color(0xFFF0F0F0); 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); diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 0c32d87d..4c754b65 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -26,6 +26,8 @@ class CollapsingListView extends StatelessWidget { VoidCallback? sendEmail; VoidCallback? doctorResponse; VoidCallback? downloadReport; + VoidCallback? viewImage; + VoidCallback? location; Widget? bottomChild; Widget? trailing; bool isClose; @@ -49,6 +51,8 @@ class CollapsingListView extends StatelessWidget { this.sendEmail, this.doctorResponse, this.downloadReport, + this.viewImage, + this.location, this.isLeading = true, this.trailing, this.leadingCallback, @@ -92,6 +96,8 @@ class CollapsingListView extends StatelessWidget { sendEmail: sendEmail, doctorResponse: doctorResponse, downloadReport: downloadReport, + viewImage: viewImage, + location: location, bottomChild: bottomChild, trailing: trailing, aiOverview: aiOverview, @@ -204,6 +210,8 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget VoidCallback? sendEmail; VoidCallback? doctorResponse; VoidCallback? downloadReport; + VoidCallback? viewImage; + VoidCallback? location; Widget? bottomChild; Widget? trailing; @@ -222,6 +230,8 @@ class ScrollAnimatedTitle extends StatefulWidget implements PreferredSizeWidget this.sendEmail, this.doctorResponse, this.downloadReport, + this.viewImage, + this.location, this.bottomChild, this.trailing, }); @@ -254,7 +264,7 @@ class _ScrollAnimatedTitleState extends State { super.dispose(); } - double t = 0; + double t = 1.0; void _onScroll() { final double offset = widget.controller.offset; @@ -301,6 +311,8 @@ class _ScrollAnimatedTitleState extends State { 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.trailing != null) widget.trailing!, ] ], diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index 77345933..b2ae4288 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -27,6 +29,7 @@ class AppCustomChipWidget extends StatelessWidget { this.labelPadding, this.onDeleteTap, this.applyThemeColor = true, + this.isEnglishOnly = false }); final String? labelText; @@ -48,6 +51,7 @@ class AppCustomChipWidget extends StatelessWidget { final void Function()? onChipTap; final void Function()? onDeleteTap; final bool applyThemeColor; + final bool isEnglishOnly; @override Widget build(BuildContext context) { @@ -85,7 +89,7 @@ class AppCustomChipWidget extends StatelessWidget { applyThemeColor: applyThemeColor, ) : SizedBox.shrink(), - label: richText ?? (labelText?? "").toText10(weight: FontWeight.w500, letterSpacing: 0, color: resolvedTextColor), + label: richText ?? (labelText?? "").toText10(weight: FontWeight.w500, letterSpacing: 0, color: resolvedTextColor, isEnglishOnly: isEnglishOnly), padding: padding, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, labelPadding: labelPadding ?? EdgeInsetsDirectional.only(end: deleteIcon?.isNotEmpty == true ? 2.w : 8.w), @@ -99,12 +103,15 @@ class AppCustomChipWidget extends StatelessWidget { deleteIcon: deleteIcon?.isNotEmpty == true ? InkWell( onTap: onDeleteTap, - child: Utils.buildSvgWithAssets( - icon: deleteIcon!, - width: iconS, - height: iconS, - iconColor: deleteIconHasColor ? resolvedDeleteIconColor : null, - applyThemeColor: applyThemeColor, + child: Transform.flip( + flipX: getIt.get().isArabic(), + child: Utils.buildSvgWithAssets( + icon: deleteIcon!, + width: iconS, + height: iconS, + iconColor: deleteIconHasColor ? resolvedDeleteIconColor : null, + applyThemeColor: applyThemeColor, + ), ), ) : null, @@ -112,7 +119,7 @@ class AppCustomChipWidget extends StatelessWidget { ) : Chip( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - label: richText ?? (labelText?? "").toText10(weight: FontWeight.w500, letterSpacing: 0, color: resolvedTextColor, isCenter: true), + label: richText ?? (labelText?? "").toText10(weight: FontWeight.w500, letterSpacing: 0, color: resolvedTextColor, isCenter: true, isEnglishOnly: isEnglishOnly), padding: EdgeInsets.zero, backgroundColor: resolvedBackgroundColor, shape: shape ?? diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 5269a8dc..bc52c0cc 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -2,6 +2,7 @@ import 'dart:io' show Platform; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/utils/calender_utils_new.dart'; @@ -10,6 +11,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_reminder_view.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/services/permission_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:permission_handler/permission_handler.dart'; @@ -22,19 +24,66 @@ class BottomSheetUtils { _showReminderBottomSheet(context, dateTime, doctorName, eventId, appoDateFormatted, appoTimeFormatted, onSuccess: onSuccess, title: title, description: description, onMultiDateSuccess: onMultiDateSuccess, isMultiAllowed: isMultiAllowed); } else { - // Utils.showPermissionConsentDialog(context, TranslationBase.of(context).calendarPermission, () async { - // if (await Permission.calendarFullAccess.request().isGranted) { - // _showReminderDialog(context, dateTime, doctorName, eventId, appoDateFormatted, appoTimeFormatted, - // onSuccess: onSuccess, title: title, description: description, onMultiDateSuccess: onMultiDateSuccess, isMultiAllowed: isMultiAllowed); - // } - // }); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: GetIt.instance().navigatorKey.currentContext!), + GetIt.instance().navigatorKey.currentContext!, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.calendarPermissionAlert.tr(), + isShowActionButtons: true, + onCancelTap: () { + GetIt.instance().pop(); + }, + onConfirmTap: () async { + GetIt.instance().pop(); + openAppSettings(); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); } } else { if (await Permission.calendarWriteOnly.request().isGranted) { if (await Permission.calendarFullAccess.request().isGranted) { _showReminderBottomSheet(context, dateTime, doctorName, eventId, appoDateFormatted, appoTimeFormatted, onSuccess: onSuccess, title: title, description: description, onMultiDateSuccess: onMultiDateSuccess, isMultiAllowed: isMultiAllowed); + } else { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: GetIt.instance().navigatorKey.currentContext!), + GetIt.instance().navigatorKey.currentContext!, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.calendarPermissionAlert.tr(), + isShowActionButtons: true, + onCancelTap: () { + GetIt.instance().pop(); + }, + onConfirmTap: () async { + GetIt.instance().pop(); + openAppSettings(); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); } + } else { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: GetIt.instance().navigatorKey.currentContext!), + GetIt.instance().navigatorKey.currentContext!, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.calendarPermissionAlert.tr(), + isShowActionButtons: true, + onCancelTap: () { + GetIt.instance().pop(); + }, + onConfirmTap: () async { + GetIt.instance().pop(); + openAppSettings(); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); } } } diff --git a/pubspec.yaml b/pubspec.yaml index 43969000..4484fa77 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,8 @@ name: hmg_patient_app_new description: "New HMG Patient App" publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 0.0.11+8 +version: 0.0.15+12 +#version: 0.0.1+14 environment: sdk: ">=3.6.0 <4.0.0"