diff --git a/assets/images/svg/add_new_family_icon.svg b/assets/images/svg/add_new_family_icon.svg new file mode 100644 index 00000000..ad7ee356 --- /dev/null +++ b/assets/images/svg/add_new_family_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index faa65060..9c974574 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1654,5 +1654,8 @@ "twoWaySubtitle": "نقل من الموقع إلى المستشفى والعودة مرة أخرى", "toHospitalSubtitle": "نقل من موقعك الحالي إلى المستشفى", "fromHospitalSubtitle": "نقل من المستشفى إلى منزلك", - "clinicSchedule": "جدول العيادة" + "clinicSchedule": "جدول العيادة", + "doctorAddedToFavourite": "تمت إضافة الطبيب إلى قائمة المفضلة", + "doctorRemovedFromFavourite": "تمت إزالة الطبيب من قائمة الأطباء المفضلين", + "addNewFavorite": "إضافة مفضلة جديدة" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 4b944e5b..727cd16a 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1646,5 +1646,8 @@ "twoWaySubtitle": "Round trip from location to hospital and back", "toHospitalSubtitle": "Transfer from your current location to the hospital", "fromHospitalSubtitle": "Transfer from the hospital back to your home", - "clinicSchedule": "Clinic Schedule" + "clinicSchedule": "Clinic Schedule", + "doctorAddedToFavourite": "Doctor added to favorites list", + "doctorRemovedFromFavourite": "Doctor removed from favorites list", + "addNewFavorite": "Add new favourite" } diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 709d0166..374ac9d1 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -199,8 +199,8 @@ class ApiClientImp implements ApiClient { body['TokenID'] = "@dm!n"; } - body['TokenID'] = "@dm!n"; - body['PatientID'] = 1018977; + // body['TokenID'] = "@dm!n"; + // body['PatientID'] = 1018977; // body['PatientTypeID'] = 1; // body['PatientOutSA'] = 0; // body['SessionID'] = "45786230487560q"; diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 5fb73a37..9546250d 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -233,6 +233,11 @@ class ApiConsts { static String getAiOverViewLabOrders = "Services/Patients.svc/REST/HMGAI_Lab_Analyze_Orders_API"; static String getAiOverViewLabOrder = "Services/Patients.svc/REST/HMGAI_Lab_Analyzer_API"; + static String googleCloudStorageARTranslationFileURL = "https://storage.googleapis.com/hmg-patientapp-translations/ar-SA.json"; + static String googleCloudStorageENTranslationFileURL = "https://storage.googleapis.com/hmg-patientapp-translations/en-US.json"; + + static String googleCloudStorageENTranslationFileBaseURL = "https://storage.googleapis.com/hmg-patientapp-translations"; + // ************ static values for Api **************** static final double appVersionID = 20.9; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 6d36098e..b8b7f177 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -220,6 +220,7 @@ class AppAssets { static const String ibw = '$svgBasePath/ibw.svg'; static const String general_health = '$svgBasePath/general_health.svg'; static const String women_health = '$svgBasePath/women_health.svg'; + static const String add_new_family_icon = '$svgBasePath/add_new_family_icon.svg'; static const String height = '$svgBasePath/height.svg'; static const String weight = '$svgBasePath/weight.svg'; diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 8d313774..8116b2a0 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -630,7 +630,7 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setUserBloodGroup = activation.patientBlodType ?? "N/A"; // Fetch patient blood type from new API - await getPatientBloodTypeNew(); + // await getPatientBloodTypeNew(); } // _appState.setUserBloodGroup = (activation.patientBlodType ?? ""); _appState.setAppAuthToken = activation.authenticationTokenId; @@ -722,6 +722,7 @@ class AuthenticationViewModel extends ChangeNotifier { } Future navigateToHomeScreen() async { + getPatientBloodTypeNew(); Navigator.pushAndRemoveUntil( _navigationService.navigatorKey.currentContext!, CustomPageRoute( @@ -1018,7 +1019,9 @@ class AuthenticationViewModel extends ChangeNotifier { nationalId: _appState.getAuthenticatedUser()!.patientIdentificationNo, gender: _appState.getAuthenticatedUser()!.gender) .toJson()); - resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { + resultEither.fold((failure) async { + // await _errorHandlerService.handleError(failure: failure); + }, (apiResponse) async { if (apiResponse.messageStatus == 2) {} if (apiResponse.messageStatus == 1) { log("Insert Device Data Success"); diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index eab01644..88c9b21c 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -857,8 +857,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { } initialSlotDuration = apiResponse.data["InitialSlotDuration"]; freeSlotsResponse = apiResponse.data['FreeTimeSlots']; - isWaitingAppointmentAvailable = apiResponse.data["IsAllowToBookWaitingAppointment"]; - // isWaitingAppointmentAvailable = true; + // isWaitingAppointmentAvailable = apiResponse.data["IsAllowToBookWaitingAppointment"]; + isWaitingAppointmentAvailable = true; freeSlotsResponse.forEach((element) { // date = (isLiveCareSchedule != null && isLiveCareSchedule) diff --git a/lib/features/translations/custom_asset_loader.dart b/lib/features/translations/custom_asset_loader.dart new file mode 100644 index 00000000..8cf9e7c9 --- /dev/null +++ b/lib/features/translations/custom_asset_loader.dart @@ -0,0 +1,27 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:ui'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/services.dart'; +import 'package:path_provider/path_provider.dart'; + +class RemoteFileLoader extends AssetLoader { + @override + Future?> load(String path, Locale locale) async { + final directory = await getApplicationDocumentsDirectory(); + + // easy_localization uses toLanguageTag() which gives 'en-US' or 'ar-SA' + final fileName = "${locale.toLanguageTag()}.json"; + final file = File("${directory.path}/$fileName"); + + if (await file.exists()) { + final String jsonString = await file.readAsString(); + return jsonDecode(jsonString); + } else { + // If the download failed or hasn't happened yet, load from local assets + // path is the 'path' property from the EasyLocalization widget + final localData = await rootBundle.loadString('$path/$fileName'); + return jsonDecode(localData); + } + } +} diff --git a/lib/features/translations/translation_service.dart b/lib/features/translations/translation_service.dart new file mode 100644 index 00000000..9405de3d --- /dev/null +++ b/lib/features/translations/translation_service.dart @@ -0,0 +1,35 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:http/http.dart' as http; +import 'package:path_provider/path_provider.dart'; + +class TranslationService { +// Replace with your actual hosting URL + static final String baseUrl = ApiConsts.googleCloudStorageENTranslationFileBaseURL; + + static Future downloadTranslations() async { + final directory = await getApplicationDocumentsDirectory(); + +// Ensure these match your locale codes exactly (en-US, ar-SA) + List files = ["en-US.json", "ar-SA.json"]; + + for (String fileName in files) { + try { + String url = "$baseUrl/$fileName"; + debugPrint(url); + // final response = await http.get(Uri.parse("$baseUrl/$fileName")); + final response = await http.get(Uri.parse(url)); + + if (response.statusCode == 200) { + final file = File("${directory.path}/$fileName"); + await file.writeAsBytes(response.bodyBytes); + } else { + print("Failed to download $fileName: Status ${response.statusCode}"); + } + } catch (e) { + print("Network error while downloading $fileName: $e"); + } + } + } +} diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index f9bcef45..07fb694a 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1647,5 +1647,8 @@ abstract class LocaleKeys { static const toHospitalSubtitle = 'toHospitalSubtitle'; static const fromHospitalSubtitle = 'fromHospitalSubtitle'; static const clinicSchedule = 'clinicSchedule'; + static const doctorAddedToFavourite = 'doctorAddedToFavourite'; + static const doctorRemovedFromFavourite = 'doctorRemovedFromFavourite'; + static const addNewFavorite = 'addNewFavorite'; } diff --git a/lib/main.dart b/lib/main.dart index d38c9aab..38dc539b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -41,6 +41,8 @@ import 'package:hmg_patient_app_new/features/services_price_list/services_price_ import 'package:hmg_patient_app_new/features/smartwatch_health_data/health_provider.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; +import 'package:hmg_patient_app_new/features/translations/custom_asset_loader.dart'; +import 'package:hmg_patient_app_new/features/translations/translation_service.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculator_view_model.dart'; import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart'; @@ -95,6 +97,9 @@ Future callInitializations() async { WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); + // Download the latest JSONs from your server + await TranslationService.downloadTranslations(); + // await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); try { @@ -125,6 +130,7 @@ void main() async { Locale('ar', 'SA'), ], path: 'assets/langs', + assetLoader: RemoteFileLoader(), fallbackLocale: Locale('en', 'US'), child: MultiProvider(providers: [ ChangeNotifierProvider( diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index 1ac49f85..b171f26a 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -255,7 +255,8 @@ class _BookAppointmentPageState extends State { height: 110.h, child: ListView.separated( scrollDirection: Axis.horizontal, - itemCount: 3, // Show 3 shimmer items + itemCount: 3, + // Show 3 shimmer items shrinkWrap: true, padding: EdgeInsets.only(left: 24.w, right: 24.w), itemBuilder: (context, index) { @@ -341,6 +342,8 @@ class _BookAppointmentPageState extends State { SizedBox(height: 24.h), LocaleKeys.favouriteDoctors.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.w, 0.h), SizedBox(height: 16.h), + Row( + children: [ SizedBox( height: 110.h, child: ListView.separated( @@ -407,8 +410,19 @@ class _BookAppointmentPageState extends State { separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h), ), ), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.add_new_family_icon, height: 64.h, width: 64.h), + SizedBox(width: 80.w, child: LocaleKeys.addNewFavorite.tr(context: context).toText12(color: AppColors.textColor, fontWeight: FontWeight.w500, isCenter: true).paddingOnly(top: 4.h)), + ], + ).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: SearchDoctorByName())); + }), ], - ); + ), + ], + ); }), ], ], diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart index 90d0fc99..2bad1af9 100644 --- a/lib/presentation/book_appointment/doctor_profile_page.dart +++ b/lib/presentation/book_appointment/doctor_profile_page.dart @@ -54,11 +54,9 @@ class DoctorProfilePage extends StatelessWidget { doctorID: viewModel.doctorsProfileResponseModel.doctorID ?? 0, isActive: viewModel.isFavouriteDoctor, onSuccess: (response) { + Utils.showToast(viewModel.isFavouriteDoctor ? LocaleKeys.doctorAddedToFavourite.tr(context: context) : LocaleKeys.doctorRemovedFromFavourite.tr(context: context)); // Successfully added/removed favorite - refresh the favorites list getIt.get().refreshFavouriteDoctors(); - print( - viewModel.isFavouriteDoctor ? "Doctor added to favorites" : "Doctor removed from favorites", - ); }, onError: (error) { // Revert the state on error diff --git a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_online_checkin_sheet.dart b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_online_checkin_sheet.dart index 6f3a773a..f762ae0a 100644 --- a/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_online_checkin_sheet.dart +++ b/lib/presentation/book_appointment/waiting_appointment/waiting_appointment_online_checkin_sheet.dart @@ -63,20 +63,20 @@ class WaitingAppointmentOnlineCheckinSheet extends StatelessWidget { }); }), SizedBox(height: 16.h), - checkInOptionCard( - AppAssets.checkin_nfc_icon, - LocaleKeys.nfcNearFieldCommunication.tr(), - LocaleKeys.scanPhoneViaNFC.tr(), - ).onPress(() { - Future.delayed(const Duration(milliseconds: 500), () { - showNfcReader(context, onNcfScan: (String nfcId) { - Future.delayed(const Duration(milliseconds: 100), () { - checkScannedNFCAndQRCode(nfcId, context); - }); - }, onCancel: () {}); - }); - }), - SizedBox(height: 16.h), + // checkInOptionCard( + // AppAssets.checkin_nfc_icon, + // LocaleKeys.nfcNearFieldCommunication.tr(), + // LocaleKeys.scanPhoneViaNFC.tr(), + // ).onPress(() { + // Future.delayed(const Duration(milliseconds: 500), () { + // showNfcReader(context, onNcfScan: (String nfcId) { + // Future.delayed(const Duration(milliseconds: 100), () { + // checkScannedNFCAndQRCode(nfcId, context); + // }); + // }, onCancel: () {}); + // }); + // }), + // SizedBox(height: 16.h), checkInOptionCard( AppAssets.checkin_qr_icon, LocaleKeys.qrCode.tr(), diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 0d7d373a..67b09664 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -65,7 +65,7 @@ class _PrescriptionDetailPageState extends State { Expanded( child: CollapsingListView( title: LocaleKeys.prescriptions.tr(context: context), - instructions: widget.prescriptionsResponseModel.isInOutPatient! + instructions: widget.prescriptionsResponseModel.isInOutPatient ?? false ? () async { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionPDFPleaseWait.tr(context: context)); await prescriptionsViewModel.getPrescriptionInstructionsPDF(widget.prescriptionsResponseModel, onSuccess: (val) { @@ -112,8 +112,9 @@ class _PrescriptionDetailPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ AppCustomChipWidget( - labelText: - "${getIt.get().isArabic() ? widget.prescriptionsResponseModel.isInOutPatientDescriptionN : widget.prescriptionsResponseModel.isInOutPatientDescription}", + labelText: getIt.get().isArabic() + ? (widget.prescriptionsResponseModel.isInOutPatientDescriptionN ?? "مراجع للعيادات الخارجية") + : (widget.prescriptionsResponseModel.isInOutPatientDescription ?? "OutPatient"), backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), textColor: AppColors.warningColorYellow, ),