Water Intake Final Touches

pull/135/head
faizatflutter 3 weeks ago
parent fd2abbbc7b
commit a1aaa041d1

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 2C3.55229 2 4 2.44772 4 3L4 14C4 15.6782 4.00213 16.8362 4.11923 17.7072C4.17092 18.0917 4.23383 18.5232 4.45131 18.8563C4.60954 18.5142 4.76255 18.1696 4.91553 17.8252C5.29664 16.967 5.67762 16.1092 6.13888 15.2891C6.58328 14.499 7.11953 13.7058 7.77477 13.1011C8.43871 12.4882 9.28219 12.0189 10.3058 12.0189C11.6975 12.0189 12.5705 12.9129 13.1308 13.4867L13.1883 13.5455C13.8415 14.2124 14.1538 14.4717 14.6132 14.4717C15.0859 14.4717 15.4115 14.2943 15.733 13.9424C16.1205 13.5181 16.396 12.9986 16.6658 12.49C16.7134 12.4003 16.7608 12.3109 16.8086 12.2224C17.5056 10.9328 18.5501 9 20.9995 9C21.5518 9 21.9995 9.44772 21.9995 10C21.9995 10.5523 21.5518 11 20.9995 11C19.8601 11 19.3296 11.7648 18.5475 13.2114L18.4738 13.348C18.1403 13.9674 17.7401 14.7105 17.2097 15.2911C16.5941 15.9651 15.7622 16.4717 14.6132 16.4717C13.2476 16.4717 12.3831 15.5847 11.8309 15.0182C11.7865 14.9725 11.7412 14.925 11.6952 14.8766C11.3159 14.4783 10.8784 14.0189 10.3058 14.0189C9.93769 14.0189 9.55589 14.1788 9.13128 14.5707C8.69797 14.9707 8.28242 15.5577 7.88206 16.2696C7.45308 17.0322 7.09983 17.8308 6.7465 18.6295C6.56362 19.043 6.38072 19.4565 6.18729 19.865C6.22215 19.8708 6.25731 19.876 6.2928 19.8808C7.16378 19.9979 8.32182 20 10 20H21C21.5523 20 22 20.4477 22 21C22 21.5523 21.5523 22 21 22H9.928C8.33933 22 7.04616 22.0001 6.0263 21.8629C4.96232 21.7199 4.04736 21.4113 3.31802 20.682C2.58869 19.9526 2.28011 19.0377 2.13706 17.9737C1.99995 16.9539 1.99997 15.6607 2 14.0721L2 3C2 2.44772 2.44772 2 3 2Z" fill="#2E3039"/>
<path d="M8 4C8.55229 4 9 3.55228 9 3C9 2.44772 8.55229 2 8 2L7 2C6.44772 2 6 2.44772 6 3C6 3.55228 6.44772 4 7 4L8 4Z" fill="#2E3039"/>
<path d="M11 8C11.5523 8 12 7.55228 12 7C12 6.44772 11.5523 6 11 6H7C6.44772 6 6 6.44772 6 7C6 7.55228 6.44772 8 7 8L11 8Z" fill="#2E3039"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -0,0 +1,3 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 8.0625C0 12.5153 3.60975 16.125 8.0625 16.125C12.5153 16.125 16.125 12.5153 16.125 8.0625C16.125 3.60975 12.5153 0 8.0625 0C3.60975 0 0 3.60975 0 8.0625ZM11.5695 5.25974C11.8748 5.53949 11.895 6.01423 11.6153 6.31948L7.49026 10.8195C7.35226 10.9702 7.158 11.058 6.954 11.0625C6.74925 11.067 6.552 10.9875 6.40725 10.8428L4.53225 8.96777C4.239 8.67527 4.239 8.19973 4.53225 7.90723C4.82475 7.61398 5.30026 7.61398 5.59276 7.90723L6.91425 9.228L10.5097 5.30552C10.7895 5.00027 11.2643 4.97999 11.5695 5.25974Z" fill="#18C273"/>
</svg>

