diff --git a/assets/images/svg/add_family.svg b/assets/images/svg/add_family.svg
new file mode 100644
index 0000000..e873f89
--- /dev/null
+++ b/assets/images/svg/add_family.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/svg/wallet.svg b/assets/images/svg/wallet.svg
new file mode 100644
index 0000000..3382609
--- /dev/null
+++ b/assets/images/svg/wallet.svg
@@ -0,0 +1,4 @@
+
diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart
index bad2e4c..42f10f4 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.prod;
+ static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT
diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart
index 2603fb5..7f4fc2a 100644
--- a/lib/core/app_assets.dart
+++ b/lib/core/app_assets.dart
@@ -51,6 +51,8 @@ class AppAssets {
static const String insurance_update_icon = '$svgBasePath/insurance_update.svg';
static const String home_calendar_icon = '$svgBasePath/home_calendar_icon.svg';
static const String add_icon = '$svgBasePath/add_icon.svg';
+ static const String add_family = '$svgBasePath/add_family.svg';
+ static const String wallet = '$svgBasePath/wallet.svg';
static const String livecare_icon = '$svgBasePath/livecare_icon.svg';
static const String file_icon = '$svgBasePath/file_icon.svg';
static const String checkmark_icon = '$svgBasePath/checkmark_icon.svg';
diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart
index 191b75a..6edd3cc 100644
--- a/lib/core/dependencies.dart
+++ b/lib/core/dependencies.dart
@@ -145,6 +145,7 @@ class AppDependencies {
),
);
+
getIt.registerLazySingleton(
() => MedicalFileViewModel(
medicalFileRepo: getIt(),
diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart
index e228591..09f455b 100644
--- a/lib/core/utils/utils.dart
+++ b/lib/core/utils/utils.dart
@@ -251,45 +251,43 @@ class Utils {
);
}
- static bool isSAUDIIDValid(String id, type) {
- if (type == 1) {
- try {
- id = id.toString();
- id = id.trim();
- var returnValue = int.parse(id);
- var sum = 0;
- if (returnValue > 0) {
- var type = int.parse(id[0]);
-
- if (id.length != 10) {
- return false;
- }
- if (type != 2 && type != 1) {
- return false;
- }
+ static bool isSAUDIIDValid(
+ String id,
+ ) {
+ try {
+ id = id.toString();
+ id = id.trim();
+ var returnValue = int.parse(id);
+ var sum = 0;
+ if (returnValue > 0) {
+ var type = int.parse(id[0]);
+
+ if (id.length != 10) {
+ return false;
+ }
+ if (type != 2 && type != 1) {
+ return false;
+ }
- for (var i = 0; i < 10; i++) {
- if (i % 2 == 0) {
- var a = id[i];
- var x = int.parse(a) * 2;
- var b = x.toString();
- if (b.length == 1) {
- b = "0$b";
- }
- sum += int.parse(b[0]) + int.parse(b[1]);
- } else {
- sum += int.parse(id[i]);
+ for (var i = 0; i < 10; i++) {
+ if (i % 2 == 0) {
+ var a = id[i];
+ var x = int.parse(a) * 2;
+ var b = x.toString();
+ if (b.length == 1) {
+ b = "0$b";
}
+ sum += int.parse(b[0]) + int.parse(b[1]);
+ } else {
+ sum += int.parse(id[i]);
}
- return sum % 10 == 0;
}
- } catch (err) {
- log("errr: ${err.toString()}");
+ return sum % 10 == 0;
}
- return false;
- } else {
- return true;
+ } catch (err) {
+ log("errr: ${err.toString()}");
}
+ return false;
}
static Widget getNoDataWidget(BuildContext context, {String? errorText}) {
@@ -396,14 +394,14 @@ class Utils {
return '';
}
- // Replace HTML line breaks with newlines
+// Replace HTML line breaks with newlines
var withLineBreaks =
htmlString.replaceAll(RegExp(r'
', multiLine: true), '\n').replaceAll(RegExp(r'<\/p>', multiLine: true), '\n').replaceAll(RegExp(r'', multiLine: true), '\n');
- // Remove all other HTML tags
+// Remove all other HTML tags
var withoutTags = withLineBreaks.replaceAll(RegExp(r'<[^>]*>'), '');
- // Decode HTML entities
+// Decode HTML entities
var decodedString = withoutTags
.replaceAll(' ', ' ')
.replaceAll('&', '&')
@@ -416,7 +414,7 @@ class Utils {
.replaceAll('”', '"')
.replaceAll('“', '"');
- // Remove extra whitespace and normalize line breaks
+// Remove extra whitespace and normalize line breaks
var normalizedString = decodedString
.replaceAll(RegExp(r'\n\s*\n'), '\n\n') // Replace multiple blank lines with double line break
.replaceAll(RegExp(r' +'), ' ') // Replace multiple spaces with single space
@@ -427,13 +425,13 @@ class Utils {
Widget mDivider(Color color) {
return Divider(
- // width: double.infinity,
+// width: double.infinity,
height: 1,
color: color,
);
}
- // New Ui Items
+// New Ui Items
static String formatDateToDisplay(String isoDateString) {
try {
@@ -441,7 +439,7 @@ class Utils {
final day = dateTime.day.toString().padLeft(2, '0');
final year = dateTime.year.toString();
- // Map month number to short month name
+// Map month number to short month name
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
final month = monthNames[dateTime.month - 1];
@@ -467,7 +465,7 @@ class Utils {
static String formatHijriDateToDisplay(String hijriDateString) {
try {
- // Assuming hijriDateString is in the format yyyy-MM-dd
+// Assuming hijriDateString is in the format yyyy-MM-dd
final datePart = hijriDateString.split("T").first;
final parts = datePart.split('-');
if (parts.length != 3) return "";
@@ -475,7 +473,7 @@ class Utils {
final day = parts[2].padLeft(2, '0');
final year = parts[0];
- // Map month number to short month name (Hijri months)
+// Map month number to short month name (Hijri months)
const hijriMonthNames = ['Muharram', 'Safar', 'Rabi I', 'Rabi II', 'Jumada I', 'Jumada II', 'Rajab', 'Sha\'ban', 'Ramadan', 'Shawwal', 'Dhu al-Qi\'dah', 'Dhu al-Hijjah'];
final monthIndex = int.tryParse(parts[1]) ?? 1;
final month = hijriMonthNames[monthIndex - 1];
@@ -641,5 +639,4 @@ class Utils {
await file.writeAsBytes(bytes);
return file.path;
}
-
}
diff --git a/lib/core/utils/validation_utils.dart b/lib/core/utils/validation_utils.dart
index cb54af5..098059c 100644
--- a/lib/core/utils/validation_utils.dart
+++ b/lib/core/utils/validation_utils.dart
@@ -1,6 +1,8 @@
import 'dart:developer';
import 'package:hmg_patient_app_new/core/dependencies.dart';
+import 'package:hmg_patient_app_new/core/enums.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
class ValidationUtils {
@@ -20,4 +22,80 @@ class ValidationUtils {
}
return true;
}
+
+ static bool isValidatedId({String? nationalId, required Function() onOkPress, CountryEnum? selectedCountry, bool? isTermsAccepted, String? dob}) {
+ bool isCorrectID = true;
+ if (nationalId == null || nationalId.isEmpty) {
+ _dialogService.showExceptionBottomSheet(message: "Please enter a national ID", onOkPressed: onOkPress);
+ isCorrectID = false;
+ }
+
+ if (nationalId != null && nationalId.isNotEmpty && selectedCountry != null) {
+ if (selectedCountry == CountryEnum.saudiArabia) {
+ if (!validateIqama(nationalId)) {
+ _dialogService.showExceptionBottomSheet(message: "Please enter a valid national ID", onOkPressed: onOkPress);
+ return false;
+ }
+ }
+
+ if (selectedCountry == CountryEnum.unitedArabEmirates) {
+ if (!validateUaeNationalId(nationalId)) {
+ _dialogService.showExceptionBottomSheet(message: "Please enter a valid national ID", onOkPressed: onOkPress);
+ return false;
+ }
+ }
+
+ if (dob == null || dob.isEmpty) {
+ _dialogService.showExceptionBottomSheet(message: "Please enter a valid date of birth", onOkPressed: onOkPress);
+ return false;
+ }
+
+ if (isTermsAccepted != null && !isTermsAccepted) {
+ _dialogService.showExceptionBottomSheet(message: "Please accept the terms and conditions", onOkPressed: onOkPress);
+ return false;
+ }
+ }
+ return isCorrectID;
+ }
+
+ static bool isValidatePhone({String? phoneNumber, required Function() onOkPress}) {
+ if (phoneNumber == null || phoneNumber.isEmpty) {
+ _dialogService.showExceptionBottomSheet(message: "Please enter a valid phone number", onOkPressed: onOkPress);
+ return false;
+ }
+ return true;
+ }
+
+ static bool isValidate({String? phoneNumber, required Function() onOkPress}) {
+ if (phoneNumber == null || phoneNumber.isEmpty) {
+ _dialogService.showExceptionBottomSheet(message: "Please enter a valid phone number", onOkPressed: onOkPress);
+ return false;
+ }
+ return true;
+ }
+
+ static bool validateIqama(String iqamaNumber) {
+ String cleanedIqama = iqamaNumber.replaceAll(RegExp(r'[^0-9]'), '');
+ if (cleanedIqama.length != 10) {
+ return false;
+ }
+ int firstDigit = int.parse(cleanedIqama[0]);
+ if (firstDigit != 2 && firstDigit != 1) {
+ return false;
+ }
+ int sum = 0;
+ for (int i = 0; i < 10; i++) {
+ int digit = int.parse(cleanedIqama[i]);
+ int weight = (i % 2 == 0) ? 2 : 1; // Alternate weights: 2, 1, 2, 1...
+ int product = digit * weight;
+ sum += (product > 9) ? product - 9 : product; // Sum digits if product > 9
+ }
+ return sum % 10 == 0;
+ }
+
+ static bool validateUaeNationalId(String id) {
+ // Must be exactly 15 digits
+ final regex = RegExp(r'^784\d{4}\d{7}\d{1}$');
+ return regex.hasMatch(id);
+ }
}
diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart
index 1c38a5e..509385c 100644
--- a/lib/extensions/string_extensions.dart
+++ b/lib/extensions/string_extensions.dart
@@ -34,7 +34,8 @@ extension EmailValidator on String {
),
);
- Widget toText10({Color? color, FontWeight? weight, bool isBold = false, bool isUnderLine = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow, double letterSpacing = -1}) => Text(
+ Widget toText10({Color? color, FontWeight? weight, bool isBold = false, bool isUnderLine = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow, double letterSpacing = -1}) =>
+ Text(
this,
maxLines: maxlines,
overflow: textOverflow,
@@ -119,16 +120,7 @@ extension EmailValidator on String {
),
);
- Widget toText13({
- Color? color,
- bool isUnderLine = false,
- bool isBold = false,
- bool isCenter = false,
- int maxLine = 0,
- FontWeight? weight,
- double? letterSpacing = -1
- }) =>
- Text(
+ Widget toText13({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0, FontWeight? weight, double? letterSpacing = -1}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: (maxLine > 0) ? maxLine : null,
@@ -193,10 +185,11 @@ extension EmailValidator on String {
style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
);
- Widget toText18({Color? color, FontWeight? weight, bool isBold = false, bool isCenter = false, int? maxlines}) => Text(
+ Widget toText18({Color? color, FontWeight? weight, bool isBold = false, bool isCenter = false, int? maxlines, TextOverflow? textOverflow}) => Text(
maxLines: maxlines,
textAlign: isCenter ? TextAlign.center : null,
this,
+ overflow: textOverflow,
style: TextStyle(fontSize: 18.fSize, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4),
);
@@ -205,7 +198,7 @@ extension EmailValidator on String {
style: TextStyle(fontSize: 19.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4),
);
- Widget toText20({Color? color,FontWeight? weight, bool isBold = false}) => Text(
+ Widget toText20({Color? color, FontWeight? weight, bool isBold = false}) => Text(
this,
style: TextStyle(fontSize: 20.fSize, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4),
);
diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart
index b2464f6..e17d576 100644
--- a/lib/features/authentication/authentication_view_model.dart
+++ b/lib/features/authentication/authentication_view_model.dart
@@ -1,5 +1,6 @@
import 'dart:convert';
import 'dart:developer';
+import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter/material.dart';
import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart';
@@ -23,6 +24,7 @@ import 'package:hmg_patient_app_new/features/authentication/models/request_model
import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_activation_code_resp_model.dart';
import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_user_staus_nhic_response_model.dart';
import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart';
+import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/authentication/login.dart';
import 'package:hmg_patient_app_new/presentation/authentication/saved_login_screen.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
@@ -103,7 +105,7 @@ class AuthenticationViewModel extends ChangeNotifier {
}
}
- void clearDefaultInputValues() {
+ Future clearDefaultInputValues() async {
nationalIdController.clear();
phoneNumberController.clear();
dobController.clear();
@@ -265,10 +267,7 @@ class AuthenticationViewModel extends ChangeNotifier {
if (phoneNumberController.text.isEmpty) {
phoneNumberController.text = "504278212";
}
- bool isValidated = ValidationUtils.isValidatePhoneAndId(
- phoneNumber: phoneNumberController.text,
- nationalId: nationalIdController.text,
- );
+ bool isValidated = ValidationUtils.isValidatePhoneAndId(phoneNumber: phoneNumberController.text, nationalId: nationalIdController.text);
if (!isValidated) {
return;
@@ -289,7 +288,20 @@ class AuthenticationViewModel extends ChangeNotifier {
final result = await _authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq);
result.fold(
- (failure) async => await _errorHandlerService.handleError(failure: failure),
+ (failure) async => await _errorHandlerService.handleError(
+ failure: failure,
+ onUnHandledFailure: (failure) async {
+ LoaderBottomSheet.hideLoader();
+ await _dialogService.showCommonBottomSheetWithoutH(
+ message: failure.message,
+ label: LocaleKeys.notice.tr(),
+ onOkPressed: () {
+ _navigationService.pushAndReplace(AppRoutes.register);
+ },
+ onCancelPressed: () {
+ _navigationService.pop();
+ });
+ }),
(apiResponse) async {
if (apiResponse.messageStatus == 2) {
LoaderBottomSheet.hideLoader();
@@ -335,7 +347,6 @@ class AuthenticationViewModel extends ChangeNotifier {
if (checkIsUserComingForRegister(request: payload)) {
_appState.setUserRegistrationPayload = RegistrationDataModelPayload.fromJson(payload);
- print("====== Demo ==========");
}
final resultEither = await _authenticationRepo.sendActivationCodeRepo(sendActivationCodeReq: request, isRegister: checkIsUserComingForRegister(request: payload), languageID: 'er');
@@ -345,7 +356,11 @@ class AuthenticationViewModel extends ChangeNotifier {
(apiResponse) async {
if (apiResponse.messageStatus == 2) {
LoaderBottomSheet.hideLoader();
- await _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage ?? "ErrorEmpty");
+ await _dialogService.showCommonBottomSheetWithoutH(
+ message: apiResponse.errorMessage ?? "Something Went Wrong",
+ onOkPressed: () {
+ _navigationService.pop();
+ });
} else {
if (apiResponse.data != null && apiResponse.data['isSMSSent'] == true) {
LoaderBottomSheet.hideLoader();
@@ -403,18 +418,25 @@ class AuthenticationViewModel extends ChangeNotifier {
request["ForRegisteration"] = _appState.getUserRegistrationPayload.isRegister;
request["isRegister"] = false;
- // if (request.containsKey("OTP_SendType")) {
- // request.remove("OTP_SendType");
- // print("====== Demo: Removed OTP_SendType for Register state");
- // }
-
- print("====== Req");
-
final resultEither = await _authenticationRepo.checkActivationCodeRepo(newRequest: request, activationCode: activationCode.toString(), isRegister: true);
LoaderBottomSheet.hideLoader();
- resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) {
+ resultEither.fold(
+ (failure) async => await _errorHandlerService.handleError(
+ failure: failure,
+ onUnHandledFailure: (failure) async {
+ LoaderBottomSheet.hideLoader();
+ await _dialogService.showCommonBottomSheetWithoutH(
+ message: failure.message,
+ label: LocaleKeys.notice.tr(),
+ onOkPressed: () {
+ _navigationService.pushAndReplace(AppRoutes.register);
+ },
+ onCancelPressed: () {
+ _navigationService.pop();
+ });
+ }), (apiResponse) {
final activation = CheckActivationCode.fromJson(apiResponse.data as Map);
if (_appState.getUserRegistrationPayload.isRegister == true) {
//TODO: KSA Version Came Hre
@@ -425,18 +447,20 @@ class AuthenticationViewModel extends ChangeNotifier {
}
});
} else {
- final resultEither = await _authenticationRepo.checkActivationCodeRepo(
- newRequest: CheckActivationCodeRegisterReq.fromJson(request),
- activationCode: activationCode,
- isRegister: false,
- );
-
- resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async {
+ final resultEither = await _authenticationRepo.checkActivationCodeRepo(newRequest: CheckActivationCodeRegisterReq.fromJson(request), activationCode: activationCode, isRegister: false);
+
+ resultEither.fold(
+ (failure) async => await _errorHandlerService.handleError(
+ failure: failure,
+ onUnHandledFailure: (failure) async {
+ LoaderBottomSheet.hideLoader();
+ await _dialogService.showCommonBottomSheetWithoutH(message: failure.message, label: LocaleKeys.notice.tr(), onOkPressed: () {});
+ },
+ ), (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);
@@ -465,7 +489,7 @@ class AuthenticationViewModel extends ChangeNotifier {
}
LoaderBottomSheet.hideLoader();
insertPatientIMEIData(loginTypeEnum.toInt);
- clearDefaultInputValues();
+ await clearDefaultInputValues();
if (isUserAgreedBefore) {
navigateToHomeScreen();
} else {
@@ -595,7 +619,7 @@ class AuthenticationViewModel extends ChangeNotifier {
Future onRegistrationStart({required OTPTypeEnum otpTypeEnum}) async {
bool isOutSidePatient = selectedCountrySignup.countryCode == CountryEnum.unitedArabEmirates.countryCode ? true : false;
-
+ LoaderBottomSheet.showLoader();
final request = await RequestUtils.getPatientAuthenticationRequest(
phoneNumber: phoneNumberController.text,
nationId: nationalIdController.text,
@@ -639,7 +663,7 @@ class AuthenticationViewModel extends ChangeNotifier {
print(apiResponse.data as Map);
if (apiResponse.data["MessageStatus"] == 1) {
//TODO: Here We Need to Show a Dialog Of Something in the case of Success.
- clearDefaultInputValues(); // This will Clear All Default Values Of User.
+ await clearDefaultInputValues(); // This will Clear All Default Values Of User.
_navigationService.pushAndReplace(AppRoutes.loginScreen);
}
}
@@ -651,24 +675,22 @@ class AuthenticationViewModel extends ChangeNotifier {
Future checkUserStatusForRegistration({required dynamic response, required dynamic request}) async {
if (response is Map) {
if (response["MessageStatus"] == 2) {
+ LoaderBottomSheet.hideLoader();
print(response["ErrorEndUserMessage"]);
return;
}
if (response['hasFile'] == true) {
//TODO: Show Here Ok And Cancel Dialog and On OKPress it will go for sendActivationCode
-
- _navigationService.context?.showBottomSheet(
- child: ExceptionBottomSheet(
- message: response["ErrorMessage"],
- showCancel: true,
- showOKButton: true,
- onOkPressed: () {
- _navigationService.popUntilNamed(AppRoutes.loginScreen);
- },
- onCancelPressed: () {
- _navigationService.pop();
- },
- ));
+ LoaderBottomSheet.hideLoader();
+ _dialogService.showCommonBottomSheetWithoutH(
+ message: response["ErrorMessage"],
+ onOkPressed: () async {
+ await clearDefaultInputValues();
+ _navigationService.pushAndReplace(AppRoutes.loginScreen);
+ },
+ onCancelPressed: () {
+ _navigationService.pop();
+ });
} else {
request['forRegister'] = true;
request['isRegister'] = true;
@@ -727,41 +749,6 @@ class AuthenticationViewModel extends ChangeNotifier {
);
}
});
-
- // this.authService.checkUserStatus(request).then((result) {
- // // Keep loader active, continue to next step
- // if (result is Map) {
- // RegisterInfoResponse? resultSet;
- // CheckUserStatusResponse res = CheckUserStatusResponse.fromJson(result as Map);
- // nHICData = res;
- // sharedPref.setObject(NHIC_DATA, res.toJson());
- // resultSet = RegisterInfoResponse.fromJson(res.toJson());
- //
- // sendActivationCode(type, loginToken, resultSet, isSkipRegistration);
- // } else {
- // GifLoaderDialogUtils.hideDialog(context);
- // context.showBottomSheet(
- // child: ExceptionBottomSheet(
- // message: result != null ? result : TranslationBase.of(context).somethingWentWrong,
- // showCancel: false,
- // onOkPressed: () {
- // Navigator.of(context).pop();
- // },
- // ),
- // );
- // }
- // }).catchError((err) {
- // GifLoaderDialogUtils.hideDialog(context);
- // context.showBottomSheet(
- // child: ExceptionBottomSheet(
- // message: err.toString(),
- // showCancel: false,
- // onOkPressed: () {
- // Navigator.of(context).pop();
- // },
- // ),
- // );
- // });
}
void setNHICData(dynamic data, dynamic request) {
diff --git a/lib/features/authentication/widgets/otp_verification_screen.dart b/lib/features/authentication/widgets/otp_verification_screen.dart
index e009593..1ea9be5 100644
--- a/lib/features/authentication/widgets/otp_verification_screen.dart
+++ b/lib/features/authentication/widgets/otp_verification_screen.dart
@@ -1,12 +1,394 @@
import 'dart:async';
+import 'package:flutter/animation.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/services.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
-import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
-import 'package:hmg_patient_app_new/presentation/authentication/register_step2.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart';
+typedef OnDone = void Function(String text);
+
+class ProvidedPinBoxTextAnimation {
+ static AnimatedSwitcherTransitionBuilder scalingTransition = (child, animation) {
+ return ScaleTransition(
+ child: child,
+ scale: animation,
+ );
+ };
+
+ static AnimatedSwitcherTransitionBuilder defaultNoTransition = (Widget child, Animation animation) {
+ return child;
+ };
+}
+
+class OTPWidget extends StatefulWidget {
+ final int maxLength;
+ final TextEditingController? controller;
+
+ final Color defaultBorderColor;
+ final Color pinBoxColor;
+ final double pinBoxBorderWidth;
+ final double pinBoxRadius;
+ final bool hideDefaultKeyboard;
+
+ final TextStyle? pinTextStyle;
+ final double pinBoxHeight;
+ final double pinBoxWidth;
+ final OnDone? onDone;
+ final bool hasError;
+ final Color errorBorderColor;
+ final Color textBorderColor;
+ final Function(String)? onTextChanged;
+ final bool autoFocus;
+ final FocusNode? focusNode;
+ final AnimatedSwitcherTransitionBuilder? pinTextAnimatedSwitcherTransition;
+ final Duration pinTextAnimatedSwitcherDuration;
+ final TextDirection textDirection;
+ final TextInputType keyboardType;
+ final EdgeInsets pinBoxOuterPadding;
+
+ OTPWidget({
+ Key? key,
+ this.maxLength = 4,
+ this.controller,
+ this.pinBoxWidth = 70.0,
+ this.pinBoxHeight = 100.0,
+ this.pinTextStyle,
+ this.onDone,
+ this.defaultBorderColor = Colors.black,
+ this.textBorderColor = Colors.black,
+ this.pinTextAnimatedSwitcherTransition,
+ this.pinTextAnimatedSwitcherDuration = const Duration(),
+ this.hasError = false,
+ this.errorBorderColor = Colors.red,
+ this.onTextChanged,
+ this.autoFocus = false,
+ this.focusNode,
+ this.textDirection = TextDirection.ltr,
+ this.keyboardType = TextInputType.number,
+ this.pinBoxOuterPadding = const EdgeInsets.symmetric(horizontal: 4.0),
+ this.pinBoxColor = Colors.white,
+ this.pinBoxBorderWidth = 2.0,
+ this.pinBoxRadius = 0,
+ this.hideDefaultKeyboard = false,
+ }) : super(key: key);
+
+ @override
+ State createState() {
+ return OTPWidgetState();
+ }
+}
+
+class OTPWidgetState extends State with SingleTickerProviderStateMixin {
+ late AnimationController _highlightAnimationController;
+ late FocusNode focusNode;
+ String text = "";
+ int currentIndex = 0;
+ List strList = [];
+ bool hasFocus = false;
+
+ @override
+ void didUpdateWidget(OTPWidget oldWidget) {
+ super.didUpdateWidget(oldWidget);
+ focusNode = widget.focusNode ?? focusNode;
+
+ if (oldWidget.maxLength < widget.maxLength) {
+ setState(() {
+ currentIndex = text.length;
+ });
+ widget.controller?.text = text;
+ widget.controller?.selection = TextSelection.collapsed(offset: text.length);
+ } else if (oldWidget.maxLength > widget.maxLength && widget.maxLength > 0 && text.length > 0 && text.length > widget.maxLength) {
+ setState(() {
+ text = text.substring(0, widget.maxLength);
+ currentIndex = text.length;
+ });
+ widget.controller?.text = text;
+ widget.controller?.selection = TextSelection.collapsed(offset: text.length);
+ }
+ }
+
+ _calculateStrList() {
+ if (strList.length > widget.maxLength) {
+ strList.length = widget.maxLength;
+ }
+ while (strList.length < widget.maxLength) {
+ strList.add("");
+ }
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ focusNode = widget.focusNode ?? FocusNode();
+ _highlightAnimationController = AnimationController(vsync: this);
+ _initTextController();
+ _calculateStrList();
+ widget.controller!.addListener(_controllerListener);
+ focusNode.addListener(_focusListener);
+ }
+
+ void _controllerListener() {
+ if (mounted == true) {
+ setState(() {
+ _initTextController();
+ });
+ var onTextChanged = widget.onTextChanged;
+ if (onTextChanged != null) {
+ onTextChanged(widget.controller?.text ?? "");
+ }
+ }
+ }
+
+ void _focusListener() {
+ if (mounted == true) {
+ setState(() {
+ hasFocus = focusNode.hasFocus;
+ });
+ }
+ }
+
+ void _initTextController() {
+ if (widget.controller == null) {
+ return;
+ }
+ strList.clear();
+ var text = widget.controller?.text ?? "";
+ if (text.isNotEmpty) {
+ if (text.length > widget.maxLength) {
+ throw Exception("TextEditingController length exceeded maxLength!");
+ }
+ }
+ for (var i = 0; i < text.length; i++) {
+ strList.add(text[i]);
+ }
+ }
+
+ double get _width {
+ var width = 0.0;
+ for (var i = 0; i < widget.maxLength; i++) {
+ width += widget.pinBoxWidth;
+ if (i == 0) {
+ width += widget.pinBoxOuterPadding.left;
+ } else if (i + 1 == widget.maxLength) {
+ width += widget.pinBoxOuterPadding.right;
+ } else {
+ width += widget.pinBoxOuterPadding.left;
+ }
+ }
+ return width;
+ }
+
+ @override
+ void dispose() {
+ if (widget.focusNode == null) {
+ focusNode.dispose();
+ } else {
+ focusNode.removeListener(_focusListener);
+ }
+ _highlightAnimationController.dispose();
+ widget.controller?.removeListener(_controllerListener);
+
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Stack(
+ children: [
+ _otpTextInput(),
+ _touchPinBoxRow(),
+ ],
+ );
+ }
+
+ Widget _touchPinBoxRow() {
+ return widget.hideDefaultKeyboard
+ ? _pinBoxRow(context)
+ : GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onTap: () {
+ if (hasFocus) {
+ FocusScope.of(context).requestFocus(FocusNode());
+ Future.delayed(Duration(milliseconds: 100), () {
+ FocusScope.of(context).requestFocus(focusNode);
+ });
+ } else {
+ FocusScope.of(context).requestFocus(focusNode);
+ }
+ },
+ child: _pinBoxRow(context),
+ );
+ }
+
+ Widget _otpTextInput() {
+ var transparentBorder = OutlineInputBorder(
+ borderSide: BorderSide(
+ color: Colors.transparent,
+ width: 0.0,
+ ),
+ );
+ return SizedBox(
+ width: _width,
+ height: widget.pinBoxHeight,
+ child: TextField(
+ autofocus: !kIsWeb ? widget.autoFocus : false,
+ enableInteractiveSelection: false,
+ focusNode: focusNode,
+ controller: widget.controller,
+ keyboardType: widget.keyboardType,
+ inputFormatters: widget.keyboardType == TextInputType.number ? [FilteringTextInputFormatter.digitsOnly] : null,
+ // Enable SMS autofill
+ autofillHints: const [AutofillHints.oneTimeCode],
+ style: TextStyle(
+ height: 0.1,
+ color: Colors.transparent,
+ ),
+ decoration: InputDecoration(
+ contentPadding: EdgeInsets.all(0),
+ focusedErrorBorder: transparentBorder,
+ errorBorder: transparentBorder,
+ disabledBorder: transparentBorder,
+ enabledBorder: transparentBorder,
+ focusedBorder: transparentBorder,
+ counterText: null,
+ counterStyle: null,
+ helperStyle: TextStyle(
+ height: 0.0,
+ color: Colors.transparent,
+ ),
+ labelStyle: TextStyle(height: 0.1),
+ fillColor: Colors.transparent,
+ border: InputBorder.none,
+ ),
+ cursorColor: Colors.transparent,
+ showCursor: false,
+ maxLength: widget.maxLength,
+ onChanged: _onTextChanged,
+ ),
+ );
+ }
+
+ void _onTextChanged(text) {
+ var onTextChanged = widget.onTextChanged;
+ if (onTextChanged != null) {
+ onTextChanged(text);
+ }
+ setState(() {
+ this.text = text;
+ if (text.length >= currentIndex) {
+ for (int i = currentIndex; i < text.length; i++) {
+ strList[i] = text[i];
+ }
+ }
+ currentIndex = text.length;
+ });
+ if (text.length == widget.maxLength) {
+ FocusScope.of(context).requestFocus(FocusNode());
+ var onDone = widget.onDone;
+ if (onDone != null) {
+ onDone(text);
+ }
+ }
+ }
+
+ Widget _pinBoxRow(BuildContext context) {
+ _calculateStrList();
+ List pinCodes = List.generate(widget.maxLength, (int i) {
+ return _buildPinCode(i, context);
+ });
+ return Row(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: pinCodes,
+ );
+ }
+
+ Widget _buildPinCode(int i, BuildContext context) {
+ Color borderColor;
+ Color pinBoxColor;
+
+ // Determine if OTP is complete
+ bool isComplete = text.length == widget.maxLength;
+
+ if (widget.hasError) {
+ borderColor = widget.errorBorderColor;
+ pinBoxColor = widget.pinBoxColor;
+ } else if (isComplete) {
+ borderColor = Colors.transparent;
+ pinBoxColor = AppColors.successColor;
+ } else if (i < text.length) {
+ borderColor = Colors.transparent;
+ pinBoxColor = AppColors.blackBgColor;
+ } else {
+ borderColor = Colors.transparent;
+ pinBoxColor = widget.pinBoxColor;
+ }
+
+ EdgeInsets insets;
+ if (i == 0) {
+ insets = EdgeInsets.only(
+ left: 0,
+ top: widget.pinBoxOuterPadding.top,
+ right: widget.pinBoxOuterPadding.right,
+ bottom: widget.pinBoxOuterPadding.bottom,
+ );
+ } else if (i == strList.length - 1) {
+ insets = EdgeInsets.only(
+ left: widget.pinBoxOuterPadding.left,
+ top: widget.pinBoxOuterPadding.top,
+ right: 0,
+ bottom: widget.pinBoxOuterPadding.bottom,
+ );
+ } else {
+ insets = widget.pinBoxOuterPadding;
+ }
+ return Container(
+ key: ValueKey("container$i"),
+ alignment: Alignment.center,
+ padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 1.0),
+ margin: insets,
+ decoration: BoxDecoration(
+ border: Border.all(
+ color: borderColor,
+ width: widget.pinBoxBorderWidth,
+ ),
+ color: pinBoxColor,
+ borderRadius: BorderRadius.circular(widget.pinBoxRadius),
+ ),
+ width: widget.pinBoxWidth,
+ height: widget.pinBoxHeight,
+ child: _animatedTextBox(strList[i], i),
+ );
+ }
+
+ Widget _animatedTextBox(String text, int i) {
+ if (widget.pinTextAnimatedSwitcherTransition != null) {
+ return AnimatedSwitcher(
+ duration: widget.pinTextAnimatedSwitcherDuration,
+ transitionBuilder: widget.pinTextAnimatedSwitcherTransition ??
+ (Widget child, Animation animation) {
+ return child;
+ },
+ child: Text(
+ text,
+ key: ValueKey("$text$i"),
+ style: widget.pinTextStyle,
+ ),
+ );
+ } else {
+ return Text(
+ text,
+ key: ValueKey("${strList[i]}$i"),
+ style: widget.pinTextStyle,
+ );
+ }
+ }
+}
+
class OTPVerificationScreen extends StatefulWidget {
final String phoneNumber;
final Function(int code) checkActivationCode;
@@ -25,36 +407,21 @@ class OTPVerificationScreen extends StatefulWidget {
class _OTPVerificationScreenState extends State {
final int _otpLength = 4;
- late final List _controllers;
- late final List _focusNodes;
+ late TextEditingController _otpController;
Timer? _resendTimer;
int _resendTime = 60;
- bool _isOtpComplete = false;
@override
void initState() {
super.initState();
- _controllers = List.generate(_otpLength, (_) => TextEditingController());
- _focusNodes = List.generate(_otpLength, (_) => FocusNode());
+ _otpController = TextEditingController();
_startResendTimer();
-
- // Focus the first field once the screen is built
- WidgetsBinding.instance.addPostFrameCallback((_) {
- if (_focusNodes.isNotEmpty) {
- FocusScope.of(context).requestFocus(_focusNodes[0]);
- }
- });
}
@override
void dispose() {
- for (final c in _controllers) {
- c.dispose();
- }
- for (final f in _focusNodes) {
- f.dispose();
- }
+ _otpController.dispose();
_resendTimer?.cancel();
super.dispose();
}
@@ -69,25 +436,23 @@ class _OTPVerificationScreenState extends State {
});
}
- void _onOtpChanged(int index, String value) {
- if (value.length == 1 && index < _otpLength - 1) {
- _focusNodes[index + 1].requestFocus();
- } else if (value.isEmpty && index > 0) {
- _focusNodes[index - 1].requestFocus();
+ void _onOtpChanged(String value) {
+ // Handle clipboard paste or programmatic input
+ if (value.length > 1) {
+ String? otp = _extractOtpFromText(value);
+ if (otp != null) {
+ autoFillOtp(otp);
+ return;
+ }
}
- _checkOtpCompletion();
+
+ // The OTPWidget will automatically call onDone when complete
+ // This method can be used for any additional logic on text change
}
- void _checkOtpCompletion() {
- final isComplete = _controllers.every((c) => c.text.isNotEmpty);
-
- if (isComplete != _isOtpComplete) {
- setState(() => _isOtpComplete = isComplete);
-
- if (isComplete) {
- _verifyOtp();
- }
- }
+ void _onOtpCompleted(String otp) {
+ debugPrint('OTP Completed: $otp');
+ widget.checkActivationCode(int.parse(otp));
}
void _resendOtp() {
@@ -95,8 +460,7 @@ class _OTPVerificationScreenState extends State {
setState(() => _resendTime = 60);
_startResendTimer();
autoFillOtp("1234");
-
- // call resend API here
+ widget.onResendOTPPressed(widget.phoneNumber);
}
}
@@ -105,6 +469,68 @@ class _OTPVerificationScreenState extends State {
return phone.length > 4 ? '05xxxxxx${phone.substring(phone.length - 2)}' : phone;
}
+ /// Extract OTP from text using multiple patterns
+ String? _extractOtpFromText(String text) {
+ // Pattern 1: Find 4-6 consecutive digits
+ RegExp digitPattern = RegExp(r'\b\d{4,6}\b');
+ Match? match = digitPattern.firstMatch(text);
+
+ if (match != null) {
+ String digits = match.group(0)!;
+ if (digits.length >= _otpLength) {
+ return digits.substring(0, _otpLength);
+ }
+ }
+
+ // Pattern 2: Find digits separated by spaces or special characters
+ String cleanedText = text.replaceAll(RegExp(r'[^\d]'), '');
+ if (cleanedText.length >= _otpLength) {
+ return cleanedText.substring(0, _otpLength);
+ }
+
+ return null;
+ }
+
+ /// Paste OTP from clipboard
+ Future _pasteFromClipboard() async {
+ try {
+ ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
+ if (data != null && data.text != null) {
+ String clipboardText = data.text!;
+ String? otp = _extractOtpFromText(clipboardText);
+
+ if (otp != null) {
+ autoFillOtp(otp);
+
+ // Show feedback to user
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('OTP pasted: $otp'),
+ duration: const Duration(seconds: 2),
+ backgroundColor: AppColors.successColor,
+ ),
+ );
+ } else {
+ // Show error if no valid OTP found
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('No valid OTP found in clipboard'),
+ duration: Duration(seconds: 2),
+ ),
+ );
+ }
+ }
+ } catch (e) {
+ debugPrint('Error pasting from clipboard: $e');
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('Failed to paste from clipboard'),
+ duration: Duration(seconds: 2),
+ ),
+ );
+ }
+ }
+
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -134,56 +560,35 @@ class _OTPVerificationScreenState extends State {
),
SizedBox(height: 40.h),
- // OTP Input Fields
- SizedBox(
- height: 100,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: List.generate(_otpLength, (index) {
- return ValueListenableBuilder(
- valueListenable: _controllers[index],
- builder: (context, value, _) {
- final hasText = value.text.isNotEmpty;
-
- return AnimatedContainer(
- duration: const Duration(milliseconds: 200),
- curve: Curves.easeInOut,
- width: 70.h,
- margin: EdgeInsets.symmetric(horizontal: 4.h),
- decoration: RoundedRectangleBorder()
- .toSmoothCornerDecoration(color: _isOtpComplete ? AppColors.successColor : (hasText ? AppColors.blackBgColor : AppColors.whiteColor), borderRadius: 16),
- child: Center(
- child: TextField(
- controller: _controllers[index],
- focusNode: _focusNodes[index],
- textAlign: TextAlign.center,
- keyboardType: TextInputType.number,
- maxLength: 1,
- style: TextStyle(
- fontSize: 40.fSize,
- fontWeight: FontWeight.bold,
- color: AppColors.whiteColor,
- ),
- decoration: InputDecoration(
- counterText: '',
- filled: true,
- fillColor: Colors.transparent,
- border: OutlineInputBorder(
- borderRadius: BorderRadius.circular(18),
- borderSide: BorderSide.none,
- ),
- ),
- onChanged: (v) => _onOtpChanged(index, v),
- ),
- ),
- );
- },
- );
- }),
+ // OTP Input Fields using new OTPWidget
+ Center(
+ child: AutofillGroup(
+ child: OTPWidget(
+ maxLength: _otpLength,
+ controller: _otpController,
+ pinBoxWidth: 75.h,
+ pinBoxHeight: 100.h,
+ autoFocus: true,
+ pinBoxRadius: 16,
+ pinBoxBorderWidth: 0,
+ pinBoxOuterPadding: EdgeInsets.symmetric(horizontal: 4.h),
+ defaultBorderColor: Colors.transparent,
+ textBorderColor: Colors.transparent,
+ errorBorderColor: AppColors.primaryRedColor,
+ pinBoxColor: AppColors.whiteColor,
+ pinTextStyle: TextStyle(
+ fontSize: 50.fSize,
+ fontWeight: FontWeight.bold,
+ color: AppColors.whiteColor,
+ ),
+ onTextChanged: _onOtpChanged,
+ onDone: _onOtpCompleted,
+ ),
),
),
- const SizedBox(height: 32),
+ const SizedBox(height: 16),
+
// Resend OTP
Row(
@@ -215,30 +620,50 @@ class _OTPVerificationScreenState extends State {
);
}
- void _verifyOtp() {
- final otp = _controllers.map((c) => c.text).join();
- debugPrint('Verifying OTP: $otp');
-
- widget.checkActivationCode(int.parse(otp));
- // ScaffoldMessenger.of(context).showSnackBar(
- // SnackBar(content: Text('Verifying OTP: $otp')),
- // );
-
- // Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => RegisterNewStep2(null, {"nationalID": "12345678654321"})));
- }
-
/// Auto fill OTP into text fields
void autoFillOtp(String otp) {
if (otp.length != _otpLength) return;
+
+ // Clear any existing text first
+ _otpController.clear();
+
+ // Add a small delay to ensure the UI is updated
+ Future.delayed(const Duration(milliseconds: 50), () {
+ _otpController.text = otp;
+ // Move cursor to the end
+ _otpController.selection = TextSelection.fromPosition(
+ TextPosition(offset: otp.length),
+ );
+ });
+ }
- for (int i = 0; i < _otpLength; i++) {
- _controllers[i].text = otp[i];
- }
+ /// Clear OTP fields
+ void clearOtp() {
+ _otpController.clear();
+ }
+
+ /// Get current OTP value
+ String getCurrentOtp() {
+ return _otpController.text;
+ }
- // Move focus to the last field
- _focusNodes[_otpLength - 1].requestFocus();
+ /// Check if OTP is complete
+ bool isOtpComplete() {
+ return _otpController.text.length == _otpLength;
+ }
- // Trigger completion check and color update
- _checkOtpCompletion();
+ /// Simulate SMS received with OTP (for testing purposes)
+ void simulateSMSReceived(String otp) {
+ if (otp.length == _otpLength && RegExp(r'^\d+$').hasMatch(otp)) {
+ autoFillOtp(otp);
+ // Show a brief indicator that SMS was detected
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('OTP detected from SMS: $otp'),
+ duration: const Duration(seconds: 2),
+ backgroundColor: AppColors.successColor,
+ ),
+ );
+ }
}
}
diff --git a/lib/features/profile_settings/profile_settings_view_model.dart b/lib/features/profile_settings/profile_settings_view_model.dart
new file mode 100644
index 0000000..61acb26
--- /dev/null
+++ b/lib/features/profile_settings/profile_settings_view_model.dart
@@ -0,0 +1,8 @@
+import 'package:flutter/foundation.dart';
+
+class ProfileSettingsViewModel extends ChangeNotifier {
+
+ void notify(){
+ notifyListeners();
+ }
+}
diff --git a/lib/main.dart b/lib/main.dart
index 901c529..24f5c36 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_mode
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/features/prescriptions/prescriptions_view_model.dart';
+import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
@@ -102,6 +103,9 @@ void main() async {
errorHandlerService: getIt(),
),
),
+ ChangeNotifierProvider(
+ create: (_) => ProfileSettingsViewModel(),
+ ),
ChangeNotifierProvider(
create: (_) => MyAppointmentsViewModel(
myAppointmentsRepo: getIt(),
diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart
index 0b463cc..2a8c744 100644
--- a/lib/presentation/authentication/login.dart
+++ b/lib/presentation/authentication/login.dart
@@ -7,6 +7,7 @@ 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/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/core/utils/validation_utils.dart';
import 'package:hmg_patient_app_new/extensions/context_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
@@ -28,13 +29,17 @@ class LoginScreen extends StatefulWidget {
}
class LoginScreenState extends State {
+ late FocusNode _nationalIdFocusNode;
+
@override
void initState() {
super.initState();
+ _nationalIdFocusNode = FocusNode();
}
@override
void dispose() {
+ _nationalIdFocusNode.dispose();
super.dispose();
}
@@ -48,12 +53,14 @@ class LoginScreenState extends State {
Navigator.of(context).pop();
},
onLanguageChanged: (String value) {
- // context.setLocale(value == 'en' ? Locale('ar', 'SA') : Locale('en', 'US'));
+ context.setLocale(value == 'en' ? Locale('en', 'US') : Locale('ar', 'SA'));
},
),
body: GestureDetector(
onTap: () {
- FocusScope.of(context).unfocus(); // Dismiss the keyboard when tapping outside
+ // Dismiss the keyboard and unfocus any focused widget when tapping outside
+ _nationalIdFocusNode.unfocus();
+ FocusScope.of(context).unfocus();
},
child: SingleChildScrollView(
child: Padding(
@@ -70,6 +77,7 @@ class LoginScreenState extends State {
labelText: "${LocaleKeys.nationalId.tr()} / ${LocaleKeys.fileNo.tr()}",
hintText: "xxxxxxxxx",
controller: authVm.nationalIdController,
+ focusNode: _nationalIdFocusNode,
keyboardType: TextInputType.number,
isEnable: true,
prefix: null,
@@ -80,7 +88,7 @@ class LoginScreenState extends State {
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 10.h),
leadingIcon: AppAssets.student_card,
errorMessage: "Please enter a valid national ID or file number",
- hasError: true,
+ hasError: false,
),
SizedBox(height: 16.h), // Adjusted to sizer unit (approx 16px)
CustomButton(
@@ -88,20 +96,16 @@ class LoginScreenState extends State {
icon: AppAssets.login1,
iconColor: Colors.white,
onPressed: () {
- showLoginModelSheet(context: context, phoneNumberController: authVm.phoneNumberController, authViewModel: authVm);
- // if (nationIdController.text.isNotEmpty) {
+ _nationalIdFocusNode.unfocus();
+ FocusScope.of(context).unfocus();
- // } else {
- // showBottomSheet(
- // child: ExceptionBottomSheet(
- // message: TranslationBase.of(context).pleaseEnterNationalIdOrFileNo,
- // showCancel: false,
- // onOkPressed: () {
- // Navigator.of(context).pop();
- // },
- // ),
- // );
- // }
+ if (ValidationUtils.isValidatedId(
+ nationalId: authVm.nationalIdController.text,
+ onOkPress: () {
+ Navigator.of(context).pop();
+ })) {
+ showLoginModelSheet(context: context, phoneNumberController: authVm.phoneNumberController, authViewModel: authVm);
+ }
},
),
SizedBox(height: 10.h), // Adjusted to sizer unit (approx 14px)
@@ -121,11 +125,10 @@ class LoginScreenState extends State {
TextSpan(
text: LocaleKeys.registernow.tr(),
style: context.dynamicTextStyle(
- color: AppColors.primaryRedColor,
- fontSize: 14.fSize, // Adjusted to sizer unit
- height: 26 / 16, // Ratio
- fontWeight: FontWeight.w500,
- ),
+ color: AppColors.primaryRedColor,
+ fontSize: 14.fSize, // Adjusted to sizer unit
+ height: 26 / 16, // Ratio
+ fontWeight: FontWeight.w500),
recognizer: TapGestureRecognizer()
..onTap = () {
Navigator.of(context).push(
@@ -172,7 +175,13 @@ class LoginScreenState extends State {
child: CustomButton(
text: LocaleKeys.sendOTPSMS.tr(),
onPressed: () async {
- await authViewModel.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms);
+ if (ValidationUtils.isValidatePhone(
+ phoneNumber: phoneNumberController!.text,
+ onOkPress: () {
+ Navigator.of(context).pop();
+ })) {
+ await authViewModel.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms);
+ }
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedBorderColor,
@@ -195,9 +204,13 @@ class LoginScreenState extends State {
child: CustomButton(
text: LocaleKeys.sendOTPWHATSAPP.tr(),
onPressed: () async {
- log("phoneNumberController: ${phoneNumberController == null}");
- log("phoneNumberControllerVa: ${phoneNumberController?.text}");
- await authViewModel.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp);
+ if (ValidationUtils.isValidatePhone(
+ phoneNumber: phoneNumberController!.text,
+ onOkPress: () {
+ Navigator.of(context).pop();
+ })) {
+ await authViewModel.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp);
+ }
},
backgroundColor: Colors.white,
borderColor: AppColors.borderOnlyColor,
diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart
index efb0a18..8ec988d 100644
--- a/lib/presentation/authentication/register.dart
+++ b/lib/presentation/authentication/register.dart
@@ -2,11 +2,10 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
-import 'package:hmg_patient_app_new/core/app_state.dart';
-import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/core/utils/validation_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
@@ -26,19 +25,25 @@ class RegisterNew extends StatefulWidget {
}
class _RegisterNew extends State {
+ late FocusNode _nationalIdFocusNode;
+ late FocusNode _dobFocusNode;
+
@override
void initState() {
super.initState();
+ _nationalIdFocusNode = FocusNode();
+ _dobFocusNode = FocusNode();
}
@override
void dispose() {
+ _nationalIdFocusNode.dispose();
+ _dobFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
- AppState appState = getIt.get();
AuthenticationViewModel authVm = context.read();
return Scaffold(
@@ -48,11 +53,14 @@ class _RegisterNew extends State {
Navigator.of(context).pop();
},
onLanguageChanged: (String value) {
- // context.setLocale(value == 'en' ? Locale('ar', 'SA') : Locale('en', 'US'));
+ context.setLocale(value == 'en' ? Locale('en', 'US') : Locale('ar', 'SA'));
},
),
body: GestureDetector(
onTap: () {
+ // Dismiss keyboard and unfocus all input fields
+ _nationalIdFocusNode.unfocus();
+ _dobFocusNode.unfocus();
FocusScope.of(context).unfocus();
},
child: ScrollConfiguration(
@@ -90,6 +98,7 @@ class _RegisterNew extends State {
labelText: LocaleKeys.nationalIdNumber.tr(),
hintText: "xxxxxxxxx",
controller: authVm.nationalIdController,
+ focusNode: _nationalIdFocusNode,
isEnable: true,
prefix: null,
isAllowRadius: true,
@@ -104,6 +113,7 @@ class _RegisterNew extends State {
labelText: LocaleKeys.dob.tr(),
hintText: "11 July, 1994",
controller: authVm.dobController,
+ focusNode: _dobFocusNode,
isEnable: true,
prefix: null,
isAllowRadius: true,
@@ -156,7 +166,21 @@ class _RegisterNew extends State {
text: "Register",
icon: AppAssets.note_edit,
onPressed: () {
- showRegisterModel(context: context, authVM: authVm);
+ // Dismiss keyboard before proceeding
+ _nationalIdFocusNode.unfocus();
+ _dobFocusNode.unfocus();
+ FocusScope.of(context).unfocus();
+
+ if (ValidationUtils.isValidatedId(
+ nationalId: authVm.nationalIdController.text,
+ selectedCountry: authVm.selectedCountrySignup,
+ isTermsAccepted: authVm.isTermsAccepted,
+ dob: authVm.dobController.text,
+ onOkPress: () {
+ Navigator.of(context).pop();
+ })) {
+ showRegisterModel(context: context, authVM: authVm);
+ }
},
),
SizedBox(height: 14),
@@ -216,13 +240,24 @@ class _RegisterNew extends State {
isEnableCountryDropdown: false,
onCountryChange: authVM.onCountryChange,
onChange: authVM.onPhoneNumberChange,
+ autoFocus: true,
buttons: [
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: CustomButton(
text: LocaleKeys.sendOTPSMS.tr(),
onPressed: () async {
- await authVM.onRegistrationStart(otpTypeEnum: OTPTypeEnum.sms);
+ // Dismiss keyboard before validation
+ FocusScope.of(context).unfocus();
+
+ if (ValidationUtils.isValidatePhone(
+ phoneNumber: authVM.phoneNumberController.text,
+ onOkPress: () {
+ Navigator.of(context).pop();
+ },
+ )) {
+ await authVM.onRegistrationStart(otpTypeEnum: OTPTypeEnum.sms);
+ }
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedBorderColor,
@@ -245,7 +280,17 @@ class _RegisterNew extends State {
child: CustomButton(
text: LocaleKeys.sendOTPWHATSAPP.tr(),
onPressed: () async {
- await authVM.onRegistrationStart(otpTypeEnum: OTPTypeEnum.whatsapp);
+ // Dismiss keyboard before validation
+ FocusScope.of(context).unfocus();
+
+ if (ValidationUtils.isValidatePhone(
+ phoneNumber: authVM.phoneNumberController.text,
+ onOkPress: () {
+ Navigator.of(context).pop();
+ },
+ )) {
+ await authVM.onRegistrationStart(otpTypeEnum: OTPTypeEnum.whatsapp);
+ }
},
backgroundColor: AppColors.whiteColor,
borderColor: AppColors.borderOnlyColor,
diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart
index d711402..9f55c85 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,8 +114,9 @@ class _SavedLogin extends State {
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(0, 10, 0, 10),
- icon: getTypeIcons(loginType.toInt), //loginType == LoginTypeEnum.sms ? AppAssets.sms :AppAssets.whatsapp,
- iconColor: loginType != LoginTypeEnum.whatsapp ? Colors.white: null ,
+ icon: getTypeIcons(loginType.toInt),
+ //loginType == LoginTypeEnum.sms ? AppAssets.sms :AppAssets.whatsapp,
+ iconColor: loginType != LoginTypeEnum.whatsapp ? Colors.white : null,
),
),
],
@@ -226,7 +227,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;
@@ -272,11 +273,12 @@ class _SavedLogin extends State {
width: MediaQuery.of(context).size.width * 0.05,
),
Expanded(
- child: Container(
+ child: SizedBox(
height: 56,
child: CustomButton(
text: LocaleKeys.switchAccount.tr(),
- onPressed: () {
+ onPressed: () async {
+ await authVm.clearDefaultInputValues();
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (BuildContext context) => LoginScreen()),
);
diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart
new file mode 100644
index 0000000..baccbe2
--- /dev/null
+++ b/lib/presentation/profile_settings/profile_settings.dart
@@ -0,0 +1,173 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_swiper_view/flutter_swiper_view.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/features/profile_settings/profile_settings_view_model.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/chip/app_custom_chip_widget.dart';
+import 'package:provider/provider.dart';
+
+class ProfileSettings extends StatefulWidget {
+ ProfileSettings({Key? key}) : super(key: key);
+
+ @override
+ _ProfileSettingsState createState() {
+ return _ProfileSettingsState();
+ }
+}
+
+class _ProfileSettingsState extends State {
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ }
+
+ int length = 3;
+ final SwiperController _controller = SwiperController();
+
+ int _index = 0;
+
+ @override
+ Widget build(BuildContext context) {
+ return CollapsingListView(
+ title: "Profile & Settings".needTranslation,
+ logout: () {},
+ isClose: true,
+ child: SingleChildScrollView(
+ padding: EdgeInsets.only(top: 24, bottom: 24),
+ physics: NeverScrollableScrollPhysics(),
+ child: Consumer(
+ builder: (context, model, child) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Swiper(
+ itemCount: length,
+ layout: SwiperLayout.STACK,
+ loop: true,
+ itemWidth: MediaQuery.of(context).size.width - 42,
+ indicatorLayout: PageIndicatorLayout.COLOR,
+ axisDirection: AxisDirection.right,
+ controller: _controller,
+ itemHeight: 210 + 16,
+ pagination: const SwiperPagination(
+ alignment: Alignment.bottomCenter,
+ margin: EdgeInsets.only(top: 210 + 8 + 24),
+ builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor),
+ ),
+ itemBuilder: (BuildContext context, int index) {
+ return FamilyCardWidget().paddingOnly(right: 16);
+ },
+ ),
+ GridView(
+ gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 105 / 105, crossAxisSpacing: 9, mainAxisSpacing: 9),
+ physics: const NeverScrollableScrollPhysics(),
+ padding: const EdgeInsets.all(24),
+ shrinkWrap: true,
+ children: [
+ Container(
+ padding: EdgeInsets.all(16),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.h,
+ hasShadow: true,
+ ),
+ child: Column(
+ children: [
+ Row(
+ children: [
+ Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.h, height: 40.h),
+ ],
+ )
+ ],
+ ),
+ )
+ ],
+ )
+ ],
+ );
+ },
+ ),
+ ),
+ );
+ }
+}
+
+class FamilyCardWidget extends StatelessWidget {
+ FamilyCardWidget();
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 20.h,
+ hasShadow: true,
+ ),
+ child: Column(
+ children: [
+ Column(
+ spacing: 8.h,
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ spacing: 8.h,
+ children: [
+ Image.asset(true ? AppAssets.male_img : AppAssets.femaleImg, width: 56.h, height: 56.h),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ spacing: 0.h,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ "Mahmoud Shrouf Shrouf".toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1),
+ AppCustomChipWidget(
+ icon: AppAssets.file_icon,
+ labelText: "File no: 3423443",
+ iconSize: 14,
+ ),
+ ],
+ ).expanded,
+ Icon(Icons.qr_code, size: 56)
+ ],
+ ).expanded,
+ SizedBox(
+ width: double.infinity,
+ child: Wrap(
+ alignment: WrapAlignment.start,
+ spacing: 8.h,
+ children: [
+ AppCustomChipWidget(labelText: "35 Years Old"),
+ AppCustomChipWidget(labelText: "Blood: A+"),
+ AppCustomChipWidget(
+ icon: AppAssets.insurance_active_icon,
+ labelText: "Insurance Active",
+ iconColor: AppColors.bgGreenColor,
+ iconSize: 14,
+ backgroundColor: AppColors.bgGreenColor.withValues(alpha: 0.15),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ).paddingOnly(top: 16, right: 16, left: 16, bottom: 12).expanded,
+ Divider(
+ height: 1,
+ thickness: 1,
+ color: Color(0x30D2D2D2),
+ ),
+ CustomButton(icon: AppAssets.add_family, text: "Add a new family member", onPressed: () {}).paddingOnly(top: 12, right: 16, left: 16, bottom: 16),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart
index 9ba939b..29aee3d 100644
--- a/lib/services/dialog_service.dart
+++ b/lib/services/dialog_service.dart
@@ -1,10 +1,28 @@
+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/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
+import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
+import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
+import 'package:hmg_patient_app_new/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/bottomsheet/exception_bottom_sheet.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:provider/provider.dart';
abstract class DialogService {
Future showErrorBottomSheet({required String message, Function()? onOkPressed});
+
+ Future showExceptionBottomSheet({required String message, required Function() onOkPressed, Function()? onCancelPressed});
+
+ Future showCommonBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed});
+
+ Future showPhoneNumberPickerSheet({String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress});
+// TODO : Need to be Fixed showPhoneNumberPickerSheet ( From Login ADn Signup Bottom Sheet Move Here
}
class DialogServiceImp implements DialogService {
@@ -23,9 +41,166 @@ class DialogServiceImp implements DialogService {
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
- builder: (_) => _ErrorBottomSheet(message: message, onOkPressed: onOkPressed),
+ builder: (_) => _ErrorBottomSheet(message: message, onOkPressed: onOkPressed ?? () {}),
+ );
+ }
+
+ @override
+ Future showExceptionBottomSheet({required String message, required Function() onOkPressed, Function()? onCancelPressed}) async {
+ final context = navigationService.navigatorKey.currentContext;
+ if (context == null) return;
+
+ await showModalBottomSheet(
+ context: context,
+ isScrollControlled: false,
+ shape: const RoundedRectangleBorder(
+ borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
+ ),
+ builder: (_) => ExceptionBottomSheet(
+ message: message,
+ showCancel: onCancelPressed != null ? true : false,
+ onOkPressed: onOkPressed,
+ onCancelPressed: () {
+ if (onCancelPressed != null) {
+ Navigator.of(context).pop();
+ }
+ },
+ ),
);
}
+
+ @override
+ Future showCommonBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed}) async {
+ final context = navigationService.navigatorKey.currentContext;
+ if (context == null) return;
+ showCommonBottomSheetWithoutHeight(context, title: label ?? "", child: exceptionBottomSheetWidget(context: context, message: message, onOkPressed: onOkPressed, onCancelPressed: onCancelPressed),
+ callBackFunc: () {
+ });
+ }
+
+ @override
+ Future showPhoneNumberPickerSheet({String? label, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) async {
+ final context = navigationService.navigatorKey.currentContext;
+ if (context == null) return;
+ showCommonBottomSheetWithoutHeight(context,
+ title: label ?? "", child: showPhoneNumberPickerWidget(context: context, message: message, onSMSPress: onSMSPress, onWhatsappPress: onWhatsappPress), callBackFunc: () {});
+ }
+}
+
+Widget exceptionBottomSheetWidget({required BuildContext context, required String message, required Function() onOkPressed, Function()? onCancelPressed}) {
+ return Column(
+ children: [
+ (message ?? "").toText16(isBold: false, color: AppColors.textColor),
+ SizedBox(height: 10.h),
+ SizedBox(height: 24.h),
+ if (onOkPressed != null && onCancelPressed != null)
+ Row(
+ children: [
+ Expanded(
+ child: CustomButton(
+ text: LocaleKeys.cancel.tr(),
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ backgroundColor: AppColors.secondaryLightRedColor,
+ borderColor: AppColors.secondaryLightRedColor,
+ textColor: AppColors.primaryRedColor,
+ icon: AppAssets.cancel,
+ iconColor: AppColors.primaryRedColor,
+ ),
+ ),
+ SizedBox(width: 5.h),
+ Expanded(
+ child: CustomButton(
+ text: onCancelPressed != null ? LocaleKeys.confirm.tr() : LocaleKeys.ok.tr(),
+ onPressed: onOkPressed,
+ backgroundColor: AppColors.bgGreenColor,
+ borderColor: AppColors.bgGreenColor,
+ textColor: Colors.white,
+ icon: AppAssets.confirm,
+ ),
+ ),
+ ],
+ ),
+ if (onOkPressed != null && onCancelPressed == null)
+ Padding(
+ padding: EdgeInsets.only(bottom: 10.h),
+ child: CustomButton(
+ text: LocaleKeys.ok.tr(),
+ onPressed: (onOkPressed != null && onCancelPressed == null)
+ ? () {
+ Navigator.of(context).pop();
+ }
+ : onOkPressed,
+ backgroundColor: AppColors.primaryRedColor,
+ borderColor: AppColors.primaryRedBorderColor,
+ textColor: Colors.white,
+ icon: AppAssets.confirm,
+ ),
+ ),
+ ],
+ );
+}
+
+Widget showPhoneNumberPickerWidget({required BuildContext context, String? message, required Function() onSMSPress, required Function() onWhatsappPress}) {
+ return StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) {
+ AuthenticationViewModel authViewModel = context.read();
+ return Column(
+ children: [
+ (message ?? "").toText16(isBold: false, color: AppColors.textColor),
+ SizedBox(height: 10.h),
+ Padding(
+ padding: EdgeInsets.only(bottom: 10.h),
+ child: CustomButton(
+ text: LocaleKeys.sendOTPSMS.tr(),
+ onPressed: onSMSPress,
+ backgroundColor: AppColors.primaryRedColor,
+ borderColor: AppColors.primaryRedBorderColor,
+ textColor: AppColors.whiteColor,
+ icon: AppAssets.message,
+ ),
+ ),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Padding(
+ padding: EdgeInsets.symmetric(horizontal: 8.h),
+ child: LocaleKeys.oR.tr().toText16(color: AppColors.textColor),
+ ),
+ ],
+ ),
+ Padding(
+ padding: EdgeInsets.only(bottom: 10.h, top: 10.h),
+ child: CustomButton(
+ text: LocaleKeys.sendOTPWHATSAPP.tr(),
+ onPressed: onWhatsappPress,
+ backgroundColor: Colors.white,
+ borderColor: AppColors.borderOnlyColor,
+ textColor: AppColors.textColor,
+ icon: AppAssets.whatsapp,
+ iconColor: null,
+ ),
+ ),
+ ],
+ );
+ //return Padding(
+ // padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
+ // child: SingleChildScrollView(
+ // child: GenericBottomSheet(
+ // countryCode: authViewModel.selectedCountrySignup.countryCode,
+ // initialPhoneNumber: "",
+ // textController: authViewModel.phoneNumberController,
+ // isEnableCountryDropdown: true,
+ // onCountryChange: authViewModel.onCountryChange,
+ // onChange: authViewModel.onPhoneNumberChange,
+ // buttons: [
+ //
+ // ],
+ // ),
+ // ),
+ // );
+ });
}
class _ErrorBottomSheet extends StatelessWidget {
@@ -71,7 +246,7 @@ class _ErrorBottomSheet extends StatelessWidget {
borderRadius: BorderRadius.circular(8),
),
),
- child: const Text("OK", style: TextStyle(color: Colors.white)).onPress((){
+ child: const Text("OK", style: TextStyle(color: Colors.white)).onPress(() {
Navigator.of(context).pop();
}),
),
diff --git a/lib/services/error_handler_service.dart b/lib/services/error_handler_service.dart
index 092cf38..d4c15f2 100644
--- a/lib/services/error_handler_service.dart
+++ b/lib/services/error_handler_service.dart
@@ -3,12 +3,13 @@ import 'dart:io';
import 'package:hmg_patient_app_new/core/exceptions/api_exception.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/core/utils/loading_utils.dart';
+import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
abstract class ErrorHandlerService {
- Future handleError({required Failure failure, Function() onOkPressed});
+ Future handleError({required Failure failure, Function() onOkPressed, Function(Failure)? onUnHandledFailure});
}
class ErrorHandlerServiceImp implements ErrorHandlerService {
@@ -23,7 +24,7 @@ class ErrorHandlerServiceImp implements ErrorHandlerService {
});
@override
- Future handleError({required Failure failure, Function()? onOkPressed}) async {
+ Future handleError({required Failure failure, Function()? onOkPressed, Function(Failure)? onUnHandledFailure}) async {
if (failure is APIException) {
loggerService.errorLogs("API Exception: ${failure.message}");
} else if (failure is ServerFailure) {
@@ -44,9 +45,14 @@ class ErrorHandlerServiceImp implements ErrorHandlerService {
} else if (failure is InvalidCredentials) {
loggerService.errorLogs("Invalid Credentials : ${failure.message}");
await _showDialog(failure, title: "Unknown Error");
+ } else if (failure is UserIntimationFailure) {
+ if (onUnHandledFailure != null) {
+ onUnHandledFailure(failure);
+ } else {
+ await _showDialog(failure, title: "Error", onOkPressed: onOkPressed);
+ }
} else {
loggerService.errorLogs("Unhandled failure type: $failure");
-
await _showDialog(failure, title: "Error", onOkPressed: onOkPressed);
}
}
@@ -55,6 +61,7 @@ class ErrorHandlerServiceImp implements ErrorHandlerService {
if (LoadingUtils.isLoading) {
LoadingUtils.hideFullScreenLoader();
}
+
await dialogService.showErrorBottomSheet(message: failure.message, onOkPressed: onOkPressed);
}
}
diff --git a/lib/widgets/appbar/app_bar_widget.dart b/lib/widgets/appbar/app_bar_widget.dart
index 4bc08a9..1b6b8f1 100644
--- a/lib/widgets/appbar/app_bar_widget.dart
+++ b/lib/widgets/appbar/app_bar_widget.dart
@@ -32,11 +32,11 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
title: Padding(
padding: EdgeInsets.symmetric(horizontal: 10.h),
child: Row(
- mainAxisAlignment: MainAxisAlignment.start,
+ // mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: Align(
- alignment: Alignment.centerLeft,
+ alignment: context.locale.languageCode == "ar" ? Alignment.centerRight : Alignment.centerLeft,
child: GestureDetector(
onTap: onBackPressed,
child: Utils.buildSvgWithAssets(icon: AppAssets.arrow_back, width: 32.h, height: 32.h),
@@ -53,7 +53,7 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
if (!hideLogoAndLang)
Expanded(
child: Align(
- alignment: Alignment.centerRight,
+ alignment: context.locale.languageCode == "ar" ? Alignment.centerLeft : Alignment.centerRight,
child: LanguageSelector(
currentLanguage: context.locale.languageCode,
showOnlyIcon: false,
diff --git a/lib/widgets/bottomsheet/generic_bottom_sheet.dart b/lib/widgets/bottomsheet/generic_bottom_sheet.dart
index 74062ef..4c7f5af 100644
--- a/lib/widgets/bottomsheet/generic_bottom_sheet.dart
+++ b/lib/widgets/bottomsheet/generic_bottom_sheet.dart
@@ -22,6 +22,7 @@ class GenericBottomSheet extends StatefulWidget {
final bool isEnableCountryDropdown;
final bool isFromSavedLogin;
Function(String?)? onChange;
+ final bool autoFocus;
// FocusNode myFocusNode;
@@ -36,6 +37,7 @@ class GenericBottomSheet extends StatefulWidget {
this.isEnableCountryDropdown = false,
this.isFromSavedLogin = false,
this.onChange,
+ this.autoFocus = false,
// required this.myFocusNode
});
@@ -44,17 +46,28 @@ class GenericBottomSheet extends StatefulWidget {
}
class _GenericBottomSheetState extends State {
+ late FocusNode _textFieldFocusNode;
+
@override
void initState() {
super.initState();
+ _textFieldFocusNode = FocusNode();
if (!widget.isForEmail && widget.textController != null) {
widget.textController!.text = widget.initialPhoneNumber ?? "";
}
+
+ // Auto focus the text field if specified
+ if (widget.autoFocus && widget.textController != null) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ _textFieldFocusNode.requestFocus();
+ });
+ }
}
@override
void dispose() {
+ _textFieldFocusNode.dispose();
super.dispose();
}
@@ -65,6 +78,8 @@ class _GenericBottomSheetState extends State {
bottom: Platform.isIOS ? false : true,
child: GestureDetector(
onTap: () {
+ // Dismiss keyboard and unfocus text field
+ _textFieldFocusNode.unfocus();
FocusScope.of(context).unfocus();
},
child: Directionality(
@@ -90,6 +105,9 @@ class _GenericBottomSheetState extends State {
: LocaleKeys.enterPhoneNumber.tr().toText24()),
InkWell(
onTap: () {
+ // Dismiss keyboard before closing
+ _textFieldFocusNode.unfocus();
+ FocusScope.of(context).unfocus();
Navigator.of(context).pop();
},
child: Padding(
@@ -115,6 +133,8 @@ class _GenericBottomSheetState extends State {
labelText: widget.isForEmail ? LocaleKeys.email : LocaleKeys.phoneNumber,
hintText: widget.isForEmail ? "demo@gmail.com" : "5xxxxxxxx",
controller: widget.textController!,
+ focusNode: _textFieldFocusNode,
+ autoFocus: widget.autoFocus,
padding: EdgeInsets.all(8.h),
keyboardType: widget.isForEmail ? TextInputType.emailAddress : TextInputType.number,
onChange: (value) {
diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart
index cc0c03b..b88c922 100644
--- a/lib/widgets/common_bottom_sheet.dart
+++ b/lib/widgets/common_bottom_sheet.dart
@@ -126,25 +126,27 @@ void showCommonBottomSheetWithoutHeight(
top: false,
left: false,
right: false,
- 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(
- mainAxisSize: MainAxisSize.min,
- spacing: 16.h,
- children: [
- Row(
- crossAxisAlignment: CrossAxisAlignment.center,
+ 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(
+ mainAxisSize: MainAxisSize.min,
+ spacing: 16.h,
children: [
- if (title.isNotEmpty) title.toText20(weight: FontWeight.w600).expanded,
- Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, iconColor: Color(0xff2B353E)).onPress(() {
- Navigator.of(context).pop();
- }),
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ title.toText20(weight: FontWeight.w600).expanded,
+ Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, iconColor: Color(0xff2B353E)).onPress(() {
+ Navigator.of(context).pop();
+ }),
+ ],
+ ),
+ child,
],
- ),
- child,
- ],
- )) : child,
+ ))
+ : child,
);
}).then((value) {
callBackFunc();
diff --git a/lib/widgets/dropdown/country_dropdown_widget.dart b/lib/widgets/dropdown/country_dropdown_widget.dart
index 4e41c31..4507423 100644
--- a/lib/widgets/dropdown/country_dropdown_widget.dart
+++ b/lib/widgets/dropdown/country_dropdown_widget.dart
@@ -1,6 +1,8 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
+import 'package:hmg_patient_app_new/core/app_state.dart';
+import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
@@ -60,6 +62,7 @@ class _CustomCountryDropdownState extends State {
@override
Widget build(BuildContext context) {
+ AppState appState = getIt.get();
return Container(
height: 40.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 10.h),
@@ -120,7 +123,11 @@ class _CustomCountryDropdownState extends State {
),
if (!widget.isFromBottomSheet)
Text(
- selectedCountry != null ? selectedCountry!.displayName : "Select Country",
+ selectedCountry != null
+ ? appState.getLanguageCode() == "ar"
+ ? selectedCountry!.nameArabic
+ : selectedCountry!.displayName
+ : "Select Country",
style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, letterSpacing: -0.2),
),
],
@@ -132,7 +139,7 @@ class _CustomCountryDropdownState extends State {
if (textFocusNode.hasFocus) {
textFocusNode.unfocus();
}
-
+ AppState appState = getIt.get();
RenderBox renderBox = context.findRenderObject() as RenderBox;
Offset offset = renderBox.localToGlobal(Offset.zero);
@@ -172,7 +179,9 @@ class _CustomCountryDropdownState extends State {
children: [
Utils.buildSvgWithAssets(icon: country.iconPath, width: 38.h, height: 38.h),
if (!widget.isFromBottomSheet) SizedBox(width: 12.h),
- if (!widget.isFromBottomSheet) Text(country.displayName, style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, letterSpacing: -0.2)),
+ if (!widget.isFromBottomSheet)
+ Text(appState.getLanguageCode() == "ar" ? country.nameArabic : country.displayName,
+ style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, letterSpacing: -0.2)),
],
),
),
diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart
index 0fb9223..33b8183 100644
--- a/lib/widgets/input_widget.dart
+++ b/lib/widgets/input_widget.dart
@@ -77,31 +77,32 @@ class TextInputWidget extends StatelessWidget {
final FocusNode _focusNode = FocusNode();
- KeyboardActionsConfig get _keyboardActionsConfig {
- return KeyboardActionsConfig(
- keyboardActionsPlatform: KeyboardActionsPlatform.ALL,
- keyboardBarColor: const Color(0xFFCAD1D9), //Apple keyboard color
- actions: [
- KeyboardActionsItem(
- focusNode: focusNode ?? _focusNode,
- toolbarButtons: [
- (node) {
- return GestureDetector(
- onTap: () => node.unfocus(),
- child: Container(
- padding: const EdgeInsets.all(12.0),
- child: "Done".toText16(weight: FontWeight.w500, color: AppColors.infoColor),
- ),
- );
- }
- ],
- ),
- ],
- );
- }
+ // KeyboardActionsConfig get _keyboardActionsConfig {
+ // return KeyboardActionsConfig(
+ // keyboardActionsPlatform: KeyboardActionsPlatform.ALL,
+ // keyboardBarColor: const Color(0xFFCAD1D9), //Apple keyboard color
+ // actions: [
+ // KeyboardActionsItem(
+ // focusNode: focusNode ?? _focusNode,
+ // toolbarButtons: [
+ // (node) {
+ // return GestureDetector(
+ // onTap: () => node.unfocus(),
+ // child: Container(
+ // padding: const EdgeInsets.all(12.0),
+ // child: "Done".toText16(weight: FontWeight.w500, color: AppColors.infoColor),
+ // ),
+ // );
+ // }
+ // ],
+ // ),
+ // ],
+ // );
+ // }
@override
Widget build(BuildContext context) {
+ AppState appState = getIt.get();
final errorColor = AppColors.primaryRedColor;
return Column(
mainAxisSize: MainAxisSize.min,
@@ -134,7 +135,7 @@ class TextInputWidget extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- _buildLabelText(),
+ _buildLabelText().paddingOnly(right: (appState.getLanguageCode() == "ar" ? 10 : 0)),
_buildTextField(context),
],
),
@@ -183,6 +184,7 @@ class TextInputWidget extends StatelessWidget {
onTap: () async {
bool isGregorian = true;
final picked = await showHijriGregBottomSheet(context,
+ isShowTimeSlots: true,
switcherIcon: Utils.buildSvgWithAssets(icon: AppAssets.language, width: 24.h, height: 24.h),
language: appState.getLanguageCode()!,
initialDate: DateTime.now(),
@@ -217,30 +219,27 @@ class TextInputWidget extends StatelessWidget {
}
Widget _buildTextField(BuildContext context) {
- return KeyboardActions(
- config: _keyboardActionsConfig,
- disableScroll: true,
- child: TextField(
- enabled: isEnable,
- scrollPadding: EdgeInsets.zero,
- keyboardType: keyboardType,
- controller: controller,
- readOnly: isReadOnly,
- textAlignVertical: TextAlignVertical.top,
- textAlign: TextAlign.left,
- textDirection: TextDirection.ltr,
- onChanged: onChange,
- focusNode: focusNode ?? _focusNode,
- autofocus: autoFocus,
- textInputAction: TextInputAction.done,
- cursorHeight: isWalletAmountInput! ? 40.h : 18.h,
- style: TextStyle(fontSize: fontSize!.fSize, height: isWalletAmountInput! ? 1 / 4 : 21 / 14, fontWeight: FontWeight.w500, color: AppColors.textColor, letterSpacing: -0.2),
- decoration: InputDecoration(
- isDense: true,
- hintText: hintText,
- hintStyle: TextStyle(fontSize: 14.fSize, height: 21 / 16, fontWeight: FontWeight.w500, color: Color(0xff898A8D), letterSpacing: -0.2),
- prefixIconConstraints: BoxConstraints(minWidth: 45.h),
- suffixIconConstraints: BoxConstraints(maxHeight: 20.h),
+ return TextField(
+ enabled: isEnable,
+ scrollPadding: EdgeInsets.zero,
+ keyboardType: keyboardType,
+ controller: controller,
+ readOnly: isReadOnly,
+ textAlignVertical: TextAlignVertical.top,
+ textAlign: TextAlign.left,
+ textDirection: TextDirection.ltr,
+ onChanged: onChange,
+ focusNode: focusNode ?? _focusNode,
+ autofocus: autoFocus,
+ textInputAction: TextInputAction.done,
+ cursorHeight: isWalletAmountInput! ? 40.h : 18.h,
+ style: TextStyle(fontSize: fontSize!.fSize, height: isWalletAmountInput! ? 1 / 4 : 21 / 14, fontWeight: FontWeight.w500, color: AppColors.textColor, letterSpacing: -0.2),
+ decoration: InputDecoration(
+ isDense: true,
+ hintText: hintText,
+ hintStyle: TextStyle(fontSize: 14.fSize, height: 21 / 16, fontWeight: FontWeight.w500, color: Color(0xff898A8D), letterSpacing: -0.2),
+ prefixIconConstraints: BoxConstraints(minWidth: 45.h),
+ suffixIconConstraints: BoxConstraints(maxHeight: 20.h),
prefixIcon: prefix == null
? null
: Text(
@@ -259,11 +258,10 @@ class TextInputWidget extends StatelessWidget {
child: suffix,
)
: null,
- contentPadding: EdgeInsets.zero,
- border: InputBorder.none,
- focusedBorder: InputBorder.none,
- enabledBorder: InputBorder.none,
- ),
+ contentPadding: EdgeInsets.zero,
+ border: InputBorder.none,
+ focusedBorder: InputBorder.none,
+ enabledBorder: InputBorder.none,
),
);
}
diff --git a/lib/widgets/otp_widget.dart b/lib/widgets/otp_widget.dart
deleted file mode 100644
index d606090..0000000
--- a/lib/widgets/otp_widget.dart
+++ /dev/null
@@ -1,377 +0,0 @@
-import 'dart:async';
-
-import 'package:flutter/animation.dart';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/rendering.dart';
-import 'package:flutter/services.dart';
-
-typedef OnDone = void Function(String text);
-
-class ProvidedPinBoxTextAnimation {
- static AnimatedSwitcherTransitionBuilder scalingTransition = (child, animation) {
- return ScaleTransition(
- child: child,
- scale: animation,
- );
- };
-
- static AnimatedSwitcherTransitionBuilder defaultNoTransition = (Widget child, Animation animation) {
- return child;
- };
-}
-
-class OTPWidget extends StatefulWidget {
- final int maxLength;
- late TextEditingController? controller;
-
- final Color defaultBorderColor;
- final Color pinBoxColor;
- final double pinBoxBorderWidth;
- final double pinBoxRadius;
- final bool hideDefaultKeyboard;
-
- final TextStyle? pinTextStyle;
- final double pinBoxHeight;
- final double pinBoxWidth;
- final OnDone? onDone;
- final bool hasError;
- final Color errorBorderColor;
- final Color textBorderColor;
- final Function(String)? onTextChanged;
- final bool autoFocus;
- final FocusNode? focusNode;
- final AnimatedSwitcherTransitionBuilder? pinTextAnimatedSwitcherTransition;
- final Duration pinTextAnimatedSwitcherDuration;
- final TextDirection textDirection;
- final TextInputType keyboardType;
- final EdgeInsets pinBoxOuterPadding;
-
- OTPWidget({
- Key? key,
- this.maxLength = 4,
- this.controller,
- this.pinBoxWidth = 70.0,
- this.pinBoxHeight = 70.0,
- this.pinTextStyle,
- this.onDone,
- this.defaultBorderColor = Colors.black,
- this.textBorderColor = Colors.black,
- this.pinTextAnimatedSwitcherTransition,
- this.pinTextAnimatedSwitcherDuration = const Duration(),
- this.hasError = false,
- this.errorBorderColor = Colors.red,
- this.onTextChanged,
- this.autoFocus = false,
- this.focusNode,
- this.textDirection = TextDirection.ltr,
- this.keyboardType = TextInputType.number,
- this.pinBoxOuterPadding = const EdgeInsets.symmetric(horizontal: 4.0),
- this.pinBoxColor = Colors.white,
- this.pinBoxBorderWidth = 2.0,
- this.pinBoxRadius = 0,
- this.hideDefaultKeyboard = false,
- }) : super(key: key);
-
- @override
- State createState() {
- return OTPWidgetState();
- }
-}
-
-class OTPWidgetState extends State with SingleTickerProviderStateMixin {
- late AnimationController _highlightAnimationController;
- late FocusNode focusNode;
- String text = "";
- int currentIndex = 0;
- List strList = [];
- bool hasFocus = false;
-
- @override
- void didUpdateWidget(OTPWidget oldWidget) {
- super.didUpdateWidget(oldWidget);
- focusNode = widget.focusNode ?? focusNode;
-
- if (oldWidget.maxLength < widget.maxLength) {
- setState(() {
- currentIndex = text.length;
- });
- widget.controller?.text = text;
- widget.controller?.selection = TextSelection.collapsed(offset: text.length);
- } else if (oldWidget.maxLength > widget.maxLength && widget.maxLength > 0 && text.length > 0 && text.length > widget.maxLength) {
- setState(() {
- text = text.substring(0, widget.maxLength);
- currentIndex = text.length;
- });
- widget.controller?.text = text;
- widget.controller?.selection = TextSelection.collapsed(offset: text.length);
- }
- }
-
- _calculateStrList() {
- if (strList.length > widget.maxLength) {
- strList.length = widget.maxLength;
- }
- while (strList.length < widget.maxLength) {
- strList.add("");
- }
- }
-
- @override
- void initState() {
- super.initState();
- focusNode = widget.focusNode ?? FocusNode();
- _highlightAnimationController = AnimationController(vsync: this);
- _initTextController();
- _calculateStrList();
- widget.controller!.addListener(_controllerListener);
- focusNode.addListener(_focusListener);
- }
-
- void _controllerListener() {
- if (mounted == true) {
- setState(() {
- _initTextController();
- });
- var onTextChanged = widget.onTextChanged;
- if (onTextChanged != null) {
- onTextChanged(widget.controller?.text ?? "");
- }
- }
- }
-
- void _focusListener() {
- if (mounted == true) {
- setState(() {
- hasFocus = focusNode?.hasFocus ?? false;
- });
- }
- }
-
- void _initTextController() {
- if (widget.controller == null) {
- return;
- }
- strList.clear();
- var text = widget.controller?.text ?? "";
- if (text.isNotEmpty) {
- if (text.length > widget.maxLength) {
- throw Exception("TextEditingController length exceeded maxLength!");
- }
- }
- for (var i = 0; i < text.length; i++) {
- strList.add(text[i]);
- }
- }
-
- double get _width {
- var width = 0.0;
- for (var i = 0; i < widget.maxLength; i++) {
- width += widget.pinBoxWidth;
- if (i == 0) {
- width += widget.pinBoxOuterPadding.left;
- } else if (i + 1 == widget.maxLength) {
- width += widget.pinBoxOuterPadding.right;
- } else {
- width += widget.pinBoxOuterPadding.left;
- }
- }
- return width;
- }
-
- @override
- void dispose() {
- if (widget.focusNode == null) {
- focusNode.dispose();
- } else {
- focusNode.removeListener(_focusListener);
- }
- _highlightAnimationController.dispose();
- widget.controller?.removeListener(_controllerListener);
-
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return Stack(
- children: [
- _otpTextInput(),
- _touchPinBoxRow(),
- ],
- );
- }
-
- Widget _touchPinBoxRow() {
- return widget.hideDefaultKeyboard
- ? _pinBoxRow(context)
- : GestureDetector(
- behavior: HitTestBehavior.opaque,
- onTap: () {
- if (hasFocus) {
- FocusScope.of(context).requestFocus(FocusNode());
- Future.delayed(Duration(milliseconds: 100), () {
- FocusScope.of(context).requestFocus(focusNode);
- });
- } else {
- FocusScope.of(context).requestFocus(focusNode);
- }
- },
- child: _pinBoxRow(context),
- );
- }
-
- Widget _otpTextInput() {
- var transparentBorder = OutlineInputBorder(
- borderSide: BorderSide(
- color: Colors.transparent,
- width: 0.0,
- ),
- );
- return Container(
- width: _width,
- height: widget.pinBoxHeight,
- child: TextField(
- autofocus: !kIsWeb ? widget.autoFocus : false,
- enableInteractiveSelection: false,
- focusNode: focusNode,
- controller: widget.controller,
- keyboardType: widget.keyboardType,
- inputFormatters: widget.keyboardType == TextInputType.number ? [FilteringTextInputFormatter.digitsOnly] : null,
- style: TextStyle(
- height: 0.1,
- color: Colors.transparent,
- ),
- decoration: InputDecoration(
- contentPadding: EdgeInsets.all(0),
- focusedErrorBorder: transparentBorder,
- errorBorder: transparentBorder,
- disabledBorder: transparentBorder,
- enabledBorder: transparentBorder,
- focusedBorder: transparentBorder,
- counterText: null,
- counterStyle: null,
- helperStyle: TextStyle(
- height: 0.0,
- color: Colors.transparent,
- ),
- labelStyle: TextStyle(height: 0.1),
- fillColor: Colors.transparent,
- border: InputBorder.none,
- ),
- cursorColor: Colors.transparent,
- showCursor: false,
- maxLength: widget.maxLength,
- onChanged: _onTextChanged,
- ),
- );
- }
-
- void _onTextChanged(text) {
- var onTextChanged = widget.onTextChanged;
- if (onTextChanged != null) {
- onTextChanged(text);
- }
- setState(() {
- this.text = text;
- if (text.length >= currentIndex) {
- for (int i = currentIndex; i < text.length; i++) {
- strList[i] = text[i];
- }
- }
- currentIndex = text.length;
- });
- if (text.length == widget.maxLength) {
- FocusScope.of(context).requestFocus(FocusNode());
- var onDone = widget.onDone;
- if (onDone != null) {
- onDone(text);
- }
- }
- }
-
- Widget _pinBoxRow(BuildContext context) {
- _calculateStrList();
- List pinCodes = List.generate(widget.maxLength, (int i) {
- return _buildPinCode(i, context);
- });
- return Row(
- children: pinCodes,
- mainAxisSize: MainAxisSize.min,
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- );
- }
-
- Widget _buildPinCode(int i, BuildContext context) {
- Color borderColor;
- Color pinBoxColor = widget.pinBoxColor;
-
- if (widget.hasError) {
- borderColor = widget.errorBorderColor;
- } else if (i < text.length) {
- borderColor = widget.textBorderColor;
- } else {
- borderColor = widget.defaultBorderColor;
- pinBoxColor = widget.pinBoxColor;
- }
-
- EdgeInsets insets;
- if (i == 0) {
- insets = EdgeInsets.only(
- left: 0,
- top: widget.pinBoxOuterPadding.top,
- right: widget.pinBoxOuterPadding.right,
- bottom: widget.pinBoxOuterPadding.bottom,
- );
- } else if (i == strList.length - 1) {
- insets = EdgeInsets.only(
- left: widget.pinBoxOuterPadding.left,
- top: widget.pinBoxOuterPadding.top,
- right: 0,
- bottom: widget.pinBoxOuterPadding.bottom,
- );
- } else {
- insets = widget.pinBoxOuterPadding;
- }
- return Container(
- key: ValueKey("container$i"),
- alignment: Alignment.center,
- padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 1.0),
- margin: insets,
- child: _animatedTextBox(strList[i], i),
- decoration: BoxDecoration(
- border: Border.all(
- color: borderColor,
- width: widget.pinBoxBorderWidth,
- ),
- color: pinBoxColor,
- borderRadius: BorderRadius.circular(widget.pinBoxRadius),
- ),
- width: widget.pinBoxWidth,
- height: widget.pinBoxHeight,
- );
- }
-
- Widget _animatedTextBox(String text, int i) {
- if (widget.pinTextAnimatedSwitcherTransition != null) {
- return AnimatedSwitcher(
- duration: widget.pinTextAnimatedSwitcherDuration,
- transitionBuilder: widget.pinTextAnimatedSwitcherTransition ??
- (Widget child, Animation animation) {
- return child;
- },
- child: Text(
- text,
- key: ValueKey("$text$i"),
- style: widget.pinTextStyle,
- ),
- );
- } else {
- return Text(
- text,
- key: ValueKey("${strList[i]}$i"),
- style: widget.pinTextStyle,
- );
- }
- }
-}
diff --git a/pubspec.yaml b/pubspec.yaml
index 147d7ac..a635cd1 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -78,6 +78,7 @@ dependencies:
keyboard_actions: ^4.2.0
path_provider: ^2.0.8
open_filex: ^4.7.0
+ flutter_swiper_view: ^1.1.8
dev_dependencies:
flutter_test: