diff --git a/assets/images/svg/graph_icon.svg b/assets/images/svg/graph_icon.svg
new file mode 100644
index 0000000..7bb6fbb
--- /dev/null
+++ b/assets/images/svg/graph_icon.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/svg/green_tick_icon.svg b/assets/images/svg/green_tick_icon.svg
new file mode 100644
index 0000000..e041191
--- /dev/null
+++ b/assets/images/svg/green_tick_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/list_icon.svg b/assets/images/svg/list_icon.svg
new file mode 100644
index 0000000..e68f20b
--- /dev/null
+++ b/assets/images/svg/list_icon.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/images/svg/yellow_arrow_down_icon.svg b/assets/images/svg/yellow_arrow_down_icon.svg
new file mode 100644
index 0000000..f2ca09f
--- /dev/null
+++ b/assets/images/svg/yellow_arrow_down_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart
index f366329..039787b 100644
--- a/lib/core/api/api_client.dart
+++ b/lib/core/api/api_client.dart
@@ -210,7 +210,7 @@ class ApiClientImp implements ApiClient {
final int statusCode = response.statusCode;
log("uri: ${Uri.parse(url.trim())}");
log("body: ${json.encode(body)}");
- // log("response.body: ${response.body}");
+ log("response.body: ${response.body}");
if (statusCode < 200 || statusCode >= 400) {
onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data"));
logApiEndpointError(endPoint, 'Error While Fetching data', statusCode);
diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart
index a5283c0..8a91a6f 100644
--- a/lib/core/api_consts.dart
+++ b/lib/core/api_consts.dart
@@ -437,14 +437,6 @@ var RATE_DOCTOR_RESPONSE = 'Services/OUTPs.svc/REST/insertAppointmentQuestionRat
var GET_PATIENT_ALLERGIES = 'Services/Patients.svc/REST/GetPatientAllergies';
-// H2O
-var H2O_GET_USER_PROGRESS = "Services/H2ORemainder.svc/REST/H2O_GetUserProgress";
-var H2O_INSERT_USER_ACTIVITY = "Services/H2ORemainder.svc/REST/H2O_InsertUserActivity";
-var H2O_GET_USER_DETAIL = "Services/H2ORemainder.svc/REST/H2O_GetUserDetails_New";
-var H2O_UPDATE_USER_DETAIL = "Services/H2ORemainder.svc/REST/H2O_UpdateUserDetails_New";
-var H2O_UNDO_USER_ACTIVITY = "Services/H2ORemainder.svc/REST/H2o_UndoUserActivity";
-//E_Referral Services
-
// Encillary Orders
var GET_ANCILLARY_ORDERS = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderList';
@@ -670,25 +662,6 @@ var addPayFortApplePayResponse = "Services/PayFort_Serv.svc/REST/AddResponse";
// Auth Provider Consts
-const String INSERT_DEVICE_IMEI = 'Services/Patients.svc/REST/Patient_INSERTDeviceIMEI';
-const String SELECT_DEVICE_IMEI = 'Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI';
-const String CHECK_PATIENT_AUTH = 'Services/Authentication.svc/REST/CheckPatientAuthentication';
-const GET_MOBILE_INFO = 'Services/Authentication.svc/REST/GetMobileLoginInfo';
-
-const FORGOT_PASSWORD = 'Services/Authentication.svc/REST/CheckActivationCodeForSendFileNo';
-const CHECK_PATIENT_FOR_REGISTRATION = "Services/Authentication.svc/REST/CheckPatientForRegisteration";
-
-const CHECK_USER_STATUS = "Services/NHIC.svc/REST/GetPatientInfo";
-const REGISTER_USER = 'Services/Authentication.svc/REST/PatientRegistration';
-const LOGGED_IN_USER_URL = 'Services/MobileNotifications.svc/REST/Insert_PatientMobileDeviceInfo';
-
-const FORGOT_PATIENT_ID = 'Services/Authentication.svc/REST/SendPatientIDSMSByMobileNumber';
-const DASHBOARD = 'Services/Patients.svc/REST/PatientDashboard';
-const PROFILE_SETTING = 'Services/Patients.svc/REST/GetPateintInfoForUpdate';
-const SAVE_SETTING = 'Services/Patients.svc/REST/UpdatePateintInfo';
-
-const DEACTIVATE_ACCOUNT = 'Services/Patients.svc/REST/PatientAppleActivation_InsertUpdate';
-
var ER_CREATE_ADVANCE_PAYMENT = "services/Doctors.svc/REST/ER_CreateAdvancePaymentForClinic";
//family Files
@@ -868,6 +841,7 @@ class ApiConsts {
//WATER CONSUMPTION
static String h2oGetUserProgress = "Services/H2ORemainder.svc/REST/H2O_GetUserProgress";
static String h2oInsertUserActivity = "Services/H2ORemainder.svc/REST/H2O_InsertUserActivity";
+ static String h2oInsertUserDetailsNew = "Services/H2ORemainder.svc/REST/H2O_InsertUserDetails_New";
static String h2oGetUserDetail = "Services/H2ORemainder.svc/REST/H2O_GetUserDetails_New";
static String h2oUpdateUserDetail = "Services/H2ORemainder.svc/REST/H2O_UpdateUserDetails_New";
static String h2oUndoUserActivity = "Services/H2ORemainder.svc/REST/H2o_UndoUserActivity";
@@ -883,3 +857,34 @@ class ApiConsts {
class ApiKeyConstants {
static final String googleMapsApiKey = 'AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng';
}
+
+//flutter: URL : https://hmgwebservices.com/Services/H2ORemainder.svc/REST/H2O_InsertUserActivity
+// flutter: {"IdentificationNo":"2530976584","MobileNumber":"504278212","QuantityIntake":200,"VersionID":20.0,"Channel":3,"LanguageID":2,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","PatientOutSA":0,"SessionID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","isDentalAllowedBackend":false,"DeviceTypeID":1,"PatientID":4515697,"TokenID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","PatientTypeID":1,"PatientType":1,"Latitude":37.785834,"Longitude":-122.406417}
+// flutter: response.body:
+// flutter: {"Date":null,"LanguageID":0,"ServiceName":0,"Time":null,"AndroidLink":null,"AuthenticationTokenID":null,"Data":null,"Dataw":false,"DietType":0,"DietTypeID":0,"ErrorCode":null,"ErrorEndUserMessage":null,"ErrorEndUserMessageN":null,"ErrorMessage":null,"ErrorStatusCode":0,"ErrorType":0,"FoodCategory":0,"IOSLink":null,"IsAuthenticated":true,"MealOrderStatus":0,"MealType":0,"MessageStatus":1,"NumberOfResultRecords":0,"PatientBlodType":null,"SuccessMsg":null,"SuccessMsgN":null,"VidaUpdatedResponse":null,"IsHMGPatient":false,"LogInTokenID":null,"PhysicalActivityData":null,"RowExists":0,"UserDetailData":null,"UserDetailData_New":null,"UserProgressForMonthData":null,"UserProgressForTodayData":[{"QuantityConsumed":200.00,"PercentageConsumed":9.41,"PercentageLeft":90.59,"QuantityLimit":2125.00}],"UserProgressForWeekData":null,"UserProgressHistoryData":[{"Quantity":200.000,"CreatedDate":"\/Date(1766911222217+0300)\/"}],"VerificationCode":null,"isSMSSent":false}
+
+// URL : https://hmgwebservices.com/Services/H2ORemainder.svc/REST/H2o_UndoUserActivity
+// flutter: {"Progress":1,"MobileNumber":"504278212","IdentificationNo":"2530976584","VersionID":20.0,"Channel":3,"LanguageID":2,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","PatientOutSA":0,"SessionID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","isDentalAllowedBackend":false,"DeviceTypeID":1,"PatientID":4515697,"TokenID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","PatientTypeID":1,"PatientType":1,"Latitude":37.785834,"Longitude":-122.406417}
+// flutter: response.body:
+// flutter: {"Date":null,"LanguageID":0,"ServiceName":0,"Time":null,"AndroidLink":null,"AuthenticationTokenID":null,"Data":null,"Dataw":false,"DietType":0,"DietTypeID":0,"ErrorCode":null,"ErrorEndUserMessage":null,"ErrorEndUserMessageN":null,"ErrorMessage":null,"ErrorStatusCode":0,"ErrorType":0,"FoodCategory":0,"IOSLink":null,"IsAuthenticated":false,"MealOrderStatus":0,"MealType":0,"MessageStatus":1,"NumberOfResultRecords":0,"PatientBlodType":null,"SuccessMsg":null,"SuccessMsgN":null,"VidaUpdatedResponse":null,"IsHMGPatient":false,"LogInTokenID":null,"PhysicalActivityData":null,"RowExists":0,"UserDetailData":null,"UserDetailData_New":null,"UserProgressForMonthData":null,"UserProgressForTodayData":[{"QuantityConsumed":0.00,"PercentageConsumed":0.00,"PercentageLeft":100.00,"QuantityLimit":2125.00}],"UserProgressForWeekData":null,"UserProgressHistoryData":[],"VerificationCode":null,"isSMSSent":false}
+
+// Progress":2 means weekly data
+
+// flutter: URL : https://hmgwebservices.com/Services/H2ORemainder.svc/REST/H2O_GetUserProgress
+// flutter: {"Progress":2,"MobileNumber":"504278212","IdentificationNo":"2530976584","VersionID":20.0,"Channel":3,"LanguageID":2,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","PatientOutSA":0,"SessionID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","isDentalAllowedBackend":false,"DeviceTypeID":1,"PatientID":4515697,"TokenID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","PatientTypeID":1,"PatientType":1,"Latitude":37.785834,"Longitude":-122.406417}
+// flutter: response.body:
+// [log] {"Date":null,"LanguageID":0,"ServiceName":0,"Time":null,"AndroidLink":null,"AuthenticationTokenID":null,"Data":null,"Dataw":false,"DietType":0,"DietTypeID":0,"ErrorCode":null,"ErrorEndUserMessage":null,"ErrorEndUserMessageN":null,"ErrorMessage":null,"ErrorStatusCode":0,"ErrorType":0,"FoodCategory":0,"IOSLink":null,"IsAuthenticated":false,"MealOrderStatus":0,"MealType":0,"MessageStatus":1,"NumberOfResultRecords":0,"PatientBlodType":null,"SuccessMsg":null,"SuccessMsgN":null,"VidaUpdatedResponse":null,"IsHMGPatient":false,"LogInTokenID":null,"PhysicalActivityData":null,"RowExists":0,"UserDetailData":null,"UserDetailData_New":null,"UserProgressForMonthData":null,"UserProgressForTodayData":null,"UserProgressForWeekData":[{"DayNumber":1,"DayDate":null,"DayName":"Sunday","PercentageConsumed":0},{"DayNumber":7,"DayDate":null,"DayName":"Saturday","PercentageConsumed":0},{"DayNumber":6,"DayDate":null,"DayName":"Friday","PercentageConsumed":0},{"DayNumber":5,"DayDate":null,"DayName":"Thursday","PercentageConsumed":0},{"DayNumber":4,"DayDate":null,"DayName":"Wednesday","PercentageConsumed":0},{"DayNumber":3,"DayDate":null,"DayName":"Tuesday","PercentageConsumed":0},{"DayNumber":2,"DayDate":null,"DayName":"Monday","PercentageConsumed":0}],"UserProgressHistoryData":[],"VerificationCode":null,"isSMSSent":false}
+
+// Progress":1 means daily data
+
+//URL : https://hmgwebservices.com/Services/H2ORemainder.svc/REST/H2O_GetUserProgress
+// flutter: {"Progress":1,"MobileNumber":"504278212","IdentificationNo":"2530976584","VersionID":20.0,"Channel":3,"LanguageID":2,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","PatientOutSA":0,"SessionID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","isDentalAllowedBackend":false,"DeviceTypeID":1,"PatientID":4515697,"TokenID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","PatientTypeID":1,"PatientType":1,"Latitude":37.785834,"Longitude":-122.406417}
+// flutter: response.body:
+// flutter: {"Date":null,"LanguageID":0,"ServiceName":0,"Time":null,"AndroidLink":null,"AuthenticationTokenID":null,"Data":null,"Dataw":false,"DietType":0,"DietTypeID":0,"ErrorCode":null,"ErrorEndUserMessage":null,"ErrorEndUserMessageN":null,"ErrorMessage":null,"ErrorStatusCode":0,"ErrorType":0,"FoodCategory":0,"IOSLink":null,"IsAuthenticated":false,"MealOrderStatus":0,"MealType":0,"MessageStatus":1,"NumberOfResultRecords":0,"PatientBlodType":null,"SuccessMsg":null,"SuccessMsgN":null,"VidaUpdatedResponse":null,"IsHMGPatient":false,"LogInTokenID":null,"PhysicalActivityData":null,"RowExists":0,"UserDetailData":null,"UserDetailData_New":null,"UserProgressForMonthData":null,"UserProgressForTodayData":[{"QuantityConsumed":0.00,"PercentageConsumed":0.00,"PercentageLeft":100.00,"QuantityLimit":2125.00}],"UserProgressForWeekData":null,"UserProgressHistoryData":[],"VerificationCode":null,"isSMSSent":false}
+
+// Progress":1 means monthly data
+
+// flutter: URL : https://hmgwebservices.com/Services/H2ORemainder.svc/REST/H2O_GetUserProgress
+// flutter: {"Progress":3,"MobileNumber":"504278212","IdentificationNo":"2530976584","VersionID":20.0,"Channel":3,"LanguageID":2,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","PatientOutSA":0,"SessionID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","isDentalAllowedBackend":false,"DeviceTypeID":1,"PatientID":4515697,"TokenID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","PatientTypeID":1,"PatientType":1,"Latitude":37.785834,"Longitude":-122.406417}
+// flutter: response.body:
+// [log] {"Date":null,"LanguageID":0,"ServiceName":0,"Time":null,"AndroidLink":null,"AuthenticationTokenID":null,"Data":null,"Dataw":false,"DietType":0,"DietTypeID":0,"ErrorCode":null,"ErrorEndUserMessage":null,"ErrorEndUserMessageN":null,"ErrorMessage":null,"ErrorStatusCode":0,"ErrorType":0,"FoodCategory":0,"IOSLink":null,"IsAuthenticated":false,"MealOrderStatus":0,"MealType":0,"MessageStatus":1,"NumberOfResultRecords":0,"PatientBlodType":null,"SuccessMsg":null,"SuccessMsgN":null,"VidaUpdatedResponse":null,"IsHMGPatient":false,"LogInTokenID":null,"PhysicalActivityData":null,"RowExists":0,"UserDetailData":null,"UserDetailData_New":null,"UserProgressForMonthData":[{"MonthNumber":1,"MonthName":"January","PercentageConsumed":0},{"MonthNumber":2,"MonthName":"February","PercentageConsumed":0},{"MonthNumber":3,"MonthName":"March","PercentageConsumed":0},{"MonthNumber":4,"MonthName":"April","PercentageConsumed":0},{"MonthNumber":5,"MonthName":"May","PercentageConsumed":0},{"MonthNumber":6,"MonthName":"June","PercentageConsumed":0},{"MonthNumber":7,"MonthName":"July","PercentageConsumed":0},{"MonthNumber":8,"MonthName":"August","PercentageConsumed":0},{"MonthNumber":9,"MonthName":"September","PercentageConsumed":0},{"MonthNumber":10,"MonthName":"October","PercentageConsumed":0},{"MonthNumber":11,"MonthName":"November","PercentageConsumed":0},{"MonthNumber":12,"MonthName":"December","PercentageConsumed":0}],"UserProgressForTodayData":null,"UserProgressForWeekData":null,"UserProgressHistoryData":[],"VerificationCode":null,"isSMSSent":false}
diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart
index 34224f8..a4fbc62 100644
--- a/lib/core/app_assets.dart
+++ b/lib/core/app_assets.dart
@@ -261,6 +261,10 @@ class AppAssets {
static const String minimizeIcon = '$svgBasePath/minimize_icon.svg';
static const String addIconDark = '$svgBasePath/add_icon_dark.svg';
static const String glassIcon = '$svgBasePath/glass_icon.svg';
+ static const String graphIcon = '$svgBasePath/graph_icon.svg';
+ static const String listIcon = '$svgBasePath/list_icon.svg';
+ static const String yellowArrowDownIcon = '$svgBasePath/yellow_arrow_down_icon.svg';
+ static const String greenTickIcon = '$svgBasePath/green_tick_icon.svg';
// PNGS
static const String hmgLogo = '$pngBasePath/hmg_logo.png';
diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart
index 96cbfa9..ee3b7eb 100644
--- a/lib/core/dependencies.dart
+++ b/lib/core/dependencies.dart
@@ -45,6 +45,7 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_r
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
+import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.dart';
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart';
@@ -125,10 +126,8 @@ class AppDependencies {
getIt.registerLazySingleton(() => ContactUsRepoImp(loggerService: getIt(), apiClient: getIt()));
getIt.registerLazySingleton(() => HmgServicesRepoImp(loggerService: getIt(), apiClient: getIt()));
getIt.registerLazySingleton(() => SymptomsCheckerRepoImp(loggerService: getIt(), apiClient: getIt()));
- getIt.registerLazySingleton(() => BloodDonationRepoImp(
- loggerService: getIt(),
- apiClient: getIt(),
- ));
+ getIt.registerLazySingleton(() => BloodDonationRepoImp(loggerService: getIt(), apiClient: getIt()));
+ getIt.registerLazySingleton(() => WaterMonitorRepoImp(loggerService: getIt(), apiClient: getIt()));
// ViewModels
// Global/shared VMs → LazySingleton
@@ -268,7 +267,7 @@ class AppDependencies {
() => HealthProvider(),
);
- getIt.registerLazySingleton(() => WaterMonitorViewModel());
+ getIt.registerLazySingleton(() => WaterMonitorViewModel(waterMonitorRepo: getIt()));
// Screen-specific VMs → Factory
// getIt.registerFactory(
diff --git a/lib/core/utils/size_utils.dart b/lib/core/utils/size_utils.dart
index 8a0703e..4fdc09c 100644
--- a/lib/core/utils/size_utils.dart
+++ b/lib/core/utils/size_utils.dart
@@ -1,4 +1,5 @@
import 'dart:developer';
+import 'dart:math' as math;
import 'package:flutter/material.dart'; // These are the Viewport values of your Figma Design.
@@ -6,6 +7,16 @@ import 'package:flutter/material.dart'; // These are the Viewport values of your
const num figmaDesignWidth = 375; // iPhone X / 12 base width
const num figmaDesignHeight = 812; // iPhone X / 12 base height
+
+extension ConstrainedResponsive on num {
+ /// Width with max cap for tablets
+ double get wCapped => isTablet ? math.min( w, this * 1.3) : w;
+
+ /// Height with max cap for tablets
+ double get hCapped => isTablet ? math.min(h, this * 1.3) : h;
+}
+
+
extension ResponsiveExtension on num {
double get _screenWidth => SizeUtils.width;
diff --git a/lib/features/water_monitor/models/insert_user_activity_request_model.dart b/lib/features/water_monitor/models/insert_user_activity_request_model.dart
new file mode 100644
index 0000000..a0af649
--- /dev/null
+++ b/lib/features/water_monitor/models/insert_user_activity_request_model.dart
@@ -0,0 +1,26 @@
+class InsertUserActivityRequestModel {
+ String? identificationNo;
+ String? mobileNumber;
+ num? quantityIntake;
+
+ InsertUserActivityRequestModel({
+ this.identificationNo,
+ this.mobileNumber,
+ this.quantityIntake,
+ });
+
+ Map toJson() {
+ final map = {};
+ if (identificationNo != null) map['IdentificationNo'] = identificationNo;
+ if (mobileNumber != null) map['MobileNumber'] = mobileNumber;
+ if (quantityIntake != null) map['QuantityIntake'] = quantityIntake;
+ return map;
+ }
+
+ factory InsertUserActivityRequestModel.fromJson(Map json) => InsertUserActivityRequestModel(
+ identificationNo: json['IdentificationNo']?.toString(),
+ mobileNumber: json['MobileNumber']?.toString(),
+ quantityIntake: json['QuantityIntake'],
+ );
+}
+
diff --git a/lib/features/water_monitor/models/undo_user_activity_request_model.dart b/lib/features/water_monitor/models/undo_user_activity_request_model.dart
new file mode 100644
index 0000000..01dbd92
--- /dev/null
+++ b/lib/features/water_monitor/models/undo_user_activity_request_model.dart
@@ -0,0 +1,26 @@
+class UndoUserActivityRequestModel {
+ num? progress;
+ String? mobileNumber;
+ String? identificationNo;
+
+ UndoUserActivityRequestModel({
+ this.progress,
+ this.mobileNumber,
+ this.identificationNo,
+ });
+
+ Map toJson() {
+ final map = {};
+ if (progress != null) map['Progress'] = progress;
+ if (mobileNumber != null) map['MobileNumber'] = mobileNumber;
+ if (identificationNo != null) map['IdentificationNo'] = identificationNo;
+ return map;
+ }
+
+ factory UndoUserActivityRequestModel.fromJson(Map json) => UndoUserActivityRequestModel(
+ progress: json['Progress'],
+ mobileNumber: json['MobileNumber']?.toString(),
+ identificationNo: json['IdentificationNo']?.toString(),
+ );
+}
+
diff --git a/lib/features/water_monitor/models/update_user_detail_request_model.dart b/lib/features/water_monitor/models/update_user_detail_request_model.dart
new file mode 100644
index 0000000..33dbbb2
--- /dev/null
+++ b/lib/features/water_monitor/models/update_user_detail_request_model.dart
@@ -0,0 +1,88 @@
+class UpdateUserDetailRequestModel {
+ // User detail fields
+ String? activityID;
+ String? dOB;
+ String? email;
+ String? firstName;
+ String? lastName;
+ String? firstNameN;
+ String? middleName;
+ String? middleNameN;
+ String? lastNameN;
+ String? gender;
+ num? height;
+ bool? isHeightInCM;
+ bool? isWeightInKG;
+ String? zipCode;
+ num? weight;
+ bool? isNotificationOn;
+ String? mobileNumber;
+ String? identificationNo;
+
+ UpdateUserDetailRequestModel({
+ this.activityID,
+ this.dOB,
+ this.email,
+ this.firstName,
+ this.lastName,
+ this.firstNameN,
+ this.middleName,
+ this.middleNameN,
+ this.lastNameN,
+ this.gender,
+ this.height,
+ this.isHeightInCM,
+ this.isWeightInKG,
+ this.zipCode,
+ this.weight,
+ this.isNotificationOn,
+ this.mobileNumber,
+ this.identificationNo,
+ });
+
+ Map toJson() {
+ final map = {};
+
+ // User detail fields
+ if (activityID != null) map['ActivityID'] = activityID;
+ if (dOB != null) map['DOB'] = dOB;
+ if (email != null) map['Email'] = email;
+ if (firstName != null) map['FirstName'] = firstName;
+ if (lastName != null) map['LastName'] = lastName;
+ if (firstNameN != null) map['FirstNameN'] = firstNameN;
+ if (middleName != null) map['MiddleName'] = middleName;
+ if (middleNameN != null) map['MiddleNameN'] = middleNameN;
+ if (lastNameN != null) map['LastNameN'] = lastNameN;
+ if (gender != null) map['Gender'] = gender;
+ if (height != null) map['Height'] = height;
+ if (isHeightInCM != null) map['IsHeightInCM'] = isHeightInCM;
+ if (isWeightInKG != null) map['IsWeightInKG'] = isWeightInKG;
+ if (zipCode != null) map['ZipCode'] = zipCode;
+ if (weight != null) map['Weight'] = weight;
+ if (isNotificationOn != null) map['IsNotificationOn'] = isNotificationOn;
+ if (mobileNumber != null) map['MobileNumber'] = mobileNumber;
+ if (identificationNo != null) map['IdentificationNo'] = identificationNo;
+ return map;
+ }
+
+ factory UpdateUserDetailRequestModel.fromJson(Map json) => UpdateUserDetailRequestModel(
+ activityID: json['ActivityID']?.toString(),
+ dOB: json['DOB']?.toString(),
+ email: json['Email']?.toString(),
+ firstName: json['FirstName']?.toString(),
+ lastName: json['LastName']?.toString(),
+ firstNameN: json['FirstNameN']?.toString(),
+ middleName: json['MiddleName']?.toString(),
+ middleNameN: json['MiddleNameN']?.toString(),
+ lastNameN: json['LastNameN']?.toString(),
+ gender: json['Gender']?.toString(),
+ height: json['Height'],
+ isHeightInCM: json['IsHeightInCM'] as bool?,
+ isWeightInKG: json['IsWeightInKG'] as bool?,
+ zipCode: json['ZipCode']?.toString(),
+ weight: json['Weight'],
+ isNotificationOn: json['IsNotificationOn'] as bool?,
+ mobileNumber: json['MobileNumber']?.toString(),
+ identificationNo: json['IdentificationNo']?.toString(),
+ );
+}
diff --git a/lib/features/water_monitor/models/user_progress_models.dart b/lib/features/water_monitor/models/user_progress_models.dart
new file mode 100644
index 0000000..667653e
--- /dev/null
+++ b/lib/features/water_monitor/models/user_progress_models.dart
@@ -0,0 +1,111 @@
+/// Model for today's water progress data
+class UserProgressForTodayModel {
+ num? quantityConsumed;
+ num? percentageConsumed;
+ num? percentageLeft;
+ num? quantityLimit;
+
+ UserProgressForTodayModel({
+ this.quantityConsumed,
+ this.percentageConsumed,
+ this.percentageLeft,
+ this.quantityLimit,
+ });
+
+ factory UserProgressForTodayModel.fromJson(Map json) => UserProgressForTodayModel(
+ quantityConsumed: json['QuantityConsumed'],
+ percentageConsumed: json['PercentageConsumed'],
+ percentageLeft: json['PercentageLeft'],
+ quantityLimit: json['QuantityLimit'],
+ );
+
+ Map toJson() {
+ return {
+ 'QuantityConsumed': quantityConsumed,
+ 'PercentageConsumed': percentageConsumed,
+ 'PercentageLeft': percentageLeft,
+ 'QuantityLimit': quantityLimit,
+ };
+ }
+}
+
+/// Model for weekly water progress data
+class UserProgressForWeekModel {
+ int? dayNumber;
+ String? dayDate;
+ String? dayName;
+ num? percentageConsumed;
+
+ UserProgressForWeekModel({
+ this.dayNumber,
+ this.dayDate,
+ this.dayName,
+ this.percentageConsumed,
+ });
+
+ factory UserProgressForWeekModel.fromJson(Map json) => UserProgressForWeekModel(
+ dayNumber: json['DayNumber'] as int?,
+ dayDate: json['DayDate']?.toString(),
+ dayName: json['DayName']?.toString(),
+ percentageConsumed: json['PercentageConsumed'],
+ );
+
+ Map toJson() {
+ return {
+ 'DayNumber': dayNumber,
+ 'DayDate': dayDate,
+ 'DayName': dayName,
+ 'PercentageConsumed': percentageConsumed,
+ };
+ }
+}
+
+/// Model for monthly water progress data
+class UserProgressForMonthModel {
+ int? monthNumber;
+ String? monthName;
+ num? percentageConsumed;
+
+ UserProgressForMonthModel({
+ this.monthNumber,
+ this.monthName,
+ this.percentageConsumed,
+ });
+
+ factory UserProgressForMonthModel.fromJson(Map json) => UserProgressForMonthModel(
+ monthNumber: json['MonthNumber'] as int?,
+ monthName: json['MonthName']?.toString(),
+ percentageConsumed: json['PercentageConsumed'],
+ );
+
+ Map toJson() {
+ return {
+ 'MonthNumber': monthNumber,
+ 'MonthName': monthName,
+ 'PercentageConsumed': percentageConsumed,
+ };
+ }
+}
+
+/// Model for user progress history data
+class UserProgressHistoryModel {
+ num? quantity;
+ String? createdDate;
+
+ UserProgressHistoryModel({
+ this.quantity,
+ this.createdDate,
+ });
+
+ factory UserProgressHistoryModel.fromJson(Map json) => UserProgressHistoryModel(
+ quantity: json['Quantity'],
+ createdDate: json['CreatedDate']?.toString(),
+ );
+
+ Map toJson() {
+ return {
+ 'Quantity': quantity,
+ 'CreatedDate': createdDate,
+ };
+ }
+}
diff --git a/lib/features/water_monitor/water_monitor_repo.dart b/lib/features/water_monitor/water_monitor_repo.dart
new file mode 100644
index 0000000..f52c971
--- /dev/null
+++ b/lib/features/water_monitor/water_monitor_repo.dart
@@ -0,0 +1,320 @@
+import 'dart:developer';
+
+import 'package:dartz/dartz.dart';
+import 'package:hmg_patient_app_new/core/api/api_client.dart';
+import 'package:hmg_patient_app_new/core/api_consts.dart';
+import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
+import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
+import 'package:hmg_patient_app_new/features/water_monitor/models/insert_user_activity_request_model.dart';
+import 'package:hmg_patient_app_new/features/water_monitor/models/undo_user_activity_request_model.dart';
+import 'package:hmg_patient_app_new/features/water_monitor/models/update_user_detail_request_model.dart';
+import 'package:hmg_patient_app_new/services/logger_service.dart';
+
+/// Progress types to request different ranges from the progress API.
+enum ProgressType { today, week, month }
+
+abstract class WaterMonitorRepo {
+ /// Fetch user details for water monitoring.
+ /// The request will include the standard payload injected by ApiClient and
+ /// additionally these three parameters: Progress, MobileNumber, IdentificationNo.
+ Future>> getUserDetailsForWaterMonitoring({
+ required num progress,
+ required String mobileNumber,
+ required String identificationNo,
+ });
+
+ /// Fetch user progress for water monitoring (H2O_GetUserProgress).
+ Future>> getUserProgressForWaterMonitoring({
+ required ProgressType progressType,
+ required String mobileNumber,
+ required String identificationNo,
+ });
+
+ /// Update user details for water monitoring.
+ Future>> updateOrInsertUserDetailForWaterMonitoring(
+ {required UpdateUserDetailRequestModel requestModel, bool isUpdate = false});
+
+ /// Insert user activity (water intake).
+ Future>> insertUserActivity({
+ required InsertUserActivityRequestModel requestModel,
+ });
+
+ /// Undo last user activity.
+ Future>> undoUserActivity({
+ required UndoUserActivityRequestModel requestModel,
+ });
+}
+
+class WaterMonitorRepoImp implements WaterMonitorRepo {
+ final ApiClient apiClient;
+ final LoggerService loggerService;
+
+ WaterMonitorRepoImp({required this.loggerService, required this.apiClient});
+
+ @override
+ Future>> getUserDetailsForWaterMonitoring({
+ required num progress,
+ required String mobileNumber,
+ required String identificationNo,
+ }) async {
+ Map request = {
+ "Progress": progress,
+ "MobileNumber": mobileNumber,
+ "IdentificationNo": identificationNo,
+ };
+
+ try {
+ GenericApiModel? apiResponse;
+ Failure? failure;
+ await apiClient.post(
+ ApiConsts.h2oGetUserDetail,
+ body: request,
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ log("response h2oGetUserDetail: ${response.toString()}");
+ // Extract only the specific key from the API response as requested
+ dynamic extracted;
+ if (response is Map && response.containsKey('UserDetailData_New')) {
+ extracted = response['UserDetailData_New'];
+ } else {
+ extracted = null;
+ }
+
+ apiResponse = GenericApiModel(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: errorMessage,
+ data: extracted,
+ );
+ } catch (e) {
+ failure = DataParsingFailure(e.toString());
+ }
+ },
+ );
+
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
+
+ @override
+ Future>> getUserProgressForWaterMonitoring({
+ required ProgressType progressType,
+ required String mobileNumber,
+ required String identificationNo,
+ }) async {
+ final progressValue = progressType == ProgressType.today ? 1 : (progressType == ProgressType.week ? 2 : 3);
+
+ Map request = {
+ "Progress": progressValue,
+ "MobileNumber": mobileNumber,
+ "IdentificationNo": identificationNo,
+ };
+
+ try {
+ GenericApiModel? apiResponse;
+ Failure? failure;
+ await apiClient.post(
+ ApiConsts.h2oGetUserProgress,
+ body: request,
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ // Try a few likely nested keys and fall back to the full response
+ dynamic extracted;
+ if (response is Map) {
+ switch (progressType) {
+ case ProgressType.today:
+ if (response.containsKey('UserProgressForTodayData')) {
+ extracted = response['UserProgressForTodayData'];
+ }
+ break;
+ case ProgressType.week:
+ if (response.containsKey('UserProgressForWeekData')) {
+ extracted = response['UserProgressForWeekData'];
+ }
+ break;
+ case ProgressType.month:
+ if (response.containsKey('UserProgressForMonthData')) {
+ extracted = response['UserProgressForMonthData'];
+ }
+ break;
+ }
+
+ // fallbacks
+ if (extracted == null) {
+ if (response.containsKey('UserProgress')) {
+ extracted = response['UserProgress'];
+ } else if (response.containsKey('UserProgressData_New')) {
+ extracted = response['UserProgressData_New'];
+ } else {
+ extracted = response;
+ }
+ }
+ } else {
+ extracted = response;
+ }
+
+ apiResponse = GenericApiModel(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: errorMessage,
+ data: extracted,
+ );
+ } catch (e) {
+ failure = DataParsingFailure(e.toString());
+ }
+ },
+ );
+
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
+
+ @override
+ Future>> updateOrInsertUserDetailForWaterMonitoring({
+ required UpdateUserDetailRequestModel requestModel,
+ bool isUpdate = false,
+ }) async {
+ try {
+ GenericApiModel? apiResponse;
+ Failure? failure;
+
+ // Use different endpoint based on isUpdate flag
+ final endpoint = isUpdate ? ApiConsts.h2oUpdateUserDetail : ApiConsts.h2oInsertUserDetailsNew;
+ await apiClient.post(
+ endpoint,
+ body: requestModel.toJson(),
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ dynamic extracted;
+ if (response is Map && response.containsKey('UserDetailData_New')) {
+ extracted = response['UserDetailData_New'];
+ } else {
+ extracted = null;
+ }
+
+ apiResponse = GenericApiModel(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: errorMessage,
+ data: extracted,
+ );
+ } catch (e) {
+ failure = DataParsingFailure(e.toString());
+ }
+ },
+ );
+
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
+
+ @override
+ Future>> insertUserActivity({
+ required InsertUserActivityRequestModel requestModel,
+ }) async {
+ try {
+ GenericApiModel? apiResponse;
+ Failure? failure;
+
+ await apiClient.post(
+ ApiConsts.h2oInsertUserActivity,
+ body: requestModel.toJson(),
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ // Extract UserProgressForTodayData from the response
+ dynamic extracted;
+ if (response is Map && response.containsKey('UserProgressForTodayData')) {
+ extracted = response['UserProgressForTodayData'];
+ } else {
+ extracted = response;
+ }
+
+ apiResponse = GenericApiModel(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: errorMessage,
+ data: extracted,
+ );
+ } catch (e) {
+ failure = DataParsingFailure(e.toString());
+ }
+ },
+ );
+
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
+
+ @override
+ Future>> undoUserActivity({
+ required UndoUserActivityRequestModel requestModel,
+ }) async {
+ try {
+ GenericApiModel? apiResponse;
+ Failure? failure;
+
+ await apiClient.post(
+ ApiConsts.h2oUndoUserActivity,
+ body: requestModel.toJson(),
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ log("response h2oUndoUserActivity: ${response.toString()}");
+ // Extract UserProgressForTodayData from the response
+ dynamic extracted;
+ if (response is Map && response.containsKey('UserProgressForTodayData')) {
+ extracted = response['UserProgressForTodayData'];
+ } else {
+ extracted = response;
+ }
+
+ apiResponse = GenericApiModel(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: errorMessage,
+ data: extracted,
+ );
+ } catch (e) {
+ failure = DataParsingFailure(e.toString());
+ }
+ },
+ );
+
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
+}
diff --git a/lib/features/water_monitor/water_monitor_view_model.dart b/lib/features/water_monitor/water_monitor_view_model.dart
index 4e8d5f5..4ad8f1e 100644
--- a/lib/features/water_monitor/water_monitor_view_model.dart
+++ b/lib/features/water_monitor/water_monitor_view_model.dart
@@ -1,9 +1,25 @@
+import 'dart:developer';
+
import 'package:flutter/material.dart';
+import 'package:get_it/get_it.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/utils/date_util.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
+import 'package:hmg_patient_app_new/features/water_monitor/models/insert_user_activity_request_model.dart';
+import 'package:hmg_patient_app_new/features/water_monitor/models/undo_user_activity_request_model.dart';
+import 'package:hmg_patient_app_new/features/water_monitor/models/update_user_detail_request_model.dart';
+import 'package:hmg_patient_app_new/features/water_monitor/models/user_progress_models.dart';
import 'package:hmg_patient_app_new/features/water_monitor/models/water_cup_model.dart';
+import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.dart';
+import 'package:hmg_patient_app_new/routes/app_routes.dart';
+import 'package:hmg_patient_app_new/services/navigation_service.dart';
class WaterMonitorViewModel extends ChangeNotifier {
+ WaterMonitorRepo waterMonitorRepo;
+
+ WaterMonitorViewModel({required this.waterMonitorRepo});
+
// Controllers
final TextEditingController nameController = TextEditingController();
final TextEditingController heightController = TextEditingController();
@@ -21,6 +37,11 @@ class WaterMonitorViewModel extends ChangeNotifier {
String _selectedNumberOfReminders = '3 Time';
String _selectedGender = "Male";
+ // ConsumptionScreen
+
+ String _selectedDuration = "Daily";
+ bool _isGraphView = true;
+
// Validation error message
String? _validationError;
@@ -37,6 +58,10 @@ class WaterMonitorViewModel extends ChangeNotifier {
String get selectedGender => _selectedGender;
+ String get selectedDurationFilter => _selectedDuration;
+
+ bool get isGraphView => _isGraphView;
+
// Activity level options
List get activityLevels =>
["Almost Inactive (no exercise)", "Lightly active", "Lightly active (1-3) days per week", "Super active (very hard exercise)"];
@@ -47,10 +72,299 @@ class WaterMonitorViewModel extends ChangeNotifier {
// Gender options
List get genderOptions => ["Male", "Female"];
+//Duration Options
+ List get durationFilters => ["Daily", "Weekly", "Monthly"];
+
+ // Network/data
+ final AppState _appState = GetIt.instance();
+ final NavigationService _navigationService = GetIt.instance();
+
+ bool _isLoading = false;
+ dynamic _userDetailData;
+
+ // Progress data lists
+ List _todayProgressList = [];
+ List _weekProgressList = [];
+ List _monthProgressList = [];
+ List _historyList = [];
+
+ bool get isLoading => _isLoading;
+
+ dynamic get userDetailData => _userDetailData;
+
+ // Getters for progress data
+ List get todayProgressList => _todayProgressList;
+
+ List get weekProgressList => _weekProgressList;
+
+ List get monthProgressList => _monthProgressList;
+
+ List get historyList => _historyList;
+
+ // Get current progress list based on selected duration
+ dynamic get currentProgressData {
+ switch (_selectedDuration) {
+ case 'Daily':
+ return _todayProgressList;
+ case 'Weekly':
+ return _weekProgressList;
+ case 'Monthly':
+ return _monthProgressList;
+ default:
+ return _todayProgressList;
+ }
+ }
+
// Initialize method to be called when needed
- void initialize() {
+ Future initialize() async {
_loadSettings();
_initializeDefaultCups();
+ await fetchUserDetailsForMonitoring();
+ await fetchUserProgressForMonitoring();
+ }
+
+ /// Map selected duration to ProgressType enum
+ ProgressType _getProgressTypeFromDuration() {
+ switch (_selectedDuration) {
+ case 'Daily':
+ return ProgressType.today;
+ case 'Weekly':
+ return ProgressType.week;
+ case 'Monthly':
+ return ProgressType.month;
+ default:
+ return ProgressType.today;
+ }
+ }
+
+ /// Fetch user progress data based on selected duration
+ Future fetchUserProgressForMonitoring() async {
+ try {
+ _isLoading = true;
+ notifyListeners();
+
+ final authenticated = _appState.getAuthenticatedUser();
+ if (authenticated == null) {
+ _isLoading = false;
+ notifyListeners();
+ return;
+ }
+
+ final mobile = (authenticated.mobileNumber ?? '').replaceAll('+', '');
+ final identification = authenticated.patientIdentificationNo ?? '';
+ final progressType = _getProgressTypeFromDuration();
+
+ final result = await waterMonitorRepo.getUserProgressForWaterMonitoring(
+ progressType: progressType,
+ mobileNumber: mobile,
+ identificationNo: identification,
+ );
+
+ result.fold((failure) {
+ log('Error fetching user progress: ${failure.message}');
+ }, (apiModel) {
+ log("User Progress Data ($_selectedDuration): ${apiModel.data.toString()}");
+
+ // Parse the response based on progress type
+ try {
+ if (apiModel.data != null && apiModel.data is List) {
+ switch (progressType) {
+ case ProgressType.today:
+ _todayProgressList.clear();
+ for (var item in apiModel.data) {
+ if (item is Map) {
+ _todayProgressList.add(UserProgressForTodayModel.fromJson(item as Map));
+ }
+ }
+
+ break;
+
+ case ProgressType.week:
+ _weekProgressList.clear();
+ for (var item in apiModel.data) {
+ if (item is Map) {
+ _weekProgressList.add(UserProgressForWeekModel.fromJson(item as Map));
+ }
+ }
+ log('Week Progress: ${_weekProgressList.length} items');
+ break;
+
+ case ProgressType.month:
+ _monthProgressList.clear();
+ for (var item in apiModel.data) {
+ if (item is Map) {
+ _monthProgressList.add(UserProgressForMonthModel.fromJson(item as Map));
+ }
+ }
+ log('Month Progress: ${_monthProgressList.length} items');
+ break;
+ }
+ }
+ } catch (e) {
+ log('Error parsing progress data: $e');
+ }
+ });
+ } catch (e) {
+ log('Exception in fetchUserProgressForMonitoring: $e');
+ } finally {
+ _isLoading = false;
+ notifyListeners();
+ }
+ }
+
+ Future fetchUserDetailsForMonitoring() async {
+ try {
+ _isLoading = true;
+
+ notifyListeners();
+
+ final authenticated = _appState.getAuthenticatedUser();
+ if (authenticated == null) {
+ _isLoading = false;
+ notifyListeners();
+ return;
+ }
+ final mobile = (authenticated.mobileNumber ?? '').replaceAll('+', '');
+ final identification = authenticated.patientIdentificationNo ?? '';
+
+ final result = await waterMonitorRepo.getUserDetailsForWaterMonitoring(progress: 1, mobileNumber: mobile, identificationNo: identification);
+ result.fold((failure) {
+ _userDetailData = null;
+ }, (apiModel) {
+ log("_userDetailData: ${apiModel.data.toString()}");
+ _userDetailData = apiModel.data;
+
+ // Populate form fields from the fetched data
+ if (_userDetailData != null) {
+ _populateFormFields(_userDetailData);
+
+ // Calculate and set daily water goal based on the fetched user details
+ final calculatedGoal = calculateDailyWaterIntake();
+ log("calculatedGoal: $calculatedGoal");
+ if (calculatedGoal > 0) {
+ setDailyGoal(calculatedGoal.toInt());
+ }
+ }
+ });
+ } catch (e) {
+ _userDetailData = null;
+ } finally {
+ _isLoading = false;
+ notifyListeners();
+
+ if (_userDetailData == null) {
+ try {
+ _navigationService.pushAndReplace(AppRoutes.waterMonitorSettingsScreen);
+ } catch (navErr) {
+ log('Navigation to water monitor settings failed: $navErr');
+ }
+ }
+ }
+ }
+
+ /// Populates form fields from the API response data
+ void _populateFormFields(dynamic data) {
+ if (data == null) return;
+
+ try {
+ // Parse the response and populate fields
+ if (data is Map) {
+ // Name
+ if (data['FirstName'] != null) {
+ nameController.text = data['FirstName'].toString();
+ }
+
+ // Gender
+ if (data['Gender'] != null) {
+ final gender = data['Gender'].toString();
+ if (gender == 'M' || gender == 'Male') {
+ _selectedGender = 'Male';
+ } else if (gender == 'F' || gender == 'Female') {
+ _selectedGender = 'Female';
+ }
+ }
+
+ // Age - calculate from DOB if available
+ if (data['DOB'] != null) {
+ final dob = data['DOB'].toString();
+ final age = _calculateAgeFromDOB(dob);
+ if (age > 0) {
+ ageController.text = age.toString();
+ }
+ }
+
+ // Height
+ if (data['Height'] != null) {
+ heightController.text = data['Height'].toString();
+ // Set height unit
+ if (data['IsHeightInCM'] != null) {
+ _selectedHeightUnit = data['IsHeightInCM'] == true ? 'cm' : 'in';
+ }
+ }
+
+ // Weight
+ if (data['Weight'] != null) {
+ weightController.text = data['Weight'].toString();
+ // Set weight unit
+ if (data['IsWeightInKG'] != null) {
+ _selectedWeightUnit = data['IsWeightInKG'] == true ? 'kg' : 'lb';
+ }
+ }
+
+ // Activity Level - map ActivityID to activity level
+ if (data['ActivityID'] != null) {
+ final activityId = data['ActivityID'].toString();
+ _selectedActivityLevel = _getActivityLevelFromID(activityId);
+ }
+
+ // Number of reminders (if available in response)
+ // Note: This may not be in the response, keeping default if not present
+
+ notifyListeners();
+ }
+ } catch (e) {
+ log('Error populating form fields: $e');
+ }
+ }
+
+ /// Calculate age from DOB string in format /Date(milliseconds+0300)/
+ int _calculateAgeFromDOB(String dobString) {
+ try {
+ // Parse the /Date(milliseconds+0300)/ format
+ final regex = RegExp(r'\/Date\((\d+)');
+ final match = regex.firstMatch(dobString);
+ if (match != null) {
+ final milliseconds = int.tryParse(match.group(1)!);
+ if (milliseconds != null) {
+ final dob = DateTime.fromMillisecondsSinceEpoch(milliseconds);
+ final now = DateTime.now();
+ int age = now.year - dob.year;
+ if (now.month < dob.month || (now.month == dob.month && now.day < dob.day)) {
+ age--;
+ }
+ return age;
+ }
+ }
+ } catch (e) {
+ log('Error calculating age from DOB: $e');
+ }
+ return 0;
+ }
+
+ /// Map activity ID to activity level string
+ String _getActivityLevelFromID(String activityId) {
+ switch (activityId) {
+ case '1':
+ return "Almost Inactive (no exercise)";
+ case '2':
+ return "Lightly active";
+ case '3':
+ return "Lightly active (1-3) days per week";
+ case '4':
+ return "Super active (very hard exercise)";
+ default:
+ return "Lightly active";
+ }
}
// Reset all fields to default
@@ -67,6 +381,20 @@ class WaterMonitorViewModel extends ChangeNotifier {
notifyListeners();
}
+ // Setters with notification
+ void setFilterDuration(String duration) async {
+ _selectedDuration = duration;
+ notifyListeners();
+ // Fetch new progress data when duration filter changes
+ await fetchUserProgressForMonitoring();
+ }
+
+ // Setters with notification
+ void setGraphView(bool value) {
+ _isGraphView = value;
+ notifyListeners();
+ }
+
// Setters with notification
void setGender(String gender) {
_selectedGender = gender;
@@ -229,15 +557,125 @@ class WaterMonitorViewModel extends ChangeNotifier {
}
}
+ String _getApiCompatibleGender() {
+ if (_selectedGender == "Female") {
+ return "F";
+ }
+ return "M";
+ }
+
// Save settings
Future saveSettings() async {
if (!isValid) {
notifyListeners(); // Notify so error can be read
return false;
}
- _validationError = null; // Clear error on success
- notifyListeners();
- return true;
+
+ try {
+ _isLoading = true;
+ _validationError = null;
+ notifyListeners();
+
+ // Determine if this is an update or insert
+ final isUpdate = _userDetailData != null;
+
+ // Get authenticated user for mobile number and identification number
+ final authenticated = _appState.getAuthenticatedUser();
+ final mobile = (authenticated?.mobileNumber?.replaceAll('+', '') ?? '').replaceFirst("0", "");
+ final identification = authenticated?.patientIdentificationNo ?? '';
+ final zipCode = authenticated?.zipCode ?? '966';
+ final email = authenticated?.emailAddress ?? '';
+
+ // Get activity ID based on selected activity level
+ String activityID = _getActivityID();
+
+ // Create request model with user detail fields only
+ final requestModel = UpdateUserDetailRequestModel(
+ firstName: nameController.text.trim(),
+ lastName: nameController.text.trim(),
+ firstNameN: nameController.text.trim(),
+ middleName: '',
+ middleNameN: '',
+ lastNameN: nameController.text.trim(),
+ gender: _getApiCompatibleGender(),
+ dOB: _calculateDOBFromAge(),
+ height: double.tryParse(heightController.text.trim()),
+ isHeightInCM: _selectedHeightUnit == 'cm',
+ weight: double.tryParse(weightController.text.trim()),
+ isWeightInKG: _selectedWeightUnit == 'kg',
+ activityID: activityID,
+ isNotificationOn: true,
+ mobileNumber: mobile,
+ identificationNo: identification,
+ email: email,
+ zipCode: zipCode,
+ );
+
+ // Call the API
+ final result = await waterMonitorRepo.updateOrInsertUserDetailForWaterMonitoring(
+ requestModel: requestModel,
+ isUpdate: isUpdate,
+ );
+
+ return result.fold(
+ (failure) {
+ _validationError = failure.message;
+ _isLoading = false;
+ notifyListeners();
+ return false;
+ },
+ (apiModel) {
+ // Update local data with response
+ _userDetailData = apiModel.data;
+ // Calculate and set daily goal based on the inputs
+ final calculatedGoal = calculateDailyWaterIntake();
+
+ log("calculatedGoal: $calculatedGoal");
+ if (calculatedGoal > 0) {
+ setDailyGoal(calculatedGoal.toInt());
+ }
+
+ _isLoading = false;
+ notifyListeners();
+ return true;
+ },
+ );
+ } catch (e) {
+ _validationError = e.toString();
+ _isLoading = false;
+ notifyListeners();
+ return false;
+ }
+ }
+
+ // Helper method to get activity ID based on activity level
+ String _getActivityID() {
+ switch (_selectedActivityLevel) {
+ case "Almost Inactive (no exercise)":
+ return "1";
+ case "Lightly active":
+ return "2";
+ case "Lightly active (1-3) days per week":
+ return "3";
+ case "Super active (very hard exercise)":
+ return "4";
+ default:
+ return "2";
+ }
+ }
+
+ // Helper method to calculate DOB from age
+ String _calculateDOBFromAge() {
+ final age = int.tryParse(ageController.text.trim()) ?? 0;
+ if (age > 0) {
+ final currentYear = DateTime.now().year;
+ final birthYear = currentYear - age;
+ // Create a DateTime for January 1st of the birth year
+ final birthDate = DateTime(birthYear, 1, 1);
+ // Convert to API format: /Date(milliseconds+0300)/ using DateUtil
+ return DateUtil.convertDateToString(birthDate);
+ }
+ return "";
}
// Load settings
@@ -258,8 +696,9 @@ class WaterMonitorViewModel extends ChangeNotifier {
String? _selectedCupId;
int _totalConsumedMl = 1000;
int _dailyGoalMl = 2000;
+
// Calibration: portion of the bottle SVG height that is fillable (0.0 - 1.0)
- double _fillableHeightPercent = 0.823;
+ double _fillableHeightPercent = 0.7;
List get cups => _cups;
@@ -290,6 +729,129 @@ class WaterMonitorViewModel extends ChangeNotifier {
// Convenience percent (0 - 100)
double get progressPercent => (progress * 100.0).clamp(0.0, 100.0).toDouble();
+ // Calculate hydration status based on progress
+ String get hydrationStatus {
+ final percent = progressPercent;
+
+ if (percent >= 90) {
+ return "Well Hydrated";
+ } else if (percent >= 70) {
+ return "Hydrated";
+ } else if (percent >= 50) {
+ return "Moderately Hydrated";
+ } else if (percent >= 30) {
+ return "Slightly Dehydrated";
+ } else {
+ return "Dehydrated";
+ }
+ }
+
+ // Get hydration status color
+ Color get hydrationStatusColor {
+ final percent = progressPercent;
+
+ if (percent >= 90) {
+ return const Color(0xFF00C853); // Dark Green
+ } else if (percent >= 70) {
+ return const Color(0xFF4CAF50); // Green
+ } else if (percent >= 50) {
+ return const Color(0xFFFFC107); // Amber
+ } else if (percent >= 30) {
+ return const Color(0xFFFF9800); // Orange
+ } else {
+ return const Color(0xFFF44336); // Red
+ }
+ }
+
+ String get nextDrinkTime {
+ if (progressPercent >= 100) {
+ return "Goal Achieved!";
+ }
+
+ // Get number of reminders from selected string (e.g., "3 Time" -> 3)
+ final remindersPerDay = int.tryParse(_selectedNumberOfReminders.replaceAll(' Time', '').trim()) ?? 3;
+
+ // Define waking hours (e.g., 6 AM to 10 PM = 16 hours)
+ const wakingHoursStart = 6; // 6 AM
+ const wakingHoursEnd = 22; // 10 PM
+ const totalWakingHours = wakingHoursEnd - wakingHoursStart;
+
+ // Calculate interval between drinks in hours
+ final intervalHours = totalWakingHours / remindersPerDay;
+
+ // Get current time
+ final now = DateTime.now();
+ final currentHour = now.hour + (now.minute / 60.0);
+
+ // If before waking hours, next drink is at start time
+ if (currentHour < wakingHoursStart) {
+ return "${wakingHoursStart.toString().padLeft(2, '0')}:00 AM";
+ }
+
+ // If after waking hours, next drink is tomorrow morning
+ if (currentHour >= wakingHoursEnd) {
+ return "Tomorrow ${wakingHoursStart.toString().padLeft(2, '0')}:00 AM";
+ }
+
+ // Calculate which interval we're in
+ final hoursSinceWakeup = currentHour - wakingHoursStart;
+ final currentInterval = (hoursSinceWakeup / intervalHours).floor();
+
+ // Calculate next drink time
+ final nextDrinkHour = wakingHoursStart + ((currentInterval + 1) * intervalHours);
+
+ // If next drink time is past bedtime, show tomorrow
+ if (nextDrinkHour >= wakingHoursEnd) {
+ return "Tomorrow ${wakingHoursStart.toString().padLeft(2, '0')}:00 AM";
+ }
+
+ // Format the time
+ final hour = nextDrinkHour.floor();
+ final minute = ((nextDrinkHour - hour) * 60).round();
+
+ // Convert to 12-hour format
+ final hour12 = hour > 12 ? hour - 12 : (hour == 0 ? 12 : hour);
+ final period = hour >= 12 ? 'PM' : 'AM';
+
+ return "${hour12.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')} $period";
+ }
+
+ /// Get time until next drink in minutes
+ int get minutesUntilNextDrink {
+ if (progressPercent >= 100) return 0;
+
+ final remindersPerDay = int.tryParse(_selectedNumberOfReminders.replaceAll(' Time', '').trim()) ?? 3;
+ const wakingHoursStart = 6;
+ const wakingHoursEnd = 22;
+ const totalWakingHours = wakingHoursEnd - wakingHoursStart;
+ final intervalHours = totalWakingHours / remindersPerDay;
+
+ final now = DateTime.now();
+ final currentHour = now.hour + (now.minute / 60.0);
+
+ if (currentHour < wakingHoursStart) {
+ final minutesUntil = ((wakingHoursStart - currentHour) * 60).round();
+ return minutesUntil;
+ }
+
+ if (currentHour >= wakingHoursEnd) {
+ final hoursUntilTomorrow = 24 - currentHour + wakingHoursStart;
+ return (hoursUntilTomorrow * 60).round();
+ }
+
+ final hoursSinceWakeup = currentHour - wakingHoursStart;
+ final currentInterval = (hoursSinceWakeup / intervalHours).floor();
+ final nextDrinkHour = wakingHoursStart + ((currentInterval + 1) * intervalHours);
+
+ if (nextDrinkHour >= wakingHoursEnd) {
+ final hoursUntilTomorrow = 24 - currentHour + wakingHoursStart;
+ return (hoursUntilTomorrow * 60).round();
+ }
+
+ final minutesUntil = ((nextDrinkHour - currentHour) * 60).round();
+ return minutesUntil.clamp(0, 1440);
+ }
+
// Allow updating consumed and goal through the vm so UI doesn't manipulate internal fields directly
void setTotalConsumedMl(int ml) {
_totalConsumedMl = ml;
@@ -401,8 +963,150 @@ class WaterMonitorViewModel extends ChangeNotifier {
}
}
+ // Returns the currently selected cup capacity in ml (0 if none)
+ int get selectedCupCapacityMl => selectedCup?.capacityMl ?? 0;
+
+ /// Increment the consumed amount by the currently selected cup capacity.
+ /// This centralizes business logic here so UI just calls this method.
+ void incrementBySelectedCup() {
+ if (selectedCup != null) {
+ addConsumedMl(selectedCup!.capacityMl);
+ }
+ }
+
+ /// Decrement the consumed amount by the currently selected cup capacity.
+ /// Ensures value is clamped inside the VM (subtractConsumedMl already clamps).
+ void decrementBySelectedCup() {
+ if (selectedCup != null) {
+ subtractConsumedMl(selectedCup!.capacityMl);
+ }
+ }
+
void resetDaily() {
_totalConsumedMl = 0;
notifyListeners();
}
+
+ /// Insert user activity (record water intake)
+ Future insertUserActivity({required int quantityIntake}) async {
+ try {
+ _isLoading = true;
+ notifyListeners();
+
+ // Get authenticated user info
+ final authenticated = _appState.getAuthenticatedUser();
+ if (authenticated == null) {
+ _isLoading = false;
+ notifyListeners();
+ return false;
+ }
+
+ final mobile = (authenticated.mobileNumber ?? '').replaceAll('+', '').replaceFirst("0", "");
+ final identification = authenticated.patientIdentificationNo ?? '';
+
+ // Create request model
+ final requestModel = InsertUserActivityRequestModel(
+ identificationNo: identification,
+ mobileNumber: mobile,
+ quantityIntake: quantityIntake,
+ );
+
+ // Call the API
+ final result = await waterMonitorRepo.insertUserActivity(requestModel: requestModel);
+
+ return result.fold(
+ (failure) {
+ log('Error inserting user activity: ${failure.message}');
+ _isLoading = false;
+ notifyListeners();
+ return false;
+ },
+ (apiModel) {
+ log("Insert user activity success: ${apiModel.data.toString()}");
+ // Update consumed amount from the response
+ if (apiModel.data != null && apiModel.data is List && (apiModel.data as List).isNotEmpty) {
+ final progressData = (apiModel.data as List).first;
+ if (progressData is Map && progressData.containsKey('QuantityConsumed')) {
+ final consumed = progressData['QuantityConsumed'];
+ if (consumed != null) {
+ _totalConsumedMl = (consumed is num) ? consumed.toInt() : int.tryParse(consumed.toString()) ?? _totalConsumedMl;
+ }
+ }
+
+ fetchUserProgressForMonitoring();
+ }
+
+ _isLoading = false;
+ notifyListeners();
+ return true;
+ },
+ );
+ } catch (e) {
+ log('Exception in insertUserActivity: $e');
+ _isLoading = false;
+ notifyListeners();
+ return false;
+ }
+ }
+
+ /// Undo last user activity
+ Future undoUserActivity() async {
+ try {
+ _isLoading = true;
+ notifyListeners();
+
+ // Get authenticated user info
+ final authenticated = _appState.getAuthenticatedUser();
+ if (authenticated == null) {
+ _isLoading = false;
+ notifyListeners();
+ return false;
+ }
+
+ final mobile = (authenticated.mobileNumber ?? '').replaceAll('+', '').replaceFirst("0", "");
+ final identification = authenticated.patientIdentificationNo ?? '';
+
+ // Create request model
+ final requestModel = UndoUserActivityRequestModel(
+ progress: 1,
+ mobileNumber: mobile,
+ identificationNo: identification,
+ );
+
+ // Call the API
+ final result = await waterMonitorRepo.undoUserActivity(requestModel: requestModel);
+
+ return result.fold(
+ (failure) {
+ log('Error undoing user activity: ${failure.message}');
+ _isLoading = false;
+ notifyListeners();
+ return false;
+ },
+ (apiModel) {
+ log("Undo user activity success: ${apiModel.data.toString()}");
+
+ // Update consumed amount from the response
+ if (apiModel.data != null && apiModel.data is List && (apiModel.data as List).isNotEmpty) {
+ final progressData = (apiModel.data as List).first;
+ if (progressData is Map && progressData.containsKey('QuantityConsumed')) {
+ final consumed = progressData['QuantityConsumed'];
+ if (consumed != null) {
+ _totalConsumedMl = (consumed is num) ? consumed.toInt() : int.tryParse(consumed.toString()) ?? _totalConsumedMl;
+ }
+ }
+ }
+
+ _isLoading = false;
+ notifyListeners();
+ return true;
+ },
+ );
+ } catch (e) {
+ log('Exception in undoUserActivity: $e');
+ _isLoading = false;
+ notifyListeners();
+ return false;
+ }
+ }
}
diff --git a/lib/presentation/water_monitor/water_consumption_screen.dart b/lib/presentation/water_monitor/water_consumption_screen.dart
index 198f65b..625839e 100644
--- a/lib/presentation/water_monitor/water_consumption_screen.dart
+++ b/lib/presentation/water_monitor/water_consumption_screen.dart
@@ -4,16 +4,21 @@ import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
+import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/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/water_monitor/water_monitor_view_model.dart';
import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/bottle_shape_clipper.dart';
+import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart';
import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/water_consumption_progress_widget.dart';
+import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:provider/provider.dart';
+import 'package:shimmer/shimmer.dart';
class WaterConsumptionScreen extends StatefulWidget {
const WaterConsumptionScreen({super.key});
@@ -26,19 +31,16 @@ class _WaterConsumptionScreenState extends State {
@override
void initState() {
super.initState();
- // Initialize VM once the widget is mounted
- WidgetsBinding.instance.addPostFrameCallback((_) {
- final vm = Provider.of(context, listen: false);
- vm.initialize();
- // optional: log current progress for debugging
- // ignore: avoid_print
- log('WaterMonitor initialized: consumed=${vm.totalConsumedMl}, goal=${vm.dailyGoalMl}, progress=${vm.progressPercent}');
+ WidgetsBinding.instance.addPostFrameCallback((_) async {
+ final vm = context.read();
+ await vm.initialize();
+ log('WaterMonitor initialized: consumed=${vm.totalConsumedMl}, goal=${vm.dailyGoalMl}, progress=${vm.progressPercent}');
});
}
- _buildHydrationTipsWidget() {
+ Widget _buildHydrationTipsWidget() {
return Container(
- margin: EdgeInsets.symmetric(horizontal: 24.h),
+ margin: EdgeInsets.symmetric(horizontal: 24.w),
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
@@ -56,39 +58,32 @@ class _WaterConsumptionScreenState extends State {
],
),
SizedBox(height: 8.h),
- " • ${"Drink before you feel thirsty"}".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
+ " • ${"Drink before you feel thirsty"}".needTranslation.toText12(
+ fontWeight: FontWeight.w500,
+ color: AppColors.textColorLight,
+ ),
SizedBox(height: 4.h),
- " • ${"Keep a refillable bottle next to you"}".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
+ " • ${"Keep a refillable bottle next to you"}".needTranslation.toText12(
+ fontWeight: FontWeight.w500,
+ color: AppColors.textColorLight,
+ ),
SizedBox(height: 4.h),
- " • ${"Track your daily intake to stay motivated"}".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
+ " • ${"Track your daily intake to stay motivated"}".needTranslation.toText12(
+ fontWeight: FontWeight.w500,
+ color: AppColors.textColorLight,
+ ),
SizedBox(height: 4.h),
- " • ${"Choose sparkling water instead of soda"}".needTranslation.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
+ " • ${"Choose sparkling water instead of soda"}".needTranslation.toText12(
+ fontWeight: FontWeight.w500,
+ color: AppColors.textColorLight,
+ ),
SizedBox(height: 8.h),
],
),
);
}
- _buildStatsColumn() {
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- "Next Drink Time".toText18(weight: FontWeight.w600, color: AppColors.textColor),
- "03:00 AM".toText32(weight: FontWeight.w600, color: AppColors.blueColor),
- SizedBox(height: 12.h),
- _buildStatusColumn(title: "Your Goal".needTranslation, subTitle: "2000ml"),
- SizedBox(height: 8.h),
- _buildStatusColumn(title: "Remaining".needTranslation, subTitle: "300ml"),
- SizedBox(height: 8.h),
- _buildStatusColumn(title: "Completed".needTranslation, subTitle: "90%", subTitleColor: AppColors.successColor),
- SizedBox(height: 8.h),
- _buildStatusColumn(title: "Hydration Status".needTranslation, subTitle: "Hydrated", subTitleColor: AppColors.blueColor),
- SizedBox(height: 8.h),
- ],
- );
- }
-
- _buildWaterIntakeSummaryWidget() {
+ Widget _buildWaterIntakeSummaryWidget() {
return Container(
width: double.infinity,
padding: EdgeInsets.all(24.w),
@@ -102,44 +97,68 @@ class _WaterConsumptionScreenState extends State {
],
),
),
- child: LayoutBuilder(
- builder: (context, constraints) {
- // Responsive: stack vertically on small screens
- final isSmallScreen = constraints.maxWidth < 600;
+ child: Column(
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Expanded(
+ flex: isTablet ? 2 : 3,
+ child: Consumer(builder: (context, vm, _) {
+ if (vm.isLoading) {
+ // show shimmer placeholder while fetching server-side user details
+ return _buildLoadingShimmer(isForHistory: false);
+ }
- if (isSmallScreen) {
- return Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Expanded(flex: 3, child: _buildStatsColumn()),
- SizedBox(width: 16.w),
- Expanded(flex: 2, child: _buildWaterBottleWidget()),
- ],
- );
- } else {
- // Larger screens: more space for bottle
- return Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Flexible(flex: 2, child: _buildStatsColumn()),
- SizedBox(width: 32.w),
- Flexible(flex: 1, child: _buildWaterBottleWidget()),
- ],
- );
- }
- },
+ final goalMl = vm.dailyGoalMl;
+ final consumed = vm.totalConsumedMl;
+ final remaining = (goalMl - consumed) > 0 ? (goalMl - consumed) : 0;
+ final completedPercent = "${(vm.progress * 100).clamp(0.0, 100.0).toStringAsFixed(0)}%";
+
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ "Next Drink Time".toText18(weight: FontWeight.w600, color: AppColors.textColor),
+ vm.nextDrinkTime.toText32(weight: FontWeight.w600, color: AppColors.blueColor),
+ SizedBox(height: 12.h),
+ _buildStatusColumn(title: "Your Goal".needTranslation, subTitle: "${goalMl}ml"),
+ SizedBox(height: 8.h),
+ _buildStatusColumn(title: "Remaining".needTranslation, subTitle: "${remaining}ml"),
+ SizedBox(height: 8.h),
+ _buildStatusColumn(title: "Completed".needTranslation, subTitle: completedPercent, subTitleColor: AppColors.successColor),
+ SizedBox(height: 8.h),
+ _buildStatusColumn(
+ title: "Hydration Status".needTranslation,
+ subTitle: vm.hydrationStatus,
+ subTitleColor: vm.hydrationStatusColor,
+ ),
+ ],
+ );
+ }),
+ ),
+ SizedBox(width: isTablet ? 32 : 16.w),
+ Expanded(
+ flex: isTablet ? 1 : 2,
+ child: _buildWaterBottleWidget(),
+ ),
+ ],
+ ),
+ _buildBottomActionWidgets(),
+ ],
),
);
}
- _buildStatusColumn({required String title, required String subTitle, Color? subTitleColor}) {
+ Widget _buildStatusColumn({required String title, required String subTitle, Color? subTitleColor}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- "$title:".toText16(weight: FontWeight.w500, color: AppColors.textColor),
- subTitle.toText12(fontWeight: FontWeight.w600, color: subTitleColor ?? AppColors.greyTextColor),
+ "$title: ".toText16(weight: FontWeight.w500, color: AppColors.textColor),
+ subTitle.toText12(
+ fontWeight: FontWeight.w600,
+ color: subTitleColor ?? AppColors.greyTextColor,
+ ),
],
);
}
@@ -147,86 +166,507 @@ class _WaterConsumptionScreenState extends State {
Widget _buildWaterBottleWidget() {
return Consumer(
builder: (context, vm, _) {
- // final progressPercent = 100.0;
final progressPercent = (vm.progress * 100).clamp(0.0, 100.0);
- log("progressPercent: ${progressPercent}");
- // SVG original dimensions
- final svgOriginalWidth = 143.w;
- final svgOriginalHeight = 315.h;
+ log("progressPercent: $progressPercent");
+
+ // SVG aspect ratio
+ const svgAspectRatio = 315.0 / 143.0; // ~2.2
- // Responsive sizing - scale based on screen width but maintain aspect ratio
- final bottleWidth = 120.w; // Adjust this for your layout
- final aspectRatio = svgOriginalHeight / svgOriginalWidth; // 2. 2027
- final bottleHeight = bottleWidth * aspectRatio;
+ // Responsive bottle sizing with device-specific constraints
+ double bottleWidth;
+ if (isTablet) {
+ // Tablet: use logical pixels (not scaled) with a reasonable max
+ bottleWidth = math.min(SizeUtils.width * 0.15, 180.0); // 15% of width, max 180
+ } else if (isFoldable) {
+ // Foldable: moderate scaling
+ bottleWidth = math.min(100.w, 160.0);
+ } else {
+ // Phone: use your responsive . w with a cap
+ bottleWidth = math.min(120.w, 140.0);
+ }
- // Exact fillable area based on SVG paths (provided by VM calibration)
- final fillableHeightPercent = vm.fillableHeightPercent;
- final fillableWidthPercent = 0.8; // 80.7% is body width
- final bottomPaddingPercent = 0; // 0% padding from bottom (8px of 315px)
+ final bottleHeight = bottleWidth * svgAspectRatio;
+
+ // Fillable area percentages (calibrated to your SVG)
+ final fillableHeightPercent = 0.7;
+ const fillableWidthPercent = 0.8;
final fillableHeight = bottleHeight * fillableHeightPercent;
final fillableWidth = bottleWidth * fillableWidthPercent;
- final bottomPadding = bottleHeight * bottomPaddingPercent;
+
+ // Device-specific positioning offsets
+ final double leftOffset = isTablet ? 4.w : 8.w;
+ final double bottomOffset = isTablet ? -65.h : -78.h;
+
return SizedBox(
height: bottleHeight,
width: bottleWidth,
child: Stack(
+ fit: StackFit.expand,
alignment: Alignment.center,
children: [
- // Bottle SVG
+ // Bottle SVG outline
Center(
child: Utils.buildSvgWithAssets(
icon: AppAssets.waterBottle,
height: bottleHeight,
width: bottleWidth,
- fit: BoxFit.contain, // Important for scaling
+ fit: BoxFit.contain,
),
),
- // Water wave - positioned from bottom
- Positioned(
- bottom: bottomPadding,
- right: 10.w,
- child: ClipPath(
- clipper: BottleShapeClipper(), // Use custom clipper for exact bottle shape
+ // Wave and bubbles clipped to bottle shape
+ Positioned.fill(
+ left: leftOffset,
+ bottom: bottomOffset,
+ child: Center(
child: SizedBox(
- height: fillableHeight,
width: fillableWidth,
- child: Align(
- alignment: Alignment.bottomCenter,
- child: WaterConsumptionProgressWidget(
- progress: progressPercent,
- // Pass a square size so the painter's vertical range matches the clip area.
- // Use the larger of width/height so the waves render correctly and won't be squashed.
- size: math.max(fillableWidth, fillableHeight),
- // Provide exact container dimensions so the painter draws waves using the bottle's rect
- containerWidth: fillableWidth,
- containerHeight: fillableHeight,
- waveDuration: const Duration(milliseconds: 3000),
- waveColor: AppColors.blueColor,
+ height: fillableHeight,
+ child: ClipPath(
+ clipper: BottleShapeClipper(),
+ child: Stack(
+ alignment: Alignment.bottomCenter,
+ children: [
+ // Animated wave
+ Positioned(
+ child: WaterConsumptionProgressWidget(
+ progress: progressPercent,
+ size: math.min(fillableWidth, fillableHeight),
+ containerWidth: fillableWidth,
+ containerHeight: fillableHeight,
+ waveDuration: const Duration(milliseconds: 3000),
+ waveColor: AppColors.blueColor,
+ ),
+ ),
+
+ // Bubbles (only show if progress > 10%)
+ if (progressPercent > 10)
+ Positioned(
+ bottom: fillableHeight * 0.12,
+ child: Utils.buildSvgWithAssets(
+ icon: AppAssets.waterBottleOuterBubbles,
+ // Cap bubble size based on device type
+ height: isTablet ? math.min(45.0, fillableHeight * 0.2) : math.min(55.0, fillableHeight * 0.22),
+ width: fillableWidth * 0.65,
+ ),
+ ),
+ ],
),
),
),
),
),
+ ],
+ ),
+ );
+ },
+ );
+ }
+
+ _buildBottomActionWidgets() {
+ return Consumer(builder: (context, vm, _) {
+ final cupAmount = vm.selectedCupCapacityMl;
+ return Column(
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ InkWell(
+ onTap: () async {
+ // Undo last activity via API
+ if (cupAmount > 0) {
+ await vm.undoUserActivity();
+ }
+ },
+ child: Utils.buildSvgWithAssets(
+ icon: AppAssets.minimizeIcon,
+ height: 20.w,
+ width: 20.w,
+ iconColor: AppColors.textColor,
+ ),
+ ),
+ Container(
+ margin: EdgeInsets.symmetric(horizontal: 4.w),
+ padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.blueColor,
+ borderRadius: 99.r,
+ hasShadow: true,
+ ),
+ child: (cupAmount > 0 ? "+ $cupAmount ml" : "+ 0ml").toText12(
+ fontWeight: FontWeight.w600,
+ color: AppColors.whiteColor,
+ ),
+ ),
+ InkWell(
+ onTap: () async {
+ // Insert user activity via API
+ if (cupAmount > 0) {
+ await vm.insertUserActivity(quantityIntake: cupAmount);
+ }
+ },
+ child: Utils.buildSvgWithAssets(
+ icon: AppAssets.addIconDark,
+ ),
+ ),
+ ],
+ ),
+ SizedBox(height: 8.h),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ _buildActionButton(
+ onTap: () => showSwitchCupBottomSheet(context),
+ overlayWidget: AppAssets.refreshIcon,
+ title: "Switch Cup".needTranslation,
+ icon: Utils.buildSvgWithAssets(icon: AppAssets.glassIcon, height: 24.w, width: 24.w),
+ ),
+ _buildActionButton(
+ onTap: () {},
+ title: "Add Water".needTranslation,
+ icon: Utils.buildSvgWithAssets(icon: AppAssets.glassIcon, height: 24.w, width: 24.w),
+ ),
+ _buildActionButton(
+ onTap: () => context.navigateWithName(AppRoutes.waterMonitorSettingsScreen),
+ title: "Settings".needTranslation,
+ icon: Icon(
+ Icons.settings,
+ color: AppColors.blueColor,
+ size: 24.w,
+ ),
+ ),
+ ],
+ ),
+ ],
+ );
+ });
+ }
- // Bubbles positioned dynamically based on water level
- if (progressPercent > 10)
+ _buildActionButton({String? overlayWidget, required String title, required Widget icon, required VoidCallback onTap}) {
+ return InkWell(
+ onTap: onTap,
+ child: Column(
+ children: [
+ Stack(
+ children: [
+ Container(
+ height: 46.w,
+ width: 46.w,
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.blueColor.withValues(alpha: 0.14),
+ borderRadius: 12.r,
+ hasShadow: true,
+ ),
+ child: Center(child: icon),
+ ),
+ if (overlayWidget != null) ...[
Positioned(
- bottom: 40.h,
- child: Utils.buildSvgWithAssets(
- icon: AppAssets.waterBottleOuterBubbles,
- height: 60.h,
- width: fillableWidth * 0.7,
+ top: 0,
+ right: 0,
+ child: Container(
+ padding: EdgeInsets.all(2.w),
+ height: 16.w,
+ width: 16.w,
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.blueColor,
+ borderRadius: 100.r,
+ hasShadow: true,
+ ),
+ child: Center(child: Utils.buildSvgWithAssets(icon: AppAssets.refreshIcon, iconColor: AppColors.whiteColor)),
),
),
+ ]
],
),
+ SizedBox(height: 4.h),
+ title.toText10(),
+ ],
+ ),
+ );
+ }
+
+ Widget buildHistoryListTile({required String title, required String subTitle}) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ title.toText14(weight: FontWeight.w500, color: AppColors.labelTextColor),
+ subTitle.toText18(weight: FontWeight.w600),
+ ],
+ ),
+ Utils.buildSvgWithAssets(icon: AppAssets.greenTickIcon)
+ ],
+ ).paddingSymmetrical(0, 8.h);
+ }
+
+ Widget _buildHistoryGraphOrList() {
+ return Container(
+ margin: EdgeInsets.symmetric(horizontal: 24.w),
+ padding: EdgeInsets.all(16.h),
+ decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
+ color: AppColors.whiteColor,
+ borderRadius: 24.r,
+ hasShadow: true,
+ ),
+ child: Consumer(builder: (BuildContext context, WaterMonitorViewModel viewModel, Widget? child) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ "History".needTranslation.toText16(isBold: true),
+ Row(
+ children: [
+ InkWell(
+ onTap: () => viewModel.setGraphView(!viewModel.isGraphView),
+ child: AnimatedSwitcher(
+ duration: const Duration(milliseconds: 300),
+ transitionBuilder: (Widget child, Animation animation) {
+ return FadeTransition(
+ opacity: animation,
+ child: ScaleTransition(
+ scale: animation,
+ child: child,
+ ),
+ );
+ },
+ child: Container(
+ key: ValueKey(viewModel.isGraphView),
+ child: Utils.buildSvgWithAssets(
+ icon: viewModel.isGraphView ? AppAssets.listIcon : AppAssets.graphIcon,
+ height: 24.h,
+ width: 24.h,
+ ),
+ ),
+ ),
+ ),
+ SizedBox(width: 8.w),
+ InkWell(
+ onTap: () => _showHistoryDurationBottomsheet(context, viewModel),
+ child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_calendar_icon, height: 24.h, width: 24.h))
+ ],
+ ),
+ ],
+ ),
+ if (!viewModel.isGraphView)
+ _buildHistoryListView(viewModel)
+ else
+ Center(
+ child: Padding(
+ padding: EdgeInsets.symmetric(vertical: 32.h),
+ child: "Graph view coming soon".toText14(color: AppColors.greyTextColor),
+ ),
+ ),
+ ],
+ );
+ }),
+ );
+ }
+
+ Widget _buildLoadingShimmer({bool isForHistory = true}) {
+ return ListView.separated(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ padding: EdgeInsets.all(0.w),
+ itemCount: 4,
+ separatorBuilder: (_, __) => SizedBox(height: 12.h),
+ itemBuilder: (context, index) {
+ return Shimmer.fromColors(
+ baseColor: Colors.grey[300]!,
+ highlightColor: Colors.grey[100]!,
+ child: Container(
+ height: isForHistory ? 60.h : 40.h,
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(10.r),
+ ),
+ ),
);
},
);
}
+ Widget _buildHistoryListView(WaterMonitorViewModel viewModel) {
+ final selectedDuration = viewModel.selectedDurationFilter;
+
+ // Build list items based on duration
+ List listItems = [];
+
+ if (selectedDuration == 'Daily') {
+ if (viewModel.todayProgressList.isNotEmpty) {
+ final todayData = viewModel.todayProgressList.first;
+ listItems.add(
+ buildHistoryListTile(
+ title: "Today's Progress",
+ subTitle: "${todayData.quantityConsumed?.toStringAsFixed(0) ?? '0'} ml / ${todayData.quantityLimit?.toStringAsFixed(0) ?? '0'} ml",
+ ),
+ );
+ listItems.add(Divider(height: 1, color: AppColors.dividerColor));
+ listItems.add(
+ buildHistoryListTile(
+ title: "Percentage Completed",
+ subTitle: "${todayData.percentageConsumed?.toStringAsFixed(1) ?? '0'}%",
+ ),
+ );
+ } else {
+ listItems.add(
+ Center(
+ child: Padding(
+ padding: EdgeInsets.symmetric(vertical: 16.h),
+ child: "No data available for today".toText14(color: AppColors.greyTextColor),
+ ),
+ ),
+ );
+ }
+ } else if (selectedDuration == 'Weekly') {
+ if (viewModel.weekProgressList.isNotEmpty) {
+ for (var dayData in viewModel.weekProgressList) {
+ listItems.add(
+ buildHistoryListTile(
+ title: dayData.dayName ?? 'Unknown',
+ subTitle: "${dayData.percentageConsumed?.toStringAsFixed(1) ?? '0'}%",
+ ),
+ );
+ if (dayData != viewModel.weekProgressList.last) {
+ listItems.add(Divider(height: 1, color: AppColors.dividerColor));
+ }
+ }
+ } else {
+ listItems.add(
+ Center(
+ child: Padding(
+ padding: EdgeInsets.symmetric(vertical: 16.h),
+ child: "No data available for this week".toText14(color: AppColors.greyTextColor),
+ ),
+ ),
+ );
+ }
+ } else if (selectedDuration == 'Monthly') {
+ if (viewModel.monthProgressList.isNotEmpty) {
+ for (var monthData in viewModel.monthProgressList) {
+ listItems.add(
+ buildHistoryListTile(
+ title: monthData.monthName ?? 'Unknown',
+ subTitle: "${monthData.percentageConsumed?.toStringAsFixed(1) ?? '0'}%",
+ ),
+ );
+ if (monthData != viewModel.monthProgressList.last) {
+ listItems.add(Divider(height: 1, color: AppColors.dividerColor));
+ }
+ }
+ } else {
+ listItems.add(
+ Center(
+ child: Padding(
+ padding: EdgeInsets.symmetric(vertical: 16.h),
+ child: "No data available for this year".toText14(color: AppColors.greyTextColor),
+ ),
+ ),
+ );
+ }
+ }
+
+ // Return scrollable list with min and max height constraints
+ return ConstrainedBox(
+ constraints: BoxConstraints(
+ minHeight: 80.h,
+ maxHeight: 270.h,
+ ),
+ child: viewModel.isLoading
+ ? _buildLoadingShimmer().paddingOnly(top: 16.h)
+ : listItems.isEmpty
+ ? Center(
+ child: "No history data available".toText14(color: AppColors.greyTextColor),
+ )
+ : ListView.separated(
+ padding: EdgeInsets.only(top: 16.h),
+ shrinkWrap: true,
+ itemCount: listItems.length,
+ separatorBuilder: (context, index) => SizedBox.shrink(),
+ itemBuilder: (context, index) => listItems[index],
+ ),
+ );
+ }
+
+ // Reusable method to build selection row widget
+ Widget _buildSelectionRow({
+ required String value,
+ required String groupValue,
+ required VoidCallback onTap,
+ bool useUpperCase = false,
+ }) {
+ return SizedBox(
+ height: 70.h,
+ child: Row(
+ spacing: 8.h,
+ children: [
+ Radio(
+ value: value,
+ groupValue: groupValue,
+ activeColor: AppColors.errorColor,
+ onChanged: (_) => onTap(),
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ (useUpperCase ? value.toUpperCase() : value.toCamelCase)
+ .toText16(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1)
+ .expanded,
+ ],
+ ).onPress(onTap),
+ );
+ }
+
+ void _showSelectionBottomSheet({
+ required BuildContext context,
+ required String title,
+ required List items,
+ required String selectedValue,
+ required Function(String) onSelected,
+ bool useUpperCase = false,
+ }) {
+ final dialogService = getIt.get();
+
+ dialogService.showFamilyBottomSheetWithoutHWithChild(
+ label: title.needTranslation,
+ message: "",
+ child: Container(
+ padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h),
+ decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20.r)),
+ child: ListView.separated(
+ shrinkWrap: true,
+ itemCount: items.length,
+ itemBuilder: (context, index) {
+ final item = items[index];
+ return _buildSelectionRow(
+ value: item,
+ groupValue: selectedValue,
+ useUpperCase: useUpperCase,
+ onTap: () {
+ onSelected(item);
+ Navigator.pop(context);
+ },
+ );
+ },
+ separatorBuilder: (_, __) => Divider(height: 1, color: AppColors.dividerColor),
+ ),
+ ),
+ onOkPressed: () {},
+ );
+ }
+
+ void _showHistoryDurationBottomsheet(BuildContext context, WaterMonitorViewModel viewModel) {
+ _showSelectionBottomSheet(
+ context: context,
+ title: "Select Duration".needTranslation,
+ items: viewModel.durationFilters,
+ selectedValue: viewModel.selectedDurationFilter,
+ onSelected: viewModel.setFilterDuration,
+ );
+ }
+
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -245,7 +685,7 @@ class _WaterConsumptionScreenState extends State {
text: "Set Reminder".needTranslation,
textColor: AppColors.successColor,
backgroundColor: AppColors.successLightBgColor,
- onPressed: () => {},
+ onPressed: () {},
icon: AppAssets.bell,
iconColor: AppColors.successColor,
borderRadius: 12.r,
@@ -259,11 +699,13 @@ class _WaterConsumptionScreenState extends State {
SizedBox(height: 16.h),
_buildWaterIntakeSummaryWidget(),
SizedBox(height: 16.h),
+ _buildHistoryGraphOrList(),
+ SizedBox(height: 16.h),
_buildHydrationTipsWidget(),
+ SizedBox(height: 16.h),
],
),
),
);
- // );
}
}
diff --git a/lib/presentation/water_monitor/water_monitor_settings_screen.dart b/lib/presentation/water_monitor/water_monitor_settings_screen.dart
index e49c8a3..5f1bce5 100644
--- a/lib/presentation/water_monitor/water_monitor_settings_screen.dart
+++ b/lib/presentation/water_monitor/water_monitor_settings_screen.dart
@@ -6,30 +6,16 @@ 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/water_monitor/water_monitor_view_model.dart';
-import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:provider/provider.dart';
-class WaterMonitorSettingsScreen extends StatefulWidget {
+class WaterMonitorSettingsScreen extends StatelessWidget {
const WaterMonitorSettingsScreen({super.key});
- @override
- State createState() => _WaterMonitorSettingsScreenState();
-}
-
-class _WaterMonitorSettingsScreenState extends State {
- @override
- void initState() {
- super.initState();
- WidgetsBinding.instance.addPostFrameCallback((_) {
- context.read().initialize();
- });
- }
-
- void _showSnackbar(String text) {
+ void _showSnackbar(String text, BuildContext context) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(text),
@@ -276,13 +262,13 @@ class _WaterMonitorSettingsScreenState extends State
child: CustomButton(
text: "Save".needTranslation,
onPressed: () async {
- showSwitchCupBottomSheet(context);
- return;
final success = await viewModel.saveSettings();
if (!success && viewModel.validationError != null) {
_showSnackBar(context, viewModel.validationError!);
} else if (success) {
- _showSnackBar(context, "Saved Successfully".needTranslation);
+ _showSnackBar(context, "Settings saved successfully".needTranslation);
+ // Navigate back on success
+ Navigator.pop(context);
}
},
borderRadius: 12.r,
diff --git a/lib/widgets/shimmer/common_shimmer_widget.dart b/lib/widgets/shimmer/common_shimmer_widget.dart
index d6a2906..3d935cb 100644
--- a/lib/widgets/shimmer/common_shimmer_widget.dart
+++ b/lib/widgets/shimmer/common_shimmer_widget.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/extensions/int_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@@ -11,9 +12,7 @@ class CommonShimmerWidget extends StatelessWidget {
return SizedBox(
child: Container(
decoration: BoxDecoration(
- borderRadius: const BorderRadius.all(
- Radius.circular(10),
- ),
+ borderRadius: BorderRadius.all(Radius.circular(10.r)),
border: Border.all(color: AppColors.lightGreyEFColor, width: 1),
boxShadow: [
BoxShadow(
@@ -27,7 +26,11 @@ class CommonShimmerWidget extends StatelessWidget {
padding: const EdgeInsets.all(12.0),
child: Column(
children: [
- Container(height: 100).toShimmer(),
+ Container(
+ height: 100,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.all(Radius.circular(24.r)),
+ )).toShimmer(),
16.height,
Container(height: 24).toShimmer(),
16.height,