After

Width:  |  Height:  |  Size: 681 B

@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 4C2.44772 4 2 4.44772 2 5C2 5.55228 2.44772 6 3 6L5 6C5.55228 6 6 5.55229 6 5C6 4.44772 5.55228 4 5 4L3 4Z" fill="#2E3039"/>
<path d="M9 4C8.44772 4 8 4.44772 8 5C8 5.55228 8.44772 6 9 6L21 6C21.5523 6 22 5.55229 22 5C22 4.44772 21.5523 4 21 4L9 4Z" fill="#2E3039"/>
<path d="M8 12C8 11.4477 8.44772 11 9 11L21 11C21.5523 11 22 11.4477 22 12C22 12.5523 21.5523 13 21 13L9 13C8.44772 13 8 12.5523 8 12Z" fill="#2E3039"/>
<path d="M3 11C2.44772 11 2 11.4477 2 12C2 12.5523 2.44772 13 3 13H5C5.55228 13 6 12.5523 6 12C6 11.4477 5.55228 11 5 11L3 11Z" fill="#2E3039"/>
<path d="M8 19C8 18.4477 8.44772 18 9 18L21 18C21.5523 18 22 18.4477 22 19C22 19.5523 21.5523 20 21 20L9 20C8.44772 20 8 19.5523 8 19Z" fill="#2E3039"/>
<path d="M3 18C2.44772 18 2 18.4477 2 19C2 19.5523 2.44772 20 3 20H5C5.55228 20 6 19.5523 6 19C6 18.4477 5.55228 18 5 18H3Z" fill="#2E3039"/>
</svg>

After

Width:  |  Height:  |  Size: 975 B

@ -0,0 +1,3 @@
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.0625 16.125C3.6097 16.125 0 12.5153 0 8.0625C0 3.6097 3.6097 0 8.0625 0C12.5153 0 16.125 3.6097 16.125 8.0625C16.125 12.5153 12.5153 16.125 8.0625 16.125ZM8.0625 4.3125C8.47671 4.3125 8.8125 4.64829 8.8125 5.0625V8.43749H8.93401C9.15181 8.43742 9.38226 8.43734 9.56413 8.45912C9.69673 8.47499 10.1846 8.53481 10.4097 8.99153C10.635 9.4486 10.3763 9.85561 10.3061 9.96553C10.2094 10.1169 10.0646 10.2914 9.92764 10.4564L9.90413 10.4848C9.68853 10.7449 9.43178 11.0409 9.1743 11.2764C9.04572 11.394 8.89699 11.5151 8.73553 11.6109C8.58584 11.6996 8.34945 11.8125 8.0625 11.8125C7.77556 11.8125 7.53916 11.6996 7.38947 11.6109C7.22801 11.5151 7.07929 11.394 6.95071 11.2764C6.69322 11.0409 6.43647 10.7449 6.22087 10.4848L6.19736 10.4564C6.06041 10.2914 5.91565 10.1169 5.81894 9.96552C5.74871 9.85561 5.49001 9.4486 5.71529 8.99153C5.94038 8.53481 6.42827 8.47499 6.56087 8.45912C6.74274 8.43734 6.9732 8.43742 7.191 8.43749H7.3125L7.3125 5.0625C7.3125 4.64829 7.64829 4.3125 8.0625 4.3125Z" fill="#FF9E15"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -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);

@ -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}

@ -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';

