diff --git a/assets/images/jpg/report.jpg b/assets/images/jpg/report.jpg new file mode 100644 index 0000000..5846cd5 Binary files /dev/null and b/assets/images/jpg/report.jpg differ diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index de917b7..88164bd 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -826,6 +826,7 @@ class ApiConsts { static final String removeFileFromFamilyMembers = 'Services/Authentication.svc/REST/ActiveDeactive_PatientFile'; static final String acceptAndRejectFamilyFile = 'Services/Authentication.svc/REST/Update_FileStatus'; static final String getActivePrescriptionsDetails = 'Services/Patients.svc/Rest/GetActivePrescriptionReportByPatientID'; + static final String getTermsConditions = 'Services/Patients.svc/Rest/GetUserTermsAndConditions'; // Ancillary Order Apis static final String getOnlineAncillaryOrderList = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderList'; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 5fccc6e..b34b47e 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -1,6 +1,7 @@ class AppAssets { static const String svgBasePath = 'assets/images/svg'; static const String pngBasePath = 'assets/images/png'; + static const String jpgBasePath = 'assets/images/jpg'; static const String hmg = '$svgBasePath/hmg.svg'; static const String arrow_back = '$svgBasePath/arrow-back.svg'; @@ -206,6 +207,9 @@ class AppAssets { static const String dummy_user = '$pngBasePath/dummy_user.png'; static const String comprehensiveCheckupEn = '$pngBasePath/cc_en.png'; static const String comprehensiveCheckupAr = '$pngBasePath/cc_er.png'; + + // JPGS // + static const String report = '$jpgBasePath/hmg_logo.jpg'; } class AppAnimations { diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index d4afbd9..c167aa0 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -55,6 +55,8 @@ import 'package:logger/web.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../features/active_prescriptions/active_prescriptions_repo.dart'; +import '../features/terms_conditions/terms_conditions_repo.dart'; +import '../features/terms_conditions/terms_conditions_view_model.dart'; GetIt getIt = GetIt.instance; @@ -121,6 +123,10 @@ class AppDependencies { getIt.registerLazySingleton(() => ContactUsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => HmgServicesRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ActivePrescriptionsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => TermsConditionsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerFactory(() => TermsConditionsViewModel(termsConditionsRepo: getIt(), errorHandlerService: getIt(), + ), + ); // ViewModels // Global/shared VMs → LazySingleton diff --git a/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart b/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart index 42faafa..dc859a9 100644 --- a/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart +++ b/lib/features/active_prescriptions/models/active_prescriptions_response_model.dart @@ -1,165 +1,78 @@ -import 'dart:convert'; class ActivePrescriptionsResponseModel { - dynamic address; - int? appointmentNo; - dynamic clinic; - dynamic companyName; - int? days; - dynamic doctorName; - int? doseDailyQuantity; // doses per day + String? itemId; + String? itemDescription; + String? route; String? frequency; int? frequencyNumber; - dynamic image; - dynamic imageExtension; - dynamic imageSrcUrl; - String? imageString; - dynamic imageThumbUrl; - dynamic isCovered; - String? itemDescription; - int? itemId; + int? doseDailyQuantity; + int? days; + String? startDate; + String? endDate; String? orderDate; - int? patientId; - dynamic patientName; - dynamic phoneOffice1; - dynamic prescriptionQr; - dynamic prescriptionTimes; - dynamic productImage; - String? productImageBase64; String? productImageString; - int? projectId; - dynamic projectName; - dynamic remarks; - String? route; - String? sku; - int? scaleOffset; - String? startDate; - - // Added for reminder feature + bool isReminderOn; List selectedDoseTimes = []; - bool isReminderOn = false; // toggle status ActivePrescriptionsResponseModel({ - this.address, - this.appointmentNo, - this.clinic, - this.companyName, - this.days, - this.doctorName, - this.doseDailyQuantity, + this.itemId, + this.itemDescription, + this.route, this.frequency, this.frequencyNumber, - this.image, - this.imageExtension, - this.imageSrcUrl, - this.imageString, - this.imageThumbUrl, - this.isCovered, - this.itemDescription, - this.itemId, + this.doseDailyQuantity, + this.days, + this.startDate, + this.endDate, this.orderDate, - this.patientId, - this.patientName, - this.phoneOffice1, - this.prescriptionQr, - this.prescriptionTimes, - this.productImage, - this.productImageBase64, this.productImageString, - this.projectId, - this.projectName, - this.remarks, - this.route, - this.sku, - this.scaleOffset, - this.startDate, - - // ✅ Default values for new fields (won’t break API) - List? selectedDoseTimes, this.isReminderOn = false, - }) : selectedDoseTimes = selectedDoseTimes ?? []; - - factory ActivePrescriptionsResponseModel.fromRawJson(String str) => - ActivePrescriptionsResponseModel.fromJson(json.decode(str)); - - String toRawJson() => json.encode(toJson()); - - factory ActivePrescriptionsResponseModel.fromJson(Map json) => - ActivePrescriptionsResponseModel( - address: json["Address"], - appointmentNo: json["AppointmentNo"], - clinic: json["Clinic"], - companyName: json["CompanyName"], - days: json["Days"], - doctorName: json["DoctorName"], - doseDailyQuantity: json["DoseDailyQuantity"], - frequency: json["Frequency"], - frequencyNumber: json["FrequencyNumber"], - image: json["Image"], - imageExtension: json["ImageExtension"], - imageSrcUrl: json["ImageSRCUrl"], - imageString: json["ImageString"], - imageThumbUrl: json["ImageThumbUrl"], - isCovered: json["IsCovered"], - itemDescription: json["ItemDescription"], - itemId: json["ItemID"], - orderDate: json["OrderDate"], - patientId: json["PatientID"], - patientName: json["PatientName"], - phoneOffice1: json["PhoneOffice1"], - prescriptionQr: json["PrescriptionQR"], - prescriptionTimes: json["PrescriptionTimes"], - productImage: json["ProductImage"], - productImageBase64: json["ProductImageBase64"], - productImageString: json["ProductImageString"], - projectId: json["ProjectID"], - projectName: json["ProjectName"], - remarks: json["Remarks"], - route: json["Route"], - sku: json["SKU"], - scaleOffset: json["ScaleOffset"], - startDate: json["StartDate"], + List? selectedDoseTimes, + }) { + this.selectedDoseTimes = selectedDoseTimes ?? []; + } - // ✅ Ensure local reminder values are not overwritten by API - selectedDoseTimes: [], - isReminderOn: false, - ); + /// ========== JSON FROM ========== + factory ActivePrescriptionsResponseModel.fromJson(Map json) { + return ActivePrescriptionsResponseModel( + itemId: json["ItemID"]?.toString() ?? "", + itemDescription: json["ItemDescription"] ?? "", + route: json["Route"] ?? "", + frequency: json["Frequency"] ?? "", + frequencyNumber: json["FrequencyNumber"], + doseDailyQuantity: json["DoseDailyQuantity"] ?? 1, + days: json["Days"] ?? 0, + startDate: json["StartDate"] ?? "", + endDate: json["EndDate"] ?? "", + orderDate: json["OrderDate"] ?? "", + productImageString: json["ProductImageString"] ?? "", + isReminderOn: json["IsReminderOn"] == true, + selectedDoseTimes: + (json["SelectedDoseTimes"] as List?) + ?.map((e) => e?.toString()) + .toList() ?? + [], + ); + } - Map toJson() => { - "Address": address, - "AppointmentNo": appointmentNo, - "Clinic": clinic, - "CompanyName": companyName, - "Days": days, - "DoctorName": doctorName, - "DoseDailyQuantity": doseDailyQuantity, - "Frequency": frequency, - "FrequencyNumber": frequencyNumber, - "Image": image, - "ImageExtension": imageExtension, - "ImageSRCUrl": imageSrcUrl, - "ImageString": imageString, - "ImageThumbUrl": imageThumbUrl, - "IsCovered": isCovered, - "ItemDescription": itemDescription, - "ItemID": itemId, - "OrderDate": orderDate, - "PatientID": patientId, - "PatientName": patientName, - "PhoneOffice1": phoneOffice1, - "PrescriptionQR": prescriptionQr, - "PrescriptionTimes": prescriptionTimes, - "ProductImage": productImage, - "ProductImageBase64": productImageBase64, - "ProductImageString": productImageString, - "ProjectID": projectId, - "ProjectName": projectName, - "Remarks": remarks, - "Route": route, - "SKU": sku, - "ScaleOffset": scaleOffset, - "StartDate": startDate, - }; + /// ========== JSON TO ========== + Map toJson() { + return { + "ItemID": itemId, + "ItemDescription": itemDescription, + "Route": route, + "Frequency": frequency, + "FrequencyNumber": frequencyNumber, + "DoseDailyQuantity": doseDailyQuantity, + "Days": days, + "StartDate": startDate, + "EndDate": endDate, + "OrderDate": orderDate, + "ProductImageString": productImageString, + "IsReminderOn": isReminderOn, + "SelectedDoseTimes": selectedDoseTimes, + }; + } } diff --git a/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart b/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart index d5180ae..6c24998 100644 --- a/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart +++ b/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart @@ -10,6 +10,7 @@ class HmgServicesComponentModel { Color bgColor; Color textColor; String route; + bool isExternalLink; HmgServicesComponentModel( this.action, @@ -21,5 +22,6 @@ class HmgServicesComponentModel { this.bgColor = Colors.white, this.textColor = Colors.black, this.route = '', + this.isExternalLink = false, }); } diff --git a/lib/features/terms_conditions/terms_conditions_repo.dart b/lib/features/terms_conditions/terms_conditions_repo.dart new file mode 100644 index 0000000..a5d3f95 --- /dev/null +++ b/lib/features/terms_conditions/terms_conditions_repo.dart @@ -0,0 +1,60 @@ +import 'package:dartz/dartz.dart'; +import '../../core/api/api_client.dart'; +import '../../core/api_consts.dart'; +import '../../core/exceptions/api_failure.dart'; +import '../../services/logger_service.dart'; + +abstract class TermsConditionsRepo { + Future> getTermsConditions(); +} + +class TermsConditionsRepoImp implements TermsConditionsRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + TermsConditionsRepoImp({ + required this.loggerService, + required this.apiClient, + }); + + @override + Future> getTermsConditions() async { + Failure? failure; + String? html; + + try { + await apiClient.post( + ApiConsts.getTermsConditions, + body: {}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType ?? ServerFailure(error.toString()); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + + final content = response['UserAgreementContent']; + + if (content is String && content.isNotEmpty) { + html = content; + } else { + failure = DataParsingFailure( + 'UserAgreementContent is null or not String'); + } + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + } catch (e) { + failure = UnknownFailure(e.toString()); + } + + if (failure != null) return Left(failure!); + if (html == null || html!.isEmpty) { + return Left(ServerFailure('No terms and conditions returned')); + } + + return Right(html!); + } +} + diff --git a/lib/features/terms_conditions/terms_conditions_view_model.dart b/lib/features/terms_conditions/terms_conditions_view_model.dart new file mode 100644 index 0000000..5d67ae7 --- /dev/null +++ b/lib/features/terms_conditions/terms_conditions_view_model.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/terms_conditions/terms_conditions_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class TermsConditionsViewModel extends ChangeNotifier { + final TermsConditionsRepo termsConditionsRepo; + final ErrorHandlerService errorHandlerService; + + String? termsConditionsHtml; + bool isLoading = false; + + TermsConditionsViewModel({ + required this.termsConditionsRepo, + required this.errorHandlerService, + }); + + Future getTermsConditions({ + Function()? onSuccess, + Function(String)? onError, + }) async { + isLoading = true; + notifyListeners(); + + final result = await termsConditionsRepo.getTermsConditions(); + + result.fold( + (failure) async { + await errorHandlerService.handleError(failure: failure); + isLoading = false; + notifyListeners(); + if (onError != null) { + onError(failure.message ?? 'Something went wrong'); + } + }, + (html) { + termsConditionsHtml = html; + isLoading = false; + notifyListeners(); + if (onSuccess != null) onSuccess(); + }, + ); + } +} + + diff --git a/lib/main.dart b/lib/main.dart index f127400..e9ceec7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -38,6 +38,7 @@ import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; import 'core/utils/size_utils.dart'; +import 'features/terms_conditions/terms_conditions_view_model.dart'; import 'firebase_options.dart'; @pragma('vm:entry-point') @@ -146,9 +147,12 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), - ) + ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), diff --git a/lib/presentation/active_medication/active_medication_page.dart b/lib/presentation/active_medication/active_medication_page.dart index d27b35d..d0720fb 100644 --- a/lib/presentation/active_medication/active_medication_page.dart +++ b/lib/presentation/active_medication/active_medication_page.dart @@ -547,7 +547,7 @@ class _ActiveMedicationPageState extends State { AppColors.textGreenColor, AppColors.infoColor, AppColors.labelColorYellow, - AppColors.purpleBg + AppColors.mainPurple ][doseIndex % 4]; final doseLabel = "${doseIndex + 1}${_getSuffix(doseIndex + 1)}"; diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index af576aa..bc63876 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_s import 'package:hmg_patient_app_new/presentation/hmg_services/services_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:url_launcher/url_launcher.dart'; class ServicesPage extends StatelessWidget { ServicesPage({super.key}); @@ -41,8 +42,30 @@ class ServicesPage extends StatelessWidget { textColor: AppColors.blackColor, route: AppRoutes.homeHealthCarePage, ), + HmgServicesComponentModel( + 12, + "Latest News".needTranslation, + "".needTranslation, + AppAssets.news, + true, + bgColor: AppColors.bgGreenColor, + textColor: AppColors.blackColor, + route: "https://twitter.com/HMG", + isExternalLink: true, + ), + HmgServicesComponentModel( + 12, + "Monthly Reports".needTranslation, + "".needTranslation, + AppAssets.report_icon, + true, + bgColor: AppColors.bgGreenColor, + textColor: AppColors.blackColor, + route: AppRoutes.monthlyReports, + ), ]; + @override Widget build(BuildContext context) { return CollapsingListView( @@ -72,7 +95,7 @@ class ServicesPage extends StatelessWidget { return ServiceGridViewItem(hmgServices[index], index, false); }, ), - ) + ), ], ), ), diff --git a/lib/presentation/hmg_services/services_view.dart b/lib/presentation/hmg_services/services_view.dart index 225bd96..3a0c211 100644 --- a/lib/presentation/hmg_services/services_view.dart +++ b/lib/presentation/hmg_services/services_view.dart @@ -5,6 +5,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/features/hmg_services/models/ui_models/hmg_services_component_model.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:url_launcher/url_launcher.dart'; class ServiceGridViewItem extends StatelessWidget { final HmgServicesComponentModel hmgServiceComponentModel; @@ -12,12 +13,18 @@ class ServiceGridViewItem extends StatelessWidget { final bool isHomePage; final bool isLocked; - const ServiceGridViewItem(this.hmgServiceComponentModel, this.index, this.isHomePage, {super.key, this.isLocked = false}); + const ServiceGridViewItem( + this.hmgServiceComponentModel, this.index, this.isHomePage, + {super.key, this.isLocked = false}); @override Widget build(BuildContext context) { return InkWell( - onTap: () => getIt.get().pushPageRoute(hmgServiceComponentModel.route), + onTap: () => hmgServiceComponentModel.isExternalLink + ? _openLink(hmgServiceComponentModel.route) + : getIt + .get() + .pushPageRoute(hmgServiceComponentModel.route), child: Column( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, @@ -47,4 +54,14 @@ class ServiceGridViewItem extends StatelessWidget { ], )); } + + Future _openLink(String link) async { + final Uri url = Uri.parse(link); + + if (await canLaunchUrl(url)) { + await launchUrl(url, mode: LaunchMode.externalApplication); + } else { + throw "Could not launch $url"; + } + } } diff --git a/lib/presentation/monthly_reports/monthly_reports_page.dart b/lib/presentation/monthly_reports/monthly_reports_page.dart new file mode 100644 index 0000000..97cc0e3 --- /dev/null +++ b/lib/presentation/monthly_reports/monthly_reports_page.dart @@ -0,0 +1,283 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.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/presentation/monthly_reports/user_agreement_page.dart'; + +import '../../generated/locale_keys.g.dart'; +import '../../theme/colors.dart'; +import '../../widgets/appbar/app_bar_widget.dart'; +import '../../widgets/input_widget.dart'; + +class MonthlyReportsPage extends StatefulWidget { + const MonthlyReportsPage({super.key}); + + @override + State createState() => _MonthlyReportsPageState(); +} + +class _MonthlyReportsPageState extends State { + bool isHealthSummaryEnabled = false; + bool isTermsAccepted = false; + + final TextEditingController emailController = TextEditingController(); + + @override + void dispose() { + emailController.dispose(); + super.dispose(); + } + + void _showError(String message) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + behavior: SnackBarBehavior.floating, + ), + ); + } + + void _onSavePressed() { + if (!isTermsAccepted) { + _showError("Please accept the terms and conditions".needTranslation); + return; + } + + final email = emailController.text.trim(); + if (email.isEmpty) { + _showError("Please enter your email".needTranslation); + return; + } + + setState(() { + isHealthSummaryEnabled = true; + }); + + // TODO: هنا حطي API/logic حق الحفظ + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.scaffoldBgColor, + appBar: CustomAppBar( + onBackPressed: () => Navigator.of(context).pop(), + onLanguageChanged: (_) {}, + hideLogoAndLang: true, + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Monthly Reports".needTranslation, + style: TextStyle( + color: AppColors.textColor, + fontSize: 27.f, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 16.h), + + Container( + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), + height: 54.h, + alignment: Alignment.center, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Colors.white, + borderRadius: (12.r), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Patient Health Summary Report".needTranslation, + style: TextStyle( + color: AppColors.textColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + ), + ), + _buildToggle(), + ], + ), + ), + + SizedBox(height: 16.h), + + TextInputWidget( + controller: emailController, + labelText: "Eamil*".needTranslation, + hintText: "email@email.com", + isEnable: true, + prefix: null, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + autoFocus: true, + keyboardType: TextInputType.emailAddress, + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), + onChange: (value) { + setState(() {}); + }, + ).paddingOnly(top: 8.h, bottom: 8.h), + + Row( + children: [ + Text( + "To View The Terms and Conditions".needTranslation, + style: TextStyle( + color: AppColors.textColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + ), + ), + InkWell( + child: Text( + "Click here".needTranslation, + style: TextStyle( + color: AppColors.errorColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + ), + ), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const UserAgreementPage(), + ), + ); + }, + ), + ], + ), + + SizedBox(height: 12.h), + + GestureDetector( + onTap: () => setState(() => isTermsAccepted = !isTermsAccepted), + child: Row( + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 200), + height: 24.h, + width: 24.h, + decoration: BoxDecoration( + color: isTermsAccepted + ? AppColors.textGreenColor + : Colors.transparent, + borderRadius: BorderRadius.circular(6), + border: Border.all( + color: isTermsAccepted + ? AppColors.lightGreenColor + : AppColors.greyColor, + width: 2.h, + ), + ), + child: isTermsAccepted + ? Icon(Icons.check, size: 16.f, color: Colors.white) + : null, + ), + SizedBox(width: 12.h), + Text( + "I agree to the terms and conditions".needTranslation, + style: context.dynamicTextStyle( + fontSize: 12.f, + fontWeight: FontWeight.w500, + color: const Color(0xFF2E3039), + ), + ), + ], + ), + ), + + SizedBox(height: 12.h), + + Text( + "This is monthly health summary report".needTranslation, + style: TextStyle( + color: AppColors.textColor, + fontSize: 10.f, + fontWeight: FontWeight.w600, + ), + ), + + SizedBox(height: 12.h), + + Image.asset('assets/images/jpg/report.jpg'), + + SizedBox(height: 16.h), + + Row( + children: [ + Expanded( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.successColor, + foregroundColor: AppColors.whiteColor, + elevation: 0, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: _onSavePressed, + child: Text( + LocaleKeys.save.tr(), + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16.f, + ), + ), + ), + ), + ], + ), + ], + ), + ).paddingAll(16), + ); + } + + Widget _buildToggle() { + final value = isHealthSummaryEnabled; + + return AbsorbPointer( + absorbing: true, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 50.h, + height: 28.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: value + ? AppColors.lightGreenColor + : AppColors.greyColor.withOpacity(0.3), + ), + child: AnimatedAlign( + duration: const Duration(milliseconds: 200), + alignment: value ? Alignment.centerRight : Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(3), + child: Container( + width: 22.h, + height: 22.h, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: value + ? AppColors.textGreenColor + : AppColors.greyTextColor, + ), + ), + ), + ), + ), + ); + } +} + + diff --git a/lib/presentation/monthly_reports/user_agreement_page.dart b/lib/presentation/monthly_reports/user_agreement_page.dart new file mode 100644 index 0000000..f6379ad --- /dev/null +++ b/lib/presentation/monthly_reports/user_agreement_page.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/terms_conditions/terms_conditions_view_model.dart'; +import 'package:provider/provider.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +import '../../theme/colors.dart'; +import '../../widgets/appbar/app_bar_widget.dart'; + +class UserAgreementPage extends StatefulWidget { + const UserAgreementPage({super.key}); + + @override + State createState() => _UserAgreementPageState(); +} + +class _UserAgreementPageState extends State { + late final WebViewController _webViewController; + bool _isLoading = true; + String? _errorMessage; + + @override + void initState() { + super.initState(); + + _webViewController = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(const Color(0x00000000)) + ..setNavigationDelegate( + NavigationDelegate( + onPageStarted: (_) { + setState(() { + _isLoading = true; + }); + }, + onPageFinished: (_) { + setState(() { + _isLoading = false; + }); + }, + onWebResourceError: (error) { + }, + ), + ); + + WidgetsBinding.instance.addPostFrameCallback((_) { + final vm = + Provider.of(context, listen: false); + + vm.getTermsConditions( + onSuccess: () { + final htmlString = vm.termsConditionsHtml ?? ''; + + if (htmlString.isNotEmpty) { + setState(() { + _errorMessage = null; + _isLoading = true; + }); + _webViewController.loadHtmlString(htmlString); + } else { + setState(() { + _isLoading = false; + _errorMessage = 'لا توجد شروط متاحة حالياً'.needTranslation; + }); + } + }, + onError: (msg) { + setState(() { + _isLoading = false; + _errorMessage = msg; + }); + }, + ); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.scaffoldBgColor, + appBar: CustomAppBar( + onBackPressed: () => Navigator.of(context).pop(), + onLanguageChanged: (_) {}, + hideLogoAndLang: true, + ), + body: Stack( + children: [ + WebViewWidget(controller: _webViewController), + + if (_errorMessage != null) + Center( + child: Container( + margin: const EdgeInsets.all(16), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.9), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + _errorMessage!, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.red, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + if (_isLoading) + const Center( + child: CircularProgressIndicator(), + ), + ], + ), + ); + } +} diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index a0ee1e5..7330744 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -9,6 +9,8 @@ import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/splashPage.dart'; +import '../presentation/monthly_reports/monthly_reports_page.dart'; + class AppRoutes { static const String initialRoute = '/initialRoute'; static const String loginScreen = '/loginScreen'; @@ -19,7 +21,7 @@ class AppRoutes { static const String eReferralPage = '/erReferralPage'; static const String comprehensiveCheckupPage = '/comprehensiveCheckupPage'; static const String homeHealthCarePage = '/homeHealthCarePage'; - + static const String monthlyReports = '/monthlyReportsPage'; static Map get routes => { initialRoute: (context) => SplashPage(), loginScreen: (context) => LoginScreen(), @@ -29,6 +31,7 @@ class AppRoutes { medicalFilePage: (context) => MedicalFilePage(), eReferralPage: (context) => EReferralPage(), comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage(), - homeHealthCarePage: (context) => HhcProceduresPage() + homeHealthCarePage: (context) => HhcProceduresPage(), + monthlyReports: (context) => MonthlyReportsPage() }; } diff --git a/pubspec.yaml b/pubspec.yaml index 3d6604c..0de3829 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -79,7 +79,7 @@ dependencies: path_provider: ^2.0.8 open_filex: ^4.7.0 flutter_swiper_view: ^1.1.8 - + webview_flutter: ^4.9.0 location: ^8.0.1 gms_check: ^1.0.4 huawei_location: ^6.14.2+301