calendar implementation contd.

pull/34/head
haroon amjad 2 months ago
parent 73f8baa77e
commit 6f745f3666

@ -438,12 +438,13 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
void _onOtpChanged(String value) { void _onOtpChanged(String value) {
// Handle clipboard paste or programmatic input // Handle clipboard paste or programmatic input
if (value.length > 1) { if (value.length >= 4) {
String? otp = _extractOtpFromText(value); _onOtpCompleted(value);
if (otp != null) { // String? otp = _extractOtpFromText(value);
autoFillOtp(otp); // if (otp != null) {
return; // autoFillOtp(otp);
} // return;
// }
} }
// The OTPWidget will automatically call onDone when complete // The OTPWidget will automatically call onDone when complete
@ -622,19 +623,53 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
/// Auto fill OTP into text fields /// Auto fill OTP into text fields
void autoFillOtp(String otp) { void autoFillOtp(String otp) {
if (!mounted) return;
if (otp.length != _otpLength) return; if (otp.length != _otpLength) return;
// Clear any existing text first try {
_otpController.clear(); // Clear any existing text first
_otpController.clear();
// Add a small delay to ensure the UI is updated
Future.delayed(const Duration(milliseconds: 50), () { // Use WidgetsBinding to ensure the widget tree is ready
_otpController.text = otp; WidgetsBinding.instance.addPostFrameCallback((_) {
// Move cursor to the end if (!mounted) return;
_otpController.selection = TextSelection.fromPosition(
TextPosition(offset: otp.length), try {
); // Set the text first
}); _otpController.text = otp;
// Use a longer delay for iOS and add validation
Future.delayed(const Duration(milliseconds: 300), () {
if (!mounted) return;
try {
// Only attempt to set selection if conditions are met
if (_otpController.text == otp &&
_otpController.text.length == _otpLength &&
_otpController.text.length <= _otpController.text.length) {
final newSelection = TextSelection.fromPosition(
TextPosition(offset: _otpController.text.length),
);
// Validate selection before setting
if (newSelection.baseOffset <= _otpController.text.length &&
newSelection.extentOffset <= _otpController.text.length) {
_otpController.selection = newSelection;
}
}
} catch (selectionError) {
// Silently fail on selection - text is already set correctly
debugPrint('Selection error (non-critical): $selectionError');
}
});
} catch (textError) {
debugPrint('Error setting OTP text: $textError');
}
});
} catch (e) {
debugPrint('Error in autoFillOtp: $e');
}
} }
/// Clear OTP fields /// Clear OTP fields

@ -18,8 +18,7 @@ abstract class BookAppointmentsRepo {
Future<Either<Failure, GenericApiModel<DoctorsProfileResponseModel>>> getDoctorProfile(int clinicID, int projectID, int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError}); Future<Either<Failure, GenericApiModel<DoctorsProfileResponseModel>>> getDoctorProfile(int clinicID, int projectID, int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<dynamic>>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, bool isBookingForLiveCare, Future<Either<Failure, GenericApiModel<dynamic>>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, bool isBookingForLiveCare, {Function(dynamic)? onSuccess, Function(String)? onError});
{Function(dynamic)? onSuccess, Function(String)? onError});
} }
class BookAppointmentsRepoImp implements BookAppointmentsRepo { class BookAppointmentsRepoImp implements BookAppointmentsRepo {
@ -77,9 +76,9 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
@override @override
Future<Either<Failure, GenericApiModel<List<DoctorsListResponseModel>>>> getDoctorsList(int clinicID, int projectID, bool isNearest, int doctorId, String doctorName, Future<Either<Failure, GenericApiModel<List<DoctorsListResponseModel>>>> getDoctorsList(int clinicID, int projectID, bool isNearest, int doctorId, String doctorName,
{isContinueDentalPlan = false}) async { {isContinueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
Map<String, dynamic> mapDevice = { Map<String, dynamic> mapDevice = {
"ClinicID": clinicID, "ClinicID": (doctorName == "") ? clinicID : 0,
"ProjectID": projectID, "ProjectID": projectID,
"DoctorName": doctorName, //!= null ? doctorId : 0, "DoctorName": doctorName, //!= null ? doctorId : 0,
"ContinueDentalPlan": isContinueDentalPlan, "ContinueDentalPlan": isContinueDentalPlan,
@ -98,6 +97,7 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
body: mapDevice, body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) { onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType; failure = failureType;
onError!(error);
}, },
onSuccess: (response, statusCode, {messageStatus, errorMessage}) { onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try { try {
@ -208,7 +208,7 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
// final freeSlotsList = list.map((item) => DoctorsListResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<DoctorsListResponseModel>(); // final freeSlotsList = list.map((item) => DoctorsListResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<DoctorsListResponseModel>();
apiResponse = GenericApiModel<List<DoctorsListResponseModel>>( apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus, messageStatus: messageStatus,
statusCode: statusCode, statusCode: statusCode,
errorMessage: null, errorMessage: null,

@ -1,9 +1,13 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/free_slot.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/free_slot.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_profile_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_profile_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/timeslots.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart';
class BookAppointmentsViewModel extends ChangeNotifier { class BookAppointmentsViewModel extends ChangeNotifier {
@ -12,6 +16,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
bool isClinicsListLoading = false; bool isClinicsListLoading = false;
bool isDoctorsListLoading = false; bool isDoctorsListLoading = false;
bool isDoctorProfileLoading = false; bool isDoctorProfileLoading = false;
bool isDoctorSearchByNameStarted = false;
List<GetClinicsListResponseModel> clinicsList = []; List<GetClinicsListResponseModel> clinicsList = [];
List<GetClinicsListResponseModel> _filteredClinicsList = []; List<GetClinicsListResponseModel> _filteredClinicsList = [];
@ -26,6 +31,14 @@ class BookAppointmentsViewModel extends ChangeNotifier {
late DoctorsProfileResponseModel doctorsProfileResponseModel; late DoctorsProfileResponseModel doctorsProfileResponseModel;
List<FreeSlot> slotsList = []; List<FreeSlot> slotsList = [];
List<TimeSlot> docFreeSlots = [];
List<TimeSlot> dayEvents = [];
List<TimeSlot> nextDayEvents = [];
String selectedAppointmentDate = "";
String selectedAppointmentTime = "";
dynamic freeSlotsResponse;
BookAppointmentsRepo bookAppointmentsRepo; BookAppointmentsRepo bookAppointmentsRepo;
ErrorHandlerService errorHandlerService; ErrorHandlerService errorHandlerService;
@ -52,7 +65,6 @@ class BookAppointmentsViewModel extends ChangeNotifier {
isDoctorProfileLoading = true; isDoctorProfileLoading = true;
clinicsList.clear(); clinicsList.clear();
doctorsList.clear(); doctorsList.clear();
slotsList.clear();
notifyListeners(); notifyListeners();
} }
@ -87,11 +99,22 @@ class BookAppointmentsViewModel extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
setIsDoctorSearchByNameStarted(bool value) {
isDoctorSearchByNameStarted = value;
notifyListeners();
}
setDoctorsProfile(DoctorsProfileResponseModel profile) { setDoctorsProfile(DoctorsProfileResponseModel profile) {
doctorsProfileResponseModel = profile; doctorsProfileResponseModel = profile;
notifyListeners(); notifyListeners();
} }
setSelectedAppointmentDateTime(String date, String time) {
selectedAppointmentDate = date;
selectedAppointmentTime = time;
notifyListeners();
}
void onTabChanged(int index) { void onTabChanged(int index) {
selectedTabIndex = index; selectedTabIndex = index;
notifyListeners(); notifyListeners();
@ -121,10 +144,13 @@ class BookAppointmentsViewModel extends ChangeNotifier {
//TODO: Make the API dynamic with parameters for ProjectID, isNearest, languageID, doctorId, doctorName //TODO: Make the API dynamic with parameters for ProjectID, isNearest, languageID, doctorId, doctorName
Future<void> getDoctorsList( Future<void> getDoctorsList(
{int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", isContinueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { {int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", isContinueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID!, 0, isNearest, doctorId, doctorName); doctorsList.clear();
final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID ?? 0, projectID, isNearest, doctorId, doctorName);
result.fold( result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure), (failure) async {
onError!("No doctors found for the search criteria".needTranslation);
},
(apiResponse) { (apiResponse) {
if (apiResponse.messageStatus == 2) { if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@ -161,21 +187,38 @@ class BookAppointmentsViewModel extends ChangeNotifier {
); );
} }
//TODO: Handle the cases for LiveCare Schedule
Future<void> getDoctorFreeSlots({bool isBookingForLiveCare = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { Future<void> getDoctorFreeSlots({bool isBookingForLiveCare = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
slotsList.clear(); docFreeSlots.clear();
DateTime date;
final DateFormat formatter = DateFormat('HH:mm');
final DateFormat dateFormatter = DateFormat('yyyy-MM-dd');
Map<DateTime, List> _eventsParsed;
final result = await bookAppointmentsRepo.getDoctorFreeSlots(selectedDoctor.clinicID ?? 0, selectedDoctor.projectID ?? 0, selectedDoctor.doctorID ?? 0, isBookingForLiveCare, onError: onError); final result = await bookAppointmentsRepo.getDoctorFreeSlots(selectedDoctor.clinicID ?? 0, selectedDoctor.projectID ?? 0, selectedDoctor.doctorID ?? 0, isBookingForLiveCare, onError: onError);
result.fold( result.fold(
(failure) async {}, (failure) async {
print(failure);
},
(apiResponse) { (apiResponse) {
if (apiResponse.messageStatus == 2) { if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage ?? "Unknown error occurred"); onError!(apiResponse.errorMessage ?? "Unknown error occurred");
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) { } else if (apiResponse.messageStatus == 1) {
if (apiResponse.data == null || apiResponse.data!.isEmpty) {
// apiResponse.data.forEach((element) { onError!("No free slots available".tr());
// slotsList.add(FreeSlot.fromJson(element)); return;
// }); }
freeSlotsResponse = apiResponse.data;
apiResponse.data!.forEach((element) {
// date = (isLiveCareSchedule != null && isLiveCareSchedule)
// ? DateUtil.convertStringToDate(element)
// :
date = DateUtil.convertStringToDateSaudiTimezone(element, int.parse(selectedDoctor.projectID.toString()));
slotsList.add(FreeSlot(date, ['slot']));
docFreeSlots.add(TimeSlot(isoTime: formatter.format(date), start: new DateTime(date.year, date.month, date.day, 0, 0, 0, 0), end: date, vidaDate: element));
});
notifyListeners(); notifyListeners();
if (onSuccess != null) { if (onSuccess != null) {

@ -33,7 +33,7 @@ class DoctorsProfileResponseModel {
List<String>? specialty; List<String>? specialty;
num? actualDoctorRate; num? actualDoctorRate;
String? consultationFee; String? consultationFee;
double? decimalDoctorRate; num? decimalDoctorRate;
String? doctorImageURL; String? doctorImageURL;
String? doctorMobileNumber; String? doctorMobileNumber;
num? doctorRate; num? doctorRate;

@ -177,8 +177,8 @@ class DoctorsListResponseModel {
regionNameN = json['RegionNameN']; regionNameN = json['RegionNameN'];
serviceID = json['ServiceID']; serviceID = json['ServiceID'];
setupID = json['SetupID']; setupID = json['SetupID'];
speciality = json['Speciality'].cast<String>(); speciality = json['Speciality'] != null ? json['Speciality'].cast<String>() : [];
specialityN = json['SpecialityN'].cast<String>(); // specialityN = json['SpecialityN'].cast<String>();
transactionType = json['TransactionType']; transactionType = json['TransactionType'];
virtualEmploymentType = json['VirtualEmploymentType']; virtualEmploymentType = json['VirtualEmploymentType'];
workingHours = json['WorkingHours']; workingHours = json['WorkingHours'];

@ -0,0 +1,8 @@
class TimeSlot {
String? isoTime;
DateTime? start;
DateTime? end;
String? vidaDate;
TimeSlot({required this.isoTime, required this.start, required this.end, this.vidaDate});
}

@ -138,6 +138,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h),
], ],
).onPress(() { ).onPress(() {
bookAppointmentsViewModel.setIsDoctorSearchByNameStarted(false);
Navigator.of(context).push( Navigator.of(context).push(
FadePage( FadePage(
page: SearchDoctorByName(), page: SearchDoctorByName(),

@ -1,19 +1,25 @@
import 'dart:math';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart';
import 'package:hmg_patient_app_new/core/app_assets.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/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/dependencies.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/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/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/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart';
import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class DoctorProfilePage extends StatelessWidget { class DoctorProfilePage extends StatelessWidget {
@ -51,6 +57,7 @@ class DoctorProfilePage extends StatelessWidget {
).circle(100), ).circle(100),
SizedBox(width: 8.h), SizedBox(width: 8.h),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
("${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorTitleForProfile} ${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName}") ("${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorTitleForProfile} ${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName}")
.toString() .toString()
@ -106,8 +113,59 @@ class DoctorProfilePage extends StatelessWidget {
), ),
child: CustomButton( child: CustomButton(
text: "View available appointments".needTranslation, text: "View available appointments".needTranslation,
onPressed: () { onPressed: () async {
showCommonBottomSheetWithoutHeight(context, title: "".needTranslation, child: Utils.getLoadingWidget(), callBackFunc: () {}, isFullScreen: false); LoaderBottomSheet.showLoader();
await bookAppointmentsViewModel.getDoctorFreeSlots(
isBookingForLiveCare: false,
onSuccess: (dynamic respData) async {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
title: "Pick a Date",
context,
child: AppointmentCalendar(),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
// //TODO: Calendar design to be changed as per new design & handle Dubai & KSA both cases
// final DateTimeResult picked = await showHijriGregBottomSheet(context,
// design: Design.v2,
// isShowTimeSlots: true,
// height: 750.h,
// dateTimeSlots: bookAppointmentsViewModel.freeSlotsResponse,
// showCalendarToggle: false,
// switcherIcon: Utils.buildSvgWithAssets(icon: AppAssets.language, width: 24.h, height: 24.h),
// language: appState.getLanguageCode()!,
// initialDate: DateUtil.convertStringToDate(bookAppointmentsViewModel.freeSlotsResponse.first),
// okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)),
// cancelWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)),
// onCalendarTypeChanged: (bool value) {
// // isGregorian = true;
// });
// if (picked != null) {
// print("Selected Date & Time:");
// print(picked.date.toIso8601String());
// String formattedTime = '${picked.time.hour.toString().padLeft(2, '0')}:${picked.time.minute.toString().padLeft(2, '0')}';
// print(formattedTime);
//
// bookAppointmentsViewModel.setSelectedAppointmentDateTime(picked.date.toIso8601String().split("T")[0], formattedTime);
// } else {
// print("User cancelled the picker");
// return;
// }
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}, },
backgroundColor: AppColors.primaryRedColor, backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor,

@ -1,15 +1,27 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.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/utils/size_utils.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/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_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/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/doctor_card.dart';
import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.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/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/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart';
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';
import '../../features/book_appointments/models/resp_models/doctors_list_response_model.dart';
class SearchDoctorByName extends StatefulWidget { class SearchDoctorByName extends StatefulWidget {
const SearchDoctorByName({super.key}); const SearchDoctorByName({super.key});
@ -28,48 +40,170 @@ class _SearchDoctorByNameState extends State<SearchDoctorByName> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
appState = getIt.get<AppState>();
return Scaffold( return Scaffold(
backgroundColor: AppColors.bgScaffoldColor, backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView( body: Column(
title: "Choose Doctor".needTranslation, children: [
child: SingleChildScrollView( Expanded(
child: Padding( child: CollapsingListView(
padding: EdgeInsets.symmetric(horizontal: 24.h), title: "Choose Doctor".needTranslation,
child: Column( child: SingleChildScrollView(
children: [ child: Padding(
SizedBox(height: 16.h), padding: EdgeInsets.symmetric(horizontal: 24.h),
TextInputWidget( child: Column(
labelText: LocaleKeys.search.tr(context: context), children: [
hintText: LocaleKeys.doctorName.tr(context: context), SizedBox(height: 16.h),
controller: searchEditingController, TextInputWidget(
isEnable: true, labelText: LocaleKeys.search.tr(context: context),
prefix: null, hintText: LocaleKeys.doctorName.tr(context: context),
autoFocus: false, controller: searchEditingController,
isBorderAllowed: false, isEnable: true,
keyboardType: TextInputType.text, prefix: null,
focusNode: textFocusNode, autoFocus: false,
suffix: searchEditingController.text.isNotEmpty isBorderAllowed: false,
? GestureDetector( keyboardType: TextInputType.text,
onTap: () { focusNode: textFocusNode,
searchEditingController.clear(); suffix: searchEditingController.text.isNotEmpty
bookAppointmentsViewModel.filterClinics(""); ? GestureDetector(
textFocusNode.unfocus(); onTap: () {
}, searchEditingController.clear();
child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown), // bookAppointmentsViewModel.filterClinics("");
) textFocusNode.unfocus();
: null, },
onChange: (value) { child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown),
bookAppointmentsViewModel.filterClinics(value!); )
}, : null,
padding: EdgeInsets.symmetric( onChange: (value) {
vertical: ResponsiveExtension(10).h, // bookAppointmentsViewModel.filterClinics(value!);
horizontal: ResponsiveExtension(15).h, },
padding: EdgeInsets.symmetric(
vertical: ResponsiveExtension(10).h,
horizontal: ResponsiveExtension(15).h,
),
),
SizedBox(height: 16.h),
Consumer<BookAppointmentsViewModel>(builder: (context, bookAppointmentsVM, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
bookAppointmentsVM.isDoctorSearchByNameStarted
? ListView.separated(
padding: EdgeInsets.only(top: 24.h),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: bookAppointmentsVM.isDoctorsListLoading ? 5 : bookAppointmentsVM.doctorsList.length,
itemBuilder: (context, index) {
return bookAppointmentsVM.isDoctorsListLoading
? DoctorCard(
doctorsListResponseModel: DoctorsListResponseModel(),
isLoading: true,
)
: 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,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: DoctorCard(
doctorsListResponseModel: bookAppointmentsVM.doctorsList[index],
isLoading: false,
).onPress(() async {
bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.doctorsList[index]);
// bookAppointmentsVM.setSelectedDoctor(DoctorsListResponseModel());
LoaderBottomSheet.showLoader();
await bookAppointmentsVM.getDoctorProfile(onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
FadePage(
page: DoctorProfilePage(),
),
);
}, onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}),
),
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
)
: SizedBox.shrink(),
SizedBox(height: 24.h),
],
);
}),
],
), ),
), ),
], ),
),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true,
), ),
child: CustomButton(
text: LocaleKeys.search.tr(context: context),
onPressed: () async {
textFocusNode.unfocus();
if (searchEditingController.text.isNotEmpty) {
bookAppointmentsViewModel.setIsDoctorSearchByNameStarted(true);
bookAppointmentsViewModel.setIsDoctorsListLoading(true);
// LoaderBottomSheet.showLoader();
await bookAppointmentsViewModel.getDoctorsList(
doctorName: searchEditingController.text,
onSuccess: (dynamic respData) {},
onError: (err) {
bookAppointmentsViewModel.setIsDoctorSearchByNameStarted(false);
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Please enter doctor name to search"),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 16,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 50.h,
icon: AppAssets.search_icon,
iconColor: AppColors.whiteColor,
iconSize: 20.h,
).paddingSymmetrical(24.h, 24.h),
), ),
), ],
), ),
); );
} }

@ -144,6 +144,7 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
}, },
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
), ),
SizedBox(height: 24.h),
], ],
); );
}), }),

@ -0,0 +1,242 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_export.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/utils/date_util.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/free_slot.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/timeslots.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:provider/provider.dart';
import 'package:smooth_corner/smooth_corner.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart';
class AppointmentCalendar extends StatefulWidget {
const AppointmentCalendar({super.key});
@override
State<AppointmentCalendar> createState() => _AppointmentCalendarState();
}
class _AppointmentCalendarState extends State<AppointmentCalendar> {
late CalendarController _calendarController;
late AppState appState;
late BookAppointmentsViewModel bookAppointmentsViewModel;
var selectedDate = "";
var selectedNextDate = "";
int selectedButtonIndex = 0;
int selectedNextDayButtonIndex = -1;
List<TimeSlot> dayEvents = [];
List<TimeSlot> nextDayEvents = [];
late Map<DateTime, List> _events;
static String? selectedTime;
bool isWaitingAppointmentAvailable = false;
final _selectedDay = DateTime.now();
@override
void initState() {
scheduleMicrotask(() {
_calendarController = CalendarController();
_events = {
_selectedDay: ['Event A0']
};
_onDaySelected(DateUtil.convertStringToDate(bookAppointmentsViewModel.freeSlotsResponse[0]));
});
super.initState();
}
@override
Widget build(BuildContext context) {
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
appState = getIt.get<AppState>();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 380.h,
child: SfCalendar(
controller: _calendarController,
minDate: DateTime.now(),
showNavigationArrow: true,
headerHeight: 60.h,
headerStyle: CalendarHeaderStyle(
backgroundColor: AppColors.scaffoldBgColor,
textAlign: TextAlign.start,
textStyle: TextStyle(fontSize: 18.fSize, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.primaryRedColor, fontFamily: "Poppins"),
),
viewHeaderStyle: ViewHeaderStyle(
backgroundColor: AppColors.scaffoldBgColor,
dayTextStyle: TextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.textColor),
),
view: CalendarView.month,
todayHighlightColor: Colors.transparent,
todayTextStyle: TextStyle(color: AppColors.textColor),
selectionDecoration: ShapeDecoration(
color: AppColors.transparent,
shape: SmoothRectangleBorder(
borderRadius: BorderRadius.circular(12 ?? 0),
smoothness: 1,
side: BorderSide(color: AppColors.primaryRedColor, width: 1.5),
),
),
cellBorderColor: AppColors.transparent,
dataSource: MeetingDataSource(_getDataSource()),
monthCellBuilder: (context, details) => Padding(
padding: EdgeInsets.all(12.h),
child: details.date.day.toString().toText14(
isCenter: true,
color: details.date == _calendarController.selectedDate ? AppColors.primaryRedColor : AppColors.textColor,
),
),
monthViewSettings: MonthViewSettings(
dayFormat: "EEE",
appointmentDisplayMode: MonthAppointmentDisplayMode.indicator,
showTrailingAndLeadingDates: false,
appointmentDisplayCount: 1,
monthCellStyle: MonthCellStyle(
textStyle: TextStyle(fontSize: 19.fSize),
),
),
onTap: (CalendarTapDetails details) {
_calendarController.selectedDate = details.date;
_onDaySelected(details.date!);
},
),
),
//TODO: Add Next Day Span here
Container(
height: 40,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: dayEvents.length,
itemBuilder: (context, index) {
return Container(
margin: EdgeInsets.only(right: (index == dayEvents.length - 1) ? 16 : 5.0, left: index == 0 ? 16 : 5),
child: AppCustomChipWidget(
labelText: dayEvents[index].isoTime,
backgroundColor: AppColors.whiteColor,
),
// index == selectedButtonIndex ? getSelectedButton(index) : getNormalButton(index),
);
},
),
),
],
);
}
List<Meeting> _getDataSource() {
final List<Meeting> meetings = <Meeting>[];
for (var slot in bookAppointmentsViewModel.freeSlotsResponse) {
final startTime = DateUtil.convertStringToDate(slot);
final endTime = startTime.add(const Duration(minutes: 15));
meetings.add(Meeting(
"Available", // Or leave empty with ""
startTime,
endTime,
AppColors.primaryRedColor,
false,
"" // Optional notes
));
}
return meetings;
}
// TODO:
openTimeSlotsPickerForDate(DateTime dateStart, List<TimeSlot> freeSlots) {
dayEvents.clear();
DateTime dateStartObj = new DateTime(dateStart.year, dateStart.month, dateStart.day, 0, 0, 0, 0, 0);
if (isWaitingAppointmentAvailable && DateUtils.isSameDay(dateStart, DateTime.now())) {
dayEvents.add(TimeSlot(isoTime: "Waiting Appointment", start: DateTime.now(), end: DateTime.now(), vidaDate: ""));
}
freeSlots.forEach((v) {
if (v.start == dateStartObj) dayEvents.add(v);
});
selectedButtonIndex = 0;
List<Map<String, dynamic>> timeList = [];
for (var i = 0; i < dayEvents.length; i++) {
Map<String, dynamic> timeSlot = {"isoTime": dayEvents[i].isoTime, "start": dayEvents[i].start.toString(), "end": dayEvents[i].end.toString(), "vidaDate": dayEvents[i].vidaDate};
timeList.add(timeSlot);
}
selectedTime = dayEvents[selectedButtonIndex].isoTime;
}
void _onDaySelected(DateTime day) {
final DateFormat formatter = DateFormat('yyyy-MM-dd');
setState(() {
selectedDate = DateUtil.getWeekDayMonthDayYearDateFormatted(day, "en");
selectedNextDate = DateUtil.getWeekDayMonthDayYearDateFormatted(day.add(Duration(days: 1)), "en");
_calendarController.selectedDate = day;
openTimeSlotsPickerForDate(day, bookAppointmentsViewModel.docFreeSlots);
selectedDate = formatter.format(day);
selectedNextDayButtonIndex = -1;
print(_calendarController.selectedDate);
});
}
}
class MeetingDataSource extends CalendarDataSource {
MeetingDataSource(List<Meeting> source) {
appointments = source;
}
@override
DateTime getStartTime(int index) {
return _getMeetingData(index)!.from;
}
@override
DateTime getEndTime(int index) {
return _getMeetingData(index)!.to;
}
@override
String getSubject(int index) {
return _getMeetingData(index)!.eventName;
}
@override
Color getColor(int index) {
return _getMeetingData(index)!.background;
}
@override
bool isAllDay(int index) {
return _getMeetingData(index)!.isAllDay;
}
Meeting? _getMeetingData(int index) {
final dynamic meeting = appointments?[index];
Meeting? meetingData;
if (meeting is Meeting) {
meetingData = meeting;
}
return meetingData;
}
}
class Meeting {
Meeting(this.eventName, this.from, this.to, this.background, this.isAllDay, this.notes);
String eventName;
DateTime from;
DateTime to;
Color background;
bool isAllDay;
String notes;
}

@ -1,4 +1,5 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:family_bottom_sheet/family_bottom_sheet.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.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/app_state.dart';
@ -122,9 +123,27 @@ class _RechargeWalletPageState extends State<RechargeWalletPage> {
), ),
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h),
], ],
).onPress(() { ).onPress(() async {
// showCommonBottomSheetWithoutHeight(context, title: "Select Medical File".needTranslation, child: SelectMedicalFile(), callBackFunc: () {}, isFullScreen: false); // showCommonBottomSheetWithoutHeight(context, title: "Select Medical File".needTranslation, child: SelectMedicalFile(), callBackFunc: () {}, isFullScreen: false);
showCommonBottomSheetWithoutHeight(context, title: "Select Medical File".needTranslation, child: const MultiPageBottomSheet(), callBackFunc: () {}, isFullScreen: false); // showCommonBottomSheetWithoutHeight(context, title: "Select Medical File".needTranslation, child: const MultiPageBottomSheet(), callBackFunc: () {}, isFullScreen: false);
await FamilyModalSheet.show<void>(
context: context,
contentBackgroundColor: AppColors.scaffoldBgColor,
backgroundColor: AppColors.bottomSheetBgColor,
mainContentPadding: EdgeInsets.all(24.h),
isScrollControlled: true,
safeAreaMinimum: EdgeInsets.zero,
useSafeArea: false,
sheetAnimationStyle: AnimationStyle(
duration: Duration(milliseconds: 500), // Custom animation duration
reverseDuration: Duration(milliseconds: 300), // Custom reverse animation duration
),
builder: (ctx) {
return const MultiPageBottomSheet();
},
// Optional configurations
);
}), }),
SizedBox(height: 16.h), SizedBox(height: 16.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h),

@ -38,199 +38,89 @@ class MultiPageBottomSheet extends StatefulWidget {
} }
class _MultiPageBottomSheetState extends State<MultiPageBottomSheet> { class _MultiPageBottomSheetState extends State<MultiPageBottomSheet> {
final PageController _pageController = PageController();
int _currentPage = 0;
final int _totalPages = 3;
late AppState appState; late AppState appState;
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
void _nextPage() {
if (_currentPage < _totalPages - 1) {
_pageController.animateToPage(
_currentPage + 2,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
}
void _previousPage() {
if (_currentPage > 0) {
_pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
appState = getIt.get<AppState>(); appState = getIt.get<AppState>();
return Container( return SizedBox(
height: MediaQuery.of(context).size.height * 0.38, height: MediaQuery.of(context).size.height * 0.38,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 12.h), Row(
Expanded( crossAxisAlignment: CrossAxisAlignment.center,
child: PageView(
controller: _pageController,
onPageChanged: (index) {
setState(() {
_currentPage = index;
});
},
children: [
_buildPage1(),
_buildPage2(),
_buildPage3(),
],
),
),
],
),
);
}
Widget _buildPage1() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 16.h,
hasShadow: false,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Column( "Select Medical File".toText20(weight: FontWeight.w600).expanded,
crossAxisAlignment: CrossAxisAlignment.start, Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, iconColor: Color(0xff2B353E)).onPress(() {
children: [ Navigator.of(context).pop();
LocaleKeys.myMedicalFile.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), }),
"${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500),
],
),
Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h),
], ],
).paddingAll(16.h),
).onPress(() {
Navigator.of(context).pop();
}),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 16.h,
hasShadow: false,
), ),
child: Row( Container(
mainAxisAlignment: MainAxisAlignment.spaceBetween, decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
children: [ color: AppColors.whiteColor,
Column( borderRadius: 16.h,
crossAxisAlignment: CrossAxisAlignment.start, hasShadow: false,
children: [ ),
LocaleKeys.familyTitle.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), child: Row(
"Select a medical file from your family".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), mainAxisAlignment: MainAxisAlignment.spaceBetween,
], children: [
), Column(
Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), crossAxisAlignment: CrossAxisAlignment.start,
], children: [
).paddingAll(16.h), LocaleKeys.myMedicalFile.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500),
), "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500),
SizedBox(height: 16.h), ],
Container( ),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h),
color: AppColors.whiteColor, ],
borderRadius: 16.h, ).paddingAll(16.h),
hasShadow: false, ).onPress(() {
), Navigator.of(context).pop();
child: Row( }),
mainAxisAlignment: MainAxisAlignment.spaceBetween, SizedBox(height: 16.h),
children: [ Container(
Column( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
crossAxisAlignment: CrossAxisAlignment.start, color: AppColors.whiteColor,
children: [ borderRadius: 16.h,
LocaleKeys.otherAccount.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), hasShadow: false,
"Any active medical file from HMG".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h),
],
).paddingAll(16.h),
).onPress(() {
_pageController.animateToPage(
2,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}),
],
);
}
Widget _buildPage2() {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Page 2: Settings',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 16),
SwitchListTile(
title: const Text('Enable notifications'),
value: true,
onChanged: (value) {},
),
SwitchListTile(
title: const Text('Dark mode'),
value: false,
onChanged: (value) {},
),
const ListTile(
leading: Icon(Icons.language),
title: Text('Language'),
subtitle: Text('English'),
trailing: Icon(Icons.arrow_forward_ios),
),
],
),
);
}
Widget _buildPage3() {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Page 3: Summary',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 16),
const Card(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Configuration Complete!'),
SizedBox(height: 8),
Text('Your settings have been saved successfully.'),
],
),
), ),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.familyTitle.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500),
"Select a medical file from your family".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h),
],
).paddingAll(16.h),
), ),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 16.h,
hasShadow: false,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.otherAccount.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500),
"Any active medical file from HMG".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h),
],
).paddingAll(16.h),
).onPress(() {}),
], ],
), ),
); );

@ -233,6 +233,9 @@ class TextInputWidget extends StatelessWidget {
autofocus: autoFocus, autofocus: autoFocus,
textInputAction: TextInputAction.done, textInputAction: TextInputAction.done,
cursorHeight: isWalletAmountInput! ? 40.h : 18.h, cursorHeight: isWalletAmountInput! ? 40.h : 18.h,
onTapOutside: (event) {
FocusManager.instance.primaryFocus?.unfocus();
},
style: TextStyle(fontSize: fontSize!.fSize, height: isWalletAmountInput! ? 1 / 4 : 21 / 14, fontWeight: FontWeight.w500, color: AppColors.textColor, letterSpacing: -0.2), style: TextStyle(fontSize: fontSize!.fSize, height: isWalletAmountInput! ? 1 / 4 : 21 / 14, fontWeight: FontWeight.w500, color: AppColors.textColor, letterSpacing: -0.2),
decoration: InputDecoration( decoration: InputDecoration(
isDense: true, isDense: true,

@ -65,7 +65,7 @@ dependencies:
google_api_availability: ^5.0.1 google_api_availability: ^5.0.1
firebase_analytics: ^11.5.1 firebase_analytics: ^11.5.1
jiffy: ^6.4.3 jiffy: ^6.4.3
hijri_gregorian_calendar: ^0.1.0 hijri_gregorian_calendar: ^0.1.1
web: any web: any
flutter_staggered_animations: ^1.1.1 flutter_staggered_animations: ^1.1.1
@ -79,6 +79,7 @@ dependencies:
path_provider: ^2.0.8 path_provider: ^2.0.8
open_filex: ^4.7.0 open_filex: ^4.7.0
flutter_swiper_view: ^1.1.8 flutter_swiper_view: ^1.1.8
family_bottom_sheet: ^0.1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

Loading…
Cancel
Save