diff --git a/assets/images/svg/logout.svg b/assets/images/svg/logout.svg new file mode 100644 index 0000000..b52e142 --- /dev/null +++ b/assets/images/svg/logout.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index a603cf9..bad2e4c 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 @@ -802,9 +802,9 @@ 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 final String registerUser = 'Services/Authentication.svc/REST/PatientRegistration'; + static final String getPatientMobileData = '/Services/Authentication.svc/REST/GetMobileLoginInfo'; + static final String getPrivileges = 'Services/Patients.svc/REST/Service_Privilege'; + static final String registerUser = 'Services/Authentication.svc/REST/PatientRegistration'; // static values for Api static final double appVersionID = 18.7; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index a2cbc49..d0acea0 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -93,6 +93,7 @@ class AppAssets { static const String checkin_location_icon = '$svgBasePath/checkin_location_icon.svg'; static const String checkin_nfc_icon = '$svgBasePath/checkin_nfc_icon.svg'; static const String checkin_qr_icon = '$svgBasePath/checkin_qr_icon.svg'; + static const String logout = '$svgBasePath/logout.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/extensions/widget_extensions.dart b/lib/extensions/widget_extensions.dart index f576d0c..1136c32 100644 --- a/lib/extensions/widget_extensions.dart +++ b/lib/extensions/widget_extensions.dart @@ -39,13 +39,15 @@ extension WidgetExtensions on Widget { child: this, ); - Widget toShimmer2({bool isShow = true, double radius = 20}) => isShow + Widget toShimmer2({bool isShow = true, double radius = 20, double? width, double? height}) => isShow ? Shimmer.fromColors( baseColor: const Color(0xffe8eff0), highlightColor: Colors.white, child: ClipRRect( borderRadius: BorderRadius.circular(radius), child: Container( + width: width, + height: height, color: Colors.white, child: this, ), diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 22ca90f..f3f4119 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -37,6 +37,8 @@ abstract class AuthenticationRepo { Future>> insertPatientIMEIData({required dynamic patientIMEIDataRequest}); Future>> insertPatientDeviceData({required dynamic patientDeviceDataRequest}); + + Future>> getPatientDeviceData({required dynamic patientDeviceDataRequest}); } class AuthenticationRepoImp implements AuthenticationRepo { @@ -490,4 +492,39 @@ class AuthenticationRepoImp implements AuthenticationRepo { return Future.value(Left(UnknownFailure(e.toString()))); } } + + @override + Future> getPatientDeviceData({required patientDeviceDataRequest}) { + try { + GenericApiModel? apiResponse; + Failure? failure; + return apiClient.post( + ApiConsts.getPatientMobileData, + 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 0cf38f5..95f821f 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -31,8 +31,10 @@ import 'package:hmg_patient_app_new/services/dialog_service.dart'; 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 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/bottomsheet/exception_bottom_sheet.dart'; +import 'models/request_models/get_user_mobile_device_data.dart'; import 'models/request_models/insert_patient_mobile_deviceinfo.dart'; import 'models/request_models/patient_insert_device_imei_request.dart'; @@ -80,10 +82,11 @@ class AuthenticationViewModel extends ChangeNotifier { String errorMsg = ''; final FocusNode myFocusNode = FocusNode(); var healthId; + int getDeviceLastLogin =1; Future onLoginPressed() async { try { - LoadingUtils.showFullScreenLoader(); + LoaderBottomSheet.showLoader(); //TODO: We will remove this delay // await Future.delayed(Duration(seconds: 3)); var data = _appState.getSelectDeviceByImeiRespModelElement; @@ -95,7 +98,7 @@ class AuthenticationViewModel extends ChangeNotifier { } } catch (e) { log("Error in onLoginPressed: $e"); - LoadingUtils.hideFullScreenLoader(); + LoaderBottomSheet.hideLoader(); _dialogService.showErrorBottomSheet(message: "An unexpected error occurred. Please try again.", onOkPressed: () {}); } } @@ -192,7 +195,7 @@ class AuthenticationViewModel extends ChangeNotifier { (failure) async { // LoadingUtils.hideFullScreenLoader(); // await _errorHandlerService.handleError(failure: failure); - LoadingUtils.hideFullScreenLoader(); + LoaderBottomSheet.hideLoader(); _navigationService.pushPage(page: LoginScreen()); }, (apiResponse) { @@ -211,7 +214,7 @@ class AuthenticationViewModel extends ChangeNotifier { Future _handleExistingImeiData(dynamic data) async { try { SelectDeviceByImeiRespModelElement? savedData = _appState.getSelectDeviceByImeiRespModelElement; - LoadingUtils.hideFullScreenLoader(); + LoaderBottomSheet.hideLoader(); if (savedData != null) { // TODO: Navigate to SavedLogin when available @@ -220,7 +223,7 @@ class AuthenticationViewModel extends ChangeNotifier { } } catch (e) { log("Error handling existing IMEI data: $e"); - LoadingUtils.hideFullScreenLoader(); + LoaderBottomSheet.hideLoader(); _navigationService.pushPage(page: LoginScreen()); } } @@ -231,7 +234,7 @@ class AuthenticationViewModel extends ChangeNotifier { if (respData != null) { dynamic data = SelectDeviceByImeiRespModelElement.fromJson(respData.toJson()); _appState.setSelectDeviceByImeiRespModelElement(data); - LoadingUtils.hideFullScreenLoader(); + LoaderBottomSheet.hideLoader(); // TODO: Navigate to SavedLogin when available // SelectDeviceByImeiRespModelElement savedData = @@ -239,16 +242,17 @@ class AuthenticationViewModel extends ChangeNotifier { _navigationService.pushPage(page: SavedLogin()); // _navigationService.pushPage(page: LoginScreen()); } else { - LoadingUtils.hideFullScreenLoader(); + print("print login........"); + LoaderBottomSheet.hideLoader(); _navigationService.pushPage(page: LoginScreen()); } } catch (e) { log("Error processing IMEI registration response: $e"); - LoadingUtils.hideFullScreenLoader(); + LoaderBottomSheet.hideLoader(); _navigationService.pushPage(page: LoginScreen()); } }, onError: (String error) { - LoadingUtils.hideFullScreenLoader(); + LoaderBottomSheet.hideLoader(); _dialogService.showErrorBottomSheet(message: error, onOkPressed: () {}); }); } @@ -269,8 +273,8 @@ class AuthenticationViewModel extends ChangeNotifier { if (!isValidated) { return; } - - LoadingUtils.showFullScreenLoader(); + LoaderBottomSheet.showLoader(); + // LoadingUtils.showFullScreenLoader(); dynamic checkPatientAuthenticationReq = RequestUtils.getPatientAuthenticationRequest( phoneNumber: phoneNumberController.text, @@ -283,20 +287,22 @@ class AuthenticationViewModel extends ChangeNotifier { calenderType: calenderType); final result = await _authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq); - LoadingUtils.hideFullScreenLoader(); + result.fold( (failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.messageStatus == 2) { + LoaderBottomSheet.hideLoader(); await _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage ?? "ErrorEmpty", onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { if (apiResponse.data['isSMSSent']) { _appState.setAppAuthToken = apiResponse.data['LogInTokenID']; - sendActivationCode( + await sendActivationCode( otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumberController.text, nationalIdOrFileNumber: nationalIdController.text, ); + } else { if (apiResponse.data['IsAuthenticated']) { await checkActivationCode( @@ -304,6 +310,7 @@ class AuthenticationViewModel extends ChangeNotifier { onWrongActivationCode: (String? message) {}, activationCode: null, //todo silent login case halded on the repo itself.. ); + } } } @@ -338,9 +345,11 @@ class AuthenticationViewModel extends ChangeNotifier { (failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.messageStatus == 2) { + LoaderBottomSheet.hideLoader(); await _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage ?? "ErrorEmpty"); } else { if (apiResponse.data != null && apiResponse.data['isSMSSent'] == true) { + LoaderBottomSheet.hideLoader(); navigateToOTPScreen(otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumber); } else { // TODO: Handle isSMSSent false @@ -375,8 +384,8 @@ class AuthenticationViewModel extends ChangeNotifier { countryCode: selectedCountrySignup.countryCode, loginType: loginTypeEnum.toInt) .toJson(); - - bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == 1) ? true : false; + LoaderBottomSheet.showLoader(); + bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true); if (isForRegister) { if (_appState.getUserRegistrationPayload.patientOutSa == 0) request['DOB'] = _appState.getUserRegistrationPayload.dob; request['HealthId'] = _appState.getUserRegistrationPayload.healthId; @@ -394,6 +403,8 @@ class AuthenticationViewModel extends ChangeNotifier { final resultEither = await _authenticationRepo.checkActivationCodeRepo(newRequest: request, activationCode: activationCode.toString(), isRegister: true); + LoaderBottomSheet.hideLoader(); + resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) { final activation = CheckActivationCode.fromJson(apiResponse.data as Map); if (_appState.getUserRegistrationPayload.isRegister == true) { @@ -413,19 +424,26 @@ class AuthenticationViewModel extends ChangeNotifier { resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { final activation = CheckActivationCode.fromJson(apiResponse.data as Map); + + if (activation.errorCode == '699') { // Todo: Hide Loader // GifLoaderDialogUtils.hideDialog(context); + LoaderBottomSheet.hideLoader(); onWrongActivationCode(activation.errorEndUserMessage); + return; } else if (activation.messageStatus == 2) { + LoaderBottomSheet.hideLoader(); onWrongActivationCode(activation.errorEndUserMessage); return; } else if (_appState.getUserRegistrationPayload.isRegister == true) { + LoaderBottomSheet.hideLoader(); _navigationService.pushAndReplace(AppRoutes.registerStepTwo); // Navigator.popUntil(context, (route) => Utils.route(route, equalsTo: RegisterNew)); return; } else { + if (activation.list != null && activation.list!.isNotEmpty) { _appState.setAuthenticatedUser(activation.list!.first); } @@ -433,6 +451,12 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setAppAuthToken = activation.authenticationTokenId; final request = RequestUtils.getAuthanticatedCommonRequest().toJson(); bool isUserAgreedBefore = await checkIfUserAgreedBefore(request: request); + + //updating the last login type in app state to show the fingerprint/face id option on home screen + if( _appState.getSelectDeviceByImeiRespModelElement !=null) { + _appState.getSelectDeviceByImeiRespModelElement!.logInType = loginTypeEnum.toInt; + } + LoaderBottomSheet.hideLoader(); insertPatientIMEIData(loginTypeEnum.toInt); clearDefaultInputValues(); if (isUserAgreedBefore) { @@ -519,20 +543,22 @@ class AuthenticationViewModel extends ChangeNotifier { await _dialogService.showErrorBottomSheet(message: message ?? "Something went wrong. ", onOkPressed: () {}); } - loginWithFingerPrintFace() async { + loginWithFingerPrintFace(Function success) async { _localAuthService.authenticate().then((value) async { if (value) { - // we have to handle this if verification true; + LoaderBottomSheet.showLoader(); + success(); + loginTypeEnum = (_appState.deviceTypeID == 1 ? LoginTypeEnum.face : LoginTypeEnum.fingerprint); 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)); + //commenting this api to check either the same flow working or not because this api does not needed further if work fine we will remove this + // await getPatientDeviceData(loginTypeEnum.toInt); + await checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: null, onWrongActivationCode: (String? message) {}); + await insertPatientIMEIData(loginTypeEnum.toInt); } else { // authenticated = true; - insertPatientIMEIData((_appState.deviceTypeID == 1 ? LoginTypeEnum.face.toInt : LoginTypeEnum.fingerprint.toInt)); + await insertPatientIMEIData(loginTypeEnum.toInt); } - + LoaderBottomSheet.hideLoader(); notifyListeners(); // navigateToHomeScreen(); } else { @@ -740,6 +766,7 @@ class AuthenticationViewModel extends ChangeNotifier { deviceTypeId: _appState.getDeviceTypeID(), patientId: _appState.getAuthenticatedUser()!.patientId!, patientIdentificationNo: _appState.getAuthenticatedUser()!.patientIdentificationNo!, + identificationNo: _appState.getAuthenticatedUser()!.patientIdentificationNo!, firstName: _appState.getAuthenticatedUser()!.firstName!, lastName: _appState.getAuthenticatedUser()!.lastName!, patientTypeId: _appState.getAuthenticatedUser()!.patientType, @@ -786,6 +813,33 @@ class AuthenticationViewModel extends ChangeNotifier { log("Insert IMEI Failed"); } }); + + } + + + Future getPatientDeviceData(int loginType) async { + final resultEither = await _authenticationRepo.getPatientDeviceData( + patientDeviceDataRequest: GetUserMobileDeviceData( + deviceToken: _appState.deviceToken, + deviceTypeId: _appState.getDeviceTypeID(), + patientId: _appState.getSelectDeviceByImeiRespModelElement!.patientId!, + patientType: _appState.getSelectDeviceByImeiRespModelElement!.patientType, + patientOutSa:_appState.getSelectDeviceByImeiRespModelElement!.outSa == true ?1 :0, + loginType: loginType, + languageId: _appState.getLanguageID(), + latitude: _appState.userLat, + longitude: _appState.userLong, + mobileNo:_appState.getSelectDeviceByImeiRespModelElement!.mobile! , + patientMobileNumber:int.parse(_appState.getSelectDeviceByImeiRespModelElement!.mobile!), + nationalId:_appState.getSelectDeviceByImeiRespModelElement!.identificationNo) + .toJson()); + resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { + if (apiResponse.messageStatus == 1) { + dynamic deviceInfo= apiResponse.data['List_MobileLoginInfo']; + getDeviceLastLogin = deviceInfo['LoginType']; + } + }); + } @override diff --git a/lib/features/authentication/models/request_models/get_user_mobile_device_data.dart b/lib/features/authentication/models/request_models/get_user_mobile_device_data.dart new file mode 100644 index 0000000..270f3ac --- /dev/null +++ b/lib/features/authentication/models/request_models/get_user_mobile_device_data.dart @@ -0,0 +1,113 @@ +import 'dart:convert'; + +class GetUserMobileDeviceData { + int? patientMobileNumber; + String? mobileNo; + String? deviceToken; + bool? projectOutSa; + int? loginType; + String? zipCode; + bool? isRegister; + String? logInTokenId; + int? searchType; + int? patientId; + String? nationalId; + String? patientIdentificationId; + int? otpSendType; + int? languageId; + double? versionId; + int? channel; + String? ipAdress; + String? generalid; + int? patientOutSa; + bool? isDentalAllowedBackend; + int? deviceTypeId; + double? latitude; + double? longitude; + int? patientType; + + GetUserMobileDeviceData({ + this.patientMobileNumber, + this.mobileNo, + this.deviceToken, + this.projectOutSa, + this.loginType, + this.zipCode, + this.isRegister, + this.logInTokenId, + this.searchType, + this.patientId, + this.nationalId, + this.patientIdentificationId, + this.otpSendType, + this.languageId, + this.versionId, + this.channel, + this.ipAdress, + this.generalid, + this.patientOutSa, + this.isDentalAllowedBackend, + this.deviceTypeId, + this.latitude, + this.longitude, + this.patientType, + }); + + factory GetUserMobileDeviceData.fromRawJson(String str) => GetUserMobileDeviceData.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory GetUserMobileDeviceData.fromJson(Map json) => GetUserMobileDeviceData( + patientMobileNumber: json["PatientMobileNumber"], + mobileNo: json["MobileNo"], + deviceToken: json["DeviceToken"], + projectOutSa: json["ProjectOutSA"], + loginType: json["LoginType"], + zipCode: json["ZipCode"], + isRegister: json["isRegister"], + logInTokenId: json["LogInTokenID"], + searchType: json["SearchType"], + patientId: json["PatientID"], + nationalId: json["NationalID"], + patientIdentificationId: json["PatientIdentificationID"], + otpSendType: json["OTP_SendType"], + languageId: json["LanguageID"], + versionId: json["VersionID"]?.toDouble(), + channel: json["Channel"], + ipAdress: json["IPAdress"], + generalid: json["generalid"], + patientOutSa: json["PatientOutSA"], + isDentalAllowedBackend: json["isDentalAllowedBackend"], + deviceTypeId: json["DeviceTypeID"], + latitude: json["Latitude"], + longitude: json["Longitude"], + patientType: json["PatientType"], + ); + + Map toJson() => { + "PatientMobileNumber": patientMobileNumber, + "MobileNo": mobileNo, + "DeviceToken": deviceToken, + "ProjectOutSA": projectOutSa, + "LoginType": loginType, + "ZipCode": zipCode, + "isRegister": isRegister, + "LogInTokenID": logInTokenId, + "SearchType": searchType, + "PatientID": patientId, + "NationalID": nationalId, + "PatientIdentificationID": patientIdentificationId, + "OTP_SendType": otpSendType, + "LanguageID": languageId, + "VersionID": versionId, + "Channel": channel, + "IPAdress": ipAdress, + "generalid": generalid, + "PatientOutSA": patientOutSa, + "isDentalAllowedBackend": isDentalAllowedBackend, + "DeviceTypeID": deviceTypeId, + "Latitude": latitude, + "Longitude": longitude, + "PatientType": patientType, + }; +} diff --git a/lib/presentation/authentication/quick_login.dart b/lib/presentation/authentication/quick_login.dart index ff70dab..a370fb4 100644 --- a/lib/presentation/authentication/quick_login.dart +++ b/lib/presentation/authentication/quick_login.dart @@ -2,9 +2,11 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -22,6 +24,7 @@ class _QuickLogin extends State { @override Widget build(BuildContext context) { + NavigationService navigationService = getIt.get(); return Container( decoration: const BoxDecoration( color: Colors.white, @@ -43,7 +46,7 @@ class _QuickLogin extends State { children: [ InkWell( onTap: () { - Navigator.pop(context, true); + navigationService.pop(); }, child: Utils.buildSvgWithAssets(icon: AppAssets.cross_circle)), ], @@ -101,9 +104,6 @@ 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 589616f..edfc1e7 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -42,7 +42,7 @@ class _SavedLogin extends State { authVm.nationalIdController.text = appState.getSelectDeviceByImeiRespModelElement!.identificationNo!; if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { - authVm.loginWithFingerPrintFace(); + authVm.loginWithFingerPrintFace((){}); } super.initState(); @@ -101,7 +101,7 @@ class _SavedLogin extends State { text: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}", onPressed: () { if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { - authVm.loginWithFingerPrintFace(); + authVm.loginWithFingerPrintFace((){}); } else { // int? val = loginType.toInt; authVm.checkUserAuthentication(otpTypeEnum: loginType == LoginTypeEnum.sms ? OTPTypeEnum.sms : OTPTypeEnum.whatsapp); @@ -114,7 +114,8 @@ class _SavedLogin extends State { fontWeight: FontWeight.w500, borderRadius: 12, padding: EdgeInsets.fromLTRB(0, 10, 0, 10), - icon: AppAssets.sms, + icon: getTypeIcons(loginType.toInt), //loginType == LoginTypeEnum.sms ? AppAssets.sms :AppAssets.whatsapp, + iconColor: loginType != LoginTypeEnum.whatsapp ? Colors.white: null , ), ), ], @@ -210,7 +211,8 @@ class _SavedLogin extends State { textColor: AppColors.textColor, borderWidth: 2, padding: EdgeInsets.fromLTRB(0, 14, 0, 14), - icon: AppAssets.password_validation, + icon: AppAssets.sms, + iconColor: AppColors.textColor, ) : Container(), SizedBox( @@ -224,7 +226,7 @@ class _SavedLogin extends State { iconColor: null, onPressed: () { if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { - authVm.loginWithFingerPrintFace(); + authVm.loginWithFingerPrintFace((){}); } else { loginType = LoginTypeEnum.whatsapp; int? val = loginType.toInt; diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 55afffe..58aff04 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -23,6 +23,7 @@ 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/custom_tab_bar.dart' show CustomTabBar; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; @@ -35,14 +36,14 @@ class LandingPage extends StatefulWidget { class _LandingPageState extends State { late final AuthenticationViewModel authVM; - + bool isDone = false; @override void initState() { authVM = context.read(); authVM.savePushTokenToAppState(); if (mounted) { authVM.checkLastLoginStatus(() { - showQuickLogin(context, false); + showQuickLogin(context); }); } super.initState(); @@ -321,20 +322,33 @@ class _LandingPageState extends State { ); } - void showQuickLogin(BuildContext context, bool isDone) { - showCommonBottomSheet( + void showQuickLogin(BuildContext context) { + showCommonBottomSheetWithoutHeight( + context, title: "", - child: QuickLogin( + isCloseButtonVisible: false, + child: + StatefulBuilder( + builder: (context, setState) { + return QuickLogin( isDone: isDone, onPressed: () { // sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true); - authVM.loginWithFingerPrintFace(); + authVM.loginWithFingerPrintFace((){ + + isDone = true; + setState(() { + + }); + }); + }, - ), - 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) { + callBackFunc: () { isDone = true; setState(() {}); }, diff --git a/lib/presentation/lab/collapsing_list_view.dart b/lib/presentation/lab/collapsing_list_view.dart new file mode 100644 index 0000000..c7f1540 --- /dev/null +++ b/lib/presentation/lab/collapsing_list_view.dart @@ -0,0 +1,149 @@ +import 'dart:ui'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/utils/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/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; + +class CollapsingListView extends StatelessWidget { + final String title; + Widget? child; + VoidCallback? search; + VoidCallback? report; + VoidCallback? logout; + VoidCallback? history; + Widget? bottomChild; + bool isClose; + + CollapsingListView({required this.title, this.child, this.search, this.isClose = false, this.bottomChild, this.report, this.logout, this.history}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + CustomScrollView( + slivers: [ + SliverAppBar( + pinned: true, + expandedHeight: 100, + stretch: true, + systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light), + surfaceTintColor: Colors.transparent, + backgroundColor: AppColors.bgScaffoldColor, + leading: IconButton( + icon: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.closeBottomNav : AppAssets.arrow_back, width: 32.h, height: 32.h), + padding: EdgeInsets.only(left: 12), + onPressed: () => Navigator.pop(context), + ), + flexibleSpace: LayoutBuilder( + builder: (context, constraints) { + final double maxHeight = 100; + final double minHeight = kToolbarHeight; + double t = (constraints.maxHeight - minHeight) / (maxHeight - minHeight); + t = t - 1; + if (t < 0.7) t = 0.7; + t = t.clamp(0.0, 1.0); + + final double fontSize = lerpDouble(14, 18, t)!; + final double bottomPadding = lerpDouble(0, 0, t)!; + final double leftPadding = lerpDouble(150, 24, t)!; + + return Stack( + children: [ + Align( + alignment: Alignment.lerp( + Alignment.center, + Alignment.bottomLeft, + t, + )!, + child: Padding( + padding: EdgeInsets.only(left: leftPadding, bottom: bottomPadding), + child: Row( + spacing: 4.h, + children: [ + Text( + title, + maxLines: 1, + style: TextStyle( + fontSize: (27 - (5 * (2 - t))).fSize, + fontWeight: FontWeight.lerp( + FontWeight.w300, + FontWeight.w600, + t, + )!, + color: AppColors.blackColor, + letterSpacing: -0.5), + ).expanded, + if (logout != null) actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(logout!), + if (report != null) actionButton(context, t, title: "Report".needTranslation, icon: AppAssets.report_icon).onPress(report!), + if (history != null) actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon).onPress(history!), + if (search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(search!).paddingOnly(right: 24) + ], + )), + ), + ], + ); + }, + ), + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => child, + childCount: 1, + ), + ), + ], + ).expanded, + if (bottomChild != null) bottomChild! + ], + ), + ); + } + + Widget actionButton(BuildContext context, double t, {required String title, required String icon}) { + return AnimatedSize( + duration: Duration(milliseconds: 150), + child: Container( + height: 40, + padding: EdgeInsets.all(8), + margin: EdgeInsets.only(right: 24), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.secondaryLightRedColor, + borderRadius: 12, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + spacing: 8.h, + children: [ + Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.primaryRedColor), + if (t == 1) + Text( + title, + style: context.dynamicTextStyle( + color: AppColors.primaryRedColor, + letterSpacing: -0.4, + fontSize: (14 - (2 * (1 - t))).fSize, + fontWeight: FontWeight.lerp( + FontWeight.w300, + FontWeight.w500, + t, + )!, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 4705a2a..2c6a131 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1,26 +1,21 @@ 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/enums.dart'; -import 'package:hmg_patient_app_new/core/utils/date_util.dart'; -import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; -import 'package:hmg_patient_app_new/core/utils/utils.dart'; -import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; -import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; -import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; -import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:provider/provider.dart'; +import 'collapsing_list_view.dart'; + class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @@ -32,7 +27,8 @@ class _LabOrdersPageState extends State { late LabViewModel labProvider; List?> labSuggestions = []; int? expandedIndex; - String? selectedFilterText=''; + String? selectedFilterText = ''; + @override void initState() { scheduleMicrotask(() { @@ -45,214 +41,75 @@ class _LabOrdersPageState extends State { Widget build(BuildContext context) { labProvider = Provider.of(context); return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - appBar: AppBar( - title: LocaleKeys.labResults.tr(context: context).toText18(), backgroundColor: AppColors.bgScaffoldColor, - ), - body: Padding( - padding: EdgeInsets.all(24.h), - child: SingleChildScrollView( - child: Consumer( - builder: (context, model, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.labResults.tr(context: context).toText24(isBold: true), - Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(() { - if (model.isLabOrdersLoading) { - return; - }else { - - showCommonBottomSheet(context, child: SearchLabResultsContent(labSuggestionsList: model.labSuggestions), - callBackFunc: (value) { - selectedFilterText = value; - model.filterLabReports(value!); - }, - title: LocaleKeys.searchLabReport.tr(), - height: ResponsiveExtension.screenHeight, - isFullScreen: true, - isCloseButtonVisible: true); - } - }), - ], - ), - SizedBox(height: 16.h), - // Build Tab Bar - SizedBox(height: 16.h), - // Expandable list - - selectedFilterText!.isNotEmpty ? CustomChipWidget(chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), - ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.length, - itemBuilder: (context, index) { - final isExpanded = expandedIndex == index; - return model.isLabOrdersLoading - ? const MoviesShimmerWidget() - : AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - margin: EdgeInsets.symmetric(vertical: 8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), - child: InkWell( - onTap: () { - setState(() { - expandedIndex = isExpanded ? null : index; - }); - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CustomButton( - text: getLabOrderStatusText(model.patientLabOrders[index].status!), - onPressed: () {}, - backgroundColor: getLabOrderStatusColor(model.patientLabOrders[index].status!).withOpacity(0.15), - borderColor: getLabOrderStatusColor(model.patientLabOrders[index].status!).withOpacity(0.01), - textColor: getLabOrderStatusColor(model.patientLabOrders[index].status!), - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - Icon(isExpanded ? Icons.expand_less : Icons.expand_more), - ], - ), - SizedBox(height: 8.h), - Row( - children: [ - Image.network( - model.patientLabOrders[index].doctorImageURL!, - width: 24.h, - height: 24.h, - fit: BoxFit.fill, - ).circle(100), - SizedBox(width: 4.h), - model.patientLabOrders[index].doctorName!.toText16(isBold: true) - ], - ), - SizedBox(height: 8.h), - Row( - children: [ - CustomButton( - text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(model.patientLabOrders[index].createdOn), false), - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, - ), - SizedBox(width: 8.h), - CustomButton( - text: model.patientLabOrders[index].clinicDescription!, - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, - ), - ], - ), - ], - ), - ), - AnimatedSwitcher( - duration: Duration(milliseconds: 300), - switchInCurve: Curves.easeIn, - switchOutCurve: Curves.easeOut, - transitionBuilder: (Widget child, Animation animation) { - return FadeTransition( - opacity: animation, - child: SizeTransition( - sizeFactor: animation, - axisAlignment: 0.0, - child: child, - ), - ); - }, - child: isExpanded - ? Container( - key: ValueKey(index), - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...model.patientLabOrders[index].testDetails!.map((detail) { - return Padding( - padding: EdgeInsets.only(bottom: 8.h), - child: '● ${detail.description}'.toText14(weight: FontWeight.w500), - ); - }).toList(), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(), - CustomButton( - icon: AppAssets.view_report_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - text: LocaleKeys.viewReport.tr(context: context), - onPressed: () {}, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.bold, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - ], - ), - ], - ), - ) - : SizedBox.shrink(key: ValueKey(-index)), - ), - ], - ), - ), - ), + body: CollapsingListView( + title: LocaleKeys.labResults.tr(), + search: () async { + final lavVM = Provider.of(context, listen: false); + if (lavVM.isLabOrdersLoading) { + return; + } else { + String? value = await Navigator.of(context).push(CupertinoPageRoute(fullscreenDialog: true, builder: (context) => SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions))); + if (value != null) { + selectedFilterText = value; + lavVM.filterLabReports(value); + } + } + }, + child: SingleChildScrollView( + padding: EdgeInsets.all(24), + physics: NeverScrollableScrollPhysics(), + child: Consumer( + builder: (context, model, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + selectedFilterText!.isNotEmpty + ? CustomChipWidget( + chipText: selectedFilterText!, + chipType: ChipTypeEnum.alert, + isSelected: true, + ) + : SizedBox(), + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.length, + itemBuilder: (context, index) { + final isExpanded = expandedIndex == index; + return model.isLabOrdersLoading + ? LabResultItemView( + onTap: () {}, + labOrder: null, + index: index, + isLoading: true, + ) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: LabResultItemView( + onTap: () { + setState(() { + expandedIndex = isExpanded ? null : index; + }); + }, + labOrder: model.patientLabOrders[index], + index: index, + isExpanded: isExpanded)), ), - ), - ); - }, - ), - ], - ); - }, + ); + }, + ), + ], + ); + }, + ), ), - ), - ), - ); + )); } Color getLabOrderStatusColor(num status) { diff --git a/lib/presentation/lab/lab_result_item_view.dart b/lib/presentation/lab/lab_result_item_view.dart new file mode 100644 index 0000000..03f721e --- /dev/null +++ b/lib/presentation/lab/lab_result_item_view.dart @@ -0,0 +1,172 @@ +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_export.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/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'; + +class LabResultItemView extends StatelessWidget { + final VoidCallback onTap; + final int index; + final PatientLabOrdersResponseModel? labOrder; + final bool isLoading; + final bool isExpanded; + + LabResultItemView({Key? key, required this.onTap, this.labOrder, required this.index, this.isLoading = false, this.isExpanded = false}) : super(key: key); + + @override + Widget build(BuildContext context) { + return AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), + child: InkWell( + onTap: () { + if (!isLoading) { + onTap(); + } + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + AppCustomChipWidget( + labelText: getLabOrderStatusText(labOrder?.status ?? 0, context), + backgroundColor: getLabOrderStatusColor(labOrder?.status ?? 0).withOpacity(0.15), + textColor: getLabOrderStatusColor(labOrder?.status ?? 0), + ).toShimmer2(isShow: isLoading, width: 100), + if (!isLoading) Icon(isExpanded ? Icons.expand_less : Icons.expand_more), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Image.network( + isLoading ? "" : labOrder!.doctorImageURL!, + width: 24.h, + height: 24.h, + fit: BoxFit.fill, + errorBuilder: (cxt, child, tr) { + return SizedBox(height: 24, width: 24); + }, + ).toShimmer2(isShow: isLoading).circle(100), + SizedBox(width: 4.h), + (labOrder?.doctorName ?? "").toText16(isBold: true).toShimmer2(isShow: isLoading, width: 200) + ], + ), + SizedBox(height: 8.h), + Wrap( + spacing: 8.h, + runSpacing: 0.h, + children: [ + AppCustomChipWidget(labelText: isLoading ? "null" : DateUtil.formatDateToDate(DateUtil.convertStringToDate(labOrder!.createdOn), false)).toShimmer2(isShow: isLoading, width: 70), + AppCustomChipWidget(labelText: isLoading ? "null" : labOrder!.clinicDescription!).toShimmer2(isShow: isLoading, width: 100), + ], + ), + ], + ), + ), + AnimatedSwitcher( + duration: Duration(milliseconds: 300), + switchInCurve: Curves.easeIn, + switchOutCurve: Curves.easeOut, + transitionBuilder: (Widget child, Animation animation) { + return FadeTransition( + opacity: animation, + child: SizeTransition( + sizeFactor: animation, + axisAlignment: 0.0, + child: child, + ), + ); + }, + child: isExpanded + ? Container( + key: ValueKey(index), + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...labOrder!.testDetails!.map((detail) { + return Padding( + padding: EdgeInsets.only(bottom: 8.h), + child: '● ${detail.description}'.toText14(weight: FontWeight.w500), + ); + }).toList(), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(), + CustomButton( + icon: AppAssets.view_report_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + text: LocaleKeys.viewReport.tr(context: context), + onPressed: () {}, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.bold, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ), + ], + ), + ) + : SizedBox.shrink(key: ValueKey(-index)), + ), + ], + ), + ), + ); + } + + String getLabOrderStatusText(num status, context) { + switch (status) { + case 44: + return LocaleKeys.resultsPending.tr(context: context); + case 45: + return LocaleKeys.resultsPending.tr(context: context); + case 16: + return LocaleKeys.resultsAvailable.tr(context: context); + case 17: + return LocaleKeys.resultsAvailable.tr(context: context); + default: + return ""; + } + } + + Color getLabOrderStatusColor(num status) { + switch (status) { + case 44: + return AppColors.warningColorYellow; + case 45: + return AppColors.warningColorYellow; + case 16: + return AppColors.successColor; + case 17: + return AppColors.successColor; + default: + return AppColors.greyColor; + } + } +} diff --git a/lib/presentation/lab/search_lab_report.dart b/lib/presentation/lab/search_lab_report.dart index e5b52e0..1b47a55 100644 --- a/lib/presentation/lab/search_lab_report.dart +++ b/lib/presentation/lab/search_lab_report.dart @@ -4,6 +4,7 @@ import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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/input_widget.dart'; @@ -48,74 +49,75 @@ class _SearchLabResultsContentState extends State { }); } else { setState(() { - filteredSuggestions = widget.labSuggestionsList - .where((suggestion) => suggestion.toLowerCase().contains(query)) - .toList(); + filteredSuggestions = widget.labSuggestionsList.where((suggestion) => suggestion.toLowerCase().contains(query)).toList(); }); } } @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextInputWidget( - labelText: "Search lab results", - hintText: "Type test name", - controller: searchEditingController, - isEnable: true, - prefix: null, - autoFocus: false, - isBorderAllowed: false, - keyboardType: TextInputType.text, - padding: EdgeInsets.symmetric( - vertical: ResponsiveExtension(10).h, - horizontal: ResponsiveExtension(15).h, + return CollapsingListView( + title: LocaleKeys.labResults.tr(), + isClose: true, + bottomChild: Container( + color: Colors.white, + padding: EdgeInsets.all(ResponsiveExtension(20).h), + child: CustomButton( + text: LocaleKeys.search.tr(), + icon: AppAssets.search_icon, + iconColor: Colors.white, + onPressed: () => Navigator.pop(context, searchEditingController.text), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 24, right: 24, top: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextInputWidget( + labelText: "Search lab results", + hintText: "Type test name", + controller: searchEditingController, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), ), - ), - SizedBox(height: ResponsiveExtension(20).h), - if (filteredSuggestions.isNotEmpty) ...[ - "Suggestions".toText16(isBold: true), - const SizedBox(height: 12), + SizedBox(height: ResponsiveExtension(20).h), + if (filteredSuggestions.isNotEmpty) ...[ + "Suggestions".toText16(isBold: true), + ], ], - ], + ), ), - ), - Expanded( - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 16), + SingleChildScrollView( + physics: NeverScrollableScrollPhysics(), + padding: const EdgeInsets.only(left: 24, right: 24, bottom: 24, top: 16), child: Wrap( alignment: WrapAlignment.start, spacing: 10, runSpacing: 10, children: filteredSuggestions .map((label) => SuggestionChip( - label: label, - onTap: () { - searchEditingController.text = label; - }, - )) + label: label, + onTap: () { + searchEditingController.text = label; + }, + )) .toList(), ), ), - ), - Container( - color: Colors.white, - padding: EdgeInsets.all(ResponsiveExtension(20).h), - child: CustomButton( - text: LocaleKeys.search.tr(), - icon: AppAssets.search_icon, - iconColor: Colors.white, - onPressed: () => Navigator.pop(context, searchEditingController.text), - ), - ), - ], + ], + ), ); } } @@ -141,10 +143,6 @@ class SuggestionChip extends StatelessWidget { decoration: BoxDecoration( color: isSelected ? AppColors.primaryRedColor : AppColors.whiteColor, borderRadius: BorderRadius.circular(8), - border: Border.all( - color: AppColors.greyColor, - width: 1, - ), ), child: label.toText12( color: isSelected ? Colors.white : Colors.black87, @@ -153,4 +151,4 @@ class SuggestionChip extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart index 45d2ddf..ed103e5 100644 --- a/lib/theme/app_theme.dart +++ b/lib/theme/app_theme.dart @@ -47,6 +47,7 @@ class AppTheme { color: Colors.grey[800], ), systemOverlayStyle: SystemUiOverlayStyle.light, + surfaceTintColor: Colors.transparent, ), ); } diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index ee5dfc4..203cb86 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -44,10 +44,12 @@ class AppCustomChipWidget extends StatelessWidget { avatar: icon.isNotEmpty ? Utils.buildSvgWithAssets(icon: icon, width: iconSize.h, height: iconSize.h, iconColor: iconColor) : SizedBox.shrink(), label: labelText!.toText10(weight: FontWeight.w500, letterSpacing: -0.64, color: textColor), padding: EdgeInsets.all(0.0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, labelPadding: EdgeInsets.only(left: -4.h, right: 8.h), backgroundColor: backgroundColor, ) : Chip( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, label: labelText!.toText10(weight: FontWeight.w500, letterSpacing: -0.64, color: textColor), padding: EdgeInsets.all(0.0), backgroundColor: backgroundColor, diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index f156fe4..cc0c03b 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -126,7 +126,7 @@ void showCommonBottomSheetWithoutHeight( top: false, left: false, right: false, - child: Container( + child:isCloseButtonVisible ? Container( padding: EdgeInsets.only(left: 24, top: 24, right: 24, bottom: 12), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.bottomSheetBgColor, borderRadius: 24.h), child: Column( @@ -144,7 +144,7 @@ void showCommonBottomSheetWithoutHeight( ), child, ], - )), + )) : child, ); }).then((value) { callBackFunc(); diff --git a/lib/widgets/loader/bottomsheet_loader.dart b/lib/widgets/loader/bottomsheet_loader.dart new file mode 100644 index 0000000..61f7f43 --- /dev/null +++ b/lib/widgets/loader/bottomsheet_loader.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; + +class LoaderBottomSheet { + static final NavigationService _navService = GetIt.I(); + static bool _isVisible = false; + + static void showLoader() { + if (_isVisible) return; + + _isVisible = true; + + showModalBottomSheet( + context: _navService.navigatorKey.currentContext!, + isDismissible: false, + enableDrag: false, + backgroundColor: Colors.transparent, + builder: (_) { + return Container( + height: MediaQuery.of(_navService.navigatorKey.currentContext!).size.height * 0.3, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(16)), + ), + child: Center( + child: Utils.getLoadingWidget(), + ), + ); + }, + ).whenComplete(() { + // reset state if dismissed by system + _isVisible = false; + }); + } + + static void hideLoader() { + if (_isVisible) { + Navigator.of(_navService.navigatorKey.currentContext!).pop(); + _isVisible = false; + } + } +}