@ -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<ContactUsRepo>(() => ContactUsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<HmgServicesRepo>(() => HmgServicesRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<SymptomsCheckerRepo>(() => SymptomsCheckerRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<BloodDonationRepo>(() => BloodDonationRepoImp(
loggerService: getIt<LoggerService>(),
apiClient: getIt(),
));
getIt.registerLazySingleton<BloodDonationRepo>(() => BloodDonationRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<WaterMonitorRepo>(() => WaterMonitorRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
// ViewModels
// Global/shared VMs LazySingleton
@ -268,7 +267,7 @@ class AppDependencies {
() => HealthProvider(),
);
getIt.registerLazySingleton<WaterMonitorViewModel>(() => WaterMonitorViewModel());
getIt.registerLazySingleton<WaterMonitorViewModel>(() => WaterMonitorViewModel(waterMonitorRepo: getIt()));
// Screen-specific VMs Factory
// getIt.registerFactory<BookAppointmentsViewModel>(

@ -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;

@ -0,0 +1,26 @@
class InsertUserActivityRequestModel {
String? identificationNo;
String? mobileNumber;
num? quantityIntake;
InsertUserActivityRequestModel({
this.identificationNo,
this.mobileNumber,
this.quantityIntake,
});
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
if (identificationNo != null) map['IdentificationNo'] = identificationNo;
if (mobileNumber != null) map['MobileNumber'] = mobileNumber;
if (quantityIntake != null) map['QuantityIntake'] = quantityIntake;
return map;
}
factory InsertUserActivityRequestModel.fromJson(Map<String, dynamic> json) => InsertUserActivityRequestModel(
identificationNo: json['IdentificationNo']?.toString(),
mobileNumber: json['MobileNumber']?.toString(),
quantityIntake: json['QuantityIntake'],
);
}

@ -0,0 +1,26 @@
class UndoUserActivityRequestModel {
num? progress;
String? mobileNumber;
String? identificationNo;
UndoUserActivityRequestModel({
this.progress,
this.mobileNumber,
this.identificationNo,
});
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
if (progress != null) map['Progress'] = progress;
if (mobileNumber != null) map['MobileNumber'] = mobileNumber;
if (identificationNo != null) map['IdentificationNo'] = identificationNo;
return map;
}
factory UndoUserActivityRequestModel.fromJson(Map<String, dynamic> json) => UndoUserActivityRequestModel(
progress: json['Progress'],
mobileNumber: json['MobileNumber']?.toString(),
identificationNo: json['IdentificationNo']?.toString(),
);
}

@ -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<String, dynamic> toJson() {
final map = <String, dynamic>{};
// 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<String, dynamic> 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(),
);
}

@ -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<String, dynamic> json) => UserProgressForTodayModel(
quantityConsumed: json['QuantityConsumed'],
percentageConsumed: json['PercentageConsumed'],
percentageLeft: json['PercentageLeft'],
quantityLimit: json['QuantityLimit'],
);
Map<String, dynamic> 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<String, dynamic> json) => UserProgressForWeekModel(
dayNumber: json['DayNumber'] as int?,
dayDate: json['DayDate']?.toString(),
dayName: json['DayName']?.toString(),
percentageConsumed: json['PercentageConsumed'],
);
Map<String, dynamic> 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<String, dynamic> json) => UserProgressForMonthModel(
monthNumber: json['MonthNumber'] as int?,
monthName: json['MonthName']?.toString(),
percentageConsumed: json['PercentageConsumed'],
);
Map<String, dynamic> 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<String, dynamic> json) => UserProgressHistoryModel(
quantity: json['Quantity'],
createdDate: json['CreatedDate']?.toString(),
);
Map<String, dynamic> toJson() {
return {
'Quantity': quantity,
'CreatedDate': createdDate,
};
}
}

