From 9b063ba7649aec71e6060c9625fe0ac3c0607dde Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Tue, 9 Sep 2025 15:08:34 +0300 Subject: [PATCH 1/7] fixes for signup --- lib/core/api_consts.dart | 1 + lib/core/app_state.dart | 11 ++ lib/core/utils/push_notification_handler.dart | 1 + .../authentication/authentication_repo.dart | 27 +++-- .../authentication_view_model.dart | 112 +++++++++--------- .../registration_payload_model.dart | 69 +++++++++++ .../authentication/register_step2.dart | 47 ++++---- lib/presentation/home/landing_page.dart | 1 + lib/routes/app_routes.dart | 7 +- .../dropdown/country_dropdown_widget.dart | 2 +- 10 files changed, 186 insertions(+), 92 deletions(-) create mode 100644 lib/features/authentication/models/request_models/registration_payload_model.dart diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 5b6c873..3b3ed28 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -766,6 +766,7 @@ class ApiConsts { SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; break; case AppEnvironmentTypeEnum.preProd: + baseUrl = "https://webservices.hmg.com/"; payFortEnvironment = FortEnvironment.production; applePayMerchantId = "merchant.com.hmgwebservices"; diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index e5537c6..4408844 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -7,6 +7,8 @@ import 'package:hmg_patient_app_new/features/authentication/models/resp_models/c import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import '../features/authentication/models/request_models/registration_payload_model.dart'; + class AppState { NavigationService navigationService; @@ -103,4 +105,13 @@ class AppState { set setNHICUserData(CheckUserStatusResponseNHIC value) { _nHICUserData = value; } + + + RegistrationDataModelPayload? _userRegistrationPayload; + + RegistrationDataModelPayload get getUserRegistrationPayload => _userRegistrationPayload!; + + set setUserRegistrationPayload(RegistrationDataModelPayload value) { + _userRegistrationPayload = value; + } } diff --git a/lib/core/utils/push_notification_handler.dart b/lib/core/utils/push_notification_handler.dart index 1d2141e..4568a15 100644 --- a/lib/core/utils/push_notification_handler.dart +++ b/lib/core/utils/push_notification_handler.dart @@ -351,6 +351,7 @@ class PushNotificationHandler { } onToken(String token) async { + print("Push Notification Token: " + token); await Utils.saveStringFromPrefs(CacheConst.pushToken, token); } diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 21061dc..7a4d1c1 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -52,7 +52,13 @@ class AuthenticationRepoImp implements AuthenticationRepo { try { final list = response['Patient_SELECTDeviceIMEIbyIMEIList']; if (list == null || list.isEmpty) { - throw Exception("Device list is empty"); + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: null, + ); + return; } final model = SelectDeviceByImeiRespModelElement.fromJson(list[0] as Map); @@ -160,12 +166,17 @@ class AuthenticationRepoImp implements AuthenticationRepo { required String? activationCode, required bool isRegister, }) async { - newRequest.activationCode = activationCode ?? "0000"; - newRequest.isSilentLogin = activationCode != null ? false : true; - newRequest.projectOutSA = newRequest.zipCode == '966' ? false : true; - newRequest.isDentalAllowedBackend = false; - newRequest.forRegisteration = newRequest.isRegister ?? false; - newRequest.isRegister = false; + if (isRegister) { + newRequest["activationCode"] = activationCode ?? "0000"; + newRequest["isSilentLogin"] = activationCode != null ? false : true; + } else { + newRequest.activationCode = activationCode ?? "0000"; + newRequest.isSilentLogin = activationCode != null ? false : true; + newRequest.projectOutSA = newRequest.zipCode == '966' ? false : true; + newRequest.isDentalAllowedBackend = false; + newRequest.forRegisteration = newRequest.isRegister ?? false; + newRequest.isRegister = false; + } final endpoint = isRegister ? ApiConsts.checkActivationCodeRegister : ApiConsts.checkActivationCode; @@ -175,7 +186,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { await apiClient.post( endpoint, - body: newRequest.toJson(), + body: isRegister ? newRequest : newRequest.toJson(), onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; }, diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 6a2ea74..e3c8520 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -1,6 +1,6 @@ import 'dart:convert'; import 'dart:developer'; - +import 'package:flutter/services.dart' show rootBundle; import 'package:flutter/material.dart'; import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/core/utils/validation_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; import 'package:hmg_patient_app_new/features/authentication/models/request_models/check_activation_code_register_request_model.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/request_models/registration_payload_model.dart'; 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'; @@ -49,7 +50,7 @@ class AuthenticationViewModel extends ChangeNotifier { _authenticationRepo = authenticationRepo, _localAuthService = localAuthService; - final TextEditingController nationalIdController = TextEditingController(), phoneNumberController = TextEditingController(), dobController = TextEditingController(); + final TextEditingController nationalIdController = TextEditingController(), phoneNumberController = TextEditingController(), dobController = TextEditingController(), nameController = TextEditingController(); CountryEnum selectedCountrySignup = CountryEnum.saudiArabia; MaritalStatusTypeEnum? maritalStatus; GenderTypeEnum? genderType; @@ -62,23 +63,8 @@ class AuthenticationViewModel extends ChangeNotifier { //================== - bool isDubai = false; - bool authenticated = false; - late int mobileNumber; String errorMsg = ''; - var registerd_data; - bool isMoreOption = false; - var zipCode; - var patientOutSA; - var loginTokenID; - var loginType; - var deviceToken; - var lastLogin; final FocusNode myFocusNode = FocusNode(); - late int selectedOption = 1; - bool onlySMSBox = false; - int fingrePrintBefore = 0; - var healthId; Future onLoginPressed() async { @@ -134,8 +120,8 @@ class AuthenticationViewModel extends ChangeNotifier { notifyListeners(); } - void loadCountriesData({required BuildContext context}) async { - final String response = await DefaultAssetBundle.of(context).loadString('assets/json/countriesList.json'); + Future loadCountriesData() async { + final String response = await rootBundle.loadString('assets/json/countriesList.json'); final List data = json.decode(response); countriesList = data.map((e) => NationalityCountries.fromJson(e)).toList(); } @@ -164,6 +150,18 @@ class AuthenticationViewModel extends ChangeNotifier { notifyListeners(); } + bool isUserFromUAE() { + bool isFromUAE = false; + if (_appState.getUserRegistrationPayload.patientOutSa != 0) { + isFromUAE = true; + } + return isFromUAE; + } + + void savePushTokenToAppState() async { + _appState.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken); + } + Future selectDeviceImei({required Function(dynamic data) onSuccess, Function(String)? onError}) async { // LoadingUtils.showFullScreenLoading(); // String firebaseToken = _appState.deviceToken; @@ -293,9 +291,9 @@ class AuthenticationViewModel extends ChangeNotifier { } Future sendActivationCode({required OTPTypeEnum otpTypeEnum, required String nationalIdOrFileNumber, required String phoneNumber, dynamic payload}) async { - bool isForRegister = await checkIsUserComingForRegister(request: payload); - bool isPatientOutSA = await isPatientOutsideSA(request: payload); - bool isFileNo = await isPatientHasFile(request: payload); + bool isForRegister = checkIsUserComingForRegister(request: payload); + bool isPatientOutSA = isPatientOutsideSA(request: payload); + bool isFileNo = isPatientHasFile(request: payload); var request = RequestUtils.getCommonRequestSendActivationCode( otpTypeEnum: otpTypeEnum, @@ -345,10 +343,9 @@ class AuthenticationViewModel extends ChangeNotifier { ); } - Future checkIsUserComingForRegister({required dynamic request}) async { + bool checkIsUserComingForRegister({required dynamic request}) { bool isUserComingForRegister = false; - print(request); - if (request != null && request['isRegister']) { + if (request != null && request['isRegister'] == true) { isUserComingForRegister = true; } return isUserComingForRegister; @@ -370,18 +367,20 @@ class AuthenticationViewModel extends ChangeNotifier { countryCode: selectedCountrySignup.countryCode, ).toJson(); - bool isForRegister = healthId != null || isDubai; + bool isForRegister = _appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true; if (isForRegister) { - if (isDubai) request['DOB'] = dob; - request['HealthId'] = healthId; - request['IsHijri'] = calenderType.toInt; + if (_appState.getUserRegistrationPayload.patientOutSa == true) request['DOB'] = _appState.getUserRegistrationPayload.dob; + request['HealthId'] = _appState.getUserRegistrationPayload.healthId; + request['IsHijri'] = _appState.getUserRegistrationPayload.isHijri; final resultEither = await _authenticationRepo.checkActivationCodeRepo(newRequest: request, activationCode: activationCode.toString(), isRegister: true); resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) { final activation = CheckActivationCode.fromJson(apiResponse.data as Map); - if (registerd_data?.isRegister == true) { - _navigationService.popUntilNamed(AppRoutes.registerNewScreen); + if (_appState.getUserRegistrationPayload.isRegister == true) { + //TODO: KSA Version Came Hre + loadCountriesData(); + _navigationService.pushAndReplace(AppRoutes.registerStepTwo); // Navigator.popUntil(context, (route) => Utils.route(route, equalsTo: RegisterNew)); return; } @@ -403,8 +402,8 @@ class AuthenticationViewModel extends ChangeNotifier { } else if (activation.messageStatus == 2) { onWrongActivationCode(activation.errorEndUserMessage); return; - } else if (registerd_data?.isRegister == true) { - _navigationService.popUntilNamed(AppRoutes.registerNewScreen); + } else if (_appState.getUserRegistrationPayload.isRegister == true) { + _navigationService.pushAndReplace(AppRoutes.registerStepTwo); // Navigator.popUntil(context, (route) => Utils.route(route, equalsTo: RegisterNew)); return; } else { @@ -513,7 +512,6 @@ class AuthenticationViewModel extends ChangeNotifier { notifyListeners(); } }); - this.selectedOption = selectedOption; notifyListeners(); } @@ -569,16 +567,16 @@ class AuthenticationViewModel extends ChangeNotifier { } else { request['forRegister'] = true; request['isRegister'] = true; - if (await isPatientOutsideSA(request: response)) { - print("=======IN SA======="); - chekUserNHICData(request: request); - } else { + if (isPatientOutsideSA(request: response)) { print("=======OUT SA======="); _appState.setAppAuthToken = response['LogInTokenID']; sendActivationCode( otpTypeEnum: OTPTypeEnumExtension.fromInt(request["OTP_SendType"]), nationalIdOrFileNumber: request["PatientIdentificationID"].toString(), phoneNumber: request["PatientMobileNumber"].toString()); + } else { + print("=======IN SA======="); + chekUserNHICData(request: request); } } } else { @@ -587,23 +585,20 @@ class AuthenticationViewModel extends ChangeNotifier { } } - Future isPatientOutsideSA({required dynamic request}) { - try { - if (request is Map && request.containsKey("PatientOutSA")) { - if (!request["PatientOutSA"]) { - return Future.value(true); - } else { - return Future.value(false); - } + bool isPatientOutsideSA({required dynamic request}) { + bool isOutSideSa = false; + if (request is Map && request.containsKey("PatientOutSA")) { + if (request["PatientOutSA"] == true) { + isOutSideSa = true; } else { - return Future.value(false); + isOutSideSa = false; } - } catch (e) { - return Future.value(false); } + print(isOutSideSa); + return isOutSideSa; } - Future isPatientHasFile({required dynamic request}) async { + bool isPatientHasFile({required dynamic request}) { bool isFile = false; if (request != null && request["NationalID"] != null) { isFile = request["NationalID"].length < 10; @@ -615,13 +610,12 @@ class AuthenticationViewModel extends ChangeNotifier { final resultEither = await _authenticationRepo.checkUserStatus(commonAuthanticatedRequest: request); resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.data is Map) { - _appState.setNHICUserData = CheckUserStatusResponseNHIC.fromJson(apiResponse.data as Map); + setNHICData(apiResponse.data, request); sendActivationCode( - otpTypeEnum: OTPTypeEnumExtension.fromInt(request["OTP_SendType"]), - nationalIdOrFileNumber: request["PatientIdentificationID"].toString(), - phoneNumber: request["PatientMobileNumber"].toString(), - payload: request, - ); + otpTypeEnum: OTPTypeEnumExtension.fromInt(request["OTP_SendType"]), + nationalIdOrFileNumber: request["PatientIdentificationID"].toString(), + phoneNumber: request["PatientMobileNumber"].toString(), + payload: request); } }); @@ -661,6 +655,12 @@ class AuthenticationViewModel extends ChangeNotifier { // }); } + void setNHICData(dynamic data, dynamic request) { + _appState.setNHICUserData = CheckUserStatusResponseNHIC.fromJson(data as Map); + request["healthId"] = _appState.getNHICUserData.healthId; + _appState.setUserRegistrationPayload = RegistrationDataModelPayload.fromJson(request); + } + @override void dispose() { nationalIdController.dispose(); diff --git a/lib/features/authentication/models/request_models/registration_payload_model.dart b/lib/features/authentication/models/request_models/registration_payload_model.dart new file mode 100644 index 0000000..fa4f612 --- /dev/null +++ b/lib/features/authentication/models/request_models/registration_payload_model.dart @@ -0,0 +1,69 @@ +import 'dart:convert'; + +class RegistrationDataModelPayload { + int? patientMobileNumber; + String? zipCode; + int? searchType; + int? patientId; + int? patientIdentificationId; + int? otpSendType; + int? patientOutSa; + bool? isDentalAllowedBackend; + String? dob; + int? isHijri; + bool? forRegister; + bool? isRegister; + String? healthId; + + RegistrationDataModelPayload({ + this.patientMobileNumber, + this.zipCode, + this.searchType, + this.patientId, + this.patientIdentificationId, + this.otpSendType, + this.patientOutSa, + this.isDentalAllowedBackend, + this.dob, + this.isHijri, + this.forRegister, + this.isRegister, + this.healthId, + }); + + factory RegistrationDataModelPayload.fromRawJson(String str) => RegistrationDataModelPayload.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory RegistrationDataModelPayload.fromJson(Map json) => RegistrationDataModelPayload( + patientMobileNumber: json["PatientMobileNumber"], + zipCode: json["ZipCode"], + searchType: json["SearchType"], + patientId: json["PatientID"], + patientIdentificationId: json["PatientIdentificationID"], + otpSendType: json["OTP_SendType"], + patientOutSa: json["PatientOutSA"], + isDentalAllowedBackend: json["isDentalAllowedBackend"], + dob: json["DOB"], + isHijri: json["IsHijri"], + forRegister: json["forRegister"], + isRegister: json["isRegister"], + healthId: json["healthId"], + ); + + Map toJson() => { + "PatientMobileNumber": patientMobileNumber, + "ZipCode": zipCode, + "SearchType": searchType, + "PatientID": patientId, + "PatientIdentificationID": patientIdentificationId, + "OTP_SendType": otpSendType, + "PatientOutSA": patientOutSa, + "isDentalAllowedBackend": isDentalAllowedBackend, + "DOB": dob, + "IsHijri": isHijri, + "forRegister": forRegister, + "isRegister": isRegister, + "healthId": healthId, + }; +} diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart index 1e4c650..92faaee 100644 --- a/lib/presentation/authentication/register_step2.dart +++ b/lib/presentation/authentication/register_step2.dart @@ -19,31 +19,25 @@ import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:provider/provider.dart'; class RegisterNewStep2 extends StatefulWidget { - var nHICData; - var payload; - - RegisterNewStep2(this.nHICData, this.payload, {Key? key}) : super(key: key); + RegisterNewStep2({Key? key}) : super(key: key); @override _RegisterNew createState() => _RegisterNew(); } class _RegisterNew extends State { - bool isFromDubai = true; AuthenticationViewModel? authVM; @override void initState() { super.initState(); authVM = context.read(); - authVM!.loadCountriesData(context: context); - // isFromDubai = widget.payload.zipCode!.contains("971") || widget.payload.zipCode!.contains("+971"); } @override void dispose() { super.dispose(); - authVM!.clearDefaultInputValues(); + // authVM!.clearDefaultInputValues(); } @override @@ -72,22 +66,23 @@ class _RegisterNew extends State { child: Column( children: [ TextInputWidget( - labelText: isFromDubai ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(), - hintText: isFromDubai ? "name" ?? "" : (widget.nHICData!.firstNameEn!.toUpperCase() + " " + widget.nHICData!.lastNameEn!.toUpperCase()), - controller: null, + labelText: authVM!.isUserFromUAE() ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(), + hintText: + authVM!.isUserFromUAE() ? "" : ("${appState.getNHICUserData.firstNameEn!.toUpperCase()} ${appState.getNHICUserData.lastNameEn!.toUpperCase()}"), + controller: authVM!.isUserFromUAE() ? authVM!.nameController : null, isEnable: true, prefix: null, isAllowRadius: false, isBorderAllowed: false, keyboardType: TextInputType.text, isAllowLeadingIcon: true, - isReadOnly: isFromDubai ? false : true, + isReadOnly: authVM!.isUserFromUAE() ? false : true, leadingIcon: AppAssets.user_circle) .paddingSymmetrical(0.h, 16.h), Divider(height: 1, color: AppColors.greyColor), TextInputWidget( labelText: LocaleKeys.nationalIdNumber.tr(), - hintText: isFromDubai ? "widget.payload.nationalID!" : (widget.nHICData!.idNumber ?? ""), + hintText: authVM!.isUserFromUAE() ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : (appState.getNHICUserData.idNumber ?? ""), controller: null, isEnable: true, prefix: null, @@ -98,7 +93,7 @@ class _RegisterNew extends State { leadingIcon: AppAssets.student_card) .paddingSymmetrical(0.h, 16.h), Divider(height: 1, color: AppColors.greyColor), - isFromDubai + authVM!.isUserFromUAE() ? Selector( selector: (_, authViewModel) => authViewModel.genderType, shouldRebuild: (previous, next) => previous != next, @@ -121,19 +116,19 @@ class _RegisterNew extends State { }) : TextInputWidget( labelText: LocaleKeys.gender.tr(), - hintText: (widget.nHICData!.gender ?? ""), + hintText: (appState.getNHICUserData.gender ?? ""), controller: null, isEnable: true, prefix: null, isAllowRadius: false, isBorderAllowed: false, isAllowLeadingIcon: true, - isReadOnly: isFromDubai ? false : true, + isReadOnly: authVM!.isUserFromUAE() ? false : true, leadingIcon: AppAssets.user_full, onChange: (value) {}) .paddingSymmetrical(0.h, 16.h), Divider(height: 1, color: AppColors.greyColor), - isFromDubai + authVM!.isUserFromUAE() ? Selector( selector: (_, authViewModel) => authViewModel.maritalStatus, shouldRebuild: (previous, next) => previous != next, @@ -158,8 +153,8 @@ class _RegisterNew extends State { : TextInputWidget( labelText: LocaleKeys.maritalStatus.tr(), hintText: appState.isArabic() - ? (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.typeAr) - : (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.type), + ? (MaritalStatusTypeExtension.fromValue(appState.getNHICUserData.maritalStatusCode)!.typeAr) + : (MaritalStatusTypeExtension.fromValue(appState.getNHICUserData.maritalStatusCode)!.type), isEnable: true, prefix: null, isAllowRadius: false, @@ -170,7 +165,7 @@ class _RegisterNew extends State { onChange: (value) {}) .paddingSymmetrical(0.h, 16.h), Divider(height: 1, color: AppColors.greyColor), - isFromDubai + authVM!.isUserFromUAE() ? Selector? countriesList, NationalityCountries? selectedCountry, bool isArabic})>( selector: (context, authViewModel) { final appState = getIt.get(); @@ -206,8 +201,8 @@ class _RegisterNew extends State { : TextInputWidget( labelText: LocaleKeys.nationality.tr(), hintText: appState.isArabic() - ? (authVM!.countriesList!.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).nameN ?? "") - : (authVM!.countriesList!.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).name ?? ""), + ? (authVM!.countriesList!.firstWhere((e) => e.id == (appState.getNHICUserData.nationalityCode ?? ""), orElse: () => NationalityCountries()).nameN ?? "") + : (authVM!.countriesList!.firstWhere((e) => e.id == (appState.getNHICUserData.nationalityCode ?? ""), orElse: () => NationalityCountries()).name ?? ""), isEnable: true, prefix: null, isAllowRadius: false, @@ -223,7 +218,7 @@ class _RegisterNew extends State { ), TextInputWidget( labelText: LocaleKeys.mobileNumber.tr(), - hintText: ("widget.payload.mobileNo" ?? ""), + hintText: (appState.getUserRegistrationPayload.patientMobileNumber.toString() ?? ""), controller: authVM!.phoneNumberController, isEnable: true, prefix: null, @@ -240,15 +235,15 @@ class _RegisterNew extends State { ), TextInputWidget( labelText: LocaleKeys.dob.tr(), - hintText: isFromDubai ? "widget.payload.dob!" : (widget.nHICData!.dateOfBirth ?? ""), + hintText: authVM!.isUserFromUAE() ? appState.getUserRegistrationPayload.dob! : (appState.getNHICUserData.dateOfBirth ?? ""), controller: authVM!.dobController, - isEnable: true, + isEnable: authVM!.isUserFromUAE() ? true : false, prefix: null, isBorderAllowed: false, isAllowLeadingIcon: true, isReadOnly: true, leadingIcon: AppAssets.birthday_cake, - selectionType: SelectionTypeEnum.calendar, + selectionType: authVM!.isUserFromUAE() ? SelectionTypeEnum.calendar : null, ).paddingSymmetrical(0.h, 16.h), ], ), diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index b33658f..29bed41 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -39,6 +39,7 @@ class _LandingPageState extends State { @override void initState() { authVM = context.read(); + authVM.savePushTokenToAppState(); if (mounted) { authVM.checkLastLoginStatus(() { // showQuickLogin(context, false); diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 93146d6..67bf208 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; +import 'package:hmg_patient_app_new/presentation/authentication/register.dart'; +import 'package:hmg_patient_app_new/presentation/authentication/register_step2.dart'; import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/splashPage.dart'; @@ -7,12 +9,15 @@ import 'package:hmg_patient_app_new/splashPage.dart'; class AppRoutes { static const String initialRoute = '/initialRoute'; static const String loginScreen = '/loginScreen'; - static const String registerNewScreen = '/registerNewScreen'; + static const String register = '/register'; + static const String registerStepTwo = '/registerStepTwo'; static const String landingScreen = '/landingScreen'; static Map get routes => { initialRoute: (context) => SplashPage(), loginScreen: (context) => LoginScreen(), landingScreen: (context) => LandingNavigation(), + register: (context) => RegisterNew(), + registerStepTwo: (context) => RegisterNewStep2(), }; } diff --git a/lib/widgets/dropdown/country_dropdown_widget.dart b/lib/widgets/dropdown/country_dropdown_widget.dart index 3d28e23..4e41c31 100644 --- a/lib/widgets/dropdown/country_dropdown_widget.dart +++ b/lib/widgets/dropdown/country_dropdown_widget.dart @@ -105,7 +105,7 @@ class _CustomCountryDropdownState extends State { SizedBox(width: 4.h), if (widget.isEnableTextField) SizedBox( - height: 20, + height: 18, width: 200, child: TextField( focusNode: textFocusNode, From a199ca040b7c1c5e0daa30608159eebe26da6c00 Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Tue, 9 Sep 2025 15:09:13 +0300 Subject: [PATCH 2/7] quick login inprogress --- lib/core/api_consts.dart | 4 + .../authentication/authentication_repo.dart | 55 +++++++- .../authentication_view_model.dart | 51 ++++++- .../insert_patient_mobile_deviceinfo.dart | 109 +++++++++++++++ .../patient_insert_device_imei_request.dart | 125 ++++++++++++++++++ lib/presentation/authentication/login.dart | 1 + .../authentication/saved_login_screen.dart | 14 +- lib/presentation/home/landing_page.dart | 4 +- lib/services/localauth_service.dart | 13 +- lib/widgets/buttons/custom_button.dart | 2 +- 10 files changed, 362 insertions(+), 16 deletions(-) create mode 100644 lib/features/authentication/models/request_models/insert_patient_mobile_deviceinfo.dart create mode 100644 lib/features/authentication/models/request_models/patient_insert_device_imei_request.dart diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 5b6c873..1bbbff8 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -799,6 +799,10 @@ class ApiConsts { static final String checkPatientForRegistration = 'Services/Authentication.svc/REST/CheckPatientForRegisteration'; static final String checkUserStatus= 'Services/NHIC.svc/REST/GetPatientInfo'; + static final String insertPatientDeviceIMEIData = 'Services/Patients.svc/REST/Patient_INSERTDeviceIMEI'; + static final String insertPatientMobileData = 'Services/MobileNotifications.svc/REST/Insert_PatientMobileDeviceInfo'; + + // static values for Api static final double appVersionID = 18.7; static final int appChannelId = 3; diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 21061dc..d58ffeb 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -28,6 +28,11 @@ abstract class AuthenticationRepo { Future>> checkPatientForRegistration({required dynamic commonAuthanticatedRequest}); Future>> checkUserStatus({required dynamic commonAuthanticatedRequest}); + + Future>> insertPatientIMEIData({required dynamic patientIMEIDataRequest}); + + Future>> insertPatientDeviceData({required dynamic patientDeviceDataRequest}); + } class AuthenticationRepoImp implements AuthenticationRepo { @@ -52,7 +57,14 @@ class AuthenticationRepoImp implements AuthenticationRepo { try { final list = response['Patient_SELECTDeviceIMEIbyIMEIList']; if (list == null || list.isEmpty) { - throw Exception("Device list is empty"); + //throw Exception(response); + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: null, + ); + return; } final model = SelectDeviceByImeiRespModelElement.fromJson(list[0] as Map); @@ -337,4 +349,45 @@ class AuthenticationRepoImp implements AuthenticationRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> insertPatientIMEIData({required patientIMEIDataRequest}) { + try { + GenericApiModel? apiResponse; + Failure? failure; + return apiClient.post( + ApiConsts.insertPatientDeviceIMEIData, + body: patientIMEIDataRequest, + 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()); + } + }, + ).then((_) { + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + }); + } catch (e) { + return Future.value(Left(UnknownFailure(e.toString()))); + } + } + + @override + Future> insertPatientDeviceData({required patientDeviceDataRequest}) { + + // TODO: implement insertPatientDeviceData + + throw UnimplementedError(); + } } diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 6a2ea74..4866f61 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -26,6 +26,9 @@ import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/localauth_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'models/request_models/insert_patient_mobile_deviceinfo.dart'; +import 'models/request_models/patient_insert_device_imei_request.dart'; + class AuthenticationViewModel extends ChangeNotifier { final AuthenticationRepo _authenticationRepo; final AppState _appState; @@ -59,6 +62,7 @@ class AuthenticationViewModel extends ChangeNotifier { NationalityCountries? pickedCountryByUAEUser; CalenderEnum calenderType = CalenderEnum.gregorian; + LoginTypeEnum loginTypeEnum = LoginTypeEnum.sms; //================== @@ -239,6 +243,8 @@ class AuthenticationViewModel extends ChangeNotifier { Future checkUserAuthentication({required OTPTypeEnum otpTypeEnum, Function(dynamic)? onSuccess, Function(String)? onError}) async { // TODO: THIS SHOULD BE REMOVED LATER ON AND PASSED FROM APP STATE DIRECTLY INTO API CLIENT. BECAUSE THIS API ONLY NEEDS FEW PARAMS FROM USER + loginTypeEnum = otpTypeEnum == OTPTypeEnum.sms ? LoginTypeEnum.sms : LoginTypeEnum.whatsapp; + if (phoneNumberController.text.isEmpty) { phoneNumberController.text = "504278212"; } @@ -500,12 +506,13 @@ class AuthenticationViewModel extends ChangeNotifier { await _dialogService.showErrorBottomSheet(message: message ?? "Something went wrong. ", onOkPressed: () {}); } - loginWithFingerPrintFace(int selectedOption) async { + loginWithFingerPrintFace() async { _localAuthService.authenticate().then((value) { if (value) { // we have to handle this if verification true; - checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: 0000, onWrongActivationCode: (String? message) {}); + // checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: 0000, onWrongActivationCode: (String? message) {}); // authenticated = true; + insertPatientIMEIData( OTPTypeEnum.faceIDFingerprint.toInt()); notifyListeners(); // navigateToHomeScreen(); } else { @@ -518,12 +525,22 @@ class AuthenticationViewModel extends ChangeNotifier { } checkLastLoginStatus(Function() onSuccess) async { + Future.delayed(Duration(seconds: 1), () { if (_appState.getSelectDeviceByImeiRespModelElement != null && (_appState.getSelectDeviceByImeiRespModelElement!.logInType == 1 || _appState.getSelectDeviceByImeiRespModelElement!.logInType == 4)) { - Future.delayed(Duration(seconds: 1), () { + phoneNumberController.text = (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") + ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") + : _appState.getAuthenticatedUser()!.mobileNumber)!; + nationalIdController.text = _appState.getAuthenticatedUser()!.nationalityId!; onSuccess(); - }); + } else if((loginTypeEnum == LoginTypeEnum.sms || loginTypeEnum == LoginTypeEnum.whatsapp && _appState.getSelectDeviceByImeiRespModelElement == null) && _appState.getAuthenticatedUser() != null){ + phoneNumberController.text = (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") + ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") + : _appState.getAuthenticatedUser()!.mobileNumber)!; + nationalIdController.text = _appState.getAuthenticatedUser()!.nationalityId!; + onSuccess(); } + }); } Future onRegisterPress({required OTPTypeEnum otpTypeEnum}) async { @@ -661,6 +678,32 @@ class AuthenticationViewModel extends ChangeNotifier { // }); } + Future insertPatientIMEIData(int loginType) async{ + + final resultEither = await _authenticationRepo.insertPatientIMEIData(patientIMEIDataRequest: PatientInsertDeviceImei(imei: _appState.deviceToken, deviceTypeId: _appState.getDeviceTypeID(), patientId: _appState.getAuthenticatedUser()!.patientId!, patientIdentificationNo:_appState.getAuthenticatedUser()!.nationalityId!, firstName: _appState.getAuthenticatedUser()!.firstName!, lastName: _appState.getAuthenticatedUser()!.lastName!, patientTypeId: _appState.getAuthenticatedUser()!.patientType, mobileNo:_appState.getAuthenticatedUser()!.mobileNumber!, logInTypeId: loginType, patientOutSa:_appState.getAuthenticatedUser()!.outSa! ).toJson()); + resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { + if (apiResponse.messageStatus == 1) { + print("Insert IMEI Success"); + insertPatientDeviceData( loginType); + } else { + print("Insert IMEI Failed"); + } + }); + + } + Future insertPatientDeviceData(int loginType) async{ + + final resultEither = await _authenticationRepo.insertPatientDeviceData(patientDeviceDataRequest: InsertPatientMobileDeviceInfo(deviceToken: _appState.deviceToken, deviceTypeId: _appState.getDeviceTypeID(), patientId: _appState.getAuthenticatedUser()!.patientId!, patientTypeId: _appState.getAuthenticatedUser()!.patientType, patientOutSa:_appState.getAuthenticatedUser()!.outSa!, loginType: loginType, languageId: _appState.getLanguageID(), latitude: _appState.userLat, longitude:_appState.userLong ).toJson()); + resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { + if (apiResponse.messageStatus == 1) { + print("Insert Device Data Success"); + } else { + print("Insert IMEI Failed"); + } + }); + + } + @override void dispose() { nationalIdController.dispose(); diff --git a/lib/features/authentication/models/request_models/insert_patient_mobile_deviceinfo.dart b/lib/features/authentication/models/request_models/insert_patient_mobile_deviceinfo.dart new file mode 100644 index 0000000..dbfb0e1 --- /dev/null +++ b/lib/features/authentication/models/request_models/insert_patient_mobile_deviceinfo.dart @@ -0,0 +1,109 @@ +import 'dart:convert'; + +class InsertPatientMobileDeviceInfo { + double? versionId; + int? channel; + int? languageId; + String? ipAdress; + String? generalid; + int? patientOutSa; + bool? isDentalAllowedBackend; + int? deviceTypeId; + int? patientId; + String? tokenId; + String? voipToken; + int? patientTypeId; + int? patientType; + String? deviceToken; + String? deviceType; + String? patientMobileNumber; + String? nationalId; + int? gender; + int? loginType; + String? macAddress; + double? latitude; + double? longitude; + String? sessionId; + + InsertPatientMobileDeviceInfo({ + this.versionId, + this.channel, + this.languageId, + this.ipAdress, + this.generalid, + this.patientOutSa, + this.isDentalAllowedBackend, + this.deviceTypeId, + this.patientId, + this.tokenId, + this.voipToken, + this.patientTypeId, + this.patientType, + this.deviceToken, + this.deviceType, + this.patientMobileNumber, + this.nationalId, + this.gender, + this.loginType, + this.macAddress, + this.latitude, + this.longitude, + this.sessionId, + }); + + factory InsertPatientMobileDeviceInfo.fromRawJson(String str) => InsertPatientMobileDeviceInfo.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory InsertPatientMobileDeviceInfo.fromJson(Map json) => InsertPatientMobileDeviceInfo( + versionId: json["VersionID"]?.toDouble(), + channel: json["Channel"], + languageId: json["LanguageID"], + ipAdress: json["IPAdress"], + generalid: json["generalid"], + patientOutSa: json["PatientOutSA"], + isDentalAllowedBackend: json["isDentalAllowedBackend"], + deviceTypeId: json["DeviceTypeID"], + patientId: json["PatientID"], + tokenId: json["TokenID"], + voipToken: json["VoipToken"], + patientTypeId: json["PatientTypeID"], + patientType: json["PatientType"], + deviceToken: json["DeviceToken"], + deviceType: json["DeviceType"], + patientMobileNumber: json["PatientMobileNumber"], + nationalId: json["NationalID"], + gender: json["Gender"], + loginType: json["LoginType"], + macAddress: json["MACAddress"], + latitude: json["Latitude"], + longitude: json["Longitude"], + sessionId: json[" SessionID"], + ); + + Map toJson() => { + "VersionID": versionId, + "Channel": channel, + "LanguageID": languageId, + "IPAdress": ipAdress, + "generalid": generalid, + "PatientOutSA": patientOutSa, + "isDentalAllowedBackend": isDentalAllowedBackend, + "DeviceTypeID": deviceTypeId, + "PatientID": patientId, + "TokenID": tokenId, + "VoipToken": voipToken, + "PatientTypeID": patientTypeId, + "PatientType": patientType, + "DeviceToken": deviceToken, + "DeviceType": deviceType, + "PatientMobileNumber": patientMobileNumber, + "NationalID": nationalId, + "Gender": gender, + "LoginType": loginType, + "MACAddress": macAddress, + "Latitude": latitude, + "Longitude": longitude, + " SessionID": sessionId, + }; +} diff --git a/lib/features/authentication/models/request_models/patient_insert_device_imei_request.dart b/lib/features/authentication/models/request_models/patient_insert_device_imei_request.dart new file mode 100644 index 0000000..f54bb83 --- /dev/null +++ b/lib/features/authentication/models/request_models/patient_insert_device_imei_request.dart @@ -0,0 +1,125 @@ +import 'dart:convert'; + +class PatientInsertDeviceImei { + String? setupId; + int? patientType; + int? patientId; + String? firstName; + String? firstNameN; + String? lastNameN; + String? lastName; + int? preferredLanguage; + String? patientIdentificationNo; + bool? outSa; + String? identificationNo; + String? mobileNo; + String? tokenId; + String? imei; + bool? biometricEnabled; + int? logInTypeId; + double? versionId; + int? channel; + int? languageId; + String? ipAdress; + String? generalid; + int? latitude; + int? longitude; + int? deviceTypeId; + int? patientTypeId; + int? patientOutSa; + String? sessionId; + + PatientInsertDeviceImei({ + this.setupId, + this.patientType, + this.patientId, + this.firstName, + this.firstNameN, + this.lastNameN, + this.lastName, + this.preferredLanguage, + this.patientIdentificationNo, + this.outSa, + this.identificationNo, + this.mobileNo, + this.tokenId, + this.imei, + this.biometricEnabled, + this.logInTypeId, + this.versionId, + this.channel, + this.languageId, + this.ipAdress, + this.generalid, + this.latitude, + this.longitude, + this.deviceTypeId, + this.patientTypeId, + this.patientOutSa, + this.sessionId, + }); + + factory PatientInsertDeviceImei.fromRawJson(String str) => PatientInsertDeviceImei.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory PatientInsertDeviceImei.fromJson(Map json) => PatientInsertDeviceImei( + setupId: json["SetupID"], + patientType: json["PatientType"], + patientId: json["PatientID"], + firstName: json["FirstName"], + firstNameN: json["FirstNameN"], + lastNameN: json["LastNameN"], + lastName: json["LastName"], + preferredLanguage: json["PreferredLanguage"], + patientIdentificationNo: json["PatientIdentificationNo"], + outSa: json["OutSA"], + identificationNo: json["IdentificationNo"], + mobileNo: json["MobileNo"], + tokenId: json["TokenID"], + imei: json["IMEI"], + biometricEnabled: json["BiometricEnabled"], + logInTypeId: json["LogInTypeID"], + versionId: json["VersionID"]?.toDouble(), + channel: json["Channel"], + languageId: json["LanguageID"], + ipAdress: json["IPAdress"], + generalid: json["generalid"], + latitude: json["Latitude"], + longitude: json["Longitude"], + deviceTypeId: json["DeviceTypeID"], + patientTypeId: json["PatientTypeID"], + patientOutSa: json["PatientOutSA"], + sessionId: json[" SessionID"], + ); + + Map toJson() => { + "SetupID": setupId, + "PatientType": patientType, + "PatientID": patientId, + "FirstName": firstName, + "FirstNameN": firstNameN, + "LastNameN": lastNameN, + "LastName": lastName, + "PreferredLanguage": preferredLanguage, + "PatientIdentificationNo": patientIdentificationNo, + "OutSA": outSa, + "IdentificationNo": identificationNo, + "MobileNo": mobileNo, + "TokenID": tokenId, + "IMEI": imei, + "BiometricEnabled": biometricEnabled, + "LogInTypeID": logInTypeId, + "VersionID": versionId, + "Channel": channel, + "LanguageID": languageId, + "IPAdress": ipAdress, + "generalid": generalid, + "Latitude": latitude, + "Longitude": longitude, + "DeviceTypeID": deviceTypeId, + "PatientTypeID": patientTypeId, + "PatientOutSA": patientOutSa, + " SessionID": sessionId, + }; +} diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart index f6e725a..0b463cc 100644 --- a/lib/presentation/authentication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -203,6 +203,7 @@ class LoginScreenState extends State { borderColor: AppColors.borderOnlyColor, textColor: AppColors.textColor, icon: AppAssets.whatsapp, + iconColor: null, ), ), ], diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index 357daa6..3285163 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -81,7 +81,7 @@ class _SavedLogin extends State { ("${LocaleKeys.lastloginBy.tr()} ${LoginTypeExtension.fromValue(appState.getSelectDeviceByImeiRespModelElement!.logInType!)!.displayName}") .toText14(isBold: true, color: AppColors.greyTextColor), (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null - ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y at HH:mm") + ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y 'at' HH:mm") : '--') .toText16(isBold: true, color: AppColors.textColor), @@ -95,7 +95,7 @@ class _SavedLogin extends State { text: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}", onPressed: () { if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { - authVm.loginWithFingerPrintFace(loginType.toInt); + authVm.loginWithFingerPrintFace(); } else { // int? val = loginType.toInt; authVm.checkUserAuthentication(otpTypeEnum: loginType == LoginTypeEnum.sms ? OTPTypeEnum.sms : OTPTypeEnum.whatsapp); @@ -108,7 +108,7 @@ class _SavedLogin extends State { fontWeight: FontWeight.w500, borderRadius: 12, padding: EdgeInsets.fromLTRB(0, 10, 0, 10), - icon: AppAssets.apple_finder, + icon: AppAssets.sms, ), ), ], @@ -165,7 +165,7 @@ class _SavedLogin extends State { backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedBorderColor, textColor: AppColors.whiteColor, - icon: AppAssets.message, + icon: AppAssets.sms, ), ), Row( @@ -213,9 +213,11 @@ class _SavedLogin extends State { ) : CustomButton( text: "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}", + icon: AppAssets.whatsapp, + iconColor: null, onPressed: () { if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { - authVm.loginWithFingerPrintFace(loginType.toInt); + authVm.loginWithFingerPrintFace(); } else { loginType = LoginTypeEnum.whatsapp; int? val = loginType.toInt; @@ -227,7 +229,7 @@ class _SavedLogin extends State { textColor: Color(0xFF2E3039), borderWidth: 2, padding: EdgeInsets.fromLTRB(0, 14, 0, 14), - icon: AppAssets.whatsapp, + ), const Spacer(flex: 2), // OR divider diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index b33658f..e569edb 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -41,7 +41,7 @@ class _LandingPageState extends State { authVM = context.read(); if (mounted) { authVM.checkLastLoginStatus(() { - // showQuickLogin(context, false); + showQuickLogin(context, false); }); } super.initState(); @@ -328,7 +328,7 @@ class _LandingPageState extends State { isDone: isDone, onPressed: () { // sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true); - // loginWithFingerPrintFace(3, 1, user, deviceToken); + authVM.loginWithFingerPrintFace(); }, ), height: 400, diff --git a/lib/services/localauth_service.dart b/lib/services/localauth_service.dart index a01d1c1..2875a7f 100644 --- a/lib/services/localauth_service.dart +++ b/lib/services/localauth_service.dart @@ -6,22 +6,31 @@ class LocalAuthService { final LocalAuthentication localAuth; final LoggerService loggerService; LocalAuthService({required this.localAuth, required this.loggerService}); - Future authenticate() async { try { - bool isAuthenticated = await localAuth.authenticate( + final canCheck = await localAuth.canCheckBiometrics; + final isDeviceSupported = await localAuth.isDeviceSupported(); + + if (!canCheck || !isDeviceSupported) { + return false; + } + + final isAuthenticated = await localAuth.authenticate( localizedReason: 'Please authenticate to proceed', options: const AuthenticationOptions( biometricOnly: true, stickyAuth: true, ), ); + return isAuthenticated; } catch (e) { + print(e); return false; } } + Future canCheckBiometrics() async { try { return await localAuth.canCheckBiometrics; diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index 5413cea..e1448dc 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -18,7 +18,7 @@ class CustomButton extends StatelessWidget { final String? fontFamily; final FontWeight fontWeight; final bool isDisabled; - final Color iconColor; + final Color? iconColor; final double height; final double iconSize; From ffb399c704ac155e152153fbda204e99aa573d88 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Tue, 9 Sep 2025 15:22:32 +0300 Subject: [PATCH 3/7] fixes for signup --- .../authentication_view_model.dart | 18 ++++++++++++++++-- lib/presentation/authentication/register.dart | 4 ++-- .../authentication/register_step2.dart | 14 +++----------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index e3c8520..07c8980 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -50,7 +50,7 @@ class AuthenticationViewModel extends ChangeNotifier { _authenticationRepo = authenticationRepo, _localAuthService = localAuthService; - final TextEditingController nationalIdController = TextEditingController(), phoneNumberController = TextEditingController(), dobController = TextEditingController(), nameController = TextEditingController(); + final TextEditingController nationalIdController = TextEditingController(), phoneNumberController = TextEditingController(), dobController = TextEditingController(), nameController = TextEditingController(), emailController = TextEditingController(); CountryEnum selectedCountrySignup = CountryEnum.saudiArabia; MaritalStatusTypeEnum? maritalStatus; GenderTypeEnum? genderType; @@ -524,7 +524,7 @@ class AuthenticationViewModel extends ChangeNotifier { } } - Future onRegisterPress({required OTPTypeEnum otpTypeEnum}) async { + Future onRegistrationStart({required OTPTypeEnum otpTypeEnum}) async { bool isOutSidePatient = selectedCountrySignup.countryCode == CountryEnum.unitedArabEmirates.countryCode ? true : false; final request = await RequestUtils.getPatientAuthenticationRequest( @@ -555,6 +555,20 @@ class AuthenticationViewModel extends ChangeNotifier { }); } + + Future onRegistrationComplete() async{ + + // if (emailAddress.text.isEmpty) { + // Utils.showErrorToast(TranslationBase.of(context).enterEmailAddress); + // return; + // } else { + // Navigator.of(context).pop(); + // registerNow(); + // } + //authVM!.clearDefaultInputValues(); + } + + Future checkUserStatusForRegistration({required dynamic response, required dynamic request}) async { if (response is Map) { _appState.setAppAuthToken = response["LogInTokenID"]; diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart index 00f4808..f238582 100644 --- a/lib/presentation/authentication/register.dart +++ b/lib/presentation/authentication/register.dart @@ -222,7 +222,7 @@ class _RegisterNew extends State { child: CustomButton( text: LocaleKeys.sendOTPSMS.tr(), onPressed: () async { - await authVM.onRegisterPress(otpTypeEnum: OTPTypeEnum.sms); + await authVM.onRegistrationStart(otpTypeEnum: OTPTypeEnum.sms); }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedBorderColor, @@ -245,7 +245,7 @@ class _RegisterNew extends State { child: CustomButton( text: LocaleKeys.sendOTPWHATSAPP.tr(), onPressed: () async { - await authVM.onRegisterPress(otpTypeEnum: OTPTypeEnum.whatsapp); + await authVM.onRegistrationStart(otpTypeEnum: OTPTypeEnum.whatsapp); }, backgroundColor: AppColors.whiteColor, borderColor: AppColors.borderOnlyColor, diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart index 92faaee..bd23be1 100644 --- a/lib/presentation/authentication/register_step2.dart +++ b/lib/presentation/authentication/register_step2.dart @@ -37,7 +37,6 @@ class _RegisterNew extends State { @override void dispose() { super.dispose(); - // authVM!.clearDefaultInputValues(); } @override @@ -67,8 +66,7 @@ class _RegisterNew extends State { children: [ TextInputWidget( labelText: authVM!.isUserFromUAE() ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(), - hintText: - authVM!.isUserFromUAE() ? "" : ("${appState.getNHICUserData.firstNameEn!.toUpperCase()} ${appState.getNHICUserData.lastNameEn!.toUpperCase()}"), + hintText: authVM!.isUserFromUAE() ? "" : ("${appState.getNHICUserData.firstNameEn!.toUpperCase()} ${appState.getNHICUserData.lastNameEn!.toUpperCase()}"), controller: authVM!.isUserFromUAE() ? authVM!.nameController : null, isEnable: true, prefix: null, @@ -319,7 +317,7 @@ class _RegisterNew extends State { padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom), child: SingleChildScrollView( child: GenericBottomSheet( - textController: TextEditingController(), + textController: authVM!.emailController, isForEmail: true, buttons: [ Padding( @@ -327,13 +325,7 @@ class _RegisterNew extends State { child: CustomButton( text: LocaleKeys.submit, onPressed: () { - // if (emailAddress.text.isEmpty) { - // Utils.showErrorToast(TranslationBase.of(context).enterEmailAddress); - // return; - // } else { - // Navigator.of(context).pop(); - // registerNow(); - // } + authVM!.onRegistrationComplete(); }, backgroundColor: AppColors.bgGreenColor, borderColor: AppColors.bgGreenColor, From e37eb78f815e8cf410b265fbbd1b9f0b3861ee04 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 9 Sep 2025 15:40:14 +0300 Subject: [PATCH 4/7] my appointments changes implementation contd. --- assets/images/svg/lab_result_icon.svg | 7 +- assets/images/svg/prescription_item_icon.svg | 6 + assets/images/svg/radiology_icon.svg | 4 + assets/images/svg/rebook_appointment_icon.svg | 4 + assets/images/svg/report_icon.svg | 3 + lib/core/app_assets.dart | 4 + .../authentication/authentication_repo.dart | 2 +- .../authentication_view_model.dart | 4 +- lib/features/insurance/insurance_repo.dart | 2 +- .../my_appointments/my_appointments_repo.dart | 48 +- .../my_appointments_view_model.dart | 51 +- .../utils/appointment_type.dart | 19 + .../prescriptions/prescriptions_repo.dart | 41 +- .../prescriptions_view_model.dart | 1 - .../appointment_details_page.dart | 501 ++++++++++++++---- .../appointment_payment_page.dart | 32 +- .../appointments/my_appointments_page.dart | 177 +++++-- .../widgets/appointment_card.dart | 142 +++-- .../widgets/appointment_doctor_card.dart | 50 +- .../insurance_update_details_card.dart | 41 +- .../widgets/patient_insurance_card.dart | 44 +- .../medical_file/medical_file_page.dart | 42 +- .../widgets/medical_file_card.dart | 11 +- .../prescription_detail_page.dart | 87 +-- 24 files changed, 883 insertions(+), 440 deletions(-) create mode 100644 assets/images/svg/prescription_item_icon.svg create mode 100644 assets/images/svg/radiology_icon.svg create mode 100644 assets/images/svg/rebook_appointment_icon.svg create mode 100644 assets/images/svg/report_icon.svg diff --git a/assets/images/svg/lab_result_icon.svg b/assets/images/svg/lab_result_icon.svg index 245048d..a8c442d 100644 --- a/assets/images/svg/lab_result_icon.svg +++ b/assets/images/svg/lab_result_icon.svg @@ -1,4 +1,5 @@ - - - + + + + diff --git a/assets/images/svg/prescription_item_icon.svg b/assets/images/svg/prescription_item_icon.svg new file mode 100644 index 0000000..6aba05f --- /dev/null +++ b/assets/images/svg/prescription_item_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/radiology_icon.svg b/assets/images/svg/radiology_icon.svg new file mode 100644 index 0000000..e1ae996 --- /dev/null +++ b/assets/images/svg/radiology_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/rebook_appointment_icon.svg b/assets/images/svg/rebook_appointment_icon.svg new file mode 100644 index 0000000..da4533a --- /dev/null +++ b/assets/images/svg/rebook_appointment_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/report_icon.svg b/assets/images/svg/report_icon.svg new file mode 100644 index 0000000..97b6db8 --- /dev/null +++ b/assets/images/svg/report_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index eff2ccc..eec83ba 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -85,6 +85,10 @@ class AppAssets { static const String directions_icon = '$svgBasePath/directions_icon.svg'; static const String apple_pay_button = '$svgBasePath/pay_with_apple_pay.svg'; static const String bell = '$svgBasePath/bell.svg'; + static const String rebook_appointment_icon = '$svgBasePath/rebook_appointment_icon.svg'; + static const String report_icon = '$svgBasePath/report_icon.svg'; + static const String radiology_icon = '$svgBasePath/radiology_icon.svg'; + static const String prescription_item_icon = '$svgBasePath/prescription_item_icon.svg'; //bottom navigation// diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 21061dc..21415df 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -38,7 +38,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { @override Future>> selectDeviceByImei({required String firebaseToken}) async { - final mapDevice = {"IMEI": firebaseToken}; + Map mapDevice = {"IMEI": firebaseToken}; try { GenericApiModel? apiResponse; Failure? failure; diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index c451c40..d324a71 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -167,8 +167,8 @@ class AuthenticationViewModel extends ChangeNotifier { Future selectDeviceImei({required Function(dynamic data) onSuccess, Function(String)? onError}) async { // LoadingUtils.showFullScreenLoading(); // String firebaseToken = _appState.deviceToken; - // String firebaseToken = await Utils.getStringFromPrefs(CacheConst.pushToken); - String firebaseToken = "fY1fq_cITMmUCztA3UKKL9:APA91bEb2ZcdCPQPq3QsA0NW6a6btFvN-JjB1Pn3ZCoCzBMmVUhhh1ZQMtRn9tYPQ5G-jHDLiEpVAlBuRCVMkLDxa-zijsqbIui-4A-ynwclDWGFT4bUHTc"; + String firebaseToken = await Utils.getStringFromPrefs(CacheConst.pushToken); + // String firebaseToken = "fY1fq_cITMmUCztA3UKKL9:APA91bEb2ZcdCPQPq3QsA0NW6a6btFvN-JjB1Pn3ZCoCzBMmVUhhh1ZQMtRn9tYPQ5G-jHDLiEpVAlBuRCVMkLDxa-zijsqbIui-4A-ynwclDWGFT4bUHTc"; // == "" // ? "dOGRRszQQMGe_9wA5Hx3kO:APA91bFV5IcIJXvcCXXk0tc2ddtZgWwCPq7sGSuPr-YW7iiJpQZKgFGN9GAzCVOWL8MfheaP1slE8MdxB7lczdPBGdONQ7WbMmhgHcsUCUktq-hsapGXXqc" // : _appState.deviceToken; diff --git a/lib/features/insurance/insurance_repo.dart b/lib/features/insurance/insurance_repo.dart index 68b53e4..874cc28 100644 --- a/lib/features/insurance/insurance_repo.dart +++ b/lib/features/insurance/insurance_repo.dart @@ -106,7 +106,7 @@ class InsuranceRepoImp implements InsuranceRepo { @override Future>> getPatientInsuranceDetailsForUpdate({required String patientId, required String identificationNo}) async { - final mapDevice = {"SetupID": "010266", "ProjectID": 15, "PatientIdentificationID": identificationNo, "IsFamily": false, "ParentID": 0}; + Map mapDevice = {"SetupID": "010266", "ProjectID": 15, "PatientIdentificationID": identificationNo, "IsFamily": false, "ParentID": 0}; try { GenericApiModel? apiResponse; diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 0815b94..8fc477c 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -8,8 +8,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class MyAppointmentsRepo { - Future>>> getPatientAppointments( - {required bool isActiveAppointment, required bool isArrivedAppointments}); + Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments}); Future>> getPatientShareAppointment({required int projectID, required int clinicID, required String appointmentNo}); @@ -28,6 +27,8 @@ abstract class MyAppointmentsRepo { Future>> generateAppointmentQR({required int clinicID, required int projectID, required String appointmentNo, required int isFollowUp}); Future>> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); + + Future>> confirmAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -37,8 +38,7 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { MyAppointmentsRepoImp({required this.loggerService, required this.apiClient}); @override - Future>>> getPatientAppointments( - {required bool isActiveAppointment, required bool isArrivedAppointments}) async { + Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments}) async { Map mapDevice = { "IsActiveAppointment": isActiveAppointment, "isDentalAllowedBackend": false, @@ -310,4 +310,44 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> confirmAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}) async { + Map requestBody = { + "AppointmentNumber": patientAppointmentHistoryResponseModel.appointmentNo, + "IsLiveCareAppointment": patientAppointmentHistoryResponseModel.isLiveCareAppointment, + "ClinicID": patientAppointmentHistoryResponseModel.clinicID, + "ProjectID": patientAppointmentHistoryResponseModel.projectID, + "ConfirmationBy": 102, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + CONFIRM_APPOINTMENT, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + 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/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 5c952e9..2815977 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -14,6 +14,10 @@ class MyAppointmentsViewModel extends ChangeNotifier { bool isAppointmentPatientShareLoading = false; List patientAppointmentsHistoryList = []; + + List patientUpcomingAppointmentsHistoryList = []; + List patientArrivedAppointmentsHistoryList = []; + PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel; MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService}); @@ -25,6 +29,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { initAppointmentsViewModel() { patientAppointmentsHistoryList.clear(); + patientUpcomingAppointmentsHistoryList.clear(); + patientArrivedAppointmentsHistoryList.clear(); isMyAppointmentsLoading = true; isAppointmentPatientShareLoading = true; notifyListeners(); @@ -50,6 +56,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { Future getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: isActiveAppointment, isArrivedAppointments: isArrivedAppointments); + final resultArrived = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), @@ -57,7 +64,23 @@ class MyAppointmentsViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { - patientAppointmentsHistoryList = apiResponse.data!; + patientUpcomingAppointmentsHistoryList = apiResponse.data!; + isMyAppointmentsLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + + resultArrived.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientArrivedAppointmentsHistoryList = apiResponse.data!; isMyAppointmentsLoading = false; notifyListeners(); if (onSuccess != null) { @@ -66,6 +89,13 @@ class MyAppointmentsViewModel extends ChangeNotifier { } }, ); + + patientAppointmentsHistoryList.addAll(patientUpcomingAppointmentsHistoryList); + patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList); + + print('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}'); + print('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}'); + print('All Appointments: ${patientAppointmentsHistoryList.length}'); } Future getPatientShareAppointment(int projectID, int clinicID, String appointmentNo, {Function(dynamic)? onSuccess, Function(String)? onError}) async { @@ -145,6 +175,25 @@ class MyAppointmentsViewModel extends ChangeNotifier { ); } + Future confirmAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.confirmAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + Future createAdvancePayment( {required String paymentMethodName, required int projectID, diff --git a/lib/features/my_appointments/utils/appointment_type.dart b/lib/features/my_appointments/utils/appointment_type.dart index cd8bb56..abc23dc 100644 --- a/lib/features/my_appointments/utils/appointment_type.dart +++ b/lib/features/my_appointments/utils/appointment_type.dart @@ -43,6 +43,25 @@ class AppointmentType { } } + static Color getNextActionTextColor(nextAction) { + switch (nextAction) { + case 0: + return AppColors.successColor; + case 10: + return AppColors.successColor; + case 15: + return AppColors.textColor; + case 20: + return AppColors.infoColor; + case 50: + return AppColors.successColor; + case 90: + return AppColors.alertColor; + default: + return AppColors.successColor; + } + } + static Color getNextActionButtonColor(nextAction) { switch (nextAction) { case 0: diff --git a/lib/features/prescriptions/prescriptions_repo.dart b/lib/features/prescriptions/prescriptions_repo.dart index f584f7b..5f2db30 100644 --- a/lib/features/prescriptions/prescriptions_repo.dart +++ b/lib/features/prescriptions/prescriptions_repo.dart @@ -21,23 +21,7 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { @override Future>>> getPatientPrescriptionOrders({required String patientId}) async { - final mapDevice = { - "isDentalAllowedBackend": false, - "VersionID": 50.0, - "Channel": 3, - "LanguageID": 2, - "IPAdress": "10.20.10.20", - "generalid": "Cs2020@2016\$2958", - "Latitude": 0.0, - "Longitude": 0.0, - "DeviceTypeID": 1, - "PatientType": 1, - "PatientTypeID": 1, - "TokenID": "@dm!n", - "PatientID": "1018977", - "PatientOutSA": "0", - "SessionID": "03478TYC02N80874CTYN04883475!?" - }; + Map mapDevice = {}; try { GenericApiModel>? apiResponse; @@ -52,7 +36,7 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { try { final list = response['PatientPrescriptionList']; if (list == null || list.isEmpty) { - throw Exception("lab list is empty"); + // throw Exception("lab list is empty"); } final prescriptionOrders = list.map((item) => PatientPrescriptionsResponseModel.fromJson(item as Map)).toList().cast(); @@ -78,28 +62,13 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { @override Future>>> getPatientPrescriptionDetails({required PatientPrescriptionsResponseModel prescriptionsResponseModel}) async { - final mapDevice = { + Map mapDevice = { "AppointmentNo": prescriptionsResponseModel.appointmentNo.toString(), "SetupID": prescriptionsResponseModel.setupID, "EpisodeID": prescriptionsResponseModel.episodeID.toString(), "ClinicID": prescriptionsResponseModel.clinicID.toString(), "ProjectID": prescriptionsResponseModel.projectID.toString(), - "DischargeNo": prescriptionsResponseModel.dischargeNo.toString(), - "isDentalAllowedBackend": false, - "VersionID": 50.0, - "Channel": 3, - "LanguageID": 2, - "IPAdress": "10.20.10.20", - "generalid": "Cs2020@2016\$2958", - "Latitude": 0.0, - "Longitude": 0.0, - "DeviceTypeID": 1, - "PatientType": 1, - "PatientTypeID": 1, - "TokenID": "@dm!n", - "PatientID": "1018977", - "PatientOutSA": "0", - "SessionID": "03478TYC02N80874CTYN04883475!?" + "DischargeNo": prescriptionsResponseModel.dischargeNo.toString() }; try { @@ -115,7 +84,7 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { try { final list = prescriptionsResponseModel.isInOutPatient! ? response['ListPRM'] : response['INP_GetPrescriptionReport_List']; if (list == null || list.isEmpty) { - throw Exception("prescription list is empty"); + // throw Exception("prescription list is empty"); } final prescriptionOrders = list.map((item) => PrescriptionDetailResponseModel.fromJson(item as Map)).toList().cast(); diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index ff93f84..d57b17e 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -31,7 +31,6 @@ class PrescriptionsViewModel extends ChangeNotifier { patientPrescriptionOrdersByHospital.clear(); patientPrescriptionOrdersViewList.clear(); isPrescriptionsOrdersLoading = true; - isPrescriptionsDetailsLoading = true; isSortByClinic = true; getPatientPrescriptionOrders(); notifyListeners(); diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 4255c71..91a15a4 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -1,6 +1,9 @@ +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.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'; @@ -11,16 +14,23 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/models/resp_models/patient_prescriptions_response_model.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_payment_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart'; +import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart'; +import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_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/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:maps_launcher/maps_launcher.dart'; import 'package:provider/provider.dart'; +import '../medical_file/widgets/medical_file_card.dart'; + class AppointmentDetailsPage extends StatefulWidget { AppointmentDetailsPage({super.key, required this.patientAppointmentHistoryResponseModel}); @@ -32,10 +42,23 @@ class AppointmentDetailsPage extends StatefulWidget { class _AppointmentDetailsPageState extends State { late MyAppointmentsViewModel myAppointmentsViewModel; + late PrescriptionsViewModel prescriptionsViewModel; + + @override + void initState() { + scheduleMicrotask(() { + if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { + prescriptionsViewModel.setPrescriptionsDetailsLoading(); + prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); + } + }); + super.initState(); + } @override Widget build(BuildContext context) { myAppointmentsViewModel = Provider.of(context); + prescriptionsViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, appBar: AppBar( @@ -52,7 +75,23 @@ class _AppointmentDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Appointment Details".needTranslation.toText24(isBold: true), + "Appointment Details".needTranslation.toText20(isBold: true), + if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) + CustomButton( + text: "Report".needTranslation, + onPressed: () {}, + 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, + iconSize: 16.h, + icon: AppAssets.report_icon, + iconColor: AppColors.primaryRedColor, + ) ], ), SizedBox(height: 24.h), @@ -74,8 +113,7 @@ class _AppointmentDetailsPageState extends State { Navigator.of(context).pop(); showCommonBottomSheet(context, child: Utils.getSuccessWidget(loadingText: "Appointment Cancelled Successfully".needTranslation), - callBackFunc: (str) { - }, + callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, @@ -89,106 +127,266 @@ class _AppointmentDetailsPageState extends State { onRescheduleTap: () {}, ), SizedBox(height: 16.h), - if (!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) - Column( - children: [ - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - "Appointment Status".needTranslation.toText16(isBold: true), - ], - ), - SizedBox(height: 4.h), - (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) - ? "Not Confirmed".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) - : "Confirmed".needTranslation.toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), - SizedBox(height: 16.h), - Stack( + !AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? Column( + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - ClipRRect( - clipBehavior: Clip.hardEdge, - borderRadius: BorderRadius.circular(24), - child: Image.network( - "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=350x165&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng", - fit: BoxFit.contain, - ), + Row( + children: [ + "Appointment Status".needTranslation.toText16(isBold: true), + ], ), - Positioned( - bottom: 0, - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.785, - child: CustomButton( - text: "Get Directions".needTranslation, - onPressed: () { - MapsLauncher.launchCoordinates(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), - double.parse(widget.patientAppointmentHistoryResponseModel.longitude!), widget.patientAppointmentHistoryResponseModel.projectName); - }, - backgroundColor: AppColors.textColor.withOpacity(0.8), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), - textColor: AppColors.whiteColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12.h, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppAssets.directions_icon, - iconColor: AppColors.whiteColor, - iconSize: 13.h, - ).paddingAll(12.h), - ), + SizedBox(height: 4.h), + (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) + ? "Not Confirmed".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) + : "Confirmed".needTranslation.toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), + SizedBox(height: 16.h), + Stack( + children: [ + ClipRRect( + clipBehavior: Clip.hardEdge, + borderRadius: BorderRadius.circular(24), + child: Image.network( + "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=350x165&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng", + fit: BoxFit.contain, + ), + ), + Positioned( + bottom: 0, + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.785, + child: CustomButton( + text: "Get Directions".needTranslation, + onPressed: () { + MapsLauncher.launchCoordinates(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), + double.parse(widget.patientAppointmentHistoryResponseModel.longitude!), widget.patientAppointmentHistoryResponseModel.projectName); + }, + backgroundColor: AppColors.textColor.withOpacity(0.8), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + textColor: AppColors.whiteColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.directions_icon, + iconColor: AppColors.whiteColor, + iconSize: 13.h, + ).paddingAll(12.h), + ), + ), + ], ), ], ), - ], + ), ), - ), - ), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 35.h, height: 35.h), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisSize: MainAxisSize.max, children: [ - LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), - "Notify me before the appointment".needTranslation.toText11(color: AppColors.textColorLight, weight: FontWeight.w500), + Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 35.h, height: 35.h), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), + "Notify me before the appointment".needTranslation.toText11(color: AppColors.textColorLight, weight: FontWeight.w500), + ], + ), + const Spacer(), + Switch( + activeColor: AppColors.successColor, + activeTrackColor: AppColors.successColor.withValues(alpha: .15), + value: widget.patientAppointmentHistoryResponseModel.hasReminder!, + onChanged: (newValue) { + setState(() { + myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); + }); + }, + ), ], - ), - const Spacer(), - Switch( - activeColor: AppColors.successColor, - activeTrackColor: AppColors.successColor.withValues(alpha: .15), - value: widget.patientAppointmentHistoryResponseModel.hasReminder!, - onChanged: (newValue) { - setState(() { - myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); - }); - }, - ), - ], - ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(16.h, 16.h), + ), + SizedBox(height: 16.h), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Lab & Radiology".needTranslation.toText18(isBold: true), + SizedBox(height: 16.h), + GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, crossAxisSpacing: 13.h, mainAxisSpacing: 13.h, childAspectRatio: 7 / 6), + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + children: [ + MedicalFileCard( + label: LocaleKeys.labResults.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.lab_result_icon, + iconSize: 40, + isLargeText: true, + ), + MedicalFileCard( + label: LocaleKeys.radiology.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.radiology_icon, + iconSize: 40, + isLargeText: true, + ), + ], + ), + LocaleKeys.prescriptions.tr().toText18(isBold: true), + SizedBox(height: 16.h), + Consumer(builder: (context, prescriptionVM, child) { + return prescriptionVM.isPrescriptionsDetailsLoading + ? const MoviesShimmerWidget() + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Colors.white, + borderRadius: 20.h, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + ListView.separated( + itemCount: prescriptionVM.prescriptionDetailsList.length, + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.prescription_item_icon, + width: 40.h, + height: 40.h, + ), + SizedBox(width: 8.h), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + children: [ + SizedBox(width: 150.h, child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText12(isBold: true, maxLine: 1)), + SizedBox( + width: 150.h, + child: + "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}" + .needTranslation + .toText10(weight: FontWeight.w500, color: AppColors.greyTextColor, letterSpacing: -0.4), + ), + ], + ), + SizedBox(width: 68.h), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ], + ), + ], + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ).onPress(() { + prescriptionVM.setPrescriptionsDetailsLoading(); + Navigator.of(context).push( + FadePage( + page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()), + ), + ); + }), + SizedBox(height: 16.h), + const Divider(color: AppColors.dividerColor), + SizedBox(height: 16.h), + Row( + children: [ + // Expanded( + // child: CustomButton( + // text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), + // onPressed: () {}, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: 14, + // fontWeight: FontWeight.w500, + // borderRadius: 12.h, + // height: 40.h, + // icon: AppAssets.appointment_calendar_icon, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.h, + // ), + // ), + // SizedBox(width: 16.h), + Expanded( + child: CustomButton( + text: "All Prescriptions".needTranslation, + onPressed: () { + Navigator.of(context) + .push( + FadePage( + page: PrescriptionsListPage(), + ), + ) + .then((val) { + prescriptionsViewModel.setPrescriptionsDetailsLoading(); + prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); + }); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.requests, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ), + ), + ], + ), + ], + ), + ), + ); + }), + ], ), - SizedBox(height: 16.h), - ], - ), ], ).paddingSymmetrical(24.h, 24.h), ), @@ -242,28 +440,39 @@ class _AppointmentDetailsPageState extends State { ) ], ).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h), - CustomButton( - text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), - onPressed: () { - myAppointmentsViewModel.setIsPatientAppointmentShareLoading(true); - Navigator.of(context).push( - FadePage( - page: AppointmentPaymentPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - ), - ); - }, - backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), - textColor: AppColors.whiteColor, - fontSize: 16, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50.h, - icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), - iconColor: AppColors.whiteColor, - iconSize: 18.h, - ).paddingSymmetrical(16.h, 24.h), + AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? CustomButton( + text: "Re-book Appointment".needTranslation, + onPressed: () {}, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.add_icon, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(16.h, 24.h) + : CustomButton( + text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), + onPressed: () { + handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction); + }, + backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + textColor: widget.patientAppointmentHistoryResponseModel.nextAction == 15 ? AppColors.textColor : AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(16.h, 24.h), ], ), ), @@ -272,4 +481,64 @@ class _AppointmentDetailsPageState extends State { ), ); } + + Future handleAppointmentNextAction(nextAction) async { + switch (nextAction) { + case 0: + break; + case 10: + showCommonBottomSheet(context, + child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + await myAppointmentsViewModel.confirmAppointment( + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + onSuccess: (apiResponse) { + Navigator.of(context).pop(); + showCommonBottomSheet(context, + child: Utils.getSuccessWidget(loadingText: "Appointment Confirmed Successfully".needTranslation), + callBackFunc: (str) {}, + title: "", + height: ResponsiveExtension.screenHeight * 0.3, + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false, + isSuccessDialog: true); + }); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + case 15: + break; + case 20: + myAppointmentsViewModel.setIsPatientAppointmentShareLoading(true); + Navigator.of(context).push( + FadePage( + page: AppointmentPaymentPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + ), + ); + case 50: + // return LocaleKeys.confirmLiveCare.tr(); + case 90: + // return LocaleKeys.checkinOption.tr(); + default: + // return "No Action".needTranslation; + } + } + + PatientPrescriptionsResponseModel getPrescriptionRequestModel() { + return PatientPrescriptionsResponseModel( + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, + setupID: widget.patientAppointmentHistoryResponseModel.setupID, + episodeID: widget.patientAppointmentHistoryResponseModel.episodeID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + dischargeNo: 0, + isInOutPatient: widget.patientAppointmentHistoryResponseModel.isInOutPatient, + isHomeMedicineDeliverySupported: false, + doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL, + doctorName: "${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}", + appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate, + clinicDescription: widget.patientAppointmentHistoryResponseModel.clinicName, + decimalDoctorRate: widget.patientAppointmentHistoryResponseModel.decimalDoctorRate, + name: widget.patientAppointmentHistoryResponseModel.projectName, + ); + } } diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 861d5b9..7f2ea9d 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -19,12 +19,16 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; +import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/services/cache_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/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/in_app_browser/InAppBrowser.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; import 'package:smooth_corner/smooth_corner.dart'; @@ -220,7 +224,13 @@ class _AppointmentPaymentPageState extends State { "Insurance expired or inactive".needTranslation.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h), CustomButton( text: LocaleKeys.updateInsurance.tr(context: context), - onPressed: () {}, + onPressed: () { + Navigator.of(context).push( + FadePage( + page: InsuranceHomePage(), + ), + ); + }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.secondaryLightRedBorderColor, textColor: AppColors.whiteColor, @@ -366,14 +376,24 @@ class _AppointmentPaymentPageState extends State { clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, projectID: widget.patientAppointmentHistoryResponseModel.projectID, appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), - isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!); + isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + Navigator.of(context).pop(); + Navigator.pushAndRemoveUntil( + context, + FadePage( + page: LandingNavigation(), + ), + (r) => false); + Navigator.of(context).push( + FadePage(page: MyAppointmentsPage()), + ); + }); + }); } }); }); - Future.delayed(Duration(milliseconds: 500), () { - Navigator.of(context).pop(); - print(payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!); - }); } else {} }); // checkPaymentStatus(appo); diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index 0850641..4a3a46e 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -64,49 +64,154 @@ class _MyAppointmentsPageState extends State { ], onTabChange: (index) { print(index); + myAppointmentsViewModel.onTabChange(index); }, ).paddingSymmetrical(24.h, 0.h), Consumer(builder: (context, myAppointmentsVM, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 24.h), - // Expandable list - ListView.separated( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: myAppointmentsVM.isMyAppointmentsLoading ? 5 : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty ? myAppointmentsVM.patientAppointmentsHistoryList.length : 1, - itemBuilder: (context, index) { - return myAppointmentsVM.isMyAppointmentsLoading - ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) - : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty ? 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: 24.h, hasShadow: true), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - ), - ).paddingSymmetrical(24.h, 0.h), - ), - ), - ) : Utils.getNoDataWidget(context); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ), - ], - ); + return getSelectedTabData(myAppointmentsVM.selectedTabIndex, myAppointmentsVM); }), ], ), ), ); } + + Widget getSelectedTabData(int index, MyAppointmentsViewModel myAppointmentsVM) { + switch (index) { + case 0: + //All Appointments Tab Data + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + // Expandable list + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: myAppointmentsVM.isMyAppointmentsLoading + ? 5 + : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty + ? myAppointmentsVM.patientAppointmentsHistoryList.length + : 1, + itemBuilder: (context, index) { + return myAppointmentsVM.isMyAppointmentsLoading + ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) + : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty + ? 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: 24.h, hasShadow: true), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ) + : Utils.getNoDataWidget(context); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + ], + ); + case 1: + //Upcoming Appointments Tab Data + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + // Expandable list + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: myAppointmentsVM.isMyAppointmentsLoading + ? 5 + : myAppointmentsVM.patientUpcomingAppointmentsHistoryList.isNotEmpty + ? myAppointmentsVM.patientUpcomingAppointmentsHistoryList.length + : 1, + itemBuilder: (context, index) { + return myAppointmentsVM.isMyAppointmentsLoading + ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) + : myAppointmentsVM.patientUpcomingAppointmentsHistoryList.isNotEmpty + ? 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: 24.h, hasShadow: true), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientUpcomingAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ) + : Utils.getNoDataWidget(context); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + ], + ); + case 2: + //Completed Appointments Tab Data + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + // Expandable list + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: myAppointmentsVM.isMyAppointmentsLoading + ? 5 + : myAppointmentsVM.patientArrivedAppointmentsHistoryList.isNotEmpty + ? myAppointmentsVM.patientArrivedAppointmentsHistoryList.length + : 1, + itemBuilder: (context, index) { + return myAppointmentsVM.isMyAppointmentsLoading + ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) + : myAppointmentsVM.patientArrivedAppointmentsHistoryList.isNotEmpty + ? 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: 24.h, hasShadow: true), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientArrivedAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ) + : Utils.getNoDataWidget(context); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + ], + ); + default: + return Container(); + } + } } diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 51af492..e9babeb 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -1,3 +1,4 @@ +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'; @@ -10,6 +11,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_details_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -33,11 +35,16 @@ class _AppointmentCardState extends State { AppState appState = getIt.get(); return InkWell( onTap: () { - Navigator.of(context).push( + Navigator.of(context) + .push( FadePage( page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), ), - ); + ) + .then((val) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); }, child: Padding( padding: EdgeInsets.all(14.h), @@ -94,24 +101,26 @@ class _AppointmentCardState extends State { ), ), // TODO: Implement the logic to enable/disable the switch based on reminder status - Switch( - activeColor: AppColors.successColor, - activeTrackColor: AppColors.successColor.withValues(alpha: .15), - thumbIcon: WidgetStateProperty.resolveWith( - (Set states) { - if (states.contains(WidgetState.selected)) { - return const Icon(Icons.check); // Icon when switch is ON - } - return const Icon(Icons.close); // Icon when switch is OFF - }, - ), - value: widget.patientAppointmentHistoryResponseModel.hasReminder!, - onChanged: (newValue) { - setState(() { - widget.myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); - }); - }, - ), + AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? SizedBox() + : Switch( + activeColor: AppColors.successColor, + activeTrackColor: AppColors.successColor.withValues(alpha: .15), + thumbIcon: WidgetStateProperty.resolveWith( + (Set states) { + if (states.contains(WidgetState.selected)) { + return const Icon(Icons.check); // Icon when switch is ON + } + return const Icon(Icons.close); // Icon when switch is OFF + }, + ), + value: widget.patientAppointmentHistoryResponseModel.hasReminder!, + onChanged: (newValue) { + setState(() { + widget.myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); + }); + }, + ), ], ), SizedBox(height: 16.h), @@ -155,30 +164,32 @@ class _AppointmentCardState extends State { children: [ Expanded( flex: 6, - child: CustomButton( - text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), - onPressed: () { - Navigator.of(context) - .push(FadePage( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - )) - .then((val) { - widget.myAppointmentsViewModel.initAppointmentsViewModel(); - widget.myAppointmentsViewModel.getPatientAppointments(true, false); - }); - }, - backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.1), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), - textColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), - iconColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), - iconSize: 14.h, - ), + child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? getArrivedAppointmentButton() + : CustomButton( + text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), + onPressed: () { + Navigator.of(context) + .push(FadePage( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + )) + .then((val) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }, + backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), + iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), + iconSize: 14.h, + ), ), SizedBox(width: 8.h), Expanded( @@ -200,11 +211,16 @@ class _AppointmentCardState extends State { ), ), ).onPress(() { - Navigator.of(context).push( + Navigator.of(context) + .push( FadePage( page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), ), - ); + ) + .then((val) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); }), ), ], @@ -215,5 +231,39 @@ class _AppointmentCardState extends State { ); } + Widget getArrivedAppointmentButton() { + return DateTime.now().difference(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15 + ? CustomButton( + text: LocaleKeys.askDoctor.tr(context: context), + onPressed: () {}, + 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.ask_doctor_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ) + : CustomButton( + text: "Rebook with same doctor".needTranslation, + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.rebook_appointment_icon, + iconColor: AppColors.blackColor, + iconSize: 16.h, + ); + } + void performAppointmentNextAction(nextAction) {} } diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 801fdca..e6d7338 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -59,10 +59,7 @@ class AppointmentDoctorCard extends StatelessWidget { icon: AppAssets.doctor_calendar_icon, labelText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}, ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}"), - AppCustomChipWidget( - icon: AppAssets.rating_icon, - iconColor: AppColors.ratingColorYellow, - labelText: "Rating: ${patientAppointmentHistoryResponseModel.decimalDoctorRate}"), + AppCustomChipWidget(icon: AppAssets.rating_icon, iconColor: AppColors.ratingColorYellow, labelText: "Rating: ${patientAppointmentHistoryResponseModel.decimalDoctorRate}"), ], ), ], @@ -82,20 +79,37 @@ class AppointmentDoctorCard extends StatelessWidget { Widget getAppointmentActionButtons(bool isAppointmentArrived) { if (isAppointmentArrived) { - return CustomButton( - text: LocaleKeys.askDoctor.tr(), - onPressed: () {}, - 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.ask_doctor_icon, - iconColor: AppColors.primaryRedColor, - ); + return DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15 + ? CustomButton( + text: LocaleKeys.askDoctor.tr(), + onPressed: () {}, + 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, + iconSize: 16.h, + icon: AppAssets.ask_doctor_icon, + iconColor: AppColors.primaryRedColor, + ) + : CustomButton( + text: "Rebook with same doctor".needTranslation, + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.rebook_appointment_icon, + iconColor: AppColors.blackColor, + iconSize: 16.h, + ); } else { return Row( children: [ diff --git a/lib/presentation/insurance/widgets/insurance_update_details_card.dart b/lib/presentation/insurance/widgets/insurance_update_details_card.dart index 51afdc4..b6a38b2 100644 --- a/lib/presentation/insurance/widgets/insurance_update_details_card.dart +++ b/lib/presentation/insurance/widgets/insurance_update_details_card.dart @@ -10,6 +10,7 @@ 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/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/shimmer/movies_shimmer_widget.dart'; import 'package:provider/provider.dart'; @@ -68,42 +69,14 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { Wrap( direction: Axis.horizontal, spacing: 4.h, - runSpacing: 4.h, + runSpacing: -8.h, children: [ - Row( - children: [ - CustomButton( - icon: AppAssets.doctor_calendar_icon, - iconColor: AppColors.blackColor, - iconSize: 13.h, - text: "${LocaleKeys.expiryOn.tr(context: context)} ${insuranceViewModel.patientInsuranceUpdateResponseModel!.effectiveTo}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: "${LocaleKeys.expiryOn.tr(context: context)} ${insuranceViewModel.patientInsuranceUpdateResponseModel!.effectiveTo}", ), - Row( - children: [ - CustomButton( - text: "Member ID: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.memberID!}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], + AppCustomChipWidget( + labelText: "Member ID: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.memberID!}", ), ], ), diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index 899016e..720c452 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -13,6 +13,7 @@ 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/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:provider/provider.dart'; @@ -73,44 +74,13 @@ class PatientInsuranceCard extends StatelessWidget { SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.h, + spacing: 4.h, + runSpacing: -8.h, children: [ - Row( - children: [ - CustomButton( - icon: AppAssets.doctor_calendar_icon, - iconColor: AppColors.blackColor, - iconSize: 13.h, - text: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - children: [ - CustomButton( - text: "Patient Card ID: ${insuranceCardDetailsModel.patientCardID}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}"), + AppCustomChipWidget(labelText: "Patient Card ID: ${insuranceCardDetailsModel.patientCardID}"), ], ), SizedBox(height: 10.h), diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index d6cbd14..47951c4 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -167,22 +167,22 @@ class _MedicalFilePageState extends State { padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 30.h, ), - SizedBox(width: 4.h), - CustomButton( - icon: AppAssets.insurance_active_icon, - iconColor: AppColors.successColor, - iconSize: 13.h, - text: "Insurance Active", - onPressed: () {}, - backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), - borderColor: AppColors.bgGreenColor.withOpacity(0.0), - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.normal, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), + // SizedBox(width: 4.h), + // CustomButton( + // icon: AppAssets.insurance_active_icon, + // iconColor: AppColors.successColor, + // iconSize: 13.h, + // text: "Insurance Active", + // onPressed: () {}, + // backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), + // borderColor: AppColors.bgGreenColor.withOpacity(0.0), + // textColor: AppColors.blackColor, + // fontSize: 10, + // fontWeight: FontWeight.normal, + // borderRadius: 12, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 30.h, + // ), ], ), SizedBox(height: 8.h), @@ -255,7 +255,7 @@ class _MedicalFilePageState extends State { ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.0) : PatientInsuranceCard( insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, - isInsuranceExpired: DateTime.now().isBefore(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))); + isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))); }), SizedBox(height: 10.h), GridView( @@ -264,10 +264,10 @@ class _MedicalFilePageState extends State { padding: EdgeInsets.only(top: 12), shrinkWrap: true, children: [ - MedicalFileCard(label: "Update Insurance", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), - MedicalFileCard(label: "Insurance Approvals", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), - MedicalFileCard(label: "My Invoices List", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), - MedicalFileCard(label: "Ancillary Orders List", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + MedicalFileCard(label: "Update Insurance".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + MedicalFileCard(label: "Insurance Approvals".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + MedicalFileCard(label: "My Invoices List".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + MedicalFileCard(label: "Ancillary Orders List".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), ], ).paddingSymmetrical(24.h, 0.0), SizedBox(height: 16.h), diff --git a/lib/presentation/medical_file/widgets/medical_file_card.dart b/lib/presentation/medical_file/widgets/medical_file_card.dart index 92bb4ee..82f38ec 100644 --- a/lib/presentation/medical_file/widgets/medical_file_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_card.dart @@ -6,18 +6,19 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; class MedicalFileCard extends StatelessWidget { final String label; - - // final Color svgColor; final Color textColor; final Color backgroundColor; final String svgIcon; + final num iconSize; + bool isLargeText; MedicalFileCard({ required this.label, - // required this.svgColor, required this.textColor, required this.backgroundColor, this.svgIcon = "", + this.iconSize = 30, + this.isLargeText = false }); @override @@ -33,9 +34,9 @@ class MedicalFileCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - Utils.buildSvgWithAssets(icon: svgIcon, width: 30.h, height: 30.h, fit: BoxFit.contain), + Utils.buildSvgWithAssets(icon: svgIcon, width: iconSize.h, height: iconSize.h, fit: BoxFit.contain), SizedBox(height: 12.h), - label.toText11(color: textColor, isBold: true), + isLargeText ? label.toText14(color: textColor, isBold: true) : label.toText11(color: textColor, isBold: true), ], ), ), diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 1f8e0a2..3bd9273 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo 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:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:provider/provider.dart'; @@ -34,6 +35,7 @@ class _PrescriptionDetailPageState extends State { @override void initState() { scheduleMicrotask(() { + prescriptionsViewModel.setPrescriptionsDetailsLoading(); prescriptionsViewModel.getPrescriptionDetails(widget.prescriptionsResponseModel); }); super.initState(); @@ -85,82 +87,23 @@ class _PrescriptionDetailPageState extends State { SizedBox(height: 16.h), Wrap( direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.h, + spacing: 4.h, + runSpacing: -8.h, children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - icon: AppAssets.doctor_calendar_icon, - iconColor: AppColors.textColor, - iconSize: 13.h, - text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), 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: 30.h, - ), - ], + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: widget.prescriptionsResponseModel.clinicDescription!, - 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, - ), - ], + AppCustomChipWidget( + labelText: widget.prescriptionsResponseModel.clinicDescription!, ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - icon: AppAssets.rating_icon, - iconColor: AppColors.ratingColorYellow, - iconSize: 13.h, - text: "Rating: ${widget.prescriptionsResponseModel.decimalDoctorRate}", - 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, - ), - ], + AppCustomChipWidget( + icon: AppAssets.rating_icon, + iconColor: AppColors.ratingColorYellow, + labelText: "Rating: ${widget.prescriptionsResponseModel.decimalDoctorRate}".needTranslation, ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: widget.prescriptionsResponseModel.name!, - 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, - ), - ], + AppCustomChipWidget( + labelText: widget.prescriptionsResponseModel.name!, ), ], ), From b10523dac35ef32a2c8c75815b476d103c30000a Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 9 Sep 2025 17:41:05 +0300 Subject: [PATCH 5/7] Online CheckIn implementation contd. --- assets/animations/lottie/ErrorAnimation.json | 1 + lib/core/api/api_client.dart | 292 +++++++++--------- lib/core/app_assets.dart | 1 + lib/core/utils/utils.dart | 13 + .../my_appointments/my_appointments_repo.dart | 45 +++ .../my_appointments_view_model.dart | 24 ++ .../appointment_details_page.dart | 1 + .../appointment_checkin_bottom_sheet.dart | 108 ++++++- .../widgets/appointment_queueing_screen.dart | 0 lib/widgets/nfc/nfc_reader_sheet.dart | 212 +++++++++++++ pubspec.yaml | 2 + 11 files changed, 546 insertions(+), 153 deletions(-) create mode 100644 assets/animations/lottie/ErrorAnimation.json create mode 100644 lib/presentation/appointments/widgets/appointment_queueing_screen.dart create mode 100644 lib/widgets/nfc/nfc_reader_sheet.dart diff --git a/assets/animations/lottie/ErrorAnimation.json b/assets/animations/lottie/ErrorAnimation.json new file mode 100644 index 0000000..a9fc775 --- /dev/null +++ b/assets/animations/lottie/ErrorAnimation.json @@ -0,0 +1 @@ +{"nm":"Bouncy Fail","ddd":0,"h":512,"w":512,"meta":{"g":"@lottiefiles/toolkit-js 0.33.2"},"layers":[{"ty":4,"nm":"X line 2","sr":1,"st":0,"op":60,"ip":26,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[41,-3,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,256,0],"ix":2},"r":{"a":0,"k":90,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[112,-74],[-30,68]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"c":{"a":0,"k":[1,1,1],"ix":3}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8863,0.1176,0.1882],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"Trim Paths 1","ix":2,"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":26},{"s":[0],"t":40}],"ix":1},"m":1}],"ind":1},{"ty":4,"nm":"X line 1","sr":1,"st":0,"op":60,"ip":26,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[41,-3,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,256,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[112,-74],[-30,68]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"c":{"a":0,"k":[1,1,1],"ix":3}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8863,0.1176,0.1882],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"Trim Paths 1","ix":2,"e":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[0],"t":26},{"s":[100],"t":40}],"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":0,"k":0,"ix":1},"m":1}],"ind":2},{"ty":4,"nm":"Circle 2","sr":1,"st":0,"op":360,"ip":10,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[140.061,140.061,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[0,0,100],"t":10},{"s":[140,140,100],"t":20}],"ix":6,"x":"var $bm_rt;\nvar scaleInertialBounce, scaleBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nscaleInertialBounce = effect('Bounce & Drop - ukramedia.com')(16);\nscaleBounceBack = effect('Bounce & Drop - ukramedia.com')(17);\ntry {\n if (scaleInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(19);\n freq = effect('Bounce & Drop - ukramedia.com')(20);\n decay = effect('Bounce & Drop - ukramedia.com')(21);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (scaleBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(24);\n g = effect('Bounce & Drop - ukramedia.com')(25);\n nMax = effect('Bounce & Drop - ukramedia.com')(26);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,256,0],"ix":2,"x":"var $bm_rt;\nvar positionInertialBounce, positionBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\npositionInertialBounce = effect('Bounce & Drop - ukramedia.com')(2);\npositionBounceBack = effect('Bounce & Drop - ukramedia.com')(3);\ntry {\n if (positionInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(5);\n freq = effect('Bounce & Drop - ukramedia.com')(6);\n decay = effect('Bounce & Drop - ukramedia.com')(7);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (positionBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(10);\n g = effect('Bounce & Drop - ukramedia.com')(11);\n nMax = effect('Bounce & Drop - ukramedia.com')(12);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"r":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\nvar rotationInertialBounce, rotationBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nrotationInertialBounce = effect('Bounce & Drop - ukramedia.com')(30);\nrotationBounceBack = effect('Bounce & Drop - ukramedia.com')(31);\ntry {\n if (rotationInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(33);\n freq = effect('Bounce & Drop - ukramedia.com')(34);\n decay = effect('Bounce & Drop - ukramedia.com')(35);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (rotationBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(38);\n g = effect('Bounce & Drop - ukramedia.com')(39);\n nMax = effect('Bounce & Drop - ukramedia.com')(40);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11,"x":"var $bm_rt;\nvar opacityInertialBounce, opacityBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nopacityInertialBounce = effect('Bounce & Drop - ukramedia.com')(44);\nopacityBounceBack = effect('Bounce & Drop - ukramedia.com')(45);\ntry {\n if (opacityInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(47);\n freq = effect('Bounce & Drop - ukramedia.com')(48);\n decay = effect('Bounce & Drop - ukramedia.com')(49);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (opacityBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(52);\n g = effect('Bounce & Drop - ukramedia.com')(53);\n nMax = effect('Bounce & Drop - ukramedia.com')(54);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"}},"ef":[{"ty":5,"mn":"Pseudo/animationControl","nm":"Bounce & Drop - ukramedia.com","ix":1,"en":1,"ef":[{"ty":6,"mn":"Pseudo/animationControl-0001","nm":"Position","ix":1,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0002","nm":"Enable Inertial Bounce","ix":2,"v":{"a":0,"k":0,"ix":2}},{"ty":7,"mn":"Pseudo/animationControl-0003","nm":"Enable Bounce Back","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":6,"mn":"Pseudo/animationControl-0004","nm":"Inertial Bounce Options","ix":4,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0005","nm":"Amplitude","ix":5,"v":{"a":0,"k":0.05,"ix":5}},{"ty":0,"mn":"Pseudo/animationControl-0006","nm":"Frequency","ix":6,"v":{"a":0,"k":4,"ix":6}},{"ty":0,"mn":"Pseudo/animationControl-0007","nm":"Decay","ix":7,"v":{"a":0,"k":8,"ix":7}},{"ty":6,"mn":"Pseudo/animationControl-0008","nm":"","ix":8,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0009","nm":"Bounce Back Options","ix":9,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0010","nm":"Elasticity","ix":10,"v":{"a":0,"k":0.7,"ix":10}},{"ty":0,"mn":"Pseudo/animationControl-0011","nm":"Gravity","ix":11,"v":{"a":0,"k":5000,"ix":11}},{"ty":0,"mn":"Pseudo/animationControl-0012","nm":"nMax","ix":12,"v":{"a":0,"k":9,"ix":12}},{"ty":6,"mn":"Pseudo/animationControl-0013","nm":"","ix":13,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0014","nm":"","ix":14,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0015","nm":"Scale","ix":15,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0016","nm":"Enable Inertial Bounce","ix":16,"v":{"a":0,"k":1,"ix":16}},{"ty":7,"mn":"Pseudo/animationControl-0017","nm":"Enable Bounce Back","ix":17,"v":{"a":0,"k":0,"ix":17}},{"ty":6,"mn":"Pseudo/animationControl-0018","nm":"Inertial Bounce Options","ix":18,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0019","nm":"Amplitude","ix":19,"v":{"a":0,"k":0.8,"ix":19}},{"ty":0,"mn":"Pseudo/animationControl-0020","nm":"Frequency","ix":20,"v":{"a":0,"k":4,"ix":20}},{"ty":0,"mn":"Pseudo/animationControl-0021","nm":"Decay","ix":21,"v":{"a":0,"k":8,"ix":21}},{"ty":6,"mn":"Pseudo/animationControl-0022","nm":"","ix":22,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0023","nm":"Bounce Back Options","ix":23,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0024","nm":"Elasticity","ix":24,"v":{"a":0,"k":0.7,"ix":24}},{"ty":0,"mn":"Pseudo/animationControl-0025","nm":"Gravity","ix":25,"v":{"a":0,"k":5000,"ix":25}},{"ty":0,"mn":"Pseudo/animationControl-0026","nm":"nMax","ix":26,"v":{"a":0,"k":9,"ix":26}},{"ty":6,"mn":"Pseudo/animationControl-0027","nm":"","ix":27,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0028","nm":"","ix":28,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0029","nm":"Rotation","ix":29,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0030","nm":"Enable Inertial Bounce","ix":30,"v":{"a":0,"k":0,"ix":30}},{"ty":7,"mn":"Pseudo/animationControl-0031","nm":"Enable Bounce Back","ix":31,"v":{"a":0,"k":0,"ix":31}},{"ty":6,"mn":"Pseudo/animationControl-0032","nm":"Inertial Bounce Options","ix":32,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0033","nm":"Amplitude","ix":33,"v":{"a":0,"k":0.05,"ix":33}},{"ty":0,"mn":"Pseudo/animationControl-0034","nm":"Frequency","ix":34,"v":{"a":0,"k":4,"ix":34}},{"ty":0,"mn":"Pseudo/animationControl-0035","nm":"Decay","ix":35,"v":{"a":0,"k":8,"ix":35}},{"ty":6,"mn":"Pseudo/animationControl-0036","nm":"","ix":36,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0037","nm":"Bounce Back Options","ix":37,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0038","nm":"Elasticity","ix":38,"v":{"a":0,"k":0.7,"ix":38}},{"ty":0,"mn":"Pseudo/animationControl-0039","nm":"Gravity","ix":39,"v":{"a":0,"k":5000,"ix":39}},{"ty":0,"mn":"Pseudo/animationControl-0040","nm":"nMax","ix":40,"v":{"a":0,"k":9,"ix":40}},{"ty":6,"mn":"Pseudo/animationControl-0041","nm":"","ix":41,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0042","nm":"","ix":42,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0043","nm":"Opacity","ix":43,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0044","nm":"Enable Inertial Bounce","ix":44,"v":{"a":0,"k":0,"ix":44}},{"ty":7,"mn":"Pseudo/animationControl-0045","nm":"Enable Bounce Back","ix":45,"v":{"a":0,"k":0,"ix":45}},{"ty":6,"mn":"Pseudo/animationControl-0046","nm":"Inertial Bounce Options","ix":46,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0047","nm":"Amplitude","ix":47,"v":{"a":0,"k":0.05,"ix":47}},{"ty":0,"mn":"Pseudo/animationControl-0048","nm":"Frequency","ix":48,"v":{"a":0,"k":4,"ix":48}},{"ty":0,"mn":"Pseudo/animationControl-0049","nm":"Decay","ix":49,"v":{"a":0,"k":8,"ix":49}},{"ty":6,"mn":"Pseudo/animationControl-0050","nm":"","ix":50,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0051","nm":"Bounce Back Options","ix":51,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0052","nm":"Elasticity","ix":52,"v":{"a":0,"k":0.7,"ix":52}},{"ty":0,"mn":"Pseudo/animationControl-0053","nm":"Gravity","ix":53,"v":{"a":0,"k":5000,"ix":53}},{"ty":0,"mn":"Pseudo/animationControl-0054","nm":"nMax","ix":54,"v":{"a":0,"k":9,"ix":54}},{"ty":6,"mn":"Pseudo/animationControl-0055","nm":"","ix":55,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0056","nm":"","ix":56,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0057","nm":"Global Inertial Bounce Options","ix":57,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0058","nm":"Enable Global Inertial Bounce","ix":58,"v":{"a":0,"k":0,"ix":58}},{"ty":0,"mn":"Pseudo/animationControl-0059","nm":"Amplitude","ix":59,"v":{"a":0,"k":0.05,"ix":59}},{"ty":0,"mn":"Pseudo/animationControl-0060","nm":"Frequency","ix":60,"v":{"a":0,"k":4,"ix":60}},{"ty":0,"mn":"Pseudo/animationControl-0061","nm":"Decay","ix":61,"v":{"a":0,"k":8,"ix":61}},{"ty":6,"mn":"Pseudo/animationControl-0062","nm":"","ix":62,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0063","nm":"Global Bounce Back Options","ix":63,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0064","nm":"Enable Global Bounce Back","ix":64,"v":{"a":0,"k":0,"ix":64}},{"ty":0,"mn":"Pseudo/animationControl-0065","nm":"Elasticity","ix":65,"v":{"a":0,"k":0.7,"ix":65}},{"ty":0,"mn":"Pseudo/animationControl-0066","nm":"Gravity","ix":66,"v":{"a":0,"k":5000,"ix":66}},{"ty":0,"mn":"Pseudo/animationControl-0067","nm":"nMax","ix":67,"v":{"a":0,"k":9,"ix":67}},{"ty":6,"mn":"Pseudo/animationControl-0068","nm":"","ix":68,"v":0}]}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,-77.215],[77.216,0],[0,77.215],[-77.215,0]],"o":[[0,77.215],[-77.215,0],[0,-77.215],[77.216,0]],"v":[[139.811,0],[0,139.811],[-139.811,0],[0,-139.811]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8863,0.1176,0.1882],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[140.061,140.061],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3},{"ty":4,"nm":"Circle 1","sr":1,"st":0,"op":360,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[140.061,140.061,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[0,0,100],"t":0},{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[132,132,100],"t":10},{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[130,130,100],"t":35},{"s":[175,175,100],"t":55}],"ix":6,"x":"var $bm_rt;\nvar scaleInertialBounce, scaleBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nscaleInertialBounce = effect('Bounce & Drop - ukramedia.com')(16);\nscaleBounceBack = effect('Bounce & Drop - ukramedia.com')(17);\ntry {\n if (scaleInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(19);\n freq = effect('Bounce & Drop - ukramedia.com')(20);\n decay = effect('Bounce & Drop - ukramedia.com')(21);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (scaleBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(24);\n g = effect('Bounce & Drop - ukramedia.com')(25);\n nMax = effect('Bounce & Drop - ukramedia.com')(26);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,256,0],"ix":2,"x":"var $bm_rt;\nvar positionInertialBounce, positionBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\npositionInertialBounce = effect('Bounce & Drop - ukramedia.com')(2);\npositionBounceBack = effect('Bounce & Drop - ukramedia.com')(3);\ntry {\n if (positionInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(5);\n freq = effect('Bounce & Drop - ukramedia.com')(6);\n decay = effect('Bounce & Drop - ukramedia.com')(7);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (positionBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(10);\n g = effect('Bounce & Drop - ukramedia.com')(11);\n nMax = effect('Bounce & Drop - ukramedia.com')(12);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"r":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\nvar rotationInertialBounce, rotationBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nrotationInertialBounce = effect('Bounce & Drop - ukramedia.com')(30);\nrotationBounceBack = effect('Bounce & Drop - ukramedia.com')(31);\ntry {\n if (rotationInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(33);\n freq = effect('Bounce & Drop - ukramedia.com')(34);\n decay = effect('Bounce & Drop - ukramedia.com')(35);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (rotationBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(38);\n g = effect('Bounce & Drop - ukramedia.com')(39);\n nMax = effect('Bounce & Drop - ukramedia.com')(40);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[50],"t":41.25},{"s":[0],"t":55}],"ix":11,"x":"var $bm_rt;\nvar opacityInertialBounce, opacityBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nopacityInertialBounce = effect('Bounce & Drop - ukramedia.com')(44);\nopacityBounceBack = effect('Bounce & Drop - ukramedia.com')(45);\ntry {\n if (opacityInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(47);\n freq = effect('Bounce & Drop - ukramedia.com')(48);\n decay = effect('Bounce & Drop - ukramedia.com')(49);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (opacityBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(52);\n g = effect('Bounce & Drop - ukramedia.com')(53);\n nMax = effect('Bounce & Drop - ukramedia.com')(54);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"}},"ef":[{"ty":5,"mn":"Pseudo/animationControl","nm":"Bounce & Drop - ukramedia.com","ix":1,"en":1,"ef":[{"ty":6,"mn":"Pseudo/animationControl-0001","nm":"Position","ix":1,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0002","nm":"Enable Inertial Bounce","ix":2,"v":{"a":0,"k":0,"ix":2}},{"ty":7,"mn":"Pseudo/animationControl-0003","nm":"Enable Bounce Back","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":6,"mn":"Pseudo/animationControl-0004","nm":"Inertial Bounce Options","ix":4,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0005","nm":"Amplitude","ix":5,"v":{"a":0,"k":0.05,"ix":5}},{"ty":0,"mn":"Pseudo/animationControl-0006","nm":"Frequency","ix":6,"v":{"a":0,"k":4,"ix":6}},{"ty":0,"mn":"Pseudo/animationControl-0007","nm":"Decay","ix":7,"v":{"a":0,"k":8,"ix":7}},{"ty":6,"mn":"Pseudo/animationControl-0008","nm":"","ix":8,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0009","nm":"Bounce Back Options","ix":9,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0010","nm":"Elasticity","ix":10,"v":{"a":0,"k":0.7,"ix":10}},{"ty":0,"mn":"Pseudo/animationControl-0011","nm":"Gravity","ix":11,"v":{"a":0,"k":5000,"ix":11}},{"ty":0,"mn":"Pseudo/animationControl-0012","nm":"nMax","ix":12,"v":{"a":0,"k":9,"ix":12}},{"ty":6,"mn":"Pseudo/animationControl-0013","nm":"","ix":13,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0014","nm":"","ix":14,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0015","nm":"Scale","ix":15,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0016","nm":"Enable Inertial Bounce","ix":16,"v":{"a":0,"k":1,"ix":16}},{"ty":7,"mn":"Pseudo/animationControl-0017","nm":"Enable Bounce Back","ix":17,"v":{"a":0,"k":0,"ix":17}},{"ty":6,"mn":"Pseudo/animationControl-0018","nm":"Inertial Bounce Options","ix":18,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0019","nm":"Amplitude","ix":19,"v":{"a":0,"k":0.8,"ix":19}},{"ty":0,"mn":"Pseudo/animationControl-0020","nm":"Frequency","ix":20,"v":{"a":0,"k":4,"ix":20}},{"ty":0,"mn":"Pseudo/animationControl-0021","nm":"Decay","ix":21,"v":{"a":0,"k":8,"ix":21}},{"ty":6,"mn":"Pseudo/animationControl-0022","nm":"","ix":22,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0023","nm":"Bounce Back Options","ix":23,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0024","nm":"Elasticity","ix":24,"v":{"a":0,"k":0.7,"ix":24}},{"ty":0,"mn":"Pseudo/animationControl-0025","nm":"Gravity","ix":25,"v":{"a":0,"k":5000,"ix":25}},{"ty":0,"mn":"Pseudo/animationControl-0026","nm":"nMax","ix":26,"v":{"a":0,"k":9,"ix":26}},{"ty":6,"mn":"Pseudo/animationControl-0027","nm":"","ix":27,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0028","nm":"","ix":28,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0029","nm":"Rotation","ix":29,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0030","nm":"Enable Inertial Bounce","ix":30,"v":{"a":0,"k":0,"ix":30}},{"ty":7,"mn":"Pseudo/animationControl-0031","nm":"Enable Bounce Back","ix":31,"v":{"a":0,"k":0,"ix":31}},{"ty":6,"mn":"Pseudo/animationControl-0032","nm":"Inertial Bounce Options","ix":32,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0033","nm":"Amplitude","ix":33,"v":{"a":0,"k":0.05,"ix":33}},{"ty":0,"mn":"Pseudo/animationControl-0034","nm":"Frequency","ix":34,"v":{"a":0,"k":4,"ix":34}},{"ty":0,"mn":"Pseudo/animationControl-0035","nm":"Decay","ix":35,"v":{"a":0,"k":8,"ix":35}},{"ty":6,"mn":"Pseudo/animationControl-0036","nm":"","ix":36,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0037","nm":"Bounce Back Options","ix":37,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0038","nm":"Elasticity","ix":38,"v":{"a":0,"k":0.7,"ix":38}},{"ty":0,"mn":"Pseudo/animationControl-0039","nm":"Gravity","ix":39,"v":{"a":0,"k":5000,"ix":39}},{"ty":0,"mn":"Pseudo/animationControl-0040","nm":"nMax","ix":40,"v":{"a":0,"k":9,"ix":40}},{"ty":6,"mn":"Pseudo/animationControl-0041","nm":"","ix":41,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0042","nm":"","ix":42,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0043","nm":"Opacity","ix":43,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0044","nm":"Enable Inertial Bounce","ix":44,"v":{"a":0,"k":0,"ix":44}},{"ty":7,"mn":"Pseudo/animationControl-0045","nm":"Enable Bounce Back","ix":45,"v":{"a":0,"k":0,"ix":45}},{"ty":6,"mn":"Pseudo/animationControl-0046","nm":"Inertial Bounce Options","ix":46,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0047","nm":"Amplitude","ix":47,"v":{"a":0,"k":0.05,"ix":47}},{"ty":0,"mn":"Pseudo/animationControl-0048","nm":"Frequency","ix":48,"v":{"a":0,"k":4,"ix":48}},{"ty":0,"mn":"Pseudo/animationControl-0049","nm":"Decay","ix":49,"v":{"a":0,"k":8,"ix":49}},{"ty":6,"mn":"Pseudo/animationControl-0050","nm":"","ix":50,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0051","nm":"Bounce Back Options","ix":51,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0052","nm":"Elasticity","ix":52,"v":{"a":0,"k":0.7,"ix":52}},{"ty":0,"mn":"Pseudo/animationControl-0053","nm":"Gravity","ix":53,"v":{"a":0,"k":5000,"ix":53}},{"ty":0,"mn":"Pseudo/animationControl-0054","nm":"nMax","ix":54,"v":{"a":0,"k":9,"ix":54}},{"ty":6,"mn":"Pseudo/animationControl-0055","nm":"","ix":55,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0056","nm":"","ix":56,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0057","nm":"Global Inertial Bounce Options","ix":57,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0058","nm":"Enable Global Inertial Bounce","ix":58,"v":{"a":0,"k":0,"ix":58}},{"ty":0,"mn":"Pseudo/animationControl-0059","nm":"Amplitude","ix":59,"v":{"a":0,"k":0.05,"ix":59}},{"ty":0,"mn":"Pseudo/animationControl-0060","nm":"Frequency","ix":60,"v":{"a":0,"k":4,"ix":60}},{"ty":0,"mn":"Pseudo/animationControl-0061","nm":"Decay","ix":61,"v":{"a":0,"k":8,"ix":61}},{"ty":6,"mn":"Pseudo/animationControl-0062","nm":"","ix":62,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0063","nm":"Global Bounce Back Options","ix":63,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0064","nm":"Enable Global Bounce Back","ix":64,"v":{"a":0,"k":0,"ix":64}},{"ty":0,"mn":"Pseudo/animationControl-0065","nm":"Elasticity","ix":65,"v":{"a":0,"k":0.7,"ix":65}},{"ty":0,"mn":"Pseudo/animationControl-0066","nm":"Gravity","ix":66,"v":{"a":0,"k":5000,"ix":66}},{"ty":0,"mn":"Pseudo/animationControl-0067","nm":"nMax","ix":67,"v":{"a":0,"k":9,"ix":67}},{"ty":6,"mn":"Pseudo/animationControl-0068","nm":"","ix":68,"v":0}]}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,-77.215],[77.216,0],[0,77.215],[-77.215,0]],"o":[[0,77.215],[-77.215,0],[0,-77.215],[77.216,0]],"v":[[139.811,0],[0,139.811],[-139.811,0],[0,-139.811]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8863,0.1176,0.1882],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[140.061,140.061],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":4}],"v":"5.7.11","fr":30,"op":60,"ip":0,"assets":[]} \ No newline at end of file diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 7555400..2fd8082 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -17,6 +17,7 @@ import '../exceptions/api_failure.dart'; abstract class ApiClient { static final NavigationService _navigationService = getIt.get(); + Future post( String endPoint, { required Map body, @@ -93,7 +94,7 @@ class ApiClientImp implements ApiClient { 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 isAllowAny = true, + bool isAllowAny = false, bool isExternal = false, bool isRCService = false, bool bypassConnectionCheck = false, @@ -109,187 +110,186 @@ class ApiClientImp implements ApiClient { } } // try { - var user = _appState.getAuthenticatedUser(); - Map headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}; - if (!isExternal) { - String? token = _appState.appAuthToken; + var user = _appState.getAuthenticatedUser(); + Map headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}; + if (!isExternal) { + String? token = _appState.appAuthToken; - if (body.containsKey('SetupID')) { - body['SetupID'] = body.containsKey('SetupID') ? body['SetupID'] ?? body[''] : SETUP_ID; - } else {} + if (body.containsKey('SetupID')) { + body['SetupID'] = body.containsKey('SetupID') ? body['SetupID'] ?? body[''] : SETUP_ID; + } else {} - if (body.containsKey('isDentalAllowedBackend')) { - body['isDentalAllowedBackend'] = body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND; - } + if (body.containsKey('isDentalAllowedBackend')) { + body['isDentalAllowedBackend'] = body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND; + } - if (!body.containsKey('IsPublicRequest')) { - // if (!body.containsKey('PatientType')) { - if (user != null && user.patientType != null) { - body['PatientType'] = user.patientType; - } else { - body['PatientType'] = PATIENT_TYPE.toString(); - } + if (!body.containsKey('IsPublicRequest')) { + // if (!body.containsKey('PatientType')) { + if (user != null && user.patientType != null) { + body['PatientType'] = user.patientType; + } else { + body['PatientType'] = PATIENT_TYPE.toString(); + } - if (user != null && user.patientType != null) { - body['PatientTypeID'] = user.patientType; - } else { - body['PatientType'] = PATIENT_TYPE_ID.toString(); - } + if (user != null && user.patientType != null) { + body['PatientTypeID'] = user.patientType; + } else { + body['PatientType'] = PATIENT_TYPE_ID.toString(); + } - // TODO : These should be from the appState - if (user != null) { - body['TokenID'] = body['TokenID'] ?? token; - body['PatientID'] = body['PatientID'] ?? user.patientId; + // TODO : These should be from the appState + if (user != null) { + body['TokenID'] = body['TokenID'] ?? token; + body['PatientID'] = body['PatientID'] ?? user.patientId; - body['PatientOutSA'] = body.containsKey('PatientOutSA') ? body['PatientOutSA'] ?? user.outSa : user.outSa; - body['SessionID'] = body['TokenID'] == null ? ApiConsts.sessionID : getSessionId(body['TokenID'] ?? ""); //getSe - } - // else { - // body['SessionID'] = body['TokenID'] == null ? ApiConsts.sessionID : getSessionId(body['TokenID'] ?? ""); //getSe - // - // } + body['PatientOutSA'] = body.containsKey('PatientOutSA') ? body['PatientOutSA'] ?? user.outSa : user.outSa; + body['SessionID'] = body['TokenID'] == null ? ApiConsts.sessionID : getSessionId(body['TokenID'] ?? ""); //getSe } + // else { + // body['SessionID'] = body['TokenID'] == null ? ApiConsts.sessionID : getSessionId(body['TokenID'] ?? ""); //getSe + // + // } } + } - // request.versionID = VERSION_ID; - // request.channel = CHANNEL; - // request.iPAdress = IP_ADDRESS; - // request.generalid = GENERAL_ID; - // request.languageID = (languageID == 'ar' ? 1 : 2); - // request.patientOutSA = (request.zipCode == '966' || request.zipCode == '+966') ? 0 : 1; - - // body['VersionID'] = ApiConsts.appVersionID.toString(); - if (!isExternal) { - body['VersionID'] = "50.0"; - 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; - } - - // body['TokenID'] = "@dm!n"; - // body['PatientID'] = "4767477"; + // request.versionID = VERSION_ID; + // request.channel = CHANNEL; + // request.iPAdress = IP_ADDRESS; + // request.generalid = GENERAL_ID; + // request.languageID = (languageID == 'ar' ? 1 : 2); + // request.patientOutSA = (request.zipCode == '966' || request.zipCode == '+966') ? 0 : 1; + + // body['VersionID'] = ApiConsts.appVersionID.toString(); + if (!isExternal) { + body['VersionID'] = "50.0"; + 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; } - body.removeWhere((key, value) => value == null); - log("body: ${json.encode(body)}"); - log("uri: ${Uri.parse(url.trim())}"); + // body['TokenID'] = "@dm!n"; + // body['PatientID'] = "4767477"; + } - final bool networkStatus = await Utils.checkConnection(bypassConnectionCheck: bypassConnectionCheck); + body.removeWhere((key, value) => value == null); + log("body: ${json.encode(body)}"); + log("uri: ${Uri.parse(url.trim())}"); - if (!networkStatus) { - onFailure( - 'Please Check The Internet Connection 1', - -1, - failureType: ConnectivityFailure("Please Check The Internet Connection 1"), - ); - _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); - return; - } + final bool networkStatus = await Utils.checkConnection(bypassConnectionCheck: bypassConnectionCheck); - final response = await http.post(Uri.parse(url.trim()), body: json.encode(body), headers: headers); - final int statusCode = response.statusCode; - log("response.body: ${response.body}"); - if (statusCode < 200 || statusCode >= 400) { - var parsed = json.decode(utf8.decode(response.bodyBytes)); - onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data")); - logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); + if (!networkStatus) { + onFailure( + 'Please Check The Internet Connection 1', + -1, + failureType: ConnectivityFailure("Please Check The Internet Connection 1"), + ); + _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); + return; + } + + final response = await http.post(Uri.parse(url.trim()), body: json.encode(body), headers: headers); + final int statusCode = response.statusCode; + log("response.body: ${response.body}"); + if (statusCode < 200 || statusCode >= 400) { + var parsed = json.decode(utf8.decode(response.bodyBytes)); + onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data")); + logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); + } else { + var parsed = json.decode(utf8.decode(response.bodyBytes)); + if (isAllowAny) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage']); } else { - var parsed = json.decode(utf8.decode(response.bodyBytes)); - if (isAllowAny) { - onSuccess(parsed, statusCode, messageStatus: 1, errorMessage: ""); + if (parsed['Response_Message'] != null) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else { - if (parsed['Response_Message'] != null) { + if (parsed['ErrorType'] == 4) { + //TODO : handle app update + logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); + } + if (parsed['ErrorType'] == 2) { + // todo: handle Logout + logApiEndpointError(endPoint, "session logged out", statusCode); + } + if (isAllowAny) { onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else { - if (parsed['ErrorType'] == 4) { - //TODO : handle app update + } else if (parsed['IsAuthenticated'] == null) { + if (parsed['isSMSSent'] == true) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + } else if (parsed['MessageStatus'] == 1) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + } else if (parsed['Result'] == 'OK') { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + } else { + onFailure( + parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode, + failureType: MessageStatusFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']), + ); logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } - if (parsed['ErrorType'] == 2) { - // todo: handle Logout - logApiEndpointError(endPoint, "session logged out", statusCode); - } - if (isAllowAny) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else if (parsed['IsAuthenticated'] == null) { - if (parsed['isSMSSent'] == true) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else if (parsed['MessageStatus'] == 1) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else if (parsed['Result'] == 'OK') { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else { - onFailure( - parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], - statusCode, - failureType: MessageStatusFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']), - ); - logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); - } - } else if (parsed['MessageStatus'] == 1 || parsed['SMSLoginRequired'] == true) { + } else if (parsed['MessageStatus'] == 1 || parsed['SMSLoginRequired'] == true) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + } else if (parsed['MessageStatus'] == 2 && parsed['IsAuthenticated']) { + if (parsed['SameClinicApptList'] != null) { onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else if (parsed['MessageStatus'] == 2 && parsed['IsAuthenticated']) { - if (parsed['SameClinicApptList'] != null) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else { - if (parsed['message'] == null && parsed['ErrorEndUserMessage'] == null) { - if (parsed['ErrorSearchMsg'] == null) { - onFailure( - "Server Error found with no available message", - statusCode, - failureType: ServerFailure("Error While Fetching data"), - ); - logApiEndpointError(endPoint, "Server Error found with no available message", statusCode); - } else { - onFailure( - parsed['ErrorSearchMsg'], - statusCode, - failureType: ServerFailure("Error While Fetching data"), - ); - logApiEndpointError(endPoint, parsed['ErrorSearchMsg'], statusCode); - } - } else { - onFailure( - parsed['message'] ?? parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], - statusCode, - failureType: UserIntimationFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']), - ); - logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); - } - } - } else if (!parsed['IsAuthenticated']) { - } else { - if (parsed['SameClinicApptList'] != null) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else { - if (parsed['message'] != null) { + if (parsed['message'] == null && parsed['ErrorEndUserMessage'] == null) { + if (parsed['ErrorSearchMsg'] == null) { onFailure( - parsed['message'] ?? parsed['message'], + "Server Error found with no available message", statusCode, failureType: ServerFailure("Error While Fetching data"), ); - logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); + logApiEndpointError(endPoint, "Server Error found with no available message", statusCode); } else { onFailure( - parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + parsed['ErrorSearchMsg'], statusCode, failureType: ServerFailure("Error While Fetching data"), ); - logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); + logApiEndpointError(endPoint, parsed['ErrorSearchMsg'], statusCode); } + } else { + onFailure( + parsed['message'] ?? parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode, + failureType: UserIntimationFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']), + ); + logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); + } + } + } else if (!parsed['IsAuthenticated']) { + } else { + if (parsed['SameClinicApptList'] != null) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + } else { + if (parsed['message'] != null) { + onFailure( + parsed['message'] ?? parsed['message'], + statusCode, + failureType: ServerFailure("Error While Fetching data"), + ); + logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); + } else { + onFailure( + parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode, + failureType: ServerFailure("Error While Fetching data"), + ); + logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } } } } } + } // } catch (e, stackTrace) { // _loggerService.errorLogs(stackTrace.toString()); // if (e.toString().contains("ClientException")) { diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 35371cc..a2cbc49 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -121,4 +121,5 @@ class AppAnimations { static const String register = '$lottieBasePath/register.json'; static const String checkmark = '$lottieBasePath/checkmark.json'; static const String loadingAnimation = '$lottieBasePath/Loader.json'; + static const String errorAnimation = '$lottieBasePath/ErrorAnimation.json'; } diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 75c0cf9..216da22 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -323,6 +323,19 @@ class Utils { ).center; } + static Widget getErrorWidget({String? loadingText}) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + SizedBox(height: 8.h), + (loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor), + SizedBox(height: 8.h), + ], + ).center; + } + static bool isVidaPlusProject(AppState appState, int projectID) { bool isVidaPlus = false; for (var element in appState.vidaPlusProjectList) { diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 8fc477c..804f9f9 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -29,6 +29,9 @@ abstract class MyAppointmentsRepo { Future>> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); Future>> confirmAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); + + Future>> sendCheckInNfcRequest( + {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String scannedCode, required int checkInType}); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -350,4 +353,46 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> sendCheckInNfcRequest( + {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String scannedCode, required int checkInType}) async { + Map requestBody = { + "AppointmentNo": patientAppointmentHistoryResponseModel.appointmentNo, + "NFC_Code": scannedCode, + "ProjectID": patientAppointmentHistoryResponseModel.projectID, + "ClinicID": patientAppointmentHistoryResponseModel.clinicID, + "CheckinBy": checkInType, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + SEND_CHECK_IN_NFC_REQUEST, + body: requestBody, + 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()); + } + }, + isAllowAny: true, + ); + 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/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 2815977..4a921da 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -229,4 +229,28 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); } + + Future sendCheckInNfcRequest( + {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + required String scannedCode, + required int checkInType, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { + final result = await myAppointmentsRepo.sendCheckInNfcRequest(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, scannedCode: scannedCode, checkInType: checkInType); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index d6ff672..7188620 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -522,6 +522,7 @@ class _AppointmentDetailsPageState extends State { title: LocaleKeys.onlineCheckIn.tr(), child: AppointmentCheckinBottomSheet( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + myAppointmentsViewModel: myAppointmentsViewModel, ), callBackFunc: () {}, isFullScreen: false); diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart index f3f05b3..f8e0f59 100644 --- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -1,44 +1,138 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; +import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:barcode_scan2/barcode_scan2.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/nfc/nfc_reader_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; class AppointmentCheckinBottomSheet extends StatelessWidget { - AppointmentCheckinBottomSheet({super.key, required this.patientAppointmentHistoryResponseModel}); + AppointmentCheckinBottomSheet({super.key, required this.patientAppointmentHistoryResponseModel, required this.myAppointmentsViewModel}); PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; + MyAppointmentsViewModel myAppointmentsViewModel; + + bool _supportsNFC = false; @override Widget build(BuildContext context) { + FlutterNfcKit.nfcAvailability.then((value) { + _supportsNFC = (value == NFCAvailability.available); + }); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - checkInOptionCard(AppAssets.checkin_location_icon, "Live Location".needTranslation, "".needTranslation), + checkInOptionCard( + AppAssets.checkin_location_icon, + "Live Location".needTranslation, + "Verify your location to be at hospital to check in".needTranslation, + ).onPress(() { + Navigator.of(context).pop(); + }), SizedBox(height: 16.h), - checkInOptionCard(AppAssets.checkin_nfc_icon, "NFC (Near Field Communication)".needTranslation, "".needTranslation), + checkInOptionCard( + AppAssets.checkin_nfc_icon, + "NFC (Near Field Communication)".needTranslation, + "Scan your phone via NFC board to check in".needTranslation, + ).onPress(() { + Future.delayed(const Duration(milliseconds: 500), () { + showNfcReader(context, onNcfScan: (String nfcId) { + Future.delayed(const Duration(milliseconds: 100), () { + sendCheckInRequest(nfcId, context); + }); + }, onCancel: () {}); + }); + }), SizedBox(height: 16.h), - checkInOptionCard(AppAssets.checkin_qr_icon, "QR Code".needTranslation, "".needTranslation), + checkInOptionCard( + AppAssets.checkin_qr_icon, + "QR Code".needTranslation, + "Scan QR code with your camera to check in".needTranslation, + ).onPress(() async { + String onlineCheckInQRCode = (await BarcodeScanner.scan().then((value) => value.rawContent)); + if (onlineCheckInQRCode != "") { + sendCheckInRequest(onlineCheckInQRCode, context); + } else {} + }), ], ); } Widget checkInOptionCard(String icon, String title, String subTitle) { return Container( - height: 120.h, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, ), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Utils.buildSvgWithAssets(icon: icon), + Utils.buildSvgWithAssets(icon: icon, width: 40.h, height: 40.h, fit: BoxFit.fill), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText16(isBold: true, color: AppColors.textColor), + subTitle.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + ], + ), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ], + ), ], - ), + ).paddingAll(16.h), + ); + } + + void sendCheckInRequest(String scannedCode, BuildContext context) async { + showCommonBottomSheet(context, + child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + await myAppointmentsViewModel.sendCheckInNfcRequest( + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + scannedCode: scannedCode, + checkInType: 2, + onSuccess: (apiResponse) { + Navigator.of(context).pop(); + showCommonBottomSheetWithoutHeight(context, title: "Success".needTranslation, child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () { + Navigator.of(context).pop(); + Navigator.pushAndRemoveUntil( + context, + FadePage( + page: LandingNavigation(), + ), + (r) => false); + Navigator.of(context).push( + FadePage(page: MyAppointmentsPage()), + ); + }, isFullScreen: false); + }, + onError: (error) { + Navigator.of(context).pop(); + showCommonBottomSheetWithoutHeight(context, title: "Error".needTranslation, child: Utils.getErrorWidget(loadingText: error), callBackFunc: () { + Navigator.of(context).pop(); + }, isFullScreen: false); + }, ); } } diff --git a/lib/presentation/appointments/widgets/appointment_queueing_screen.dart b/lib/presentation/appointments/widgets/appointment_queueing_screen.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/widgets/nfc/nfc_reader_sheet.dart b/lib/widgets/nfc/nfc_reader_sheet.dart new file mode 100644 index 0000000..b76b36f --- /dev/null +++ b/lib/widgets/nfc/nfc_reader_sheet.dart @@ -0,0 +1,212 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.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/utils/size_utils.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'; + +void showNfcReader(BuildContext context, {required Function onNcfScan, required VoidCallback onCancel}) { + showModalBottomSheet( + context: context, + enableDrag: false, + isDismissible: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)), + ), + backgroundColor: Colors.white, + builder: (context) { + return NfcLayout( + onNcfScan: onNcfScan, + onCancel: onCancel, + ); + }); +} + +class NfcLayout extends StatefulWidget { + Function? onNcfScan; + VoidCallback? onCancel; + + NfcLayout({this.onNcfScan, this.onCancel}); + + @override + _NfcLayoutState createState() => _NfcLayoutState(); +} + +class _NfcLayoutState extends State { + bool _reading = false; + Widget? mainWidget; + late String nfcId; + + @override + void initState() { + super.initState(); + readNFC(); + } + + void readNFC() async { + FlutterNfcKit.finish(); + FlutterNfcKit.poll(timeout: Duration(seconds: 10), androidPlatformSound: true, androidCheckNDEF: false, iosMultipleTagMessage: "Multiple tags found!").then((value) async { + setState(() { + _reading = true; + mainWidget = doneNfc(); + }); + Future.delayed(const Duration(milliseconds: 500), () async { + await FlutterNfcKit.finish(); + widget.onNcfScan!(nfcId); + Navigator.pop(context); + }); + nfcId = value.id; + }).catchError((err) { + print(err); + Navigator.of(context).pop(); + }); + } + + @override + Widget build(BuildContext context) { + // return SizedBox(); + (mainWidget == null && !_reading) ? mainWidget = scanNfc() : mainWidget = doneNfc(); + return Platform.isAndroid ? AnimatedSwitcher(duration: Duration(milliseconds: 500), child: mainWidget) : SizedBox.shrink(); + } + + Widget scanNfc() { + return Container( + key: ValueKey(1), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 30, + ), + Text( + "Ready To Scan", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24, + ), + ), + SizedBox( + height: 30, + ), + SvgPicture.asset( + "assets/images/nfc/contactless.svg", + height: MediaQuery.of(context).size.width / 3, + ), + SizedBox( + height: 30, + ), + Text( + "Approach an NFC Tag", + style: TextStyle( + fontSize: 18, + ), + ), + SizedBox( + height: 30, + ), + ButtonTheme( + minWidth: MediaQuery.of(context).size.width / 1.2, + height: 45.0, + buttonColor: Colors.grey[300], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + child: CustomButton( + text: LocaleKeys.cancel.tr(), + onPressed: () { + widget.onCancel!(); + Navigator.pop(context); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.cancel, + iconColor: AppColors.whiteColor, + iconSize: 16.h, + ), + ), + SizedBox( + height: 30, + ), + ], + ), + ); + } + + Widget doneNfc() { + return Container( + key: ValueKey(2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 30, + ), + Text( + "Successfully Scanned", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24, + ), + ), + SizedBox( + height: 30, + ), + Image.asset( + "assets/images/nfc/ic_done.png", + height: MediaQuery.of(context).size.width / 3, + ), + SizedBox( + height: 30, + ), + Text( + "Approach an NFC Tag", + style: TextStyle( + fontSize: 18, + ), + ), + SizedBox( + height: 30, + ), + ButtonTheme( + minWidth: MediaQuery.of(context).size.width / 1.2, + height: 45.0, + buttonColor: Colors.grey[300], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + child: CustomButton( + text: LocaleKeys.done.tr(), + onPressed: () { + widget.onCancel!(); + Navigator.pop(context); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.cancel, + iconColor: AppColors.whiteColor, + iconSize: 16.h, + ), + ), + SizedBox( + height: 30, + ), + ], + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 7536aaa..afbac11 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -73,6 +73,8 @@ dependencies: maps_launcher: ^3.0.0+1 amazon_payfort: ^1.1.4 network_info_plus: ^6.1.4 + flutter_nfc_kit: ^3.6.0 + barcode_scan2: ^4.5.1 dev_dependencies: flutter_test: From 2a081e30b5e2a7e4d17f99eb1e56fe0ae0f4eacb Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Tue, 9 Sep 2025 18:50:40 +0300 Subject: [PATCH 6/7] saved login done. --- lib/core/api_consts.dart | 2 +- lib/core/app_state.dart | 2 +- lib/core/utils/request_utils.dart | 3 +- .../authentication/authentication_repo.dart | 33 +++++++++++++-- .../authentication_view_model.dart | 40 +++++++++++-------- .../patient_insert_device_imei_request.dart | 13 +++--- .../authentication/quick_login.dart | 6 ++- .../authentication/saved_login_screen.dart | 6 ++- lib/presentation/home/landing_page.dart | 11 +++-- lib/widgets/common_bottom_sheet.dart | 2 +- 10 files changed, 83 insertions(+), 35 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index a034570..f5c19e0 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -726,7 +726,7 @@ const DEACTIVATE_ACCOUNT = 'Services/Patients.svc/REST/PatientAppleActivation_In class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 4408844..4ab98f6 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -109,7 +109,7 @@ class AppState { RegistrationDataModelPayload? _userRegistrationPayload; - RegistrationDataModelPayload get getUserRegistrationPayload => _userRegistrationPayload!; + RegistrationDataModelPayload get getUserRegistrationPayload => _userRegistrationPayload ?? RegistrationDataModelPayload(); set setUserRegistrationPayload(RegistrationDataModelPayload value) { _userRegistrationPayload = value; diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index 1045d9c..1bf1e06 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -61,6 +61,7 @@ class RequestUtils { int? patientId, required String nationIdText, required String countryCode, + required int loginType }) { bool fileNo = false; if (nationIdText.isNotEmpty) { @@ -71,7 +72,7 @@ class RequestUtils { request.mobileNo = '0$phoneNumber'; request.deviceToken = deviceToken; request.projectOutSA = patientOutSA; - request.loginType = otpTypeEnum.toInt(); + request.loginType = loginType; request.oTPSendType = otpTypeEnum.toInt(); // could map OTPTypeEnum if needed request.zipCode = countryCode; // or countryCode if defined elsewhere request.logInTokenID = loginTokenID ?? ""; diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 222ac7c..989c667 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -180,6 +180,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { newRequest.projectOutSA = newRequest.zipCode == '966' ? false : true; newRequest.isDentalAllowedBackend = false; newRequest.forRegisteration = newRequest.isRegister ?? false; + newRequest.isRegister = false; } @@ -390,8 +391,34 @@ class AuthenticationRepoImp implements AuthenticationRepo { @override Future> insertPatientDeviceData({required patientDeviceDataRequest}) { - // TODO: implement insertPatientDeviceData - - throw UnimplementedError(); + try { + GenericApiModel? apiResponse; + Failure? failure; + return apiClient.post( + ApiConsts.insertPatientMobileData, + body: patientDeviceDataRequest, + 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()); + } + }, + ).then((_) { + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + }); + } catch (e) { + return Future.value(Left(UnknownFailure(e.toString()))); + } } } diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 097079a..eeccfc8 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -289,7 +289,7 @@ class AuthenticationViewModel extends ChangeNotifier { await checkActivationCode( otpTypeEnum: otpTypeEnum, onWrongActivationCode: (String? message) {}, - activationCode: 0000, + activationCode: null, //todo silent login case halded on the repo itself.. ); } } @@ -360,7 +360,7 @@ class AuthenticationViewModel extends ChangeNotifier { } Future checkActivationCode({ - required int activationCode, + required String? activationCode, required OTPTypeEnum otpTypeEnum, required Function(String? message) onWrongActivationCode, }) async { @@ -373,9 +373,10 @@ class AuthenticationViewModel extends ChangeNotifier { registeredData: null, nationIdText: nationalIdController.text, countryCode: selectedCountrySignup.countryCode, + loginType: loginTypeEnum.toInt ).toJson(); - bool isForRegister = _appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true; + bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true); if (isForRegister) { if (_appState.getUserRegistrationPayload.patientOutSa == true) request['DOB'] = _appState.getUserRegistrationPayload.dob; request['HealthId'] = _appState.getUserRegistrationPayload.healthId; @@ -396,7 +397,7 @@ class AuthenticationViewModel extends ChangeNotifier { } else { final resultEither = await _authenticationRepo.checkActivationCodeRepo( newRequest: CheckActivationCodeRegisterReq.fromJson(request), - activationCode: activationCode.toString(), + activationCode: activationCode, isRegister: false, ); @@ -422,6 +423,7 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setAppAuthToken = activation.authenticationTokenId; final request = RequestUtils.getAuthanticatedCommonRequest().toJson(); bool isUserAgreedBefore = await checkIfUserAgreedBefore(request: request); + insertPatientIMEIData(loginTypeEnum.toInt); clearDefaultInputValues(); if (isUserAgreedBefore) { navigateToHomeScreen(); @@ -491,7 +493,7 @@ class AuthenticationViewModel extends ChangeNotifier { phoneNumber: phoneNumber, checkActivationCode: (int activationCode) async { await checkActivationCode( - activationCode: activationCode, + activationCode: activationCode.toString(), otpTypeEnum: otpTypeEnum, onWrongActivationCode: (String? value) { onWrongActivationCode(message: value); @@ -508,12 +510,19 @@ class AuthenticationViewModel extends ChangeNotifier { } loginWithFingerPrintFace() async { - _localAuthService.authenticate().then((value) { + _localAuthService.authenticate().then((value) async { if (value) { // we have to handle this if verification true; - // checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: 0000, onWrongActivationCode: (String? message) {}); - // authenticated = true; - insertPatientIMEIData( OTPTypeEnum.faceIDFingerprint.toInt()); + if(!_appState.isAuthenticated) { + loginTypeEnum = (_appState.deviceTypeID == 1 ? LoginTypeEnum.face : LoginTypeEnum.fingerprint); + print(loginTypeEnum); + checkActivationCode(otpTypeEnum:OTPTypeEnum.faceIDFingerprint , activationCode: null, onWrongActivationCode: (String? message) {}); + insertPatientIMEIData((_appState.deviceTypeID == 1 ? LoginTypeEnum.face.toInt : LoginTypeEnum.fingerprint.toInt)); + }else { + // authenticated = true; + insertPatientIMEIData((_appState.deviceTypeID == 1 ? LoginTypeEnum.face.toInt : LoginTypeEnum.fingerprint.toInt)); + } + notifyListeners(); // navigateToHomeScreen(); } else { @@ -521,7 +530,6 @@ class AuthenticationViewModel extends ChangeNotifier { notifyListeners(); } }); - notifyListeners(); } checkLastLoginStatus(Function() onSuccess) async { @@ -696,25 +704,25 @@ class AuthenticationViewModel extends ChangeNotifier { Future insertPatientIMEIData(int loginType) async{ - final resultEither = await _authenticationRepo.insertPatientIMEIData(patientIMEIDataRequest: PatientInsertDeviceImei(imei: _appState.deviceToken, deviceTypeId: _appState.getDeviceTypeID(), patientId: _appState.getAuthenticatedUser()!.patientId!, patientIdentificationNo:_appState.getAuthenticatedUser()!.nationalityId!, firstName: _appState.getAuthenticatedUser()!.firstName!, lastName: _appState.getAuthenticatedUser()!.lastName!, patientTypeId: _appState.getAuthenticatedUser()!.patientType, mobileNo:_appState.getAuthenticatedUser()!.mobileNumber!, logInTypeId: loginType, patientOutSa:_appState.getAuthenticatedUser()!.outSa! ).toJson()); + final resultEither = await _authenticationRepo.insertPatientIMEIData(patientIMEIDataRequest: PatientInsertDeviceImei(imei: _appState.deviceToken, deviceTypeId: _appState.getDeviceTypeID(), patientId: _appState.getAuthenticatedUser()!.patientId!, patientIdentificationNo:_appState.getAuthenticatedUser()!.nationalityId!, firstName: _appState.getAuthenticatedUser()!.firstName!, lastName: _appState.getAuthenticatedUser()!.lastName!, patientTypeId: _appState.getAuthenticatedUser()!.patientType, mobileNo:_appState.getAuthenticatedUser()!.mobileNumber!, logInTypeId: loginType, patientOutSa:_appState.getAuthenticatedUser()!.outSa!, outSa: _appState.getAuthenticatedUser()!.outSa == 1 ? true :false, biometricEnabled: loginType == 1 || loginType ==2 ? false :true, firstNameN:_appState.getAuthenticatedUser()!.firstNameN , lastNameN:_appState.getAuthenticatedUser()!.lastNameN ).toJson()); resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.messageStatus == 1) { - print("Insert IMEI Success"); + log("Insert IMEI Success"); insertPatientDeviceData( loginType); } else { - print("Insert IMEI Failed"); + log("Insert IMEI Failed"); } }); } Future insertPatientDeviceData(int loginType) async{ - final resultEither = await _authenticationRepo.insertPatientDeviceData(patientDeviceDataRequest: InsertPatientMobileDeviceInfo(deviceToken: _appState.deviceToken, deviceTypeId: _appState.getDeviceTypeID(), patientId: _appState.getAuthenticatedUser()!.patientId!, patientTypeId: _appState.getAuthenticatedUser()!.patientType, patientOutSa:_appState.getAuthenticatedUser()!.outSa!, loginType: loginType, languageId: _appState.getLanguageID(), latitude: _appState.userLat, longitude:_appState.userLong ).toJson()); + final resultEither = await _authenticationRepo.insertPatientDeviceData(patientDeviceDataRequest: InsertPatientMobileDeviceInfo(deviceToken: _appState.deviceToken, deviceTypeId: _appState.getDeviceTypeID(), patientId: _appState.getAuthenticatedUser()!.patientId!, patientTypeId: _appState.getAuthenticatedUser()!.patientType, patientOutSa:_appState.getAuthenticatedUser()!.outSa!, loginType: loginType, languageId: _appState.getLanguageID(), latitude: _appState.userLat, longitude:_appState.userLong, voipToken: "", deviceType: _appState.deviceTypeID, patientMobileNumber:_appState.getAuthenticatedUser()!.mobileNumber, nationalId: _appState.getAuthenticatedUser()!.patientIdentificationNo, gender: _appState.getAuthenticatedUser()!.gender ).toJson()); resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.messageStatus == 1) { - print("Insert Device Data Success"); + log("Insert Device Data Success"); } else { - print("Insert IMEI Failed"); + log("Insert IMEI Failed"); } }); diff --git a/lib/features/authentication/models/request_models/patient_insert_device_imei_request.dart b/lib/features/authentication/models/request_models/patient_insert_device_imei_request.dart index f54bb83..0b2a99f 100644 --- a/lib/features/authentication/models/request_models/patient_insert_device_imei_request.dart +++ b/lib/features/authentication/models/request_models/patient_insert_device_imei_request.dart @@ -7,7 +7,6 @@ class PatientInsertDeviceImei { String? firstName; String? firstNameN; String? lastNameN; - String? lastName; int? preferredLanguage; String? patientIdentificationNo; bool? outSa; @@ -28,6 +27,7 @@ class PatientInsertDeviceImei { int? patientTypeId; int? patientOutSa; String? sessionId; + String? lastName; PatientInsertDeviceImei({ this.setupId, @@ -36,7 +36,6 @@ class PatientInsertDeviceImei { this.firstName, this.firstNameN, this.lastNameN, - this.lastName, this.preferredLanguage, this.patientIdentificationNo, this.outSa, @@ -57,6 +56,7 @@ class PatientInsertDeviceImei { this.patientTypeId, this.patientOutSa, this.sessionId, + this.lastName }); factory PatientInsertDeviceImei.fromRawJson(String str) => PatientInsertDeviceImei.fromJson(json.decode(str)); @@ -70,7 +70,6 @@ class PatientInsertDeviceImei { firstName: json["FirstName"], firstNameN: json["FirstNameN"], lastNameN: json["LastNameN"], - lastName: json["LastName"], preferredLanguage: json["PreferredLanguage"], patientIdentificationNo: json["PatientIdentificationNo"], outSa: json["OutSA"], @@ -90,7 +89,9 @@ class PatientInsertDeviceImei { deviceTypeId: json["DeviceTypeID"], patientTypeId: json["PatientTypeID"], patientOutSa: json["PatientOutSA"], - sessionId: json[" SessionID"], + sessionId: json["SessionID"], + lastName: json["LastName"], + ); Map toJson() => { @@ -100,7 +101,6 @@ class PatientInsertDeviceImei { "FirstName": firstName, "FirstNameN": firstNameN, "LastNameN": lastNameN, - "LastName": lastName, "PreferredLanguage": preferredLanguage, "PatientIdentificationNo": patientIdentificationNo, "OutSA": outSa, @@ -120,6 +120,7 @@ class PatientInsertDeviceImei { "DeviceTypeID": deviceTypeId, "PatientTypeID": patientTypeId, "PatientOutSA": patientOutSa, - " SessionID": sessionId, + "SessionID": sessionId, + "LastName": lastName, }; } diff --git a/lib/presentation/authentication/quick_login.dart b/lib/presentation/authentication/quick_login.dart index 9cf0431..c5f540b 100644 --- a/lib/presentation/authentication/quick_login.dart +++ b/lib/presentation/authentication/quick_login.dart @@ -19,6 +19,7 @@ class QuickLogin extends StatefulWidget { } class _QuickLogin extends State { + @override Widget build(BuildContext context) { return Container( @@ -70,7 +71,7 @@ class _QuickLogin extends State { height: 101, ), const SizedBox(height: 10), - LocaleKeys.enableQuickLogin.tr().toText26(isBold: true), + LocaleKeys.enableQuickLogin.tr().toText26(isBold: true), // Text( // ' TranslationBase.of(context).enableQuickLogin', // style: context.dynamicTextStyle( @@ -100,6 +101,9 @@ class _QuickLogin extends State { text:LocaleKeys.enableQuickLogin.tr(), onPressed: () { widget.onPressed(); + setState(() { + + }); }, backgroundColor: Color(0xffED1C2B), borderColor: Color(0xffED1C2B), diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index bf97987..55cbadf 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -34,6 +34,8 @@ class _SavedLogin extends State { void initState() { authVm = context.read(); appState = getIt.get(); + loginType = LoginTypeExtension.fromValue(appState.getSelectDeviceByImeiRespModelElement!.logInType!)!; + authVm.phoneNumberController.text = appState.getSelectDeviceByImeiRespModelElement!.mobile!.startsWith("0") ? appState.getSelectDeviceByImeiRespModelElement!.mobile!.replaceFirst("0", "") : appState.getSelectDeviceByImeiRespModelElement!.mobile!; @@ -78,7 +80,7 @@ class _SavedLogin extends State { children: [ // Last login info - ("${LocaleKeys.lastloginBy.tr()} ${LoginTypeExtension.fromValue(appState.getSelectDeviceByImeiRespModelElement!.logInType ?? 0)!.displayName}") + ("${LocaleKeys.lastloginBy.tr()} ${loginType.displayName}") .toText14(isBold: true, color: AppColors.greyTextColor), (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y 'at' HH:mm") @@ -87,7 +89,7 @@ class _SavedLogin extends State { Container( margin: EdgeInsets.all(16.h), - child: Utils.buildSvgWithAssets(icon: getTypeIcons(loginType.toInt), height: 54, width: 54, iconColor: loginType.toInt == 4 ? null : AppColors.primaryRedColor)), + child: Utils.buildSvgWithAssets(icon: getTypeIcons(appState.getSelectDeviceByImeiRespModelElement!.logInType!), height: 54, width: 54, iconColor: loginType.toInt == 4 ? null : AppColors.primaryRedColor)), // Face ID login button SizedBox( height: 45, diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index a9ced9c..b55632b 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -326,15 +326,20 @@ class _LandingPageState extends State { context, title: "", child: QuickLogin( - isDone: isDone, + isDone: isDone, onPressed: () { // sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true); authVM.loginWithFingerPrintFace(); }, ), - height: 400, + height:isDone == false ? ResponsiveExtension.screenHeight * 0.5 : ResponsiveExtension.screenHeight * 0.3, isFullScreen: false, - callBackFunc: (str) {}, + callBackFunc: (str) { + isDone = true; + setState(() { + + }); + }, ); } } diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 11b7df8..583eab6 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -54,7 +54,7 @@ class ButtonSheetContent extends StatelessWidget { @override Widget build(BuildContext context) { return Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ // SizedBox( // height: 20.h, From 6b2ec5aa2cfd61d7b331171b6a341df650f3fd20 Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Tue, 9 Sep 2025 21:42:41 +0300 Subject: [PATCH 7/7] updates, fixes & service privileges --- lib/core/api_consts.dart | 1 + lib/core/app_state.dart | 23 ++- .../privilege/HMCProjectListModel.dart | 15 ++ .../privilege/PrivilegeModel.dart | 24 +++ .../privilege/ProjectDetailListModel.dart | 32 ++++ .../VidaPlusProjectListModel.dart | 0 lib/core/utils/request_utils.dart | 28 +-- lib/core/utils/utils.dart | 31 ++++ .../authentication/authentication_repo.dart | 39 +++- .../authentication_view_model.dart | 166 +++++++++++++----- .../appointment_checkin_bottom_sheet.dart | 28 ++- .../authentication/quick_login.dart | 2 +- .../authentication/saved_login_screen.dart | 13 +- lib/presentation/home/landing_page.dart | 10 +- lib/splashPage.dart | 24 ++- 15 files changed, 343 insertions(+), 93 deletions(-) create mode 100644 lib/core/common_models/privilege/HMCProjectListModel.dart create mode 100644 lib/core/common_models/privilege/PrivilegeModel.dart create mode 100644 lib/core/common_models/privilege/ProjectDetailListModel.dart rename lib/core/common_models/{ => privilege}/VidaPlusProjectListModel.dart (100%) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index a034570..daa094e 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -803,6 +803,7 @@ class ApiConsts { static final String insertPatientDeviceIMEIData = 'Services/Patients.svc/REST/Patient_INSERTDeviceIMEI'; static final String insertPatientMobileData = 'Services/MobileNotifications.svc/REST/Insert_PatientMobileDeviceInfo'; + static final String getPrivileges = 'Services/Patients.svc/REST/Service_Privilege'; // static values for Api static final double appVersionID = 18.7; diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 4ab98f6..3c4555e 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -1,7 +1,10 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; -import 'package:hmg_patient_app_new/core/common_models/VidaPlusProjectListModel.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/HMCProjectListModel.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/PrivilegeModel.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/ProjectDetailListModel.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/VidaPlusProjectListModel.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_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'; @@ -37,7 +40,7 @@ class AppState { if (isFamily) { _authenticatedChildUser = authenticatedUser; } else { - setIsAuthenticated = true; + setIsAuthenticated = true; _authenticatedRootUser = authenticatedUser; } } @@ -93,11 +96,26 @@ class AppState { set setDeviceTypeID(v) => deviceTypeID = v; List vidaPlusProjectList = []; + List privilegeModelList = []; + List hMCProjectListModel = []; + List projectDetailListModel = []; setVidaPlusProjectList(List vidaPlusProjectListModelInput) { vidaPlusProjectList = vidaPlusProjectListModelInput; } + setPrivilegeModelList(List privilegeModelListInput) { + privilegeModelList = privilegeModelListInput; + } + + setHMCProjectList(List hMCProjectListModelInput) { + hMCProjectListModel = hMCProjectListModelInput; + } + + setProjectsDetailList(List projectDetailListModelInput) { + projectDetailListModel = projectDetailListModelInput; + } + CheckUserStatusResponseNHIC? _nHICUserData; CheckUserStatusResponseNHIC get getNHICUserData => _nHICUserData!; @@ -106,7 +124,6 @@ class AppState { _nHICUserData = value; } - RegistrationDataModelPayload? _userRegistrationPayload; RegistrationDataModelPayload get getUserRegistrationPayload => _userRegistrationPayload ?? RegistrationDataModelPayload(); diff --git a/lib/core/common_models/privilege/HMCProjectListModel.dart b/lib/core/common_models/privilege/HMCProjectListModel.dart new file mode 100644 index 0000000..6af7bfb --- /dev/null +++ b/lib/core/common_models/privilege/HMCProjectListModel.dart @@ -0,0 +1,15 @@ +class HMCProjectListModel { + int? projectID; + + HMCProjectListModel({this.projectID}); + + HMCProjectListModel.fromJson(Map json) { + projectID = json['ProjectID']; + } + + Map toJson() { + final Map data = new Map(); + data['ProjectID'] = this.projectID; + return data; + } +} diff --git a/lib/core/common_models/privilege/PrivilegeModel.dart b/lib/core/common_models/privilege/PrivilegeModel.dart new file mode 100644 index 0000000..587490b --- /dev/null +++ b/lib/core/common_models/privilege/PrivilegeModel.dart @@ -0,0 +1,24 @@ +class PrivilegeModel { + int? iD; + String? serviceName; + bool? privilege; + dynamic region; + + PrivilegeModel({this.iD, this.serviceName, this.privilege, this.region}); + + PrivilegeModel.fromJson(Map json) { + iD = json['ID']; + serviceName = json['ServiceName']; + privilege = json['Previlege']; + region = json['Region']; + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['ServiceName'] = this.serviceName; + data['Previlege'] = this.privilege; + data['Region'] = this.region; + return data; + } +} diff --git a/lib/core/common_models/privilege/ProjectDetailListModel.dart b/lib/core/common_models/privilege/ProjectDetailListModel.dart new file mode 100644 index 0000000..782c376 --- /dev/null +++ b/lib/core/common_models/privilege/ProjectDetailListModel.dart @@ -0,0 +1,32 @@ +class ProjectDetailListModel { + int? projectID; + String? latitude; + String? longitude; + int? geofenceRadius; + String? checkInQrCode; + + ProjectDetailListModel( + {this.projectID, + this.latitude, + this.longitude, + this.geofenceRadius, + this.checkInQrCode}); + + ProjectDetailListModel.fromJson(Map json) { + projectID = json['ProjectID']; + latitude = json['Latitude']; + longitude = json['Longitude']; + geofenceRadius = json['GeofenceRadius']; + checkInQrCode = json['CheckInQrCode']; + } + + Map toJson() { + final Map data = new Map(); + data['ProjectID'] = this.projectID; + data['Latitude'] = this.latitude; + data['Longitude'] = this.longitude; + data['GeofenceRadius'] = this.geofenceRadius; + data['CheckInQrCode'] = this.checkInQrCode; + return data; + } +} diff --git a/lib/core/common_models/VidaPlusProjectListModel.dart b/lib/core/common_models/privilege/VidaPlusProjectListModel.dart similarity index 100% rename from lib/core/common_models/VidaPlusProjectListModel.dart rename to lib/core/common_models/privilege/VidaPlusProjectListModel.dart diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index 1bf1e06..f54432c 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -20,6 +20,9 @@ class RequestUtils { bool fileNo = false; if (nationId.isNotEmpty) { fileNo = nationId.length < 10; + if (fileNo) { + patientId = int.tryParse(nationId); + } } var request = SendActivationRequest(); if (phoneNumber.isNotEmpty) { @@ -51,18 +54,17 @@ class RequestUtils { return request; } - static dynamic getCommonRequestWelcome({ - required String phoneNumber, - required OTPTypeEnum otpTypeEnum, - required String? deviceToken, - required bool patientOutSA, - required String? loginTokenID, - required var registeredData, - int? patientId, - required String nationIdText, - required String countryCode, - required int loginType - }) { + static dynamic getCommonRequestWelcome( + {required String phoneNumber, + required OTPTypeEnum otpTypeEnum, + required String? deviceToken, + required bool patientOutSA, + required String? loginTokenID, + required var registeredData, + int? patientId, + required String nationIdText, + required String countryCode, + required int loginType}) { bool fileNo = false; if (nationIdText.isNotEmpty) { fileNo = nationIdText.length < 10; @@ -86,7 +88,7 @@ class RequestUtils { } else { if (fileNo) { request.patientID = patientId ?? int.parse(nationIdText); - request.patientIdentificationID = request.nationalID = "" as int?; + request.patientIdentificationID = request.nationalID = 0; request.searchType = 2; } else { request.patientID = 0; diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 216da22..ebb7037 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -10,6 +10,7 @@ import 'package:fluttertoast/fluttertoast.dart'; import 'package:google_api_availability/google_api_availability.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/common_models/privilege/ProjectDetailListModel.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'; @@ -22,6 +23,8 @@ import 'package:hmg_patient_app_new/widgets/loading_dialog.dart'; import 'package:lottie/lottie.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'dart:math' as dartMath; + class Utils { static AppState appState = getIt.get(); static NavigationService navigationService = getIt.get(); @@ -346,6 +349,34 @@ class Utils { return isVidaPlus; } + static ProjectDetailListModel getProjectDetailObj(AppState appState, int projectID) { + ProjectDetailListModel projectDetailListModel = ProjectDetailListModel(); + for (var element in appState.projectDetailListModel) { + if (element.projectID == projectID) { + projectDetailListModel = element; + } + } + return projectDetailListModel; + } + + static double distance(double lat1, double lon1, double lat2, double lon2) { + const r = 6372.8; // Earth radius in kilometers + + final dLat = _toRadians(lat2 - lat1); + final dLon = _toRadians(lon2 - lon1); + final lat1Radians = _toRadians(lat1); + final lat2Radians = _toRadians(lat2); + + final a = _haversin(dLat) + dartMath.cos(lat1Radians) * dartMath.cos(lat2Radians) * _haversin(dLon); + final c = 2 * dartMath.asin(dartMath.sqrt(a)); + + return r * c; + } + + static double _toRadians(double degrees) => degrees * dartMath.pi / 180; + + static num _haversin(double radians) => dartMath.pow(dartMath.sin(radians / 2), 2); + static getPhoneNumberWithoutZero(String number) { String newNumber = ""; if (number.startsWith('0')) { diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index e64e4ba..d81094c 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -4,12 +4,15 @@ 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/common_models/privilege/PrivilegeModel.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/features/authentication/models/request_models/check_activation_code_register_request_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class AuthenticationRepo { + Future>> getServicePrivilege(); + Future>> selectDeviceByImei({required String firebaseToken}); Future>> checkPatientAuthentication({required dynamic checkPatientAuthenticationReq}); @@ -32,7 +35,6 @@ abstract class AuthenticationRepo { Future>> insertPatientIMEIData({required dynamic patientIMEIDataRequest}); Future>> insertPatientDeviceData({required dynamic patientDeviceDataRequest}); - } class AuthenticationRepoImp implements AuthenticationRepo { @@ -390,7 +392,6 @@ class AuthenticationRepoImp implements AuthenticationRepo { @override Future> insertPatientDeviceData({required patientDeviceDataRequest}) { - try { GenericApiModel? apiResponse; Failure? failure; @@ -421,4 +422,38 @@ class AuthenticationRepoImp implements AuthenticationRepo { return Future.value(Left(UnknownFailure(e.toString()))); } } + + @override + Future>> getServicePrivilege() { + Map mapDevice = {}; + try { + GenericApiModel? apiResponse; + Failure? failure; + return apiClient.post( + ApiConsts.getPrivileges, + body: mapDevice, + 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()); + } + }, + ).then((_) { + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + }); + } catch (e) { + return Future.value(Left(UnknownFailure(e.toString()))); + } + } } diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 0797c6d..0add5a0 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -6,6 +6,10 @@ import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/HMCProjectListModel.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/PrivilegeModel.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/ProjectDetailListModel.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/VidaPlusProjectListModel.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/loading_utils.dart'; import 'package:hmg_patient_app_new/core/utils/request_utils.dart'; @@ -53,7 +57,11 @@ class AuthenticationViewModel extends ChangeNotifier { _authenticationRepo = authenticationRepo, _localAuthService = localAuthService; - final TextEditingController nationalIdController = TextEditingController(), phoneNumberController = TextEditingController(), dobController = TextEditingController(), nameController = TextEditingController(), emailController = TextEditingController(); + final TextEditingController nationalIdController = TextEditingController(), + phoneNumberController = TextEditingController(), + dobController = TextEditingController(), + nameController = TextEditingController(), + emailController = TextEditingController(); CountryEnum selectedCountrySignup = CountryEnum.saudiArabia; MaritalStatusTypeEnum? maritalStatus; GenderTypeEnum? genderType; @@ -366,18 +374,18 @@ class AuthenticationViewModel extends ChangeNotifier { required Function(String? message) onWrongActivationCode, }) async { final request = RequestUtils.getCommonRequestWelcome( - phoneNumber: phoneNumberController.text, - otpTypeEnum: otpTypeEnum, - deviceToken: _appState.deviceToken, - patientOutSA: true, - loginTokenID: _appState.appAuthToken, - registeredData: null, - nationIdText: nationalIdController.text, - countryCode: selectedCountrySignup.countryCode, - loginType: loginTypeEnum.toInt - ).toJson(); - - bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true); + phoneNumber: phoneNumberController.text, + otpTypeEnum: otpTypeEnum, + deviceToken: _appState.deviceToken, + patientOutSA: true, + loginTokenID: _appState.appAuthToken, + registeredData: null, + nationIdText: nationalIdController.text, + countryCode: selectedCountrySignup.countryCode, + loginType: loginTypeEnum.toInt) + .toJson(); + + bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true); if (isForRegister) { if (_appState.getUserRegistrationPayload.patientOutSa == true) request['DOB'] = _appState.getUserRegistrationPayload.dob; request['HealthId'] = _appState.getUserRegistrationPayload.healthId; @@ -514,12 +522,12 @@ class AuthenticationViewModel extends ChangeNotifier { _localAuthService.authenticate().then((value) async { if (value) { // we have to handle this if verification true; - if(!_appState.isAuthenticated) { + if (!_appState.isAuthenticated) { loginTypeEnum = (_appState.deviceTypeID == 1 ? LoginTypeEnum.face : LoginTypeEnum.fingerprint); print(loginTypeEnum); - checkActivationCode(otpTypeEnum:OTPTypeEnum.faceIDFingerprint , activationCode: null, onWrongActivationCode: (String? message) {}); + checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: null, onWrongActivationCode: (String? message) {}); insertPatientIMEIData((_appState.deviceTypeID == 1 ? LoginTypeEnum.face.toInt : LoginTypeEnum.fingerprint.toInt)); - }else { + } else { // authenticated = true; insertPatientIMEIData((_appState.deviceTypeID == 1 ? LoginTypeEnum.face.toInt : LoginTypeEnum.fingerprint.toInt)); } @@ -535,20 +543,19 @@ class AuthenticationViewModel extends ChangeNotifier { checkLastLoginStatus(Function() onSuccess) async { Future.delayed(Duration(seconds: 1), () { - if (_appState.getSelectDeviceByImeiRespModelElement != null && - (_appState.getSelectDeviceByImeiRespModelElement!.logInType == 1 || _appState.getSelectDeviceByImeiRespModelElement!.logInType == 4)) { - phoneNumberController.text = (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") - ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") - : _appState.getAuthenticatedUser()!.mobileNumber)!; + if (_appState.getSelectDeviceByImeiRespModelElement != null && + (_appState.getSelectDeviceByImeiRespModelElement!.logInType == 1 || _appState.getSelectDeviceByImeiRespModelElement!.logInType == 4)) { + phoneNumberController.text = + (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") : _appState.getAuthenticatedUser()!.mobileNumber)!; nationalIdController.text = _appState.getAuthenticatedUser()!.nationalityId!; onSuccess(); - } else if((loginTypeEnum == LoginTypeEnum.sms || loginTypeEnum == LoginTypeEnum.whatsapp && _appState.getSelectDeviceByImeiRespModelElement == null) && _appState.getAuthenticatedUser() != null){ - phoneNumberController.text = (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") - ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") - : _appState.getAuthenticatedUser()!.mobileNumber)!; - nationalIdController.text = _appState.getAuthenticatedUser()!.nationalityId!; - onSuccess(); - } + } else if ((loginTypeEnum == LoginTypeEnum.sms || loginTypeEnum == LoginTypeEnum.whatsapp && _appState.getSelectDeviceByImeiRespModelElement == null) && + _appState.getAuthenticatedUser() != null) { + phoneNumberController.text = + (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") : _appState.getAuthenticatedUser()!.mobileNumber)!; + nationalIdController.text = _appState.getAuthenticatedUser()!.nationalityId!; + onSuccess(); + } }); } @@ -583,9 +590,7 @@ class AuthenticationViewModel extends ChangeNotifier { }); } - - Future onRegistrationComplete() async{ - + Future onRegistrationComplete() async { // if (emailAddress.text.isEmpty) { // Utils.showErrorToast(TranslationBase.of(context).enterEmailAddress); // return; @@ -596,7 +601,6 @@ class AuthenticationViewModel extends ChangeNotifier { //authVM!.clearDefaultInputValues(); } - Future checkUserStatusForRegistration({required dynamic response, required dynamic request}) async { if (response is Map) { _appState.setAppAuthToken = response["LogInTokenID"]; @@ -703,22 +707,52 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setUserRegistrationPayload = RegistrationDataModelPayload.fromJson(request); } - Future insertPatientIMEIData(int loginType) async{ - - final resultEither = await _authenticationRepo.insertPatientIMEIData(patientIMEIDataRequest: PatientInsertDeviceImei(imei: _appState.deviceToken, deviceTypeId: _appState.getDeviceTypeID(), patientId: _appState.getAuthenticatedUser()!.patientId!, patientIdentificationNo:_appState.getAuthenticatedUser()!.nationalityId!, firstName: _appState.getAuthenticatedUser()!.firstName!, lastName: _appState.getAuthenticatedUser()!.lastName!, patientTypeId: _appState.getAuthenticatedUser()!.patientType, mobileNo:_appState.getAuthenticatedUser()!.mobileNumber!, logInTypeId: loginType, patientOutSa:_appState.getAuthenticatedUser()!.outSa!, outSa: _appState.getAuthenticatedUser()!.outSa == 1 ? true :false, biometricEnabled: loginType == 1 || loginType ==2 ? false :true, firstNameN:_appState.getAuthenticatedUser()!.firstNameN , lastNameN:_appState.getAuthenticatedUser()!.lastNameN ).toJson()); - resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { - if (apiResponse.messageStatus == 1) { - log("Insert IMEI Success"); - insertPatientDeviceData( loginType); - } else { - log("Insert IMEI Failed"); - } - }); - - } - Future insertPatientDeviceData(int loginType) async{ + Future insertPatientIMEIData(int loginType) async { + final resultEither = await _authenticationRepo.insertPatientIMEIData( + patientIMEIDataRequest: PatientInsertDeviceImei( + imei: _appState.deviceToken, + deviceTypeId: _appState.getDeviceTypeID(), + patientId: _appState.getAuthenticatedUser()!.patientId!, + patientIdentificationNo: _appState.getAuthenticatedUser()!.patientIdentificationNo!, + firstName: _appState.getAuthenticatedUser()!.firstName!, + lastName: _appState.getAuthenticatedUser()!.lastName!, + patientTypeId: _appState.getAuthenticatedUser()!.patientType, + mobileNo: _appState.getAuthenticatedUser()!.mobileNumber!, + logInTypeId: loginType, + patientOutSa: _appState.getAuthenticatedUser()!.outSa!, + outSa: _appState.getAuthenticatedUser()!.outSa == 1 ? true : false, + biometricEnabled: loginType == 1 || loginType == 2 ? false : true, + firstNameN: _appState.getAuthenticatedUser()!.firstNameN, + lastNameN: _appState.getAuthenticatedUser()!.lastNameN, + ).toJson()); + resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { + if (apiResponse.messageStatus == 1) { + log("Insert IMEI Success"); + insertPatientDeviceData(loginType); + } else { + log("Insert IMEI Failed"); + } + }); + } - final resultEither = await _authenticationRepo.insertPatientDeviceData(patientDeviceDataRequest: InsertPatientMobileDeviceInfo(deviceToken: _appState.deviceToken, deviceTypeId: _appState.getDeviceTypeID(), patientId: _appState.getAuthenticatedUser()!.patientId!, patientTypeId: _appState.getAuthenticatedUser()!.patientType, patientOutSa:_appState.getAuthenticatedUser()!.outSa!, loginType: loginType, languageId: _appState.getLanguageID(), latitude: _appState.userLat, longitude:_appState.userLong, voipToken: "", deviceType: _appState.deviceTypeID, patientMobileNumber:_appState.getAuthenticatedUser()!.mobileNumber, nationalId: _appState.getAuthenticatedUser()!.patientIdentificationNo, gender: _appState.getAuthenticatedUser()!.gender ).toJson()); + Future insertPatientDeviceData(int loginType) async { + final resultEither = await _authenticationRepo.insertPatientDeviceData( + patientDeviceDataRequest: InsertPatientMobileDeviceInfo( + deviceToken: _appState.deviceToken, + deviceTypeId: _appState.getDeviceTypeID(), + patientId: _appState.getAuthenticatedUser()!.patientId!, + patientTypeId: _appState.getAuthenticatedUser()!.patientType, + patientOutSa: _appState.getAuthenticatedUser()!.outSa!, + loginType: loginType, + languageId: _appState.getLanguageID(), + latitude: _appState.userLat, + longitude: _appState.userLong, + voipToken: "", + deviceType: _appState.deviceTypeID, + patientMobileNumber: _appState.getAuthenticatedUser()!.mobileNumber, + nationalId: _appState.getAuthenticatedUser()!.patientIdentificationNo, + gender: _appState.getAuthenticatedUser()!.gender) + .toJson()); resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.messageStatus == 1) { log("Insert Device Data Success"); @@ -726,7 +760,6 @@ class AuthenticationViewModel extends ChangeNotifier { log("Insert IMEI Failed"); } }); - } @override @@ -736,4 +769,41 @@ class AuthenticationViewModel extends ChangeNotifier { myFocusNode.dispose(); super.dispose(); } + + Future getServicePrivilege() async { + final resultEither = await _authenticationRepo.getServicePrivilege(); + List privilegeModelList = []; + List vidaPlusProjectListModel = []; + List hMCProjectListModel = []; + List projectDetailListModel = []; + + resultEither.fold( + (failure) async => await _errorHandlerService.handleError(failure: failure), + (apiResponse) async { + if (apiResponse.messageStatus == 2) { + await _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage ?? "ErrorEmpty"); + } else { + apiResponse.data["ServicePrivilegeList"].forEach((v) { + privilegeModelList.add(PrivilegeModel.fromJson(v)); + }); + _appState.setPrivilegeModelList(privilegeModelList); + + apiResponse.data["ProjectListVidaPlus"].forEach((v) { + vidaPlusProjectListModel.add(VidaPlusProjectListModel.fromJson(v)); + }); + _appState.setVidaPlusProjectList(vidaPlusProjectListModel); + + apiResponse.data["HMCProjectList"].forEach((v) { + hMCProjectListModel.add(HMCProjectListModel.fromJson(v)); + }); + _appState.setHMCProjectList(hMCProjectListModel); + + apiResponse.data["ProjectDetailList"].forEach((v) { + projectDetailListModel.add(ProjectDetailListModel.fromJson(v)); + }); + _appState.setProjectsDetailList(projectDetailListModel); + } + }, + ); + } } diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart index f8e0f59..77fec61 100644 --- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -2,6 +2,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_nfc_kit/flutter_nfc_kit.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/common_models/privilege/ProjectDetailListModel.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/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -25,8 +29,12 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { bool _supportsNFC = false; + late LocationUtils locationUtils; + ProjectDetailListModel projectDetailListModel = ProjectDetailListModel(); + @override Widget build(BuildContext context) { + AppState _appState = getIt.get(); FlutterNfcKit.nfcAvailability.then((value) { _supportsNFC = (value == NFCAvailability.available); }); @@ -38,7 +46,25 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { "Live Location".needTranslation, "Verify your location to be at hospital to check in".needTranslation, ).onPress(() { - Navigator.of(context).pop(); + // locationUtils = LocationUtils( + // isShowConfirmDialog: false, + // navigationService: myAppointmentsViewModel.navigationService, + // appState: myAppointmentsViewModel.appState, + // ); + locationUtils.getCurrentLocation(callBack: (value) { + projectDetailListModel = Utils.getProjectDetailObj(_appState, patientAppointmentHistoryResponseModel.projectID); + double dist = Utils.distance(value.latitude, value.longitude, double.parse(projectDetailListModel.latitude!), double.parse(projectDetailListModel.longitude!)).ceilToDouble() * 1000; + print(dist); + if (dist <= projectDetailListModel.geofenceRadius!) { + sendCheckInRequest(projectDetailListModel.checkInQrCode!, context); + } else { + showCommonBottomSheetWithoutHeight(context, + title: "Error".needTranslation, + child: Utils.getErrorWidget(loadingText: "Please ensure you're within the hospital location to perform online check-in.".needTranslation), callBackFunc: () { + Navigator.of(context).pop(); + }, isFullScreen: false); + } + }); }), SizedBox(height: 16.h), checkInOptionCard( diff --git a/lib/presentation/authentication/quick_login.dart b/lib/presentation/authentication/quick_login.dart index c5f540b..ff70dab 100644 --- a/lib/presentation/authentication/quick_login.dart +++ b/lib/presentation/authentication/quick_login.dart @@ -122,7 +122,7 @@ class _QuickLogin extends State { child: CustomButton( text: LocaleKeys.notNow.tr(), onPressed: () { - Navigator.pop(context, true); + Navigator.pop(context, "true"); }, backgroundColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA), diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index 55cbadf..589616f 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -34,13 +34,17 @@ class _SavedLogin extends State { void initState() { authVm = context.read(); appState = getIt.get(); - loginType = LoginTypeExtension.fromValue(appState.getSelectDeviceByImeiRespModelElement!.logInType!)!; + loginType = LoginTypeExtension.fromValue(appState.getSelectDeviceByImeiRespModelElement!.logInType!)!; authVm.phoneNumberController.text = appState.getSelectDeviceByImeiRespModelElement!.mobile!.startsWith("0") ? appState.getSelectDeviceByImeiRespModelElement!.mobile!.replaceFirst("0", "") : appState.getSelectDeviceByImeiRespModelElement!.mobile!; authVm.nationalIdController.text = appState.getSelectDeviceByImeiRespModelElement!.identificationNo!; + if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { + authVm.loginWithFingerPrintFace(); + } + super.initState(); } @@ -80,8 +84,7 @@ class _SavedLogin extends State { children: [ // Last login info - ("${LocaleKeys.lastloginBy.tr()} ${loginType.displayName}") - .toText14(isBold: true, color: AppColors.greyTextColor), + ("${LocaleKeys.lastloginBy.tr()} ${loginType.displayName}").toText14(isBold: true, color: AppColors.greyTextColor), (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y 'at' HH:mm") : '--') @@ -89,7 +92,8 @@ class _SavedLogin extends State { Container( margin: EdgeInsets.all(16.h), - child: Utils.buildSvgWithAssets(icon: getTypeIcons(appState.getSelectDeviceByImeiRespModelElement!.logInType!), height: 54, width: 54, iconColor: loginType.toInt == 4 ? null : AppColors.primaryRedColor)), + child: Utils.buildSvgWithAssets( + icon: getTypeIcons(appState.getSelectDeviceByImeiRespModelElement!.logInType!), height: 54, width: 54, iconColor: loginType.toInt == 4 ? null : AppColors.primaryRedColor)), // Face ID login button SizedBox( height: 45, @@ -232,7 +236,6 @@ class _SavedLogin extends State { textColor: Color(0xFF2E3039), borderWidth: 2, padding: EdgeInsets.fromLTRB(0, 14, 0, 14), - ), const Spacer(flex: 2), // OR divider diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index b55632b..55afffe 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -42,7 +42,7 @@ class _LandingPageState extends State { authVM.savePushTokenToAppState(); if (mounted) { authVM.checkLastLoginStatus(() { - showQuickLogin(context, false); + showQuickLogin(context, false); }); } super.initState(); @@ -326,19 +326,17 @@ class _LandingPageState extends State { context, title: "", child: QuickLogin( - isDone: isDone, + isDone: isDone, onPressed: () { // sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true); authVM.loginWithFingerPrintFace(); }, ), - height:isDone == false ? ResponsiveExtension.screenHeight * 0.5 : ResponsiveExtension.screenHeight * 0.3, + height: isDone == false ? ResponsiveExtension.screenHeight * 0.5 : ResponsiveExtension.screenHeight * 0.3, isFullScreen: false, callBackFunc: (str) { isDone = true; - setState(() { - - }); + setState(() {}); }, ); } diff --git a/lib/splashPage.dart b/lib/splashPage.dart index b852cd4..f85f01e 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -9,6 +9,8 @@ import 'package:flutter_zoom_videosdk/native/zoom_videosdk.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; + // import 'package:hmg_patient_app_new/presentation/authantication/login.dart'; import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; @@ -26,6 +28,8 @@ class SplashPage extends StatefulWidget { } class _SplashScreenState extends State { + late AuthenticationViewModel authVm; + Future initializeStuff() async { Timer( Duration(milliseconds: 500), @@ -34,7 +38,8 @@ class _SplashScreenState extends State { PushNotificationHandler().init(context); // Asyncronously }, ); - Timer(Duration(seconds: 3, milliseconds: 500), () async { + await authVm.getServicePrivilege(); + Timer(Duration(seconds: 2, milliseconds: 500), () async { LocalNotification.init(onNotificationClick: (payload) {}); Navigator.of(context).pushReplacement( FadePage( @@ -51,8 +56,6 @@ class _SplashScreenState extends State { zoom.initSdk(initConfig); } - - /// load the Privilege from service Future loadPrivilege() async { // ProjectViewModel projectProvider = Provider.of(context, listen: false); @@ -71,9 +74,9 @@ class _SplashScreenState extends State { PushNotificationHandler().init(context); // Asyncronously } - @override void initState() { + authVm = context.read(); super.initState(); initializeStuff(); } @@ -87,8 +90,7 @@ class _SplashScreenState extends State { children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 53), - child: Image.asset(AppAssets.hmg_logo, - fit: BoxFit.fitWidth, width: MediaQuery.of(context).size.width), + child: Image.asset(AppAssets.hmg_logo, fit: BoxFit.fitWidth, width: MediaQuery.of(context).size.width), ), Align( alignment: Alignment.bottomCenter, @@ -97,18 +99,12 @@ class _SplashScreenState extends State { children: [ Text( "Powered by", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: AppColors.textColor, - letterSpacing: -0.56, - height: 16 / 14), + style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.textColor, letterSpacing: -0.56, height: 16 / 14), ), SizedBox( height: 5, ), - Utils.buildSvgWithAssets( - icon: AppAssets.cloud_logo, width: 40, height: 40), + Utils.buildSvgWithAssets(icon: AppAssets.cloud_logo, width: 40, height: 40), SizedBox(height: 7), // Text( // "Version 1.1.0",