@ -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<Either<Failure, GenericApiModel<dynamic>>> getUserDetailsForWaterMonitoring({
required num progress,
required String mobileNumber,
required String identificationNo,
});
/// Fetch user progress for water monitoring (H2O_GetUserProgress).
Future<Either<Failure, GenericApiModel<dynamic>>> getUserProgressForWaterMonitoring({
required ProgressType progressType,
required String mobileNumber,
required String identificationNo,
});
/// Update user details for water monitoring.
Future<Either<Failure, GenericApiModel<dynamic>>> updateOrInsertUserDetailForWaterMonitoring(
{required UpdateUserDetailRequestModel requestModel, bool isUpdate = false});
/// Insert user activity (water intake).
Future<Either<Failure, GenericApiModel<dynamic>>> insertUserActivity({
required InsertUserActivityRequestModel requestModel,
});
/// Undo last user activity.
Future<Either<Failure, GenericApiModel<dynamic>>> undoUserActivity({
required UndoUserActivityRequestModel requestModel,
});
}
class WaterMonitorRepoImp implements WaterMonitorRepo {
final ApiClient apiClient;
final LoggerService loggerService;
WaterMonitorRepoImp({required this.loggerService, required this.apiClient});
@override
Future<Either<Failure, GenericApiModel<dynamic>>> getUserDetailsForWaterMonitoring({
required num progress,
required String mobileNumber,
required String identificationNo,
}) async {
Map<String, dynamic> request = {
"Progress": progress,
"MobileNumber": mobileNumber,
"IdentificationNo": identificationNo,
};
try {
GenericApiModel<dynamic>? 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<dynamic>(
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<Either<Failure, GenericApiModel<dynamic>>> getUserProgressForWaterMonitoring({
required ProgressType progressType,
required String mobileNumber,
required String identificationNo,
}) async {
final progressValue = progressType == ProgressType.today ? 1 : (progressType == ProgressType.week ? 2 : 3);
Map<String, dynamic> request = {
"Progress": progressValue,
"MobileNumber": mobileNumber,
"IdentificationNo": identificationNo,
};
try {
GenericApiModel<dynamic>? 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<dynamic>(
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<Either<Failure, GenericApiModel<dynamic>>> updateOrInsertUserDetailForWaterMonitoring({
required UpdateUserDetailRequestModel requestModel,
bool isUpdate = false,
}) async {
try {
GenericApiModel<dynamic>? 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<dynamic>(
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<Either<Failure, GenericApiModel<dynamic>>> insertUserActivity({
required InsertUserActivityRequestModel requestModel,
}) async {
try {
GenericApiModel<dynamic>? 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<dynamic>(
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<Either<Failure, GenericApiModel<dynamic>>> undoUserActivity({
required UndoUserActivityRequestModel requestModel,
}) async {
try {
GenericApiModel<dynamic>? 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<dynamic>(
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()));
}
}
}

@ -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<String> 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<String> get genderOptions => ["Male", "Female"];
//Duration Options
List<String> get durationFilters => ["Daily", "Weekly", "Monthly"];
// Network/data
final AppState _appState = GetIt.instance<AppState>();
final NavigationService _navigationService = GetIt.instance<NavigationService>();
bool _isLoading = false;
dynamic _userDetailData;
// Progress data lists
List<UserProgressForTodayModel> _todayProgressList = [];
List<UserProgressForWeekModel> _weekProgressList = [];
List<UserProgressForMonthModel> _monthProgressList = [];
List<UserProgressHistoryModel> _historyList = [];
bool get isLoading => _isLoading;
dynamic get userDetailData => _userDetailData;
// Getters for progress data
List<UserProgressForTodayModel> get todayProgressList => _todayProgressList;
List<UserProgressForWeekModel> get weekProgressList => _weekProgressList;
List<UserProgressForMonthModel> get monthProgressList => _monthProgressList;
List<UserProgressHistoryModel> 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<void> 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<void> 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<String, dynamic>));
}
}
break;
case ProgressType.week:
_weekProgressList.clear();
for (var item in apiModel.data) {
if (item is Map) {
_weekProgressList.add(UserProgressForWeekModel.fromJson(item as Map<String, dynamic>));
}
}
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<String, dynamic>));
}
}
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<void> 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<bool> saveSettings() async {
if (!isValid) {
notifyListeners(); // Notify so error can be read
return false;
}
_validationError = null; // Clear error on success
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<WaterCupModel> 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<bool> 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<bool> 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;
}
}
}

@ -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<WaterConsumptionScreen> {
@override
void initState() {
super.initState();
// Initialize VM once the widget is mounted
WidgetsBinding.instance.addPostFrameCallback((_) {
final vm = Provider.of<WaterMonitorViewModel>(context, listen: false);
vm.initialize();
// optional: log current progress for debugging
// ignore: avoid_print
WidgetsBinding.instance.addPostFrameCallback((_) async {
final vm = context.read<WaterMonitorViewModel>();
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<WaterConsumptionScreen> {
],
),
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),
SizedBox(height: 8.h),
],
"${"Choose sparkling water instead of soda"}".needTranslation.toText12(
fontWeight: FontWeight.w500,
color: AppColors.textColorLight,
),
);
}
_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<WaterConsumptionScreen> {
],
),
),
child: LayoutBuilder(
builder: (context, constraints) {
// Responsive: stack vertically on small screens
final isSmallScreen = constraints.maxWidth < 600;
if (isSmallScreen) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
child: Column(
children: [
Expanded(flex: 3, child: _buildStatsColumn()),
SizedBox(width: 16.w),
Expanded(flex: 2, child: _buildWaterBottleWidget()),
],
);
} else {
// Larger screens: more space for bottle
return Row(
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(flex: 2, child: _buildStatsColumn()),
SizedBox(width: 32.w),
Flexible(flex: 1, child: _buildWaterBottleWidget()),
Expanded(
flex: isTablet ? 2 : 3,
child: Consumer<WaterMonitorViewModel>(builder: (context, vm, _) {
if (vm.isLoading) {
// show shimmer placeholder while fetching server-side user details
return _buildLoadingShimmer(isForHistory: false);
}
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),
subTitle.toText12(
fontWeight: FontWeight.w600,
color: subTitleColor ?? AppColors.greyTextColor,
),
],
);
}
@ -147,84 +166,505 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
Widget _buildWaterBottleWidget() {
return Consumer<WaterMonitorViewModel>(
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");
// 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;
// SVG aspect ratio
const svgAspectRatio = 315.0 / 143.0; // ~2.2
// 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)
// 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);
}
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(
height: fillableHeight,
child: ClipPath(
clipper: BottleShapeClipper(),
child: Stack(
alignment: Alignment.bottomCenter,
children: [
// Animated wave
Positioned(
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
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,
),
),
],
),
),
),
),
),
],
),
);
},
);
}
// Bubbles positioned dynamically based on water level
if (progressPercent > 10)
_buildBottomActionWidgets() {
return Consumer<WaterMonitorViewModel>(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,
),
),
],
),
],
);
});
}
_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,
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<double> animation) {
return FadeTransition(
opacity: animation,
child: ScaleTransition(
scale: animation,
child: child,
),
);
},
child: Container(
key: ValueKey<bool>(viewModel.isGraphView),
child: Utils.buildSvgWithAssets(
icon: AppAssets.waterBottleOuterBubbles,
height: 60.h,
width: fillableWidth * 0.7,
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<Widget> 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<String>(
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<String> items,
required String selectedValue,
required Function(String) onSelected,
bool useUpperCase = false,
}) {
final dialogService = getIt.get<DialogService>();
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
@ -245,7 +685,7 @@ class _WaterConsumptionScreenState extends State<WaterConsumptionScreen> {
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<WaterConsumptionScreen> {
SizedBox(height: 16.h),
_buildWaterIntakeSummaryWidget(),
SizedBox(height: 16.h),
_buildHistoryGraphOrList(),
SizedBox(height: 16.h),
_buildHydrationTipsWidget(),
SizedBox(height: 16.h),
],
),
),
);
// );
}
}

@ -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<WaterMonitorSettingsScreen> createState() => _WaterMonitorSettingsScreenState();
}
class _WaterMonitorSettingsScreenState extends State<WaterMonitorSettingsScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<WaterMonitorViewModel>().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<WaterMonitorSettingsScreen>
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,

@ -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,

Loading…
Cancel
Save