diff --git a/assets/data/appointments.json b/assets/data/appointments.json index 587d3d50..090c19d9 100644 --- a/assets/data/appointments.json +++ b/assets/data/appointments.json @@ -7,7 +7,7 @@ "appointment_date": "Tuesday, April 30 2024", "date_attended": null, "appt_status": "Reschedule Pending Approval", - "r_status": "0", + "": "0", "appointment": "05-04-2024" } ] diff --git a/assets/data/blood_pressure_advice.json b/assets/data/blood_pressure_advice.json new file mode 100644 index 00000000..b43a064d --- /dev/null +++ b/assets/data/blood_pressure_advice.json @@ -0,0 +1,40 @@ +{ +"bloodPressureCategories": [ +{ +"status": "Optimal", +"systolic": "<120", +"diastolic": "<80", +"advice": "Maintain a healthy lifestyle with regular exercise, a balanced diet rich in fruits and vegetables, and limit salt intake." +}, +{ +"status": "Normal", +"systolic": "120-129", +"diastolic": "<80", +"advice": "Continue healthy habits; monitor your blood pressure regularly. Aim for physical activity most days of the week." +}, +{ +"status": "Elevated", +"systolic": "130-139", +"diastolic": "<80", +"advice": "Adopt a heart-healthy diet, reduce sodium intake, and increase physical activity. Consider stress management techniques." +}, +{ +"status": "Hypertension Grade 1", +"systolic": "140-159", +"diastolic": "80-89", +"advice": "Consult a healthcare provider for lifestyle changes, such as weight management, and possible medication. Regular monitoring is crucial." +}, +{ +"status": "Hypertension Grade 2", +"systolic": "160-179", +"diastolic": "90-109", +"advice": "Seek medical advice for more intensive management and treatment options. Focus on a low-sodium diet and consistent exercise." +}, +{ +"status": "Hypertension Grade 3", +"systolic": "≥180", +"diastolic": "≥110", +"advice": "Immediate medical attention is necessary. Follow a strict treatment plan, including medication adherence and regular follow-ups with your healthcare provider." +} +] +} diff --git a/assets/data/blood_sugar_advice.json b/assets/data/blood_sugar_advice.json new file mode 100644 index 00000000..f0dd325f --- /dev/null +++ b/assets/data/blood_sugar_advice.json @@ -0,0 +1,24 @@ +{ + "status": [ + { + "label": "Normal", + "description": "Blood sugar levels are within the normal range.", + "advice": "Maintain a balanced diet and regular exercise to keep your blood sugar stable." + }, + { + "label": "Impaired Fasting", + "description": "Blood sugar levels are elevated after fasting.", + "advice": "Consider lifestyle changes such as a healthier diet, regular physical activity, and monitoring your blood sugar levels." + }, + { + "label": "Impaired Glucose Tolerance", + "description": "Blood sugar levels are elevated after meals.", + "advice": "Implement dietary changes, increase physical activity, and consult a healthcare professional for a personalized plan." + }, + { + "label": "Diabetes", + "description": "Blood sugar levels indicate diabetes.", + "advice": "Seek medical advice for a comprehensive management plan, including medication, diet, and exercise." + } + ] +} diff --git a/assets/data/previous.json b/assets/data/previous.json deleted file mode 100644 index c8f65e3a..00000000 --- a/assets/data/previous.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "success": true, - "data": [ - { - "appointment_type": "Clinical Review", - "appointment_date": "Tuesday, January 9 2024", - "visit_date": null, - "appt_status": "Missed" - }, - { - "appointment_type": "Clinical Review", - "appointment_date": "Wednesday, October 4 2023", - "visit_date": null, - "appt_status": "Missed" - }, - { - "appointment_type": "Clinical Review", - "appointment_date": "Friday, September 22 2023", - "visit_date": null, - "appt_status": "Missed" - }, - { - "appointment_type": "Clinical Review", - "appointment_date": "Thursday, August 10 2023", - "visit_date": null, - "appt_status": "Missed" - }, - { - "appointment_type": "Re-Fill", - "appointment_date": "Thursday, August 10 2023", - "visit_date": "Tuesday, August 15 2023", - "appt_status": "Missed" - }, - { - "appointment_type": "Re-Fill", - "appointment_date": "Tuesday, August 8 2023", - "visit_date": "Tuesday, August 8 2023", - "appt_status": "Kept" - }, - { - "appointment_type": "Clinical Review", - "appointment_date": "Friday, August 4 2023", - "visit_date": "Tuesday, August 8 2023", - "appt_status": "Missed" - }, - { - "appointment_type": "Clinical Review", - "appointment_date": "Monday, April 10 2023", - "visit_date": "Wednesday, March 15 2023", - "appt_status": "Kept" - }, - { - "appointment_type": "Clinical Review", - "appointment_date": "Thursday, March 9 2023", - "visit_date": "Tuesday, August 8 2023", - "appt_status": "Missed" - }, - { - "appointment_type": "Clinical Review", - "appointment_date": "Friday, February 10 2023", - "visit_date": "Wednesday, March 15 2023", - "appt_status": "Missed" - } - ] -} \ No newline at end of file diff --git a/assets/data/provider.json b/assets/data/provider.json new file mode 100644 index 00000000..a37a72c3 --- /dev/null +++ b/assets/data/provider.json @@ -0,0 +1,20 @@ +{ + "success": false, + "message": "Provider already exist locally", + "provider": { + "id": 1, + "family_name": "MAKORI", + "given_name": "DENNIS ONDENYO", + "salutation": "Dr.", + "national_id": 32923183, + "license_number": "K-ANN-2024-002180", + "board_number": "PUID-002179", + "cadre": "Optometrist", + "gender": "UNSPECIFIED", + "facility_code": null, + "user_id": 53, + "createdAt": "2024-10-09T07:10:06.000Z", + "updatedAt": "2024-10-09T07:10:06.000Z", + "deletedAt": null + } +} \ No newline at end of file diff --git a/assets/data/reschedule.json b/assets/data/reschedule.json new file mode 100644 index 00000000..e7213aa8 --- /dev/null +++ b/assets/data/reschedule.json @@ -0,0 +1,36 @@ +[ + { + "id": "3123045", + "ccc_no": "1234500002", + "appointment_type": "Re-Fill", + "appointment_date": "Thursday, August 10 2023", + "reschedule_date": "Tuesday, January 9 2024", + "facility_name": "Milimani Dispensary", + "appt_status": "Reschedule Pending Approval", + "reschedule_reason": "I will be a way this week", + "reschedule_status": "0" + }, + { + "id": "3123046", + "ccc_no": "12345000023", + "appointment_type": "Re-Fill", + "appointment_date": "Tuesday, August 8 2023", + "reschedule_date": "Tuesday, January 9 2024", + "facility_name": "Milimani Dispensary", + "appt_status": "Reschedule Pending Approval", + "reschedule_reason": "I will be a way this week", + "reschedule_status": "0" + } +, + { + "id": "3123047", + "ccc_no": "1234500001", + "appointment_type": "Re-Fill", + "appointment_date": "Tuesday, August 8 2023", + "reschedule_date": "Tuesday, January 9 2024", + "facility_name": "Milimani Dispensary", + "appt_status": "Reschedule Pending Approval", + "reschedule_reason": "I will be a way this week", + "reschedule_status": "0" + } +] \ No newline at end of file diff --git a/assets/data/self_screening_bs.json b/assets/data/self_screening_bs.json new file mode 100644 index 00000000..3e4573b4 --- /dev/null +++ b/assets/data/self_screening_bs.json @@ -0,0 +1,50 @@ +[ +{ +"id": "1", +"image": "assets/images/doctors.svg", +"source": "assets/images/doctors.svg", +"header": "Insight Blood Sugar", +"title": "What is blood sugar", +"description": "According to the World Health Organization (WHO), blood sugar is defined as the amount of glucose present in the blood. Blood sugar is usually expressed in terms of two measurements: **fasting blood sugar** (the level of glucose in the blood after an overnight fast) and **postprandial blood sugar** (the level of glucose in the blood after a meal).\n\n **Blood Sugar Classification (WHO, 2018)**\n- **Normal blood sugar:** Less than 100 mg/dL (fasting) and less than 140 mg/dL (postprandial).\n- **Impaired fasting glucose:** 100-125 mg/dL (fasting).\n- **Impaired glucose tolerance:** 140-199 mg/dL (postprandial).\n- **Diabetes:** 126 mg/dL or higher (fasting) and 200 mg/dL or higher (postprandial).\n\n **Source:**\n- *World Health Organization. (2018). WHO Guidelines for the Management of Diabetes.*" +}, +{ +"id": "2", +"image": "assets/images/heart.svg", +"source": "assets/images/heart.svg", +"header": "Insight Blood Sugar", +"title": "Details of blood sugar level", +"description": "According to the World Health Organization (WHO), the following are the details of blood sugar levels:\n\n **Blood Sugar Classification (WHO, 2018)**\n- **Normal blood sugar:**\n- *Fasting blood sugar: Less than 100 mg/dL*\n- *Postprandial blood sugar: Less than 140 mg/dL*\n- **Impaired fasting glucose:**\n- *Fasting blood sugar: 100-125 mg/dL*\n- **Impaired glucose tolerance:**\n- *Postprandial blood sugar: 140-199 mg/dL*\n- **Diabetes:**\n- *Fasting blood sugar: 126 mg/dL or higher*\n- *Postprandial blood sugar: 200 mg/dL or higher*\n\n **Additional definitions:** \n- **Glycemic control:** The ability to maintain blood sugar levels within a target range.\n- **Hypoglycemia:** Blood sugar levels below 70 mg/dL.\n- **Hyperglycemia:** Blood sugar levels above 180 mg/dL.\n\n **WHO Guidelines for Blood Sugar Measurement**\n- Blood sugar should be measured in a fasting state, at least 8 hours after the last meal.\n- The measurement should be taken using a glucometer or a laboratory test.\n- Two or more readings should be taken, with the average value used for classification.\n\n **Interpretation of Blood Sugar Readings**\n- **Single reading:** A single blood sugar reading is not sufficient to diagnose diabetes. Multiple readings should be taken over time to confirm the diagnosis.\n- **Variability:** Blood sugar can vary throughout the day. A single reading may not accurately reflect an individual's usual blood sugar.\n- **Trend:** A trend of increasing blood sugar over time is more important than a single reading.\n\n**Note:** \n- It's essential to note that these values are general guidelines, and individual cases may vary. It's always best to consult with a healthcare professional for a proper diagnosis and management of blood sugar levels.\n- These reference values are for adults aged 18 years and older.\n\n\n **Source:**\n- *World Health Organization. (2018). WHO Guidelines for the Management of Diabetes.*" +}, +{ +"id": "3", +"image": "assets/images/insight_board.svg", +"source": "assets/images/insight_board.svg", +"header": "Module Disclaimer", +"title": "Disclaimer", +"description": "Self-screening is intended for monitoring and tracking your health, not for diagnosing medical conditions.\n\n\nUsers can input their daily measurement readings into the app, which then generates charts and graphs to help monitor their health based on the data provided. Please note that the app does not measure the readings itself but only records and tracks the user-entered data.\n\nThe app currently draws information from reputable sources such as the CDC and WHO. However, please be aware that the data provided may not always reflect real-life situations accurately.\n\nThe app is intended for informational purposes only and is not a medical device. Users experiencing health issues should consult with doctors or healthcare professionals for appropriate advice and treatment." +}, +{ +"id": "4", +"image": "assets/images/Medical prescription-bro.svg", +"source": "assets/images/Medical prescription-bro.svg", +"header": "Insight Blood Sugar", +"title": "How to prevent and manage high and low blood sugar", +"description": "According to the World Health Organization (WHO), preventing and managing high and low blood sugar requires a combination of lifestyle changes, dietary modifications, and medical treatment. Here are the WHO-recommended guidelines for preventing and managing high and low blood sugar:\n\n **Prevention:**\n- **Maintain a healthy lifestyle:** Engage in regular physical activity, eat a balanced diet, and avoid smoking and excessive alcohol consumption.\n- **Monitor blood sugar:** Regularly monitor blood sugar to detect any changes or abnormalities.\n- **Get enough sleep:** Aim for 7-8 hours of sleep per night to help regulate blood sugar.\n **Dietary Recommendations:**\n- **Eat a balanced diet:** Focus on whole, unprocessed foods like vegetables, fruits, whole grains, lean proteins, and healthy fats.\n- **Choose low-carb foods:** Limit foods high in carbohydrates, added sugars, and saturated fats.\n- **Stay hydrated:** Drink plenty of water and other fluids to help regulate blood sugar.\n- **Limit caffeine and nicotine:** Both caffeine and nicotine can cause blood sugar to rise.\n\n **Management:**\n- **Medications:** If you have diabetes, your doctor may prescribe medications to help manage blood sugar. These medications may include:\n- *Metformin (Glucophage) to decrease glucose production in the liver*\n- *Sulfonylureas (Glyburide) to stimulate insulin release*\n- *Pioglitazone (Actos) to increase insulin sensitivity*\n- **Insulin therapy:** If you have type 1 diabetes or insulin-dependent type 2 diabetes, you may need insulin therapy to manage blood sugar.\n- **Monitoring:** Regularly monitor blood sugar to adjust treatment as needed.\n\n**Managing High Blood Sugar Episodes:**\n- **Drink water:** Drink water or other fluids to help lower blood sugar.\n- **Take medication:** Take medication as prescribed by your doctor to help lower blood sugar.\n- **Eat a snack:** Eat a snack rich in carbohydrates and protein to help lower blood sugar.\n- **Avoid strenuous activity:** Avoid strenuous activity until blood sugar returns to normal.\n\n**Managing Low Blood Sugar Episodes:**\n- **Eat a snack:** Eat a snack rich in carbohydrates and protein to help raise blood sugar.\n- **Drink juice or soda:** Drink juice or soda to help raise blood sugar.\n- **Take glucose tablets:** Take glucose tablets as prescribed by your doctor to help raise blood sugar.\n- **Seek medical attention:** Seek medical attention if symptoms persist or worsen.\n\n**WHO Guidelines for Blood Sugar Management:**\n- **Definition:** High blood sugar is defined as a fasting blood sugar ≥ 126 mg/dL or a postprandial blood sugar ≥ 200 mg/dL.\n- **Classification:** High blood sugar can be classified into three categories:\n- **Mild:** fasting blood sugar 126-140 mg/dL or postprandial blood sugar 200-240 mg/dL\n- **Moderate:** fasting blood sugar 141-160 mg/dL or postprandial blood sugar 241-280 mg/dL\n- **Severe:** fasting blood sugar ≥ 161 mg/dL or postprandial blood sugar ≥ 281 mg/dL\n- **Treatment goals:** The treatment goal for high blood sugar is to achieve a fasting blood sugar < 100 mg/dL and a postprandial blood sugar < 140 mg/dL.\n\n**Source:**\n- *World Health Organization. (2018). Diabetes*\n- *World Health Organization. (2019). Blood Sugar*\n- *World Health Organization. (2018). Guidelines for the Management of Diabetes*" +}, +{ +"id": "5", +"image": "assets/images/pills.svg", +"source": "assets/images/pills.svg", +"header": "Insight Blood Sugar", +"title": "Knowledge of blood sugar drugs", +"description": "According to the World Health Organization (WHO), the following are some common blood sugar drugs, their mechanisms of action, and potential side effects:\n\n**Metformin:**\n- **Mechanism of action:** Decreases glucose production in the liver and increases insulin sensitivity.\n- *Side effects: Gastrointestinal upset, diarrhea, abdominal pain.*\n\n**Sulfonylureas:**\n- **Mechanism of action:** Stimulates insulin release from the pancreas.\n- *Side effects: Hypoglycemia, weight gain, skin rash.*\n\n**Pioglitazone:**\n- **Mechanism of action:** Increases insulin sensitivity and decreases glucose production in the liver.\n- *Side effects: Edema, weight gain, increased risk of bladder cancer.*\n\n**Insulin:**\n- **Mechanism of action:** Lowers blood sugar by facilitating glucose uptake in cells.\n- *Side effects: Hypoglycemia, weight gain, injection site reactions.*\n\n**GLP-1 Receptor Agonists:**\n- **Mechanism of action:** Increases insulin release and decreases glucagon release.\n- *Side effects: Nausea, vomiting, diarrhea, increased risk of pancreatitis.*\n\n**SGLT-2 Inhibitors:**\n- **Mechanism of action:** Increases glucose excretion in the urine.\n- *Side effects: Genital mycotic infections, urinary tract infections, increased risk of kidney damage.*\n\n**WHO Guidelines for Blood Sugar Management:**\n- 1. **Lifestyle modifications**: Encourage lifestyle modifications, such as regular physical activity, a balanced diet, and stress reduction.\n- 2. **Pharmacological treatment**: Use pharmacological treatment to achieve a fasting blood sugar < 100 mg/dL and a postprandial blood sugar < 140 mg/dL.\n- 3. **Combination therapy**: Use combination therapy to achieve blood sugar targets, especially in patients with multiple risk factors.\n- 4. **Monitoring**: Regularly monitor blood sugar and adjust treatment as needed.\n\n**Sources:**\n- *World Health Organization. (2018).*\n- World Health Organization. (2019). Blood Sugar.*\n- World Health Organization. (2018). Guidelines for the Management of Diabetes.*" +}, +{ +"id": "6", +"image": "assets/images/food.svg", +"source": "assets/images/food.svg", +"header": "Insight Blood Sugar", +"title": "Foods to help manage high and low blood sugar", +"description": "According to the World Health Organization (WHO), the following foods can help manage high and low blood sugar:\n\n**Foods to Help Manage High Blood Sugar:**\n- **Leafy Greens:** Spinach, kale, and collard greens are rich in fiber, vitamins, and minerals that can help lower blood sugar.\n- **Berries:** Berries such as blueberries, strawberries, and raspberries are rich in antioxidants and fiber that can help lower blood sugar.\n- **Fatty Fish:** Fatty fish such as salmon, tuna, and mackerel are rich in omega-3 fatty acids that can help lower inflammation and improve insulin sensitivity.\n- **Whole Grains:** Whole grains such as brown rice, quinoa, and whole wheat bread can help lower blood sugar by reducing carbohydrate intake and increasing fiber intake.\n- **Legumes:** Legumes such as lentils, chickpeas, and black beans are rich in protein, fiber, and complex carbohydrates that can help lower blood sugar.\n- **Nuts and Seeds:** Nuts and seeds such as almonds, sunflower seeds, and pumpkin seeds are rich in healthy fats, protein, and fiber that can help lower blood sugar.\n- **Herbs and Spices:** Herbs and spices such as cinnamon, turmeric, and ginger have anti-inflammatory properties that can help lower blood sugar.\n- **Low-Fat Dairy:** Low-fat dairy products such as milk, cheese, and yogurt are rich in protein, calcium, and vitamins that can help lower blood sugar.\n\n**Foods to Help Manage Low Blood Sugar:**\n- **Fast-Acting Carbohydrates:** Foods high in fast-acting carbohydrates such as white bread, sugary snacks, and sweetened beverages can help raise blood sugar quickly.\n- **Fruits:** Fruits such as bananas, apples, and oranges are rich in natural sugars that can help raise blood sugar.\n- **Dried Fruits:** Dried fruits such as dates, apricots, and prunes are rich in natural sugars that can help raise blood sugar.\n- **Honey and Maple Syrup:** Honey and maple syrup are natural sweeteners that can help raise blood sugar.\n- **Candy and Chocolate:** Candy and chocolate are high in sugar and can help raise blood sugar quickly.\n\n**WHO Guidelines for Blood Sugar Management:**\n- **Dietary Recommendations:** Encourage a balanced diet that is low in added sugars, saturated fats, and refined carbohydrates, and high in whole, unprocessed foods.\n- **Carbohydrate Intake:** Limit carbohydrate intake to 45-65% of total daily calories.\n- **Fiber Intake:** Increase fiber intake to at least 25 grams per day.\n- **Protein Intake:** Increase protein intake to at least 0.8 grams per kilogram of body weight per day.\n\n**Sources:**\n- *World Health Organization. (2018).*\n- World Health Organization. (2019). Blood Sugar.*\n- World Health Organization. (2018). Guidelines for the Management of Diabetes.*" +} +] \ No newline at end of file diff --git a/assets/data/self_screening_insight.json b/assets/data/self_screening_insight.json new file mode 100644 index 00000000..7811abba --- /dev/null +++ b/assets/data/self_screening_insight.json @@ -0,0 +1,51 @@ +[ + { + "id": "1", + "image": "assets/images/doctors.svg", + "source": "assets/images/doctors.svg", + "header": "Insight Blood Pressure", + "title": "What is blood pressure", + "description": "According to the World Health Organization (WHO), blood pressure is defined as the force exerted by the blood on the walls of the arteries as it circulates throughout the body. Blood pressure is usually expressed in terms of two measurements: **systolic pressure** (the pressure in the arteries when the heart beats) and **diastolic pressure** (the pressure in the arteries when the heart is at rest between beats).\n\n **Blood Pressure Classification (WHO, 2018)**\n- **Optimal blood pressure:** Less than 120/80 mmHg.\n- **Normal blood pressure:** 120-129/80-84 mmHg\n- **Elevated blood pressure:** 130-139/85-89 mmHg.\n- **Grade 1 hypertension:** 140-159/90-99 mmHg.\n- **Grade 2 hypertension:** 160-179/100-109 mmHg\n- **Grade 3 hypertension:** 180 or higher/110 or higher mmHg.\n\n **Source:**\n- *World Health Organization. (2018). WHO Guidelines for the Management of Hypertension.*" + }, + { + "id": "2", + "image": "assets/images/heart.svg", + "source": "assets/images/heart.svg", + "header": "Insight Blood Pressure", + "title": "Details of blood pressure level", + "description": "According to the World Health Organization (WHO), the following are the details of blood pressure levels:\n\n **Blood Pressure Classification (WHO, 2018)**\n- **Optimal blood pressure:**\n- *Systolic blood pressure (SBP): Less than 120 mmHg*\n- *Diastolic blood pressure (DBP): Less than 80mmHg*\n- **Normal blood pressure:**\n- *SBP 120-129 mmHg* \n- *DBP 80-84 mmHg*\n- **Elevated blood pressure:**\n- *SBP 130-139 mmHg* \n- *DBP 85-89 mmHg*\n- **Grade 1 hypertension:** \n- *SBP 140-159* \n- *DBP 90-99 mmHg*\n- **Grade 2 hypertension:** \n- *SBP 160-179* \n- *100-109 mmHg*\n- **Grade 3 hypertension:** \n- *180 or higher mmHg*\n- *110 or higher mmHg*\n\n **Additional definitions:** \n- **Isolated systolic hypertension:** SBP ≥ 140 mmHg and DBP < 90 mmHg. \n- **Isolated diastolic hypertension:** SBP < 140 mmHg and DBP ≥ 90 mmHg. \n- **White coat hypertension:** Elevated blood pressure in a clinical setting, but normal blood pressure at home.\n- **Masked hypertension:** Normal blood pressure in a clinical setting, but elevated blood pressure at home\n\n **WHO Guidelines for Blood Pressure Measurement**\n- Blood pressure should be measured in a seated position, with the arm at heart level.\n- The cuff should be of the correct size for the arm.\n- The measurement should be taken after a period of rest (at least 5 minutes).\n- Two or more readings should be taken, with the average value used for classification.\n\n **Interpretation of Blood Pressure Readings**\n- **Single reading:** A single blood pressure reading is not sufficient to diagnose hypertension. Multiple readings should be taken over time to confirm the diagnosis.\n- **Variability:** Blood pressure can vary throughout the day. A single reading may not accurately reflect an individual's usual blood pressure.\n- **Trend:** A trend of increasing blood pressure over time is more important than a single reading.\n\n**Note:** \n- It's important to note that these values are general guidelines, and individual cases may vary. It's always best to consult with a healthcare professional for a proper diagnosis and management of blood pressure levels.\n- These reference values are for adults aged 18 years and older.\n\n\n **Source:**\n- *World Health Organization. (2018). WHO Guidelines for the Management of Hypertension.*" + }, + { + "id": "3", + "image": "assets/images/insight_board.svg", + "source": "assets/images/insight_board.svg", + "header": "Module Disclaimer", + "title": "Disclaimer", + "description": "Self-screening is intended for monitoring and tracking your health, not for diagnosing medical conditions.\n\n\nUsers can input their daily measurement readings into the app, which then generates charts and graphs to help monitor their health based on the data provided. Please note that the app does not measure the readings itself but only records and tracks the user-entered data.\n\nThe app currently draws information from reputable sources such as the CDC and WHO. However, please be aware that the data provided may not always reflect real-life situations accurately.\n\nThe app is intended for informational purposes only and is not a medical device. Users experiencing health issues should consult with doctors or healthcare professionals for appropriate advice and treatment." + }, + { + "id": "4", + "image": "assets/images/Medical prescription-bro.svg", + "source": "assets/images/Medical prescription-bro.svg", + "header": "Insight Blood Pressure", + "title": "How to prevent and manage low blood pressure", + "description": "According to the World Health Organization (WHO), preventing and managing low blood pressure (hypotension) requires a combination of lifestyle changes, dietary modifications, and medical treatment. Here are the WHO-recommended guidelines for preventing and managing low blood pressure:\n\n **Prevention:**\n- **Maintain a healthy lifestyle:** Engage in regular physical activity, eat a balanced diet, and avoid smoking and excessive alcohol consumption.\n- **Stay hydrated:** Drink plenty of water and other fluids to help maintain blood volume and prevent dehydration.\n- **Manage stress:** Practice stress-reducing techniques like meditation, deep breathing, or yoga.\n- **Get enough sleep:** Aim for 7-8 hours of sleep per night to help regulate blood pressure.\n **Dietary Recommendations:**\n- **Increase salt intake:** Consume more salt to help increase blood volume and blood pressure. However, be mindful of excessive salt consumption, which can lead to other health problems.\n- **Eat small, frequent meals:** Eating smaller meals throughout the day can help prevent a drop in blood pressure after meals.\n- **Include blood-pressure-boosting foods:** Foods rich in vitamin B12, such as fish, eggs, and dairy products, can help increase blood pressure.\n- **Avoid caffeine and nicotine:** Both caffeine and nicotine can cause blood vessels to constrict, leading to a drop in blood pressure.\n\n **Management:**\n- **Monitor blood pressure:** Regularly monitor blood pressure to detect any changes or abnormalities.\n- **Medications:** If you have chronic low blood pressure, your doctor may prescribe medications to help increase blood pressure. These medications may include:\n- *Fludrocortisone (Florinef) to increase blood volume*/n- *Midodrine (ProAmatine) to constrict blood vessels and increase blood pressure*\n- *Ephedrine to increase blood pressure and heart rate*\n- **Compression stockings:** Wearing compression stockings can help improve circulation and increase blood pressure.\n- **Elevating the head of your bed:** Raising the head of your bed by 4-6 inches can help improve circulation and increase blood pressure.\n\n**Managing Low Blood Pressure Episodes:**\n- **Lie down:** If you experience a sudden drop in blood pressure, lie down and elevate your legs above the level of your heart.\n- **Drink water or other fluids:** Drink water or other fluids to help increase blood volume and blood pressure.\n- **Eat a snack:** Eating a snack rich in carbohydrates and protein can help increase blood sugar and blood pressure.\n- **Avoid standing up quickly:** When standing up, do so slowly and carefully to avoid a sudden drop in blood pressure.\n\n**WHO Guidelines for Low Blood Pressure:**\n- **Definition:** Low blood pressure is defined as a systolic blood pressure <90 mmHg or a diastolic blood pressure <60 mmHg.\n- **Classification:** Low blood pressure can be classified into three categories:\n- **Mild:** systolic blood pressure 90-100 mmHg or diastolic blood pressure 60-70 mmHg\n- **Moderate:** systolic blood pressure 80-89 mmHg or diastolic blood pressure 50-59 mmHg\n- **Severe:** systolic blood pressure <80 mmHg or diastolic blood pressure <50 mmHg\n- **Treatment goals:** The treatment goal for low blood pressure is to increase blood pressure to a level that is sufficient to maintain adequate organ perfusion and prevent symptoms.\n**Source:**\n- *World Health Organization. (2018). Hypotension*\n- *World Health Organization. (2019). Blood Pressure*\n- *World Health Organization. (2018). Guidelines for the Management of Hypotension*" + }, + { + "id": "5", + "image": "assets/images/pills.svg", + "source": "assets/images/pills.svg", + "header": "Insight Blood Pressure", + "title": "Knowledge of blood pressure drugs", + "description": "According to the World Health Organization (WHO), the following are some common blood pressure drugs, their mechanisms of action, and potential side effects:\n\n**Diuretics:**\n- **Hydrochlorothiazide (HCTZ)**: Increases urine production, reducing blood volume and pressure.\n- *Side effects: Increased urination, potassium loss, dehydration.*\n- **Furosemide (Lasix)**: Increases urine production, reducing blood volume and pressure.\n- *Side effects: Increased urination, potassium loss, dehydration.*\n- **Spironolactone (Aldactone)**: Blocks aldosterone, reducing sodium reabsorption and increasing potassium levels.\n- *Side effects: Breast tenderness, gynecomastia, hyperkalemia.*\n\n**Beta Blockers:**\n- **Metoprolol (Lopressor)**: Reduces heart rate and contractility, decreasing cardiac output and blood pressure.\n- *Side effects: Fatigue, dizziness, bradycardia, bronchospasm.*\n- **Atenolol (Tenormin)**: Reduces heart rate and contractility, decreasing cardiac output and blood pressure.\n- *Side effects: Fatigue, dizziness, bradycardia, bronchospasm.*\n- **Propranolol (Inderal)**: Reduces heart rate and contractility, decreasing cardiac output and blood pressure.\n- *Side effects: Fatigue, dizziness, bradycardia, bronchospasm.*\n\n **Angiotensin-Converting Enzyme (ACE) Inhibitors:**\n- **Enalapril (Vasotec)**: Blocks angiotensin-converting enzyme, reducing angiotensin II levels and blood pressure.\n- *Side effects: Cough, hyperkalemia, renal impairment.*\n- **Lisinopril (Zestril)**: Blocks angiotensin-converting enzyme, reducing angiotensin II levels and blood pressure.\n- *Side effects: Cough, hyperkalemia, renal impairment.*\n- **Captopril (Capoten)**: Blocks angiotensin-converting enzyme, reducing angiotensin II levels and blood pressure.\n- *Side effects: Cough, hyperkalemia, renal impairment.*\n\n**Angiotensin Receptor Blockers (ARBs):**\n- **Losartan (Cozaar)**: Blocks angiotensin II receptors, reducing blood pressure.\n- *Side effects: Hyperkalemia, renal impairment, dizziness.*\n- **Valsartan (Diovan)**: Blocks angiotensin II receptors, reducing blood pressure.\n- *Side effects: Hyperkalemia, renal impairment, dizziness.*\n- **Candesartan (Atacand)**: Blocks angiotensin II receptors, reducing blood pressure.\n- *Side effects: Hyperkalemia, renal impairment, dizziness.*\n\n**Calcium Channel Blockers:**\n- **Amlodipine (Norvasc)**: Blocks calcium channels, reducing blood pressure.\n- *Side effects: Edema, dizziness, flushing.*\n- **Verapamil (Calan)**: Blocks calcium channels, reducing blood pressure.\n- *Side effects: Constipation, dizziness, bradycardia.*\n- **Diltiazem (Cardizem)**: Blocks calcium channels, reducing blood pressure.\n- *Side effects: Constipation, dizziness, bradycardia.*\n\n**Alpha Blockers:**\n- **Prazosin (Minipress)**: Blocks alpha receptors, reducing blood pressure.\n- *Side effects: Orthostatic hypotension, dizziness, nasal congestion.*\n- **Terazosin (Hytrin)**: Blocks alpha receptors, reducing blood pressure.\n- *Side effects: Orthostatic hypotension, dizziness, nasal congestion.*\n\n**Centrally Acting Agents:**\n- **Clonidine (Catapres)**: Stimulates alpha receptors in the brain, reducing blood pressure.\n- *Side effects: Drowsiness, dry mouth, constipation.*\n- **Methyldopa (Aldomet)**: Stimulates alpha receptors in the brain, reducing blood pressure.\n- *Side effects: Drowsiness, dry mouth, constipation.*\n\n**Vasodilators:**\n- **Hydralazine (Apresoline)**: Dilates blood vessels, reducing blood pressure.\n- *Side effects: Headache, tachycardia, lupus-like syndrome.*\n- **Minoxidil (Loniten)**: Dilates blood vessels, reducing blood pressure.\n- *Side effects: Hirsutism, tachycardia, pericardial effusion.*\n\n**WHO Guidelines for Blood Pressure Management:**\n- 1. **Lifestyle modifications**: Encourage lifestyle modifications, such as regular physical activity, a balanced diet, and stress reduction.\n- 2. **Pharmacological treatment**: Use pharmacological treatment to achieve a blood pressure target of <140/90 mmHg.\n- 3. **Combination therapy**: Use combination therapy to achieve blood pressure targets, especially in patients with multiple risk factors.\n- 4. **Monitoring**: Regularly monitor blood pressure and adjust treatment as needed.\n\n**Sources:**\n- *World Health Organization. (2018).*\n- World Health Organization. (2019). Blood Pressure.*\n- World Health Organization. (2018). Guidelines for the Management of Hypertension.*" + } +, + { + "id": "6", + "image": "assets/images/food.svg", + "source": "assets/images/food.svg", + "header": "Insight Blood Pressure", + "title": "Foods to help manage high and low blood pressure", + "description": "According to the World Health Organization (WHO), the following foods can help manage high and low blood pressure:\n\n**Foods to Help Manage High Blood Pressure:**\n- **Leafy Greens:** Spinach, kale, and collard greens are rich in potassium, calcium, and magnesium, which can help lower blood pressure.\n- **Berries:** Berries such as blueberries, strawberries, and raspberries are rich in flavonoids, which can help improve blood vessel function and lower blood pressure.\n- **Beets:** Beets are rich in nitrates, which can help relax blood vessels and lower blood pressure.\n- **Olive Oil:** Olive oil is rich in monounsaturated fats, which can help lower total cholesterol and LDL ('bad') cholesterol levels.\n- **Fatty Fish:** Fatty fish such as salmon, tuna, and mackerel are rich in omega-3 fatty acids, which can help lower triglycerides and blood pressure.\n- **Whole Grains:** Whole grains such as brown rice, quinoa, and whole wheat bread can help lower blood pressure by reducing sodium intake and increasing potassium intake.\n- **Legumes:** Legumes such as lentils, chickpeas, and black beans are rich in potassium, magnesium, and fiber, which can help lower blood pressure.\n- **Nuts and Seeds:** Nuts and seeds such as almonds, sunflower seeds, and pumpkin seeds are rich in magnesium and potassium, which can help lower blood pressure.\n- **Herbs and Spices:** Herbs and spices such as garlic, ginger, and turmeric have anti-inflammatory properties that can help lower blood pressure.\n- **Low-Fat Dairy:** Low-fat dairy products such as milk, cheese, and yogurt are rich in calcium, potassium, and magnesium, which can help lower blood pressure.\n\n**Foods to Help Manage Low Blood Pressure:**\n- **Salt-Rich Foods:** Foods high in salt such as soy sauce, fish sauce, and processed meats can help increase blood pressure.\n- **Caffeine:** Caffeine can help increase blood pressure by constricting blood vessels.\n- **Sugar-Rich Foods:** Foods high in sugar such as candy, baked goods, and sweetened beverages can help increase blood pressure.\n- **Refined Carbohydrates:** Refined carbohydrates such as white bread, sugary snacks, and sweetened beverages can help increase blood pressure.\n- **Foods High in Tyramine:** Foods high in tyramine such as aged cheese, wine, and fermented meats can help increase blood pressure.\n- **Ginseng:** Ginseng can help increase blood pressure by stimulating the nervous system.\n- **Licorice Root:** Licorice root can help increase blood pressure by increasing aldosterone levels.\n- **Cayenne Pepper:** Cayenne pepper can help increase blood pressure by constricting blood vessels.\n- **Ginger:** Ginger can help increase blood pressure by stimulating the nervous system.\n- **Foods High in Vitamin B12:** Foods high in vitamin B12 such as fish, eggs, and dairy products can help increase blood pressure.\n\n**WHO Guidelines for Blood Pressure Management:**\n- **Dietary Recommendations:** Encourage a balanced diet that is low in sodium, added sugars, and saturated fats, and high in fruits, vegetables, whole grains, and lean protein sources.\n- **Sodium Intake:** Limit sodium intake to less than 2,000 mg per day.\n- **Potassium Intake:** Increase potassium intake to at least 3,500 mg per day.\n- **Calcium Intake:** Increase calcium intake to at least 1,000 mg per day.\n- **Magnesium Intake:** Increase magnesium intake to at least 400 mg per day.\n\n**Sources:**\n- *World Health Organization. (2018).*\n- World Health Organization. (2019). Blood Pressure.*\n- World Health Organization. (2018). Guidelines for the Management of Hypertension.*" + } +] \ No newline at end of file diff --git a/assets/data/visits.json b/assets/data/visits.json index 817aa695..a00d3565 100644 --- a/assets/data/visits.json +++ b/assets/data/visits.json @@ -1,203 +1,344 @@ -{ - "uuid": "1", - "visitDate": "2023-03-06", - "conditions": [ - { - "uuid": "18aac1664e1-b38b6152-5534-430b-8b6a-75e4991b4811", - "name": "Mixed Hyperlipidaemia", - "onsetDate": "", - "dateRecorded": "2023-03-22", - "status": "ACTIVE", - "value": "Mixed Hyperlipidaemia" - }, - { - "uuid": "18aac1664e1-76e384c4-1a7f-46a0-bd8c-a111e9587791", - "name": "Hypothyroidism", - "onsetDate": "", - "dateRecorded": "2023-06-22", - "status": "ACTIVE", - "value": "Hypothyroidism" - }, - { - "uuid": "18aac1664e1-fe9d9bcc-d4d1-407e-ac90-e0ed126c8f12", - "name": "Chronic Kidney Disease", - "onsetDate": "", - "dateRecorded": "2023-03-22", - "status": "ACTIVE", - "value": "Chronic Kidney Disease" - }, - { - "uuid": "18aac1664e1-8d395dfe-ac0c-4175-8682-b99f02b20b05", - "name": "Generalised Epilepsy", - "onsetDate": "", - "dateRecorded": "2023-03-22", - "status": "ACTIVE", - "value": "Generalised Epilepsy" - }, - { - "uuid": "18aac1664e1-58bee68a-1610-4663-b793-cbecfc508f0f", - "name": "Cystic Fibrosis", - "onsetDate": "", - "dateRecorded": "2023-03-22", - "status": "ACTIVE", - "value": "Cystic Fibrosis" - } - ], - "diagnosis": [ - { - "uuid": "18aac1664e1-04a0bdce-476f-412d-b0cc-3d44460b63ba", - "name": "Syndactyly of Fingers with Fusion of Bone", - "dateRecorded": "2023-03-22", - "value": "Syndactyly of Fingers with Fusion of Bone" - }, - { - "uuid": "18aac1664e2-a35ed577-b747-454c-85cf-1caf0b5ec76c", - "name": "Syndactyly of Fingers with Fusion of Bone", - "dateRecorded": "2023-03-22", - "value": "Syndactyly of Fingers with Fusion of Bone" - }, - { - "uuid": "18aac1664e2-5071db27-e8dc-480e-bae0-65686dc38f6b", - "name": "Furuncle of Other Specified Site", - "dateRecorded": "2023-03-22", - "value": "Furuncle of Other Specified Site" - }, - { - "uuid": "18aac1664e2-ac2b5c17-fc83-4256-8299-f9b222d50175", - "name": "Skin Rash due to Fur", - "dateRecorded": "2023-03-22", - "value": "Skin Rash due to Fur" - }, - { - "uuid": "18aac1664e2-4fef0625-96e6-4c6a-a61e-e5defe4c1c09", - "name": "Juvenile Fucosidosis", - "dateRecorded": "2023-03-22", - "value": "Juvenile Fucosidosis" - } - ], - "allergies": [ - { - "uuid": "18aac1664e2-bc3027eb-5446-44ee-877f-97270632dd94", - "allergen": "Sulfonamides", - "reaction": "Diarrhea", - "severity": "MILD", - "onsetDate": "", - "dateRecorded": "2023-03-06" - }, - { - "uuid": "18aac1664e2-8a0dd5e0-e4eb-4346-b9f3-ade9640f7bbc", - "allergen": "Heparins", - "reaction": "Pruritus", - "severity": "MILD", - "onsetDate": "", - "dateRecorded": "2023-03-06" - }, - { - "uuid": "18aac1664e2-14a8324c-ca0f-4221-bb2a-58444a0835c5", - "allergen": "ATAZANAVIR", - "reaction": "Angioedema", - "severity": "MILD", - "onsetDate": "", - "dateRecorded": "2023-03-06" - }, - { - "uuid": "18aac1664e2-eb5661dc-671e-4cdc-b8ce-48d40a16fdc4", - "allergen": "Cephalosporins", - "reaction": "Bronchospasm", - "severity": "MILD", - "onsetDate": "", - "dateRecorded": "2023-03-06" - }, - { - "uuid": "18aac1664e2-b4e3fe29-6e27-4509-86e8-30a4034da231", - "allergen": "TETRACYCLINE", - "reaction": "Hypertension", - "severity": "MILD", - "onsetDate": "", - "dateRecorded": "2023-03-06" - } - ], - "vitals": [ - { - "uuid": "28f55adb-b3b6-4276-8ed6-308e0a599bb8", - "name": "Height (cm)", - "dateRecorded": "2023-09-18", - "value": "175.0" - }, - { - "uuid": "618861ec-0807-4b60-a901-a7683ff9c7d4", - "name": "Systolic blood pressure", - "dateRecorded": "2023-09-18", - "value": "120.0" - }, - { - "uuid": "b1d0c707-f106-4547-89dc-ecdeea871f99", - "name": "Weight (kg)", - "dateRecorded": "2023-09-18", - "value": "57.0" - }, - { - "uuid": "c6c41390-5c6c-4e97-812b-2efbe5521931", - "name": "Diastolic blood pressure", - "dateRecorded": "2023-09-18", - "value": "89.0" - } - ], - "labResults": [ - { - "uuid": "3fa72777-deec-40c8-8d6b-347829e98c86", - "name": "Tuberculosis polymerase chain reaction with rifampin resistance checking", - "dateRecorded": "2023-09-19", - "value": "NEGATIVE" - }, - { - "uuid": "5a186ae0-5886-46c4-84da-022607cc523b", - "name": "SPUTUM FOR ACID FAST BACILLI", - "dateRecorded": "2023-09-19", - "value": "POSITIVE" - }, - { - "uuid": "de54580e-eb49-4aa6-90f3-64917f395350", - "name": "X-ray, chest", - "dateRecorded": "2023-09-19", - "value": "Abnormal Chest X-Ray" - } - ], - "complaints": [ - { - "uuid": "0bb86f42-386f-4970-9ef1-c0d753f0ae09", - "name": "CHIEF COMPLAINT", - "dateRecorded": "2023-03-22", - "onsetDate": "", - "value": "Dysphagia" - }, - { - "uuid": "54556532-1a03-4ad1-b5fd-2a41c7378446", - "name": "CHIEF COMPLAINT", - "dateRecorded": "2023-03-22", - "onsetDate": "", - "value": "Seizure" - }, - { - "uuid": "75421f5a-6c1c-4c4f-9fa9-86cbf81cd795", - "name": "CHIEF COMPLAINT", - "dateRecorded": "2023-03-22", - "onsetDate": "", - "value": "Pain of Breast" - }, - { - "uuid": "bb10e890-4afb-4065-9304-27347a3d5c86", - "name": "CHIEF COMPLAINT", - "dateRecorded": "2023-03-22", - "onsetDate": "", - "value": "Crying Infant" - }, - { - "uuid": "e714c7fa-7e97-47ea-8d72-dec21b06563b", - "name": "CHIEF COMPLAINT", - "dateRecorded": "2023-03-22", - "onsetDate": "", - "value": "Depression" - } - ] -} \ No newline at end of file +[ + { + "uuid": "1", + "visitDate": "2023-03-06", + "facility": "KENYATTA NATIONAL HOSPITAL", + "conditions": [ + { + "uuid": "c1", + "name": "Mixed Hyperlipidaemia", + "onsetDate": "", + "dateRecorded": "2023-03-22", + "status": "ACTIVE", + "value": "Mixed Hyperlipidaemia" + }, + { + "uuid": "c2", + "name": "Hypothyroidism", + "onsetDate": "", + "dateRecorded": "2023-06-22", + "status": "ACTIVE", + "value": "Hypothyroidism" + } + ], + "medications": [ + { + "uuid": "m1", + "name": "Levothyroxine", + "dateRecorded": "2023-03-06", + "value": "Levothyroxine 50 mcg", + "indication": "Pain" + } + ], + "allergies": [ + { + "uuid": "a1", + "allergen": "Penicillin", + "reaction": "Rash", + "severity": "MILD", + "onsetDate": "", + "dateRecorded": "2023-03-06" + } + ], + "vitals": [ + { + "uuid": "v1", + "name": "Vitals", + "weight": "67 Kgs", + "temp": "36.5 C", + "systolic": "120 mm/Hg", + "diastolic": "80 mm/Hg", + "respiratory": "20", + "oxygenSaturation": "", + "height": "180 CM", + "complain": "Abdominal pain" + } + ], + "labResults": [ + { + "uuid": "lb1", + "name": "CD4 Counts Test", + "results": "1200 cells/ul", + "orderedDate": "2019-09-18", + "status": "Viral Suppressed", + "plot": 49 + } + ], + "procedures": [ + { + "uuid": "pr1", + "name": "Vasectomy", + "status": "", + "site": "Penis", + "repeat": 0 + } + ], + "immunization": [ + { + "uuid": "imm1", + "name": "HPV Vaccine", + "immunizationDate": "2024-09-18", + "manufacturer": "Johnson & Johnson", + "lot": "LOT-004" + } + ] + }, + { + "uuid": "2", + "visitDate": "2023-07-15", + "facility": "MOUNT KENYA MEDICAL CENTER", + "conditions": [ + { + "uuid": "c3", + "name": "Type 2 Diabetes", + "onsetDate": "2020-01-10", + "dateRecorded": "2023-07-15", + "status": "ACTIVE", + "value": "Type 2 Diabetes" + }, + { + "uuid": "c4", + "name": "Hypertension", + "onsetDate": "2021-11-25", + "dateRecorded": "2023-07-15", + "status": "ACTIVE", + "value": "Hypertension" + } + ], + "medications": [ + { + "uuid": "m2", + "name": "Metformin", + "dateRecorded": "2023-07-15", + "value": "Metformin 500 mg", + "indication": "Type 2 Diabetes" + }, + { + "uuid": "m3", + "name": "Lisinopril", + "dateRecorded": "2023-07-15", + "value": "Lisinopril 20 mg", + "indication": "Hypertension" + } + ], + "allergies": [ + { + "uuid": "a2", + "allergen": "Sulfa Drugs", + "reaction": "Hives", + "severity": "MODERATE", + "onsetDate": "", + "dateRecorded": "2023-07-15" + } + ], + "vitals": [ + { + "uuid": "v2", + "name": "Vitals", + "weight": "92 Kgs", + "temp": "37.1 C", + "systolic": "145 mm/Hg", + "diastolic": "90 mm/Hg", + "respiratory": "22", + "oxygenSaturation": "", + "height": "175 CM", + "complain": "Headache" + } + ], + "labResults": [ + { + "uuid": "lb2", + "name": "Blood Glucose Test", + "results": "160 mg/dL", + "orderedDate": "2023-07-15", + "status": "Uncontrolled", + "plot": 75 + } + ], + "procedures": [ + { + "uuid": "pr2", + "name": "Electrocardiogram (ECG)", + "status": "Normal", + "site": "Chest", + "repeat": 1 + } + ], + "immunization": [ + { + "uuid": "imm2", + "name": "Influenza Vaccine", + "immunizationDate": "2024-01-10", + "manufacturer": "Pfizer", + "lot": "FLU-123" + } + ] + }, + { + "uuid": "3", + "visitDate": "2024-05-25", + "facility": "Nairobi Women's Hospital", + "conditions": [ + { + "uuid": "c5", + "name": "Asthma", + "onsetDate": "2018-03-12", + "dateRecorded": "2024-05-25", + "status": "ACTIVE", + "value": "Asthma" + }, + { + "uuid": "c6", + "name": "Chronic Sinusitis", + "onsetDate": "2022-08-04", + "dateRecorded": "2024-05-25", + "status": "ACTIVE", + "value": "Chronic Sinusitis" + } + ], + "medications": [ + { + "uuid": "m4", + "name": "Salbutamol", + "dateRecorded": "2024-05-25", + "value": "Salbutamol 100 mcg", + "indication": "Asthma" + }, + { + "uuid": "m5", + "name": "Fluticasone", + "dateRecorded": "2024-05-25", + "value": "Fluticasone 50 mcg", + "indication": "Asthma" + } + ], + "allergies": [ + { + "uuid": "a3", + "allergen": "Dust", + "reaction": "Sneezing", + "severity": "MODERATE", + "onsetDate": "", + "dateRecorded": "2024-05-25" + } + ], + "vitals": [ + { + "uuid": "v3", + "name": "Vitals", + "weight": "58 Kgs", + "temp": "36.9 C", + "systolic": "110 mm/Hg", + "diastolic": "70 mm/Hg", + "respiratory": "18", + "oxygenSaturation": "", + "height": "165 CM", + "complain": "Breathing difficulty" + } + ], + "labResults": [ + { + "uuid": "lb3", + "name": "Pulmonary Function Test", + "results": "FEV1: 75%", + "orderedDate": "2024-05-25", + "status": "Mild Obstruction", + "plot": 45 + } + ], + "procedures": [ + { + "uuid": "pr3", + "name": "CT Scan Sinuses", + "status": "Normal", + "site": "Sinuses", + "repeat": 0 + } + ], + "immunization": [ + { + "uuid": "imm3", + "name": "Pneumococcal Vaccine", + "immunizationDate": "2024-06-10", + "manufacturer": "AstraZeneca", + "lot": "PNEUMO-789" + } + ] + }, + { + "uuid": "4", + "visitDate": "2024-09-10", + "facility": "Nairobi Hospital", + "conditions": [ + { + "uuid": "c7", + "name": "Gastroesophageal Reflux Disease (GERD)", + "onsetDate": "2021-05-20", + "dateRecorded": "2024-09-10", + "status": "ACTIVE", + "value": "GERD" + } + ], + "medications": [ + { + "uuid": "m6", + "name": "Omeprazole", + "dateRecorded": "2024-09-10", + "value": "Omeprazole 20 mg", + "indication": "GERD" + } + ], + "allergies": [ + { + "uuid": "a4", + "allergen": "Lactose", + "reaction": "Stomach cramps", + "severity": "SEVERE", + "onsetDate": "", + "dateRecorded": "2024-09-10" + } + ], + "vitals": [ + { + "uuid": "v4", + "name": "Vitals", + "weight": "72 Kgs", + "temp": "37.0 C", + "systolic": "130 mm/Hg", + "diastolic": "85 mm/Hg", + "respiratory": "22", + "oxygenSaturation": "", + "height": "170 CM", + "complain": "Heartburn" + } + ], + "labResults": [ + { + "uuid": "lb4", + "name": "Upper Endoscopy", + "results": "Mild Inflammation", + "orderedDate": "2024-09-10", + "status": "Stable", + "plot": 60 + } + ], + "procedures": [ + { + "uuid": "pr4", + "name": "Upper Endoscopy", + "status": "Completed", + "site": "Esophagus", + "repeat": 0 + } + ], + "immunization": [ + { + "uuid": "imm4", + "name": "Hepatitis B Vaccine", + "immunizationDate": "2024-10-10", + "manufacturer": "GSK", + "lot": "HEPB-567" + } + ] + } +] diff --git a/assets/images/Calculator.svg b/assets/images/Calculator.svg index 2f563a93..d7f523bf 100644 --- a/assets/images/Calculator.svg +++ b/assets/images/Calculator.svg @@ -1,10 +1,4 @@ - - - - - - - - - + + + diff --git a/assets/images/Calendar.svg b/assets/images/Calendar.svg index 6281ead0..6f88f68c 100644 --- a/assets/images/Calendar.svg +++ b/assets/images/Calendar.svg @@ -1,10 +1,5 @@ - - - - - - - - - + + + + diff --git a/assets/images/Pills.svg b/assets/images/Pills.svg index 0f94ebe8..8584e52b 100644 --- a/assets/images/Pills.svg +++ b/assets/images/Pills.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/assets/images/Syringe.svg b/assets/images/Syringe.svg index 64db1e08..8cc81f84 100644 --- a/assets/images/Syringe.svg +++ b/assets/images/Syringe.svg @@ -1,11 +1,7 @@ - - - - - - - - - - + + + + + + diff --git a/assets/images/bmi.svg b/assets/images/bmi.svg new file mode 100644 index 00000000..450ca468 --- /dev/null +++ b/assets/images/bmi.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/boldDuotoneFoldersFolderPathConnect.svg b/assets/images/boldDuotoneFoldersFolderPathConnect.svg new file mode 100644 index 00000000..b9fc51fe --- /dev/null +++ b/assets/images/boldDuotoneFoldersFolderPathConnect.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/boldDuotoneLikeHearts.svg b/assets/images/boldDuotoneLikeHearts.svg new file mode 100644 index 00000000..45e3de07 --- /dev/null +++ b/assets/images/boldDuotoneLikeHearts.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/boldDuotoneMedicineBone.svg b/assets/images/boldDuotoneMedicineBone.svg new file mode 100644 index 00000000..5f4de829 --- /dev/null +++ b/assets/images/boldDuotoneMedicineBone.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/boldDuotoneMedicineHeartPulse2.svg b/assets/images/boldDuotoneMedicineHeartPulse2.svg new file mode 100644 index 00000000..80887742 --- /dev/null +++ b/assets/images/boldDuotoneMedicineHeartPulse2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/boldDuotoneMedicineJarOfPills2.svg b/assets/images/boldDuotoneMedicineJarOfPills2.svg new file mode 100644 index 00000000..4828b2df --- /dev/null +++ b/assets/images/boldDuotoneMedicineJarOfPills2.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/boldDuotoneMedicinePulse.svg b/assets/images/boldDuotoneMedicinePulse.svg new file mode 100644 index 00000000..eef4df13 --- /dev/null +++ b/assets/images/boldDuotoneMedicinePulse.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/boldDuotoneMedicineStethoscope.svg b/assets/images/boldDuotoneMedicineStethoscope.svg new file mode 100644 index 00000000..4b692319 --- /dev/null +++ b/assets/images/boldDuotoneMedicineStethoscope.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/boldDuotoneMedicineSyringe.svg b/assets/images/boldDuotoneMedicineSyringe.svg new file mode 100644 index 00000000..805439bd --- /dev/null +++ b/assets/images/boldDuotoneMedicineSyringe.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/boldDuotoneMedicineTestTube.svg b/assets/images/boldDuotoneMedicineTestTube.svg new file mode 100644 index 00000000..aa54bbd4 --- /dev/null +++ b/assets/images/boldDuotoneMedicineTestTube.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/boldDuotoneMedicineVirus.svg b/assets/images/boldDuotoneMedicineVirus.svg new file mode 100644 index 00000000..89a0b4e0 --- /dev/null +++ b/assets/images/boldDuotoneMedicineVirus.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/images/chat/userImage1.png b/assets/images/chat/userImage1.png new file mode 100644 index 00000000..c17f9233 Binary files /dev/null and b/assets/images/chat/userImage1.png differ diff --git a/assets/images/chat/userImage2.png b/assets/images/chat/userImage2.png new file mode 100644 index 00000000..19d19cd6 Binary files /dev/null and b/assets/images/chat/userImage2.png differ diff --git a/assets/images/chat/userImage3.png b/assets/images/chat/userImage3.png new file mode 100644 index 00000000..1aa36a39 Binary files /dev/null and b/assets/images/chat/userImage3.png differ diff --git a/assets/images/chat/userImage4.png b/assets/images/chat/userImage4.png new file mode 100644 index 00000000..52b88fed Binary files /dev/null and b/assets/images/chat/userImage4.png differ diff --git a/assets/images/chat/userImage5.png b/assets/images/chat/userImage5.png new file mode 100644 index 00000000..b9f8eeb2 Binary files /dev/null and b/assets/images/chat/userImage5.png differ diff --git a/assets/images/chat/userImage6.png b/assets/images/chat/userImage6.png new file mode 100644 index 00000000..acd1dc21 Binary files /dev/null and b/assets/images/chat/userImage6.png differ diff --git a/assets/images/chat/userImage7.png b/assets/images/chat/userImage7.png new file mode 100644 index 00000000..e311eae9 Binary files /dev/null and b/assets/images/chat/userImage7.png differ diff --git a/assets/images/chat/userImage8.png b/assets/images/chat/userImage8.png new file mode 100644 index 00000000..9d75c8e6 Binary files /dev/null and b/assets/images/chat/userImage8.png differ diff --git a/assets/images/clinicCardCard.svg b/assets/images/clinicCardCard.svg new file mode 100644 index 00000000..70644aee --- /dev/null +++ b/assets/images/clinicCardCard.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/clinic_menu.svg b/assets/images/clinic_menu.svg new file mode 100644 index 00000000..5fee8409 --- /dev/null +++ b/assets/images/clinic_menu.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/discharge1.svg b/assets/images/discharge1.svg new file mode 100644 index 00000000..2c4d09e3 --- /dev/null +++ b/assets/images/discharge1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/doctor-coat.svg b/assets/images/doctor-coat.svg new file mode 100644 index 00000000..c15692b1 --- /dev/null +++ b/assets/images/doctor-coat.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/assets/images/emptyself_screening.svg b/assets/images/emptyself_screening.svg new file mode 100644 index 00000000..e5bbe42a --- /dev/null +++ b/assets/images/emptyself_screening.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/food.svg b/assets/images/food.svg new file mode 100644 index 00000000..cec19f48 --- /dev/null +++ b/assets/images/food.svg @@ -0,0 +1,2022 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/group_clinic_card.svg b/assets/images/group_clinic_card.svg new file mode 100644 index 00000000..f0499eb0 --- /dev/null +++ b/assets/images/group_clinic_card.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/heart.svg b/assets/images/heart.svg new file mode 100644 index 00000000..32483330 --- /dev/null +++ b/assets/images/heart.svg @@ -0,0 +1,1447 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/insight_board.svg b/assets/images/insight_board.svg new file mode 100644 index 00000000..697f0e9e --- /dev/null +++ b/assets/images/insight_board.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/moods1.svg b/assets/images/moods1.svg new file mode 100644 index 00000000..64bb1e7f --- /dev/null +++ b/assets/images/moods1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/partners2.svg b/assets/images/partners2.svg new file mode 100644 index 00000000..12594e20 --- /dev/null +++ b/assets/images/partners2.svg @@ -0,0 +1,38 @@ + \ No newline at end of file diff --git a/assets/images/period_calender1.svg b/assets/images/period_calender1.svg new file mode 100644 index 00000000..098d3386 --- /dev/null +++ b/assets/images/period_calender1.svg @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/assets/images/period_planner2.svg b/assets/images/period_planner2.svg new file mode 100644 index 00000000..f6792382 --- /dev/null +++ b/assets/images/period_planner2.svg @@ -0,0 +1,38 @@ + \ No newline at end of file diff --git a/assets/images/period_planner4.svg b/assets/images/period_planner4.svg new file mode 100644 index 00000000..3c0dd795 --- /dev/null +++ b/assets/images/period_planner4.svg @@ -0,0 +1,36 @@ + \ No newline at end of file diff --git a/assets/images/providerimage.svg b/assets/images/providerimage.svg new file mode 100644 index 00000000..a787128a --- /dev/null +++ b/assets/images/providerimage.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/selfscreeningImage.svg b/assets/images/selfscreeningImage.svg new file mode 100644 index 00000000..6ed2cf33 --- /dev/null +++ b/assets/images/selfscreeningImage.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/selfscreeningimage2.svg b/assets/images/selfscreeningimage2.svg new file mode 100644 index 00000000..65cb5c15 --- /dev/null +++ b/assets/images/selfscreeningimage2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/shopping-cart-dawa.svg b/assets/images/shopping-cart-dawa.svg new file mode 100644 index 00000000..0ae42f53 --- /dev/null +++ b/assets/images/shopping-cart-dawa.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/symptoms.svg b/assets/images/symptoms.svg new file mode 100644 index 00000000..e587c86b --- /dev/null +++ b/assets/images/symptoms.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/src/app/navigation/app_router.dart b/lib/src/app/navigation/app_router.dart index a47eb5f4..9c179103 100644 --- a/lib/src/app/navigation/app_router.dart +++ b/lib/src/app/navigation/app_router.dart @@ -4,7 +4,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:nishauri/src/features/appointments/data/models/appointment.dart'; import 'package:nishauri/src/features/appointments/presentation/pages/AppointmentRescheduleScreen.dart'; -import 'package:nishauri/src/features/appointments/presentation/pages/Appointments.dart'; import 'package:nishauri/src/features/appointments/presentation/pages/AppointmentsScreen.dart'; import 'package:nishauri/src/features/art/presentation/FacilityDirectory.dart'; import 'package:nishauri/src/features/auth/data/models/auth_state.dart'; @@ -20,12 +19,27 @@ import 'package:nishauri/src/features/auth/presentation/pages/VerificationScreen import 'package:nishauri/src/features/auth/presentation/pages/VerifiedResetPassword.dart'; import 'package:nishauri/src/features/auth/presentation/pages/VerifyResetPasswordScreen.dart'; import 'package:nishauri/src/features/auth/presentation/pages/WelcomeScreen.dart'; -import 'package:nishauri/src/features/blood_sugar/presentation/pages/AddBloodSugarScreen.dart'; -import 'package:nishauri/src/features/blood_sugar/presentation/pages/BloodSugarScreen.dart'; -import 'package:nishauri/src/features/bmi/presentation/pages/BMICalculatorResultsScreen.dart'; -import 'package:nishauri/src/features/bmi/presentation/pages/BMICalculatorScreen.dart'; -import 'package:nishauri/src/features/bmi/presentation/pages/BMIHistoryScreen.dart'; -import 'package:nishauri/src/features/bp/presentation/pages/bpMonitorScreen.dart'; +import 'package:nishauri/src/features/clinic_card/presentation/widgets/allergy_records.dart'; +import 'package:nishauri/src/features/clinic_card/presentation/widgets/condition_records.dart'; +import 'package:nishauri/src/features/clinic_card/presentation/widgets/lab_result_records.dart'; +import 'package:nishauri/src/features/clinic_card/presentation/widgets/medication.dart'; +import 'package:nishauri/src/features/clinic_card/presentation/widgets/vital_records.dart'; +import 'package:nishauri/src/features/clinic_card/relationship/presentation/pages/dependant_profile.dart'; +import 'package:nishauri/src/features/clinic_card/relationship/widgets/allergy_rship_records.dart'; +import 'package:nishauri/src/features/clinic_card/relationship/widgets/condition_rship.dart'; +import 'package:nishauri/src/features/clinic_card/relationship/widgets/immunization_rship.dart'; +import 'package:nishauri/src/features/clinic_card/relationship/widgets/lab_rship.dart'; +import 'package:nishauri/src/features/clinic_card/relationship/widgets/medication_rship.dart'; +import 'package:nishauri/src/features/clinic_card/relationship/widgets/procedures_rship.dart'; +import 'package:nishauri/src/features/clinic_card/relationship/widgets/vital_rship_records.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/presentation/pages/BloodSugarScreen.dart'; +import 'package:nishauri/src/features/self_screening/bmi/presentation/pages/BMICalculatorResultsScreen.dart'; +import 'package:nishauri/src/features/self_screening/bmi/presentation/pages/BMICalculatorScreen.dart'; +import 'package:nishauri/src/features/self_screening/bmi/presentation/pages/BMIHistoryScreen.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/presentation/pages/bs_input_screen.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/presentation/pages/bs_line_list_Screen.dart'; +import 'package:nishauri/src/features/self_screening/bp/presentation/pages/BPLinelistScreen.dart'; +import 'package:nishauri/src/features/self_screening/bp/presentation/pages/bpMonitorScreen.dart'; import 'package:nishauri/src/features/chatbot/presentations/ChatScreen.dart'; import 'package:nishauri/src/features/clinic_card/presentation/pages/ClinicCardScreen.dart'; import 'package:nishauri/src/features/common/presentation/pages/FaqPage.dart'; @@ -55,15 +69,31 @@ import 'package:nishauri/src/features/hiv/presentation/pages/groups/ARTGroups.da import 'package:nishauri/src/features/dawa_drop/presentation/pages/request_order/DrugOrderWizardFormScreen.dart'; import 'package:nishauri/src/features/dawa_drop/presentation/pages/request_order/DrugOrders.dart'; import 'package:nishauri/src/features/lab/presentation/pages/LabResultsScreen.dart'; +import 'package:nishauri/src/features/nishauri_chat/chat/presentation/pages/ChatDetailScreen.dart'; +import 'package:nishauri/src/features/nishauri_chat/chat/presentation/pages/ChatUserList.dart'; +import 'package:nishauri/src/features/nishauri_chat/chat/presentation/pages/ConversationList.dart'; +import 'package:nishauri/src/features/period_planner/presentation/pages/editPeriodsScreen.dart'; +import 'package:nishauri/src/features/period_planner/presentation/pages/logPeriods.dart'; +import 'package:nishauri/src/features/period_planner/presentation/pages/periodPlannerScreen.dart'; +import 'package:nishauri/src/features/period_planner/presentation/pages/periods_history.dart'; import 'package:nishauri/src/features/programs/presentation/pages/programs.dart'; -import 'package:nishauri/src/features/self_screening/presentation/self_screening_menu.dart'; +import 'package:nishauri/src/features/provider/appointment_management/presentation/pages/reschedule_request_list.dart'; +import 'package:nishauri/src/features/provider/dawa_drop_management/presentation/pages/dawa_drop_manager_screen.dart'; +import 'package:nishauri/src/features/provider/presentation/pages/provider_main_Screen.dart'; +import 'package:nishauri/src/features/provider/provider_registry/presentaion/pages/location_selection_screen.dart'; +import 'package:nishauri/src/features/provider/provider_registry/presentaion/pages/provider_details.dart'; +import 'package:nishauri/src/features/self_screening/bp/presentation/pages/bp_input_screen.dart'; +import 'package:nishauri/src/features/self_screening/presentation/pages/blood_pressure_posts.dart'; +import 'package:nishauri/src/features/self_screening/presentation/pages/blood_sugar_posts.dart'; +import 'package:nishauri/src/features/self_screening/presentation/pages/bpInsightScreen.dart'; +import 'package:nishauri/src/features/self_screening/presentation/pages/bsInsightScreen.dart'; +import 'package:nishauri/src/features/self_screening/presentation/pages/insight_screen.dart'; +import 'package:nishauri/src/features/self_screening/presentation/pages/self_screening_menu.dart'; import 'package:nishauri/src/features/treatment_support/presentation/pages/TreatmentSupport.dart'; -import 'package:nishauri/src/features/user/data/providers/user_provider.dart'; import 'package:nishauri/src/features/user/presentation/pages/ProfileScreen.dart'; import 'package:nishauri/src/features/user/presentation/pages/ProfileWizardFormScreen.dart'; import 'package:nishauri/src/features/user_preference/presentation/pages/PinAuthScreen.dart'; import 'package:nishauri/src/features/user_preference/presentation/pages/PrivacySettingsScreen.dart'; -import 'package:nishauri/src/features/user_programs/data/models/program_verification_detail.dart'; import 'package:nishauri/src/features/user_programs/presentation/pages/ProgramRegistrationScreen.dart'; import 'package:nishauri/src/features/user_programs/presentation/pages/ProgramUpdateScreen.dart'; import 'package:nishauri/src/features/user_programs/presentation/pages/ProgramVerificationScreen.dart'; @@ -71,7 +101,7 @@ import 'package:nishauri/src/features/visits/presentations/pages/FacilityVisitDe import 'package:nishauri/src/features/visits/presentations/pages/FacilityVisitsScreen.dart'; import 'package:nishauri/src/utils/routes.dart'; -import '../../features/lab/presentation/pages/LabResults.dart'; +import '../../features/clinic_card/presentation/widgets/immunization.dart'; final routesProvider = Provider((ref) { final router = RouterNotifier(ref); @@ -89,7 +119,7 @@ class RouterNotifier extends ChangeNotifier { RouterNotifier(this._ref) { _ref.listen>( authStateProvider, - (_, __) => notifyListeners(), + (_, __) => notifyListeners(), ); } @@ -134,66 +164,66 @@ class RouterNotifier extends ChangeNotifier { } List get routes => [ - GoRoute( - name: RouteNames.SPLASH_SCREEN, - path: '/splash', - builder: (BuildContext context, GoRouterState state) { - return const SplashScreen(); - }, - ), - GoRoute( - name: RouteNames.WELCOME_SCREEN, - path: '/auth', - builder: (BuildContext context, GoRouterState state) { - return const WelcomeScreen(); - }, - routes: openRoutes, - ), - GoRoute( - name: RouteNames.LANDING_SCREEN, - path: '/', - builder: (context, state) => const MainScreen(), - routes: secureRoutes, - ), - // GoRoute( - // name: RouteNames.VERIFY_ACCOUNT, - // path: '/account-verify', - // builder: (BuildContext context, GoRouterState state) { - // final extras = getPhone() as String; - // print("this is extras $extras"); - // return VerificationScreen(username: extras); - // }, - // ), - GoRoute( - name: RouteNames.VERIFY_ACCOUNT, - path: '/account-verify', - builder: (BuildContext context, GoRouterState state) { - return FutureBuilder( - future: getPhone(), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - return Center(child: Text('Error: ${snapshot.error}')); - } else if (snapshot.hasData) { - final extras = snapshot.data!; - return VerificationScreen(username: extras); - } else { - return const Center(child: Text('No data available')); - } - }, - ); + GoRoute( + name: RouteNames.SPLASH_SCREEN, + path: '/splash', + builder: (BuildContext context, GoRouterState state) { + return const SplashScreen(); + }, + ), + GoRoute( + name: RouteNames.WELCOME_SCREEN, + path: '/auth', + builder: (BuildContext context, GoRouterState state) { + return const WelcomeScreen(); + }, + routes: openRoutes, + ), + GoRoute( + name: RouteNames.LANDING_SCREEN, + path: '/', + builder: (context, state) => const MainScreen(), + routes: secureRoutes, + ), + // GoRoute( + // name: RouteNames.VERIFY_ACCOUNT, + // path: '/account-verify', + // builder: (BuildContext context, GoRouterState state) { + // final extras = getPhone() as String; + // print("this is extras $extras"); + // return VerificationScreen(username: extras); + // }, + // ), + GoRoute( + name: RouteNames.VERIFY_ACCOUNT, + path: '/account-verify', + builder: (BuildContext context, GoRouterState state) { + return FutureBuilder( + future: getPhone(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (snapshot.hasData) { + final extras = snapshot.data!; + return VerificationScreen(username: extras); + } else { + return const Center(child: Text('No data available')); + } }, - ), + ); + }, + ), - GoRoute( - name: RouteNames.PROFILE_EDIT_FORM, - path: '/profile-edit', - builder: (BuildContext context, GoRouterState state) { - return const ProfileWizardFormScreen(); - }, - ), - ]; + GoRoute( + name: RouteNames.PROFILE_EDIT_FORM, + path: '/profile-edit', + builder: (BuildContext context, GoRouterState state) { + return const ProfileWizardFormScreen(); + }, + ), + ]; } final List secureRoutes = [ @@ -293,6 +323,14 @@ final List secureRoutes = [ return const Dashboard(); }, ), + GoRoute( + name: RouteNames.CHAT_HCW, + path: 'chat-hcw', + builder: (BuildContext context, GoRouterState state) { + return const ChatHCWScreen(); + }, + routes: chatRoutes, + ), GoRoute( name: RouteNames.APPOINTMENTS, path: 'appointments', @@ -335,7 +373,19 @@ final List secureRoutes = [ builder: (BuildContext context, GoRouterState state) { return const ClinicCardScreen(); }, + routes: clinicCardRoutes, ), + + + GoRoute( + name: RouteNames.MY_DEPENDANT_CLINIC_CARD, + path: 'dependent-clinic-card', + builder: (BuildContext context, GoRouterState state) { + return const DependantProfileScreen(); + }, + routes: dependentclinicCardRoutes, + ), + GoRoute( name: RouteNames.DAWA_DROP, path: 'dawa-drop', @@ -344,7 +394,7 @@ final List secureRoutes = [ }, ), GoRoute( - name: RouteNames.CHAT_HCW, + name: RouteNames.CHAT_BOT, path: 'chat-bot', builder: (BuildContext context, GoRouterState state) { return const ChatScreen(); @@ -364,16 +414,32 @@ final List secureRoutes = [ return const FacilityVisitsScreen(); }, routes: [ - GoRoute( - name: RouteNames.FACILITY_VISIT_DETAIL, - path: ':visitId', - builder: (BuildContext context, GoRouterState state) { - return FacilityVisitDetailScreen( - visitId: state.pathParameters["visitId"]!); - }, - ) + // GoRoute( + // name: RouteNames.FACILITY_VISIT_DETAIL, + // path: ':visitId', + // builder: (BuildContext context, GoRouterState state) { + // return FacilityVisitDetailScreen( + // visitId: state.pathParameters["visitId"]!); + // }, + // ) ]), + GoRoute( + name: RouteNames.PROVIDER_MAIN_SCREEN, + path: 'provider-main-screen', + builder: (BuildContext context, GoRouterState state) { + return const ProviderMainScreen(); + }, + routes: providerRoutes, + ), + GoRoute( + name: RouteNames.LOCATION_SELECTION, + path: 'location-selection', + builder: (BuildContext context, GoRouterState state) { + return LocationSelectionScreen(); + }, + ), ]; + final List openRoutes = [ GoRoute( name: RouteNames.LOGIN_SCREEN, @@ -414,42 +480,206 @@ final List openRoutes = [ final List selfScreeningRoutes = [ GoRoute( - name: RouteNames.BMI_CALCULATOR, - path: 'bmi-calculator', + name: RouteNames.BMI_CALCULATOR_RESULTS, + path: "bmi-calculator-results", builder: (BuildContext context, GoRouterState state) { - return const BMICalculatorScreen(); + // Ensure that state.extra is of the expected type + final extra = state.extra; + + if (extra is Map) { + double? bmi = extra['bmi'] as double?; + bool? isForSelf = extra['isForSelf'] as bool?; + return BMICalculatorResultsScreen( + otherBMI: bmi, + isForSelf: isForSelf, + ); + } else { + return BMICalculatorResultsScreen( + otherBMI: null, + isForSelf: true, + ); + } }, routes: [ GoRoute( - name: RouteNames.BMI_CALCULATOR_RESULTS, - path: "bmi-calculator-results", - builder: (BuildContext context, GoRouterState state) { - double extra = state.extra! as double; - return BMICalculatorResultsScreen(bmi: extra); - }), + name: RouteNames.BMI_CALCULATOR, + path: 'bmi-calculator', + builder: (BuildContext context, GoRouterState state) { + return const BMICalculatorScreen(); + }, + ), GoRoute( name: RouteNames.BMI_HISTORY, path: "bmi-history", builder: (BuildContext context, GoRouterState state) { return BMIHistoryScreen(); - }), + } + ), + ] + ), + GoRoute( + name: RouteNames.BLOOD_PRESSURE, + path: 'blood-pressure', + builder: (BuildContext context, GoRouterState state) { + return BPMonitorScreen(); + }, + routes: [ + GoRoute( + name: RouteNames.BLOOD_PRESSURE_INSIGHT, + path: 'blood-pressure-insight', + builder: (BuildContext context, GoRouterState state) { + return BpInsightScreen(); + }, + routes: [ + GoRoute( + name: RouteNames.BLOOD_PRESSURE_POSTS, + path: 'blood-pressure-posts', + builder: (BuildContext context, GoRouterState state) { + dynamic ann = state.extra; + return BloodPressurePostScreen(announcement: ann,); + }, + ) + ] + ), + GoRoute( + name: RouteNames.BLOOD_PRESSURE_RECORDS, + path: 'blood-pressure-records', + builder: (context, state) { + dynamic extras = state.extra; + return BloodPressureRecords(data: extras); + }, + ), + GoRoute( + name: RouteNames.BLOOD_PRESSURE_INPUT, + path: 'blood-pressure-input', + builder: (context, state) { + return BloodPressureInputs(); + }, + ), + ] + ), + GoRoute( + name: RouteNames.BLOOD_SUGAR, + path: 'blood-sugar', + builder: (BuildContext context, GoRouterState state) { + return BloodSugarScreen(); + }, + routes: [ + GoRoute( + name: RouteNames.BLOOD_SUGAR_INSIGHT, + path: 'blood-sugar-insight', + builder: (BuildContext context, GoRouterState state) { + return BsInsightScreen(); + }, + routes: [ + GoRoute( + name: RouteNames.BLOOD_SUGAR_POSTS, + path: 'blood-sugar-posts', + builder: (BuildContext context, GoRouterState state) { + dynamic ann = state.extra; + return BloodSugarPostScreen(announcement: ann,); + }, + ) + ] + ), + GoRoute( + name: RouteNames.BLOOD_SUGAR_RECORDS, + path: 'blood-sugar-records', + builder: (context, state) { + dynamic extras = state.extra; + return BloodSugarRecords(data: extras); + }, + ), + GoRoute( + name: RouteNames.BLOOD_SUGAR_INPUT, + path: 'blood-sugar-input', + builder: (context, state) { + return BloodSugarInputs(); + }, + ), + ] + ), + //Routes for the Period Planner + GoRoute( + name: RouteNames.PERIOD_PLANNER_SCREEN, + path: 'period-planner-screen', + builder: (BuildContext context, GoRouterState state) { + return const PeriodPlannerScreen(); + }, + routes: [ + GoRoute( + name: RouteNames.PERIOD_PLANNER_LOG_PERIODS, + path: 'period-planner-log-period-calendar', + builder: (BuildContext context, GoRouterState state) { + return LogPeriodScreen(); + }, + ), + GoRoute( + name: RouteNames.PERIOD_PLANNER_PERIOD_HISTORY, + path: 'period-planner-period-history', + builder: (BuildContext context, GoRouterState state) { + return const PeriodsHistory(); + }, + routes: [ + GoRoute( + name: RouteNames.PERIOD_PLANNER_EDIT_PERIODS, + path: 'period-planner-edit-periods', + builder: (BuildContext context, GoRouterState state) { + final extra = state.extra as Map; + final startDate = extra['startDate'] as DateTime; + final endDate = extra['endDate'] as DateTime; + final id = extra['id'] as int; + return EditPeriods( + initialStartDate: startDate, + initialEndDate: endDate, + cycleId: id, + ); + }, + ), + ], + ), + ], + ), + GoRoute( + name: RouteNames.INSIGHT, + path: 'insight', + builder: (BuildContext context, GoRouterState state) { + return const InsightScreen(); + }, + routes: [ + GoRoute( + name: RouteNames.BP_INSIGHT, + path: 'bp-insight', + builder: (BuildContext context, GoRouterState state) { + return const BpInsightScreen(); + }, + ), ]), +]; + +final List providerRoutes = [ + GoRoute( + name: RouteNames.REQUEST_APP_RESCHEDULE, + path: 'request-app-reschedule', + builder: (BuildContext context, GoRouterState state) { + return const RescheduleRequestListScreen(); + }, + ), GoRoute( - name: RouteNames.BLOOD_PRESSURE, - path: 'blood-pressure', + name: RouteNames.DAWA_DROP_MANAGER, + path: 'dawa-drop-manager', builder: (BuildContext context, GoRouterState state) { - return BPMonitorScreen(); + return const DawaDropManagemerScreen(); }, ), GoRoute( - name: MenuItemNames.BLOOD_SUGAR, - path: 'blood-sugar', + name: RouteNames.PROVIDER_DETAILS, + path: 'provider-details', builder: (BuildContext context, GoRouterState state) { - return BloodSugarScreen(); + return const ProviderDetails(); }, ), ]; - final List hivProgramRoutes = [ GoRoute( name: RouteNames.HIV_ART_SITES, @@ -586,6 +816,22 @@ final List dawaDropRoutes = [ ]), ]; +final List chatRoutes = [ + GoRoute( + name: RouteNames.CHAT_DETAIL, + path: 'chat-detail', + builder: (BuildContext context, GoRouterState state) { + return ChatDetailScreen(); + }, + ), + GoRoute( + name: RouteNames.CHAT_USER, + path: 'chat-user', + builder: (BuildContext context, GoRouterState state) { + return ChatUserListScreen(); + }, + ), +]; final List programMenu = [ GoRoute( name: RouteNames.PROGRAME_REGISTRATION_SCREEN, @@ -620,3 +866,123 @@ final List programMenu = [ routes: hivProgramRoutes, ), ]; + +final List clinicCardRoutes = [ + GoRoute( + name: RouteNames.HEALTH_RECORD, + path: 'health-record', + builder: (BuildContext context, GoRouterState state) { + return ConditionHealthRecord(); + }, + ), + + GoRoute( + name: RouteNames.IMMUNIZATION_RECORD, + path: 'immunization-record', + builder: (BuildContext context, GoRouterState state) { + return ImmunizationTest(); + }, + ), + + + //Medication + + GoRoute( + name: RouteNames.MEDICATION_RECORD, + path: 'Medication-record', + builder: (BuildContext context, GoRouterState state) { + return MedicationRecord(); + }, + ), + GoRoute( + name: RouteNames.DEPENDANT_PROFILE, + path: 'dependant-profile', + builder: (BuildContext context, GoRouterState state) { + return DependantProfileScreen(); + }, + ), + GoRoute( + name: RouteNames.VITAL_HEALTH_RECORD, + path: 'vital-health-record', + builder: (BuildContext context, GoRouterState state) { + return VitalHealthRecord(); + }, + ), + GoRoute( + name: RouteNames.ALLERGY_HEALTH_RECORD, + path: 'allergy-health-record', + builder: (BuildContext context, GoRouterState state) { + return AllergyHealthRecord(); + }, + ), + GoRoute( + name: RouteNames.LAB_RESULTS_HEALTH_RECORD, + path: 'blood-result-health-record', + builder: (BuildContext context, GoRouterState state) { + return LabResultHealthRecord(); + }, + ), +]; + +final List dependentclinicCardRoutes =[ + //immunization_rship + GoRoute( + name: RouteNames.IMMUNIZATION_RSHIP_RECORD, + path: 'immunization-rship-record', + builder: (BuildContext context, GoRouterState state) { + return ImmunizationRship(); + }, + ), + + //allergy_rship + GoRoute( + name: RouteNames.ALLERGY_HEALTH_RSHIP_RECORD, + path: 'allergy-health-rship-record', + builder: (BuildContext context, GoRouterState state) { + return AllergyRshipRecord(); + }, + ), + + //procedure_rship + // GoRoute( + // name: RouteNames.PROCEDURE_RSHIP_RECORD, + // path: 'procedure-rship-record', + // builder: (BuildContext context, GoRouterState state) { + // return ClinicRshipDetails(); + // }, + // ), + //lab + GoRoute( + name: RouteNames.LAB_RESULTS_HEALTH_RSHIP_RECORD, + path: 'lab-result-rship-record', + builder: (BuildContext context, GoRouterState state) { + return LabRshpRecord(); + }, + ), + + //Medication + GoRoute( + name: RouteNames.MEDICATION_RSHIP_RECORD, + path: 'medication-rship-record', + builder: (BuildContext context, GoRouterState state) { + return MedicationRship(); + }, + ), + + GoRoute( + name: RouteNames.HEALTH_RSHIP_RECORD, + path: 'health-rship-record', + builder: (BuildContext context, GoRouterState state) { + return ConditionRship(); + }, + ), + + GoRoute( + name: RouteNames.VITAL_HEALTH_RSHIP_RECORD, + path: 'vital-health-rship-record', + builder: (BuildContext context, GoRouterState state) { + return VitalHealthRshipRecord(); + }, + ), + +]; diff --git a/lib/src/app/navigation/drawer/customeDrawer.dart b/lib/src/app/navigation/drawer/customeDrawer.dart index ff494237..5c38cafb 100644 --- a/lib/src/app/navigation/drawer/customeDrawer.dart +++ b/lib/src/app/navigation/drawer/customeDrawer.dart @@ -71,6 +71,15 @@ class _CustomDrawerState extends ConsumerState { Navigator.pop(context); }, ), + ListTile( + leading: const Icon(Icons.chat_outlined), + title: const Text("Chat with HCW"), + onTap: () { + context.goNamed(RouteNames.CHAT_HCW); + // Close drawer + Navigator.pop(context); + }, + ), // ListTile( // leading: const Icon(Icons.notifications), diff --git a/lib/src/app/navigation/menu/menuItems.dart b/lib/src/app/navigation/menu/menuItems.dart index d649f290..19afb322 100644 --- a/lib/src/app/navigation/menu/menuItems.dart +++ b/lib/src/app/navigation/menu/menuItems.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:go_router/go_router.dart'; +import 'package:nishauri/src/features/period_planner/data/models/cycle.dart'; import 'package:nishauri/src/utils/constants.dart'; import 'package:nishauri/src/utils/routes.dart'; @@ -23,105 +24,135 @@ class MenuItem { }); } -MenuItem getProgramMenuItemByProgramCode( - BuildContext context, String programCode) { - if (programCode == ProgramCodeNameIds.HIV) { - return MenuItem( - icon: const Icon( - Icons.vaccines, - size: Constants.iconSize, - ), - shortcutIcon: const Icon( - Icons.vaccines, - ), +MenuItem getProgramMenuItemByProgramCode(BuildContext context, String programCode) { + final theme = Theme.of(context); + switch (programCode) { + case ProgramCodeNameIds.HIV: + return MenuItem( + icon: const Icon(Icons.vaccines, size: Constants.iconSize), + shortcutIcon: const Icon(Icons.vaccines), title: MenuItemNames.HIV_PROGRAM_MENU, onPressed: () => context.goNamed(RouteNames.HIV_PROGRAM), - color: Constants.appointmentsColor); - } else if (programCode == ProgramCodeNameIds.TB) { - return MenuItem( - icon: const Icon( - Icons.sick, - size: Constants.iconSize, - ), - shortcutIcon: const Icon( - Icons.sick, - ), - title: MenuItemNames.TB_PROGRAM_MENU, - onPressed: () => "", - ); - } else if (programCode == ProgramCodeNameIds.ASTHMA) { - return MenuItem( - icon: const Icon( - Icons.ac_unit, - size: Constants.iconSize, - ), - shortcutIcon: const Icon( - Icons.ac_unit, - ), - title: MenuItemNames.ASTHMA_PROGRAM_MENU, - onPressed: () => "", - ); - } else if (programCode == ProgramCodeNameIds.DIABETES) { - return MenuItem( - icon: const Icon( - Icons.monitor_weight_outlined, - size: Constants.iconSize, - ), - shortcutIcon: const Icon( - Icons.monitor_weight_outlined, - ), - title: MenuItemNames.DIABETES_PROGRAM_MENU, - onPressed: () => "", - ); - } else if (programCode == ProgramCodeNameIds.CANCER) { - return MenuItem( - icon: const Icon( - Icons.group_work, - size: Constants.iconSize, - ), - shortcutIcon: const Icon( - Icons.group_work, - ), - title: MenuItemNames.CANCER_PROGRAM_MENU, - onPressed: () => "", - ); - } else if (programCode == ProgramCodeNameIds.HYPERTENSION) { - return MenuItem( - icon: const Icon( - Icons.speed, - size: Constants.iconSize, + color: Constants.appointmentsColor, + ); + case ProgramCodeNameIds.TB: + return MenuItem( + icon: const Icon(Icons.sick, size: Constants.iconSize), + shortcutIcon: const Icon(Icons.sick), + title: MenuItemNames.TB_PROGRAM_MENU, + onPressed: () => "", + ); + case ProgramCodeNameIds.ASTHMA: + return MenuItem( + icon: const Icon(Icons.ac_unit, size: Constants.iconSize), + shortcutIcon: const Icon(Icons.ac_unit), + title: MenuItemNames.ASTHMA_PROGRAM_MENU, + onPressed: () => "", + ); + case ProgramCodeNameIds.DIABETES: + return MenuItem( + icon: const Icon(Icons.monitor_weight_outlined, size: Constants.iconSize), + shortcutIcon: const Icon(Icons.monitor_weight_outlined), + title: MenuItemNames.DIABETES_PROGRAM_MENU, + onPressed: () => "", + ); + case ProgramCodeNameIds.CANCER: + return MenuItem( + icon: const Icon(Icons.group_work, size: Constants.iconSize), + shortcutIcon: const Icon(Icons.group_work), + title: MenuItemNames.CANCER_PROGRAM_MENU, + onPressed: () => "", + ); + case ProgramCodeNameIds.HYPERTENSION: + return MenuItem( + icon: const Icon(Icons.speed, size: Constants.iconSize), + shortcutIcon: const Icon(Icons.speed), + title: MenuItemNames.HYPERTENSION_PROGRAM_MENU, + onPressed: () => "", + ); + default: + return MenuItem( + icon: const Icon(Icons.more_horiz, size: Constants.iconSize), + shortcutIcon: const Icon(Icons.more_horiz), + ); + } +} + +List getProviderModules(BuildContext context) { + return [ + MenuItem( + shortcutBackgroundColor: Constants.providerBgColor.withOpacity(0.5), + icon: SvgPicture.asset( + "assets/images/providerimage.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + width: 80, + height: 80, ), - shortcutIcon: const Icon( - Icons.speed, + shortcutIcon: SvgPicture.asset( + "assets/images/providerimage.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + width: Constants.shortcutIconSize, + height: Constants.shortcutIconSize, ), - title: MenuItemNames.HYPERTENSION_PROGRAM_MENU, - onPressed: () {}, - ); - } - return MenuItem( - icon: const Icon( - Icons.more_horiz, - size: Constants.iconSize, - ), - shortcutIcon: const Icon( - Icons.more_horiz, + title: MenuItemNames.PROVIDER_MAIN_SCREEN, + onPressed: () => context.goNamed(RouteNames.PROVIDER_MAIN_SCREEN), + color: Constants.labResultsColor, ), - ); + ]; } -List getGenericMenuItems(BuildContext context) { +List getPartnerModules(BuildContext context) { return [ // MenuItem( - // icon: Icons.calendar_month_rounded, - // title: MenuItemNames.MY_CALENDAR, - // onPressed: () => context.goNamed(RouteNames.EVENTS_CALENDAR), + // color: Constants.programsColor, + // icon: FaIcon( + // Icons.file_copy_outlined, + // size: Constants.iconSize, + // color: Theme.of(context).colorScheme.inversePrimary, + // ), + // shortcutIcon: const FaIcon(Icons.file_copy_outlined), + // title: MenuItemNames.PARTNER, + // onPressed: () => context.goNamed(RouteNames.PARTNER), // ), + ]; +} + +List getAdminModules(BuildContext context) { + return [ // MenuItem( - // icon: Icons.dashboard_customize_outlined, - // title: MenuItemNames.DASHBOARD, - // onPressed: () => context.goNamed(RouteNames.DASHBOARD), + // color: Constants.programsColor, + // icon: FaIcon( + // Icons.file_copy_outlined, + // size: Constants.iconSize, + // color: Theme.of(context).colorScheme.inversePrimary, + // ), + // shortcutIcon: const FaIcon(Icons.file_copy_outlined), + // title: MenuItemNames.ADMIN, + // onPressed: () => context.goNamed(RouteNames.ADMIN), // ), + ]; +} +List getFacilityAdminModules(BuildContext context) { + return [ + // MenuItem( + // color: Constants.programsColor, + // icon: FaIcon( + // Icons.file_copy_outlined, + // size: Constants.iconSize, + // color: Theme.of(context).colorScheme.inversePrimary, + // ), + // shortcutIcon: const FaIcon(Icons.file_copy_outlined), + // title: MenuItemNames.FACILITY_ADMIN, + // onPressed: () => context.goNamed(RouteNames.FACILITY_ADMIN), + // ), + ]; +} + +List getPatientModules(BuildContext context) { + return [ MenuItem( color: Constants.programsColor, icon: FaIcon( @@ -129,113 +160,130 @@ List getGenericMenuItems(BuildContext context) { size: Constants.iconSize, color: Theme.of(context).colorScheme.inversePrimary, ), - shortcutIcon: const FaIcon( - Icons.file_copy_outlined, - ), + shortcutIcon: const FaIcon(Icons.file_copy_outlined), title: MenuItemNames.PROGRAM_MENU, onPressed: () => context.goNamed(RouteNames.PROGRAM_MENU), ), - - MenuItem( - icon: SvgPicture.asset( - "assets/images/calendar.svg", - semanticsLabel: "Doctors", - fit: BoxFit.contain, - height: 80, - width: 80, - ), - shortcutIcon: SvgPicture.asset( - "assets/images/calendar.svg", - semanticsLabel: "Doctors", - fit: BoxFit.contain, - width: Constants.shortcutIconSize, - height: Constants.shortcutIconSize, - ), - title: MenuItemNames.APPOINTMENTS, - onPressed: () => context.goNamed(RouteNames.APPOINTMENTS), - color: Constants.appointmentsColor), MenuItem( - shortcutBackgroundColor: Constants.labResultsShortcutBgColor, - icon: SvgPicture.asset( - "assets/images/syringe.svg", - semanticsLabel: "Doctors", - fit: BoxFit.contain, - height: 80, - width: 80, - ), - shortcutIcon: SvgPicture.asset( - "assets/images/syringe.svg", - semanticsLabel: "Doctors", - fit: BoxFit.contain, - width: Constants.shortcutIconSize, - height: Constants.shortcutIconSize, - ), - title: MenuItemNames.LAB_RESULTS, - onPressed: () => context.goNamed(RouteNames.LAB_RESULTS), - color: Constants.labResultsColor), + icon: SvgPicture.asset( + "assets/images/calendar.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + height: 80, + width: 80, + ), + shortcutIcon: SvgPicture.asset( + "assets/images/calendar.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + width: Constants.shortcutIconSize, + height: Constants.shortcutIconSize, + ), + title: MenuItemNames.APPOINTMENTS, + onPressed: () => context.goNamed(RouteNames.APPOINTMENTS), + color: Constants.appointmentsColor, + ), MenuItem( - icon: FaIcon( - FontAwesomeIcons.addressCard, - size: 50.0, - color: Colors.blue[400], - ), - shortcutIcon: FaIcon( - FontAwesomeIcons.addressCard, - color: Colors.blue[400], - ), - title: MenuItemNames.MY_CLINIC_CARD, - onPressed: () => context.goNamed(RouteNames.MY_CLINIC_CARD), - color: Colors.blue[900]), + shortcutBackgroundColor: Constants.labResultsShortcutBgColor, + icon: SvgPicture.asset( + "assets/images/syringe.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + height: 80, + width: 80, + ), + shortcutIcon: SvgPicture.asset( + "assets/images/syringe.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + width: Constants.shortcutIconSize, + height: Constants.shortcutIconSize, + ), + title: MenuItemNames.LAB_RESULTS, + onPressed: () => context.goNamed(RouteNames.LAB_RESULTS), + color: Constants.labResultsColor, + ), MenuItem( - icon: SvgPicture.asset( - "assets/images/house.svg", - semanticsLabel: "Doctors", - fit: BoxFit.contain, - height: 80, - width: 80, - ), - shortcutIcon: SvgPicture.asset( - "assets/images/house.svg", - semanticsLabel: "Doctors", - fit: BoxFit.contain, - width: Constants.shortcutIconSize, - height: Constants.shortcutIconSize, - ), - title: MenuItemNames.FACILITY_DIRECTORY, - onPressed: () => context.goNamed(RouteNames.Facility_Directory), - color: Constants.facilityDirectoryColor), - // MenuItem( - // icon: Icons.group, - // title: MenuItemNames.TREATMENT_SUPPORT, - // onPressed: () => context.goNamed(RouteNames.TREATMENT_SUPPORT), - // ), + shortcutBackgroundColor: Constants.clinicCardBgColor.withOpacity(0.5), + icon: SvgPicture.asset( + "assets/images/clinicCardCard.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + height: 80, + width: 80, + ), + shortcutIcon: SvgPicture.asset( + "assets/images/clinicCardCard.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + width: Constants.shortcutIconSize, + height: Constants.shortcutIconSize, + ), + title: MenuItemNames.MY_CLINIC_CARD, + onPressed: () => context.goNamed(RouteNames.MY_CLINIC_CARD), + color: Constants.clinicCardBgColor, + ), + // MenuItem( - // icon: Icons.event_note, - // title: MenuItemNames.MENSTRUAL_CIRCLE, - // onPressed: () => "", + // icon: SvgPicture.asset( + // "assets/images/Hospital building-bro.svg", + // semanticsLabel: "Doctors", + // fit: BoxFit.contain, + // height: 80, + // width: 80, + // ), + // shortcutIcon: SvgPicture.asset( + // "assets/images/Hospital building-bro.svg", + // semanticsLabel: "Doctors", + // fit: BoxFit.contain, + // width: Constants.shortcutIconSize, + // height: Constants.shortcutIconSize, + // ), + // title: "FACILITY VISITS", + // onPressed: () => context.goNamed(RouteNames.FACILITY_VISITS), + // color: Colors.blueGrey, // ), - MenuItem( - shortcutBackgroundColor: Constants.bmiCalculatorShortcutBgColor, - icon: SvgPicture.asset( - "assets/images/healthcare-medical.svg", - semanticsLabel: "Doctors", - fit: BoxFit.contain, - height: 80, - width: 80, - ), - shortcutIcon: SvgPicture.asset( - "assets/images/healthcare-medical.svg", - semanticsLabel: "Doctors", - fit: BoxFit.contain, - width: Constants.shortcutIconSize, - height: Constants.shortcutIconSize, - ), - title: MenuItemNames.SELF_SCREENING, - onPressed: () => context.goNamed(RouteNames.SELF_SCREENING), - color: Constants.bmiCalculatorColor), MenuItem( - // icon: FaIcon(FontAwesomeIcons.capsules, size: Constants.iconSize, color: Colors.teal[200],), + icon: SvgPicture.asset( + "assets/images/house.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + height: 80, + width: 80, + ), + shortcutIcon: SvgPicture.asset( + "assets/images/house.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + width: Constants.shortcutIconSize, + height: Constants.shortcutIconSize, + ), + title: MenuItemNames.FACILITY_DIRECTORY, + onPressed: () => context.goNamed(RouteNames.Facility_Directory), + color: Constants.facilityDirectoryColor, + ), + MenuItem( + shortcutBackgroundColor: Constants.selfScreeningBgColor, + icon: SvgPicture.asset( + "assets/images/selfscreeningimage2.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + height: 80, + width: 80, + ), + shortcutIcon: SvgPicture.asset( + "assets/images/selfscreeningimage2.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + width: Constants.shortcutIconSize, + height: Constants.shortcutIconSize, + ), + title: MenuItemNames.SELF_SCREENING, + onPressed: () => context.goNamed(RouteNames.SELF_SCREENING), + color: Constants.selfScreeningBgColor, + ), + MenuItem( shortcutBackgroundColor: Constants.dawaDropShortcutBgColor, icon: SvgPicture.asset( "assets/images/pills.svg", @@ -255,48 +303,54 @@ List getGenericMenuItems(BuildContext context) { onPressed: () => context.goNamed(RouteNames.DAWA_DROP), color: Constants.dawaDropColor.withOpacity(0.5), ), + // // MenuItem( - // // icon: FaIcon(FontAwesomeIcons.capsules, size: Constants.iconSize, color: Colors.teal[200],), - // shortcutBackgroundColor: Constants.dawaDropShortcutBgColor, // icon: SvgPicture.asset( - // "assets/images/sugar.svg", - // semanticsLabel: "Blood sugar", + // "assets/images/calendar.svg", + // semanticsLabel: "Doctors", // fit: BoxFit.contain, - // width: 80, // height: 80, + // width: 80, // ), // shortcutIcon: SvgPicture.asset( - // "assets/images/sugar.svg", - // semanticsLabel: "Blood Sugar", + // "assets/images/calendar.svg", + // semanticsLabel: "Doctors", // fit: BoxFit.contain, // width: Constants.shortcutIconSize, // height: Constants.shortcutIconSize, // ), - // title: MenuItemNames.BLOOD_SUGAR, - // onPressed: () => context.goNamed(MenuItemNames.BLOOD_SUGAR), - // color: Constants.bloodSugarColor.withOpacity(0.5), - // ), - // MenuItem( - // icon: Icons.move_down, - // title: MenuItemNames.FACILITY_VISITS, - // onPressed: () => context.goNamed(RouteNames.FACILITY_VISITS), - // ), - // MenuItem( - // icon: Icons.send, - // title: MenuItemNames.CHAT_HCW, - // onPressed: () => context.goNamed(RouteNames.CHAT_HCW), + // title: "Location selection", + // onPressed: () => context.goNamed(RouteNames.LOCATION_SELECTION), + // color: Constants.appointmentsColor, // ), ]; } -List getMenuItemByNames(BuildContext context, List names) { +List getGenericMenuItems(BuildContext context, List roles) { + List menuItems = List.from(getPatientModules(context)); + + if (roles.contains("Provider")) { + menuItems.addAll(getProviderModules(context)); + } + + if (roles.contains("Admin")) { + menuItems.addAll(getAdminModules(context)); + // Remove patient and provider modules if admin + menuItems.removeWhere((item) => + item.title == MenuItemNames.PROGRAM_MENU || + item.title == MenuItemNames.PROVIDER_MAIN_SCREEN + ); + } + + return menuItems; +} + +List getMenuItemByNames(BuildContext context, List names, List roles) { const programNames = ProgramCodeNameIds.SUPPOTED_PROGRAM_CODES; return [ - ...getGenericMenuItems(context), + ...getGenericMenuItems(context, roles), ...programNames.map((e) => getProgramMenuItemByProgramCode(context, e)), - ] - .where( - (menuItem) => names.any((name) => name == menuItem.title), - ) - .toList(); + ].where( + (menuItem) => names.contains(menuItem.title), + ).toList(); } diff --git a/lib/src/features/appointments/data/models/appointment.dart b/lib/src/features/appointments/data/models/appointment.dart index b3045bd3..d75863ec 100644 --- a/lib/src/features/appointments/data/models/appointment.dart +++ b/lib/src/features/appointments/data/models/appointment.dart @@ -20,6 +20,8 @@ class Appointment with _$Appointment { String? appt_status, int? appointment_status, String? date_attended, + String? reschedule_date, + String? reschedule_reason, }) = _Appointment; factory Appointment.fromJson(Map json)=> _$AppointmentFromJson(json); diff --git a/lib/src/features/appointments/data/models/appointment.freezed.dart b/lib/src/features/appointments/data/models/appointment.freezed.dart index 2547c087..e910f7e1 100644 --- a/lib/src/features/appointments/data/models/appointment.freezed.dart +++ b/lib/src/features/appointments/data/models/appointment.freezed.dart @@ -34,6 +34,8 @@ mixin _$Appointment { String? get appt_status => throw _privateConstructorUsedError; int? get appointment_status => throw _privateConstructorUsedError; String? get date_attended => throw _privateConstructorUsedError; + String? get reschedule_date => throw _privateConstructorUsedError; + String? get reschedule_reason => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -61,7 +63,9 @@ abstract class $AppointmentCopyWith<$Res> { String? nextAppointmentDate, String? appt_status, int? appointment_status, - String? date_attended}); + String? date_attended, + String? reschedule_date, + String? reschedule_reason}); } /// @nodoc @@ -91,6 +95,8 @@ class _$AppointmentCopyWithImpl<$Res, $Val extends Appointment> Object? appt_status = freezed, Object? appointment_status = freezed, Object? date_attended = freezed, + Object? reschedule_date = freezed, + Object? reschedule_reason = freezed, }) { return _then(_value.copyWith( id: freezed == id @@ -149,6 +155,14 @@ class _$AppointmentCopyWithImpl<$Res, $Val extends Appointment> ? _value.date_attended : date_attended // ignore: cast_nullable_to_non_nullable as String?, + reschedule_date: freezed == reschedule_date + ? _value.reschedule_date + : reschedule_date // ignore: cast_nullable_to_non_nullable + as String?, + reschedule_reason: freezed == reschedule_reason + ? _value.reschedule_reason + : reschedule_reason // ignore: cast_nullable_to_non_nullable + as String?, ) as $Val); } } @@ -175,7 +189,9 @@ abstract class _$$AppointmentImplCopyWith<$Res> String? nextAppointmentDate, String? appt_status, int? appointment_status, - String? date_attended}); + String? date_attended, + String? reschedule_date, + String? reschedule_reason}); } /// @nodoc @@ -203,6 +219,8 @@ class __$$AppointmentImplCopyWithImpl<$Res> Object? appt_status = freezed, Object? appointment_status = freezed, Object? date_attended = freezed, + Object? reschedule_date = freezed, + Object? reschedule_reason = freezed, }) { return _then(_$AppointmentImpl( id: freezed == id @@ -261,6 +279,14 @@ class __$$AppointmentImplCopyWithImpl<$Res> ? _value.date_attended : date_attended // ignore: cast_nullable_to_non_nullable as String?, + reschedule_date: freezed == reschedule_date + ? _value.reschedule_date + : reschedule_date // ignore: cast_nullable_to_non_nullable + as String?, + reschedule_reason: freezed == reschedule_reason + ? _value.reschedule_reason + : reschedule_reason // ignore: cast_nullable_to_non_nullable + as String?, )); } } @@ -282,7 +308,9 @@ class _$AppointmentImpl implements _Appointment { this.nextAppointmentDate, this.appt_status, this.appointment_status, - this.date_attended}); + this.date_attended, + this.reschedule_date, + this.reschedule_reason}); factory _$AppointmentImpl.fromJson(Map json) => _$$AppointmentImplFromJson(json); @@ -315,10 +343,14 @@ class _$AppointmentImpl implements _Appointment { final int? appointment_status; @override final String? date_attended; + @override + final String? reschedule_date; + @override + final String? reschedule_reason; @override String toString() { - return 'Appointment(id: $id, ccc_no: $ccc_no, facility_name: $facility_name, program_name: $program_name, program_code: $program_code, program_status: $program_status, reschedule_status: $reschedule_status, appointment_type: $appointment_type, appointment_date: $appointment_date, appointment: $appointment, nextAppointmentDate: $nextAppointmentDate, appt_status: $appt_status, appointment_status: $appointment_status, date_attended: $date_attended)'; + return 'Appointment(id: $id, ccc_no: $ccc_no, facility_name: $facility_name, program_name: $program_name, program_code: $program_code, program_status: $program_status, reschedule_status: $reschedule_status, appointment_type: $appointment_type, appointment_date: $appointment_date, appointment: $appointment, nextAppointmentDate: $nextAppointmentDate, appt_status: $appt_status, appointment_status: $appointment_status, date_attended: $date_attended, reschedule_date: $reschedule_date, reschedule_reason: $reschedule_reason)'; } @override @@ -351,7 +383,11 @@ class _$AppointmentImpl implements _Appointment { (identical(other.appointment_status, appointment_status) || other.appointment_status == appointment_status) && (identical(other.date_attended, date_attended) || - other.date_attended == date_attended)); + other.date_attended == date_attended) && + (identical(other.reschedule_date, reschedule_date) || + other.reschedule_date == reschedule_date) && + (identical(other.reschedule_reason, reschedule_reason) || + other.reschedule_reason == reschedule_reason)); } @JsonKey(ignore: true) @@ -371,7 +407,9 @@ class _$AppointmentImpl implements _Appointment { nextAppointmentDate, appt_status, appointment_status, - date_attended); + date_attended, + reschedule_date, + reschedule_reason); @JsonKey(ignore: true) @override @@ -402,7 +440,9 @@ abstract class _Appointment implements Appointment { final String? nextAppointmentDate, final String? appt_status, final int? appointment_status, - final String? date_attended}) = _$AppointmentImpl; + final String? date_attended, + final String? reschedule_date, + final String? reschedule_reason}) = _$AppointmentImpl; factory _Appointment.fromJson(Map json) = _$AppointmentImpl.fromJson; @@ -436,6 +476,10 @@ abstract class _Appointment implements Appointment { @override String? get date_attended; @override + String? get reschedule_date; + @override + String? get reschedule_reason; + @override @JsonKey(ignore: true) _$$AppointmentImplCopyWith<_$AppointmentImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/src/features/appointments/data/models/appointment.g.dart b/lib/src/features/appointments/data/models/appointment.g.dart index e6ef6cce..d3fd6e71 100644 --- a/lib/src/features/appointments/data/models/appointment.g.dart +++ b/lib/src/features/appointments/data/models/appointment.g.dart @@ -22,6 +22,8 @@ _$AppointmentImpl _$$AppointmentImplFromJson(Map json) => appt_status: json['appt_status'] as String?, appointment_status: (json['appointment_status'] as num?)?.toInt(), date_attended: json['date_attended'] as String?, + reschedule_date: json['reschedule_date'] as String?, + reschedule_reason: json['reschedule_reason'] as String?, ); Map _$$AppointmentImplToJson(_$AppointmentImpl instance) => @@ -40,4 +42,6 @@ Map _$$AppointmentImplToJson(_$AppointmentImpl instance) => 'appt_status': instance.appt_status, 'appointment_status': instance.appointment_status, 'date_attended': instance.date_attended, + 'reschedule_date': instance.reschedule_date, + 'reschedule_reason': instance.reschedule_reason, }; diff --git a/lib/src/features/appointments/presentation/pages/Appointments.dart b/lib/src/features/appointments/presentation/pages/Appointments.dart index 91cf4cce..8220d1b9 100644 --- a/lib/src/features/appointments/presentation/pages/Appointments.dart +++ b/lib/src/features/appointments/presentation/pages/Appointments.dart @@ -24,7 +24,7 @@ class _AppointmentsState extends State { void initState() { super.initState(); _upcomingAppointmentsFuture = _loadAppointments('assets/data/appointments.json'); - _previousAppointmentsFuture = _loadAppointments('assets/data/previous.json'); + _previousAppointmentsFuture = _loadAppointments('assets/data/reschedule.json'); } Future> _loadAppointments(String jsonPath) async { diff --git a/lib/src/features/appointments/presentation/pages/AppointmentsScreen.dart b/lib/src/features/appointments/presentation/pages/AppointmentsScreen.dart index 52e6030b..4195c355 100644 --- a/lib/src/features/appointments/presentation/pages/AppointmentsScreen.dart +++ b/lib/src/features/appointments/presentation/pages/AppointmentsScreen.dart @@ -5,7 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:nishauri/src/features/appointments/presentation/widgets/CurrentAppoints.dart'; import 'package:nishauri/src/features/appointments/presentation/widgets/PreviousAppointments.dart'; import 'package:nishauri/src/shared/display/CustomTabBar.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/utils/constants.dart'; class AppointmentsScreen extends HookConsumerWidget { diff --git a/lib/src/features/art/presentation/FacilityDirectory.dart b/lib/src/features/art/presentation/FacilityDirectory.dart index 319f6df6..2626086b 100644 --- a/lib/src/features/art/presentation/FacilityDirectory.dart +++ b/lib/src/features/art/presentation/FacilityDirectory.dart @@ -5,7 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:nishauri/src/features/art/model/Facility.dart'; import 'package:nishauri/src/features/art/services/FacilityDirectorySerivice.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/shared/display/background_image_widget.dart'; import 'package:nishauri/src/shared/input/Button.dart'; import 'package:nishauri/src/utils/constants.dart'; @@ -44,7 +44,7 @@ class FacilityDirectoryScreen extends HookWidget { return Scaffold( body: Column( - children: [ + children: [ CustomAppBar( title: "Facility Directory 🏥", // icon: Icons.local_hospital, @@ -89,199 +89,199 @@ class FacilityDirectoryScreen extends HookWidget { padding: const EdgeInsets.all(Constants.SPACING), child: facilities.value.isEmpty && !loading.value ? SizedBox( - height: MediaQuery.of(context).size.height * 0.67, - child: Center( - child: BackgroundImageWidget( - svgImage: - 'assets/images/facility-dir-empty-state.svg', - notFoundText: textController.text.length >= 3 - ? "Facility not found" - : "Search Facility", - )), - ) + height: MediaQuery.of(context).size.height * 0.67, + child: Center( + child: BackgroundImageWidget( + svgImage: + 'assets/images/facility-dir-empty-state.svg', + notFoundText: textController.text.length >= 3 + ? "Facility not found" + : "Search Facility", + )), + ) : loading.value - ? const Center(child: CircularProgressIndicator()) - : Column( - children: facilities.value - .map( - (e) => Card( - child: Padding( - padding: const EdgeInsets.all( - Constants.SPACING), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - e.name, - style: theme - .textTheme.titleMedium - ?.copyWith( - fontWeight: - FontWeight.bold, - overflow: TextOverflow - .ellipsis), - ), - const SizedBox( - height: Constants.SPACING), - Row( - children: [ - Icon( - Icons.pin_drop, - color: theme - .colorScheme.primary, - ), - const SizedBox( - width: Constants.SPACING, - ), - Expanded( - child: Text(e.county, - style: const TextStyle( - overflow: - TextOverflow - .ellipsis))) - ], - ), - Row( - children: [ - Icon( - Icons.phone_forwarded, - color: theme - .colorScheme.primary, - ), - const SizedBox( - width: Constants.SPACING, - ), - Expanded( - child: Text(e.telephone, - style: const TextStyle( - overflow: - TextOverflow - .ellipsis))) - ], - ), - Row( - children: [ - Icon( - Icons - .location_city_outlined, - color: theme - .colorScheme.primary, - ), - const SizedBox( - width: Constants.SPACING, - ), - Expanded( - child: Text( - "MFL Code: ${e.code}", - style: const TextStyle( - overflow: - TextOverflow - .ellipsis))), - ], - ), - Row( - children: [ - Icon( - Icons.local_hospital, - color: theme - .colorScheme.primary, - ), - const SizedBox( - width: Constants.SPACING, - ), - Expanded( - child: Text( - e.facilityType, - style: const TextStyle( - overflow: - TextOverflow - .ellipsis), + ? const Center(child: CircularProgressIndicator()) + : Column( + children: facilities.value + .map( + (e) => Card( + child: Padding( + padding: const EdgeInsets.all( + Constants.SPACING), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + e.name, + style: theme + .textTheme.titleMedium + ?.copyWith( + fontWeight: + FontWeight.bold, + overflow: TextOverflow + .ellipsis), + ), + const SizedBox( + height: Constants.SPACING), + Row( + children: [ + Icon( + Icons.pin_drop, + color: theme + .colorScheme.primary, + ), + const SizedBox( + width: Constants.SPACING, + ), + Expanded( + child: Text(e.county, + style: const TextStyle( + overflow: + TextOverflow + .ellipsis))) + ], + ), + Row( + children: [ + Icon( + Icons.phone_forwarded, + color: theme + .colorScheme.primary, + ), + const SizedBox( + width: Constants.SPACING, + ), + Expanded( + child: Text(e.telephone, + style: const TextStyle( + overflow: + TextOverflow + .ellipsis))) + ], + ), + Row( + children: [ + Icon( + Icons + .location_city_outlined, + color: theme + .colorScheme.primary, + ), + const SizedBox( + width: Constants.SPACING, + ), + Expanded( + child: Text( + "MFL Code: ${e.code}", + style: const TextStyle( + overflow: + TextOverflow + .ellipsis))), + ], + ), + Row( + children: [ + Icon( + Icons.local_hospital, + color: theme + .colorScheme.primary, + ), + const SizedBox( + width: Constants.SPACING, + ), + Expanded( + child: Text( + e.facilityType, + style: const TextStyle( + overflow: + TextOverflow + .ellipsis), + ), + ) + ], + ), + ], + ), + ), + Container( + decoration: BoxDecoration( + color: theme.colorScheme.primary + .withOpacity(0.4), + shape: BoxShape.circle), + child: IconButton( + onPressed: () async { + if (e.telephone + .replaceAll(" ", "") + .isEmpty) { + ScaffoldMessenger.of( + context) + .showSnackBar(SnackBar( + content: Text( + "${e.name} have no number to call!."))); + return; + } + final numberToCall = + await showDialog( + context: context, + builder: (context) => + AlertDialog( + title: const Text( + "Call facility"), + content: + SingleChildScrollView( + child: Wrap( + children: e.telephone + .replaceAll( + "CCC:", "") + .replaceAll( + " ", "") + .split(",") + .map( + (el) => + ListTile( + title: + Text(el), + leading: + const Icon( + Icons + .phone_forwarded_outlined), + onTap: () => + context.pop( + el), ), - ) - ], - ), - ], + ) + .toList(), + ), ), + actions: [ + Button( + title: "Cancel", + onPress: () => + context.pop(), + ) + ], ), - Container( - decoration: BoxDecoration( - color: theme.colorScheme.primary - .withOpacity(0.4), - shape: BoxShape.circle), - child: IconButton( - onPressed: () async { - if (e.telephone - .replaceAll(" ", "") - .isEmpty) { - ScaffoldMessenger.of( - context) - .showSnackBar(SnackBar( - content: Text( - "${e.name} have no number to call!."))); - return; - } - final numberToCall = - await showDialog( - context: context, - builder: (context) => - AlertDialog( - title: const Text( - "Call facility"), - content: - SingleChildScrollView( - child: Wrap( - children: e.telephone - .replaceAll( - "CCC:", "") - .replaceAll( - " ", "") - .split(",") - .map( - (el) => - ListTile( - title: - Text(el), - leading: - const Icon( - Icons - .phone_forwarded_outlined), - onTap: () => - context.pop( - el), - ), - ) - .toList(), - ), - ), - actions: [ - Button( - title: "Cancel", - onPress: () => - context.pop(), - ) - ], - ), - ); - if (numberToCall != null) { - makePhoneCall(numberToCall); - } - }, - icon: const Icon( - Icons.phone_forwarded)), - ) - ], - ), - ), - ), - ) - .toList(), - ), + ); + if (numberToCall != null) { + makePhoneCall(numberToCall); + } + }, + icon: const Icon( + Icons.phone_forwarded)), + ) + ], + ), + ), + ), + ) + .toList(), + ), ), ), ), diff --git a/lib/src/features/auth/data/respositories/auth_repository.dart b/lib/src/features/auth/data/respositories/auth_repository.dart index 5fb70489..3bb27fc5 100644 --- a/lib/src/features/auth/data/respositories/auth_repository.dart +++ b/lib/src/features/auth/data/respositories/auth_repository.dart @@ -90,9 +90,14 @@ class AuthRepository { } Future saveAge(String age) async { + print("saving age: $age"); await LocalStorage.save("age", age); } + Future deleteAge() async { + await LocalStorage.delete("age"); + } + Future getAge() async { return await LocalStorage.get("age"); } diff --git a/lib/src/features/auth/presentation/controllers/auth_controller.dart b/lib/src/features/auth/presentation/controllers/auth_controller.dart index 19f1ac1d..14c3d9fc 100644 --- a/lib/src/features/auth/presentation/controllers/auth_controller.dart +++ b/lib/src/features/auth/presentation/controllers/auth_controller.dart @@ -173,6 +173,7 @@ class AuthController extends StateNotifier> { _repository.deleteToken(); _repository.deleteUserId(); _repository.deletePhoneNumber(); + _repository.deleteAge(); state.when( data: (value) => state = AsyncValue.data( value.copyWith( diff --git a/lib/src/features/auth/presentation/pages/onboarding_screen.dart b/lib/src/features/auth/presentation/pages/onboarding_screen.dart index 9c444a1c..fb3dbefc 100644 --- a/lib/src/features/auth/presentation/pages/onboarding_screen.dart +++ b/lib/src/features/auth/presentation/pages/onboarding_screen.dart @@ -1,9 +1,8 @@ -import 'package:dots_indicator/dots_indicator.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:go_router/go_router.dart'; -import 'package:nishauri/src/features/bmi/onboardng_step.dart'; +import 'package:nishauri/src/features/self_screening/bmi/onboardng_step.dart'; import 'package:nishauri/src/features/user_preference/data/providers/settings_provider.dart'; import 'package:nishauri/src/utils/data.dart'; @@ -134,7 +133,7 @@ class _OnboardingScreenState extends State final settings = ref.read(settingsNotifierProvider.notifier); settings.updateSettings( - firstNuruAccess: true, + // firstNuruAccess: true, firstTimeInstallation: false); context.goNamed(RouteNames.LOGIN_SCREEN); }, diff --git a/lib/src/features/blood_sugar/data/providers/blood_sugar_provider.dart b/lib/src/features/blood_sugar/data/providers/blood_sugar_provider.dart deleted file mode 100644 index 3b287fd8..00000000 --- a/lib/src/features/blood_sugar/data/providers/blood_sugar_provider.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:nishauri/src/features/blood_sugar/data/models/blood_sugar.dart'; -import 'package:nishauri/src/features/blood_sugar/data/repository/blood_sugar_repository.dart'; -import 'package:nishauri/src/features/blood_sugar/data/services/blood_sugar_service.dart'; - -final bloodSugarProvider = Provider((ref) { - return BloodSugarRepository(BloodSugarService()); -}); - -final bloodSugarEntriesProvider = FutureProvider>((ref) async { - final repository = ref.watch(bloodSugarProvider); - return await repository.getBloodSugars(); -}); diff --git a/lib/src/features/blood_sugar/presentation/pages/BloodSugarScreen.dart b/lib/src/features/blood_sugar/presentation/pages/BloodSugarScreen.dart deleted file mode 100644 index 8c3c6f2b..00000000 --- a/lib/src/features/blood_sugar/presentation/pages/BloodSugarScreen.dart +++ /dev/null @@ -1,183 +0,0 @@ -import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:intl/intl.dart'; -import 'package:nishauri/src/features/blood_sugar/data/providers/blood_sugar_provider.dart'; -import 'package:nishauri/src/features/blood_sugar/presentation/pages/AddBloodSugarScreen.dart'; -import 'package:nishauri/src/features/blood_sugar/presentation/widgets/blood_sugar_entry_card.dart'; -import 'package:nishauri/src/shared/charts/CustomLineChart.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; -import 'package:nishauri/src/shared/display/background_image_widget.dart'; -import 'package:nishauri/src/utils/constants.dart'; - -class BloodSugarScreen extends ConsumerWidget { - const BloodSugarScreen({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final bloodSugarListProvider = ref.watch(bloodSugarEntriesProvider); - final theme = Theme.of(context); - - return bloodSugarListProvider.when( - data: (data) { - List dataPoints = data - .asMap() - .entries - .map((entry) => FlSpot( - entry.key.toDouble(), - entry.value.level, - )) - .toList(); - - final dateTimeList = data.asMap().entries.map((e) { - return e.value.created_at.toString(); - }).toList(); - - // List dateTimeList = data - // .map((entry) => - // entry.date.toString() - // .toList(); - - const minChartValue = 40.0; - const maxChartValue = 400.0; - return Scaffold( - body: Column( - children: [ - const CustomAppBar( - title: "Blood Sugar level 🍚", - subTitle: "Keep a record of your blood sugar levels", - color: Constants.bmiCalculatorColor, - ), - const SizedBox(height: Constants.SPACING), - Expanded( - child: Column( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: CustomLineChart( - dataPoints: dataPoints, - dateTimes: dateTimeList, - gradientColors: [ - Constants.bloodSugarColor, - Constants.bloodSugarColor.withOpacity(0.3), - ], - minX: 0, - maxX: data.length.toDouble() - 1, - minY: minChartValue, - maxY: maxChartValue, - leftTile: true, - bottomTile: true, - interval: 80, - dateFormat: "HH:mm-dd/MM", - ), - ), - ), - Expanded( - child: ListView.builder( - itemCount: data.length, - itemBuilder: (context, index) { - return BloodSugarEntryCard(entry: data[index]); - }, - ), - ), - ], - ), - ), - ], - ), - floatingActionButton: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - FloatingActionButton( - onPressed: () async { - // Show add blood sugar screen as dialog - await showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text("Enter Blood Sugar data"), - content: AddBloodSugarScreen(), - scrollable: true, - ); - }, - ); - ref.refresh(bloodSugarEntriesProvider); - }, - child: const Icon(Icons.add), - ), - const SizedBox(height: Constants.SPACING), - FloatingActionButton( - onPressed: () async { - ref.refresh(bloodSugarEntriesProvider); - }, - child: const Icon(Icons.refresh), - ), - ], - ), - ); - }, - loading: () => Center( - child: Column( - children: [ - Text( - "Loading Blood sugar", - style: theme.textTheme.bodySmall, - ), - const SizedBox(height: Constants.SPACING * 2), - const CircularProgressIndicator(), - ], - ), - ), - error: (error, _) { - debugPrint("Blood sugar fetch: ${error.toString()}"); - return Scaffold( - body: const Column( - children: [ - const CustomAppBar( - title: "Blood Sugar level 🍚", - // subTitle: "Keep a record of your blood sugar levels", - color: Constants.bmiCalculatorColor, - ), - Expanded( - child: BackgroundImageWidget( - svgImage: 'assets/images/lab-empty-state.svg', - notFoundText: "No Blood sugar Data Available to display"), - ) - ], - ), - floatingActionButton: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - FloatingActionButton( - heroTag: "refreshButton", - onPressed: () async { - ref.refresh(bloodSugarEntriesProvider); - }, - child: const Icon(Icons.refresh), - ), - const SizedBox(height: 10), - FloatingActionButton( - heroTag: "addButton", - onPressed: () async { - // Show add blood sugar screen as dialog - await showDialog( - context: context, - builder: (context) { - return AlertDialog( - content: AddBloodSugarScreen(), - scrollable: true, - ); - }, - ); - ref.refresh(bloodSugarEntriesProvider); - }, - child: const Icon(Icons.add), - ), - ], - ), - ); - }); - } -} diff --git a/lib/src/features/blood_sugar/presentation/widgets/blood_suger_trend_chart.dart b/lib/src/features/blood_sugar/presentation/widgets/blood_suger_trend_chart.dart deleted file mode 100644 index b94dfa02..00000000 --- a/lib/src/features/blood_sugar/presentation/widgets/blood_suger_trend_chart.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter/material.dart'; -import 'package:nishauri/src/features/blood_sugar/data/models/blood_sugar.dart'; -import 'package:nishauri/src/shared/charts/CustomLineChart.dart'; -import 'package:nishauri/src/utils/constants.dart'; - -class BloodSugarTrendChart extends StatelessWidget { - final List data; - const BloodSugarTrendChart({ - required this.data, - Key? key, - }) : super(key: key); - @override - Widget build(BuildContext context) { - - final dataPoints = data.asMap().entries.map((entry) { - final index = entry.key.toDouble(); - final systolic = entry.value.level; - return FlSpot(index, systolic); - }).toList(); - - - final dateTimeList = data.asMap().entries.map((e) { - return e.value.created_at.toString(); - }).toList(); - - return Scaffold( - body: Padding( - padding: const EdgeInsets.all(16.0), - child: CustomLineChart( - dataPoints: dataPoints, - dateTimes: dateTimeList, - gradientColors: [ - Constants.bloodSugarColor, - Constants.bloodSugarColor.withOpacity(0.3), - ], - minX: 0, - maxX: data.length.toDouble() - 1, - minY: 40.0, - maxY: 400.0, - leftTile: true, - bottomTile: true, - interval: 80, - dateFormat: "HH:mm-dd/MM", - ), - ), - ); - } -} diff --git a/lib/src/features/bmi/presentation/pages/BMICalculatorResultsScreen.dart b/lib/src/features/bmi/presentation/pages/BMICalculatorResultsScreen.dart deleted file mode 100644 index 80955249..00000000 --- a/lib/src/features/bmi/presentation/pages/BMICalculatorResultsScreen.dart +++ /dev/null @@ -1,146 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:go_router/go_router.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:nishauri/src/features/bmi/data/providers/bmi_status_nutrition_provider.dart'; -import 'package:nishauri/src/shared/display/AppCard.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; -import 'package:nishauri/src/utils/constants.dart'; -import 'package:nishauri/src/utils/helpers.dart'; - -import '../../../../shared/input/Button.dart'; - -class BMICalculatorResultsScreen extends HookConsumerWidget { - final double bmi; - - const BMICalculatorResultsScreen({super.key, required this.bmi}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final theme = Theme.of(context); - final bmiStatusNutritionAsync = ref.watch(bmiNutritionProvider); - return Scaffold( - body: Column(children: [ - const CustomAppBar( - title: "BMI Calculator ⚖️", - // icon: Icons.calculate, - color: Constants.bmiCalculatorColor), - Expanded( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(Constants.SPACING), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Results", - style: theme.textTheme.headlineLarge?.copyWith( - color: theme.colorScheme.primary, - ), - ), - Card( - child: Padding( - padding: const EdgeInsets.all(Constants.SPACING), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text("Your BMI is", - style: theme.textTheme.titleMedium), - Text( - getBMIStatusSimplified(bmi), - style: theme.textTheme.titleMedium?.copyWith( - color: - getBMIStatusSimplified(bmi) == 'Normal' - ? Constants.activeSelectionColor - : Colors.red[600], - ), - ), - ]), - Padding( - padding: const EdgeInsets.all(Constants.SPACING), - child: Text( - bmi.toStringAsFixed(1), - style: theme.textTheme.displayLarge - ?.copyWith(color: theme.colorScheme.primary), - ), - ), - Slider( - value: bmi, - onChanged: (value) {}, - min: 0, - max: 60, - ) - ], - ), - ), - ), - const SizedBox(height: Constants.SPACING), - bmiStatusNutritionAsync.when( - data: (data) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - getBMIStatusSimplified(bmi), - style: theme.textTheme.titleLarge?.copyWith( - color: getBMIStatusSimplified(bmi) == 'Normal' - ? Constants.activeSelectionColor - : Colors.red[600], - ), - ), - const SizedBox(height: Constants.SPACING), - Text( - "Diet & Nutrition", - style: theme.textTheme.titleMedium?.copyWith( - color: theme.colorScheme.primary, - ), - ), - const SizedBox(height: Constants.SPACING), - Markdown( - data: data - .where((element) => - element.status == - getBMIStatusSimplified(bmi)) - .first - .description ?? - "", - // style: theme.textTheme.titleMedium, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - ), - const SizedBox(height: Constants.SPACING), - Button( - title: "Re-Calculate", - surfixIcon: SvgPicture.asset( - "assets/images/refresh-circle.svg", - semanticsLabel: "Doctors", - fit: BoxFit.contain, - ), - backgroundColor: Constants.activeSelectionColor, - textColor: Colors.white, - onPress: () { - context.pop(); - }, - ), - ], - ), - error: (e, stackTrace) => Align( - alignment: Alignment.center, child: Text(e.toString())), - loading: () => const Align( - alignment: Alignment.center, - child: CircularProgressIndicator(), - ), - ), - - // Markdown(data: "data") - ], - ), - ), - ), - ), - ]), - ); - } -} diff --git a/lib/src/features/bmi/presentation/pages/BMICalculatorScreen.dart b/lib/src/features/bmi/presentation/pages/BMICalculatorScreen.dart deleted file mode 100644 index fdd42444..00000000 --- a/lib/src/features/bmi/presentation/pages/BMICalculatorScreen.dart +++ /dev/null @@ -1,284 +0,0 @@ -import 'dart:developer'; -import 'dart:ffi'; - -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:go_router/go_router.dart'; -import 'package:google_maps_flutter/google_maps_flutter.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:nishauri/src/features/bmi/data/model/bmi_log.dart'; -import 'package:nishauri/src/features/bmi/data/providers/bmi_log_provider.dart'; -import 'package:nishauri/src/features/bmi/data/services/bmi_log_service.dart'; -import 'package:nishauri/src/features/bmi/presentation/widgets/GenderPicker.dart'; -import 'package:nishauri/src/features/bmi/presentation/widgets/HeightPicker.dart'; -import 'package:nishauri/src/features/bmi/presentation/widgets/HeightUnitsPicker.dart'; -import 'package:nishauri/src/features/user/data/providers/user_provider.dart'; -import 'package:nishauri/src/shared/display/AppCard.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; -import 'package:nishauri/src/shared/display/RadioGroup.dart'; -import 'package:nishauri/src/shared/input/Button.dart'; -import 'package:nishauri/src/shared/input/QuanterSizer.dart'; -import 'package:nishauri/src/utils/constants.dart'; -import 'package:nishauri/src/utils/helpers.dart'; -import 'package:nishauri/src/utils/routes.dart'; - -class BMICalculatorScreen extends HookConsumerWidget { - const BMICalculatorScreen({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final theme = Theme.of(context); - const activeColor = Constants.activeSelectionColor; - var gender = useState(GenderPickerChoices.male); - final isPregnant = useState(false); - final height = useState(180); - final heightUnits = - useState(HeightUnitsPickerOptions.In); - final weight = useState(65); - final age = useState(27); - final isForSelf = useState(true); - final userAsync = ref.watch(userProvider); - - return Scaffold( - body: Column( - children: [ - const CustomAppBar( - title: "BMI Calculator ⚖️", - subTitle: "Empower Your Health Journey \nWith BMI Insights", - // icon: Icons.calculate_outlined, - color: Constants.bmiCalculatorColor, - ), - Expanded( - child: SingleChildScrollView( - child: AppCard( - child: Padding( - padding: const EdgeInsets.all(Constants.SPACING * 0.5), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Card( - child: Container( - padding: const EdgeInsets.all(Constants.SPACING), - child: Column( - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "BMI for: ", - style: theme.textTheme.titleMedium, - ), - const SizedBox(height: Constants.SPACING), - ToggleButtons( - isSelected: [ - isForSelf.value, - !isForSelf.value - ], - onPressed: (index) { - isForSelf.value = index == 0; - // fetch user gender - userAsync.whenData((user) async { - if (user.gender != null) { - debugPrint( - 'user gender: ${user.gender}'); - if (user.gender == 'Male') { - gender = - useState( - GenderPickerChoices.male); - } else { - gender = - useState( - GenderPickerChoices.female); - } - } - }); - }, - selectedColor: Colors - .white, //color for selected button - fillColor: activeColor, - children: const [ - Padding( - padding: EdgeInsets.symmetric( - horizontal: 16.0), - child: Text("Myself"), - ), - Padding( - padding: EdgeInsets.symmetric( - horizontal: 16.0), - child: Text("Other"), - ), - ], - ), - const SizedBox(height: Constants.SPACING), - ], - ), - ], - ), - ), - ), - const SizedBox(height: Constants.SPACING), - Text( - "Choose your gender", - style: theme.textTheme.titleMedium, - ), - const SizedBox(height: Constants.SPACING), - // Check is forself is true - isForSelf.value == false - ? GenderPicker( - gender: gender.value, - onGenderChange: (gender_) { - if (gender_ == GenderPickerChoices.female) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Warning!"), - content: SingleChildScrollView( - child: Wrap( - children: [ - const Text( - "BMI Calculation for pregnant lady is highly discouraged and not supported to avoid drastic decisions.Please confirm your pregnancy status", - ), - RadioGroup( - // value: pregnant.value ? "no":"yes", - onValueChanged: (val) { - context.pop(val == "no"); - }, - items: [ - RadioGroupItem( - value: "yes", - title: "Not Pregnant", - icon: - Icons.woman_rounded), - RadioGroupItem( - value: "no", - title: "Pregnant", - icon: - Icons.pregnant_woman), - ], - ) - ], - ), - )), - ).then((isPregnant_) { - if (isPregnant_ != null) { - if (isPregnant_ == false) { - gender.value = gender_; - isPregnant.value = false; - } else { - gender.value = gender_; - isPregnant.value = true; - ScaffoldMessenger.of(context) - .showSnackBar(const SnackBar( - content: Text( - "BMI calculation for pregnant lady ain't supported"))); - } - } - }); - } else { - gender.value = gender_; - } - }, - activeColor: activeColor, - ) - : GenderPicker( - gender: gender.value, - onGenderChange: (gender) {}, - isEnabled: isForSelf.value, - ), - const SizedBox(height: Constants.SPACING), - HeightPicker( - activeColor: activeColor, - height: height.value, - heightUnits: heightUnits.value, - onHeightChange: (height_) { - height.value = height_; - }, - onHeightUnitsChange: (units) { - heightUnits.value = units; - }, - ), - const SizedBox(height: Constants.SPACING), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Quantizer( - min: 20, - max: 300, - value: weight.value, - onValueChange: (value) => weight.value = value, - label: "Weight", - units: "Kgs", - activeColor: activeColor, - ), - Quantizer( - min: 5, - max: 100, - value: age.value, - onValueChange: (value) => age.value = value, - label: "Age", - units: "Years", - activeColor: activeColor, - ), - ], - ), - const SizedBox(height: Constants.SPACING), - Button( - title: "Calculate", - surfixIcon: SvgPicture.asset( - "assets/images/refresh-circle.svg", - semanticsLabel: "Doctors", - fit: BoxFit.contain, - ), - disabled: isPregnant.value, - backgroundColor: activeColor, - textColor: theme.canvasColor, - onPress: () { - final bmi = - calculateBMI(height.value, weight.value); - print('height.value: ${height.value}'); - print('bmi: $bmi'); - - if (isForSelf.value) { - ref - .read(bmiLogProvider.notifier) - .logBMI(height.value.toString(), - weight.value.toString(), bmi.toString()) - .then((_) { - context.goNamed( - RouteNames.BMI_CALCULATOR_RESULTS, - extra: bmi); - }); - ref.refresh(bmiListProvider); - } else { - context.goNamed(RouteNames.BMI_CALCULATOR_RESULTS, - extra: bmi); - } - }), - const SizedBox(height: Constants.SPACING), - Button( - title: "BMI History", - // disabled: isPregnant.value, - backgroundColor: activeColor, - textColor: theme.canvasColor, - onPress: () { - ref.refresh(bmiListProvider); - context.goNamed(RouteNames.BMI_HISTORY); - }, - ), - ], - ), - ), - ), - ), - ), - ], - ), - ); - } -} - -//Awaiting Endpoints - - diff --git a/lib/src/features/bmi/presentation/widgets/BMILineGraph.dart b/lib/src/features/bmi/presentation/widgets/BMILineGraph.dart deleted file mode 100644 index 478bd7cc..00000000 --- a/lib/src/features/bmi/presentation/widgets/BMILineGraph.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:nishauri/src/features/bmi/data/model/bmi_log.dart'; -import 'package:nishauri/src/features/bmi/data/providers/bmi_log_provider.dart'; -import 'package:nishauri/src/shared/charts/CustomLineChart.dart'; -import 'package:nishauri/src/utils/constants.dart'; - -class BMILineGraph extends StatelessWidget { - final List data; -const BMILineGraph({required this.data, Key? key}): super(key: key); - - @override - Widget build(BuildContext context) { - final List gradientColors = [ - Constants.bmiCalculatorColor.withOpacity(0.3), - Constants.bmiCalculatorShortcutBgColor.withOpacity(0), - ]; - - final dataPoints = data.asMap().entries.map((entry) { - final index = entry.key.toDouble(); - final bmi = entry.value.results; - return FlSpot(index, bmi); - }).toList(); - - final date = data.asMap().entries.map((e) { - return e.value.created_at.toString(); - }).toList(); - - return Scaffold( - body: Padding( - padding: const EdgeInsets.all(16.0), - child: CustomLineChart( - dataPoints: dataPoints, - dateTimes: date, - minX: 0, - maxX: dataPoints.length - 1, - minY: 5, - leftTile: false, - barColor: Constants.bmiCalculatorColor, - gradientColors: gradientColors, - bottomTile: true, - dateFormat: "dd/MM/yy", - ), - ), - ); - } -} diff --git a/lib/src/features/bp/data/providers/blood_pressure_provider.dart b/lib/src/features/bp/data/providers/blood_pressure_provider.dart deleted file mode 100644 index ab7ffafb..00000000 --- a/lib/src/features/bp/data/providers/blood_pressure_provider.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:nishauri/src/features/bp/data/models/blood_pressure.dart'; -import 'package:nishauri/src/features/bp/data/repository/blood_pressure_repository.dart'; -import 'package:nishauri/src/features/bp/data/services/blood_pressure_service.dart'; - -final bloodPressureRepositoryProvider = Provider((ref) { - return BloodPressureRepository(BloodPressureService()); -}); - -final bloodPressureListProvider = FutureProvider>((ref) async { - final repository = ref.watch(bloodPressureRepositoryProvider); - return await repository.getBloodPressures(); -}); \ No newline at end of file diff --git a/lib/src/features/bp/presentation/pages/BPLinelistScreen.dart b/lib/src/features/bp/presentation/pages/BPLinelistScreen.dart deleted file mode 100644 index 159fd55f..00000000 --- a/lib/src/features/bp/presentation/pages/BPLinelistScreen.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:nishauri/src/features/bp/data/models/blood_pressure.dart'; -import 'package:nishauri/src/utils/constants.dart'; - -class BPLinelistScreen extends StatelessWidget { - final List data; - const BPLinelistScreen({ - required this.data, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - - return ListView.builder( - itemCount: data.length + 1, - itemBuilder: (context, index) { - if (index == 0) { - // Header row - return Card( - margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text('Systolic', style: TextStyle(fontWeight: FontWeight.bold)), - Text('Diastolic', style: TextStyle(fontWeight: FontWeight.bold)), - Text('Pulse Rate', style: TextStyle(fontWeight: FontWeight.bold)), - ], - ), - ), - ); - } else { - final bp = data[index - 1]; - return Card( - margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), - child: ExpansionTile( - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text(bp.systolic.toString(), style: TextStyle(color: Colors.red)), - Text(bp.diastolic.toString(), style: TextStyle(color: Colors.orange)), - Text(bp.pulse_rate.toString(), style: TextStyle(color: Constants.programsColor)), - ], - ), - children: [ - ListTile( - title: Text('Time: ${DateFormat('HH:mm - dd-MM-yy').format(DateTime.parse(bp.created_at.toString()))}'), - subtitle: bp.notes != null && bp.notes!.isNotEmpty - ? Text('Notes: ${bp.notes}') - : null, - ), - ], - ), - ); - } - }, - ); - } -} diff --git a/lib/src/features/bp/presentation/pages/bpMonitorScreen.dart b/lib/src/features/bp/presentation/pages/bpMonitorScreen.dart deleted file mode 100644 index 12044120..00000000 --- a/lib/src/features/bp/presentation/pages/bpMonitorScreen.dart +++ /dev/null @@ -1,281 +0,0 @@ -import 'dart:core'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:nishauri/src/features/bp/data/models/blood_pressure.dart'; -import 'package:nishauri/src/features/bp/data/providers/blood_pressure_provider.dart'; -import 'package:nishauri/src/features/bp/presentation/pages/BPLinelistScreen.dart'; -import 'package:nishauri/src/features/bp/presentation/pages/trend_chart_screen.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; -import 'package:nishauri/src/shared/display/background_image_widget.dart'; -import 'package:nishauri/src/utils/constants.dart'; - -class BPMonitorScreen extends ConsumerStatefulWidget { - @override - _BPMonitorScreenState createState() => _BPMonitorScreenState(); -} - -class _BPMonitorScreenState extends ConsumerState { - final _formKey = GlobalKey(); - final _notesController = TextEditingController(); - - void _clearForm(double systolic, double diastolic, double heartRate, TextEditingController notesController) { - setState(() { - systolic = 120; - diastolic = 80; - heartRate = 70; - notesController.clear(); - }); - } - - void _submitData(BuildContext context, double systolic, double diastolic, double heartRate, TextEditingController notesController) { - final String notes = notesController.text; - final DateTime measurementTime = DateTime.now(); - - final bp = BloodPressure( - systolic: systolic, - diastolic: diastolic, - pulse_rate: heartRate, - created_at: measurementTime, - notes: notes, - ); - - ref.read(bloodPressureRepositoryProvider).saveBloodPressure(bp).then((value) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(value)), - ); - _reloadData(); - _clearForm(systolic, diastolic, heartRate, notesController); - Navigator.of(context).pop(); - }).catchError((error) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(error)), - ); - }); - } - - String _getWarningText(String label, double value) { - if (label == 'Systolic') { - if (value < 90) return 'Systolic pressure is too low'; - if (value > 140) return 'Systolic pressure is too high'; - } else if (label == 'Diastolic') { - if (value < 60) return 'Diastolic pressure is too low'; - if (value > 90) return 'Diastolic pressure is too high'; - } else if (label == 'Heart Rate') { - if (value < 60) return 'Heart rate is too low'; - if (value > 100) return 'Heart rate is too high'; - } - return ''; - } - - Color _getSliderColor(String label, double value) { - if (label == 'Systolic') { - if (value < 90 || value > 140) return Colors.red; - } else if (label == 'Diastolic') { - if (value < 60 || value > 90) return Colors.orange; - } else if (label == 'Heart Rate') { - if (value < 60 || value > 100) return Colors.blue; - } - return Colors.green; - } - - void _showDialogForm(BuildContext context) { - double systolic = 120; - double diastolic = 80; - double heartRate = 70; - final notesController = TextEditingController(); - - showDialog( - context: context, - builder: (BuildContext context) { - return StatefulBuilder( - builder: (context, setState) { - return AlertDialog( - title: Text('Enter Blood Pressure Data'), - content: SingleChildScrollView( - child: Form( - key: _formKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Systolic Pressure (mmHg): ${systolic.toInt()}', - style: TextStyle(color: _getSliderColor('Systolic', systolic)), - ), - Slider( - value: systolic, - min: 50, - max: 200, - divisions: 150, - label: systolic.toInt().toString(), - onChanged: (value) { - setState(() { - systolic = value; - }); - }, - ), - Text(_getWarningText('Systolic', systolic)), - Text( - 'Diastolic Pressure (mmHg): ${diastolic.toInt()}', - style: TextStyle(color: _getSliderColor('Diastolic', diastolic)), - ), - Slider( - value: diastolic, - min: 30, - max: 120, - divisions: 90, - label: diastolic.toInt().toString(), - onChanged: (value) { - setState(() { - diastolic = value; - }); - }, - ), - Text(_getWarningText('Diastolic', diastolic)), - Text( - 'Heart Rate (bpm): ${heartRate.toInt()}', - style: TextStyle(color: _getSliderColor('Heart Rate', heartRate)), - ), - Slider( - value: heartRate, - min: 40, - max: 180, - divisions: 140, - label: heartRate.toInt().toString(), - onChanged: (value) { - setState(() { - heartRate = value; - }); - }, - ), - Text(_getWarningText('Heart Rate', heartRate)), - TextFormField( - controller: notesController, - decoration: InputDecoration( - labelText: 'Notes', - border: OutlineInputBorder(), - ), - keyboardType: TextInputType.text, - maxLines: null, - ), - ], - ), - ), - ), - actions: [ - TextButton( - onPressed: () { - _clearForm(systolic, diastolic, heartRate, notesController); - Navigator.of(context).pop(); - }, - child: Text('Cancel'), - ), - ElevatedButton( - onPressed: () { - _submitData(context, systolic, diastolic, heartRate, notesController); - _reloadData(); - }, - child: Text('Submit'), - ), - ], - ); - }, - ); - }, - ); - } - - void _reloadData() { - ref.refresh(bloodPressureListProvider); - } - - @override - Widget build(BuildContext context) { - final bloodPressureListAsync = ref.watch(bloodPressureListProvider); - final theme = Theme.of(context); - return bloodPressureListAsync.when( - data: (data) { - - final displayedData = data.length > 5 ? data.sublist(data.length - 5) : data; - return Scaffold( - body: Column( - children: [ - const CustomAppBar( - title: "Blood Pressure Monitor 📈", - color: Constants.bmiCalculatorColor, - ), - Expanded( - child: Center( - child: Padding( - padding: const EdgeInsets.only(top: 20), - child: Column( - children: [ - Expanded( - flex: 1, - child: TrendChartScreen(data: displayedData), - ), - Expanded( - flex: 1, - child: BPLinelistScreen(data: data), - ), - ], - ), - ), - ), - ), - ], - ), - floatingActionButton: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - FloatingActionButton( - onPressed: _reloadData, - child: Icon(Icons.refresh), - heroTag: null, - ), - SizedBox(height: 10), - FloatingActionButton( - onPressed: () { - _showDialogForm(context); - _reloadData(); - }, - child: Icon(Icons.add), - heroTag: null, - ), - ], - ), - ); - }, - error: (error, _) => BackgroundImageWidget( - customAppBar: const CustomAppBar( - title: "Blood Pressure Monitor 📈", - color: Constants.bmiCalculatorColor, - ), - svgImage: 'assets/images/lab-empty-state.svg', - notFoundText: "No BP Data Available to display", - floatingButtonIcon1: Icons.refresh, - floatingButtonAction1: () { - _reloadData(); - }, - floatingButtonIcon2: Icons.add, - floatingButtonAction2: () { - _showDialogForm(context); - _reloadData(); - }, - ), - loading: () => Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - "Loading Blood Pressure", - style: theme.textTheme.bodySmall, - ), - const SizedBox(height: Constants.SPACING * 2), - const CircularProgressIndicator(), - ], - ), - ), - ); - } -} diff --git a/lib/src/features/bp/presentation/pages/trend_chart_screen.dart b/lib/src/features/bp/presentation/pages/trend_chart_screen.dart deleted file mode 100644 index ec6ce603..00000000 --- a/lib/src/features/bp/presentation/pages/trend_chart_screen.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter/material.dart'; -import 'package:nishauri/src/features/bp/data/models/blood_pressure.dart'; -import 'package:nishauri/src/shared/charts/CustomeMultLineChart.dart'; -import 'package:nishauri/src/utils/constants.dart'; - -class TrendChartScreen extends StatelessWidget { - final List data; - const TrendChartScreen({ - required this.data, - Key? key, - }) : super(key: key); - @override - Widget build(BuildContext context) { - - final systolicSpots = data.asMap().entries.map((entry) { - final index = entry.key.toDouble(); - final systolic = entry.value.systolic; - return FlSpot(index, systolic); - }).toList(); - - final diastolicSpots = data.asMap().entries.map((entry) { - final index = entry.key.toDouble(); - final diastolic = entry.value.diastolic; - return FlSpot(index, diastolic); - }).toList(); - - final pulseRateSpots = data.asMap().entries.map((entry) { - final index = entry.key.toDouble(); - final pulse = entry.value.pulse_rate; - return FlSpot(index, pulse); - }).toList(); - - final date = data.asMap().entries.map((e) { - return e.value.created_at.toString(); - }).toList(); - print(data); - print(date); - - return Scaffold( - body: Padding( - padding: const EdgeInsets.all(16.0), - child: CustomMultiLineChart( - lineBarsData: [ - LineChartBarData( - spots: systolicSpots, - isCurved: true, - color: Colors.red, - barWidth: 5, - belowBarData: BarAreaData( - show: true, - gradient: LinearGradient( - colors: [Colors.red.withOpacity(0.3), Colors.red.withOpacity(0)], - ), - ), - dotData: FlDotData(show: true), - ), - LineChartBarData( - spots: diastolicSpots, - isCurved: true, - color: Colors.orange, - barWidth: 5, - belowBarData: BarAreaData( - show: true, - gradient: LinearGradient( - colors: [Colors.orange.withOpacity(0.3), Colors.orange.withOpacity(0)], - ), - ), - dotData: FlDotData(show: true), - ), - LineChartBarData( - spots: pulseRateSpots, - isCurved: true, - color: Constants.programsColor, - barWidth: 5, - belowBarData: BarAreaData( - show: true, - gradient: LinearGradient( - colors: [Constants.programsColor.withOpacity(0.3), Constants.programsColor.withOpacity(0)], - ), - ), - dotData: FlDotData(show: true), - ), - ], - minX: 0, - maxX: pulseRateSpots.length - 1, - minY: 29, - dateTimes: date, - showLeftTitles: false, - ), - ), - ); - } -} diff --git a/lib/src/features/chatbot/presentations/ChatScreen.dart b/lib/src/features/chatbot/presentations/ChatScreen.dart index 3295e9af..c58708a8 100644 --- a/lib/src/features/chatbot/presentations/ChatScreen.dart +++ b/lib/src/features/chatbot/presentations/ChatScreen.dart @@ -328,7 +328,7 @@ class _ChatScreenState extends ConsumerState { setState(() { _consent = !_consent; }); - _updateConsent(type == ConsentType.accept ? true : false); + _updateConsent(_consent); Navigator.of(context).pop(); }, child: const Text('Yes'), @@ -356,6 +356,7 @@ class _ChatScreenState extends ConsumerState { consentData.isNotEmpty ? consentData.first.chatbot_consent : null; setState(() { _consent = remoteConsent == "1" ? true : false; + print("The consent name ${_consent}"); }); } catch (e) { debugPrint("Error fetching consent: $e"); @@ -383,9 +384,11 @@ class _ChatScreenState extends ConsumerState { // Check and show consent dialog for first time use void _checkAndShowConsentDialog() { final settings = ref.read(settingsNotifierProvider); + final settingUpdate = ref.read(settingsNotifierProvider.notifier); if (settings.firstNuruAccess) { debugPrint("Nuru first time use: ${settings.firstNuruAccess}"); _showConsentDialog(context, ref, ConsentType.accept); + settingUpdate.updateSettings(firstNuruAccess: false); } else { debugPrint("Nuru first time use: ${settings.firstNuruAccess}"); } @@ -424,8 +427,9 @@ class _ChatScreenState extends ConsumerState { ], ), ), - _showConsent - ? Flex( + // _showConsent + // ? + Flex( direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -449,8 +453,8 @@ class _ChatScreenState extends ConsumerState { style: TextStyle(color: Colors.green), )) ], - ) - : const SizedBox(), + ), + // : const SizedBox(), const Divider(), Expanded( child: ListView.builder( diff --git a/lib/src/features/clinic_card/data/models/health_test.dart b/lib/src/features/clinic_card/data/models/health_test.dart new file mode 100644 index 00000000..0212c3f9 --- /dev/null +++ b/lib/src/features/clinic_card/data/models/health_test.dart @@ -0,0 +1,250 @@ +import 'dart:ffi'; + +class HealthRecordModel { + final String uuid; + final String visitDate; + final String facility; + final List conditions; + final List medications; + // final List medications; + final List allergies; + final List vitals; + final List labResults; + // final List procedures; + final List immunizations; + + HealthRecordModel({ + required this.uuid, + required this.visitDate, + required this.facility, + required this.conditions, + required this.medications, + required this.allergies, + required this.vitals, + required this.labResults, + // required this.procedures, + required this.immunizations, + }); + + factory HealthRecordModel.fromJson(Map json) { + return HealthRecordModel( + uuid: json['uuid'], + visitDate: json['visitDate'], + facility: json['facility'], + conditions: (json['conditions'] as List) + .map((condition) => Condition.fromJson(condition)) + .toList(), + medications: (json['medications'] as List) + .map((medication) => Medication.fromJson(medication)) + .toList(), + allergies: (json['allergies'] as List) + .map((allergy) => Allergy.fromJson(allergy)) + .toList(), + vitals: (json['vitals'] as List) + .map((vital) => Vital.fromJson(vital)) + .toList(), + labResults: (json['labResults'] as List) + .map((labResult) => LabResult.fromJson(labResult)) + .toList(), + // procedures: (json['procedures'] as List) + // .map((procedure) => Procedure.fromJson(procedure)) + // .toList(), + immunizations: (json['immunization'] as List) + .map((immunization) => Immunization.fromJson(immunization)) + .toList(), + ); + } +} + +class Condition { + final String uuid; + final String name; + final String onsetDate; + final String dateRecorded; + final String status; + final String value; + + Condition({ + required this.uuid, + required this.name, + required this.onsetDate, + required this.dateRecorded, + required this.status, + required this.value, + }); + + factory Condition.fromJson(Map json) { + return Condition( + uuid: json['uuid'], + name: json['name'], + onsetDate: json['onsetDate'], + dateRecorded: json['dateRecorded'], + status: json['status'], + value: json['value'], + ); + } +} + +class Vital { + final String uuid; + final String name; + final String weight; + final String temp; + final String systolic; + final String diastolic; + final String respiratory; + final String oxygenSaturation; + final String height; + final String complain; + + Vital({ + required this.uuid, + required this.name, + required this.weight, + required this.temp, + required this.systolic, + required this.diastolic, + required this.respiratory, + required this.oxygenSaturation, + required this.height, + required this.complain, + }); + + factory Vital.fromJson(Map json) { + return Vital( + uuid: json['uuid'], + name: json['name'], + weight: json['weight'], + temp: json['temp'], + systolic: json['systolic'], + diastolic: json['diastolic'], + respiratory: json['respiratory'], + oxygenSaturation: json['oxygenSaturation'], + height: json['height'], + complain: json['complain'], + ); + } +} + +class Allergy { + final String uuid; + final String allergen; + final String reaction; + final String severity; + final String onsetDate; + final String dateRecorded; + + Allergy({ + required this.uuid, + required this.allergen, + required this.reaction, + required this.severity, + required this.onsetDate, + required this.dateRecorded, + }); + + factory Allergy.fromJson(Map json) { + return Allergy( + uuid: json['uuid'], + allergen: json['allergen'], + reaction: json['reaction'], + severity: json['severity'], + onsetDate: json['onsetDate'], + dateRecorded: json['dateRecorded'], + ); + } +} + +class LabResult { + final String uuid; + final String name; + final String results; + final String orderedDate; + final String status; + final int plot; + + LabResult({ + required this.uuid, + required this.name, + required this.results, + required this.orderedDate, + required this.status, + required this.plot, + }); + + factory LabResult.fromJson(Map json) { + return LabResult( + uuid: json['uuid'], + name: json['name'], + results: json['results'], + orderedDate: json['orderedDate'], + status: json['status'], + plot: json['plot'], + ); + } +} + +// Define similar models for Medication, Allergy, Vital, LabResult, Procedure, and Immunization. +//Immunization +class Immunization{ + final String uuuid; + final String name; + final String immunizationDate; + final String manufacturer; + final String lot; + + + Immunization({ + required this.uuuid, + required this.name, + required this.immunizationDate, + required this.manufacturer, + required this.lot, +}); + + factory Immunization.fromJson(Map json){ + return Immunization( + uuuid: json['uuid'], + name: json['name'], + immunizationDate: json['immunizationDate'], + manufacturer: json['manufacturer'], + lot: json['lot'] + ); + }} + +//Medication +class Medication{ + final String uuid; + final String name; + final String dateRecorded; + final String value; + final String indication; + + Medication({ + required this.uuid, + required this.name, + required this.dateRecorded, + required this.value, + required this. indication, + }); + + factory Medication.fromJson(Map json){ + return Medication( + uuid: json['uuid'], + name: json['name'], + dateRecorded: json['dateRecorded'], + value: json['value'], + indication: json['indication'] + ); + + + } + } + + + + + + + + diff --git a/lib/src/features/clinic_card/presentation/pages/ClinicCardScreen.dart b/lib/src/features/clinic_card/presentation/pages/ClinicCardScreen.dart index 0691d2dd..e5853edf 100644 --- a/lib/src/features/clinic_card/presentation/pages/ClinicCardScreen.dart +++ b/lib/src/features/clinic_card/presentation/pages/ClinicCardScreen.dart @@ -1,15 +1,14 @@ -import 'dart:developer'; - +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:nishauri/src/features/clinic_card/data/providers/programProvider.dart'; -import 'package:nishauri/src/features/clinic_card/presentation/widgets/clinicalDetails.dart'; -import 'package:nishauri/src/shared/display/CustomTabBar.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/features/self_screening/presentation/widgets/health_list.dart'; +import 'package:nishauri/src/features/visits/data/providers/visits_provider.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/shared/display/background_image_widget.dart'; import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/routes.dart'; import '../../../user_programs/data/providers/program_provider.dart'; @@ -21,115 +20,210 @@ class ClinicCardScreen extends HookConsumerWidget { final theme = Theme.of(context); final programAsync = ref.watch(programProvider); final userPrograms = ref.watch(userProgramProvider); + final visitAsync = ref.watch(visitProvider); + + final visits = visitAsync.when( + data: (data) { + return data; // This is a List + }, + error: (error, _) { + print("Error occurred: $error"); + return []; + }, + loading: () { + return []; + }, + ); + +// Check if visits is not empty before accessing properties + if (visits.isNotEmpty) { + // Print labResults for each visit + for (var visit in visits) { + print(visit.labResults); + } + + // Alternatively, if you only want the labResults from the first visit: + print(visits.first.labResults); + } else { + print("No visit data available."); + } + + + final Map svgMapping = { + "allergies": "assets/images/boldDuotoneMedicineVirus.svg", + "immunization": "assets/images/boldDuotoneMedicineSyringe.svg", + "conditions": "assets/images/boldDuotoneMedicineStethoscope.svg", + "labResults": "assets/images/boldDuotoneMedicineTestTube.svg", + "medications": "assets/images/boldDuotoneMedicineJarOfPills2.svg", + "procedures": "assets/images/boldDuotoneMedicineBone.svg", + "vitals": "assets/images/boldDuotoneMedicineHeartPulse2.svg" + }; + + final List items = [ + "Allergies", + "Conditions", + "Immunizations", + "Lab Results", + "Medications", + "Procedures", + "Vitals" + ]; + + final List icons = [ + // "assets/images/boldDuotoneFoldersFolderPathConnect.svg", + "assets/images/boldDuotoneMedicineVirus.svg", + "assets/images/boldDuotoneMedicineStethoscope.svg", + "assets/images/boldDuotoneMedicineSyringe.svg", + "assets/images/boldDuotoneMedicineTestTube.svg", + "assets/images/boldDuotoneMedicineJarOfPills2.svg", + "assets/images/boldDuotoneMedicineBone.svg", + "assets/images/boldDuotoneMedicineHeartPulse2.svg" + ]; + + final List paths = [ + RouteNames.ALLERGY_HEALTH_RECORD, + RouteNames.HEALTH_RECORD, + RouteNames.IMMUNIZATION_RECORD, + RouteNames.LAB_RESULTS_HEALTH_RECORD, + RouteNames.MEDICATION_RECORD, + "", + RouteNames.VITAL_HEALTH_RECORD, + + ]; void _reloadData() { ref.refresh(programProvider); ref.refresh(userProgramProvider); } - final currIndex = useState(0); return programAsync.when( data: (data) { - final activePrograms = data.where((element) { - if (userPrograms.hasValue) { - return userPrograms.value! - .where((e) => element.name == e.program_name && e.isActive) - .isNotEmpty; - } - return false; + final activePrograms = data.where((program) { + return userPrograms.hasValue && userPrograms.value!.any( + (userProgram) => userProgram.program_name == program.name && userProgram.isActive + ); }); - // Check if the data list is empty + if (activePrograms.isEmpty) { - return BackgroundImageWidget( - customAppBar: CustomAppBar( - title: "My Clinic Card 🎫", - // icon: Icons.file_present, - subTitle: "Access medical services using \nyour clinic cards", - color: Colors.blue[900], - ), - svgImage: 'assets/images/lab-empty-state.svg', - notFoundText: "No programs available", - floatingButtonIcon1: Icons.refresh, - floatingButtonAction1: () { - _reloadData(); - }, - ); + return _buildEmptyState(context, _reloadData); } - final screens = activePrograms - .map((program) => ClinicalDetailsTab(program: program)); return Scaffold( - body: Column( - children: [ - CustomAppBar( - title: "My Clinic Card 🎫", - // icon: Icons.file_present, - color: Colors.blue[900], - subTitle: "Access medical services using \nyour clinic cards", - ), - CustomTabBar( - onTap: (item, index) { - currIndex.value = index; - }, - activeColor: Constants.clinicCardColor, - items: activePrograms - .map( - (program) => CustomTabBarItem( - title: program.name, - icon: Icons.multiple_stop, + body: SingleChildScrollView( + child: Column( + children: [ + const CustomAppBar( + title: "My Clinic Card 👨🏾‍💼", + color: Constants.clinicCardBgColor, + subTitle: "Access all your medical details", + svgPathGroup: "assets/images/group_clinic_card.svg", + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + "Health Records", + style: theme.textTheme.titleMedium, + overflow: TextOverflow.ellipsis, // Prevents text overflow. + maxLines: 1, + ), + ), + ], ), - ) - .toList(), - activeIndex: currIndex.value, - ), - Expanded(child: screens.elementAt(currIndex.value)) - ], + const SizedBox(height: 10), + ItemList(items: items, path: paths, svgAsset: icons), + const SizedBox(height: 10), + // Row( + // children: [ + // Expanded( + // child: Text( + // "Shared With Me", + // style: theme.textTheme.titleMedium, + // overflow: TextOverflow.ellipsis, // Prevents overflow. + // maxLines: 1, + // ), + // ), + // ], + // ), + // const ItemList( + // items: ["Eric Muthomi"], + // svgAsset: ["assets/images/clinicCardCard.svg"], + // path: [RouteNames.MY_DEPENDANT_CLINIC_CARD], + // color: Constants.clinicCardBgColor, + // relationship: ["Next of Kin"], + // ), + ], + ), + ), + ], + ), ), - floatingActionButton: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - FloatingActionButton( - onPressed: _reloadData, - child: Icon(Icons.refresh), - heroTag: null, - ), - ], + floatingActionButton: FloatingActionButton( + onPressed: _reloadData, + child: const Icon(Icons.refresh), + tooltip: 'Refresh Data', ), ); + }, - error: (error, _) => BackgroundImageWidget( - customAppBar: CustomAppBar( - title: "My Clinic Card 🎫", - // icon: Icons.file_present, - subTitle: "Access medical services using \nyour clinic cards", - color: Colors.blue[900], - ), - svgImage: 'assets/images/lab-empty-state.svg', - notFoundText: error.toString(), - floatingButtonIcon1: Icons.refresh, - floatingButtonAction1: () { - _reloadData(); - }, + error: (error, _) => _buildErrorState(context, error.toString(), _reloadData), + loading: () => _buildLoadingState(context, theme), + ); + } + + Widget _buildEmptyState(BuildContext context, VoidCallback reload) { + return BackgroundImageWidget( + customAppBar: const CustomAppBar( + title: "My Clinic Card 👨🏾‍💼", + color: Constants.clinicCardBgColor, + subTitle: "Access all your medical details", + svgPathGroup: "assets/images/group_clinic_card.svg", ), - loading: () => Scaffold( - body: Column( - // mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - CustomAppBar( - title: "My Clinic Card 🎫", - // icon: Icons.file_present, - color: Colors.blue[900], - subTitle: "Access medical services using \nyour clinic cards", - ), - Text( - "Loading Programs", - style: theme.textTheme.headline6, - ), - const SizedBox(height: Constants.SPACING * 2), - const CircularProgressIndicator(), - ], - ), + svgImage: 'assets/images/lab-empty-state.svg', + notFoundText: "No programs available", + floatingButtonIcon1: Icons.refresh, + floatingButtonAction1: reload, + ); + } + + Widget _buildErrorState(BuildContext context, String error, VoidCallback reload) { + return BackgroundImageWidget( + customAppBar: const CustomAppBar( + title: "My Clinic Card 👨🏾‍💼", + color: Constants.clinicCardBgColor, + subTitle: "Access all your medical details", + svgPathGroup: "assets/images/group_clinic_card.svg", + ), + svgImage: 'assets/images/lab-empty-state.svg', + notFoundText: error, + floatingButtonIcon1: Icons.refresh, + floatingButtonAction1: reload, + ); + } + + Widget _buildLoadingState(BuildContext context, ThemeData theme) { + return Scaffold( + body: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CustomAppBar( + title: "My Clinic Card 👨🏾‍💼", + color: Constants.clinicCardBgColor, + subTitle: "Access all your medical details", + svgPathGroup: "assets/images/group_clinic_card.svg", + ), + Text( + "Loading Programs", + style: theme.textTheme.headline6, + ), + const SizedBox(height: Constants.SPACING * 2), + const CircularProgressIndicator(), + ], ), ); } diff --git a/lib/src/features/clinic_card/presentation/widgets/allergy_records.dart b/lib/src/features/clinic_card/presentation/widgets/allergy_records.dart new file mode 100644 index 00000000..4b8bb00d --- /dev/null +++ b/lib/src/features/clinic_card/presentation/widgets/allergy_records.dart @@ -0,0 +1,274 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nishauri/src/features/clinic_card/data/models/health_test.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/extensions/extensions.dart'; +import 'package:nishauri/src/shared/helper/health_record_filter.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +import '../../../../shared/display/heath_filter_button.dart'; + +class AllergyHealthRecord extends StatefulWidget { + const AllergyHealthRecord({Key? key}) : super(key: key); + + @override + _VisitHealthRecord createState() => _VisitHealthRecord(); +} + +class _VisitHealthRecord extends State { + // Store the selected filter + DateFilter? selectedFilter; + DateTimeRange? selectedDateRange; + + Future> _loadHealthRecords() async { + final responseString = await rootBundle.loadString('assets/data/visits.json'); + final List json = jsonDecode(responseString); + return json.map((e) => HealthRecordModel.fromJson(e)).toList(); + } + + Future> _applyFilter(List records) async { + List filteredRecords = applyFilter( + records, + selectedFilter ?? DateFilter.all, + selectedDateRange?.start, + selectedDateRange?.end, + ); + + return filteredRecords; + } + + Future _selectDateRange(BuildContext context) async { + DateTimeRange? pickedRange = await selectDateRange( + context: context, + initialDateRange: selectedDateRange, + primaryColor: Constants.bmiCalculatorColor, + barrierColor: Constants.bmiCalculatorColor, + ); + + if (pickedRange != null && pickedRange != selectedDateRange) { + setState(() { + selectedDateRange = pickedRange; + selectedFilter = DateFilter.dateRange; + }); + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + color: Constants.clinicCardBgColor, + height: Constants.SMALL_APP_BAR_HEIGHT, + smallTitle: "Allergies", + rightBtTitle: "", + ), + Expanded( + child: FutureBuilder>( + future: _loadHealthRecords(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (snapshot.hasData) { + final healthRecords = snapshot.data!; + return SingleChildScrollView( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + children: [ + _buildFilterMenu(theme), + FutureBuilder>( + future: _applyFilter(healthRecords), + builder: (context, filteredSnapshot) { + if (filteredSnapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (filteredSnapshot.hasError) { + return Center(child: Text('Error: ${filteredSnapshot.error}')); + } else if (filteredSnapshot.hasData) { + final filteredRecords = filteredSnapshot.data!; + + // Check if the filtered list is empty and show 'No Data' text + if (filteredRecords.isEmpty) { + return const Center(child: Text('No data available.')); + } + + return Column( + children: filteredRecords + .map((record) => _buildRecordView(record, theme)) + .toList(), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ], + ), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ), + ], + ), + ); + } + + // Record View Widgets and Helpers (No changes here, same as before) + Widget _buildRecordView(HealthRecordModel record, ThemeData theme) { + return Column( + children: [ + _buildDateRow(record.visitDate, theme), + const SizedBox(height: Constants.SPACING), + // _buildHospitalRow(record.facility, theme), + // const SizedBox(height: Constants.SPACING), + // const Divider(), + _buildAllergyList(record.allergies, theme), + const SizedBox(height: Constants.SPACING), + const Divider(), + ], + ); + } + + Widget _buildDateRow(String date, ThemeData theme) { + return Row( + children: [ + Expanded( + child: Text( + date, + style: theme.textTheme.titleSmall), + ), + ], + ); + } + + Widget _buildHospitalRow(String hospital, ThemeData theme) { + return Row( + children: [ + Expanded( // Wrap the Text widget in Expanded + child: Text( + hospital, + style: theme.textTheme.titleMedium, + overflow: TextOverflow.ellipsis, // Truncate if too long + ), + ), + ], + ); + } + + Widget _buildAllergyList(List allergies, ThemeData theme) { + if (allergies.isEmpty) { + return const Center( + child:Text('No Vitals recorded.') + ); + } + + return Column( + children: allergies.map((allergy) => _buildAllergyRow(allergy, theme)).toList(), + ); + } + + Widget _buildAllergyRow(Allergy allergy, ThemeData theme) { + return ListTile( + title: ExpansionTile( + title: Row( + children: [ + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildAllergiesRow(allergy, theme), + ], + )), + ], + ), + children: [ + ListTile( + title: Column( + children: [ + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Reactions", style: theme.textTheme.bodySmall), + Text(allergy.reaction, style: theme.textTheme.bodySmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Onset Date", style: theme.textTheme.bodySmall), + Text(allergy.onsetDate, style: theme.textTheme.titleSmall), + ], + ), + ], + ), + ) + ], + ), + ); + } + + Widget _buildAllergiesRow(Allergy allergy, ThemeData theme) { + return Row( + children: [ + SvgPicture.asset( + "assets/images/boldDuotoneMedicineVirus.svg", + width: Constants.TWENTY, + height: Constants.TWENTY, + ), + const SizedBox(width: Constants.SPACING), + Expanded( + child:Text(allergy.allergen, + style: theme.textTheme.titleSmall, + overflow: TextOverflow.ellipsis, // Truncate if too long + maxLines: 1, + ), + ), + const Spacer(), + Text(allergy.severity.titleCase, style: theme.textTheme.bodySmall?.copyWith(color: getSeverityColor(allergy.severity), )), + ], + ); + } + + Color getSeverityColor(String severity) { + if (severity == 'MILD') { + return Constants.programsColor; + } else if (severity == 'MODERATE') { + return Constants.facilityDirectoryColor; + } else if (severity == 'SEVERE') { + return Constants.selfScreeningBgColor; + } else { + return Constants.selfScreeningBgColor; + } + } + + // Build the filter menu with the date range option + Widget _buildFilterMenu(ThemeData theme) { + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HealthButton( + onFilterSelected: (filter) { + setState(() { + selectedFilter = filter; + if (filter == DateFilter.dateRange) { + // Trigger the date range picker when the date range filter is selected + _selectDateRange(context); + } + }); + }, + ), + ], + ); + } +} diff --git a/lib/src/features/clinic_card/presentation/widgets/condition_records.dart b/lib/src/features/clinic_card/presentation/widgets/condition_records.dart new file mode 100644 index 00000000..bbd22936 --- /dev/null +++ b/lib/src/features/clinic_card/presentation/widgets/condition_records.dart @@ -0,0 +1,273 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nishauri/src/features/clinic_card/data/models/health_test.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/heath_filter_button.dart'; +import 'package:nishauri/src/shared/extensions/extensions.dart'; +import 'package:nishauri/src/shared/helper/health_record_filter.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class ConditionHealthRecord extends StatefulWidget { + const ConditionHealthRecord({Key? key}) : super(key: key); + + @override + _ConditionHealthRecordState createState() => _ConditionHealthRecordState(); +} + +class _ConditionHealthRecordState extends State { + // Store the selected filter + DateFilter? selectedFilter; + DateTimeRange? selectedDateRange; + + // Load health records from a JSON file + Future> _loadHealthRecords() async { + final responseString = await rootBundle.loadString('assets/data/visits.json'); + final List json = jsonDecode(responseString); + return json.map((e) => HealthRecordModel.fromJson(e)).toList(); + } + + // Apply the selected filter to the health records + Future> _applyFilter(List records) async { + List filteredRecords = applyFilter( + records, + selectedFilter ?? DateFilter.all, + selectedDateRange?.start, + selectedDateRange?.end, + ); + + return filteredRecords; + } + + // Function to handle date range selection + Future _selectDateRange(BuildContext context) async { + DateTimeRange? pickedRange = await selectDateRange( + context: context, + initialDateRange: selectedDateRange, + primaryColor: Constants.bmiCalculatorColor, + barrierColor: Constants.bmiCalculatorColor, + ); + + if (pickedRange != null && pickedRange != selectedDateRange) { + setState(() { + selectedDateRange = pickedRange; + selectedFilter = DateFilter.dateRange; + }); + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + color: Constants.clinicCardBgColor, + height: Constants.SMALL_APP_BAR_HEIGHT, + smallTitle: "Conditions", + rightBtTitle: "", + ), + Expanded( + child: FutureBuilder>( + future: _loadHealthRecords(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (snapshot.hasData) { + final healthRecords = snapshot.data!; + return SingleChildScrollView( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + children: [ + _buildFilterMenu(theme), + FutureBuilder>( + future: _applyFilter(healthRecords), + builder: (context, filteredSnapshot) { + if (filteredSnapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (filteredSnapshot.hasError) { + return Center(child: Text('Error: ${filteredSnapshot.error}')); + } else if (filteredSnapshot.hasData) { + final filteredRecords = filteredSnapshot.data!; + + // Check if the filtered list is empty and show 'No Data' text + if (filteredRecords.isEmpty) { + return const Center(child: Text('No data available.')); + } + + return Column( + children: filteredRecords + .map((record) => _buildRecordView(record, theme)) + .toList(), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ], + ), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ), + ], + ), + ); + } + + // Record View Widgets and Helpers (No changes here, same as before) + Widget _buildRecordView(HealthRecordModel record, ThemeData theme) { + return Column( + children: [ + _buildDateRow(record.visitDate, theme), + const SizedBox(height: Constants.SPACING), + // _buildHospitalRow(record.facility, theme), + // const SizedBox(height: Constants.SPACING), + // const Divider(), + _buildConditionList(record.conditions, theme), + const SizedBox(height: Constants.SPACING), + const Divider(), + ], + ); + } + + Widget _buildDateRow(String date, ThemeData theme) { + return Row( + children: [ + Expanded( + child: Text( + date, + style: theme.textTheme.titleSmall), + ), + ], + ); + } + + Widget _buildHospitalRow(String hospital, ThemeData theme) { + return Row( + children: [ + Expanded( // Wrap the Text widget in Expanded + child: Text( + hospital, + style: theme.textTheme.titleMedium, + overflow: TextOverflow.ellipsis, // Truncate if too long + ), + ), + ], + ); + } + + Widget _buildConditionList(List conditions, ThemeData theme) { + if (conditions.isEmpty) { + return const Center( + child:Text('No conditions recorded.') + ); + } + + return Column( + children: conditions.map((condition) => _buildConditionRow(condition, theme)).toList(), + ); + } + + Widget _buildConditionRow(Condition condition, ThemeData theme) { + return ListTile( + title: ExpansionTile( + title: Row( + children: [ + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildConditionsRow(condition, theme), + ], + )), + ], + ), + children: [ + ListTile( + title: Column( + children: [ + Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Recorded", style: theme.textTheme.bodySmall), + Text(condition.dateRecorded, style: theme.textTheme.bodySmall), + ], + ), + ], + ), + ) + ], + ), + ); + } + + Widget _buildConditionsRow(Condition condition, ThemeData theme) { + return Row( + children: [ + SvgPicture.asset( + "assets/images/boldDuotoneMedicineStethoscope.svg", + width: Constants.TWENTY, + height: Constants.TWENTY, + ), + const SizedBox(width: Constants.SPACING), + Expanded( + child:Text(condition.name, + style: theme.textTheme.titleSmall, + overflow: TextOverflow.ellipsis, // Truncate if too long + maxLines: 1, + ),), + const Spacer(), + Text(condition.status.titleCase, style: theme.textTheme.bodySmall?.copyWith(color: getStatusColor(condition.status))), + ], + ); + } + + Color getStatusColor(String status) { + if (status == 'ACTIVE') { + return Constants.programsColor; + } else if (status == 'INACTIVE') { + return Constants.facilityDirectoryColor; + } else { + return Constants.selfScreeningBgColor; + } + } + + // Build the filter menu with the date range option + Widget _buildFilterMenu(ThemeData theme) { + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HealthButton( + onFilterSelected: (filter) { + setState(() { + selectedFilter = filter; + if (filter == DateFilter.dateRange) { + // Trigger the date range picker when the date range filter is selected + _selectDateRange(context); + } + }); + }, + ), + // if (selectedDateRange != null) + // Padding( + // padding: const EdgeInsets.only(left: Constants.SPACING), + // child: Text( + // 'From ${selectedDateRange!.start.toLocal()} - ${selectedDateRange!.end.toLocal()}', + // style: theme.textTheme.bodyMedium, + // ), + // ), + ], + ); + } +} diff --git a/lib/src/features/clinic_card/presentation/widgets/immunization.dart b/lib/src/features/clinic_card/presentation/widgets/immunization.dart new file mode 100644 index 00000000..fa27b265 --- /dev/null +++ b/lib/src/features/clinic_card/presentation/widgets/immunization.dart @@ -0,0 +1,273 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nishauri/src/features/clinic_card/data/models/health_test.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/heath_filter_button.dart'; +import 'package:nishauri/src/shared/helper/health_record_filter.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class ImmunizationTest extends StatefulWidget { + const ImmunizationTest({super.key}); + + @override + State createState() => _ImmunizationTestState(); +} + +class _ImmunizationTestState extends State { + // Store the selected filter + DateFilter? selectedFilter; + DateTimeRange? selectedDateRange; + + // Load health records from a JSON file + Future> _loadHealthRecords() async { + final responseString = await rootBundle.loadString( + 'assets/data/visits.json'); + final List json = jsonDecode(responseString); + return json.map((e) => HealthRecordModel.fromJson(e)).toList(); + } + + // Apply the selected filter to the health records + Future> _applyFilter(List records) async { + List filteredRecords = applyFilter( + records, + selectedFilter ?? DateFilter.all, + selectedDateRange?.start, + selectedDateRange?.end, + ); + + return filteredRecords; + } + + // Function to handle date range selection + Future _selectDateRange(BuildContext context) async { + DateTimeRange? pickedRange = await selectDateRange( + context: context, + initialDateRange: selectedDateRange, + primaryColor: Constants.bmiCalculatorColor, + barrierColor: Constants.bmiCalculatorColor, + ); + + if (pickedRange != null && pickedRange != selectedDateRange) { + setState(() { + selectedDateRange = pickedRange; + selectedFilter = DateFilter.dateRange; + }); + } + } + + @override + Widget build(BuildContext context) { + //return const Placeholder(); + final theme = Theme.of(context); + + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + color: Constants.clinicCardBgColor, + height: Constants.SMALL_APP_BAR_HEIGHT, + smallTitle: "Immunizations", + rightBtTitle: "", + ), + Expanded( + child: FutureBuilder>( + future: _loadHealthRecords(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (snapshot.hasData) { + final healthRecords = snapshot.data!; + return SingleChildScrollView( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + children: [ + _buildFilterMenu(theme), + FutureBuilder>( + future: _applyFilter(healthRecords), + builder: (context, filteredSnapshot) { + if (filteredSnapshot.connectionState == + ConnectionState.waiting) { + return const Center( + child: CircularProgressIndicator()); + } else if (filteredSnapshot.hasError) { + return Center(child: Text( + 'Error: ${filteredSnapshot.error}')); + } else if (filteredSnapshot.hasData) { + final filteredRecords = filteredSnapshot.data!; + + // Check if the filtered list is empty and show 'No Data' text + if (filteredRecords.isEmpty) { + return const Center( + child: Text('No data available.')); + } + + return Column( + children: filteredRecords + .map((record) => + _buildRecordView(record, theme)) + .toList(), + ); + } else { + return const Center( + child: Text('No data available.')); + } + }, + ), + ], + ), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ), + ], + ), + ); + } + + // Record View Widgets and Helpers (No changes here, same as before) + Widget _buildRecordView(HealthRecordModel record, ThemeData theme) { + return Column( + children: [ + _buildDateRow(record.visitDate, theme), + const SizedBox(height: Constants.SPACING), + // _buildHospitalRow(record.facility, theme), + // const SizedBox(height: Constants.SPACING), + // const Divider(), + _buildConditionList(record.immunizations, theme), + const SizedBox(height: Constants.SPACING), + const Divider(), + ], + ); + } + + Widget _buildDateRow(String date, ThemeData theme) { + return Row( + children: [ + Expanded( + child: Text( + date, + style: theme.textTheme.titleSmall), + ), + ], + ); + } + + Widget _buildHospitalRow(String hospital, ThemeData theme) { + return Row( + children: [ + Expanded( // Wrap the Text widget in Expanded + child: Text( + hospital, + style: theme.textTheme.titleMedium, + overflow: TextOverflow.ellipsis, // Truncate if too long + ), + ), + ], + ); + } + + Widget _buildConditionList(List immunizations, ThemeData theme) { + if (immunizations.isEmpty) { + return const Center( + child: Text('No immunizations recorded.') + ); + } + + return Column( + children: immunizations.map((immunization) => + _buildImmunizationRow(immunization, theme)).toList(), + ); + } + + + Widget _buildImmunizationRow(Immunization immunization, ThemeData theme) { + return ListTile( + title: ExpansionTile( + title: Row( + children: [ + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildImmunizationsRow(immunization, theme), + ], + )), + ], + ), + children: [ + ListTile( + title: Column( + children: [ + Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Vaccination Date", style: theme.textTheme.bodySmall), + Text(immunization.immunizationDate, + style: theme.textTheme.bodySmall), + ], + ), + ], + ), + ) + ], + ), + ); + } + + Widget _buildImmunizationsRow(Immunization immunization, ThemeData theme) { + return Row( + children: [ + SvgPicture.asset( + "assets/images/boldDuotoneMedicineStethoscope.svg", + width: Constants.TWENTY, + height: Constants.TWENTY, + ), + const SizedBox(width: Constants.SPACING), + Expanded( + child: Text(immunization.name, + style: theme.textTheme.titleSmall, + // Reduce font size + overflow: TextOverflow.ellipsis, + // Truncate if too long + maxLines: 1, + ),), + ], + ); + } + + // Build the filter menu with the date range option + Widget _buildFilterMenu(ThemeData theme) { + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HealthButton( + onFilterSelected: (filter) { + setState(() { + selectedFilter = filter; + if (filter == DateFilter.dateRange) { + // Trigger the date range picker when the date range filter is selected + _selectDateRange(context); + } + }); + }, + ), + // if (selectedDateRange != null) + // Padding( + // padding: const EdgeInsets.only(left: Constants.SPACING), + // child: Text( + // 'From ${selectedDateRange!.start.toLocal()} - ${selectedDateRange!.end.toLocal()}', + // style: theme.textTheme.bodyMedium, + // ), + // ), + ], + ); + } +} diff --git a/lib/src/features/clinic_card/presentation/widgets/lab_result_records.dart b/lib/src/features/clinic_card/presentation/widgets/lab_result_records.dart new file mode 100644 index 00000000..0546e315 --- /dev/null +++ b/lib/src/features/clinic_card/presentation/widgets/lab_result_records.dart @@ -0,0 +1,260 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nishauri/src/features/clinic_card/data/models/health_test.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/extensions/extensions.dart'; +import 'package:nishauri/src/shared/helper/health_record_filter.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +import '../../../../shared/display/heath_filter_button.dart'; + +class LabResultHealthRecord extends StatefulWidget { + const LabResultHealthRecord({Key? key}) : super(key: key); + + @override + _LabResultHealthRecord createState() => _LabResultHealthRecord(); +} + +class _LabResultHealthRecord extends State { + // Store the selected filter + DateFilter? selectedFilter; + DateTimeRange? selectedDateRange; + + Future> _loadHealthRecords() async { + final responseString = await rootBundle.loadString('assets/data/visits.json'); + final List json = jsonDecode(responseString); + return json.map((e) => HealthRecordModel.fromJson(e)).toList(); + } + + Future> _applyFilter(List records) async { + List filteredRecords = applyFilter( + records, + selectedFilter ?? DateFilter.all, + selectedDateRange?.start, + selectedDateRange?.end, + ); + + return filteredRecords; + } + + Future _selectDateRange(BuildContext context) async { + DateTimeRange? pickedRange = await selectDateRange( + context: context, + initialDateRange: selectedDateRange, + primaryColor: Constants.bmiCalculatorColor, + barrierColor: Constants.bmiCalculatorColor, + ); + + if (pickedRange != null && pickedRange != selectedDateRange) { + setState(() { + selectedDateRange = pickedRange; + selectedFilter = DateFilter.dateRange; + }); + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + color: Constants.clinicCardBgColor, + height: Constants.SMALL_APP_BAR_HEIGHT, + smallTitle: "Lab Results", + rightBtTitle: "", + ), + Expanded( + child: FutureBuilder>( + future: _loadHealthRecords(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (snapshot.hasData) { + final healthRecords = snapshot.data!; + return SingleChildScrollView( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + children: [ + _buildFilterMenu(theme), + FutureBuilder>( + future: _applyFilter(healthRecords), + builder: (context, filteredSnapshot) { + if (filteredSnapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (filteredSnapshot.hasError) { + return Center(child: Text('Error: ${filteredSnapshot.error}')); + } else if (filteredSnapshot.hasData) { + final filteredRecords = filteredSnapshot.data!; + + // Check if the filtered list is empty and show 'No Data' text + if (filteredRecords.isEmpty) { + return const Center(child: Text('No data available.')); + } + + return Column( + children: filteredRecords + .map((record) => _buildRecordView(record, theme)) + .toList(), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ], + ), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ), + ], + ), + ); + } + + // Record View Widgets and Helpers (No changes here, same as before) + Widget _buildRecordView(HealthRecordModel record, ThemeData theme) { + return Column( + children: [ + _buildDateRow(record.visitDate, theme), + const SizedBox(height: Constants.SPACING), + // _buildHospitalRow(record.facility, theme), + // const SizedBox(height: Constants.SPACING), + // const Divider(), + _buildConditionList(record.labResults, theme), + const SizedBox(height: Constants.SPACING), + const Divider(), + ], + ); + } + + Widget _buildDateRow(String date, ThemeData theme) { + return Row( + children: [ + Expanded( + child: Text( + date, + style: theme.textTheme.titleSmall), + ), + ], + ); + } + + Widget _buildHospitalRow(String hospital, ThemeData theme) { + return Row( + children: [ + Expanded( // Wrap the Text widget in Expanded + child: Text( + hospital, + style: theme.textTheme.titleMedium, + overflow: TextOverflow.ellipsis, // Truncate if too long + ), + ), + ], + ); + } + + Widget _buildConditionList(List labResults, ThemeData theme) { + if (labResults.isEmpty) { + return const Center( + child:Text('No Vitals recorded.') + ); + } + + return Column( + children: labResults.map((labResult) => _buildLabResultRow(labResult, theme)).toList(), + ); + } + + Widget _buildLabResultRow(LabResult labResult, ThemeData theme) { + return ListTile( + title: ExpansionTile( + title: Row( + children: [ + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildLabResultsRow(labResult, theme), + ], + )), + ], + ), + children: [ + ListTile( + title: Column( + children: [ + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Reactions", style: theme.textTheme.bodySmall), + Text(labResult.results, style: theme.textTheme.titleSmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Date Ordered", style: theme.textTheme.bodySmall), + Text(labResult.orderedDate, style: theme.textTheme.titleSmall), + ], + ), + ], + ), + ) + ], + ), + ); + } + + Widget _buildLabResultsRow(LabResult labResult, ThemeData theme) { + return Row( + children: [ + SvgPicture.asset( + "assets/images/boldDuotoneMedicineTestTube.svg", + width: Constants.TWENTY, + height: Constants.TWENTY, + ), + const SizedBox(width: Constants.SPACING), + Expanded( + child:Text(labResult.name, + style: theme.textTheme.titleSmall, + overflow: TextOverflow.ellipsis, // Truncate if too long + maxLines: 1, + ), + ), + ], + ); + } + + // Build the filter menu with the date range option + Widget _buildFilterMenu(ThemeData theme) { + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HealthButton( + onFilterSelected: (filter) { + setState(() { + selectedFilter = filter; + if (filter == DateFilter.dateRange) { + // Trigger the date range picker when the date range filter is selected + _selectDateRange(context); + } + }); + }, + ), + ], + ); + } +} diff --git a/lib/src/features/clinic_card/presentation/widgets/medication.dart b/lib/src/features/clinic_card/presentation/widgets/medication.dart new file mode 100644 index 00000000..27457537 --- /dev/null +++ b/lib/src/features/clinic_card/presentation/widgets/medication.dart @@ -0,0 +1,284 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nishauri/src/features/clinic_card/data/models/health_test.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/heath_filter_button.dart'; +import 'package:nishauri/src/shared/helper/health_record_filter.dart'; +import 'package:nishauri/src/utils/constants.dart'; + + +class MedicationRecord extends StatefulWidget { + const MedicationRecord({super.key}); + + @override + State createState() => _MedicationRecordState(); +} + +class _MedicationRecordState extends State { + + // Store the selected filter + DateFilter? selectedFilter; + DateTimeRange? selectedDateRange; + + // Load health records from a JSON file + Future> _loadHealthRecords() async { + final responseString = await rootBundle.loadString( + 'assets/data/visits.json'); + final List json = jsonDecode(responseString); + return json.map((e) => HealthRecordModel.fromJson(e)).toList(); + } + + // Apply the selected filter to the health records + Future> _applyFilter(List records) async { + List filteredRecords = applyFilter( + records, + selectedFilter ?? DateFilter.all, + selectedDateRange?.start, + selectedDateRange?.end, + ); + + return filteredRecords; + } + + Future _selectDateRange(BuildContext context) async { + DateTimeRange? pickedRange = await selectDateRange( + context: context, + initialDateRange: selectedDateRange, + primaryColor: Constants.bmiCalculatorColor, + barrierColor: Constants.bmiCalculatorColor, + ); + + if (pickedRange != null && pickedRange != selectedDateRange) { + setState(() { + selectedDateRange = pickedRange; + selectedFilter = DateFilter.dateRange; + }); + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + color: Constants.clinicCardBgColor, + height: Constants.SMALL_APP_BAR_HEIGHT, + smallTitle: "Medications", + rightBtTitle: "", + ), + Expanded( + child: FutureBuilder>( + future: _loadHealthRecords(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (snapshot.hasData) { + final healthRecords = snapshot.data!; + return SingleChildScrollView( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + children: [ + _buildFilterMenu(theme), + FutureBuilder>( + future: _applyFilter(healthRecords), + builder: (context, filteredSnapshot) { + if (filteredSnapshot.connectionState == + ConnectionState.waiting) { + return const Center( + child: CircularProgressIndicator()); + } else if (filteredSnapshot.hasError) { + return Center(child: Text( + 'Error: ${filteredSnapshot.error}')); + } else if (filteredSnapshot.hasData) { + final filteredRecords = filteredSnapshot.data!; + + // Check if the filtered list is empty and show 'No Data' text + if (filteredRecords.isEmpty) { + return const Center( + child: Text('No data available.')); + } + + return Column( + children: filteredRecords + .map((record) => + _buildRecordView(record, theme)) + .toList(), + ); + } else { + return const Center( + child: Text('No data available.')); + } + }, + ), + ], + ), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ), + ], + ), + ); + } + + // Record View Widgets and Helpers (No changes here, same as before) + Widget _buildRecordView(HealthRecordModel record, ThemeData theme) { + return Column( + children: [ + _buildDateRow(record.visitDate, theme), + const SizedBox(height: Constants.SPACING), + // _buildHospitalRow(record.facility, theme), + // const SizedBox(height: Constants.SPACING), + // const Divider(), + _buildMedicationList(record.medications, theme), + const SizedBox(height: Constants.SPACING), + const Divider(), + ], + ); + } + + Widget _buildDateRow(String date, ThemeData theme) { + return Row( + children: [ + Expanded( + child: Text( + date, + style: theme.textTheme.titleMedium), + ), + ], + ); + } + + Widget _buildHospitalRow(String hospital, ThemeData theme) { + return Row( + children: [ + Expanded( // Wrap the Text widget in Expanded + child: Text( + hospital, + style: theme.textTheme.titleMedium, + overflow: TextOverflow.ellipsis, // Truncate if too long + ), + ), + ], + ); + } + + Widget _buildMedicationList(List medications, ThemeData theme) { + if (medications.isEmpty) { + return const Center( + child: Text('No Medications recorded.') + ); + } + + return Column( + children: medications.map((medication) => + _buildImmunizationRow(medication, theme)).toList(), + ); + } + + + Widget _buildImmunizationRow(Medication medication, ThemeData theme) { + return ListTile( + title: ExpansionTile( + title: Row( + children: [ + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildImmunizationsRow(medication, theme), + ], + )), + ], + ), + children: [ + ListTile( + title: Column( + children: [ + Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Dose", style: theme.textTheme.titleSmall,), + Text(medication.value, + style: theme.textTheme.titleSmall), + ], + ), + ], + ), + ) + ], + ), + ); + } + + Widget _buildImmunizationsRow(Medication medication, ThemeData theme) { + return Row( + children: [ + SvgPicture.asset( + "assets/images/boldDuotoneMedicineStethoscope.svg", + width: Constants.TWENTY, + height: Constants.TWENTY, + ), + const SizedBox(width: Constants.SPACING), + Expanded( + child: Text(medication.name, + // style: theme.textTheme.titleMedium?.copyWith(fontSize: 8), + style: theme.textTheme.titleSmall, + // Reduce font size + overflow: TextOverflow.ellipsis, + // Truncate if too long + maxLines: 1, + ),), + ], + ); + } + + Color getStatusColor(String status) { + if (status == 'ACTIVE') { + return Constants.programsColor; + } else if (status == 'INACTIVE') { + return Constants.facilityDirectoryColor; + } else { + return Constants.selfScreeningBgColor; + } + } + + // Build the filter menu with the date range option + Widget _buildFilterMenu(ThemeData theme) { + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HealthButton( + onFilterSelected: (filter) { + setState(() { + selectedFilter = filter; + if (filter == DateFilter.dateRange) { + // Trigger the date range picker when the date range filter is selected + _selectDateRange(context); + } + }); + }, + ), + // if (selectedDateRange != null) + // Padding( + // padding: const EdgeInsets.only(left: Constants.SPACING), + // child: Text( + // 'From ${selectedDateRange!.start.toLocal()} - ${selectedDateRange!.end.toLocal()}', + // style: theme.textTheme.bodyMedium, + // ), + // ), + ], + ); + } +} diff --git a/lib/src/features/clinic_card/presentation/widgets/vital_records.dart b/lib/src/features/clinic_card/presentation/widgets/vital_records.dart new file mode 100644 index 00000000..48030b27 --- /dev/null +++ b/lib/src/features/clinic_card/presentation/widgets/vital_records.dart @@ -0,0 +1,307 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nishauri/src/features/clinic_card/data/models/health_test.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/helper/health_record_filter.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +import '../../../../shared/display/heath_filter_button.dart'; + +class VitalHealthRecord extends StatefulWidget { + const VitalHealthRecord({Key? key}) : super(key: key); + + @override + _VitalHealthRecord createState() => _VitalHealthRecord(); +} + +class _VitalHealthRecord extends State { + // Store the selected filter + DateFilter? selectedFilter; + DateTimeRange? selectedDateRange; + + Future> _loadHealthRecords() async { + final responseString = await rootBundle.loadString('assets/data/visits.json'); + final List json = jsonDecode(responseString); + return json.map((e) => HealthRecordModel.fromJson(e)).toList(); + } + + Future> _applyFilter(List records) async { + List filteredRecords = applyFilter( + records, + selectedFilter ?? DateFilter.all, + selectedDateRange?.start, + selectedDateRange?.end, + ); + + return filteredRecords; + } + + Future _selectDateRange(BuildContext context) async { + DateTimeRange? pickedRange = await selectDateRange( + context: context, + initialDateRange: selectedDateRange, + primaryColor: Constants.bmiCalculatorColor, + barrierColor: Constants.bmiCalculatorColor, + ); + + if (pickedRange != null && pickedRange != selectedDateRange) { + setState(() { + selectedDateRange = pickedRange; + selectedFilter = DateFilter.dateRange; + }); + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + color: Constants.clinicCardBgColor, + height: Constants.SMALL_APP_BAR_HEIGHT, + smallTitle: "Vitals", + rightBtTitle: "", + ), + Expanded( + child: FutureBuilder>( + future: _loadHealthRecords(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (snapshot.hasData) { + final healthRecords = snapshot.data!; + return SingleChildScrollView( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + children: [ + _buildFilterMenu(theme), + FutureBuilder>( + future: _applyFilter(healthRecords), + builder: (context, filteredSnapshot) { + if (filteredSnapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (filteredSnapshot.hasError) { + return Center(child: Text('Error: ${filteredSnapshot.error}')); + } else if (filteredSnapshot.hasData) { + final filteredRecords = filteredSnapshot.data!; + + // Check if the filtered list is empty and show 'No Data' text + if (filteredRecords.isEmpty) { + return const Center(child: Text('No data available.')); + } + + return Column( + children: filteredRecords + .map((record) => _buildRecordView(record, theme)) + .toList(), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ], + ), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ), + ], + ), + ); + } + + // Record View Widgets and Helpers (No changes here, same as before) + Widget _buildRecordView(HealthRecordModel record, ThemeData theme) { + return Column( + children: [ + _buildDateRow(record.visitDate, theme), + const SizedBox(height: Constants.SPACING), + // _buildHospitalRow(record.facility, theme), + // const SizedBox(height: Constants.SPACING), + // const Divider(), + _buildVitalList(record.vitals, theme), + const SizedBox(height: Constants.SPACING), + const Divider(), + ], + ); + } + + Widget _buildDateRow(String date, ThemeData theme) { + return Row( + children: [ + Expanded( + child: Text( + date, + style: theme.textTheme.titleSmall), + ), + ], + ); + } + + Widget _buildHospitalRow(String hospital, ThemeData theme) { + return Row( + children: [ + Expanded( // Wrap the Text widget in Expanded + child: Text( + hospital, + style: theme.textTheme.titleMedium, + overflow: TextOverflow.ellipsis, // Truncate if too long + ), + ), + ], + ); + } + + Widget _buildVitalList(List vitals, ThemeData theme) { + if (vitals.isEmpty) { + return const Center( + child:Text('No Vitals recorded.') + ); + } + + return Column( + children: vitals.map((vital) => _buildVitalRow(vital, theme)).toList(), + ); + } + + Widget _buildVitalRow(Vital vital, ThemeData theme) { + return ListTile( + title: ExpansionTile( + title: Row( + children: [ + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildVitalsRow(vital, theme), + ], + )), + ], + ), + children: [ + ListTile( + title: Column( + children: [ + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Temperature", style: theme.textTheme.bodySmall), + Text(vital.temp, style: theme.textTheme.bodySmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Systolic B.P", style: theme.textTheme.bodySmall), + Text(vital.systolic, style: theme.textTheme.bodySmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Diastolic B.P", style: theme.textTheme.bodySmall), + Text(vital.diastolic, style: theme.textTheme.bodySmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Respiratory Rate", style: theme.textTheme.bodySmall), + Text(vital.respiratory, style: theme.textTheme.bodySmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Oxygen Saturation", style: theme.textTheme.bodySmall), + Text(vital.oxygenSaturation, style: theme.textTheme.bodySmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Weight", style: theme.textTheme.bodySmall), + Text(vital.weight, style: theme.textTheme.bodySmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Height", style: theme.textTheme.bodySmall), + Text(vital.height, style: theme.textTheme.bodySmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Presenting Complaints", style: theme.textTheme.bodySmall), + Text(vital.complain, style: theme.textTheme.bodySmall), + ], + ), + ], + ), + ) + ], + ), + ); + } + + Widget _buildVitalsRow(Vital vital, ThemeData theme) { + return Row( + children: [ + SvgPicture.asset( + "assets/images/boldDuotoneMedicineStethoscope.svg", + width: Constants.TWENTY, + height: Constants.TWENTY, + ), + const SizedBox(width: Constants.SPACING), + Expanded( + child:Text(vital.name, + style: theme.textTheme.titleSmall, + overflow: TextOverflow.ellipsis, // Truncate if too long + maxLines: 1, + ), + ), + ], + ); + } + + // Build the filter menu with the date range option + Widget _buildFilterMenu(ThemeData theme) { + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HealthButton( + onFilterSelected: (filter) { + setState(() { + selectedFilter = filter; + if (filter == DateFilter.dateRange) { + // Trigger the date range picker when the date range filter is selected + _selectDateRange(context); + } + }); + }, + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/src/features/clinic_card/relationship/presentation/pages/dependant_profile.dart b/lib/src/features/clinic_card/relationship/presentation/pages/dependant_profile.dart new file mode 100644 index 00000000..bbd2cb8e --- /dev/null +++ b/lib/src/features/clinic_card/relationship/presentation/pages/dependant_profile.dart @@ -0,0 +1,208 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nishauri/src/features/clinic_card/data/providers/programProvider.dart'; +import 'package:nishauri/src/features/self_screening/presentation/widgets/health_list.dart'; +import 'package:nishauri/src/features/user_programs/data/providers/program_provider.dart'; +import 'package:nishauri/src/features/visits/data/providers/visits_provider.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/background_image_widget.dart'; +import 'package:nishauri/src/shared/display/profile_app_bar.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +class DependantProfileScreen extends HookConsumerWidget { + const DependantProfileScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = Theme.of(context); + final programAsync = ref.watch(programProvider); + final userPrograms = ref.watch(userProgramProvider); + final visitAsync = ref.watch(visitProvider); + + final visits = visitAsync.when( + data: (data) { + return data; // This is a List + }, + error: (error, _) { + print("Error occurred: $error"); + return []; + }, + loading: () { + return []; + }, + ); + +// Check if visits is not empty before accessing properties + if (visits.isNotEmpty) { + // Print labResults for each visit + for (var visit in visits) { + print(visit.labResults); + } + + // Alternatively, if you only want the labResults from the first visit: + print(visits.first.labResults); + } else { + print("No visit data available."); + } + + + final Map svgMapping = { + "allergies": "assets/images/boldDuotoneMedicineVirus.svg", + "conditions": "assets/images/boldDuotoneMedicineStethoscope.svg", + "immunization": "assets/images/boldDuotoneMedicineSyringe.svg", + "labResults": "assets/images/boldDuotoneMedicineTestTube.svg", + "medications": "assets/images/boldDuotoneMedicineJarOfPills2.svg", + "procedures": "assets/images/boldDuotoneMedicineBone.svg", + "vitals": "assets/images/boldDuotoneMedicineHeartPulse2.svg" + }; + + final List items = [ + "Allergies", + "Conditions", + "Immunizations", + "Lab Results", + "Medications", + "Procedures", + "Vitals" + ]; + + final List icons = [ + "assets/images/boldDuotoneMedicineVirus.svg", + "assets/images/boldDuotoneMedicineStethoscope.svg", + "assets/images/boldDuotoneMedicineSyringe.svg", + "assets/images/boldDuotoneMedicineTestTube.svg", + "assets/images/boldDuotoneMedicineJarOfPills2.svg", + "assets/images/boldDuotoneMedicineBone.svg", + "assets/images/boldDuotoneMedicineHeartPulse2.svg" + ]; + + final List paths = [ + RouteNames.ALLERGY_HEALTH_RSHIP_RECORD, + RouteNames.HEALTH_RSHIP_RECORD, + RouteNames.IMMUNIZATION_RSHIP_RECORD, + RouteNames.LAB_RESULTS_HEALTH_RSHIP_RECORD, + RouteNames.MEDICATION_RSHIP_RECORD, + RouteNames.PROCEDURE_RSHIP_RECORD, + RouteNames.VITAL_HEALTH_RSHIP_RECORD, + + ]; + + void _reloadData() { + ref.refresh(programProvider); + ref.refresh(userProgramProvider); + } + + return programAsync.when( + data: (data) { + final activePrograms = data.where((program) { + return userPrograms.hasValue && userPrograms.value!.any( + (userProgram) => userProgram.program_name == program.name && userProgram.isActive + ); + }); + + if (activePrograms.isEmpty) { + return _buildEmptyState(context, _reloadData); + } + + return Scaffold( + body: Column( + children: [ + const ProfileAppBar( + title: "👨🏾‍ Eric Muthomi", + color: Constants.clinicCardBgColor, + subTitle: "Relationship: Child", + age: "Age: 8 Years", + address: "Address: Ole Dume Road", + svgPathGroup: "assets/images/group_clinic_card.svg", + ), + Expanded( + child: SingleChildScrollView( + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [Text("Health Records", style: theme.textTheme.titleMedium), + ] + ), + const SizedBox(height: 10), + ItemList(items: items, path: paths, svgAsset: icons), + const SizedBox(height: 10), + ], + ), + ), + ], + ) + ), + ), + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: _reloadData, + child: const Icon(Icons.refresh), + tooltip: 'Refresh Data', + ), + ); + }, + error: (error, _) => _buildErrorState(context, error.toString(), _reloadData), + loading: () => _buildLoadingState(context, theme), + ); + } + + Widget _buildEmptyState(BuildContext context, VoidCallback reload) { + return BackgroundImageWidget( + customAppBar: const CustomAppBar( + title: "My Clinic Card 👨🏾‍💼", + color: Constants.clinicCardBgColor, + subTitle: "Access all your medical details", + ), + svgImage: 'assets/images/lab-empty-state.svg', + notFoundText: "No programs available", + floatingButtonIcon1: Icons.refresh, + floatingButtonAction1: reload, + ); + } + + Widget _buildErrorState(BuildContext context, String error, VoidCallback reload) { + return BackgroundImageWidget( + customAppBar: const CustomAppBar( + title: "My Clinic Card 👨🏾‍💼", + color: Constants.clinicCardBgColor, + subTitle: "Access all your medical details", + ), + svgImage: 'assets/images/lab-empty-state.svg', + notFoundText: error, + floatingButtonIcon1: Icons.refresh, + floatingButtonAction1: reload, + ); + } + + Widget _buildLoadingState(BuildContext context, ThemeData theme) { + return Scaffold( + body: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const CustomAppBar( + title: "My Clinic Card 👨🏾‍💼", + color: Constants.clinicCardBgColor, + subTitle: "Access all your medical details", + ), + Text( + "Loading Programs", + style: theme.textTheme.headline6, + ), + const SizedBox(height: Constants.SPACING * 2), + const CircularProgressIndicator(), + ], + ), + ); + } +} diff --git a/lib/src/features/clinic_card/relationship/widgets/allergy_rship_records.dart b/lib/src/features/clinic_card/relationship/widgets/allergy_rship_records.dart new file mode 100644 index 00000000..371fd24a --- /dev/null +++ b/lib/src/features/clinic_card/relationship/widgets/allergy_rship_records.dart @@ -0,0 +1,275 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nishauri/src/features/clinic_card/data/models/health_test.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/extensions/extensions.dart'; +import 'package:nishauri/src/shared/helper/health_record_filter.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +import '../../../../shared/display/heath_filter_button.dart'; +class AllergyRshipRecord extends StatefulWidget { + const AllergyRshipRecord({super.key}); + + @override + State createState() => _AllergyRshipRecordState(); +} + +class _AllergyRshipRecordState extends State { + // Store the selected filter + DateFilter? selectedFilter; + DateTimeRange? selectedDateRange; + + Future> _loadHealthRecords() async { + final responseString = await rootBundle.loadString('assets/data/visits.json'); + final List json = jsonDecode(responseString); + return json.map((e) => HealthRecordModel.fromJson(e)).toList(); + } + + Future> _applyFilter(List records) async { + List filteredRecords = applyFilter( + records, + selectedFilter ?? DateFilter.all, + selectedDateRange?.start, + selectedDateRange?.end, + ); + + return filteredRecords; + } + + Future _selectDateRange(BuildContext context) async { + DateTimeRange? pickedRange = await selectDateRange( + context: context, + initialDateRange: selectedDateRange, + primaryColor: Constants.bmiCalculatorColor, + barrierColor: Constants.bmiCalculatorColor, + ); + + if (pickedRange != null && pickedRange != selectedDateRange) { + setState(() { + selectedDateRange = pickedRange; + selectedFilter = DateFilter.dateRange; + }); + } + } + + + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + color: Constants.clinicCardBgColor, + height: Constants.SMALL_APP_BAR_HEIGHT, + smallTitle: "Allergies", + rightBtTitle: "", + ), + Expanded( + child: FutureBuilder>( + future: _loadHealthRecords(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (snapshot.hasData) { + final healthRecords = snapshot.data!; + return SingleChildScrollView( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + children: [ + _buildFilterMenu(theme), + FutureBuilder>( + future: _applyFilter(healthRecords), + builder: (context, filteredSnapshot) { + if (filteredSnapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (filteredSnapshot.hasError) { + return Center(child: Text('Error: ${filteredSnapshot.error}')); + } else if (filteredSnapshot.hasData) { + final filteredRecords = filteredSnapshot.data!; + + // Check if the filtered list is empty and show 'No Data' text + if (filteredRecords.isEmpty) { + return const Center(child: Text('No data available.')); + } + + return Column( + children: filteredRecords + .map((record) => _buildRecordView(record, theme)) + .toList(), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ], + ), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ), + ], + ), + ); + } + + // Record View Widgets and Helpers (No changes here, same as before) + Widget _buildRecordView(HealthRecordModel record, ThemeData theme) { + return Column( + children: [ + _buildDateRow(record.visitDate, theme), + const SizedBox(height: Constants.SPACING), + // _buildHospitalRow(record.facility, theme), + // const SizedBox(height: Constants.SPACING), + // const Divider(), + _buildAllergyList(record.allergies, theme), + const SizedBox(height: Constants.SPACING), + const Divider(), + ], + ); + } + + Widget _buildDateRow(String date, ThemeData theme) { + return Row( + children: [ + Expanded( + child: Text( + date, + style: theme.textTheme.titleSmall), + ), + ], + ); + } + + Widget _buildHospitalRow(String hospital, ThemeData theme) { + return Row( + children: [ + Expanded( // Wrap the Text widget in Expanded + child: Text( + hospital, + style: theme.textTheme.titleMedium, + overflow: TextOverflow.ellipsis, // Truncate if too long + ), + ), + ], + ); + } + + Widget _buildAllergyList(List allergies, ThemeData theme) { + if (allergies.isEmpty) { + return const Center( + child:Text('No Vitals recorded.') + ); + } + + return Column( + children: allergies.map((allergy) => _buildAllergyRow(allergy, theme)).toList(), + ); + } + + Widget _buildAllergyRow(Allergy allergy, ThemeData theme) { + return ListTile( + title: ExpansionTile( + title: Row( + children: [ + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildAllergiesRow(allergy, theme), + ], + )), + ], + ), + children: [ + ListTile( + title: Column( + children: [ + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Reactions", style: theme.textTheme.bodySmall), + Text(allergy.reaction, style: theme.textTheme.bodySmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Onset Date", style: theme.textTheme.bodySmall), + Text(allergy.onsetDate, style: theme.textTheme.titleSmall), + ], + ), + ], + ), + ) + ], + ), + ); + } + + Widget _buildAllergiesRow(Allergy allergy, ThemeData theme) { + return Row( + children: [ + SvgPicture.asset( + "assets/images/boldDuotoneMedicineVirus.svg", + width: Constants.TWENTY, + height: Constants.TWENTY, + ), + const SizedBox(width: Constants.SPACING), + Expanded( + child:Text(allergy.allergen, + style: theme.textTheme.titleSmall, + overflow: TextOverflow.ellipsis, // Truncate if too long + maxLines: 1, + ), + ), + const Spacer(), + Text(allergy.severity.titleCase, style: theme.textTheme.bodySmall?.copyWith(color: getSeverityColor(allergy.severity), )), + ], + ); + } + + Color getSeverityColor(String severity) { + if (severity == 'MILD') { + return Constants.programsColor; + } else if (severity == 'MODERATE') { + return Constants.facilityDirectoryColor; + } else if (severity == 'SEVERE') { + return Constants.selfScreeningBgColor; + } else { + return Constants.selfScreeningBgColor; + } + } + + // Build the filter menu with the date range option + Widget _buildFilterMenu(ThemeData theme) { + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HealthButton( + onFilterSelected: (filter) { + setState(() { + selectedFilter = filter; + if (filter == DateFilter.dateRange) { + // Trigger the date range picker when the date range filter is selected + _selectDateRange(context); + } + }); + }, + ), + ], + ); + } +} diff --git a/lib/src/features/clinic_card/relationship/widgets/condition_rship.dart b/lib/src/features/clinic_card/relationship/widgets/condition_rship.dart new file mode 100644 index 00000000..551d33b9 --- /dev/null +++ b/lib/src/features/clinic_card/relationship/widgets/condition_rship.dart @@ -0,0 +1,273 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nishauri/src/features/clinic_card/data/models/health_test.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/heath_filter_button.dart'; +import 'package:nishauri/src/shared/extensions/extensions.dart'; +import 'package:nishauri/src/shared/helper/health_record_filter.dart'; +import 'package:nishauri/src/utils/constants.dart'; + + +class ConditionRship extends StatefulWidget { + const ConditionRship({super.key}); + + @override + State createState() => _ConditionRshipState(); +} + +class _ConditionRshipState extends State { + // Store the selected filter + DateFilter? selectedFilter; + DateTimeRange? selectedDateRange; + + // Load health records from a JSON file + Future> _loadHealthRecords() async { + final responseString = await rootBundle.loadString('assets/data/visits.json'); + final List json = jsonDecode(responseString); + return json.map((e) => HealthRecordModel.fromJson(e)).toList(); + } + + // Apply the selected filter to the health records + Future> _applyFilter(List records) async { + List filteredRecords = applyFilter( + records, + selectedFilter ?? DateFilter.all, + selectedDateRange?.start, + selectedDateRange?.end, + ); + + return filteredRecords; + } + + // Function to handle date range selection + Future _selectDateRange(BuildContext context) async { + DateTimeRange? pickedRange = await selectDateRange( + context: context, + initialDateRange: selectedDateRange, + primaryColor: Constants.bmiCalculatorColor, + barrierColor: Constants.bmiCalculatorColor, + ); + + if (pickedRange != null && pickedRange != selectedDateRange) { + setState(() { + selectedDateRange = pickedRange; + selectedFilter = DateFilter.dateRange; + }); + } + } + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + color: Constants.clinicCardBgColor, + height: Constants.SMALL_APP_BAR_HEIGHT, + smallTitle: "Conditions", + rightBtTitle: "", + ), + Expanded( + child: FutureBuilder>( + future: _loadHealthRecords(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (snapshot.hasData) { + final healthRecords = snapshot.data!; + return SingleChildScrollView( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + children: [ + _buildFilterMenu(theme), + FutureBuilder>( + future: _applyFilter(healthRecords), + builder: (context, filteredSnapshot) { + if (filteredSnapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (filteredSnapshot.hasError) { + return Center(child: Text('Error: ${filteredSnapshot.error}')); + } else if (filteredSnapshot.hasData) { + final filteredRecords = filteredSnapshot.data!; + + // Check if the filtered list is empty and show 'No Data' text + if (filteredRecords.isEmpty) { + return const Center(child: Text('No data available.')); + } + + return Column( + children: filteredRecords + .map((record) => _buildRecordView(record, theme)) + .toList(), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ], + ), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ), + ], + ), + ); + } + + // Record View Widgets and Helpers (No changes here, same as before) + Widget _buildRecordView(HealthRecordModel record, ThemeData theme) { + return Column( + children: [ + _buildDateRow(record.visitDate, theme), + const SizedBox(height: Constants.SPACING), + // _buildHospitalRow(record.facility, theme), + // const SizedBox(height: Constants.SPACING), + // const Divider(), + _buildConditionList(record.conditions, theme), + const SizedBox(height: Constants.SPACING), + const Divider(), + ], + ); + } + + Widget _buildDateRow(String date, ThemeData theme) { + return Row( + children: [ + Expanded( + child: Text( + date, + style: theme.textTheme.titleSmall), + ), + ], + ); + } + + Widget _buildHospitalRow(String hospital, ThemeData theme) { + return Row( + children: [ + Expanded( // Wrap the Text widget in Expanded + child: Text( + hospital, + style: theme.textTheme.titleMedium, + overflow: TextOverflow.ellipsis, // Truncate if too long + ), + ), + ], + ); + } + + Widget _buildConditionList(List conditions, ThemeData theme) { + if (conditions.isEmpty) { + return const Center( + child:Text('No conditions recorded.') + ); + } + + return Column( + children: conditions.map((condition) => _buildConditionRow(condition, theme)).toList(), + ); + } + + Widget _buildConditionRow(Condition condition, ThemeData theme) { + return ListTile( + title: ExpansionTile( + title: Row( + children: [ + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildConditionsRow(condition, theme), + ], + )), + ], + ), + children: [ + ListTile( + title: Column( + children: [ + Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Recorded", style: theme.textTheme.bodySmall), + Text(condition.dateRecorded, style: theme.textTheme.bodySmall), + ], + ), + ], + ), + ) + ], + ), + ); + } + + Widget _buildConditionsRow(Condition condition, ThemeData theme) { + return Row( + children: [ + SvgPicture.asset( + "assets/images/boldDuotoneMedicineStethoscope.svg", + width: Constants.TWENTY, + height: Constants.TWENTY, + ), + const SizedBox(width: Constants.SPACING), + Expanded( + child:Text(condition.name, + style: theme.textTheme.titleSmall, + overflow: TextOverflow.ellipsis, // Truncate if too long + maxLines: 1, + ),), + const Spacer(), + Text(condition.status.titleCase, style: theme.textTheme.bodySmall?.copyWith(color: getStatusColor(condition.status))), + ], + ); + } + + Color getStatusColor(String status) { + if (status == 'ACTIVE') { + return Constants.programsColor; + } else if (status == 'INACTIVE') { + return Constants.facilityDirectoryColor; + } else { + return Constants.selfScreeningBgColor; + } + } + + // Build the filter menu with the date range option + Widget _buildFilterMenu(ThemeData theme) { + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HealthButton( + onFilterSelected: (filter) { + setState(() { + selectedFilter = filter; + if (filter == DateFilter.dateRange) { + // Trigger the date range picker when the date range filter is selected + _selectDateRange(context); + } + }); + }, + ), + // if (selectedDateRange != null) + // Padding( + // padding: const EdgeInsets.only(left: Constants.SPACING), + // child: Text( + // 'From ${selectedDateRange!.start.toLocal()} - ${selectedDateRange!.end.toLocal()}', + // style: theme.textTheme.bodyMedium, + // ), + // ), + ], + ); + } +} diff --git a/lib/src/features/clinic_card/relationship/widgets/immunization_rship.dart b/lib/src/features/clinic_card/relationship/widgets/immunization_rship.dart new file mode 100644 index 00000000..62cf2000 --- /dev/null +++ b/lib/src/features/clinic_card/relationship/widgets/immunization_rship.dart @@ -0,0 +1,279 @@ + +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../../../shared/display/CustomAppBar.dart'; +import '../../../../shared/display/heath_filter_button.dart'; +import '../../../../utils/constants.dart'; +import '../../data/models/health_test.dart'; +import 'package:nishauri/src/shared/helper/health_record_filter.dart'; + +class ImmunizationRship extends StatefulWidget { + const ImmunizationRship ({super.key}); + + @override + State createState() => _ImmunizationRshipState(); +} + +class _ImmunizationRshipState extends State { + // Store the selected filter + DateFilter? selectedFilter; + DateTimeRange? selectedDateRange; + + + + + // Load health records from a JSON file + Future> _loadHealthRecords() async { + final responseString = await rootBundle.loadString( + 'assets/data/visits.json'); + final List json = jsonDecode(responseString); + return json.map((e) => HealthRecordModel.fromJson(e)).toList(); + } + + // Apply the selected filter to the health records + Future> _applyFilter(List records) async { + List filteredRecords = applyFilter( + records, + selectedFilter ?? DateFilter.all, + selectedDateRange?.start, + selectedDateRange?.end, + ); + + return filteredRecords; + } + + // Function to handle date range selection + Future _selectDateRange(BuildContext context) async { + DateTimeRange? pickedRange = await selectDateRange( + context: context, + initialDateRange: selectedDateRange, + primaryColor: Constants.bmiCalculatorColor, + barrierColor: Constants.bmiCalculatorColor, + ); + + if (pickedRange != null && pickedRange != selectedDateRange) { + setState(() { + selectedDateRange = pickedRange; + selectedFilter = DateFilter.dateRange; + }); + } + } + + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + color: Constants.clinicCardBgColor, + height: Constants.SMALL_APP_BAR_HEIGHT, + smallTitle: "Immunizations", + rightBtTitle: "", + ), + Expanded( + child: FutureBuilder>( + future: _loadHealthRecords(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (snapshot.hasData) { + final healthRecords = snapshot.data!; + return SingleChildScrollView( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + children: [ + _buildFilterMenu(theme), + FutureBuilder>( + future: _applyFilter(healthRecords), + builder: (context, filteredSnapshot) { + if (filteredSnapshot.connectionState == + ConnectionState.waiting) { + return const Center( + child: CircularProgressIndicator()); + } else if (filteredSnapshot.hasError) { + return Center(child: Text( + 'Error: ${filteredSnapshot.error}')); + } else if (filteredSnapshot.hasData) { + final filteredRecords = filteredSnapshot.data!; + + // Check if the filtered list is empty and show 'No Data' text + if (filteredRecords.isEmpty) { + return const Center( + child: Text('No data available.')); + } + + return Column( + children: filteredRecords + .map((record) => + _buildRecordView(record, theme)) + .toList(), + ); + } else { + return const Center( + child: Text('No data available.')); + } + }, + ), + ], + ), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ), + ], + ), + ); + } + + // Record View Widgets and Helpers (No changes here, same as before) + Widget _buildRecordView(HealthRecordModel record, ThemeData theme) { + return Column( + children: [ + _buildDateRow(record.visitDate, theme), + const SizedBox(height: Constants.SPACING), + // _buildHospitalRow(record.facility, theme), + // const SizedBox(height: Constants.SPACING), + // const Divider(), + _buildConditionList(record.immunizations, theme), + const SizedBox(height: Constants.SPACING), + const Divider(), + ], + ); + } + + Widget _buildDateRow(String date, ThemeData theme) { + return Row( + children: [ + Expanded( + child: Text( + date, + style: theme.textTheme.titleSmall), + ), + ], + ); + } + + Widget _buildHospitalRow(String hospital, ThemeData theme) { + return Row( + children: [ + Expanded( // Wrap the Text widget in Expanded + child: Text( + hospital, + style: theme.textTheme.titleMedium, + overflow: TextOverflow.ellipsis, // Truncate if too long + ), + ), + ], + ); + } + + Widget _buildConditionList(List immunizations, ThemeData theme) { + if (immunizations.isEmpty) { + return const Center( + child: Text('No immunizations recorded.') + ); + } + + return Column( + children: immunizations.map((immunization) => + _buildImmunizationRow(immunization, theme)).toList(), + ); + } + + + Widget _buildImmunizationRow(Immunization immunization, ThemeData theme) { + return ListTile( + title: ExpansionTile( + title: Row( + children: [ + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildImmunizationsRow(immunization, theme), + ], + )), + ], + ), + children: [ + ListTile( + title: Column( + children: [ + Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Vaccination Date", style: theme.textTheme.bodySmall), + Text(immunization.immunizationDate, + style: theme.textTheme.bodySmall), + ], + ), + ], + ), + ) + ], + ), + ); + } + + Widget _buildImmunizationsRow(Immunization immunization, ThemeData theme) { + return Row( + children: [ + SvgPicture.asset( + "assets/images/boldDuotoneMedicineStethoscope.svg", + width: Constants.TWENTY, + height: Constants.TWENTY, + ), + const SizedBox(width: Constants.SPACING), + Expanded( + child: Text(immunization.name, + style: theme.textTheme.titleSmall, + // Reduce font size + overflow: TextOverflow.ellipsis, + // Truncate if too long + maxLines: 1, + ),), + ], + ); + } + + // Build the filter menu with the date range option + Widget _buildFilterMenu(ThemeData theme) { + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HealthButton( + onFilterSelected: (filter) { + setState(() { + selectedFilter = filter; + if (filter == DateFilter.dateRange) { + // Trigger the date range picker when the date range filter is selected + _selectDateRange(context); + } + }); + }, + ), + // if (selectedDateRange != null) + // Padding( + // padding: const EdgeInsets.only(left: Constants.SPACING), + // child: Text( + // 'From ${selectedDateRange!.start.toLocal()} - ${selectedDateRange!.end.toLocal()}', + // style: theme.textTheme.bodyMedium, + // ), + // ), + ], + ); + } +} diff --git a/lib/src/features/clinic_card/relationship/widgets/lab_rship.dart b/lib/src/features/clinic_card/relationship/widgets/lab_rship.dart new file mode 100644 index 00000000..fadd9bbe --- /dev/null +++ b/lib/src/features/clinic_card/relationship/widgets/lab_rship.dart @@ -0,0 +1,259 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nishauri/src/features/clinic_card/data/models/health_test.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/extensions/extensions.dart'; +import 'package:nishauri/src/shared/helper/health_record_filter.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +import '../../../../shared/display/heath_filter_button.dart'; +class LabRshpRecord extends StatefulWidget { + const LabRshpRecord({super.key}); + + @override + State createState() => _LabRshpRecordState(); +} + +class _LabRshpRecordState extends State { + // Store the selected filter + DateFilter? selectedFilter; + DateTimeRange? selectedDateRange; + + Future> _loadHealthRecords() async { + final responseString = await rootBundle.loadString('assets/data/visits.json'); + final List json = jsonDecode(responseString); + return json.map((e) => HealthRecordModel.fromJson(e)).toList(); + } + + Future> _applyFilter(List records) async { + List filteredRecords = applyFilter( + records, + selectedFilter ?? DateFilter.all, + selectedDateRange?.start, + selectedDateRange?.end, + ); + + return filteredRecords; + } + + Future _selectDateRange(BuildContext context) async { + DateTimeRange? pickedRange = await selectDateRange( + context: context, + initialDateRange: selectedDateRange, + primaryColor: Constants.bmiCalculatorColor, + barrierColor: Constants.bmiCalculatorColor, + ); + + if (pickedRange != null && pickedRange != selectedDateRange) { + setState(() { + selectedDateRange = pickedRange; + selectedFilter = DateFilter.dateRange; + }); + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + color: Constants.clinicCardBgColor, + height: Constants.SMALL_APP_BAR_HEIGHT, + smallTitle: "Lab Results", + rightBtTitle: "", + ), + Expanded( + child: FutureBuilder>( + future: _loadHealthRecords(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (snapshot.hasData) { + final healthRecords = snapshot.data!; + return SingleChildScrollView( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + children: [ + _buildFilterMenu(theme), + FutureBuilder>( + future: _applyFilter(healthRecords), + builder: (context, filteredSnapshot) { + if (filteredSnapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (filteredSnapshot.hasError) { + return Center(child: Text('Error: ${filteredSnapshot.error}')); + } else if (filteredSnapshot.hasData) { + final filteredRecords = filteredSnapshot.data!; + + // Check if the filtered list is empty and show 'No Data' text + if (filteredRecords.isEmpty) { + return const Center(child: Text('No data available.')); + } + + return Column( + children: filteredRecords + .map((record) => _buildRecordView(record, theme)) + .toList(), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ], + ), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ), + ], + ), + ); + } + + // Record View Widgets and Helpers (No changes here, same as before) + Widget _buildRecordView(HealthRecordModel record, ThemeData theme) { + return Column( + children: [ + _buildDateRow(record.visitDate, theme), + const SizedBox(height: Constants.SPACING), + // _buildHospitalRow(record.facility, theme), + // const SizedBox(height: Constants.SPACING), + // const Divider(), + _buildConditionList(record.labResults, theme), + const SizedBox(height: Constants.SPACING), + const Divider(), + ], + ); + } + + Widget _buildDateRow(String date, ThemeData theme) { + return Row( + children: [ + Expanded( + child: Text( + date, + style: theme.textTheme.titleSmall), + ), + ], + ); + } + + Widget _buildHospitalRow(String hospital, ThemeData theme) { + return Row( + children: [ + Expanded( // Wrap the Text widget in Expanded + child: Text( + hospital, + style: theme.textTheme.titleMedium, + overflow: TextOverflow.ellipsis, // Truncate if too long + ), + ), + ], + ); + } + + Widget _buildConditionList(List labResults, ThemeData theme) { + if (labResults.isEmpty) { + return const Center( + child:Text('No Vitals recorded.') + ); + } + + return Column( + children: labResults.map((labResult) => _buildLabResultRow(labResult, theme)).toList(), + ); + } + + Widget _buildLabResultRow(LabResult labResult, ThemeData theme) { + return ListTile( + title: ExpansionTile( + title: Row( + children: [ + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildLabResultsRow(labResult, theme), + ], + )), + ], + ), + children: [ + ListTile( + title: Column( + children: [ + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Reactions", style: theme.textTheme.bodySmall), + Text(labResult.results, style: theme.textTheme.titleSmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Date Ordered", style: theme.textTheme.bodySmall), + Text(labResult.orderedDate, style: theme.textTheme.titleSmall), + ], + ), + ], + ), + ) + ], + ), + ); + } + + Widget _buildLabResultsRow(LabResult labResult, ThemeData theme) { + return Row( + children: [ + SvgPicture.asset( + "assets/images/boldDuotoneMedicineTestTube.svg", + width: Constants.TWENTY, + height: Constants.TWENTY, + ), + const SizedBox(width: Constants.SPACING), + Expanded( + child:Text(labResult.name, + style: theme.textTheme.titleSmall, + overflow: TextOverflow.ellipsis, // Truncate if too long + maxLines: 1, + ), + ), + ], + ); + } + + // Build the filter menu with the date range option + Widget _buildFilterMenu(ThemeData theme) { + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HealthButton( + onFilterSelected: (filter) { + setState(() { + selectedFilter = filter; + if (filter == DateFilter.dateRange) { + // Trigger the date range picker when the date range filter is selected + _selectDateRange(context); + } + }); + }, + ), + ], + ); + } +} diff --git a/lib/src/features/clinic_card/relationship/widgets/medication_rship.dart b/lib/src/features/clinic_card/relationship/widgets/medication_rship.dart new file mode 100644 index 00000000..62445c9a --- /dev/null +++ b/lib/src/features/clinic_card/relationship/widgets/medication_rship.dart @@ -0,0 +1,282 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nishauri/src/features/clinic_card/data/models/health_test.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/heath_filter_button.dart'; +import 'package:nishauri/src/shared/helper/health_record_filter.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class MedicationRship extends StatefulWidget { + const MedicationRship({super.key}); + + @override + State createState() => _MedicationRshipState(); +} + +class _MedicationRshipState extends State { + // Store the selected filter + DateFilter? selectedFilter; + DateTimeRange? selectedDateRange; + + // Load health records from a JSON file + Future> _loadHealthRecords() async { + final responseString = await rootBundle.loadString( + 'assets/data/visits.json'); + final List json = jsonDecode(responseString); + return json.map((e) => HealthRecordModel.fromJson(e)).toList(); + } + + // Apply the selected filter to the health records + Future> _applyFilter(List records) async { + List filteredRecords = applyFilter( + records, + selectedFilter ?? DateFilter.all, + selectedDateRange?.start, + selectedDateRange?.end, + ); + + return filteredRecords; + } + + Future _selectDateRange(BuildContext context) async { + DateTimeRange? pickedRange = await selectDateRange( + context: context, + initialDateRange: selectedDateRange, + primaryColor: Constants.bmiCalculatorColor, + barrierColor: Constants.bmiCalculatorColor, + ); + + if (pickedRange != null && pickedRange != selectedDateRange) { + setState(() { + selectedDateRange = pickedRange; + selectedFilter = DateFilter.dateRange; + }); + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + color: Constants.clinicCardBgColor, + height: Constants.SMALL_APP_BAR_HEIGHT, + smallTitle: "Medications", + rightBtTitle: "", + ), + Expanded( + child: FutureBuilder>( + future: _loadHealthRecords(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (snapshot.hasData) { + final healthRecords = snapshot.data!; + return SingleChildScrollView( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + children: [ + _buildFilterMenu(theme), + FutureBuilder>( + future: _applyFilter(healthRecords), + builder: (context, filteredSnapshot) { + if (filteredSnapshot.connectionState == + ConnectionState.waiting) { + return const Center( + child: CircularProgressIndicator()); + } else if (filteredSnapshot.hasError) { + return Center(child: Text( + 'Error: ${filteredSnapshot.error}')); + } else if (filteredSnapshot.hasData) { + final filteredRecords = filteredSnapshot.data!; + + // Check if the filtered list is empty and show 'No Data' text + if (filteredRecords.isEmpty) { + return const Center( + child: Text('No data available.')); + } + + return Column( + children: filteredRecords + .map((record) => + _buildRecordView(record, theme)) + .toList(), + ); + } else { + return const Center( + child: Text('No data available.')); + } + }, + ), + ], + ), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ), + ], + ), + ); + } + + // Record View Widgets and Helpers (No changes here, same as before) + Widget _buildRecordView(HealthRecordModel record, ThemeData theme) { + return Column( + children: [ + _buildDateRow(record.visitDate, theme), + const SizedBox(height: Constants.SPACING), + // _buildHospitalRow(record.facility, theme), + // const SizedBox(height: Constants.SPACING), + // const Divider(), + _buildMedicationList(record.medications, theme), + const SizedBox(height: Constants.SPACING), + const Divider(), + ], + ); + } + + Widget _buildDateRow(String date, ThemeData theme) { + return Row( + children: [ + Expanded( + child: Text( + date, + style: theme.textTheme.titleMedium), + ), + ], + ); + } + + Widget _buildHospitalRow(String hospital, ThemeData theme) { + return Row( + children: [ + Expanded( // Wrap the Text widget in Expanded + child: Text( + hospital, + style: theme.textTheme.titleMedium, + overflow: TextOverflow.ellipsis, // Truncate if too long + ), + ), + ], + ); + } + + Widget _buildMedicationList(List medications, ThemeData theme) { + if (medications.isEmpty) { + return const Center( + child: Text('No Medications recorded.') + ); + } + + return Column( + children: medications.map((medication) => + _buildImmunizationRow(medication, theme)).toList(), + ); + } + + + Widget _buildImmunizationRow(Medication medication, ThemeData theme) { + return ListTile( + title: ExpansionTile( + title: Row( + children: [ + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildImmunizationsRow(medication, theme), + ], + )), + ], + ), + children: [ + ListTile( + title: Column( + children: [ + Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Dose", style: theme.textTheme.titleSmall,), + Text(medication.value, + style: theme.textTheme.titleSmall), + ], + ), + ], + ), + ) + ], + ), + ); + } + + Widget _buildImmunizationsRow(Medication medication, ThemeData theme) { + return Row( + children: [ + SvgPicture.asset( + "assets/images/boldDuotoneMedicineStethoscope.svg", + width: Constants.TWENTY, + height: Constants.TWENTY, + ), + const SizedBox(width: Constants.SPACING), + Expanded( + child: Text(medication.name, + // style: theme.textTheme.titleMedium?.copyWith(fontSize: 8), + style: theme.textTheme.titleSmall, + // Reduce font size + overflow: TextOverflow.ellipsis, + // Truncate if too long + maxLines: 1, + ),), + ], + ); + } + + Color getStatusColor(String status) { + if (status == 'ACTIVE') { + return Constants.programsColor; + } else if (status == 'INACTIVE') { + return Constants.facilityDirectoryColor; + } else { + return Constants.selfScreeningBgColor; + } + } + + // Build the filter menu with the date range option + Widget _buildFilterMenu(ThemeData theme) { + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HealthButton( + onFilterSelected: (filter) { + setState(() { + selectedFilter = filter; + if (filter == DateFilter.dateRange) { + // Trigger the date range picker when the date range filter is selected + _selectDateRange(context); + } + }); + }, + ), + // if (selectedDateRange != null) + // Padding( + // padding: const EdgeInsets.only(left: Constants.SPACING), + // child: Text( + // 'From ${selectedDateRange!.start.toLocal()} - ${selectedDateRange!.end.toLocal()}', + // style: theme.textTheme.bodyMedium, + // ), + // ), + ], + ); + } +} diff --git a/lib/src/features/clinic_card/relationship/widgets/procedures_rship.dart b/lib/src/features/clinic_card/relationship/widgets/procedures_rship.dart new file mode 100644 index 00000000..6ef1d9bb --- /dev/null +++ b/lib/src/features/clinic_card/relationship/widgets/procedures_rship.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:nishauri/src/features/clinic_card/data/models/program.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class ClinicRshipDetails extends StatelessWidget { + final Program program; + const ClinicRshipDetails({Key? key, required this.program}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Column( + children: [ + ListTile( + title: Text(program.name), + subtitle: Text('Facility Name: ${program.facility_name}'), + ), + Expanded( + child: ListView.builder( + itemCount: program.obs.length, + itemBuilder: (BuildContext context, int index) { + final observations = program.obs[index]; + return Column( + children: [ + const Divider(), + ListTile( + title: Card( + child: Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + observations.label, + style: theme.textTheme.headline6, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + const SizedBox(height: Constants.SPACING), + Row( + children: [ + Icon( + Icons.diamond_outlined, + color: theme.colorScheme.primary, + ), + const SizedBox(width: Constants.SPACING), + Text(observations.value?? "",), + ], + ), + ], + ), + ), + ), + // title: Text(observations.label, style: theme.textTheme.titleMedium,), + // subtitle: Text(observations.value?? "",style:TextStyle( + // color: theme.primaryColor, + // + // ),), + ), + ], + ); + }, + ), + ), + ], + ); + } +} diff --git a/lib/src/features/clinic_card/relationship/widgets/vital_rship_records.dart b/lib/src/features/clinic_card/relationship/widgets/vital_rship_records.dart new file mode 100644 index 00000000..c63fb442 --- /dev/null +++ b/lib/src/features/clinic_card/relationship/widgets/vital_rship_records.dart @@ -0,0 +1,306 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nishauri/src/features/clinic_card/data/models/health_test.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/helper/health_record_filter.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +import '../../../../shared/display/heath_filter_button.dart'; + +class VitalHealthRshipRecord extends StatefulWidget { + const VitalHealthRshipRecord({super.key}); + + @override + State createState() => _VitalHealthRshipRecordState(); +} + +class _VitalHealthRshipRecordState extends State { + // Store the selected filter + DateFilter? selectedFilter; + DateTimeRange? selectedDateRange; + + Future> _loadHealthRecords() async { + final responseString = await rootBundle.loadString('assets/data/visits.json'); + final List json = jsonDecode(responseString); + return json.map((e) => HealthRecordModel.fromJson(e)).toList(); + } + + Future> _applyFilter(List records) async { + List filteredRecords = applyFilter( + records, + selectedFilter ?? DateFilter.all, + selectedDateRange?.start, + selectedDateRange?.end, + ); + + return filteredRecords; + } + + Future _selectDateRange(BuildContext context) async { + DateTimeRange? pickedRange = await selectDateRange( + context: context, + initialDateRange: selectedDateRange, + primaryColor: Constants.bmiCalculatorColor, + barrierColor: Constants.bmiCalculatorColor, + ); + + if (pickedRange != null && pickedRange != selectedDateRange) { + setState(() { + selectedDateRange = pickedRange; + selectedFilter = DateFilter.dateRange; + }); + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + color: Constants.clinicCardBgColor, + height: Constants.SMALL_APP_BAR_HEIGHT, + smallTitle: "Vitals", + rightBtTitle: "", + ), + Expanded( + child: FutureBuilder>( + future: _loadHealthRecords(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } else if (snapshot.hasData) { + final healthRecords = snapshot.data!; + return SingleChildScrollView( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + children: [ + _buildFilterMenu(theme), + FutureBuilder>( + future: _applyFilter(healthRecords), + builder: (context, filteredSnapshot) { + if (filteredSnapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (filteredSnapshot.hasError) { + return Center(child: Text('Error: ${filteredSnapshot.error}')); + } else if (filteredSnapshot.hasData) { + final filteredRecords = filteredSnapshot.data!; + + // Check if the filtered list is empty and show 'No Data' text + if (filteredRecords.isEmpty) { + return const Center(child: Text('No data available.')); + } + + return Column( + children: filteredRecords + .map((record) => _buildRecordView(record, theme)) + .toList(), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ], + ), + ); + } else { + return const Center(child: Text('No data available.')); + } + }, + ), + ), + ], + ), + ); + } + + // Record View Widgets and Helpers (No changes here, same as before) + Widget _buildRecordView(HealthRecordModel record, ThemeData theme) { + return Column( + children: [ + _buildDateRow(record.visitDate, theme), + const SizedBox(height: Constants.SPACING), + // _buildHospitalRow(record.facility, theme), + // const SizedBox(height: Constants.SPACING), + // const Divider(), + _buildVitalList(record.vitals, theme), + const SizedBox(height: Constants.SPACING), + const Divider(), + ], + ); + } + + Widget _buildDateRow(String date, ThemeData theme) { + return Row( + children: [ + Expanded( + child: Text( + date, + style: theme.textTheme.titleSmall), + ), + ], + ); + } + + Widget _buildHospitalRow(String hospital, ThemeData theme) { + return Row( + children: [ + Expanded( // Wrap the Text widget in Expanded + child: Text( + hospital, + style: theme.textTheme.titleMedium, + overflow: TextOverflow.ellipsis, // Truncate if too long + ), + ), + ], + ); + } + + Widget _buildVitalList(List vitals, ThemeData theme) { + if (vitals.isEmpty) { + return const Center( + child:Text('No Vitals recorded.') + ); + } + + return Column( + children: vitals.map((vital) => _buildVitalRow(vital, theme)).toList(), + ); + } + + Widget _buildVitalRow(Vital vital, ThemeData theme) { + return ListTile( + title: ExpansionTile( + title: Row( + children: [ + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildVitalsRow(vital, theme), + ], + )), + ], + ), + children: [ + ListTile( + title: Column( + children: [ + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Temperature", style: theme.textTheme.bodySmall), + Text(vital.temp, style: theme.textTheme.bodySmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Systolic B.P", style: theme.textTheme.bodySmall), + Text(vital.systolic, style: theme.textTheme.bodySmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Diastolic B.P", style: theme.textTheme.bodySmall), + Text(vital.diastolic, style: theme.textTheme.bodySmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Respiratory Rate", style: theme.textTheme.bodySmall), + Text(vital.respiratory, style: theme.textTheme.bodySmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Oxygen Saturation", style: theme.textTheme.bodySmall), + Text(vital.oxygenSaturation, style: theme.textTheme.bodySmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Weight", style: theme.textTheme.bodySmall), + Text(vital.weight, style: theme.textTheme.bodySmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Height", style: theme.textTheme.bodySmall), + Text(vital.height, style: theme.textTheme.bodySmall), + ], + ), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Presenting Complaints", style: theme.textTheme.bodySmall), + Text(vital.complain, style: theme.textTheme.bodySmall), + ], + ), + ], + ), + ) + ], + ), + ); + } + + Widget _buildVitalsRow(Vital vital, ThemeData theme) { + return Row( + children: [ + SvgPicture.asset( + "assets/images/boldDuotoneMedicineStethoscope.svg", + width: Constants.TWENTY, + height: Constants.TWENTY, + ), + const SizedBox(width: Constants.SPACING), + Expanded( + child:Text(vital.name, + style: theme.textTheme.titleSmall, + overflow: TextOverflow.ellipsis, // Truncate if too long + maxLines: 1, + ), + ), + ], + ); + } + + // Build the filter menu with the date range option + Widget _buildFilterMenu(ThemeData theme) { + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + HealthButton( + onFilterSelected: (filter) { + setState(() { + selectedFilter = filter; + if (filter == DateFilter.dateRange) { + // Trigger the date range picker when the date range filter is selected + _selectDateRange(context); + } + }); + }, + ), + ], + ); + }} \ No newline at end of file diff --git a/lib/src/features/common/data/models/announcement.dart b/lib/src/features/common/data/models/announcement.dart index cbfb61b9..236f53ba 100644 --- a/lib/src/features/common/data/models/announcement.dart +++ b/lib/src/features/common/data/models/announcement.dart @@ -10,6 +10,7 @@ class Announcement with _$Announcement { required String image, required String source, required String title, + String? header, String? description, }) = _Announcement; diff --git a/lib/src/features/common/data/models/announcement.freezed.dart b/lib/src/features/common/data/models/announcement.freezed.dart index 86629d39..deba9606 100644 --- a/lib/src/features/common/data/models/announcement.freezed.dart +++ b/lib/src/features/common/data/models/announcement.freezed.dart @@ -24,6 +24,7 @@ mixin _$Announcement { String get image => throw _privateConstructorUsedError; String get source => throw _privateConstructorUsedError; String get title => throw _privateConstructorUsedError; + String? get header => throw _privateConstructorUsedError; String? get description => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @@ -43,6 +44,7 @@ abstract class $AnnouncementCopyWith<$Res> { String image, String source, String title, + String? header, String? description}); } @@ -63,6 +65,7 @@ class _$AnnouncementCopyWithImpl<$Res, $Val extends Announcement> Object? image = null, Object? source = null, Object? title = null, + Object? header = freezed, Object? description = freezed, }) { return _then(_value.copyWith( @@ -82,6 +85,10 @@ class _$AnnouncementCopyWithImpl<$Res, $Val extends Announcement> ? _value.title : title // ignore: cast_nullable_to_non_nullable as String, + header: freezed == header + ? _value.header + : header // ignore: cast_nullable_to_non_nullable + as String?, description: freezed == description ? _value.description : description // ignore: cast_nullable_to_non_nullable @@ -103,6 +110,7 @@ abstract class _$$AnnouncementImplCopyWith<$Res> String image, String source, String title, + String? header, String? description}); } @@ -121,6 +129,7 @@ class __$$AnnouncementImplCopyWithImpl<$Res> Object? image = null, Object? source = null, Object? title = null, + Object? header = freezed, Object? description = freezed, }) { return _then(_$AnnouncementImpl( @@ -140,6 +149,10 @@ class __$$AnnouncementImplCopyWithImpl<$Res> ? _value.title : title // ignore: cast_nullable_to_non_nullable as String, + header: freezed == header + ? _value.header + : header // ignore: cast_nullable_to_non_nullable + as String?, description: freezed == description ? _value.description : description // ignore: cast_nullable_to_non_nullable @@ -156,6 +169,7 @@ class _$AnnouncementImpl with DiagnosticableTreeMixin implements _Announcement { required this.image, required this.source, required this.title, + this.header, this.description}); factory _$AnnouncementImpl.fromJson(Map json) => @@ -170,11 +184,13 @@ class _$AnnouncementImpl with DiagnosticableTreeMixin implements _Announcement { @override final String title; @override + final String? header; + @override final String? description; @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'Announcement(id: $id, image: $image, source: $source, title: $title, description: $description)'; + return 'Announcement(id: $id, image: $image, source: $source, title: $title, header: $header, description: $description)'; } @override @@ -186,6 +202,7 @@ class _$AnnouncementImpl with DiagnosticableTreeMixin implements _Announcement { ..add(DiagnosticsProperty('image', image)) ..add(DiagnosticsProperty('source', source)) ..add(DiagnosticsProperty('title', title)) + ..add(DiagnosticsProperty('header', header)) ..add(DiagnosticsProperty('description', description)); } @@ -198,6 +215,7 @@ class _$AnnouncementImpl with DiagnosticableTreeMixin implements _Announcement { (identical(other.image, image) || other.image == image) && (identical(other.source, source) || other.source == source) && (identical(other.title, title) || other.title == title) && + (identical(other.header, header) || other.header == header) && (identical(other.description, description) || other.description == description)); } @@ -205,7 +223,7 @@ class _$AnnouncementImpl with DiagnosticableTreeMixin implements _Announcement { @JsonKey(ignore: true) @override int get hashCode => - Object.hash(runtimeType, id, image, source, title, description); + Object.hash(runtimeType, id, image, source, title, header, description); @JsonKey(ignore: true) @override @@ -227,6 +245,7 @@ abstract class _Announcement implements Announcement { required final String image, required final String source, required final String title, + final String? header, final String? description}) = _$AnnouncementImpl; factory _Announcement.fromJson(Map json) = @@ -241,6 +260,8 @@ abstract class _Announcement implements Announcement { @override String get title; @override + String? get header; + @override String? get description; @override @JsonKey(ignore: true) diff --git a/lib/src/features/common/data/models/announcement.g.dart b/lib/src/features/common/data/models/announcement.g.dart index 46374ddc..3144df4f 100644 --- a/lib/src/features/common/data/models/announcement.g.dart +++ b/lib/src/features/common/data/models/announcement.g.dart @@ -12,6 +12,7 @@ _$AnnouncementImpl _$$AnnouncementImplFromJson(Map json) => image: json['image'] as String, source: json['source'] as String, title: json['title'] as String, + header: json['header'] as String?, description: json['description'] as String?, ); @@ -21,5 +22,6 @@ Map _$$AnnouncementImplToJson(_$AnnouncementImpl instance) => 'image': instance.image, 'source': instance.source, 'title': instance.title, + 'header': instance.header, 'description': instance.description, }; diff --git a/lib/src/features/common/presentation/pages/FaqPage.dart b/lib/src/features/common/presentation/pages/FaqPage.dart index 2d5c8980..bd25ecdb 100644 --- a/lib/src/features/common/presentation/pages/FaqPage.dart +++ b/lib/src/features/common/presentation/pages/FaqPage.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:nishauri/src/features/common/data/models/faq_model.dart'; import 'package:nishauri/src/features/common/data/services/FAQ_service.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/shared/display/background_image_widget.dart'; import 'package:nishauri/src/utils/constants.dart'; import 'package:nishauri/src/utils/helpers.dart'; diff --git a/lib/src/features/common/presentation/pages/HomeScreen.dart b/lib/src/features/common/presentation/pages/HomeScreen.dart index b1ec5446..564fefc8 100644 --- a/lib/src/features/common/presentation/pages/HomeScreen.dart +++ b/lib/src/features/common/presentation/pages/HomeScreen.dart @@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:material_dialogs/dialogs.dart'; import 'package:nishauri/src/app/navigation/drawer/UserDrawerHeader.dart'; import 'package:nishauri/src/app/navigation/drawer/customeDrawer.dart'; import 'package:nishauri/src/features/auth/data/providers/auth_provider.dart'; @@ -17,8 +18,11 @@ import 'package:nishauri/src/features/common/presentation/widgets/Greetings.dart import 'package:nishauri/src/features/common/presentation/widgets/ShortcutsUi.dart'; import 'package:nishauri/src/features/hiv/data/providers/art_appointmen_provider.dart'; import 'package:nishauri/src/features/user/data/providers/user_provider.dart'; +import 'package:nishauri/src/features/user_preference/data/providers/settings_provider.dart'; +import 'package:nishauri/src/features/user_programs/data/providers/program_provider.dart'; import 'package:nishauri/src/hooks/use_local_avatar.dart'; import 'package:nishauri/src/local_storage/LocalStorage.dart'; +import 'package:nishauri/src/shared/dialog/dialog.dart'; import 'package:nishauri/src/shared/display/AppAvatar.dart'; import 'package:nishauri/src/shared/display/AppCard.dart'; import 'package:carousel_slider/carousel_slider.dart'; @@ -51,6 +55,7 @@ class _HomeScreenState extends ConsumerState { void initState() { super.initState(); _loadVersion(); + ref.refresh(userProvider.notifier); } Future _loadVersion() async { @@ -66,6 +71,40 @@ class _HomeScreenState extends ConsumerState { final theme = Theme.of(context); final asyncUser = ref.watch(userProvider); final size = getOrientationAwareScreenSize(context); + final programState = ref.watch(userProgramProvider); + final settings = ref.watch(settingsNotifierProvider); + final updateSettings = ref.read(settingsNotifierProvider.notifier); + + // Handle dialog display + WidgetsBinding.instance.addPostFrameCallback((_) { + // Check if the program list is empty or if all programs are inactive + final showUpdateProgram = programState.when( + data: (programs) => programs.isEmpty || programs.every((program) => program.isActive == false), + error: (error, stack) => false, + loading: () => false, + ); + + if (showUpdateProgram && settings.firstTimeNoProgram) { + HealthProgramDialog( + context, + "Tap to Choose and Enrol in a Program", + "Take Control of Your Health – Join A Program and Start Your Journey Today!", + Constants.labResultsShortcutBgColor, + "Opt-In", + Constants.labResultsColor, + "Not now", + Constants.labResultsShortcutBgColor, + (){ + context.goNamed(RouteNames.PROGRAME_REGISTRATION_SCREEN); + }, + (){ + Navigator.of(context).pop(); + }, + ).show(); + updateSettings.updateSettings(firstTimeNoProgram: false); + } + }); + return Scaffold( key: _scaffoldKey, drawer: CustomDrawer(), diff --git a/lib/src/features/common/presentation/pages/MainMenuScreen.dart b/lib/src/features/common/presentation/pages/MainMenuScreen.dart index e13428d4..107cc565 100644 --- a/lib/src/features/common/presentation/pages/MainMenuScreen.dart +++ b/lib/src/features/common/presentation/pages/MainMenuScreen.dart @@ -10,6 +10,7 @@ import 'package:nishauri/src/app/navigation/menu/MenuOption.dart'; import 'package:nishauri/src/app/navigation/menu/menuItems.dart'; import 'package:nishauri/src/features/common/presentation/helpers/constants.dart'; import 'package:nishauri/src/features/common/presentation/widgets/Greeting2.dart'; +import 'package:nishauri/src/features/user/data/providers/user_provider.dart'; import 'package:nishauri/src/features/user_programs/data/providers/program_provider.dart'; import 'package:nishauri/src/shared/display/AppCard.dart'; import 'package:nishauri/src/shared/input/FormInputTextField.dart'; @@ -17,11 +18,11 @@ import 'package:nishauri/src/utils/constants.dart'; import 'package:nishauri/src/utils/helpers.dart'; import 'package:nishauri/src/utils/routes.dart'; -class MainMenuScreen extends StatelessWidget { +class MainMenuScreen extends ConsumerWidget { const MainMenuScreen({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); final size = getOrientationAwareScreenSize(context); @@ -37,122 +38,129 @@ class MainMenuScreen extends StatelessWidget { } } - return Scaffold( - key: _scaffoldKey, - drawer: CustomDrawer(), - body: Stack( - children: [ - Positioned( - top: 0, - right: 0, - child: SvgPicture.asset( - "assets/images/rect-bg.svg", - semanticsLabel: "Doctors", - fit: BoxFit.contain, - height: size.width * 0.45, - width: size.width * 0.45, - )), - SafeArea( - child: Consumer( - builder: (context, ref, child) { - final userProgram = ref.watch(userProgramProvider); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: Constants.SPACING), - Padding( - padding: EdgeInsets.symmetric( - horizontal: Constants.SPACING, - vertical: Constants.SPACING), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + final userAdminAsync = ref.watch(userProvider); + return userAdminAsync.when( + data: (userRoles){ + List roles = List.from(userRoles.roles); + return Scaffold( + key: _scaffoldKey, + drawer: CustomDrawer(), + body: Stack( + children: [ + Positioned( + top: 0, + right: 0, + child: SvgPicture.asset( + "assets/images/rect-bg.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + height: size.width * 0.45, + width: size.width * 0.45, + )), + SafeArea( + child: Consumer( + builder: (context, ref, child) { + final userProgram = ref.watch(userProgramProvider); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "App Modules📱", - style: TextStyle( - fontSize: 32, - fontWeight: FontWeight.bold, - color: theme.colorScheme.primary, + const SizedBox(height: Constants.SPACING), + Padding( + padding: EdgeInsets.symmetric( + horizontal: Constants.SPACING, + vertical: Constants.SPACING), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "App Modules📱", + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, + color: theme.colorScheme.primary, + ), + ), + Wrap( + children: [ + IconButton( + onPressed: toggleDrawer, + icon: const Icon(Icons.more_vert_outlined), + ), + ], + ) + ], ), ), - Wrap( - children: [ - IconButton( - onPressed: toggleDrawer, - icon: const Icon(Icons.more_vert_outlined), - ), - ], - ) - ], - ), - ), - const SizedBox(height: Constants.SPACING * 2), - Expanded( - child: userProgram.when( - data: (data) { - return MenuItemsBuilder( - crossAxisCount: 2, - itemBuilder: (item) => Card( - margin: const EdgeInsets.all(Constants.SPACING), - clipBehavior: Clip.antiAlias, - child: InkWell( - splashColor: theme.colorScheme.primary, - onTap: item.onPressed, - child: Container( - padding: - const EdgeInsets.all(Constants.SPACING), - decoration: BoxDecoration( - color: - item.color ?? theme.colorScheme.primary, - image: const DecorationImage( - image: AssetImage( - "assets/images/contours.png"), - opacity: 0.2, - fit: BoxFit.cover, - ), - ), - child: Column( - mainAxisAlignment: - MainAxisAlignment.spaceEvenly, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - item.icon, - const SizedBox(height: Constants.SPACING), - Text( - item.title ?? "", - style: theme.textTheme.titleMedium - ?.copyWith( - color: Colors.white, - fontWeight: FontWeight.normal, + const SizedBox(height: Constants.SPACING * 2), + Expanded( + child: userProgram.when( + data: (data) { + return MenuItemsBuilder( + crossAxisCount: 2, + itemBuilder: (item) => Card( + margin: const EdgeInsets.all(Constants.SPACING), + clipBehavior: Clip.antiAlias, + child: InkWell( + splashColor: theme.colorScheme.primary, + onTap: item.onPressed, + child: Container( + padding: + const EdgeInsets.all(Constants.SPACING), + decoration: BoxDecoration( + color: + item.color ?? theme.colorScheme.primary, + image: const DecorationImage( + image: AssetImage( + "assets/images/contours.png"), + opacity: 0.2, + fit: BoxFit.cover, + ), + ), + child: Column( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + item.icon, + const SizedBox(height: Constants.SPACING), + Text( + item.title ?? "", + style: theme.textTheme.titleMedium + ?.copyWith( + color: Colors.white, + fontWeight: FontWeight.normal, + ), + overflow: TextOverflow.ellipsis, + softWrap: true, + ), + ], ), - overflow: TextOverflow.ellipsis, - softWrap: true, ), - ], + ), ), - ), + items: [ + ...getGenericMenuItems(context, roles), + ], + ); + }, + error: (error, _) => + Center(child: Text(error.toString())), + loading: () => const Center( + child: CircularProgressIndicator(), ), ), - items: [ - ...getGenericMenuItems(context), - ], - ); - }, - error: (error, _) => - Center(child: Text(error.toString())), - loading: () => const Center( - child: CircularProgressIndicator(), - ), - ), - ), - ], - ); - }, + ), + ], + ); + }, + ), + ), + ], ), - ), - ], - ), - ); + ); + }, + error: (error,_)=> Center(), + loading: () => Center(child: CircularProgressIndicator(),)); } } diff --git a/lib/src/features/common/presentation/pages/SettingsScreen.dart b/lib/src/features/common/presentation/pages/SettingsScreen.dart index 50615581..1bd7ed46 100644 --- a/lib/src/features/common/presentation/pages/SettingsScreen.dart +++ b/lib/src/features/common/presentation/pages/SettingsScreen.dart @@ -116,6 +116,21 @@ List<_SettingsItem> _settingsItem(BuildContext context) => <_SettingsItem>[ ); }, ), + ), + _SettingsItem( + title: "Enable self screening reminder", + subTitle: "Enable daily log reminders for self-screening", + leadingIcon: Icons.display_settings, + trailingIcon: Consumer( + builder: (context, ref, child) { + final settings = ref.read(settingsNotifierProvider.notifier); + + return Switch( + value: true, + onChanged: (value) => false, + ); + }, + ), ), ]; diff --git a/lib/src/features/common/presentation/pages/blog.dart b/lib/src/features/common/presentation/pages/blog.dart index 366957ec..d8ac1927 100644 --- a/lib/src/features/common/presentation/pages/blog.dart +++ b/lib/src/features/common/presentation/pages/blog.dart @@ -1,11 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:nishauri/src/features/common/data/models/announcement.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/blog_post_widget.dart'; import 'package:nishauri/src/shared/display/scafold_stack_body.dart'; import 'package:nishauri/src/utils/constants.dart'; -import 'package:nishauri/src/utils/helpers.dart'; class BlogPostScreen extends StatelessWidget { final Announcement announcement; @@ -14,42 +12,24 @@ class BlogPostScreen extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = Theme.of(context); return Scaffold( body: Column( children: [ CustomAppBar( - title: "Did you know 💡", - color: Constants.labResultsColor.withOpacity(0.5),), - + title: announcement.header ?? "Did you know 💡", + color: Constants.labResultsColor.withOpacity(0.5), + ), Expanded( - child: ScaffoldStackedBody( - body: Padding( - padding: const EdgeInsets.all(Constants.SPACING), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(announcement.title, style: theme.textTheme.headlineMedium), - SvgPicture.asset( - announcement.image, - width: double.infinity, - height: getOrientationAwareScreenSize(context).height * 0.30, - fit: BoxFit.cover, - ), - // Image.network( - // announcement.image, - // width: double.infinity, - // height: getOrientationAwareScreenSize(context).height * 0.30, - // fit: BoxFit.cover, - // ), - Expanded(child: Markdown(data: announcement.description!)) - ], - ), + child: ScaffoldStackedBody( + body: BlogPostWidget( + title: announcement.title, + imageUrl: announcement.image, + description: announcement.description!, ), ), - ) + ), ], - ) + ), ); } } diff --git a/lib/src/features/common/presentation/widgets/AppointmentCard.dart b/lib/src/features/common/presentation/widgets/AppointmentCard.dart index 5a4452e0..6e4f0315 100644 --- a/lib/src/features/common/presentation/widgets/AppointmentCard.dart +++ b/lib/src/features/common/presentation/widgets/AppointmentCard.dart @@ -13,8 +13,9 @@ class AppointmentCard extends StatelessWidget { final String appointmentType; final String? rescheduleButtonText; final void Function()? onRescheduleTap; - - + final String? boardNumber; + final String? cadre; // Cadre field + final bool showAppointmentTime; // Flag for visibility control const AppointmentCard({ super.key, @@ -23,7 +24,10 @@ class AppointmentCard extends StatelessWidget { required this.appointmentTime, required this.appointmentType, this.onRescheduleTap, - this.rescheduleButtonText = "Reschedule" + this.rescheduleButtonText = "Reschedule", + this.boardNumber, + this.cadre, + this.showAppointmentTime = true, }); @override @@ -31,29 +35,26 @@ class AppointmentCard extends StatelessWidget { final theme = Theme.of(context); return Container( padding: const EdgeInsets.only(bottom: 20), - // color: Colors.red, child: Stack( clipBehavior: Clip.none, children: [ Card( - clipBehavior: Clip.antiAlias, // Clip the corners of the card + clipBehavior: Clip.antiAlias, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.ROUNDNESS * 2), // Customize corner radius + borderRadius: BorderRadius.circular(Constants.ROUNDNESS * 2), ), child: Container( padding: const EdgeInsets.all(Constants.SPACING), - // width: width, - // height: height, decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - theme.colorScheme.onBackground, // Use app's surface color - theme.colorScheme.primary, // Use app's primary color - ], - )), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + theme.colorScheme.onBackground, + theme.colorScheme.primary, + ], + ), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, @@ -63,8 +64,9 @@ class AppointmentCard extends StatelessWidget { Container( padding: const EdgeInsets.all(2), decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(Constants.ROUNDNESS * 2), + borderRadius: BorderRadius.circular( + Constants.ROUNDNESS * 2, + ), border: Border.all( width: 1, color: theme.canvasColor.withOpacity(0.5), @@ -74,8 +76,9 @@ class AppointmentCard extends StatelessWidget { width: 60, height: 60, decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(Constants.ROUNDNESS * 2), + borderRadius: BorderRadius.circular( + Constants.ROUNDNESS * 2, + ), image: DecorationImage( image: NetworkImage(providerImage), fit: BoxFit.cover, @@ -85,7 +88,8 @@ class AppointmentCard extends StatelessWidget { ), Padding( padding: const EdgeInsets.symmetric( - horizontal: Constants.SPACING), + horizontal: Constants.SPACING, + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -94,51 +98,81 @@ class AppointmentCard extends StatelessWidget { style: theme.textTheme.titleSmall ?.copyWith(color: theme.canvasColor), ), - Row( - children: [ - Text( - DateFormat("EEE, MMM dd").format(appointmentTime), - style: theme.textTheme.titleSmall - ?.copyWith(color: theme.canvasColor), - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: Constants.SPACING), - child: Text( - ".", + if (boardNumber != null) + Text( + boardNumber!, + style: theme.textTheme.bodyMedium + ?.copyWith(color: theme.canvasColor), + ), + if (showAppointmentTime) + Row( + children: [ + Text( + DateFormat("EEE, MMM dd") + .format(appointmentTime), + style: theme.textTheme.titleSmall + ?.copyWith(color: theme.canvasColor), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: Constants.SPACING, + ), + child: Text( + ".", + style: theme.textTheme.titleSmall + ?.copyWith(color: theme.canvasColor), + ), + ), + Text( + appointmentType, style: theme.textTheme.titleSmall ?.copyWith(color: theme.canvasColor), ), - ), - Text( - appointmentType, - style: theme.textTheme.titleSmall - ?.copyWith(color: theme.canvasColor), - ), - ], - ) + ], + ), ], ), ) ], ), - Container( - padding: const EdgeInsets.symmetric( + if (showAppointmentTime) + Container( + padding: const EdgeInsets.symmetric( vertical: Constants.SPACING * 0.5, - horizontal: Constants.SPACING * 2), - margin: const EdgeInsets.only(top: Constants.SPACING), - decoration: BoxDecoration( - color: theme.canvasColor.withOpacity(0.2), - borderRadius: const BorderRadius.all( - Radius.circular(Constants.ROUNDNESS), + horizontal: Constants.SPACING * 2, + ), + margin: const EdgeInsets.only(top: Constants.SPACING), + decoration: BoxDecoration( + color: theme.canvasColor.withOpacity(0.2), + borderRadius: const BorderRadius.all( + Radius.circular(Constants.ROUNDNESS), + ), + ), + child: Text( + "${appointmentTime.difference(DateTime.now()).inDays} Days left", + style: theme.textTheme.titleMedium + ?.copyWith(color: theme.canvasColor), ), ), - child: Text( - "${appointmentTime.difference(DateTime.now()).inDays} Days left", - style: theme.textTheme.titleMedium - ?.copyWith(color: theme.canvasColor), + if (!showAppointmentTime && cadre != null) + Container( + padding: const EdgeInsets.symmetric( + vertical: Constants.SPACING * 0.5, + horizontal: Constants.SPACING * 2, + ), + margin: const EdgeInsets.only(top: Constants.SPACING), + decoration: BoxDecoration( + color: theme.canvasColor.withOpacity(0.2), + borderRadius: const BorderRadius.all( + Radius.circular(Constants.ROUNDNESS), + ), + ), + child: Text( + cadre!, + style: theme.textTheme.titleMedium + ?.copyWith(color: theme.canvasColor), + ), ), - ) ], ), ), @@ -148,36 +182,33 @@ class AppointmentCard extends StatelessWidget { right: getOrientationAwareScreenSize(context).width * 0.08, child: Container( decoration: BoxDecoration( - // border: Border.all(width: 2, color: theme.canvasColor), color: theme.canvasColor, - borderRadius: BorderRadius.circular(Constants.ROUNDNESS * 6) + borderRadius: BorderRadius.circular(Constants.ROUNDNESS * 6), ), child: Card( shadowColor: theme.colorScheme.primary, clipBehavior: Clip.antiAlias, - // Clip the corners of the card shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( Constants.ROUNDNESS * 6, - ), // Customize corner radius + ), ), - // shape: const OvalBorder(), child: InkWell( - - splashColor: theme.colorScheme.primary.withAlpha(30), - onTap: onRescheduleTap, - child: Container( - padding: const EdgeInsets.symmetric( - vertical: Constants.SPACING * 0.5, - horizontal: Constants.SPACING * 2), - child: Row( - children: [ - const Icon(Icons.schedule), - const SizedBox(width: Constants.SPACING), - Text(rescheduleButtonText ?? "Reschedule"), - ], - ), - )), + splashColor: theme.colorScheme.primary.withAlpha(30), + onTap: onRescheduleTap, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: Constants.SPACING * 0.5, + horizontal: Constants.SPACING * 2), + child: Row( + children: [ + const Icon(Icons.schedule), + const SizedBox(width: Constants.SPACING), + Text(rescheduleButtonText ?? "Reschedule"), + ], + ), + ), + ), ), ), ) @@ -186,3 +217,4 @@ class AppointmentCard extends StatelessWidget { ); } } + diff --git a/lib/src/features/common/presentation/widgets/Appointments.dart b/lib/src/features/common/presentation/widgets/Appointments.dart index ed445c93..1551efce 100644 --- a/lib/src/features/common/presentation/widgets/Appointments.dart +++ b/lib/src/features/common/presentation/widgets/Appointments.dart @@ -9,6 +9,7 @@ import 'package:nishauri/src/features/appointments/data/models/appointment.dart' import 'package:nishauri/src/features/appointments/data/providers/appointment_provider.dart'; import 'package:nishauri/src/features/appointments/presentation/pages/AppointmentRescheduleScreen.dart'; import 'package:nishauri/src/features/common/presentation/widgets/AppointmentCard.dart'; +import 'package:nishauri/src/features/dawa_drop/data/models/order_request/drug_order.dart'; import 'package:nishauri/src/local_storage/LocalStorage.dart'; import 'package:nishauri/src/shared/interfaces/notification_service.dart'; import 'package:nishauri/src/utils/helpers.dart'; @@ -24,13 +25,35 @@ class Appointments extends HookConsumerWidget { final appointmentsAsync = ref.watch(appointmentProvider(false)); final appointmentsNotifier = ref.watch(appointmentProvider(false).notifier); final screenSize = getOrientationAwareScreenSize(context); - final pendingOrders = ref - .watch(drugOrderProvider) - .valueOrNull - ?.where((order) => order.status != 'Fullfilled') - .toList() ?? - []; + // final pendingOrders = ref + // .watch(drugOrderProvider) + // .valueOrNull + // ?.where((order) => order.status != 'Fullfilled') + // .toList() ?? + // []; + List _pendingOrders(String appId){ + return ref + .watch(drugOrderProvider) + .valueOrNull + ?.where((order) => order.status != 'Fullfilled' && order.appointment?.id == appId) + .toList() ?? + []; + } + // final fullFilledOrders = ref + // .watch(drugOrderProvider) + // .valueOrNull + // ?.where((order) => order.status == 'Fullfilled') + // .toList() ?? + // []; + List _fullFilledOrders(String appId){ + return ref + .watch(drugOrderProvider) + .valueOrNull + ?.where((order) => order.status == 'Fullfilled' && order.appointment?.id == appId) + .toList() ?? + []; + } final theme = Theme.of(context); return appointmentsAsync.when( data: (data) { @@ -110,8 +133,9 @@ class Appointments extends HookConsumerWidget { child: SizedBox( width: size.width * 0.99, child: AppointmentCard( - rescheduleButtonText: pendingOrders.isNotEmpty + rescheduleButtonText: _pendingOrders(artAppointment.id??'').isNotEmpty ? "Has active order" + :_fullFilledOrders(artAppointment.id??'').isNotEmpty ? "Appointment order has already been fulfilled" : (artAppointment.reschedule_status .toString() == "0" @@ -134,7 +158,7 @@ class Appointments extends HookConsumerWidget { null || artAppointment.reschedule_status .toString() == - "2" + "2" && !_fullFilledOrders(artAppointment.id??'').isNotEmpty ? () => context.goNamed( RouteNames.APPOINTMENTS_RESCHEDULE, extra: diff --git a/lib/src/features/common/presentation/widgets/Greeting2.dart b/lib/src/features/common/presentation/widgets/Greeting2.dart index 50541c78..7bfdd959 100644 --- a/lib/src/features/common/presentation/widgets/Greeting2.dart +++ b/lib/src/features/common/presentation/widgets/Greeting2.dart @@ -56,7 +56,7 @@ class Greetings2 extends StatelessWidget { Radius.circular(radius), ), ), - child: const Center(child: Search()), + child: Center(child: Search(searchText: "Search...",)), ), ), ], diff --git a/lib/src/features/common/presentation/widgets/Greetings.dart b/lib/src/features/common/presentation/widgets/Greetings.dart index 6d0bfe46..ffc9ea5f 100644 --- a/lib/src/features/common/presentation/widgets/Greetings.dart +++ b/lib/src/features/common/presentation/widgets/Greetings.dart @@ -1,246 +1,180 @@ +import 'dart:ui'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; -import 'package:nishauri/src/app/navigation/menu/MenuItemsBuilder.dart'; -import 'package:nishauri/src/app/navigation/menu/MenuOption.dart'; -import 'package:nishauri/src/app/navigation/menu/menuItems.dart'; -import 'package:nishauri/src/features/common/data/providers/shortcut_provider.dart'; +import 'package:material_dialogs/dialogs.dart'; +import 'package:material_dialogs/widgets/buttons/icon_button.dart'; +import 'package:material_dialogs/widgets/buttons/icon_outline_button.dart'; import 'package:nishauri/src/features/user_programs/data/providers/program_provider.dart'; -import 'package:nishauri/src/shared/display/AppCard.dart'; import 'package:nishauri/src/utils/constants.dart'; import 'package:nishauri/src/utils/routes.dart'; -class Greetings extends StatelessWidget { +class Greetings extends ConsumerWidget { final String? image; final String name; const Greetings({super.key, this.image, required this.name}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); + final programState = ref.watch(userProgramProvider); + + // Check if the program list is empty or if all programs are inactive + final showUpdateProgram = programState.when( + data: (programs) => programs.isEmpty || + programs.every((program) => program.isActive == false), + error: (error, stack) => false, + loading: () => false, + ); return Container( - width: double.maxFinite, + width: double.infinity, padding: const EdgeInsets.all(Constants.SPACING), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Hey, 👋", - style: theme.textTheme.headlineMedium?.copyWith( - color: theme.colorScheme.primary, fontWeight: FontWeight.w700), - ), - name == 'Null Null' || name == null - ? GestureDetector( - onTap: () { - context.goNamed(RouteNames.PROFILE_EDIT_FORM); - }, - child: Text( - 'Click here to update your profile', - style: theme.textTheme.titleMedium?.copyWith( - color: Colors.red, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ) - : Text( - name, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: theme.textTheme.headlineLarge?.copyWith( - color: theme.colorScheme.primary, - fontWeight: FontWeight.w700, - ), - ), - - // Text( - // name == 'null null' || name == null ? - // GestureDetector( - // onTap: () { - // context.goNamed(RouteNames.PROFILE_EDIT_FORM); - // }, - // child: const Text( - // 'Click here to update your profile', - // style: TextStyle( - // color: Colors.red, - // // decoration: TextDecoration.underline, - // ), - // ), - // ) - // : name, - // maxLines: 1, - // overflow: TextOverflow.ellipsis, - // style: theme.textTheme.headlineLarge - // ?.copyWith(color: theme.colorScheme.primary, fontWeight: FontWeight.w700), - // ), - const SizedBox(height: Constants.SPACING), - Text( - DateFormat("EEEE, MMMM dd").format( - DateTime.now(), - ), - style: theme.textTheme.titleMedium - // ?.copyWith(color: theme.colorScheme.onSurface.withOpacity(0.3)), - ), - const SizedBox(height: Constants.SPACING * 2), - /* Stack( - clipBehavior: Clip.none, - alignment: Alignment.center, - children: [ - Positioned( - bottom: -(headerHeight * 0.25), - height: headerHeight * 1.25, - width: screenSize.width * 0.89, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "Hello 👋,", - ), - Text( - name, - style: const TextStyle(fontSize: 20), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - Expanded( - child: AppCard( - color: theme.colorScheme.onPrimary, - variant: CardVariant.ELEVETED, - child: Container( - padding: const EdgeInsets.all(Constants.SPACING), - width: double.infinity, - height: double.infinity, - child: Center( - child: Consumer( - builder: (context, ref, child) { - final shortcuts = ref.watch(shortcutProvider); - return MenuItemsBuilder( - itemBuilder: (item) => MenuOption( - title: item.title ?? "", - icon: item.icon, - bgColor: item.title == "Edit Shortcut" - ? theme.colorScheme.secondary - : null, - onPress: item.onPressed, - ), - items: getMenuItemByNames(context, shortcuts) - ..add( - MenuItem( - icon: Icons.edit_note_sharp, - title: "Edit Shortcut", - onPressed: () => _showDialog(context), - ), - ), - ); - - return Wrap( - alignment: WrapAlignment.spaceBetween, - crossAxisAlignment: WrapCrossAlignment.center, - spacing: Constants.SPACING, - runSpacing: Constants.SPACING, - children: [ - ...getMenuItemByNames(context, shortcuts) - .map((e) => MenuOption( - title: e.title ?? "", - icon: e.icon, - onPress: e.onPressed, - )), - MenuOption( - icon: Icons.add, - title: "Add Shortcut", - onPress: () { - _showDialog(context); - }, - ), - ], - ); - }, + Padding( + padding: const EdgeInsets.symmetric(horizontal: Constants.SMALL_SPACING), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + flex: 2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Hey, 👋", + style: theme.textTheme.headlineSmall?.copyWith( + color: theme.colorScheme.primary, + fontWeight: FontWeight.w600, + ), + ), + if (name == 'Null Null' || name == null) + GestureDetector( + onTap: () { + context.goNamed(RouteNames.PROFILE_EDIT_FORM); + }, + child: Text( + 'Click here to update your profile', + style: theme.textTheme.titleMedium?.copyWith( + color: Colors.red, ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ) + else + Text( + name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.bodyLarge?.copyWith( + color: theme.colorScheme.primary, + fontWeight: FontWeight.w500, ), ), + const SizedBox(height: Constants.SPACING), + Text( + DateFormat("EEEE, MMMM dd").format(DateTime.now()), + style: theme.textTheme.titleMedium, ), - ), - ], + ], + ), ), - ), - ], - ),*/ + if (showUpdateProgram) + Expanded( + child: BlinkingText(), + ), + ], + ), + ), ], ), ); } } -_showDialog(BuildContext context) { - final theme = Theme.of(context); - return showDialog( - context: context, - builder: (BuildContext context) => AlertDialog( - icon: const Icon(Icons.construction), - title: Text( - "Select Shortcut MenuOptions", - style: theme.textTheme.titleMedium, - ), - content: SizedBox( - width: double.maxFinite, - height: MediaQuery.of(context).size.height * 0.5, - child: Consumer( - builder: (context, ref, child) { - final userProgram = ref.watch(userProgramProvider); - final shortcuts = ref.watch(shortcutProvider); - final shortcutsNotifier = ref.watch(shortcutProvider.notifier); - return userProgram.when( - data: (data) => MenuItemsBuilder( - itemBuilder: (item) => MenuOption( - title: item.title ?? "", - icon: item.icon, - onPress: () { - if (shortcuts.any((element) => element == item.title)) { - // Delete shortcut - shortcutsNotifier.deleteShortcut( - item.title ?? "", - ); - } else { - // Add shortcut - if (shortcuts.length >= shortcutsNotifier.maxShortcuts) { - context.pop(); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: - Text("Max number of shortcuts reached"))); - } else { - shortcutsNotifier.addShortcut( - item.title ?? "", - ); - } - } - }, - bgColor: shortcuts.any((element) => element == item.title) - ? theme.colorScheme.secondary - : null, - ), - items: [ - // get generic menu items - ...getGenericMenuItems(context), - // get program menu items - ...data.map((e) { - final programCode = e.id; - return getProgramMenuItemByProgramCode( - context, programCode ?? ''); - }).toList(), - ], - ), - error: (error, _) => Center(child: Text(error.toString())), - loading: () => const Center( - child: CircularProgressIndicator(), - ), - ); +class BlinkingText extends StatefulWidget { + @override + _BlinkingTextState createState() => _BlinkingTextState(); +} + +class _BlinkingTextState extends State with SingleTickerProviderStateMixin { + late AnimationController _animationController; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(seconds: 1), + vsync: this, + )..repeat(reverse: true); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + void _onTap() { + Dialogs.bottomMaterialDialog( + msg: 'Tap to Choose and Enrol in a Program', + msgStyle: const TextStyle(color: Constants.labResultsColor), + context: context, + color: Constants.labResultsShortcutBgColor, + title: 'Take Control of Your Health – Join A Program and Start Your Journey Today!', + titleStyle: const TextStyle(color: Constants.labResultsColor, fontWeight: FontWeight.w600), + actions: [ + IconsOutlineButton( + onPressed: () { + context.goNamed(RouteNames.PROGRAME_REGISTRATION_SCREEN); + Navigator.of(context).pop(); }, + text: 'Opt-In', + iconData: Icons.add, + textStyle: const TextStyle(color: Colors.white), + color: Constants.labResultsColor, + iconColor: Colors.white, ), + IconsButton( + onPressed: () { + Navigator.of(context).pop(); + }, + text: 'Not now', + iconData: Icons.cancel_outlined, + color: Constants.labResultsShortcutBgColor, + textStyle: TextStyle(color: Colors.white), + iconColor: Colors.white, + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return GestureDetector( + onTap: _onTap, + child: AnimatedBuilder( + animation: _animationController, + builder: (context, child) { + return Opacity( + opacity: _animationController.value, + child: Text( + "Tap to update program", + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.primary, + fontWeight: FontWeight.w400, + ), + ), + ); + }, ), - ), - ); + ); + } } diff --git a/lib/src/features/common/presentation/widgets/ShortcutsUi.dart b/lib/src/features/common/presentation/widgets/ShortcutsUi.dart index 32c29a92..b626f4c8 100644 --- a/lib/src/features/common/presentation/widgets/ShortcutsUi.dart +++ b/lib/src/features/common/presentation/widgets/ShortcutsUi.dart @@ -1,8 +1,7 @@ -import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nishauri/src/features/user/data/providers/user_provider.dart'; import 'package:nishauri/src/shared/input/Button.dart'; import '../../../../app/navigation/menu/MenuItemsBuilder.dart'; @@ -18,6 +17,12 @@ class ShortcutsWidget extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final shortcuts = ref.watch(shortcutProvider); + final adminAsync = ref.watch(userProvider); + final List roles = adminAsync.when( + data: (data) => data.roles, + error: (_, __) => [], + loading: () => [], + ); final theme = Theme.of(context); return Column( @@ -35,136 +40,124 @@ class ShortcutsWidget extends HookConsumerWidget { InkWell( child: Text( "Edit Shortcuts", - style: theme.textTheme.titleSmall - ?.copyWith(color: theme.colorScheme.primary), + style: theme.textTheme.titleSmall?.copyWith( + color: theme.colorScheme.primary, + ), ), onTap: () { - _showDialog(context); + _showDialog(context, roles); }, ), - ], ), ), const SizedBox(height: Constants.SPACING), Padding( - padding: - const EdgeInsets.symmetric(horizontal: Constants.SPACING * 2), - child: - Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - ...getMenuItemByNames(context, shortcuts).map((item) { - return GestureDetector( - onTap: item.onPressed, - onLongPress: () { - _showDialog(context); - }, - child: MenuOption( - title: item.title ?? "", - icon: item.shortcutIcon, - bgColor: item.title == "Edit Shortcut" - ? theme.colorScheme.secondary - : item.shortcutBackgroundColor, + padding: const EdgeInsets.symmetric(horizontal: Constants.SPACING * 2), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ...getMenuItemByNames(context, shortcuts, roles).map((item) { + return GestureDetector( + onTap: item.onPressed, + onLongPress: () { + _showDialog(context, roles); + }, + child: MenuOption( + title: item.title ?? "", + icon: item.shortcutIcon, + bgColor: item.title == "Edit Shortcut" + ? theme.colorScheme.secondary + : item.shortcutBackgroundColor, + ), + ); + }).toList(), + if (getMenuItemByNames(context, shortcuts, roles).length < 3) + MenuOption( + title: "", + icon: const Icon(Icons.add), + bgColor: theme.colorScheme.secondary, + onPress: () { + _showDialog(context, roles); + }, ), - ); - }).toList(), - if (getMenuItemByNames(context, shortcuts).length < 3) - MenuOption( - title: "", - icon: const Icon(Icons.add), - bgColor: theme.colorScheme.secondary, - onPress: () { - _showDialog(context); - }, - ) - ]), + ], + ), ), ], ); } -} -_showDialog(BuildContext context) { - final theme = Theme.of(context); - return showDialog( - context: context, - builder: (BuildContext context) => - AlertDialog( - icon: const Icon(Icons.construction), - title: Text( - "Select Shortcut MenuOptions", - style: theme.textTheme.titleMedium, - ), - content: SizedBox( - width: double.maxFinite, - height: MediaQuery - .of(context) - .size - .height * 0.5, - child: Consumer( - builder: (context, ref, child) { - final userProgram = ref.watch(userProgramProvider); - final shortcuts = ref.watch(shortcutProvider); - final shortcutsNotifier = ref.watch(shortcutProvider.notifier); - return userProgram.when( - data: (data) => - MenuItemsBuilder( - itemBuilder: (item) => - MenuOption( - title: item.title ?? "", - icon: item.shortcutIcon, - onPress: () { - if (shortcuts.any((element) => - element == item.title)) { - // Delete shortcut - shortcutsNotifier.deleteShortcut( - item.title ?? "", - ); - } else { - // Add shortcut - if (shortcuts.length >= - shortcutsNotifier.maxShortcuts) { - context.pop(); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: - Text( - "Max number of shortcuts reached"))); - } else { - shortcutsNotifier.addShortcut( - item.title ?? "", - ); - } - } - }, - bgColor: shortcuts.any((element) => - element == item.title) - ? theme.colorScheme.secondary - : item.shortcutBackgroundColor, + void _showDialog(BuildContext context, List roles) { + final theme = Theme.of(context); + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + icon: const Icon(Icons.construction), + title: Text( + "Select Shortcut MenuOptions", + style: theme.textTheme.titleMedium, + ), + content: SizedBox( + width: double.maxFinite, + height: MediaQuery.of(context).size.height * 0.5, + child: Consumer( + builder: (context, ref, child) { + final userProgram = ref.watch(userProgramProvider); + final shortcuts = ref.watch(shortcutProvider); + final shortcutsNotifier = ref.watch(shortcutProvider.notifier); + return userProgram.when( + data: (data) => MenuItemsBuilder( + itemBuilder: (item) => MenuOption( + title: item.title ?? "", + icon: item.shortcutIcon, + onPress: () { + if (shortcuts.contains(item.title)) { + // Delete shortcut + shortcutsNotifier.deleteShortcut(item.title ?? ""); + } else { + // Add shortcut + if (shortcuts.length >= shortcutsNotifier.maxShortcuts) { + context.pop(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Max number of shortcuts reached"), ), - items: [ - // get generic menu items - ...getGenericMenuItems(context), - // get program menu items - ...data.where((element) => element.isActive).map((e) { - final programCode = e.id; - return getProgramMenuItemByProgramCode( - context, programCode ?? ''); - }) - ], - ), - error: (error, _) => Center(child: Text(error.toString())), - loading: () => - const Center( - child: CircularProgressIndicator(), + ); + } else { + shortcutsNotifier.addShortcut(item.title ?? ""); + } + } + }, + bgColor: shortcuts.contains(item.title) + ? theme.colorScheme.secondary + : item.shortcutBackgroundColor, ), - ); - }, - ), + items: [ + // get generic menu items + ...getGenericMenuItems(context, roles), + // get program menu items + ...data.where((element) => element.isActive).map((e) { + final programCode = e.id; + return getProgramMenuItemByProgramCode(context, programCode ?? ''); + }), + ], + ), + error: (error, _) => Center(child: Text(error.toString())), + loading: () => const Center(child: CircularProgressIndicator()), + ); + }, ), - actions: [Button(title: "Ok", onPress: () { - context.pop(); - },) - ], ), - ); + actions: [ + Button( + title: "Ok", + onPress: () { + context.pop(); + }, + ), + ], + ), + ); + } } diff --git a/lib/src/features/confirm_delivery/presentation/pages/ConfirmDeliveryScreen.dart b/lib/src/features/confirm_delivery/presentation/pages/ConfirmDeliveryScreen.dart index 583cba68..46f33669 100644 --- a/lib/src/features/confirm_delivery/presentation/pages/ConfirmDeliveryScreen.dart +++ b/lib/src/features/confirm_delivery/presentation/pages/ConfirmDeliveryScreen.dart @@ -9,7 +9,7 @@ import 'package:nishauri/src/features/auth/data/providers/auth_provider.dart'; import 'package:nishauri/src/features/confirm_delivery/data/modules/confirm_delivery.dart'; import 'package:nishauri/src/features/confirm_delivery/data/providers/confirm_delivery_provider.dart'; import 'package:nishauri/src/features/dawa_drop/presentation/pages/request_order/DrugOrders.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/shared/display/Logo.dart'; import 'package:nishauri/src/shared/display/verify.dart'; import 'package:nishauri/src/shared/exeptions/http_exceptions.dart'; @@ -136,7 +136,7 @@ class ConfirmDeliveryScreen extends HookConsumerWidget { builder: (context, ref, child) { return Button( title: "Confirm Delivery", - backgroundColor: const Color.fromRGBO(64, 87, 162, 1), + backgroundColor: Constants.dawaDropShortcutBgColor, textColor: Colors.white, onPress: handleSubmit, loading: _loading, diff --git a/lib/src/features/dashboard/presentation/widgets/GeneralDashboard.dart b/lib/src/features/dashboard/presentation/widgets/GeneralDashboard.dart index 3febd973..0f921509 100644 --- a/lib/src/features/dashboard/presentation/widgets/GeneralDashboard.dart +++ b/lib/src/features/dashboard/presentation/widgets/GeneralDashboard.dart @@ -1,20 +1,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:nishauri/src/features/blood_sugar/data/providers/blood_sugar_provider.dart'; -import 'package:nishauri/src/features/blood_sugar/presentation/widgets/blood_suger_trend_chart.dart'; -import 'package:nishauri/src/features/bmi/data/providers/bmi_log_provider.dart'; -import 'package:nishauri/src/features/bmi/presentation/widgets/BMILineGraph.dart'; -import 'package:nishauri/src/features/bp/data/providers/blood_pressure_provider.dart'; -import 'package:nishauri/src/features/bp/presentation/pages/trend_chart_screen.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/providers/bmi_filter_provider.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/providers/bmi_log_provider.dart'; +import 'package:nishauri/src/features/self_screening/bmi/presentation/widgets/BMILineGraph.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/providers/blood_pressure_provider.dart'; +import 'package:nishauri/src/features/self_screening/bp/presentation/pages/trend_chart_screen.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/providers/blood_sugar_provider.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/presentation/widgets/blood_suger_trend_chart.dart'; class GeneralDashboard extends ConsumerWidget { const GeneralDashboard({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final bmiAsync = ref.watch(bmiListProvider); - final bloodPressureAsync = ref.watch(bloodPressureListProvider); - final bloodSugarAsync = ref.watch(bloodSugarEntriesProvider); + final bmiAsync = ref.watch(bmiFilterProvider); + final bloodPressureAsync = ref.watch(bpFilterProvider); + final bsFilterListAsync = ref.watch(bsFilterListProvider); final theme = Theme.of(context); return Scaffold( @@ -31,8 +32,7 @@ class GeneralDashboard extends ConsumerWidget { Expanded( child: bmiAsync.when( data: (bmiData) { - final displayedData = bmiData.length > 5 ? bmiData.sublist(bmiData.length - 5) : bmiData; - return BMILineGraph(data: displayedData); + return BMILineGraph(data: bmiData, filter: "Week",); }, loading: () => Center(child: CircularProgressIndicator()), @@ -55,9 +55,8 @@ class GeneralDashboard extends ConsumerWidget { Expanded( child: bloodPressureAsync.when( data: (bpData) { - final displayedData = bpData.length > 5 ? bpData.sublist(bpData.length - 5) : bpData; - return TrendChartScreen(data: displayedData); + return TrendChartScreen(data: bpData, filter: 'Week',); }, loading: () => Center(child: CircularProgressIndicator()), error: (error, _) => Center( @@ -84,10 +83,9 @@ class GeneralDashboard extends ConsumerWidget { children: [ Text("Blood Sugar Trend", style: theme.textTheme.titleMedium), Expanded( - child: bloodSugarAsync.when( - data: (bSugarData) { - final displayedData = bSugarData.length > 5 ? bSugarData.sublist(bSugarData.length - 5) : bSugarData; - return BloodSugarTrendChart(data: displayedData); + child: bsFilterListAsync.when( + data: (bSFilteredData) { + return BloodSugarTrendChart(data: bSFilteredData); }, loading: () => const Center(child: CircularProgressIndicator()), @@ -112,3 +110,4 @@ class GeneralDashboard extends ConsumerWidget { ); } } + diff --git a/lib/src/features/dawa_drop/presentation/pages/dawa_drop_menu.dart b/lib/src/features/dawa_drop/presentation/pages/dawa_drop_menu.dart index ce57d617..c6cf3c29 100644 --- a/lib/src/features/dawa_drop/presentation/pages/dawa_drop_menu.dart +++ b/lib/src/features/dawa_drop/presentation/pages/dawa_drop_menu.dart @@ -4,7 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:nishauri/src/app/navigation/menu/MenuItemsBuilder.dart'; import 'package:nishauri/src/app/navigation/menu/MenuOption.dart'; import 'package:nishauri/src/app/navigation/menu/menuItems.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/utils/constants.dart'; import 'package:nishauri/src/utils/routes.dart'; diff --git a/lib/src/features/dawa_drop/presentation/pages/dawa_drop_screen.dart b/lib/src/features/dawa_drop/presentation/pages/dawa_drop_screen.dart index 28e2538c..4e8e83c8 100644 --- a/lib/src/features/dawa_drop/presentation/pages/dawa_drop_screen.dart +++ b/lib/src/features/dawa_drop/presentation/pages/dawa_drop_screen.dart @@ -6,7 +6,7 @@ import 'package:nishauri/src/app/navigation/menu/MenuOption.dart'; import 'package:nishauri/src/app/navigation/menu/menuItems.dart'; import 'package:nishauri/src/features/dawa_drop/presentation/pages/dawa_drop_menu.dart'; import 'package:nishauri/src/features/dawa_drop/presentation/widget/DawaDropGetStartedWidget.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/shared/display/meds.dart'; import 'package:nishauri/src/utils/constants.dart'; diff --git a/lib/src/features/dawa_drop/presentation/pages/dispatched_drugs.dart b/lib/src/features/dawa_drop/presentation/pages/dispatched_drugs.dart index 72447c99..99087c0c 100644 --- a/lib/src/features/dawa_drop/presentation/pages/dispatched_drugs.dart +++ b/lib/src/features/dawa_drop/presentation/pages/dispatched_drugs.dart @@ -7,7 +7,7 @@ import 'package:intl/intl.dart'; import 'package:nishauri/src/features/dawa_drop/data/models/order_request/drug_order.dart'; import 'package:nishauri/src/features/dawa_drop/data/providers/drug_order_provider.dart'; import 'package:nishauri/src/local_storage/LocalStorage.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/shared/display/background_image_widget.dart'; import 'package:nishauri/src/shared/interfaces/notification_service.dart'; import 'package:nishauri/src/utils/constants.dart'; diff --git a/lib/src/features/dawa_drop/presentation/pages/program_appointments.dart b/lib/src/features/dawa_drop/presentation/pages/program_appointments.dart index 045a007d..04b4d37b 100644 --- a/lib/src/features/dawa_drop/presentation/pages/program_appointments.dart +++ b/lib/src/features/dawa_drop/presentation/pages/program_appointments.dart @@ -3,7 +3,7 @@ import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:nishauri/src/features/appointments/data/providers/appointment_provider.dart'; import 'package:nishauri/src/features/dawa_drop/data/providers/drug_order_provider.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/shared/display/background_image_widget.dart'; import 'package:nishauri/src/utils/constants.dart'; import 'package:nishauri/src/utils/routes.dart'; @@ -57,10 +57,17 @@ class ProgramAppointmentsScreen extends ConsumerWidget { final bool hasActiveRequest = appointment.id != null && orderAsync.when( data: (orders) => orders.any((order) => - order.appointment?.id == appointment.id), + order.appointment?.id == appointment.id && order.status != "Fullfilled"), loading: () => false, error: (_, __) => false, ); + // Check if appointment is fulfilled + final bool isFulfilled = orderAsync.when( + data: (orders) => orders.any((order) => + order.appointment?.id == appointment.id && order.status == "Fullfilled"), + loading: () => false, + error: (_, __) => false, + ); final bool eligibleAppointment = appointment.id != null && orderAsync.when( @@ -88,7 +95,7 @@ class ProgramAppointmentsScreen extends ConsumerWidget { filteredAppointments[index] .program_name ?? '', - style: theme.textTheme.headline6, + style: theme.textTheme.titleSmall, overflow: TextOverflow.ellipsis, maxLines: 1, ), @@ -143,21 +150,21 @@ class ProgramAppointmentsScreen extends ConsumerWidget { height: Constants.SPACING), // Display text based on whether there is an active request Text( - hasActiveRequest + isFulfilled + ? "Appointment order has already been fulfilled" + : hasActiveRequest ? "Appointment has an active request" - : appointment - .appointment_status == - 1 - ? "Request Home delivery" - : "", + : appointment.appointment_status == 1 + ? "Request Home delivery" + : "", style: TextStyle( - color: hasActiveRequest + color: isFulfilled + ? Colors.grey // Adjust color for fulfilled status + : hasActiveRequest ? Constants.appointmentsColor - : appointment - .appointment_status == - 1 - ? Constants.clinicCardColor - : Colors.transparent, + : appointment.appointment_status == 1 + ? Constants.clinicCardColor + : Colors.transparent, ), ), ], diff --git a/lib/src/features/dawa_drop/presentation/pages/request_drug.dart b/lib/src/features/dawa_drop/presentation/pages/request_drug.dart index d8d562e7..606ff7f1 100644 --- a/lib/src/features/dawa_drop/presentation/pages/request_drug.dart +++ b/lib/src/features/dawa_drop/presentation/pages/request_drug.dart @@ -3,7 +3,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:nishauri/src/app/navigation/menu/MenuItemsBuilder.dart'; import 'package:nishauri/src/app/navigation/menu/menuItems.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/utils/constants.dart'; import 'package:nishauri/src/utils/routes.dart'; diff --git a/lib/src/features/dawa_drop/presentation/pages/request_order/DrugOrderWizardFormScreen.dart b/lib/src/features/dawa_drop/presentation/pages/request_order/DrugOrderWizardFormScreen.dart index f4fcbcae..e09d0c16 100644 --- a/lib/src/features/dawa_drop/presentation/pages/request_order/DrugOrderWizardFormScreen.dart +++ b/lib/src/features/dawa_drop/presentation/pages/request_order/DrugOrderWizardFormScreen.dart @@ -15,7 +15,7 @@ import 'package:nishauri/src/features/dawa_drop/presentation/pages/request_order import 'package:nishauri/src/features/dawa_drop/presentation/pages/request_order/forms/ReviewAndSubmit.dart'; import 'package:nishauri/src/features/hiv/data/models/event/art_event.dart'; import 'package:nishauri/src/shared/display/AppCard.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/shared/input/Button.dart'; import 'package:nishauri/src/utils/constants.dart'; import 'package:nishauri/src/utils/routes.dart'; diff --git a/lib/src/features/dawa_drop/presentation/pages/request_order/DrugOrders.dart b/lib/src/features/dawa_drop/presentation/pages/request_order/DrugOrders.dart index 25400310..d5d6a2de 100644 --- a/lib/src/features/dawa_drop/presentation/pages/request_order/DrugOrders.dart +++ b/lib/src/features/dawa_drop/presentation/pages/request_order/DrugOrders.dart @@ -7,7 +7,7 @@ import 'package:nishauri/src/features/dawa_drop/data/providers/drug_order_provid import 'package:nishauri/src/features/dawa_drop/presentation/widget/orders/FulfilledOrders.dart'; import 'package:nishauri/src/features/dawa_drop/presentation/widget/orders/active_orders.dart'; import 'package:nishauri/src/shared/display/CustomTabBar.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/shared/display/background_image_widget.dart'; import 'package:nishauri/src/utils/constants.dart'; diff --git a/lib/src/features/dawa_drop/presentation/widget/orders/DeliveryProgression.dart b/lib/src/features/dawa_drop/presentation/widget/orders/DeliveryProgression.dart index dde2cd63..cb9186c0 100644 --- a/lib/src/features/dawa_drop/presentation/widget/orders/DeliveryProgression.dart +++ b/lib/src/features/dawa_drop/presentation/widget/orders/DeliveryProgression.dart @@ -6,7 +6,7 @@ import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'package:nishauri/src/features/dawa_drop/data/models/order_request/drug_order.dart'; import 'package:nishauri/src/features/dawa_drop/data/providers/drug_order_provider.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/utils/constants.dart'; import 'package:nishauri/src/utils/routes.dart'; diff --git a/lib/src/features/dawa_drop/presentation/widget/orders/FulfilledOrders.dart b/lib/src/features/dawa_drop/presentation/widget/orders/FulfilledOrders.dart index 6d372f25..14140394 100644 --- a/lib/src/features/dawa_drop/presentation/widget/orders/FulfilledOrders.dart +++ b/lib/src/features/dawa_drop/presentation/widget/orders/FulfilledOrders.dart @@ -87,7 +87,7 @@ class FulfilledOrders extends StatelessWidget { color: Constants.dawaDropColor.withOpacity(0.5), ), const SizedBox(width: Constants.SPACING), - Text("Deliver Person: ${order.deliveryPerson?.fullName ?? ''}"), + Text("Delivery Person: ${order.deliveryPerson?.fullName ?? ''}"), ], ), if (order.deliveryPerson?.phoneNumber != null && order.deliveryPerson?.phoneNumber != '') @@ -99,7 +99,7 @@ class FulfilledOrders extends StatelessWidget { color: Constants.dawaDropColor.withOpacity(0.5), ), const SizedBox(width: Constants.SPACING), - Text("Deliver Person Phone: ${order.deliveryPerson?.phoneNumber ?? ''}"), + Text("Delivery Person Phone: ${order.deliveryPerson?.phoneNumber ?? ''}"), ], ), if (order.status != null && order.status != '') @@ -111,7 +111,7 @@ class FulfilledOrders extends StatelessWidget { color: Constants.dawaDropColor.withOpacity(0.5), ), const SizedBox(width: Constants.SPACING), - Text("Deliver Status: ${order.status ?? ''}"), + Text("Delivery Status: ${order.status ?? ''}"), ], ), ], diff --git a/lib/src/features/dawa_drop/presentation/widget/orders/active_orders.dart b/lib/src/features/dawa_drop/presentation/widget/orders/active_orders.dart index 33fac323..33a60b4c 100644 --- a/lib/src/features/dawa_drop/presentation/widget/orders/active_orders.dart +++ b/lib/src/features/dawa_drop/presentation/widget/orders/active_orders.dart @@ -90,7 +90,7 @@ class ActiveOrders extends StatelessWidget { color: Constants.dawaDropColor.withOpacity(0.5), ), const SizedBox(width: Constants.SPACING), - Text("Deliver Person: ${order.deliveryPerson?.fullName ?? ''}"), + Text("Delivery Person: ${order.deliveryPerson?.fullName ?? ''}"), ], ), if (order.deliveryPerson?.phoneNumber != null && order.deliveryPerson?.phoneNumber != '') @@ -102,7 +102,7 @@ class ActiveOrders extends StatelessWidget { color: Constants.dawaDropColor.withOpacity(0.5), ), const SizedBox(width: Constants.SPACING), - Text("Deliver Person Phone: ${order.deliveryPerson?.phoneNumber ?? ''}"), + Text("Delivery Person Phone: ${order.deliveryPerson?.phoneNumber ?? ''}"), ], ), if (order.status != null && order.status != '') @@ -114,7 +114,7 @@ class ActiveOrders extends StatelessWidget { color: Constants.dawaDropColor.withOpacity(0.5), ), const SizedBox(width: Constants.SPACING), - Text("Deliver Status: ${order.status ?? ''}"), + Text("Delivery Status: ${order.status ?? ''}"), ], ), ], diff --git a/lib/src/features/hiv/data/services/art_drug_order_service.dart b/lib/src/features/hiv/data/services/art_drug_order_service.dart index e2c09e1a..a074b1d0 100644 --- a/lib/src/features/hiv/data/services/art_drug_order_service.dart +++ b/lib/src/features/hiv/data/services/art_drug_order_service.dart @@ -73,18 +73,6 @@ class ARTDrugOrderService extends HTTPService { } } - - // Future> getOrders() async { - // final response = await call(getOrders_, null); - // final responseString = await response.stream.bytesToString(); - // final Map programData = json.decode(responseString); - // final programs = (programData["programs"] as List) - // .map((e) => ARTDrugOrder.fromJson(e)) - // .toList(); - // print(programs.toString()); - // return programs; - // } - Future createOrder_(Map data) async { final id = await _repository.getUserId(); final tokenPair = await getCachedToken(); diff --git a/lib/src/features/hiv/presentation/pages/HIVMenu.dart b/lib/src/features/hiv/presentation/pages/HIVMenu.dart index 8662430e..17d874a5 100644 --- a/lib/src/features/hiv/presentation/pages/HIVMenu.dart +++ b/lib/src/features/hiv/presentation/pages/HIVMenu.dart @@ -3,7 +3,7 @@ import 'package:go_router/go_router.dart'; import 'package:nishauri/src/app/navigation/menu/MenuItemsBuilder.dart'; import 'package:nishauri/src/app/navigation/menu/MenuOption.dart'; import 'package:nishauri/src/app/navigation/menu/menuItems.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/utils/routes.dart'; _menuItems(BuildContext context) => [ diff --git a/lib/src/features/hiv/presentation/pages/RegimenHistory.dart b/lib/src/features/hiv/presentation/pages/RegimenHistory.dart index 6ad9668c..c550bff0 100644 --- a/lib/src/features/hiv/presentation/pages/RegimenHistory.dart +++ b/lib/src/features/hiv/presentation/pages/RegimenHistory.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:nishauri/src/features/hiv/data/providers/art_regimen_provider.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/shared/display/background_image_widget.dart'; import 'package:nishauri/src/utils/constants.dart'; diff --git a/lib/src/features/hiv/presentation/pages/appointments/ARTAppointmentDetail.dart b/lib/src/features/hiv/presentation/pages/appointments/ARTAppointmentDetail.dart index e2cff03e..7a0a23df 100644 --- a/lib/src/features/hiv/presentation/pages/appointments/ARTAppointmentDetail.dart +++ b/lib/src/features/hiv/presentation/pages/appointments/ARTAppointmentDetail.dart @@ -5,7 +5,7 @@ import 'package:intl/intl.dart'; import 'package:nishauri/src/features/appointments/data/models/appointment.dart'; import 'package:nishauri/src/features/dawa_drop/data/providers/drug_order_provider.dart'; import 'package:nishauri/src/features/hiv/data/models/appointment/art_appointment.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/shared/input/Button.dart'; import 'package:nishauri/src/utils/constants.dart'; import 'package:nishauri/src/utils/routes.dart'; @@ -19,7 +19,13 @@ class ARTAppointmentDetailScreen extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final orderAsync = ref.watch(drugOrderProvider); final bool hasActiveRequest = artAppointment.id != null && orderAsync.when( - data: (orders) => orders.any((order) => order.appointment?.id == artAppointment.id), + data: (orders) => orders.any((order) => order.appointment?.id == artAppointment.id && order.status != "Fullfilled"), + loading: () => false, + error: (_, __) => false, + ); + + final bool isFulfilled = artAppointment.id != null && orderAsync.when( + data: (orders) => orders.any((order) => order.appointment?.id == artAppointment.id && order.status == "Fullfilled"), loading: () => false, error: (_, __) => false, ); @@ -88,13 +94,16 @@ class ARTAppointmentDetailScreen extends ConsumerWidget { Padding( padding: const EdgeInsets.all(Constants.SPACING), child: Button( - onPress: hasActiveRequest + onPress: hasActiveRequest? null : isFulfilled ? null : () { context.goNamed(RouteNames.HIV_ART_DELIVERY_REQUEST_FORM, extra: {"payload": artAppointment, "type": "self"}); }, - title: hasActiveRequest + title: isFulfilled + ? "Appointment order has already been fulfilled" + : + hasActiveRequest ? "Active request available for the appointment" : "Request Home delivery", ), diff --git a/lib/src/features/hiv/presentation/pages/events/ARTEventDetail.dart b/lib/src/features/hiv/presentation/pages/events/ARTEventDetail.dart index bef13cbb..62a2dff3 100644 --- a/lib/src/features/hiv/presentation/pages/events/ARTEventDetail.dart +++ b/lib/src/features/hiv/presentation/pages/events/ARTEventDetail.dart @@ -94,6 +94,7 @@ class ARTEventDetailScreen extends HookConsumerWidget { final artEvent = artEvents.where((element) => element.id == eventId).first; return ProfileCard( + color: Colors.black54, icon: Icons.event, coverPhoto: "https://picsum.photos/seed/picsum/757/300", buildItem: (context, item) => item, diff --git a/lib/src/features/hiv/presentation/pages/groups/ARTGroupDetail.dart b/lib/src/features/hiv/presentation/pages/groups/ARTGroupDetail.dart index cde0f348..b7b61530 100644 --- a/lib/src/features/hiv/presentation/pages/groups/ARTGroupDetail.dart +++ b/lib/src/features/hiv/presentation/pages/groups/ARTGroupDetail.dart @@ -30,6 +30,7 @@ class ARTGroupDetailScreen extends StatelessWidget { final artGroup = artGroups.where((element) => element.id == groupId).first; return ProfileCard( + color: Colors.black54, icon: Icons.group, coverPhoto: "https://picsum.photos/seed/picsum/200/300", buildItem: (context, item) => item, diff --git a/lib/src/features/lab/presentation/pages/LabResultsScreen.dart b/lib/src/features/lab/presentation/pages/LabResultsScreen.dart index 61b38d12..be54d10f 100644 --- a/lib/src/features/lab/presentation/pages/LabResultsScreen.dart +++ b/lib/src/features/lab/presentation/pages/LabResultsScreen.dart @@ -1,19 +1,11 @@ -import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:nishauri/src/features/hiv/presentation/pages/lab_results/art_lab_results.dart'; -import 'package:nishauri/src/features/lab/data/providers/VirolLoadprovider.dart'; -import 'package:nishauri/src/features/lab/presentation/pages/LabResults.dart'; -import 'package:nishauri/src/features/lab/presentation/widget/ViralLoadResult.dart'; -import 'package:nishauri/src/features/lab/presentation/widget/ViralLoadTrend.dart'; import 'package:nishauri/src/features/user_programs/data/providers/program_provider.dart'; import 'package:nishauri/src/shared/display/CustomTabBar.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/utils/constants.dart'; import 'package:nishauri/src/shared/display/background_image_widget.dart'; import 'package:nishauri/src/utils/routes.dart'; @@ -52,7 +44,7 @@ class LabResultsScreen extends HookConsumerWidget { body: Column( children: [ const CustomAppBar( - title: "Lab Results 🧪", + title: "Lab Results 🌡️", // icon: FontAwesomeIcons.vial, subTitle: "Unlock you health insights with lab results", color: Constants.labResultsColor, diff --git a/lib/src/features/lab/presentation/widget/ViralLoadResult.dart b/lib/src/features/lab/presentation/widget/ViralLoadResult.dart index 436c945b..4f8efde9 100644 --- a/lib/src/features/lab/presentation/widget/ViralLoadResult.dart +++ b/lib/src/features/lab/presentation/widget/ViralLoadResult.dart @@ -23,7 +23,7 @@ class ViralLoadResults extends StatelessWidget { child: ListView.builder( itemCount: data.length, itemBuilder: (BuildContext context, int index) { - final color = data[index].status == "Viral unsuppressed" + final color = data[index].status == "Virally Unsuppressed" ? Colors.red : Colors.green; return Column( @@ -54,9 +54,9 @@ class ViralLoadResults extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - data[index].status == "Viral unsuppressed" - ? 'Viral unsuppressed (${data[index].plot})' - : 'Viral Suppressed (${data[index].plot})', + data[index].status == "Virally Unsuppressed" + ? 'Virally Unsuppressed (${data[index].plot})' + : 'Virally Suppressed (${data[index].plot})', style: theme.textTheme.headline6, ), const Divider(), @@ -64,7 +64,7 @@ class ViralLoadResults extends StatelessWidget { ], ), content: Text( - data[index].status == "Viral unsuppressed" + data[index].status == "Virally Unsuppressed" ? 'This could mean the beginning of treatment failure. Kindly visit your doctor/healthcare provider as soon as possible!' : 'This means you are adhering to your treatment well. Continue taking your medication as advised by your doctor/healthcare provider.', ), diff --git a/lib/src/features/nishauri_chat/chat/models/chat_hcw.dart b/lib/src/features/nishauri_chat/chat/models/chat_hcw.dart new file mode 100644 index 00000000..61448309 --- /dev/null +++ b/lib/src/features/nishauri_chat/chat/models/chat_hcw.dart @@ -0,0 +1,16 @@ +import 'package:flutter/cupertino.dart'; + +class ChatUsers{ + String name; + String messageText; + String imageURL; + String time; + + ChatUsers( + { + required this.name, + required this.messageText, + required this.imageURL, + required this.time + }); +} \ No newline at end of file diff --git a/lib/src/features/nishauri_chat/chat/models/chat_message.dart b/lib/src/features/nishauri_chat/chat/models/chat_message.dart new file mode 100644 index 00000000..2b6402ba --- /dev/null +++ b/lib/src/features/nishauri_chat/chat/models/chat_message.dart @@ -0,0 +1,7 @@ +import 'package:flutter/cupertino.dart'; + +class ChatMessage{ + String messageContent; + String messageType; + ChatMessage({required this.messageContent, required this.messageType}); +} \ No newline at end of file diff --git a/lib/src/features/nishauri_chat/chat/presentation/pages/ChatDetailScreen.dart b/lib/src/features/nishauri_chat/chat/presentation/pages/ChatDetailScreen.dart new file mode 100644 index 00000000..0255086b --- /dev/null +++ b/lib/src/features/nishauri_chat/chat/presentation/pages/ChatDetailScreen.dart @@ -0,0 +1,141 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nishauri/src/features/nishauri_chat/chat/models/chat_message.dart'; +import 'package:nishauri/src/utils/helpers.dart'; + +class ChatDetailScreen extends StatefulWidget{ + ChatDetailScreen({Key?key}) : super (key: key); + @override + _ChatDetailScreenState createState() => _ChatDetailScreenState(); +} +class _ChatDetailScreenState extends State { + List messages = [ + ChatMessage(messageContent: "Hello, Will", messageType: "receiver"), + ChatMessage(messageContent: "How have you been?", messageType: "receiver"), + ChatMessage(messageContent: "Hey Kriss, I am doing fine dude. wbu?", messageType: "sender"), + ChatMessage(messageContent: "ehhhh, doing OK.", messageType: "receiver"), + ChatMessage(messageContent: "Is there any thing wrong?", messageType: "sender"), + ]; + @override + Widget build(BuildContext context){ + final theme = Theme.of(context); + final size = getOrientationAwareScreenSize(context); + return Scaffold( + appBar: AppBar( + elevation: 0, + automaticallyImplyLeading: false, + backgroundColor: Colors.white, + flexibleSpace: SafeArea( + child: Container( + padding: EdgeInsets.only(right: 16), + child: Row( + children: [ + IconButton(onPressed: (){ + Navigator.pop(context); + }, + icon: Icon(Icons.arrow_back, color: Colors.black,)), + SizedBox(width: 2,), + CircleAvatar( + backgroundImage: AssetImage("assets/images/chat/userImage1.png"), + maxRadius: 20, + ), + SizedBox( + width: 12, + ), + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("Charles Mabel (Kiseuni Dispensary)", style: theme.textTheme.titleSmall,), + SizedBox(height: 6,), + Text("Online", style: TextStyle(color: Colors.green.shade600, fontSize: 13),), + ], + )), + Icon(Icons.settings, color: Colors.black54,), + ], + ), + ), + ), + ), + body: Stack( + children: [ + Positioned( + top: 0, + right: 0, + child: SvgPicture.asset( + "assets/images/rect-bg.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + height: size.width * 0.55, + width: size.width * 0.55, + ) + ), + ListView.builder( + itemCount: messages.length, + shrinkWrap: true, + padding: EdgeInsets.only(top: 10,bottom: 10), + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index){ + return Container( + padding: EdgeInsets.only(left: 14,right: 14,top: 10,bottom: 10), + child: Align( + alignment: (messages[index].messageType == "receiver"?Alignment.topLeft:Alignment.topRight), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: (messages[index].messageType == "receiver" ? Colors.grey.shade200:Colors.blue[200]), + ), + padding: EdgeInsets.all(16), + child: Text(messages[index].messageContent, style: theme.textTheme.bodySmall,), + ), + ) + ); + }, + ), + Align( + alignment: Alignment.bottomLeft, + child: Container( + padding: EdgeInsets.only(left: 10,bottom: 10,top: 10), + height: 60, + width: double.infinity, + color: Colors.white, + child: Row( + children: [ + GestureDetector( + onTap: (){ + + }, + child: Container( + height: 30, + width: 30, + decoration: BoxDecoration( + color: Colors.lightBlue, + borderRadius: BorderRadius.circular(30), + ), + child: Icon(Icons.add, color: Colors.white, size: 20,), + ), + ), + SizedBox(width: 15,), + Expanded(child: TextField( + decoration: InputDecoration( + hintText: "Message", + hintStyle: TextStyle(color: Colors.black54), + border: InputBorder.none + ), + ), + ), + SizedBox(width: 15,), + FloatingActionButton(onPressed: (){}, + child: Icon(Icons.send, color: Colors.white, size:18,), + backgroundColor: Colors.blue, + elevation: 0, + ), + ], + ), + ), + ) + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/features/nishauri_chat/chat/presentation/pages/ChatUserList.dart b/lib/src/features/nishauri_chat/chat/presentation/pages/ChatUserList.dart new file mode 100644 index 00000000..30430637 --- /dev/null +++ b/lib/src/features/nishauri_chat/chat/presentation/pages/ChatUserList.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nishauri/src/features/nishauri_chat/chat/models/chat_hcw.dart'; +import 'package:nishauri/src/features/nishauri_chat/chat/presentation/widget/UserList.dart'; +import 'package:nishauri/src/features/nishauri_chat/chat/presentation/widget/conversationList.dart'; +import 'package:nishauri/src/shared/input/Search.dart'; +import 'package:nishauri/src/utils/helpers.dart'; + +class ChatUserListScreen extends StatefulWidget { + + const ChatUserListScreen({Key? key}) : super(key: key); + + @override + _ChatUserListScreenState createState() => _ChatUserListScreenState(); +} + +class _ChatUserListScreenState extends State { + List chatUsers = [ + ChatUsers(name: "Jane Kamau (Kenyatta Hospital)", messageText: "ART Program", imageURL: "assets/images/chat/userImage1.png", time: "online"), + ChatUsers(name: "Glady's Murphy (Kenyatta Hospital)", messageText: "Malaria Program", imageURL: "assets/images/chat/userImage2.png", time: "offline"), + ChatUsers(name: "Jorge Henry (Kenyatta Hospital)", messageText: "TB Program", imageURL: "assets/images/chat/userImage3.png", time: "offline"), + ChatUsers(name: "Philip Okeyo (Kenyatta Hospital)", messageText: "MCH Program", imageURL: "assets/images/chat/userImage4.png", time: "online"), + ChatUsers(name: "Debra Joho (Kenyatta Hospital)", messageText: "Some Program", imageURL: "assets/images/chat/userImage5.png", time: "offline"), + ]; + @override + Widget build(BuildContext context) { + final size = getOrientationAwareScreenSize(context); + final theme = Theme.of(context); + final searchText = "Search name or program"; + return Scaffold( + body: Stack( + children: [ + Positioned( + top: 0, + right: 0, + child: SvgPicture.asset( + "assets/images/rect-bg.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + height: size.width * 0.55, + width: size.width * 0.55, + ) + ), + SingleChildScrollView( + physics: BouncingScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SafeArea(child: Padding( + padding: EdgeInsets.only(left: 16,right: 16,top: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + IconButton(onPressed: (){ + Navigator.pop(context); + }, + icon: Icon(Icons.arrow_back,)), + Text("Select contact", style:theme.textTheme.titleLarge,), + // Container( + // padding: EdgeInsets.only(left: 8,right: 8,top: 2,bottom: 2), + // height: 30, + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(30), + // color: Colors.pink[50], + // ), + // child: Row( + // children: [ + // Icon(Icons.search, color: Colors.grey.shade600, size: 20,), + // SizedBox(width: 2,), + // ], + // ), + // ) + ], + ), + )), + Padding( + padding: EdgeInsets.only(top: 16,left: 16,right: 16), + child: Search(searchText: searchText,), + ), + ListView.builder( + itemCount: chatUsers.length, + shrinkWrap: true, + padding: EdgeInsets.only(top: 16), + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index){ + return UserHCWList( + name: chatUsers[index].name, + messageText: chatUsers[index].messageText, + imageURL: chatUsers[index].imageURL, + time: chatUsers[index].time, + isActive: (index == 0 || index == 3) ? true:false, + ); + } + ) + ], + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/features/nishauri_chat/chat/presentation/pages/ConversationList.dart b/lib/src/features/nishauri_chat/chat/presentation/pages/ConversationList.dart new file mode 100644 index 00000000..b5c29da6 --- /dev/null +++ b/lib/src/features/nishauri_chat/chat/presentation/pages/ConversationList.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:nishauri/src/features/nishauri_chat/chat/models/chat_hcw.dart'; +import 'package:nishauri/src/features/nishauri_chat/chat/presentation/widget/conversationList.dart'; +import 'package:nishauri/src/shared/input/Search.dart'; +import 'package:nishauri/src/utils/helpers.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +class ChatHCWScreen extends StatefulWidget { + + const ChatHCWScreen({Key? key}) : super(key: key); + + @override + _ChatHCWScreenState createState() => _ChatHCWScreenState(); +} + +class _ChatHCWScreenState extends State { + List chatUsers = [ + ChatUsers(name: "Jane Kamau (Kenyatta Hospital)", messageText: "Did you get it?", imageURL: "assets/images/chat/userImage1.png", time: "Now"), + ChatUsers(name: "Glady's Murphy (Kenyatta Hospital)", messageText: "That's Great", imageURL: "assets/images/chat/userImage2.png", time: "Yesterday"), + ChatUsers(name: "Jorge Henry (Kenyatta Hospital)", messageText: "Come over next week?", imageURL: "assets/images/chat/userImage3.png", time: "31 Mar"), + ChatUsers(name: "Philip Okeyo (Kenyatta Hospital)", messageText: "We are updating the appointments", imageURL: "assets/images/chat/userImage4.png", time: "28 Mar"), + ChatUsers(name: "Debra Joho (Kenyatta Hospital)", messageText: "Thankyou, It's awesome", imageURL: "assets/images/chat/userImage5.png", time: "23 Mar"), + ChatUsers(name: "Jacob Otieno (Kenyatta Hospital)", messageText: "will update you in evening", imageURL: "assets/images/chat/userImage6.png", time: "17 Mar"), + ChatUsers(name: "Andrey Kinyanjui (Kenyatta Hospital)", messageText: "Can you please share the file?", imageURL: "assets/images/chat/userImage7.png", time: "24 Feb"), + ChatUsers(name: "John Otiende (Kenyatta Hospital)", messageText: "How are you?", imageURL: "assets/images/chat/userImage8.png", time: "18 Feb"), + ]; + @override + Widget build(BuildContext context) { + final size = getOrientationAwareScreenSize(context); + final theme = Theme.of(context); + return Scaffold( + body: Stack( + children: [ + Positioned( + top: 0, + right: 0, + child: SvgPicture.asset( + "assets/images/rect-bg.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + height: size.width * 0.55, + width: size.width * 0.55, + ) + ), + SingleChildScrollView( + physics: BouncingScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SafeArea(child: Padding( + padding: EdgeInsets.only(left: 16,right: 16,top: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton(onPressed: (){ + Navigator.pop(context); + }, + icon: Icon(Icons.arrow_back, color: Colors.black,)), + Text("Chat List", style:theme.textTheme.titleLarge,), + GestureDetector( + onTap: (){ + context.goNamed(RouteNames.CHAT_USER); + }, + child: Container( + padding: EdgeInsets.only(left: 8,right: 8,top: 2,bottom: 2), + height: 30, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: Colors.pink[50], + ), + child: Row( + children: [ + Icon(Icons.add, color: Colors.pink, size: 20,), + SizedBox(width: 2,), + Text("Add New", style: theme.textTheme.titleMedium,), + ], + ), + ) + ) + ], + ), + )), + Padding( + padding: EdgeInsets.only(top: 16,left: 16,right: 16), + child: Search(searchText: "Search name",), + ), + ListView.builder( + itemCount: chatUsers.length, + shrinkWrap: true, + padding: EdgeInsets.only(top: 16), + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index){ + return ConversationList( + name: chatUsers[index].name, + messageText: chatUsers[index].messageText, + imageURL: chatUsers[index].imageURL, + time: chatUsers[index].time, + isMessageRead: (index == 0 || index == 3) ? true:false, + ); + } + ) + ], + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/features/nishauri_chat/chat/presentation/widget/UserList.dart b/lib/src/features/nishauri_chat/chat/presentation/widget/UserList.dart new file mode 100644 index 00000000..737ca549 --- /dev/null +++ b/lib/src/features/nishauri_chat/chat/presentation/widget/UserList.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +class UserHCWList extends StatefulWidget { + String name; + String messageText; + String imageURL; + String time; + + bool isActive; + + UserHCWList({required this.name,required this.messageText,required this.imageURL,required this.time,required this.isActive}); + @override + _UserHCWListState createState() => _UserHCWListState(); +} + +class _UserHCWListState extends State { + @override + Widget build(BuildContext context){ + final theme = Theme.of(context); + return GestureDetector( + onTap: (){ + context.goNamed(RouteNames.CHAT_DETAIL); + }, + child: Container( + padding: EdgeInsets.only(left: 16,right: 16,top: 10,bottom: 10), + child: Row( + children: [ + CircleAvatar( + backgroundImage: AssetImage(widget.imageURL), + maxRadius: 30, + ), + SizedBox(width: 16,), + Expanded(child: Container( + color: Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(widget.name, style: theme.textTheme.bodyLarge,), + SizedBox(height: 6,), + Text(widget.messageText, style: theme.textTheme.titleSmall?.merge(TextStyle(color: theme.primaryColorLight))) + ], + ), + )), + Text(widget.time, style: TextStyle(fontSize: 12,fontWeight: widget.isActive?FontWeight.bold:FontWeight.normal, color: widget.isActive? Colors.green:Colors.grey.shade600),), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/features/nishauri_chat/chat/presentation/widget/conversationList.dart b/lib/src/features/nishauri_chat/chat/presentation/widget/conversationList.dart new file mode 100644 index 00000000..6453f071 --- /dev/null +++ b/lib/src/features/nishauri_chat/chat/presentation/widget/conversationList.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +class ConversationList extends StatefulWidget { + String name; + String messageText; + String imageURL; + String time; + + bool isMessageRead; + + ConversationList({required this.name,required this.messageText,required this.imageURL,required this.time,required this.isMessageRead}); + @override + _ConversationListState createState() => _ConversationListState(); +} + +class _ConversationListState extends State { + @override + Widget build(BuildContext context){ + final theme = Theme.of(context); + return GestureDetector( + onTap: (){ + context.goNamed(RouteNames.CHAT_DETAIL); + }, + child: Container( + padding: EdgeInsets.only(left: 16,right: 16,top: 10,bottom: 10), + child: Row( + children: [ + CircleAvatar( + backgroundImage: AssetImage(widget.imageURL), + maxRadius: 30, + ), + SizedBox(width: 16,), + Expanded(child: Container( + color: Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(widget.name, style: theme.textTheme.bodyMedium,), + SizedBox(height: 6,), + Text(widget.messageText, style: theme.textTheme.bodySmall) + ], + ), + )), + Text(widget.time, style: TextStyle(fontSize: 12,fontWeight: widget.isMessageRead?FontWeight.bold:FontWeight.normal),), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/features/period_planner/data/models/cycle.dart b/lib/src/features/period_planner/data/models/cycle.dart new file mode 100644 index 00000000..e7004596 --- /dev/null +++ b/lib/src/features/period_planner/data/models/cycle.dart @@ -0,0 +1,56 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'cycle.freezed.dart'; +part 'cycle.g.dart'; + +@Freezed() +class Cycle with _$Cycle { + const factory Cycle({ + required DateTime period_start, + required DateTime period_end, + required DateTime fertile_start, + required DateTime fertile_end, + required DateTime ovulation, + required DateTime predicted_period_start, + required DateTime predicted_period_end, + required int cycle_length, + required int period_length, + }) = _Cycle; + + factory Cycle.fromJson(Map json) => _$CycleFromJson(json); +} + +//Helper methods for JsSON serialization of DateTime +// String _dateTimeToJson(DateTime date) => date.toIso8601String(); + +// DateTime _dateTimeFromJson(String date) => DateTime.parse(date); + +// class Cycle{ +// String cycleId; +// DateTime periodStart; +// DateTime periodEnd; +// DateTime fertileStart; +// DateTime fertileEnd; +// DateTime ovulation; +// DateTime predictedPeriodStart; +// DateTime predictedPeriodEnd; +// int cycleLength; +// int periodLength; + +// Cycle({ +// required this.cycleId, +// required this.periodStart, +// required this.periodEnd, +// required this.fertileStart, +// required this.fertileEnd, +// required this.ovulation, +// required this.predictedPeriodStart, +// required this.predictedPeriodEnd, +// required this.cycleLength, +// required this.periodLength, +// }); +// } + +//Acting as database for now +// List cycles = []; + diff --git a/lib/src/features/period_planner/data/models/cycle.freezed.dart b/lib/src/features/period_planner/data/models/cycle.freezed.dart new file mode 100644 index 00000000..e3c08837 --- /dev/null +++ b/lib/src/features/period_planner/data/models/cycle.freezed.dart @@ -0,0 +1,330 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'cycle.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Cycle _$CycleFromJson(Map json) { + return _Cycle.fromJson(json); +} + +/// @nodoc +mixin _$Cycle { + DateTime get period_start => throw _privateConstructorUsedError; + DateTime get period_end => throw _privateConstructorUsedError; + DateTime get fertile_start => throw _privateConstructorUsedError; + DateTime get fertile_end => throw _privateConstructorUsedError; + DateTime get ovulation => throw _privateConstructorUsedError; + DateTime get predicted_period_start => throw _privateConstructorUsedError; + DateTime get predicted_period_end => throw _privateConstructorUsedError; + int get cycle_length => throw _privateConstructorUsedError; + int get period_length => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $CycleCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CycleCopyWith<$Res> { + factory $CycleCopyWith(Cycle value, $Res Function(Cycle) then) = + _$CycleCopyWithImpl<$Res, Cycle>; + @useResult + $Res call( + {DateTime period_start, + DateTime period_end, + DateTime fertile_start, + DateTime fertile_end, + DateTime ovulation, + DateTime predicted_period_start, + DateTime predicted_period_end, + int cycle_length, + int period_length}); +} + +/// @nodoc +class _$CycleCopyWithImpl<$Res, $Val extends Cycle> + implements $CycleCopyWith<$Res> { + _$CycleCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? period_start = null, + Object? period_end = null, + Object? fertile_start = null, + Object? fertile_end = null, + Object? ovulation = null, + Object? predicted_period_start = null, + Object? predicted_period_end = null, + Object? cycle_length = null, + Object? period_length = null, + }) { + return _then(_value.copyWith( + period_start: null == period_start + ? _value.period_start + : period_start // ignore: cast_nullable_to_non_nullable + as DateTime, + period_end: null == period_end + ? _value.period_end + : period_end // ignore: cast_nullable_to_non_nullable + as DateTime, + fertile_start: null == fertile_start + ? _value.fertile_start + : fertile_start // ignore: cast_nullable_to_non_nullable + as DateTime, + fertile_end: null == fertile_end + ? _value.fertile_end + : fertile_end // ignore: cast_nullable_to_non_nullable + as DateTime, + ovulation: null == ovulation + ? _value.ovulation + : ovulation // ignore: cast_nullable_to_non_nullable + as DateTime, + predicted_period_start: null == predicted_period_start + ? _value.predicted_period_start + : predicted_period_start // ignore: cast_nullable_to_non_nullable + as DateTime, + predicted_period_end: null == predicted_period_end + ? _value.predicted_period_end + : predicted_period_end // ignore: cast_nullable_to_non_nullable + as DateTime, + cycle_length: null == cycle_length + ? _value.cycle_length + : cycle_length // ignore: cast_nullable_to_non_nullable + as int, + period_length: null == period_length + ? _value.period_length + : period_length // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$CycleImplCopyWith<$Res> implements $CycleCopyWith<$Res> { + factory _$$CycleImplCopyWith( + _$CycleImpl value, $Res Function(_$CycleImpl) then) = + __$$CycleImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {DateTime period_start, + DateTime period_end, + DateTime fertile_start, + DateTime fertile_end, + DateTime ovulation, + DateTime predicted_period_start, + DateTime predicted_period_end, + int cycle_length, + int period_length}); +} + +/// @nodoc +class __$$CycleImplCopyWithImpl<$Res> + extends _$CycleCopyWithImpl<$Res, _$CycleImpl> + implements _$$CycleImplCopyWith<$Res> { + __$$CycleImplCopyWithImpl( + _$CycleImpl _value, $Res Function(_$CycleImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? period_start = null, + Object? period_end = null, + Object? fertile_start = null, + Object? fertile_end = null, + Object? ovulation = null, + Object? predicted_period_start = null, + Object? predicted_period_end = null, + Object? cycle_length = null, + Object? period_length = null, + }) { + return _then(_$CycleImpl( + period_start: null == period_start + ? _value.period_start + : period_start // ignore: cast_nullable_to_non_nullable + as DateTime, + period_end: null == period_end + ? _value.period_end + : period_end // ignore: cast_nullable_to_non_nullable + as DateTime, + fertile_start: null == fertile_start + ? _value.fertile_start + : fertile_start // ignore: cast_nullable_to_non_nullable + as DateTime, + fertile_end: null == fertile_end + ? _value.fertile_end + : fertile_end // ignore: cast_nullable_to_non_nullable + as DateTime, + ovulation: null == ovulation + ? _value.ovulation + : ovulation // ignore: cast_nullable_to_non_nullable + as DateTime, + predicted_period_start: null == predicted_period_start + ? _value.predicted_period_start + : predicted_period_start // ignore: cast_nullable_to_non_nullable + as DateTime, + predicted_period_end: null == predicted_period_end + ? _value.predicted_period_end + : predicted_period_end // ignore: cast_nullable_to_non_nullable + as DateTime, + cycle_length: null == cycle_length + ? _value.cycle_length + : cycle_length // ignore: cast_nullable_to_non_nullable + as int, + period_length: null == period_length + ? _value.period_length + : period_length // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$CycleImpl implements _Cycle { + const _$CycleImpl( + {required this.period_start, + required this.period_end, + required this.fertile_start, + required this.fertile_end, + required this.ovulation, + required this.predicted_period_start, + required this.predicted_period_end, + required this.cycle_length, + required this.period_length}); + + factory _$CycleImpl.fromJson(Map json) => + _$$CycleImplFromJson(json); + + @override + final DateTime period_start; + @override + final DateTime period_end; + @override + final DateTime fertile_start; + @override + final DateTime fertile_end; + @override + final DateTime ovulation; + @override + final DateTime predicted_period_start; + @override + final DateTime predicted_period_end; + @override + final int cycle_length; + @override + final int period_length; + + @override + String toString() { + return 'Cycle(period_start: $period_start, period_end: $period_end, fertile_start: $fertile_start, fertile_end: $fertile_end, ovulation: $ovulation, predicted_period_start: $predicted_period_start, predicted_period_end: $predicted_period_end, cycle_length: $cycle_length, period_length: $period_length)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CycleImpl && + (identical(other.period_start, period_start) || + other.period_start == period_start) && + (identical(other.period_end, period_end) || + other.period_end == period_end) && + (identical(other.fertile_start, fertile_start) || + other.fertile_start == fertile_start) && + (identical(other.fertile_end, fertile_end) || + other.fertile_end == fertile_end) && + (identical(other.ovulation, ovulation) || + other.ovulation == ovulation) && + (identical(other.predicted_period_start, predicted_period_start) || + other.predicted_period_start == predicted_period_start) && + (identical(other.predicted_period_end, predicted_period_end) || + other.predicted_period_end == predicted_period_end) && + (identical(other.cycle_length, cycle_length) || + other.cycle_length == cycle_length) && + (identical(other.period_length, period_length) || + other.period_length == period_length)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + period_start, + period_end, + fertile_start, + fertile_end, + ovulation, + predicted_period_start, + predicted_period_end, + cycle_length, + period_length); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$CycleImplCopyWith<_$CycleImpl> get copyWith => + __$$CycleImplCopyWithImpl<_$CycleImpl>(this, _$identity); + + @override + Map toJson() { + return _$$CycleImplToJson( + this, + ); + } +} + +abstract class _Cycle implements Cycle { + const factory _Cycle( + {required final DateTime period_start, + required final DateTime period_end, + required final DateTime fertile_start, + required final DateTime fertile_end, + required final DateTime ovulation, + required final DateTime predicted_period_start, + required final DateTime predicted_period_end, + required final int cycle_length, + required final int period_length}) = _$CycleImpl; + + factory _Cycle.fromJson(Map json) = _$CycleImpl.fromJson; + + @override + DateTime get period_start; + @override + DateTime get period_end; + @override + DateTime get fertile_start; + @override + DateTime get fertile_end; + @override + DateTime get ovulation; + @override + DateTime get predicted_period_start; + @override + DateTime get predicted_period_end; + @override + int get cycle_length; + @override + int get period_length; + @override + @JsonKey(ignore: true) + _$$CycleImplCopyWith<_$CycleImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/period_planner/data/models/cycle.g.dart b/lib/src/features/period_planner/data/models/cycle.g.dart new file mode 100644 index 00000000..a6e57965 --- /dev/null +++ b/lib/src/features/period_planner/data/models/cycle.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'cycle.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$CycleImpl _$$CycleImplFromJson(Map json) => _$CycleImpl( + period_start: DateTime.parse(json['period_start'] as String), + period_end: DateTime.parse(json['period_end'] as String), + fertile_start: DateTime.parse(json['fertile_start'] as String), + fertile_end: DateTime.parse(json['fertile_end'] as String), + ovulation: DateTime.parse(json['ovulation'] as String), + predicted_period_start: + DateTime.parse(json['predicted_period_start'] as String), + predicted_period_end: + DateTime.parse(json['predicted_period_end'] as String), + cycle_length: (json['cycle_length'] as num).toInt(), + period_length: (json['period_length'] as num).toInt(), + ); + +Map _$$CycleImplToJson(_$CycleImpl instance) => + { + 'period_start': instance.period_start.toIso8601String(), + 'period_end': instance.period_end.toIso8601String(), + 'fertile_start': instance.fertile_start.toIso8601String(), + 'fertile_end': instance.fertile_end.toIso8601String(), + 'ovulation': instance.ovulation.toIso8601String(), + 'predicted_period_start': + instance.predicted_period_start.toIso8601String(), + 'predicted_period_end': instance.predicted_period_end.toIso8601String(), + 'cycle_length': instance.cycle_length, + 'period_length': instance.period_length, + }; diff --git a/lib/src/features/period_planner/data/models/events.dart b/lib/src/features/period_planner/data/models/events.dart new file mode 100644 index 00000000..9dc2930f --- /dev/null +++ b/lib/src/features/period_planner/data/models/events.dart @@ -0,0 +1,16 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; + +//Event +class Event { + String title; + Color color; + + Event(this.title, this.color); + + @override + String toString() { + return 'Event(title: $title, color: $color)'; + } +} + diff --git a/lib/src/features/period_planner/data/models/logs.dart b/lib/src/features/period_planner/data/models/logs.dart new file mode 100644 index 00000000..68e177e4 --- /dev/null +++ b/lib/src/features/period_planner/data/models/logs.dart @@ -0,0 +1,14 @@ +// import 'package:freezed_annotation/freezed_annotation.dart'; + +// @Freezed() +// class Logs with _$Logs { +// const factory Logs({ +// final String id, +// final String moods, +// final String symptoms, +// final String discharge, +// final String timestamp, +// }) = _Logs; + +// factory Logs.fromJson(Map json)=> _$LogsFromJson(json); +// } \ No newline at end of file diff --git a/lib/src/features/period_planner/data/providers/cycles_provider.dart b/lib/src/features/period_planner/data/providers/cycles_provider.dart new file mode 100644 index 00000000..859e9f87 --- /dev/null +++ b/lib/src/features/period_planner/data/providers/cycles_provider.dart @@ -0,0 +1,43 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:nishauri/src/features/period_planner/data/repository/cycles_repository.dart'; +import 'package:nishauri/src/features/period_planner/data/services/cycles_service.dart'; +import 'package:nishauri/src/features/period_planner/presentation/controllers/cycles_controller.dart'; +import '../models/cycle.dart'; + +final cyclesProvider = StateNotifierProvider>> ((ref) { + final service = CyclesService(); + final repository = CyclesRepository(service); + return CyclesController(repository); +}); + +// final cyclesRepositoryProvider = Provider((ref) { +// return CyclesRepository(CyclesService()); +// }); + +// final cyclesListProvider = FutureProvider>((ref) async { +// final repository = ref.watch(cyclesRepositoryProvider); +// return await repository.fetchCycles(); +// }); + +// final cyclesProvider = StateNotifierProvider>((ref) { +// return CyclesNotifier(); +// }); + +// class CyclesNotifier extends StateNotifier> { +// CyclesNotifier():super(cycles); + +// void addCycle(Cycle newCycle) { +// state = [...state, newCycle]; +// } + +// void updatedCycle(String cycleId, Cycle updatedCycle) { +// state = [ +// for (final cycle in state) +// if (cycle.cycleId == cycleId) updatedCycle else cycle +// ]; +// } + +// // void removeCycle(String cycleId) { +// // state = state.where((cycle) => cycle.cycleId != cycleId).toList(); +// // } +// } \ No newline at end of file diff --git a/lib/src/features/period_planner/data/repository/cycles_repository.dart b/lib/src/features/period_planner/data/repository/cycles_repository.dart new file mode 100644 index 00000000..dfd69ad1 --- /dev/null +++ b/lib/src/features/period_planner/data/repository/cycles_repository.dart @@ -0,0 +1,24 @@ +import 'package:nishauri/src/features/period_planner/data/models/cycle.dart'; +import 'package:nishauri/src/features/period_planner/data/services/cycles_service.dart'; + +class CyclesRepository { + final CyclesService _service; + + CyclesRepository(this._service); + + Future postCycles(Map data) async { + return await _service.postCycles(data); + } + + Future> fetchCycles() async { + return await _service.fetchCycles(); + } + + Future putCycles(int cycleId, Map data) async{ + return await _service.putCycles(cycleId, data); + } + + Future deleteCycle(int cycleId) async { + return await _service.deleteCycle(cycleId); + } +} \ No newline at end of file diff --git a/lib/src/features/period_planner/data/services/cycles_service.dart b/lib/src/features/period_planner/data/services/cycles_service.dart new file mode 100644 index 00000000..8496c082 --- /dev/null +++ b/lib/src/features/period_planner/data/services/cycles_service.dart @@ -0,0 +1,190 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; +import 'package:nishauri/src/features/auth/data/respositories/auth_repository.dart'; +import 'package:nishauri/src/features/auth/data/services/AuthApiService.dart'; +import 'package:nishauri/src/features/period_planner/data/models/cycle.dart'; +import 'package:nishauri/src/shared/interfaces/HTTPService.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class CyclesService extends HTTPService { + final AuthRepository _repository = AuthRepository(AuthApiService()); + + Future postCycles_(Map data) async { + final id = await _repository.getUserId(); + final tokenPair = await getCachedToken(); + final userId = {'user_id': id}; + var headers = { + 'Authorization': 'Bearer ${tokenPair.accessToken}', + 'Content-Type': 'application/json', + }; + var url = '${Constants.BASE_URL_NEW}/menstrual_cycle'; + final payload = {...data, ...userId}; + debugPrint("Data payload: $payload"); + + final response = request( + url: url, + token: tokenPair, + method: 'POST', + requestHeaders: headers, + userId: id, + data: payload, + ); + + return response; + } + + Future postCycles(Map data) async { + try { + final response = await call>(postCycles_, data); + debugPrint("Response status code: ${response.statusCode}"); + + if (response.statusCode == 200) { + final responseString = await response.stream.bytesToString(); + final responseData = jsonDecode(responseString); + if (responseData["success"] == true) { + return responseData["msg"]; + } else { + throw responseData["msg"]; + } + } else { + throw "Something Went Wrong Try Again Later ${response.statusCode}"; + } + } catch (e) { + debugPrint("Error posting Cycles: $e"); + throw "$e"; + } + } + + Future fetchCycles_(dynamic args) async { + final id = await _repository.getUserId(); + final tokenPair = await getCachedToken(); + var headers = {'Authorization': 'Bearer ${tokenPair.accessToken}'}; + var url = '${Constants.BASE_URL_NEW}get_menstrual_cycle?user_id=$id'; + final response = request( + url: url, + token: tokenPair, + method: 'GET', + requestHeaders: headers, + userId: id, + ); + return response; + } + + Future> fetchCycles() async { + final response = await call(fetchCycles_, null); + try { + if (response.statusCode == 200) { + final responseString = await response.stream.bytesToString(); + final Map responseData = json.decode(responseString); + final cyclesMap = { + for (var cycle in responseData["data"]["menstrual_cycle"]) + cycle['id'] as int: Cycle.fromJson(cycle) + }; + debugPrint("Cycles Map: $cyclesMap"); + return cyclesMap; + } else { + throw "Something went wrong. Status Code: ${response.statusCode}"; + } + } catch (e) { + throw "$e"; + } + } + + Future putCycles_( + int cycleId, Map data) async { + final id = await _repository.getUserId(); + final tokenPair = await getCachedToken(); + final userId = {'user_id': id}; + var headers = { + 'Authorization': 'Bearer ${tokenPair.accessToken}', + 'Content-Type': 'application/json', + }; + var url = '${Constants.BASE_URL_NEW}/update_menstrual_cycle/$cycleId'; + final payload = {...data, ...userId}; + debugPrint("Data payload: $payload"); + + final response = request( + url: url, + token: tokenPair, + method: 'PUT', + requestHeaders: headers, + userId: id, + data: payload, + ); + + return response; + } + + // Created a wrapper that only accepts the data and calls putCycles_ internally + Future editCycles_(Map data, + {required int cycleId}) { + return putCycles_(cycleId, data); + } + + Future putCycles(int cycleId, Map data) async { + try { + final response = await call>( + (data) => editCycles_(data, cycleId: cycleId), data); + debugPrint("PUT Response status code: ${response.statusCode}"); + + if (response.statusCode == 200) { + final responseString = await response.stream.bytesToString(); + final responseData = jsonDecode(responseString); + if (responseData["success"] == true) { + return responseData["msg"]; + } else { + throw responseData["msg"]; + } + } else { + throw 'Failed to update cycle. Status Code: ${response.statusCode}'; + } + } catch (e) { + debugPrint("Error updating Cycles: $e"); + throw "$e"; + } + } + + Future deleteCycle_(int cycleId) async { + final id = await _repository.getUserId(); + final tokenPair = await getCachedToken(); + var headers = { + 'Authorization': 'Bearer ${tokenPair.accessToken}', + 'Content-Type': 'application/json', + }; + var url = '${Constants.BASE_URL_NEW}/delete_menstrual_cycle/$cycleId'; + + final response = request( + url: url, + token: tokenPair, + method: 'DELETE', + requestHeaders: headers, + userId: id, + ); + + return response; + } + + Future deleteCycle(int cycleId) async { + try { + final response = await call(deleteCycle_, cycleId); + debugPrint("DELETE Response status code: ${response.statusCode}"); + + if (response.statusCode == 200) { + final responseString = await response.stream.bytesToString(); + final responseData = jsonDecode(responseString); + if (responseData["success"] == true) { + return responseData["msg"]; + } else { + throw responseData["msg"]; + } + } else { + throw "Failed to delete cycle. Status Code: ${response.statusCode}"; + } + } catch (e) { + debugPrint("Error deleting Cycle: $e"); + throw "$e"; + } + } +} diff --git a/lib/src/features/period_planner/presentation/controllers/cycles_controller.dart b/lib/src/features/period_planner/presentation/controllers/cycles_controller.dart new file mode 100644 index 00000000..ccd04c96 --- /dev/null +++ b/lib/src/features/period_planner/presentation/controllers/cycles_controller.dart @@ -0,0 +1,71 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:nishauri/src/features/period_planner/data/models/cycle.dart'; +import 'package:nishauri/src/features/period_planner/data/repository/cycles_repository.dart'; + +class CyclesController extends StateNotifier>> { + final CyclesRepository _repository; + + CyclesController(this._repository) : super(const AsyncValue.loading()); + + Future> fetchCycles() async { + state = const AsyncValue.loading(); + try { + final cycles = await _repository.fetchCycles(); + state = AsyncValue.data(cycles); + return cycles; + } catch (e, stackTrace) { + state = AsyncValue.error(e, stackTrace); + return {}; + } + } + + Future postCycles(Cycle cycle) async { + state = const AsyncValue.loading(); + try { + final response = await _repository.postCycles({ + 'period_start': cycle.period_start.toIso8601String(), + 'period_end': cycle.period_end.toIso8601String(), + 'fertile_start': cycle.fertile_start.toIso8601String(), + 'fertile_end': cycle.fertile_end.toIso8601String(), + 'ovulation': cycle.ovulation.toIso8601String(), + 'predicted_period_start': cycle.predicted_period_start.toIso8601String(), + 'predicted_period_end': cycle.predicted_period_end.toIso8601String(), + 'cycle_length': cycle.cycle_length, + 'period_length': cycle.period_length, + }); + await fetchCycles(); + } catch (e, stackTrace) { + state = AsyncValue.error(e, stackTrace); + } + } + + Future editCycle(int cycleId, Cycle updatedCycle) async { + state = const AsyncValue.loading(); + try { + final response = await _repository.putCycles(cycleId, { + 'period_start': updatedCycle.period_start.toIso8601String(), + 'period_end': updatedCycle.period_end.toIso8601String(), + 'fertile_start': updatedCycle.fertile_start.toIso8601String(), + 'fertile_end': updatedCycle.fertile_end.toIso8601String(), + 'ovulation': updatedCycle.ovulation.toIso8601String(), + 'predicted_period_start': updatedCycle.predicted_period_start.toIso8601String(), + 'predicted_period_end': updatedCycle.predicted_period_end.toIso8601String(), + 'cycle_length': updatedCycle.cycle_length, + 'period_length': updatedCycle.period_length, + }); + await fetchCycles(); + } catch (e, stackTrace) { + state = AsyncValue.error(e, stackTrace); + } + } + + Future deleteCycle(int cycleId) async { + state = const AsyncValue.loading(); + try { + final response = await _repository.deleteCycle(cycleId); + await fetchCycles(); + } catch (e, stackTrace) { + state = AsyncValue.error(e, stackTrace); + } + } +} diff --git a/lib/src/features/period_planner/presentation/pages/editPeriodsScreen.dart b/lib/src/features/period_planner/presentation/pages/editPeriodsScreen.dart new file mode 100644 index 00000000..3607441e --- /dev/null +++ b/lib/src/features/period_planner/presentation/pages/editPeriodsScreen.dart @@ -0,0 +1,166 @@ +import 'dart:ffi'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:nishauri/src/features/period_planner/data/models/cycle.dart'; +import 'package:nishauri/src/features/period_planner/data/providers/cycles_provider.dart'; +import 'package:nishauri/src/features/period_planner/presentation/pages/logPeriods.dart'; +import 'package:nishauri/src/features/period_planner/presentation/widgets/customCalendar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/routes.dart'; +import 'package:table_calendar/table_calendar.dart'; + +class EditPeriods extends ConsumerStatefulWidget { + final DateTime? initialStartDate; + final DateTime? initialEndDate; + final int? cycleId; + const EditPeriods( + {super.key, this.initialStartDate, this.initialEndDate, this.cycleId}); + + @override + ConsumerState createState() => _EditPeriodsState(); +} + +class _EditPeriodsState extends ConsumerState { + late DateTime _focusedDay; + DateTime? _rangeStart; + DateTime? _rangeEnd; + final today = DateTime.now(); + + @override + void initState() { + super.initState(); + + _focusedDay = widget.initialStartDate ?? DateTime.now(); + _rangeStart = widget.initialStartDate; + _rangeEnd = widget.initialEndDate; + } + + void _onRangeSelected(DateTime? start, DateTime? end, DateTime? focusedDay) { + setState(() { + _rangeStart = start; + _rangeEnd = end; + _focusedDay = focusedDay ?? _focusedDay; + }); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final cycleAsyncValue = ref.watch(cyclesProvider); + + return cycleAsyncValue.when( + data: (cycles) { + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + title: "Edit Period 🌸", + color: Constants.periodPlanner, + ), + TableCalendar( + focusedDay: _focusedDay, + firstDay: DateTime(2021), + lastDay: DateTime(2100), + rangeStartDay: _rangeStart, + rangeEndDay: _rangeEnd, + rangeSelectionMode: RangeSelectionMode.toggledOn, + onRangeSelected: _onRangeSelected, + calendarStyle: const CalendarStyle( + todayDecoration: BoxDecoration( + color: Colors.blue, + shape: BoxShape.circle, + ), + rangeStartDecoration: BoxDecoration( + color: Colors.pink, + shape: BoxShape.circle, + ), + rangeEndDecoration: BoxDecoration( + color: Colors.pink, + shape: BoxShape.circle, + ), + rangeHighlightColor: Constants.periodPlanner, + selectedDecoration: BoxDecoration( + color: Constants.periodPlanner, + shape: BoxShape.circle, + ), + ), + headerStyle: const HeaderStyle( + formatButtonVisible: false, + ), + ), + Expanded( + child: Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Constants.periodPlanner, + ), + onPressed: () { + if (_rangeStart == null || _rangeEnd == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Please select your Period start and end dates.')), + ); + } else if (_rangeStart!.isAfter(today) || + _rangeEnd!.isAfter(today)) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'You cannot select dates in the future. Please select valid dates.')), + ); + } else { + final currentCycleId = widget.cycleId; + final currentCycle = cycles[widget.cycleId]; + + if (currentCycle != null && currentCycleId != null) { + final updatedCycle = predictCycle( + _rangeStart!, _rangeEnd!, + cycleId: currentCycleId, cycle: cycles); + + ref + .read(cyclesProvider.notifier) + .editCycle(currentCycleId, updatedCycle); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Periods updated successfully!')), + ); + Navigator.pop(context, true); + } else { + debugPrint('Cycle with id ${widget.cycleId} not found!'); + } + } + }, + child: Text( + 'Apply', + style: theme.textTheme.titleSmall?.copyWith( + color: Colors.white, + ), + ), + ), + ), + ), + ), + ], + ), + ); + }, + error: (error, stackTrace) => Center( + child: Text( + 'Failed to load cycles: $error', + style: const TextStyle(color: Colors.red), + ), + ), + loading: () => const Center( + child: CircularProgressIndicator(), + ), + ); + } +} diff --git a/lib/src/features/period_planner/presentation/pages/logPeriods.dart b/lib/src/features/period_planner/presentation/pages/logPeriods.dart new file mode 100644 index 00000000..1d15b4d1 --- /dev/null +++ b/lib/src/features/period_planner/presentation/pages/logPeriods.dart @@ -0,0 +1,317 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:nishauri/src/features/period_planner/data/models/cycle.dart'; +import 'package:nishauri/src/features/period_planner/data/models/events.dart'; +import 'package:nishauri/src/features/period_planner/data/providers/cycles_provider.dart'; +import 'package:nishauri/src/features/period_planner/presentation/widgets/customCalendar.dart'; +import 'package:nishauri/src/features/period_planner/presentation/widgets/eventsMaker.dart'; +import 'package:nishauri/src/features/period_planner/utils/event_utils.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/routes.dart'; +import 'package:table_calendar/table_calendar.dart'; + +//printing List which is acting as a Database +// void printCycles(Cycle cycles) { +// debugPrint('----Cycle Printed----'); +// for (var cycle in cycles) { +// // debugPrint('Cycle ID: ${cycle.cycleId}'); +// debugPrint('Period Start: ${cycle.period_start}'); +// debugPrint('Period End: ${cycle.periodEnd}'); +// debugPrint('Fertile Start: ${cycle.fertileStart}'); +// debugPrint('Fertile End: ${cycle.fertileEnd}'); +// debugPrint('Ovulation: ${cycle.ovulation}'); +// debugPrint('Predicted Period Start: ${cycle.predictedPeriodStart}'); +// debugPrint('Predicted Period End: ${cycle.predictedPeriodEnd}'); +// debugPrint('Cycle Length: ${cycle.cycleLength}'); +// debugPrint('Period Length: ${cycle.periodLength}'); +// debugPrint('---'); +// } +// } + +//This is the screen the user interacts when they are logging their Period Days + +class LogPeriodScreen extends ConsumerStatefulWidget { + @override + ConsumerState createState() => _LogPeriodScreenState(); +} + +class _LogPeriodScreenState extends ConsumerState { + DateTime _focusedDay = DateTime.now(); + DateTime? _startDate; + DateTime? _endDate; + + //late Map> _filteredEvents; + //final latestCycle = cycles.last; + // bool _isNewUser = cycles.isEmpty; + // int averagePeriods = calculateAveragePeriodLength(cycles); + final today = DateTime.now(); + + @override + void initState() { + super.initState(); + final cycles = ref.read(cyclesProvider).asData?.value ?? {}; + bool _isNewUser = cycles.isEmpty; + if (!_isNewUser) { + _initializePredictedPeriodRange(cycles); + _setFocusedDayForRegularUser(cycles); + } + } + + // Map> _filterEventsForLatestCycle() { + // final Map> filteredEvents = {}; + + // final latestCycleId = events.keys.last; + // if (events.containsKey(latestCycleId)) { + // final latestCycleEvents = events[latestCycleId]!; + // latestCycleEvents.forEach((date, events) { + // if (filteredEvents.containsKey(date)) { + // filteredEvents[date]!.addAll(events); + // } else { + // filteredEvents[date] = List.from(events); + // } + // }); + // } + + // return filteredEvents; + // } + + void _initializePredictedPeriodRange(Map cycles) { + if (cycles.isNotEmpty) { + final latestCycle = cycles.values.last; + setState(() { + _startDate = latestCycle.predicted_period_start; + _endDate = latestCycle.predicted_period_end; + }); + } + // final latestCycle = cycles.last; + // _startDate = latestCycle.predicted_period_start; + // _endDate = latestCycle.predicted_period_end; + } + + void _setFocusedDayForRegularUser(Map cycles) { + if (cycles.isNotEmpty) { + final latestCycle = cycles.values.last; + setState(() { + _focusedDay = latestCycle.predicted_period_start; + }); + } + // final cycles = ref.read(cyclesProvider) as List; + // final latestCycle = cycles.last; + // _focusedDay = latestCycle.predicted_period_start; + } + + //Function handling selection of period days for a regular user + void _onDaySelected(DateTime selectedDay, DateTime focusedDay) { + setState(() { + _startDate = selectedDay; + // _endDate = _startDate?.add(Duration(days: averagePeriods - 1)); // Only start date is used for regular users + _endDate = _startDate?.add(const Duration(days: 4)); + _focusedDay = focusedDay; + // debugPrint("Average Period Length from Log Periods is $averagePeriods"); + }); + } + + // Method to validate date range ensuring selection does not exceed 7 days + bool _isDateRangeValid(DateTime start, DateTime end) { + final difference = + end.difference(start).inDays + 1; // +1 to include the start day + return difference <= 7; // Ensure the range does not exceed 7 days + } + + //Function handling selection of period days for a new user + void _onRangeSelected(DateTime? start, DateTime? end, DateTime? focusedDay) { + // If either start or end date is after today, show an error + if (start != null && + end != null && + (start.isAfter(today) || end.isAfter(today))) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Invalid Selection'), + content: const Text( + 'You cannot select dates in the future. Please select valid dates.'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('OK'), + ), + ], + ), + ); + } + // Also check if date range is valid (max 7 days) + else if (start != null && end != null && !_isDateRangeValid(start, end)) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Invalid Selection'), + content: const Text( + 'Please select a date range of 7 days or less. The average period typically lasts between 3 to 7 days.'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('OK'), + ), + ], + ), + ); + } else { + setState(() { + _startDate = start; + _endDate = end; + _focusedDay = focusedDay ?? _focusedDay; + }); + } + } + + // Function to check if two date ranges overlap + bool _datesOverlap( + DateTime start1, DateTime end1, DateTime start2, DateTime end2) { + return (start1.isBefore(end2) || isSameDay(start1, end2)) && + (start2.isBefore(end1) || isSameDay(start2, end1)); + } + + @override + Widget build(BuildContext context) { + final cycleAsyncValue = ref.watch(cyclesProvider); + final theme = Theme.of(context); + + return Scaffold( + body: cycleAsyncValue.when( + data: (cycles) { + bool _isNewUser = cycles.isEmpty; + final cyclesId = cycles.keys.lastOrNull; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomAppBar( + title: "Enter Periods 📅", + color: Constants.periodPlanner.withOpacity(1.0), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + _isNewUser + ? "Please enter your previous period start and end date." + : "Please enter when your Periods have started", + style: const TextStyle( + fontSize: 15, fontWeight: FontWeight.bold), + ), + ), + TableCalendar( + focusedDay: _focusedDay, + firstDay: DateTime(2021), + lastDay: DateTime(2100), + rangeStartDay: _startDate, + rangeEndDay: _endDate, + onRangeSelected: _isNewUser ? _onRangeSelected : null, + onDaySelected: _isNewUser ? null : _onDaySelected, + rangeSelectionMode: RangeSelectionMode.toggledOn, + calendarStyle: const CalendarStyle( + todayDecoration: BoxDecoration( + color: Colors.blue, + shape: BoxShape.circle, + ), + rangeStartDecoration: BoxDecoration( + color: Colors.pink, + shape: BoxShape.circle, + ), + rangeEndDecoration: BoxDecoration( + color: Colors.pink, + shape: BoxShape.circle, + ), + rangeHighlightColor: Constants.periodPlanner, + selectedDecoration: BoxDecoration( + color: Constants.periodPlanner, + shape: BoxShape.circle, + ), + ), + headerStyle: const HeaderStyle( + formatButtonVisible: false, + ), + ), + Expanded( + child: Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Constants.periodPlanner, + ), + onPressed: () { + if (_startDate != null && !_startDate!.isAfter(today)) { + final endDate = _endDate ?? + _startDate!.add(const Duration( + days: + 1)); // The else statement handles where a period only happens for a single day hence the end date will be same day as start date + for (Cycle cycle in cycles.values) { + if (_datesOverlap(cycle.period_start, + cycle.period_end, _startDate!, endDate)) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'The selected period overlaps with an existing cycle. Please choose different dates.'), + ), + ); + return; // Exit the function without adding the cycle + } + } + + final newCycle = + predictCycle(_startDate!, endDate, cycle: cycles); + + //using riverpod to add the cycle to the server + ref + .read(cyclesProvider.notifier) + .postCycles(newCycle) + .then((_) { + context.goNamed(RouteNames.PERIOD_PLANNER_SCREEN); + }); + // printCycles(cycles); + } else if (_startDate!.isAfter(today)) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'You cannot select dates in the future. Please select valid dates.')), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Please select your Period start and end dates.')), + ); + } + }, + child: Text( + 'Apply', + style: theme.textTheme.titleSmall?.copyWith( + color: Colors.white, + ), + ), + ), + ), + ), + ), + ], + ); + }, + error: (error, stackTrace) => Center( + child: Text( + 'Failed to load cycles: $error', + style: const TextStyle(color: Colors.red), + ), + ), + loading: () => const Center( + child: CircularProgressIndicator(), + ), + ), + ); + } +} diff --git a/lib/src/features/period_planner/presentation/pages/new_user_screen.dart b/lib/src/features/period_planner/presentation/pages/new_user_screen.dart new file mode 100644 index 00000000..f29c1a4b --- /dev/null +++ b/lib/src/features/period_planner/presentation/pages/new_user_screen.dart @@ -0,0 +1,150 @@ +// import 'package:flutter/material.dart'; +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:go_router/go_router.dart'; +// import 'package:nishauri/src/features/period_planner/presentation/widgets/customCalendar.dart'; +// import 'package:nishauri/src/features/period_planner/presentation/widgets/feature_tile.dart'; +// import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +// import 'package:nishauri/src/utils/constants.dart'; +// import 'package:nishauri/src/utils/routes.dart'; +// import 'package:table_calendar/table_calendar.dart'; + +// import '../../data/models/events.dart'; + +// class NewUserScreen extends ConsumerStatefulWidget { +// const NewUserScreen({super.key}); + +// @override +// ConsumerState createState() => _NewUserScreenState(); +// } + +// class _NewUserScreenState extends ConsumerState { +// Map>> events = {}; + +// @override +// Widget build(BuildContext context) { +// final theme = Theme.of(context); +// return Scaffold( +// body: Column( +// children: [ +// const CustomAppBar( +// title: "My Flow Tracker 🌺", +// color: Constants.periodPlanner, +// ), +// Expanded( +// child: SingleChildScrollView( +// child: Padding( +// padding: const EdgeInsets.all(16.0), +// child: Column( +// // crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// const Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Text( +// "Welcome, we are so please to have you.", +// style: TextStyle( +// color: Constants.periodPlanner, +// fontWeight: FontWeight.bold, +// fontSize: 18), +// ), +// ], +// ), +// const SizedBox( +// height: 20, +// ), +// Stack( +// alignment: Alignment.center, +// children: [ +// const SizedBox( +// width: 300, // Adjust the width as needed +// height: 300, // Adjust the height as needed +// child: CircularProgressIndicator( +// value: 0.5, +// strokeWidth: 20, +// backgroundColor: Colors.grey, +// valueColor: AlwaysStoppedAnimation( +// Constants.periodPlanner, +// ), +// ), +// ), +// Column( +// mainAxisAlignment: MainAxisAlignment.center, +// children: [ +// const Text( +// "Welcome", +// style: TextStyle( +// fontSize: 18, +// fontWeight: FontWeight.bold, +// ), +// ), +// const SizedBox(height: Constants.SPACING), +// const Text( +// "Click below to get Started", +// style: TextStyle( +// fontSize: 18, +// fontWeight: FontWeight.bold, +// ), +// ), +// const SizedBox(height: Constants.SPACING), +// ElevatedButton( +// onPressed: () { +// context.goNamed( +// RouteNames.PERIOD_PLANNER_LOG_PERIODS); +// }, +// style: ElevatedButton.styleFrom( +// foregroundColor: Colors.black, +// backgroundColor: Constants.periodPlanner, +// shape: RoundedRectangleBorder( +// borderRadius: BorderRadius.circular(20), +// ), +// padding: const EdgeInsets.symmetric( +// horizontal: 20, vertical: 10), +// ), +// child: const Text("Get Started"), +// ), +// ], +// ), +// ], +// ), +// const SizedBox( +// height: 20), // Added spacing to avoid overlap +// const Padding( +// padding: EdgeInsets.all(20.0), +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Text( +// 'Features', +// style: TextStyle( +// fontSize: 20.0, +// fontWeight: FontWeight.bold, +// ), +// ), +// SizedBox(height: 10.0), +// FeatureTile( +// title: 'Track Your Period', +// description: +// 'Keep an accurate record of your menstrual cycles to better understand your health.', +// icon: Icons.track_changes, +// ), +// FeatureTile( +// title: 'Period Calendar', +// description: +// 'View and manage your menstrual cycles with a detailed calendar.', +// icon: Icons.calendar_today, +// ), +// ], +// ), +// ), +// ], +// ), +// ), +// ), +// ), +// ], +// ), +// ); +// } +// } + + diff --git a/lib/src/features/period_planner/presentation/pages/periodCalendar.dart b/lib/src/features/period_planner/presentation/pages/periodCalendar.dart new file mode 100644 index 00000000..59727617 --- /dev/null +++ b/lib/src/features/period_planner/presentation/pages/periodCalendar.dart @@ -0,0 +1,91 @@ +// import 'package:flutter/material.dart'; +// import 'package:go_router/go_router.dart'; +// import 'package:nishauri/src/features/period_planner/data/models/cycle.dart'; +// import 'package:nishauri/src/features/period_planner/data/models/events.dart'; +// import 'package:nishauri/src/features/period_planner/presentation/widgets/calendarKey.dart'; +// import 'package:nishauri/src/features/period_planner/presentation/widgets/customCalendar.dart'; +// import 'package:nishauri/src/features/period_planner/utils/event_utils.dart'; +// import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +// import 'package:nishauri/src/utils/constants.dart'; +// import 'package:nishauri/src/utils/routes.dart'; + +// class PeriodCalendar extends StatelessWidget { +// const PeriodCalendar({super.key}); + +// @override +// Widget build(BuildContext context) { +// final theme = Theme.of(context); +// Map>> events = {}; +// events = EventUtils.generateEvents(cycles); +// return Scaffold( +// body: Column( +// children: [ +// CustomAppBar( +// title: "Calendar 🗓️", +// color: Constants.periodPlanner.withOpacity(1.0), +// ), +// Expanded( +// child: Column( +// children: [ +// CustomCalendar( +// events: events +// ), +// const SizedBox(height: 20), +// const Row( +// mainAxisAlignment: MainAxisAlignment.start, +// children: [ +// Padding( +// padding: EdgeInsets.all(8.0), +// child: Text( +// "Key:", +// style: TextStyle( +// color: Constants.periodPlanner, +// fontSize: 20, +// fontWeight: FontWeight.bold, +// ), +// ), +// ), +// ], +// ), +// const Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// CalendarKey(color: Colors.red, label: 'Today'), +// CalendarKey(color: Colors.pink, label: 'Period Days'), +// CalendarKey(color: Colors.green, label: 'Fertile Days'), +// CalendarKey(color: Colors.blue, label: 'Ovulation Day'), +// CalendarKey(color: Colors.orange, label: 'Predicted Next Period Days'), +// ], +// ), +// ], +// ), +// ), +// Align( +// alignment: Alignment.bottomCenter, +// child: Padding( +// padding: const EdgeInsets.all(16.0), +// child: ElevatedButton( +// style: ElevatedButton.styleFrom( +// backgroundColor: Constants.periodPlanner, +// ), +// onPressed: () { +// // To add functionality later +// context.goNamed(RouteNames.PERIOD_PLANNER_EDIT_PERIODS); +// }, +// child: Text( +// 'Edit period dates', +// style: theme.textTheme.titleSmall?.copyWith( +// color: Colors.white, +// ), +// ), +// ), +// ), +// ), +// ], +// ), +// ); +// } + +// } + + diff --git a/lib/src/features/period_planner/presentation/pages/periodPlanner.dart b/lib/src/features/period_planner/presentation/pages/periodPlanner.dart new file mode 100644 index 00000000..3062d396 --- /dev/null +++ b/lib/src/features/period_planner/presentation/pages/periodPlanner.dart @@ -0,0 +1,149 @@ +// import 'package:flutter/material.dart'; +// import 'package:flutter_svg/flutter_svg.dart'; +// import 'package:go_router/go_router.dart'; +// import 'package:nishauri/src/features/period_planner/presentation/pages/periodPlannerMenu.dart'; +// import 'package:nishauri/src/features/period_planner/presentation/pages/periodPlannerScreen.dart'; +// import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +// import 'package:nishauri/src/utils/constants.dart'; +// import 'package:nishauri/src/utils/routes.dart'; + +// class PeriodPlanner extends StatelessWidget { +// const PeriodPlanner({super.key}); + +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// body: Column( +// children: [ +// const CustomAppBar( +// title: "Period Planner App 🌸", +// color: Constants.periodPlanner, +// ), +// Expanded( +// child: SingleChildScrollView( +// child: Column( +// children: [ +// Container( +// padding: const EdgeInsets.all(20.0), +// decoration: BoxDecoration( +// color: Constants.periodPlanner.withOpacity(1.0), +// image: const DecorationImage( +// image: AssetImage("assets/images/contours.png"), +// opacity: 0.1, +// fit: BoxFit.cover, +// ), +// ), +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.stretch, +// children: [ +// DecoratedBox( +// decoration: const BoxDecoration(), +// child: SvgPicture.asset( +// "assets/images/period_planner2.svg", +// fit: BoxFit.contain, +// height: 100, +// width: 100, +// ), +// ), +// const SizedBox(height: 20.0), +// const Text( +// 'Track Your Cycle with Ease', +// style: TextStyle( +// color: Colors.white, +// fontSize: 20.0, +// fontWeight: FontWeight.bold, +// ), +// textAlign: TextAlign.center, +// ), +// const SizedBox(height: 10.0), +// const Text( +// 'Stay healthy And Well Informed', +// style: TextStyle( +// color: Colors.white, +// fontSize: 16.0, +// ), +// textAlign: TextAlign.center, +// ), +// const SizedBox(height: 20.0), +// ElevatedButton( +// onPressed: () { +// context.goNamed(RouteNames.PERIOD_PLANNER_MENU); +// }, +// style: ElevatedButton.styleFrom( +// padding: const EdgeInsets.symmetric( +// vertical: 15.0, +// horizontal: 40.0, +// ), +// ), +// child: const Text( +// 'Get Started', +// style: TextStyle( +// fontSize: 18.0, +// ), +// ), +// ), +// ], +// ), +// ), +// const SizedBox(height: 20.0), +// Padding( +// padding: const EdgeInsets.all(20.0), +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// const Text( +// 'Features', +// style: TextStyle( +// fontSize: 20.0, +// fontWeight: FontWeight.bold, +// ), +// ), +// const SizedBox(height: 10.0), +// FeatureTile( +// title: 'Track Your Period', +// description: 'Keep an accurate record of your menstrual cycles to better understand your health.', +// icon: Icons.track_changes, +// ), +// FeatureTile( +// title: 'Period Calendar', +// description: 'View and manage your menstrual cycles with a detailed calendar.', +// icon: Icons.calendar_today, +// ), +// // FeatureTile( +// // title: 'Partner Tracking', +// // description: 'Allow your partner to keep track of your cycle and stay informed about your health as well.', +// // icon: Icons.people, +// // ), +// ], +// ), +// ), +// ], +// ), +// ), +// ), +// ], +// ), +// ); +// } +// } + +// class FeatureTile extends StatelessWidget { +// final String title; +// final String description; +// final IconData icon; + +// FeatureTile({ +// required this.title, +// required this.description, +// required this.icon, +// }); + +// @override +// Widget build(BuildContext context) { +// return ListTile( +// leading: Icon(icon), +// title: Text(title), +// subtitle: Text(description), +// ); +// } +// } diff --git a/lib/src/features/period_planner/presentation/pages/periodPlannerMenu.dart b/lib/src/features/period_planner/presentation/pages/periodPlannerMenu.dart new file mode 100644 index 00000000..eef05f2e --- /dev/null +++ b/lib/src/features/period_planner/presentation/pages/periodPlannerMenu.dart @@ -0,0 +1,145 @@ +// import 'package:flutter/material.dart'; +// import 'package:flutter_svg/svg.dart'; +// import 'package:go_router/go_router.dart'; +// import 'package:nishauri/src/app/navigation/menu/MenuItemsBuilder.dart'; +// import 'package:nishauri/src/app/navigation/menu/menuItems.dart'; +// import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +// import 'package:nishauri/src/utils/constants.dart'; +// import 'package:nishauri/src/utils/routes.dart'; +// import 'package:nishauri/src/features/period_planner/data/models/cycle.dart'; +// class PeriodPlannerMenu extends StatelessWidget { +// const PeriodPlannerMenu({super.key}); + +// @override +// Widget build(BuildContext context) { +// final theme = Theme.of(context); +// final _items = _menuItems(context); + +// return Scaffold( +// body: Column( +// children: [ +// CustomAppBar( +// title: "Period Planner 🌸", +// color: Constants.periodPlanner.withOpacity(1.0), +// ), +// Expanded( +// child: MenuItemsBuilder( +// crossAxisCount: 2, +// itemBuilder: (item) => Card( +// margin: const EdgeInsets.all(Constants.SPACING), +// clipBehavior: Clip.antiAlias, +// child: InkWell( +// splashColor: Constants.periodPlannerShortcutBgColor, +// onTap: item.onPressed, +// child: Container( +// padding: const EdgeInsets.all(Constants.SPACING), +// decoration: BoxDecoration( +// color: item.color ?? theme.colorScheme.primary, +// image: const DecorationImage( +// image: AssetImage("assets/images/contours.png"), +// opacity: 0.2, +// fit: BoxFit.cover, +// ), +// ), +// child: Center( +// child: Column( +// mainAxisAlignment: MainAxisAlignment.center, +// crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// item.icon, +// const SizedBox(height: Constants.SPACING), +// Text( +// item.title ?? '', +// style: theme.textTheme.titleMedium?.copyWith( +// color: Colors.white, +// fontWeight: FontWeight.normal, +// overflow: TextOverflow.ellipsis, +// ), +// textAlign: TextAlign.center, +// ), +// ], +// ), +// ), +// ), +// ), +// ), +// items: _items, +// ), +// ), +// ], +// ), +// ); +// } +// } + + +// _menuItems(BuildContext context) => [ +// MenuItem( +// shortcutBackgroundColor: Constants.periodPlannerShortcutBgColor, +// icon: SvgPicture.asset( +// "assets/images/period_planner4.svg", +// semanticsLabel: "Periods", +// fit: BoxFit.contain, +// width: 80, +// height: 80, +// ), +// shortcutIcon: SvgPicture.asset("assets/images/period_planner4.svg", +// semanticsLabel: "Periods", +// fit: BoxFit.contain, +// width: Constants.shortcutIconSize, +// height: Constants.shortcutIconSize), +// title: "Track Periods", +// onPressed: () { +// if(cycles.isEmpty) { +// context.goNamed(RouteNames.PERIOD_PLANNER_LOG_PERIODS); +// } +// else { +// context.goNamed(RouteNames.PERIOD_PLANNER_SCREEN); +// } +// } , +// color: Constants.periodPlanner.withOpacity(1.0), +// ), +// MenuItem( +// shortcutBackgroundColor: Constants.periodPlannerShortcutBgColor, +// icon: SvgPicture.asset( +// "assets/images/period_calender1.svg", +// semanticsLabel: "Periods", +// fit: BoxFit.contain, +// width: 80, +// height: 80, +// ), +// shortcutIcon: SvgPicture.asset("assets/images/period_calender1.svg", +// semanticsLabel: "Periods", +// fit: BoxFit.contain, +// width: Constants.shortcutIconSize, +// height: Constants.shortcutIconSize), +// title: "Calendar", +// onPressed: () { +// if(cycles.isEmpty) { +// context.goNamed(RouteNames.PERIOD_PLANNER_LOG_PERIODS); +// } +// else { +// context.goNamed(RouteNames.PERIOD_PLANNER_CALENDAR); +// } +// }, +// color: Constants.periodPlanner.withOpacity(1.0), +// ), +// // MenuItem( +// // shortcutBackgroundColor: Constants.periodPlannerShortcutBgColor, +// // icon: SvgPicture.asset( +// // "assets/images/partners2.svg", +// // semanticsLabel: "Periods", +// // fit: BoxFit.contain, +// // width: 80, +// // height: 80, +// // ), +// // shortcutIcon: SvgPicture.asset("assets/images/partners2.svg", +// // semanticsLabel: "Periods", +// // fit: BoxFit.contain, +// // width: Constants.shortcutIconSize, +// // height: Constants.shortcutIconSize), +// // title: "Partners", +// // onPressed: () => context.goNamed(RouteNames.PERIOD_PLANNER), +// // color: Constants.periodPlanner.withOpacity(1.0), +// // ), +// ]; \ No newline at end of file diff --git a/lib/src/features/period_planner/presentation/pages/periodPlannerScreen.dart b/lib/src/features/period_planner/presentation/pages/periodPlannerScreen.dart new file mode 100644 index 00000000..e745fefc --- /dev/null +++ b/lib/src/features/period_planner/presentation/pages/periodPlannerScreen.dart @@ -0,0 +1,812 @@ +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:nishauri/src/features/period_planner/data/models/cycle.dart'; +import 'package:nishauri/src/features/period_planner/data/models/events.dart'; +import 'package:nishauri/src/features/period_planner/data/providers/cycles_provider.dart'; +import 'package:nishauri/src/features/period_planner/presentation/pages/logPeriods.dart'; +import 'package:nishauri/src/features/period_planner/presentation/pages/periods_history.dart'; +import 'package:nishauri/src/features/period_planner/presentation/widgets/carouselCard.dart'; +import 'package:nishauri/src/features/period_planner/presentation/widgets/customCalendar.dart'; +import 'package:nishauri/src/features/period_planner/presentation/widgets/feature_tile.dart'; +import 'package:nishauri/src/features/period_planner/presentation/widgets/logItems.dart'; +import 'package:nishauri/src/features/period_planner/presentation/widgets/loggerWidget.dart'; +import 'package:nishauri/src/features/period_planner/utils/event_utils.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/routes.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:table_calendar/table_calendar.dart'; + +//Function to check if two dates are on the same day by truncating the time part +bool isSameDay(DateTime date1, DateTime date2) { + return date1.year == date2.year && + date1.month == date2.month && + date1.day == date2.day; +} + +class PeriodPlannerScreen extends ConsumerStatefulWidget { + const PeriodPlannerScreen({super.key}); + + @override + ConsumerState createState() => + _PeriodPlannerScreenState(); +} + +class _PeriodPlannerScreenState extends ConsumerState { + DateTime _currentDate = DateTime.now(); + DateTime? _periodStart; + DateTime? _periodEnd; + DateTime? _ovulationDate; + DateTime? _nextPeriodStart; + DateTime? _nextPeriodEnd; + Map>> events = {}; + + @override + void initState() { + super.initState(); + _checkOverdueDialog(); + } + + Future _checkOverdueDialog() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + String? lastDialogDateStr = prefs.getString('lastDialogDate'); + DateTime? lastDialogDate = + lastDialogDateStr != null ? DateTime.parse(lastDialogDateStr) : null; + + if (lastDialogDate == null || !isSameDay(_currentDate, lastDialogDate)) { + //If the dialog hasn't been shown today and the user's periods are overdue + if (_nextPeriodEnd != null && _currentDate.isAfter(_nextPeriodEnd!)) { + _overdueDialog(); + //storing the current date as the last dialog shown date + prefs.setString('lastDialogDate', _currentDate.toIso8601String()); + } + } + } + + void _overdueDialog() { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text("Periods Overdue"), + content: const Text( + "We have noticed that your periods are currently overdue, we would just like you to confirm whether your periods have started"), + actions: [ + TextButton( + onPressed: () { + context.goNamed(RouteNames.PERIOD_PLANNER_LOG_PERIODS); + }, + child: const Text("Yes, they have"), + ), + TextButton( + onPressed: () { + _pregnancyDialogue(); + }, + child: const Text("No, they haven't"), + ), + ], + ); + }, + ); + } + + void _pregnancyDialogue() { + final GlobalKey _formKey = GlobalKey(); + showDialog( + // barrierDismissible: false, + context: context, + builder: (BuildContext context) { + String? dropdownValue; + String? pregnancyDropDownValue; + + return StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return AlertDialog( + title: const Text("Periods Overdue"), + content: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text("Have you taken a pregnancy test?"), + const SizedBox( + height: 8, + ), + DropdownButtonFormField( + value: dropdownValue, + items: const [ + DropdownMenuItem( + value: "Yes", + child: Text("Yes"), + ), + DropdownMenuItem( + value: "No", + child: Text("No"), + ), + ], + onChanged: (String? value) { + setState(() { + dropdownValue = value; + }); + }, + validator: (value) => + value == null ? 'Please Select an Option' : null, + autovalidateMode: AutovalidateMode.onUserInteraction, + decoration: const InputDecoration( + labelText: "Select an Option", + border: OutlineInputBorder(), + ), + ), + const SizedBox( + height: 16, + ), + if (dropdownValue == "Yes") + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text("If Yes: Are you pregnant?"), + const SizedBox( + height: 8, + ), + DropdownButtonFormField( + value: pregnancyDropDownValue, + items: const [ + DropdownMenuItem( + value: "Yes", + child: Text("Yes"), + ), + DropdownMenuItem( + value: "No", + child: Text("No"), + ) + ], + onChanged: (String? value) { + setState(() { + pregnancyDropDownValue = value; + }); + }, + validator: (value) => + value == null ? 'Please select an option' : null, + autovalidateMode: AutovalidateMode.onUserInteraction, + decoration: const InputDecoration( + labelText: 'Select an option', + border: OutlineInputBorder(), + ), + ), + ], + ), + if (dropdownValue == "No") + const Text("It is recommended you take a pregnancy test."), + ], + ), + ), + actions: [ + TextButton( + onPressed: () { + if (_formKey.currentState!.validate()) { + Navigator.of(context).pop(); + } + }, + child: const Text("Submit"), + ), + ], + ); + }); + }, + ); + } + + @override + Widget build(BuildContext context) { + final cycleState = ref.watch(cyclesProvider); + final theme = Theme.of(context); + + return Scaffold( + body: cycleState.when( + data: (cycles) { + bool isNewUser = cycles.isEmpty; + if (!isNewUser) { + Cycle latestCycle = cycles.values.last; + _periodStart = latestCycle.period_start; + _periodEnd = latestCycle.period_end; + _ovulationDate = latestCycle.ovulation; + _nextPeriodStart = latestCycle.predicted_period_start; + _nextPeriodEnd = latestCycle.predicted_period_end; + events = EventUtils.generateEvents(cycles); + + // If cycles are updated, recalculate the events and UI elements + int daysToOvulation = _ovulationDate != null + ? _ovulationDate!.difference(_currentDate).inDays + : 0; + + int daysToNextPeriod = _nextPeriodStart != null + ? _nextPeriodStart!.difference(_currentDate).inDays + : 0; + + int overdueDays = _nextPeriodEnd != null + ? _currentDate.difference(_nextPeriodEnd!).inDays + : 0; + //int predictedDays = _nextPeriodEnd.difference(_nextPeriodStart).inDays; + + bool isInPeriod = (_periodStart != null && + (_currentDate.isAfter(_periodStart!) || + isSameDay(_currentDate, _periodStart!))) && + (_periodEnd != null && _currentDate.isBefore(_periodEnd!)); + + bool veryCloseToPeriod = _nextPeriodStart != null && + _currentDate.isBefore(_nextPeriodStart!) && + daysToNextPeriod == 0; + + bool isCloseToOvulation = _ovulationDate != null && + _currentDate.isBefore(_ovulationDate!) && + !isInPeriod && + daysToOvulation >= 1; + + bool veryCloseToOvulation = _ovulationDate != null && + _currentDate.isBefore(_ovulationDate!) && + daysToOvulation == 0; + + bool isOvulation = _ovulationDate != null && + (isSameDay(_currentDate, _ovulationDate!)) || + (_currentDate.isAfter(_ovulationDate!) && daysToOvulation == 0); + + bool afterOvulation = + (_ovulationDate != null && _nextPeriodStart != null) && + _currentDate.isAfter(_ovulationDate!) && + (_currentDate.isBefore(_nextPeriodStart!) && + daysToNextPeriod > 0); + + bool duringPredictedPeriodRange = + (_nextPeriodStart != null && _nextPeriodEnd != null) && + (_currentDate.isAfter(_nextPeriodStart!) && + _currentDate.isBefore(_nextPeriodEnd!)) || + (isSameDay(_currentDate, _nextPeriodStart!) || + isSameDay(_currentDate, _nextPeriodEnd!)); + + bool isDangerZone = _currentDate.isAfter(_nextPeriodEnd!); + + bool inPeriods = isInPeriod; + + // Determine progress value and messages based on the current date + double progressValue = 0.0; + String message = ''; + String buttonText = ''; + String title = ''; + String chances = ''; + + if (isInPeriod) { + progressValue = 0.2; + title = 'Period'; + message = + 'Day ${DateTime.now().difference(_periodStart!).inDays + 1}'; + buttonText = 'Period End'; + chances = 'Low'; + } else if (isCloseToOvulation) { + progressValue = 0.3; + title = 'Ovulation in'; + message = '$daysToOvulation day${daysToOvulation > 1 ? 's' : ''}'; + buttonText = 'Period Start'; + chances = 'High'; + } else if (veryCloseToOvulation) { + progressValue = 0.4; + title = 'Ovulation is'; + message = 'Tomorrow'; + buttonText = 'Period Start'; + chances = 'High'; + } else if (isOvulation) { + progressValue = 0.5; + title = 'Ovulation is'; + message = 'Today'; + buttonText = 'Period Start'; + chances = 'High'; + } else if (afterOvulation) { + progressValue = 0.7; + title = 'Next Period in'; + message = + '$daysToNextPeriod day${daysToNextPeriod > 1 ? 's' : ''}'; + buttonText = 'Period Start'; + chances = 'Low'; + } else if (veryCloseToPeriod) { + progressValue = 0.7; + title = 'Next Period is'; + message = 'Tomorrow'; + buttonText = 'Period Start'; + chances = 'Low'; + } else if (duringPredictedPeriodRange) { + progressValue = 1.0; + title = 'Periods May happen'; + message = 'Today'; + buttonText = 'Period Start'; + chances = 'Low'; + } else if (isDangerZone) { + progressValue = 1.0; + title = 'Periods Overdue by'; + message = '$overdueDays Day${overdueDays > 1 ? 's' : ''}'; + buttonText = 'Period Start'; + chances = 'High'; + } + // else if (isNewUser) { + // progressValue = 1.0; + // title = "Welcome"; + // message = "Let's kickstart your predictions."; + // buttonText = 'Get Started'; + // chances = ""; + // } + return Column( + children: [ + const CustomAppBar( + title: "My Flow Tracker 🌺", + color: Constants.periodPlanner, + ), + //const SizedBox(height: Constants.SPACING), + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + if (!isNewUser) + const Text( + "Your Chances of Getting Pregnant are:", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + // style: theme.textTheme.titleLarge?.copyWith(color: Colors.black), + ), + Text( + chances, + style: TextStyle( + fontSize: 34, + color: chances == 'High' + ? Colors.red + : Colors.blue, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + )), + const SizedBox(height: Constants.SPACING), + SizedBox( + height: 150, + child: CustomCalendar( + key: ValueKey(events), + initialFormat: CalendarFormat.week, + events: events, + headerButton: true, + inPeriods: inPeriods, + ), + ), + const SizedBox(height: Constants.SPACING), + Stack( + alignment: Alignment.center, + children: [ + SizedBox( + width: 300, // Adjust the width as needed + height: 300, // Adjust the height as needed + child: CircularProgressIndicator( + value: progressValue, + strokeWidth: 20, + backgroundColor: Colors.grey, + valueColor: AlwaysStoppedAnimation( + isDangerZone + ? Colors.red + : Constants.periodPlanner, + ), + ), + ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (isDangerZone) + const SizedBox( + height: Constants.SPACING + 20), + Text( + title, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: Constants.SPACING), + Text( + message, + style: TextStyle( + fontSize: 38, + color: isDangerZone + ? Colors.red + : Constants.periodPlanner, + fontWeight: FontWeight.bold), + ), + const SizedBox( + height: Constants.SPACING + 10), + if (buttonText.isNotEmpty) + ElevatedButton( + onPressed: () { + //Logging Start of new period + if (buttonText == 'Period Start' || + buttonText == 'Get Started') { + ref + .read(cyclesProvider.notifier) + .fetchCycles() + .then((_) { + context.goNamed(RouteNames + .PERIOD_PLANNER_LOG_PERIODS); + }); + } + //Logging end of new period + else { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text( + 'Confirming End of Period'), + content: const Text( + 'Are you sure you want to log the end of your period?'), + actions: [ + TextButton( + child: const Text('Cancel'), + onPressed: () { + Navigator.of(context) + .pop(); // Close the dialog + }, + ), + TextButton( + child: + const Text('Confirm'), + onPressed: () { + setState(() { + // Log the period start here + isCloseToOvulation = + true; + isInPeriod = false; + _currentDate = + DateTime.now(); + _periodEnd = + _currentDate; + }); + //printCycles(cycles); + // Debug print to check the state update + // debugPrint("After User has logged end of Period"); + // debugPrint('Period Start after update: $_periodStart'); + // debugPrint('Period End after update: $_periodEnd'); + // debugPrint('Predicted Next Period Date after update: $_nextPeriodStart'); + // debugPrint('Current Date after update: $_currentDate'); + // debugPrint('Is In Period after update: $isInPeriod'); + // debugPrint('Is Close to Ovulation after update: $isCloseToOvulation'); + // debugPrint("--------"); + final cycleId = + cycles.keys.last; + + final updatedCycle = + predictCycle( + _periodStart!, + _periodEnd!, + cycleId: cycleId, + cycle: cycles, + ); + periodConfirmedMap[ + cycleId] = true; + + updateCycleLengths( + cycles); + + final sortedCycleKeys = cycles + .keys + .toList() + ..sort((a, b) => cycles[ + a]! + .period_start + .compareTo(cycles[ + b]! + .period_start)); + + final secondLastCycleId = + sortedCycleKeys[ + sortedCycleKeys + .length - + 2]; + + // final secondLastCycle = + // cycles[ + // secondLastCycleId]!; + + final secondLastPeriodStart = + cycles[secondLastCycleId]! + .period_start; + final secondLastPeriodEnd = + cycles[secondLastCycleId]! + .period_end; + + final updateSecondLastCycle = + predictCycle( + secondLastPeriodStart, + secondLastPeriodEnd, + cycleId: + secondLastCycleId, + cycle: cycles, + ); + + ref + .read(cyclesProvider + .notifier) + .editCycle(cycleId, + updatedCycle) + .then((_) { + // Navigator.of(context) + // .pop(); + ref + .read(cyclesProvider + .notifier) + .editCycle( + secondLastCycleId, + updateSecondLastCycle) + .then((_) { + Navigator.of(context) + .pop(); + }); + }); + }, + ), + ], + ); + }, + ); + } + }, + style: ElevatedButton.styleFrom( + foregroundColor: Colors.black, + backgroundColor: isDangerZone + ? Colors.red + : Constants.periodPlanner, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(20), + ), + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + ), + child: Text( + buttonText, + style: const TextStyle(fontSize: 18), + ), + ), + const SizedBox(height: 10), + //where to put the icon button + if (isDangerZone) + IconButton( + onPressed: () { + _overdueDialog(); + }, + icon: const Icon( + Icons.info_sharp, + color: Colors.blue, + size: 40, + ), + ), + ], + ), + ], + ), + //const SizedBox(height: 20,), + // const Text( + // "Please give your Daily Insights:", + // style: TextStyle( + // fontWeight: FontWeight.bold, + // fontSize: 15.0, + // color: Constants.periodPlanner, + // ), + // ), + // CarouselSlider( + // options: CarouselOptions( + // height: 150, + // enlargeCenterPage: true, + // enableInfiniteScroll: false, + // initialPage: 0, + // autoPlay: false, + // ), + // items: [ + // CarouselCard( + // svgPath: "assets/images/symptoms.svg", + // title: "Symptoms", + // destination: LoggerWidget( + // heading: "Log Symptoms", + // items: LogItems.getSymptoms(), + // ) + // ), + // CarouselCard( + // svgPath: "assets/images/discharge1.svg", + // title: "Discharge", + // destination: LoggerWidget( + // heading: "Log Discharge", + // items: LogItems.getDischarge(), + // ) + // ), + // CarouselCard( + // svgPath: "assets/images/moods1.svg", + // title: "Mood", + // destination: LoggerWidget( + // heading: "How are you feeling?", + // items: LogItems.getMoods(), + // ), + // ), + // ], + // ), + ], + ), + ), + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Constants.periodPlanner, + ), + onPressed: () { + context + .goNamed(RouteNames.PERIOD_PLANNER_PERIOD_HISTORY); + }, + child: Text( + 'Periods History', + style: theme.textTheme.titleSmall?.copyWith( + color: Colors.white, + ), + ), + ), + ), + ), + ], + ); + } else { + /* + This is a scenario whereby either the user is a first time user + or the user has deleted all their cycles from their history + */ + _periodStart = null; + _periodEnd = null; + _nextPeriodStart = null; + _nextPeriodEnd = null; + events = EventUtils.generateEvents(cycles); + + return Column( + children: [ + const CustomAppBar( + title: "My Flow Tracker 🌺", + color: Constants.periodPlanner, + ), + const Padding( + padding: EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Let's kickstart your predictions.", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + ], + ), + ), + const SizedBox(height: Constants.SPACING + 30), + Stack( + alignment: Alignment.center, + children: [ + const SizedBox( + width: 300, // Adjust the width as needed + height: 300, // Adjust the height as needed + child: CircularProgressIndicator( + value: 0.5, + strokeWidth: 20, + backgroundColor: Colors.grey, + valueColor: AlwaysStoppedAnimation( + Constants.periodPlanner, + ), + ), + ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "Welcome", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: Constants.SPACING), + const Text( + "Click below to Get Started", + style: TextStyle( + fontSize: 19, + color: Constants.periodPlanner, + fontWeight: FontWeight.bold), + ), + const SizedBox(height: Constants.SPACING + 10), + ElevatedButton( + onPressed: () { + context + .goNamed(RouteNames.PERIOD_PLANNER_LOG_PERIODS); + }, + style: ElevatedButton.styleFrom( + foregroundColor: Colors.black, + backgroundColor: Constants.periodPlanner, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.symmetric( + horizontal: 20, vertical: 10), + ), + child: const Text("Get Started"), + ), + ], + ), + ], + ), + const SizedBox(height: 20), + const FeatureTile( + title: 'Track Your Period', + description: + 'Keep an accurate record of your menstrual cycles to better understand your health.', + icon: Icons.track_changes, + ), + const FeatureTile( + title: 'Period Calendar', + description: + 'View and manage your menstrual cycles with a detailed calendar.', + icon: Icons.calendar_today, + ), + Expanded( + child: Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Constants.periodPlanner, + ), + onPressed: () { + context.goNamed( + RouteNames.PERIOD_PLANNER_PERIOD_HISTORY); + }, + child: Text( + 'Periods History', + style: theme.textTheme.titleSmall?.copyWith( + color: Colors.white, + ), + ), + ), + ), + ), + ), + ], + ); + } + }, + error: (error, stackTrace) => Center( + child: Text( + 'Failed to load cycles: $error', + style: const TextStyle(color: Colors.red), + ), + ), + loading: () => const Center( + child: CircularProgressIndicator(), + ), + ), + ); + } +} diff --git a/lib/src/features/period_planner/presentation/pages/periods_history.dart b/lib/src/features/period_planner/presentation/pages/periods_history.dart new file mode 100644 index 00000000..ab75a066 --- /dev/null +++ b/lib/src/features/period_planner/presentation/pages/periods_history.dart @@ -0,0 +1,326 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:intl/intl.dart'; +import 'package:nishauri/src/features/period_planner/data/models/cycle.dart'; +import 'package:nishauri/src/features/period_planner/data/providers/cycles_provider.dart'; +import 'package:nishauri/src/features/period_planner/presentation/widgets/customCalendar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +// Map containing period cycles in which the user has confirmed period end date +Map periodConfirmedMap = {}; + +class PeriodsHistory extends ConsumerStatefulWidget { + const PeriodsHistory({super.key}); + + @override + ConsumerState createState() => _PeriodsHistoryState(); +} + +class _PeriodsHistoryState extends ConsumerState { + // Function to format the date as "MMM d" + String formatDate(DateTime date) { + return DateFormat('MMM d').format(date); + } + + // Function to group cycles by year + Map>> groupCyclesByYear( + Map cycles) { + final Map>> groupedCycles = {}; + + for (var cycleEntry in cycles.entries) { + final year = cycleEntry.value.period_start.year; + if (!groupedCycles.containsKey(year)) { + groupedCycles[year] = []; + } + groupedCycles[year]!.add(cycleEntry); + } + return groupedCycles; + } + + @override + Widget build(BuildContext context) { + final cyclesAsyncValue = ref.watch(cyclesProvider); + + return cyclesAsyncValue.when( + data: (cycles) { + final averagePeriod = calculateAveragePeriodLength(cycles); + final averageCycles = calculateAverageCycleLength(cycles); + // Group cycles by year and reverse the list to show the latest first + final groupedCycles = groupCyclesByYear(cycles); + final sortedYears = groupedCycles.keys.toList() + ..sort((a, b) => b.compareTo(a)); + + return Scaffold( + body: ListView( + children: [ + const CustomAppBar( + title: "My Period History 🌼", + color: Constants.periodPlanner, + ), + const SizedBox(height: 10), + SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + // The first Card showing both Average period and cycle lengths + Card( + elevation: 4.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "My Cycles", + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + // Card showing Average Period days + child: Card( + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(12.0), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + const Text( + "Average Period", + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 8.0), + Text( + "$averagePeriod days", + style: const TextStyle( + fontSize: 30.0, + fontWeight: FontWeight.bold, + color: Colors.pink, + ), + ), + ], + ), + ), + ), + ), + Expanded( + // Card Showing Average Cycle days + child: Card( + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(12.0), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + const Text( + 'Average Cycle', + style: TextStyle( + fontSize: 20.0, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 8.0), + Text( + "$averageCycles days", + style: const TextStyle( + fontSize: 30.0, + fontWeight: FontWeight.bold, + color: Colors.blue, + ), + ), + ], + ), + ), + ), + ), + ], + ), + const SizedBox(height: 10), + ], + ), + ), + ), + const SizedBox(height: 20), + // Card Showing period history + const Text( + "History", + style: TextStyle( + fontSize: 25, + fontWeight: FontWeight.bold, + color: Colors.pink, + ), + ), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: sortedYears.length, + itemBuilder: (context, index) { + final year = sortedYears[index]; + final cyclesInYear = groupedCycles[year]!; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8.0), + child: Text( + "$year", + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 10), + ...cyclesInYear.reversed.map((cycleEntry) { + final cycleId = cycleEntry.key; + final cycle = cycleEntry.value; + + // Only display cycles that are either confirmed or whose end date has passed + final hasPeriodEnded = + DateTime.now().isAfter(cycle.period_end); + final isPeriodConfirmed = + periodConfirmedMap[cycleId] ?? false; + + if (isPeriodConfirmed || hasPeriodEnded) { + final start = formatDate(cycle.period_start); + final end = formatDate(cycle.period_end); + final cycleDays = cycle.cycle_length; + + // Check if the current cycle is the last entry + bool isLastCycle = + cycle == cyclesInYear.last.value; + + return Card( + elevation: 4.0, + margin: const EdgeInsets.symmetric( + vertical: 8.0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + child: ListTile( + leading: IconButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text( + "Confirm Delete"), + content: const Text( + "Are you sure you want to delete the current Cycle?"), + actions: [ + TextButton( + onPressed: () { + try { + ref + .read(cyclesProvider + .notifier) + .deleteCycle( + cycleId) + .then((_) { + Navigator.pop( + context); + }); + } catch (e) { + debugPrint( + 'Error deleting cycle: $e'); + } + }, + child: const Text("Yes"), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text("No"), + ), + ], + ); + }, + ); + }, + icon: const Icon(Icons.delete_outline), + ), + title: Text( + "$start - $end", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + subtitle: Text( + isLastCycle + ? "Estimated Cycle Length: $cycleDays days" + : "Length of cycle: $cycleDays days", + style: const TextStyle( + fontSize: 16.0, + ), + ), + trailing: IconButton( + onPressed: () { + context.goNamed( + RouteNames + .PERIOD_PLANNER_EDIT_PERIODS, + extra: { + 'startDate': cycle.period_start, + 'endDate': cycle.period_end, + 'id': cycleId, + }, + ); + }, + icon: + const Icon(Icons.arrow_forward_ios), + ), + ), + ); + } else { + // Skip displaying cycles that haven't been confirmed or whose end date hasn't passed + return const SizedBox.shrink(); + } + }).toList(), + ], + ); + }, + ), + ], + ), + ), + ), + ], + ), + ); + }, + error: (error, stackTrace) => Center( + child: Text( + 'Failed to load cycles: $error', + style: const TextStyle(color: Colors.red), + ), + ), + loading: () => const Center( + child: CircularProgressIndicator(), + ), + ); + } +} diff --git a/lib/src/features/period_planner/presentation/widgets/calendarKey.dart b/lib/src/features/period_planner/presentation/widgets/calendarKey.dart new file mode 100644 index 00000000..726c3d81 --- /dev/null +++ b/lib/src/features/period_planner/presentation/widgets/calendarKey.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +class CalendarKey extends StatelessWidget { + final Color color; + final String label; + + const CalendarKey({ + super.key, + required this.color, + required this.label, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Container( + width: 16, + height: 16, + margin: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + ), + ), + Text(label), + ], + ); + } +} \ No newline at end of file diff --git a/lib/src/features/period_planner/presentation/widgets/carouselCard.dart b/lib/src/features/period_planner/presentation/widgets/carouselCard.dart new file mode 100644 index 00000000..e82b7917 --- /dev/null +++ b/lib/src/features/period_planner/presentation/widgets/carouselCard.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:nishauri/src/shared/display/AppCard.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class CarouselCard extends StatelessWidget { + final String svgPath; + final String title; + final double? width; + final double? height; + final Widget destination; + + const CarouselCard({ + super.key, + required this.svgPath, + required this.title, + this.width, + this.height, + required this.destination, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return AppCard( + clipBehaviour: Clip.antiAlias, + svgImage: "assets/images/rect_bg.svg", + child: SizedBox( + width: width, + height: height, + child: Row( + children: [ + Expanded( + flex: 1, + child: Container( + color: Constants.periodPlanner.withOpacity(0.5), + padding: const EdgeInsets.all(10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 30), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Constants.periodPlanner, + ), + onPressed: () { + showModalBottomSheet( + context: context, + builder: (context) => FractionallySizedBox( + heightFactor: 0.5, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: destination, + ), + ), + ); + }, + child: Text( + 'Click Me', + style: theme.textTheme.titleSmall?.copyWith( + color: Colors.white, + ), + ), + ), + ], + ), + ), + ), + // Right column with SVG image + Expanded( + flex: 1, + child: SvgPicture.asset( + svgPath, + fit: BoxFit.scaleDown, + height: double.infinity, + ), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/features/period_planner/presentation/widgets/customCalendar.dart b/lib/src/features/period_planner/presentation/widgets/customCalendar.dart new file mode 100644 index 00000000..61987f7b --- /dev/null +++ b/lib/src/features/period_planner/presentation/widgets/customCalendar.dart @@ -0,0 +1,367 @@ +import 'dart:ffi'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:nishauri/src/features/period_planner/data/models/cycle.dart'; +import 'package:nishauri/src/features/period_planner/data/models/events.dart'; +import 'package:nishauri/src/features/period_planner/data/providers/cycles_provider.dart'; +import 'package:nishauri/src/features/period_planner/presentation/widgets/eventsMaker.dart'; +import 'package:nishauri/src/features/period_planner/presentation/widgets/modalContent.dart'; +import 'package:table_calendar/table_calendar.dart'; + +//Function to update Cycle days +void updateCycleLengths(Map cycleMap) { + final cycles = cycleMap.values.toList(); + + final updatedCycleMap = {}; + + // Start from the second last cycle because the last one does not have a successor yet + for (int i = 0; i < cycles.length - 1; i++) { + int cycleLength = + cycles[i + 1].period_start.difference(cycles[i].period_start).inDays; + // cycles[i].cycle_length = cycleLength; + final updatedCycle = cycles[i].copyWith(cycle_length: cycleLength); + updatedCycleMap[i] = updatedCycle; + } + + // The last cycle in the list should still use the average cycle length + if (cycles.isNotEmpty) { + // cycles.last.cycle_length = calculateAverageCycleLength(cycleMap); + final lastCycle = cycles.last + .copyWith(cycle_length: calculateAverageCycleLength(cycleMap)); + updatedCycleMap[cycles.length - 1] = lastCycle; + } +} + +// // Function to calculate Average Cycle days +int calculateAverageCycleLength(Map cyclesMap) { + final cycles = cyclesMap.values.toList(); + if (cycles.length < 2) { + debugPrint( + "Not enough cycles to calculate an average, defaulting to 28 days."); + return 28; // Default to 28 if there aren't enough cycles + } + + int totalLength = 0; + for (int i = 1; i < cycles.length; i++) { + int cycleLength = + cycles[i].period_start.difference(cycles[i - 1].period_start).inDays; + debugPrint("Cycle Length for cycle $i: $cycleLength"); + + // Cap to a minimum cycle length of 21 days + // if (cycleLength < 21) { + // debugPrint( + // "Cycle Length $cycleLength is less than 21 days, defaulting to 26 days."); + // cycleLength = 26; + // } + totalLength += cycleLength; + } + + int averageCycle = (totalLength / (cycles.length - 1)).round(); + debugPrint( + "Calculated Average Cycle Length before final check: $averageCycle"); + + if (averageCycle < 21) { + debugPrint( + "Average Cycle Length $averageCycle is less than 21 days, defaulting to 26 days."); + return 26; + } + + debugPrint("Final Average Cycle Length: $averageCycle"); + return averageCycle; +} + +// //Function for calculating Average Period days +int calculateAveragePeriodLength(Map cycleMap) { + final cycleList = cycleMap.values.toList(); + if (cycleList.isEmpty) return 5; // Default to 5 days if there are no cycles + + int totalPeriodLength = 0; + for (Cycle cycle in cycleList) { + debugPrint("Period Length : ${cycle.period_length}"); + totalPeriodLength += cycle.period_length; + } + int averagePeriodLength = (totalPeriodLength / cycleList.length).round(); + if (averagePeriodLength < 3) { + return 4; + } + debugPrint("Total Period Length: $totalPeriodLength"); + debugPrint("Number of Cycles: ${cycleList.length}"); + debugPrint("Average Period Length: $averagePeriodLength"); + return averagePeriodLength; +} + +// DateTime normalizeToMidnight(DateTime dateTime) { +// return DateTime(dateTime.year, dateTime.month, dateTime.day); +// } + +//Algorithm for calculating Next Period Days, Ovulation and Fertile Days +Cycle predictCycle(DateTime periodStart, DateTime periodEnd, + {int? cycleId, required Map cycle}) { + final cycleList = cycle.values.toList(); + + int index = cycleId ?? cycleList.length; + + // Calculate average cycle length from previous cycles + int averageCycleLength = calculateAverageCycleLength(cycle); + + // Calculate average period length from the period Start to the Period End + int averagePeriodLength = calculateAveragePeriodLength(cycle); + + DateTime predictedPeriodStart = + periodStart.add(Duration(days: averageCycleLength - 1)); + DateTime predictedPeriodEnd = + predictedPeriodStart.add(Duration(days: averagePeriodLength - 1)); + + //calculating ovulation day (14 days before predicted period start) + DateTime ovulation = predictedPeriodStart.subtract(const Duration(days: 14)); + + //calculating fertile window (5 days leading up to and including ovulation day) + DateTime fertileStart = ovulation.subtract(const Duration(days: 5)); + DateTime fertileEnd = ovulation.subtract(const Duration(days: 1)); + + //Calculating cycle Length + // int cycleLength = (index > 0) + // ? periodStart.difference(cycles[index - 1].periodStart).inDays + // : averageCycleLength; + + int cycleLength; + if (cycleList.isEmpty) { + cycleLength = averageCycleLength; + } else { + if (index > 0 && index < cycleList.length - 1) { + // If the current cycle has a succeeding cycle, calculate based on difference between two cycles + cycleLength = + periodStart.difference(cycleList[index - 1].period_start).inDays; + } else if (index == cycleList.length - 1) { + // If it's the last cyclStringe (no succeeding cycle), use the average cycle length as a placeholder + cycleLength = averageCycleLength; + } else { + // For the very first cycle or other fallback cases, use average cycle length + cycleLength = averageCycleLength; + } + } + + //Calculating period Length of each cycle + int periodLength = periodEnd.difference(periodStart).inDays + 1; + + return Cycle( + // cycleId: cycleId ?? uuid.v4(), + period_start: periodStart, + period_end: periodEnd, + fertile_start: fertileStart, + fertile_end: fertileEnd, + ovulation: ovulation, + predicted_period_start: predictedPeriodStart, + predicted_period_end: predictedPeriodEnd, + cycle_length: cycleLength, + period_length: periodLength, + ); +} + +class CustomCalendar extends ConsumerStatefulWidget { + final CalendarFormat initialFormat; + final Map>> events; + final bool headerButton; + final bool? inPeriods; + + const CustomCalendar({ + Key? key, + this.initialFormat = CalendarFormat.month, + required this.events, + this.headerButton = false, + this.inPeriods, + }) : super(key: key); + + @override + _CustomCalendarState createState() => _CustomCalendarState(); +} + +class _CustomCalendarState extends ConsumerState { + late CalendarFormat _calendarFormat; + late DateTime _focusedDay; + late DateTime _firstDay; + late DateTime _lastDay; + //late Map> _flatEvents; + late Map> _filteredEvents; + + @override + void initState() { + super.initState(); + _calendarFormat = widget.initialFormat; + //_flatEvents = _flattenEvents(widget.events); + _filteredEvents = _filterEventsForLatestCycle(); + // Determine the focused day based on the calendar format + _focusedDay = _calendarFormat == CalendarFormat.week + ? _getNextPredictedPeriodDate() ?? DateTime.now() + : DateTime.now(); + _firstDay = _getFirstEventDate(); + _lastDay = _getLastEventDate(); + } + + //To flatten the events so that it can be in the form of DateTime as the key and the events as the values + // Map> _flattenEvents(Map>> nestedEvents) { + // final Map> flattenedEvents = {}; + + // nestedEvents.forEach((cycleId, dateMap) { + // dateMap.forEach((date, events) { + // if (flattenedEvents.containsKey(date)) { + // flattenedEvents[date]!.addAll(events); + // } else { + // flattenedEvents[date] = List.from(events); + // } + // }); + // }); + // return flattenedEvents; + // } + +//This is for filtering events on the calendar + Map> _filterEventsForLatestCycle() { + final Map> filteredEvents = {}; + + // Identify the latest cycle + final latestCycleId = widget.events.keys.last; + + // If the latest cycle exists, add its events to the filteredEvents map + if (widget.events.containsKey(latestCycleId)) { + final latestCycleEvents = widget.events[latestCycleId]!; + + // Directly add all events from the latest cycle + latestCycleEvents.forEach((date, events) { + filteredEvents[date] = List.from(events); + }); + } + + // debugPrint("Filtered Events: $filteredEvents"); + return filteredEvents; + } + + //Function for getting the focused day + DateTime? _getNextPredictedPeriodDate() { + for (var entry in _filteredEvents.entries) { + if (widget.inPeriods == true) { + // Return the first date with a 'Period Day' event + if (entry.value.any((event) => event.title == 'Period Day')) { + return entry.key; + } + } else { + // Return the first date with a 'Predicted Period Day' event + if (entry.value.any((event) => event.title == 'Predicted Period Day')) { + return entry.key; + } + } + } + debugPrint("No matching events found."); + return null; + } + + /*Getting the first date to be displayed on the calendar + if the function _getNextPredictedPeriodDate, the start date of the calendar is 2010 + */ + DateTime _getFirstEventDate() { + DateTime? firstEventDate = _getNextPredictedPeriodDate(); + return _calendarFormat == CalendarFormat.week + ? firstEventDate ?? DateTime(2010) + : DateTime(2010); // Fallback to DateTime(2010) if null + } + + /*Getting the last date to be displayed on the calendar + if the function _getNextPredictedPeriodDate, the last date of the calendar is 2100 + */ + DateTime _getLastEventDate() { + DateTime? lastEventDate; + + for (var entry in _filteredEvents.entries.toList().reversed) { + if (widget.inPeriods == true) { + // Return the last date with a 'Period Day' event + if (entry.value.any((event) => event.title == 'Period Day')) { + lastEventDate = entry.key; + break; + } + } else { + // Return the last date with a 'Predicted Period Day' event + if (entry.value.any((event) => event.title == 'Predicted Period Day')) { + lastEventDate = entry.key; + break; + } + } + } + + return _calendarFormat == CalendarFormat.week + ? lastEventDate ?? DateTime(2100) + : DateTime(2100); // Fallback to DateTime(2100) if null + } + + @override + Widget build(BuildContext context) { + return TableCalendar( + key: ValueKey(widget.events), + firstDay: _firstDay, + lastDay: _lastDay, + focusedDay: _focusedDay, + // onDaySelected: (selectedDay, focusedDay) { + // setState(() { + // _focusedDay = focusedDay; + // }); + // }, + calendarFormat: _calendarFormat, + // eventLoader: (day) { + // return _filteredEvents[day] ?? []; + // }, + eventLoader: (day) { + DateTime normalizedDay = DateTime(day.year, day.month, day.day); + return _filteredEvents[normalizedDay] ?? []; + }, + + headerVisible: true, + headerStyle: HeaderStyle( + formatButtonVisible: widget.headerButton, + //Conditionally hide chevrons if the calendar is in a weekly format + leftChevronVisible: _calendarFormat != CalendarFormat.week, + rightChevronVisible: _calendarFormat != CalendarFormat.week, + headerPadding: const EdgeInsets.all(8.0), + ), + onFormatChanged: (format) { + showModalBottomSheet( + context: context, + builder: (context) => const ModalContent(), + showDragHandle: true, + isScrollControlled: true, + ); + }, + calendarStyle: const CalendarStyle( + isTodayHighlighted: true, + todayDecoration: BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + defaultDecoration: BoxDecoration( + shape: BoxShape.circle, + ), + weekendDecoration: BoxDecoration( + shape: BoxShape.circle, + ), + ), + daysOfWeekStyle: const DaysOfWeekStyle( + weekendStyle: TextStyle(color: Colors.black), + weekdayStyle: TextStyle(color: Colors.black), + ), + calendarBuilders: CalendarBuilders( + markerBuilder: (context, date, events) { + // debugPrint("----From CustomCalendar-----"); + print(events); + if (events.isEmpty) { + // debugPrint('Error getting Events!! - List is empty for date: $date'); + return null; + } + final eventList = events.cast(); + + // debugPrint("-----From CustomCalendar------"); + // debugPrint( + // 'Successfully cast events for date: $date, events: $eventList'); + + return EventsMaker(date: date, events: eventList); + }, + ), + ); + } +} diff --git a/lib/src/features/period_planner/presentation/widgets/eventsMaker.dart b/lib/src/features/period_planner/presentation/widgets/eventsMaker.dart new file mode 100644 index 00000000..77cda650 --- /dev/null +++ b/lib/src/features/period_planner/presentation/widgets/eventsMaker.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:nishauri/src/features/period_planner/data/models/events.dart'; + +/* +This is responsible for the highlighting of the various events as you will see in the calendar +based on their Event Title and Color +For Example if the event is a Period Day, since the corresponding color is pink, +the day in the calendar will be highlighted in Pink. The same applies for the other events that is +Fertile days, Ovulation days and Predicted Period Days. +*/ +class EventsMaker extends StatelessWidget { + const EventsMaker({ + super.key, + required this.date, + required this.events, + }); + + final DateTime date; + final List events; + + @override + Widget build(BuildContext context) { + //debugPrint("----From EventMaker Class----"); + //debugPrint("Coloured Events $events"); + // return Positioned( + // right: 20, + // bottom: 1, + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: events.map((event) { + // Color color = event.color; + // return Container( + // margin: const EdgeInsets.symmetric(horizontal: 0.5), + // width: 7.0, + // height: 7.0, + // decoration: BoxDecoration( + // shape: BoxShape.circle, + // color: color, + // ), + // ); + // }).toList(), + // ), + // ); + + //Whole date highlighted + return Stack( + children: events.map((event) { + return Container( + margin: const EdgeInsets.all(4.0), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: event.color, + ), + alignment: Alignment.center, + child: Text( + date.day.toString(), + style: const TextStyle( + color: Colors.white, + //fontWeight: FontWeight.bold, + ), + ), + ); + }).toList(), + ); + } +} diff --git a/lib/src/features/period_planner/presentation/widgets/feature_tile.dart b/lib/src/features/period_planner/presentation/widgets/feature_tile.dart new file mode 100644 index 00000000..144afc51 --- /dev/null +++ b/lib/src/features/period_planner/presentation/widgets/feature_tile.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class FeatureTile extends StatelessWidget { + final String title; + final String description; + final IconData icon; + + const FeatureTile({ + required this.title, + required this.description, + required this.icon, + }); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: Icon(icon), + title: Text(title), + subtitle: Text(description), + ); + } +} \ No newline at end of file diff --git a/lib/src/features/period_planner/presentation/widgets/logItems.dart b/lib/src/features/period_planner/presentation/widgets/logItems.dart new file mode 100644 index 00000000..3b6353bb --- /dev/null +++ b/lib/src/features/period_planner/presentation/widgets/logItems.dart @@ -0,0 +1,80 @@ +// import 'package:flutter/material.dart'; +// import 'package:nishauri/src/features/period_planner/presentation/widgets/logger.dart'; + +// class LogItems { +// static List getSymptoms() { +// Color symptomsColor = Colors.pink; +// return [ +// Logger( +// icon: Icons.favorite, +// label: 'Cramps', +// color: symptomsColor, +// ), +// Logger( +// icon: Icons.sentiment_satisfied, +// label: 'Mood swings', +// color: symptomsColor, +// ), +// Logger( +// icon: Icons.battery_alert, +// label: 'Fatigue', +// color: symptomsColor, +// ), +// // Add more symptom loggers as needed +// ]; +// } + +// static List getDischarge() { +// Color dischargeColor = Colors.blue; +// return [ +// Logger( +// icon: Icons.water_drop, +// label: 'No discharge', +// color: dischargeColor, +// ), +// Logger( +// icon: Icons.water_drop, +// label: 'Egg whites', +// color: dischargeColor, +// ), +// Logger( +// icon: Icons.water_drop, +// label: 'Sticky', +// color: dischargeColor, +// ), +// Logger( +// icon: Icons.water_drop, +// label: 'Brown', +// color: dischargeColor, +// ), +// Logger( +// icon: Icons.water_drop, +// label: 'Yellow or Green', +// color: dischargeColor, +// ), +// // Add more discharge loggers as needed +// ]; +// } + +// static List getMoods() { +// Color moodsColor = Colors.orange; +// return [ +// Logger( +// icon: Icons.sentiment_very_satisfied, +// label: 'Happy', +// color: moodsColor, +// ), +// Logger( +// icon: Icons.sentiment_dissatisfied, +// label: 'Sad', +// color: moodsColor, +// ), +// Logger( +// icon: Icons.sentiment_neutral, +// label: 'Anxious', +// color: moodsColor, +// ), +// // Add more mood loggers as needed +// ]; +// } +// } diff --git a/lib/src/features/period_planner/presentation/widgets/logger.dart b/lib/src/features/period_planner/presentation/widgets/logger.dart new file mode 100644 index 00000000..4291da82 --- /dev/null +++ b/lib/src/features/period_planner/presentation/widgets/logger.dart @@ -0,0 +1,13 @@ +// import 'package:flutter/material.dart'; + +// class Logger { +// final IconData icon; +// final String label; +// final Color color; + +// Logger({ +// required this.icon, +// required this.label, +// required this.color, +// }); +// } \ No newline at end of file diff --git a/lib/src/features/period_planner/presentation/widgets/loggerWidget.dart b/lib/src/features/period_planner/presentation/widgets/loggerWidget.dart new file mode 100644 index 00000000..e608b8c8 --- /dev/null +++ b/lib/src/features/period_planner/presentation/widgets/loggerWidget.dart @@ -0,0 +1,98 @@ +// import 'package:flutter/material.dart'; +// import 'package:nishauri/src/features/period_planner/presentation/widgets/logger.dart'; +// import 'package:nishauri/src/utils/constants.dart'; + +// class LoggerWidget extends StatefulWidget { +// final String heading; +// final List items; + +// LoggerWidget({required this.heading, required this.items}); + +// @override +// _LoggerWidgetState createState() => _LoggerWidgetState(); +// } + +// class _LoggerWidgetState extends State { +// // Create a list to keep track of the selected items +// late List _selectedItems; + +// @override +// void initState() { +// super.initState(); +// // Initialize the list with false (none of the items are selected initially) +// _selectedItems = List.filled(widget.items.length, false); +// } + +// @override +// Widget build(BuildContext context) { +// final theme = Theme.of(context); +// return Column( +// crossAxisAlignment: CrossAxisAlignment.stretch, +// children: [ +// Text( +// widget.heading, +// textAlign: TextAlign.center, +// style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), +// ), +// const SizedBox(height: 16), +// SingleChildScrollView( +// scrollDirection: Axis.horizontal, +// child: Row( +// children: widget.items +// .asMap() +// .entries +// .map((entry) => _buildItemCard(entry.key, entry.value)) +// .toList(), +// ), +// ), +// Align( +// alignment: Alignment.bottomCenter, +// child: Padding( +// padding: const EdgeInsets.all(16.0), +// child: ElevatedButton( +// style: ElevatedButton.styleFrom( +// backgroundColor: Constants.periodPlanner, +// ), +// onPressed: () { +// // To add functionality later +// }, +// child: Text( +// 'Click to Apply', +// style: theme.textTheme.titleSmall?.copyWith( +// color: Colors.white, +// ), +// ), +// ), +// ), +// ), +// ], +// ); +// } + +// Widget _buildItemCard(int index, Logger item) { +// return GestureDetector( +// onTap: () { +// setState(() { +// _selectedItems[index] = !_selectedItems[index]; +// }); +// }, +// child: Padding( +// padding: const EdgeInsets.symmetric(horizontal: 8.0), +// child: Column( +// children: [ +// Stack( +// alignment: Alignment.center, +// children: [ +// Icon(item.icon, size: 40, color: item.color), +// if (_selectedItems[index]) +// Icon(Icons.check_circle, size: 40, color: Colors.green.withOpacity(0.9)), +// ], +// ), +// const SizedBox(height: 8), +// Text(item.label), +// ], +// ), +// ), +// ); +// } +// } diff --git a/lib/src/features/period_planner/presentation/widgets/modalContent.dart b/lib/src/features/period_planner/presentation/widgets/modalContent.dart new file mode 100644 index 00000000..9b1ed891 --- /dev/null +++ b/lib/src/features/period_planner/presentation/widgets/modalContent.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:nishauri/src/features/period_planner/data/models/cycle.dart'; +import 'package:nishauri/src/features/period_planner/data/models/events.dart'; +import 'package:nishauri/src/features/period_planner/data/providers/cycles_provider.dart'; +import 'package:nishauri/src/features/period_planner/presentation/widgets/calendarKey.dart'; +import 'package:nishauri/src/features/period_planner/presentation/widgets/customCalendar.dart'; +import 'package:nishauri/src/features/period_planner/utils/event_utils.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class ModalContent extends ConsumerStatefulWidget { + const ModalContent({super.key}); + + @override + ConsumerState createState() => _ModalContentState(); +} + +class _ModalContentState extends ConsumerState { + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final cycleAsyncValue = ref.watch(cyclesProvider); + return SizedBox( + //height: MediaQuery.of(context).size.height, + height: 600, + child: cycleAsyncValue.when( + data: (cycles) { + Map>> events = EventUtils.generateEvents(cycles); + return SingleChildScrollView( + child: Column( + children: [ + CustomCalendar( + events: events, + ), + const SizedBox(height: 20), + const Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.all(8.0), + child: Text( + "Key:", + style: TextStyle( + color: Constants.periodPlanner, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CalendarKey(color: Colors.red, label: 'Today'), + CalendarKey(color: Colors.pink, label: 'Period Days'), + CalendarKey(color: Colors.green, label: 'Fertile Days'), + CalendarKey(color: Colors.blue, label: 'Ovulation Day'), + CalendarKey(color: Colors.orange, label: 'Predicted Next Period Days'), + ], + ), + ], + ), + ); + }, + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stackTrace) => Center( + child: Text( + 'Failed to load cycles: $error', + style: const TextStyle(color: Colors.red), + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/features/period_planner/utils/event_utils.dart b/lib/src/features/period_planner/utils/event_utils.dart new file mode 100644 index 00000000..75e8e9fd --- /dev/null +++ b/lib/src/features/period_planner/utils/event_utils.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:nishauri/src/features/period_planner/data/models/cycle.dart'; +import 'package:nishauri/src/features/period_planner/data/models/events.dart'; +import 'package:table_calendar/table_calendar.dart'; + +/* +This class is responsible for generating events based the logged period days +and the days the algorithm predicts(Fertile, Ovulation and Predicted Next Period days) + +The Events in this case are Period, Fertile, Ovulation and Predicted Period Day of a cycle. +The Days that are within Period Days are give a title of Period Day and a color of Colors.pink. +The rest happens for the other days. +*/ +class EventUtils { + static Map>> generateEvents( + Map cyclesMap) { + Map>> events = {}; + // debugPrint("-----Generating New Events From Event Utils-----"); + + for (int cycleId in cyclesMap.keys) { + final cycle = cyclesMap[cycleId]; + + if (cycle == null) continue; + + if (!events.containsKey(cycleId)) { + events[cycleId] = {}; + } + + Map> updatedEvents = events[cycleId]!; + + // Add period days + + for (DateTime date = cycle.period_start; + date.isBefore(cycle.period_end) || + isSameDay(date, cycle.period_end); + date = date.add(const Duration(days: 1))) { + updatedEvents.update( + date, + (existingEvents) => + existingEvents..add(Event('Period Day', Colors.pink)), + ifAbsent: () => [Event('Period Day', Colors.pink)], + ); + } + + // Add fertile window days + for (DateTime date = cycle.fertile_start; + date.isBefore(cycle.fertile_end) || + isSameDay(date, cycle.fertile_end); + date = date.add(const Duration(days: 1))) { + updatedEvents.update( + date, + (existingEvents) => + existingEvents..add(Event('Fertile Day', Colors.green)), + ifAbsent: () => [Event('Fertile Day', Colors.green)], + ); + } + + // Add ovulation day + updatedEvents.update( + cycle.ovulation, + (existingEvents) => + existingEvents..add(Event('Ovulation Day', Colors.blue)), + ifAbsent: () => [Event('Ovulation Day', Colors.blue)], + ); + + // Add predicted period start + for (DateTime date = cycle.predicted_period_start; + date.isBefore(cycle.predicted_period_end) || + isSameDay(date, cycle.predicted_period_end); + date = date.add(const Duration(days: 1))) { + updatedEvents.update( + date, + (existingEvents) => + existingEvents..add(Event('Predicted Period Day', Colors.orange)), + ifAbsent: () => [Event('Predicted Period Day', Colors.orange)], + ); + } + } + + // events.forEach((id, events) { + // print("Cycle Id: $id"); + // print("["); + // events.forEach((date, event) { + // print("Date: $date, Event: $event\n"); + // }); + // print("]"); + // }); + + // print("--------------"); + return events; + } +} diff --git a/lib/src/features/programs/presentation/pages/programs.dart b/lib/src/features/programs/presentation/pages/programs.dart index 588d7f81..c4facae6 100644 --- a/lib/src/features/programs/presentation/pages/programs.dart +++ b/lib/src/features/programs/presentation/pages/programs.dart @@ -9,7 +9,7 @@ import 'package:intl/intl.dart'; import 'package:nishauri/src/app/navigation/menu/MenuItemsBuilder.dart'; import 'package:nishauri/src/app/navigation/menu/MenuOption.dart'; import 'package:nishauri/src/app/navigation/menu/menuItems.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/shared/display/background_image_widget.dart'; import 'package:nishauri/src/shared/display/dialogs.dart'; import 'package:nishauri/src/shared/input/Button.dart'; diff --git a/lib/src/features/provider/appointment_management/data/providers/appointment_management_provider.dart b/lib/src/features/provider/appointment_management/data/providers/appointment_management_provider.dart new file mode 100644 index 00000000..d57ef1fc --- /dev/null +++ b/lib/src/features/provider/appointment_management/data/providers/appointment_management_provider.dart @@ -0,0 +1,10 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:nishauri/src/features/appointments/data/models/appointment.dart'; +import 'package:nishauri/src/features/provider/appointment_management/data/repositories/appointment_management_repository.dart'; +import 'package:nishauri/src/features/provider/appointment_management/data/services/appointment_management_service.dart'; + +final appointmentRescheduleProvider = FutureProvider>((ref) async { + final service = AppointmentManagementService(); + final repo = AppointmentManagementRepository(service); + return await repo.getAppointmentReschedule(); +}); \ No newline at end of file diff --git a/lib/src/features/provider/appointment_management/data/repositories/appointment_management_repository.dart b/lib/src/features/provider/appointment_management/data/repositories/appointment_management_repository.dart new file mode 100644 index 00000000..355a6a4b --- /dev/null +++ b/lib/src/features/provider/appointment_management/data/repositories/appointment_management_repository.dart @@ -0,0 +1,12 @@ +import 'package:nishauri/src/features/appointments/data/models/appointment.dart'; +import 'package:nishauri/src/features/provider/appointment_management/data/services/appointment_management_service.dart'; + +class AppointmentManagementRepository { + final AppointmentManagementService _service; + + AppointmentManagementRepository(this._service); + + Future> getAppointmentReschedule() async { + return await _service.getRescheduleRequests(); + } +} \ No newline at end of file diff --git a/lib/src/features/provider/appointment_management/data/services/appointment_management_service.dart b/lib/src/features/provider/appointment_management/data/services/appointment_management_service.dart new file mode 100644 index 00000000..e75186d5 --- /dev/null +++ b/lib/src/features/provider/appointment_management/data/services/appointment_management_service.dart @@ -0,0 +1,18 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:nishauri/src/features/appointments/data/models/appointment.dart'; +import 'package:nishauri/src/shared/interfaces/HTTPService.dart'; +import 'package:nishauri/src/utils/helpers.dart'; + +class AppointmentManagementService extends HTTPService { + Future> getRescheduleRequests() async { + final data = await loadJsonData("assets/data/reschedule.json"); + final json = jsonDecode(data); + final appointments = json + .map((an) => Appointment.fromJson(Map.from(an))).toList(); + + log("$appointments"); + return [...appointments]; + } +} \ No newline at end of file diff --git a/lib/src/features/provider/appointment_management/presentation/pages/reschedule_request_list.dart b/lib/src/features/provider/appointment_management/presentation/pages/reschedule_request_list.dart new file mode 100644 index 00000000..2b2b09a4 --- /dev/null +++ b/lib/src/features/provider/appointment_management/presentation/pages/reschedule_request_list.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nishauri/src/features/provider/appointment_management/data/providers/appointment_management_provider.dart'; +import 'package:nishauri/src/features/provider/appointment_management/presentation/widget/current_request.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomTabBar.dart'; +import 'package:nishauri/src/shared/display/background_image_widget.dart'; +import 'package:nishauri/src/shared/notifications/count_budge.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class RescheduleRequestListScreen extends HookConsumerWidget { + const RescheduleRequestListScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = Theme.of(context); + final appointmentRescheduleAsync = ref.watch(appointmentRescheduleProvider); + final currIndex = useState(0); + + return appointmentRescheduleAsync.when(data: (data) { + if (data.isEmpty) { + return const BackgroundImageWidget( + customAppBar: CustomAppBar( + title: "Reschedule Requests 📆", + color: Constants.labResultsColor, + ), + svgImage: "assets/images/appointments-empty.svg", + notFoundText: "No Content", + ); + } + + final screen = [ + RescheduleRequestList(appointments: data), + const Center( + child: BackgroundImageWidget( + svgImage: 'assets/images/lab-empty-state.svg', + notFoundText: "No Past reschedule request", + ), + ), + ]; + + return Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const CustomAppBar( + title: "Reschedule Requests 📆", + color: Constants.labResultsColor, + ), + CustomTabBar( + onTap: (item, index) { + currIndex.value = index; + }, + activeColor: Constants.labResultsColor.withOpacity(0.5), + activeIndex: currIndex.value, + items: [ + CustomTabBarItem( + title: "Appointments Reschedule Request", + trailing: CountBadge(count: data.length), + ), + const CustomTabBarItem(title: "History"), + ], + ), + Expanded( + child: screen[currIndex.value], + ), + ], + ), + ); + }, + error: (error, _) => Center(child: Text(error.toString())), + loading: () => const CircularProgressIndicator()); + } +} diff --git a/lib/src/features/provider/appointment_management/presentation/widget/current_request.dart b/lib/src/features/provider/appointment_management/presentation/widget/current_request.dart new file mode 100644 index 00000000..0bfb2e70 --- /dev/null +++ b/lib/src/features/provider/appointment_management/presentation/widget/current_request.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nishauri/src/features/appointments/data/models/appointment.dart'; +import 'package:nishauri/src/features/provider/appointment_management/data/providers/appointment_management_provider.dart'; +import 'package:nishauri/src/shared/dialog/dialog.dart'; +import 'package:nishauri/src/shared/list_view_builder/list_view_builder.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class RescheduleRequestList extends HookConsumerWidget { + final List appointments; + + const RescheduleRequestList({Key? key, required this.appointments}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: ListView.builder( + itemCount: appointments.length, + itemBuilder: (context, index) { + final app = appointments[index]; + return RowViewList( + details: [ + {"icon": Icons.account_circle_outlined, 'text': "Patient No: ${app.ccc_no ?? ''}"}, + {'icon': Icons.date_range, 'text': "App Type: ${app.appointment_type ?? ''}"}, + {'icon': Icons.calendar_month, 'text': "App Date: ${app.appointment_date ?? ''}"}, + {'icon': Icons.date_range, 'text': "Reschedule Date: ${app.reschedule_date ?? ''}"}, + {'icon': Icons.date_range, 'text': "Reason for reschedule: ${app.reschedule_reason ?? ''}"}, + {'icon': Icons.build_circle_outlined, 'text': "Status: ${app.appt_status ?? ''}"}, + ], + onRowTap: (index) { + HealthProgramDialog( + context, + "${app.ccc_no} from ${app.appointment_date} to ${app.reschedule_date}", + "Reschedule the Appointment for patient", + Constants.providerBgColor.withOpacity(0.5), + "Approve", + Constants.providerBgColor.withOpacity(0.2), + "Reject", + Constants.providerBgColor, + (){ + ref.refresh(appointmentRescheduleProvider); + }, + (){ + ref.refresh(appointmentRescheduleProvider); + }, + ).show(); + }, + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/features/provider/appointment_management/presentation/widget/previous_request.dart b/lib/src/features/provider/appointment_management/presentation/widget/previous_request.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/src/features/provider/dawa_drop_management/presentation/pages/dawa_drop_manager_screen.dart b/lib/src/features/provider/dawa_drop_management/presentation/pages/dawa_drop_manager_screen.dart new file mode 100644 index 00000000..ec1459f7 --- /dev/null +++ b/lib/src/features/provider/dawa_drop_management/presentation/pages/dawa_drop_manager_screen.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nishauri/src/features/dawa_drop/data/models/order_request/drug_order.dart'; +import 'package:nishauri/src/features/dawa_drop/data/providers/drug_order_provider.dart'; +import 'package:nishauri/src/features/provider/dawa_drop_management/presentation/widgets/drug_approved_list.dart'; +import 'package:nishauri/src/features/provider/dawa_drop_management/presentation/widgets/drug_dispatched_list.dart'; +import 'package:nishauri/src/features/provider/dawa_drop_management/presentation/widgets/drug_fulfilled_list.dart'; +import 'package:nishauri/src/features/provider/dawa_drop_management/presentation/widgets/drug_request_list.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomTabBar.dart'; +import 'package:nishauri/src/shared/display/background_image_widget.dart'; +import 'package:nishauri/src/shared/notifications/count_budge.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class DawaDropManagemerScreen extends HookConsumerWidget { + const DawaDropManagemerScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = Theme.of(context); + final dawaDropRequestsAsync = ref.watch(drugOrderProvider); + final currentIndex = useState(0); + + return dawaDropRequestsAsync.when(data: (data) { + if (data.isEmpty) { + return const BackgroundImageWidget( + customAppBar: CustomAppBar( + title: "Dawa Drop Requests", + color: Constants.labResultsColor, + ), + svgImage: "assets/images/appointments-empty.svg", + notFoundText: "No Requests found", + ); + } + + List pending = data.where((order) => order.status == 'Pending').toList(); + List approved = data.where((order) => order.status == 'Approved').toList(); + List dispatched = data.where((order) => order.status == 'Dispatched').toList(); + List fulfilled = data.where((order) => order.status == 'Fullfilled').toList(); + + final screen = [ + pending.isEmpty ? + const Center( + child: BackgroundImageWidget( + svgImage: 'assets/images/lab-empty-state.svg', + notFoundText: "No current drug requests to approve"), + ) : + + DrugRequestList(orders: pending), + + approved.isEmpty ? const Center( + child: BackgroundImageWidget( + svgImage: 'assets/images/lab-empty-state.svg', + notFoundText: "No current drug request to dispatch"), + ): + DrugApprovedRequestList(orders: approved), + dispatched.isEmpty ? const Center( + child: BackgroundImageWidget( + svgImage: 'assets/images/lab-empty-state.svg', + notFoundText: "No current drug dispatched"), + ): + DrugDispatchedList(orders: dispatched), + fulfilled.isEmpty ? const Center( + child: BackgroundImageWidget( + svgImage: 'assets/images/lab-empty-state.svg', + notFoundText: "No Fulfilled orders."), + ): + DrugFulfilledList(orders: fulfilled), + ]; + return Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const CustomAppBar( + title: "Dawa Drop Requests 📋", + color: Constants.labResultsColor, + ), + CustomTabBar( + onTap: (item, index) { + currentIndex.value = index; + }, + activeColor: Constants.labResultsColor.withOpacity(0.5), + activeIndex: currentIndex.value, + items: [ + CustomTabBarItem(title: "Drug Requests", trailing: CountBadge(count: pending.length,)), + CustomTabBarItem(title: "Approved Requests", trailing: CountBadge(count: approved.length,)), + const CustomTabBarItem(title: "Dispatched Requests"), + const CustomTabBarItem(title: "Fulfilled Requests"), + ], + ), + Expanded(child: screen[currentIndex.value]), + ], + ), + ); + }, + error: (error, _) => Center(child: Text(error.toString()),), + loading: () => CircularProgressIndicator() + ); + + } +} \ No newline at end of file diff --git a/lib/src/features/provider/dawa_drop_management/presentation/widgets/drug_approved_list.dart b/lib/src/features/provider/dawa_drop_management/presentation/widgets/drug_approved_list.dart new file mode 100644 index 00000000..b9076065 --- /dev/null +++ b/lib/src/features/provider/dawa_drop_management/presentation/widgets/drug_approved_list.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:intl/intl.dart'; +import 'package:nishauri/src/features/dawa_drop/data/models/order_request/drug_order.dart'; +import 'package:nishauri/src/features/dawa_drop/data/providers/drug_order_provider.dart'; +import 'package:nishauri/src/shared/dialog/dialog.dart'; +import 'package:nishauri/src/shared/list_view_builder/list_view_builder.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class DrugApprovedRequestList extends HookConsumerWidget { + + final List orders; + + const DrugApprovedRequestList({Key? key, required this.orders}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: ListView.builder( + itemCount: orders.length, + itemBuilder: (context, index) { + final order = orders[index]; + return RowViewList( + details: [ + {"icon": Icons.ac_unit_outlined, 'text': "Delivery Method: ${order.delivery_method ?? ''}"}, + if (order.courierService?.name != null && order.courierService?.name != '') + {"icon": Icons.bike_scooter_sharp, 'text': "Courier Service: ${order.courierService?.name ?? ''}"}, + if (order.deliveryPerson?.fullName != null && order.deliveryPerson?.fullName != '') + {"icon": Icons.person, 'text': "Delivery Person: ${order.deliveryPerson?.fullName ?? ''}"}, + if (order.deliveryPerson?.fullName != null && order.deliveryPerson?.fullName != '') + {"icon": Icons.phone_android, 'text': "Delivery Person: ${order.deliveryPerson?.phoneNumber ?? ''}"}, + {"icon": Icons.today, 'text': "Date order approved: ${DateFormat('dd-MM-yyyy').format(DateTime.parse(order.approved_date ?? ''))}"}, + {"icon": Icons.rotate_left_outlined, 'text': "Delivery Status: ${order.status ?? ''}"}, + ], + onRowTap: (index) { + HealthProgramDialog( + context, + "Order id : ${order.order_id}", + "Dispatch the Drug order request bellow", + Constants.providerBgColor.withOpacity(0.5), + "Approve", + Constants.providerBgColor.withOpacity(0.2), + "Reject", + Constants.providerBgColor, + (){ + ref.refresh(drugOrderProvider); + }, + (){ + ref.refresh(drugOrderProvider); + }, + ).show(); + }, + ); + }, + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/features/provider/dawa_drop_management/presentation/widgets/drug_dispatched_list.dart b/lib/src/features/provider/dawa_drop_management/presentation/widgets/drug_dispatched_list.dart new file mode 100644 index 00000000..a81a3f78 --- /dev/null +++ b/lib/src/features/provider/dawa_drop_management/presentation/widgets/drug_dispatched_list.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:intl/intl.dart'; +import 'package:nishauri/src/features/dawa_drop/data/models/order_request/drug_order.dart'; +import 'package:nishauri/src/shared/list_view_builder/list_view_builder.dart'; + +class DrugDispatchedList extends HookConsumerWidget { + + final List orders; + + const DrugDispatchedList({Key? key, required this.orders}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: ListView.builder( + itemCount: orders.length, + itemBuilder: (context, index) { + final order = orders[index]; + return RowViewList( + details: [ + {"icon": Icons.ac_unit_outlined, 'text': "Delivery Method: ${order.delivery_method ?? ''}"}, + if (order.courierService?.name != null && order.courierService?.name != '') + {"icon": Icons.bike_scooter_sharp, 'text': "Courier Service: ${order.courierService?.name ?? ''}"}, + if (order.deliveryPerson?.fullName != null && order.deliveryPerson?.fullName != '') + {"icon": Icons.person, 'text': "Delivery Person: ${order.deliveryPerson?.fullName ?? ''}"}, + if (order.deliveryPerson?.fullName != null && order.deliveryPerson?.fullName != '') + {"icon": Icons.phone_android, 'text': "Delivery Person: ${order.deliveryPerson?.phoneNumber ?? ''}"}, + {"icon": Icons.today, 'text': "Date order dispatched: ${DateFormat('dd-MM-yyyy').format(DateTime.parse(order.dispatched_date ?? ''))}"}, + {"icon": Icons.rotate_left_outlined, 'text': "Delivery Status: ${order.status ?? ''}"}, + ], + // onRowTap: (index) { + // HealthProgramDialog( + // context, + // "Order id : ${order.order_id}", + // "Approve Drug order request bellow", + // Constants.providerBgColor.withOpacity(0.5), + // "Approve", + // Constants.providerBgColor.withOpacity(0.2), + // "Reject", + // Constants.providerBgColor, + // (){ + // ref.refresh(drugOrderProvider); + // }, + // (){ + // ref.refresh(drugOrderProvider); + // }, + // ).show(); + // }, + ); + }, + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/features/provider/dawa_drop_management/presentation/widgets/drug_fulfilled_list.dart b/lib/src/features/provider/dawa_drop_management/presentation/widgets/drug_fulfilled_list.dart new file mode 100644 index 00000000..8a11b249 --- /dev/null +++ b/lib/src/features/provider/dawa_drop_management/presentation/widgets/drug_fulfilled_list.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:intl/intl.dart'; +import 'package:nishauri/src/features/dawa_drop/data/models/order_request/drug_order.dart'; +import 'package:nishauri/src/shared/list_view_builder/list_view_builder.dart'; + +class DrugFulfilledList extends HookConsumerWidget { + + final List orders; + + const DrugFulfilledList({Key? key, required this.orders}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: ListView.builder( + itemCount: orders.length, + itemBuilder: (context, index) { + final order = orders[index]; + return RowViewList( + details: [ + {"icon": Icons.ac_unit_outlined, 'text': "Delivery Method: ${order.delivery_method ?? ''}"}, + if (order.courierService?.name != null && order.courierService?.name != '') + {"icon": Icons.bike_scooter_sharp, 'text': "Courier Service: ${order.courierService?.name ?? ''}"}, + if (order.deliveryPerson?.fullName != null && order.deliveryPerson?.fullName != '') + {"icon": Icons.person, 'text': "Delivery Person: ${order.deliveryPerson?.fullName ?? ''}"}, + if (order.deliveryPerson?.fullName != null && order.deliveryPerson?.fullName != '') + {"icon": Icons.phone_android, 'text': "Delivery Person: ${order.deliveryPerson?.phoneNumber ?? ''}"}, + {"icon": Icons.today, 'text': "Date order FulFilled: ${DateFormat('dd-MM-yyyy').format(DateTime.parse(order.fullfilled_date ?? ''))}"}, + {"icon": Icons.rotate_left_outlined, 'text': "Delivery Status: ${order.status ?? ''}"}, + ], + // onRowTap: (index) { + // HealthProgramDialog( + // context, + // "Order id : ${order.order_id}", + // "Approve Drug order request bellow", + // Constants.providerBgColor.withOpacity(0.5), + // "Approve", + // Constants.providerBgColor.withOpacity(0.2), + // "Reject", + // Constants.providerBgColor, + // (){ + // ref.refresh(drugOrderProvider); + // }, + // (){ + // ref.refresh(drugOrderProvider); + // }, + // ).show(); + // }, + ); + }, + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/features/provider/dawa_drop_management/presentation/widgets/drug_request_list.dart b/lib/src/features/provider/dawa_drop_management/presentation/widgets/drug_request_list.dart new file mode 100644 index 00000000..c0a0558c --- /dev/null +++ b/lib/src/features/provider/dawa_drop_management/presentation/widgets/drug_request_list.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:intl/intl.dart'; +import 'package:nishauri/src/features/dawa_drop/data/models/order_request/drug_order.dart'; +import 'package:nishauri/src/features/dawa_drop/data/providers/drug_order_provider.dart'; +import 'package:nishauri/src/shared/dialog/dialog.dart'; +import 'package:nishauri/src/shared/list_view_builder/list_view_builder.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class DrugRequestList extends HookConsumerWidget { + + final List orders; + + const DrugRequestList({Key? key, required this.orders}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: ListView.builder( + itemCount: orders.length, + itemBuilder: (context, index) { + final order = orders[index]; + return RowViewList( + details: [ + {"icon": Icons.ac_unit_outlined, 'text': "Delivery Method: ${order.delivery_method ?? ''}"}, + if (order.courierService?.name != null && order.courierService?.name != '') + {"icon": Icons.bike_scooter_sharp, 'text': "Courier Service: ${order.courierService?.name ?? ''}"}, + if (order.deliveryPerson?.fullName != null && order.deliveryPerson?.fullName != '') + {"icon": Icons.person, 'text': "Delivery Person: ${order.deliveryPerson?.fullName ?? ''}"}, + if (order.deliveryPerson?.fullName != null && order.deliveryPerson?.fullName != '') + {"icon": Icons.phone_android, 'text': "Delivery Person: ${order.deliveryPerson?.phoneNumber ?? ''}"}, + {"icon": Icons.today, 'text': "Date order posted: ${DateFormat('dd-MM-yyyy').format(DateTime.parse(order.date_order_posted ?? ''))}"}, + {"icon": Icons.rotate_left_outlined, 'text': "Delivery Status: ${order.status ?? ''}"}, + ], + onRowTap: (index) { + HealthProgramDialog( + context, + "Order id : ${order.order_id}", + "Approve Drug order request bellow", + Constants.labResultsColor.withOpacity(0.5), + "Approve", + Constants.labResultsColor.withOpacity(0.2), + "Reject", + Constants.labResultsColor, + (){ + ref.refresh(drugOrderProvider); + }, + (){ + ref.refresh(drugOrderProvider); + }, + ).show(); + }, + ); + }, + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/features/provider/location/data/models/location.dart b/lib/src/features/provider/location/data/models/location.dart new file mode 100644 index 00000000..57e461d6 --- /dev/null +++ b/lib/src/features/provider/location/data/models/location.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'location.freezed.dart'; +part 'location.g.dart'; + +@Freezed() +class Location with _$Location { + const factory Location({ + int? county_id, + int? sub_county_id, + String? sub_county_name, + String? county_name, + String? facility_name, + int? mfl_code, + }) = _Location; + + factory Location.fromJson(Map json)=> _$LocationFromJson(json); +} \ No newline at end of file diff --git a/lib/src/features/provider/location/data/models/location.freezed.dart b/lib/src/features/provider/location/data/models/location.freezed.dart new file mode 100644 index 00000000..478c53d1 --- /dev/null +++ b/lib/src/features/provider/location/data/models/location.freezed.dart @@ -0,0 +1,261 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'location.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Location _$LocationFromJson(Map json) { + return _Location.fromJson(json); +} + +/// @nodoc +mixin _$Location { + int? get county_id => throw _privateConstructorUsedError; + int? get sub_county_id => throw _privateConstructorUsedError; + String? get sub_county_name => throw _privateConstructorUsedError; + String? get county_name => throw _privateConstructorUsedError; + String? get facility_name => throw _privateConstructorUsedError; + int? get mfl_code => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $LocationCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LocationCopyWith<$Res> { + factory $LocationCopyWith(Location value, $Res Function(Location) then) = + _$LocationCopyWithImpl<$Res, Location>; + @useResult + $Res call( + {int? county_id, + int? sub_county_id, + String? sub_county_name, + String? county_name, + String? facility_name, + int? mfl_code}); +} + +/// @nodoc +class _$LocationCopyWithImpl<$Res, $Val extends Location> + implements $LocationCopyWith<$Res> { + _$LocationCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? county_id = freezed, + Object? sub_county_id = freezed, + Object? sub_county_name = freezed, + Object? county_name = freezed, + Object? facility_name = freezed, + Object? mfl_code = freezed, + }) { + return _then(_value.copyWith( + county_id: freezed == county_id + ? _value.county_id + : county_id // ignore: cast_nullable_to_non_nullable + as int?, + sub_county_id: freezed == sub_county_id + ? _value.sub_county_id + : sub_county_id // ignore: cast_nullable_to_non_nullable + as int?, + sub_county_name: freezed == sub_county_name + ? _value.sub_county_name + : sub_county_name // ignore: cast_nullable_to_non_nullable + as String?, + county_name: freezed == county_name + ? _value.county_name + : county_name // ignore: cast_nullable_to_non_nullable + as String?, + facility_name: freezed == facility_name + ? _value.facility_name + : facility_name // ignore: cast_nullable_to_non_nullable + as String?, + mfl_code: freezed == mfl_code + ? _value.mfl_code + : mfl_code // ignore: cast_nullable_to_non_nullable + as int?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$LocationImplCopyWith<$Res> + implements $LocationCopyWith<$Res> { + factory _$$LocationImplCopyWith( + _$LocationImpl value, $Res Function(_$LocationImpl) then) = + __$$LocationImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int? county_id, + int? sub_county_id, + String? sub_county_name, + String? county_name, + String? facility_name, + int? mfl_code}); +} + +/// @nodoc +class __$$LocationImplCopyWithImpl<$Res> + extends _$LocationCopyWithImpl<$Res, _$LocationImpl> + implements _$$LocationImplCopyWith<$Res> { + __$$LocationImplCopyWithImpl( + _$LocationImpl _value, $Res Function(_$LocationImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? county_id = freezed, + Object? sub_county_id = freezed, + Object? sub_county_name = freezed, + Object? county_name = freezed, + Object? facility_name = freezed, + Object? mfl_code = freezed, + }) { + return _then(_$LocationImpl( + county_id: freezed == county_id + ? _value.county_id + : county_id // ignore: cast_nullable_to_non_nullable + as int?, + sub_county_id: freezed == sub_county_id + ? _value.sub_county_id + : sub_county_id // ignore: cast_nullable_to_non_nullable + as int?, + sub_county_name: freezed == sub_county_name + ? _value.sub_county_name + : sub_county_name // ignore: cast_nullable_to_non_nullable + as String?, + county_name: freezed == county_name + ? _value.county_name + : county_name // ignore: cast_nullable_to_non_nullable + as String?, + facility_name: freezed == facility_name + ? _value.facility_name + : facility_name // ignore: cast_nullable_to_non_nullable + as String?, + mfl_code: freezed == mfl_code + ? _value.mfl_code + : mfl_code // ignore: cast_nullable_to_non_nullable + as int?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$LocationImpl implements _Location { + const _$LocationImpl( + {this.county_id, + this.sub_county_id, + this.sub_county_name, + this.county_name, + this.facility_name, + this.mfl_code}); + + factory _$LocationImpl.fromJson(Map json) => + _$$LocationImplFromJson(json); + + @override + final int? county_id; + @override + final int? sub_county_id; + @override + final String? sub_county_name; + @override + final String? county_name; + @override + final String? facility_name; + @override + final int? mfl_code; + + @override + String toString() { + return 'Location(county_id: $county_id, sub_county_id: $sub_county_id, sub_county_name: $sub_county_name, county_name: $county_name, facility_name: $facility_name, mfl_code: $mfl_code)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LocationImpl && + (identical(other.county_id, county_id) || + other.county_id == county_id) && + (identical(other.sub_county_id, sub_county_id) || + other.sub_county_id == sub_county_id) && + (identical(other.sub_county_name, sub_county_name) || + other.sub_county_name == sub_county_name) && + (identical(other.county_name, county_name) || + other.county_name == county_name) && + (identical(other.facility_name, facility_name) || + other.facility_name == facility_name) && + (identical(other.mfl_code, mfl_code) || + other.mfl_code == mfl_code)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, county_id, sub_county_id, + sub_county_name, county_name, facility_name, mfl_code); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$LocationImplCopyWith<_$LocationImpl> get copyWith => + __$$LocationImplCopyWithImpl<_$LocationImpl>(this, _$identity); + + @override + Map toJson() { + return _$$LocationImplToJson( + this, + ); + } +} + +abstract class _Location implements Location { + const factory _Location( + {final int? county_id, + final int? sub_county_id, + final String? sub_county_name, + final String? county_name, + final String? facility_name, + final int? mfl_code}) = _$LocationImpl; + + factory _Location.fromJson(Map json) = + _$LocationImpl.fromJson; + + @override + int? get county_id; + @override + int? get sub_county_id; + @override + String? get sub_county_name; + @override + String? get county_name; + @override + String? get facility_name; + @override + int? get mfl_code; + @override + @JsonKey(ignore: true) + _$$LocationImplCopyWith<_$LocationImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/provider/location/data/models/location.g.dart b/lib/src/features/provider/location/data/models/location.g.dart new file mode 100644 index 00000000..4f277c29 --- /dev/null +++ b/lib/src/features/provider/location/data/models/location.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'location.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$LocationImpl _$$LocationImplFromJson(Map json) => + _$LocationImpl( + county_id: (json['county_id'] as num?)?.toInt(), + sub_county_id: (json['sub_county_id'] as num?)?.toInt(), + sub_county_name: json['sub_county_name'] as String?, + county_name: json['county_name'] as String?, + facility_name: json['facility_name'] as String?, + mfl_code: (json['mfl_code'] as num?)?.toInt(), + ); + +Map _$$LocationImplToJson(_$LocationImpl instance) => + { + 'county_id': instance.county_id, + 'sub_county_id': instance.sub_county_id, + 'sub_county_name': instance.sub_county_name, + 'county_name': instance.county_name, + 'facility_name': instance.facility_name, + 'mfl_code': instance.mfl_code, + }; diff --git a/lib/src/features/provider/presentation/pages/provider_main_Screen.dart b/lib/src/features/provider/presentation/pages/provider_main_Screen.dart new file mode 100644 index 00000000..ec07230e --- /dev/null +++ b/lib/src/features/provider/presentation/pages/provider_main_Screen.dart @@ -0,0 +1,241 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nishauri/src/app/navigation/menu/MenuItemsBuilder.dart'; +import 'package:nishauri/src/app/navigation/menu/menuItems.dart'; +import 'package:nishauri/src/features/user/data/providers/user_provider.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +import '../../../appointments/data/models/appointment.dart'; +import '../../../appointments/presentation/pages/AppointmentRescheduleScreen.dart'; +import '../../../common/presentation/widgets/AppointmentCard.dart'; +import '../../../self_screening/presentation/widgets/health_list.dart'; +import 'package:nishauri/src/features/provider/provider_registry/data/providers/provider_registry_provider.dart'; + +_menuItems(BuildContext context, bool isProvider) { + if (isProvider) { + return [ + {"type": "card", "item": MenuItem( + shortcutBackgroundColor: Constants.labResultsColor, + icon: SvgPicture.asset( + "assets/images/review.svg", + semanticsLabel: "Provider", + fit: BoxFit.contain, + width: 80, + height: 80, + ), + shortcutIcon: SvgPicture.asset("assets/images/review.svg", + semanticsLabel: "provider", + fit: BoxFit.contain, + width: Constants.shortcutIconSize, + height: Constants.shortcutIconSize), + title: "Update Provider Registration", + onPressed: () => context.goNamed(RouteNames.LOCATION_SELECTION), + color: Constants.labResultsColor.withOpacity(0.5), + )}, + ]; + } else { + return [ + {"type": "card", "item": MenuItem( + shortcutBackgroundColor: Constants.labResultsColor, + icon: SvgPicture.asset( + "assets/images/doctor-coat.svg", + semanticsLabel: "Provider Details", + fit: BoxFit.contain, + width: 80, + height: 80, + ), + shortcutIcon: SvgPicture.asset("assets/images/doctor-coat.svg", + semanticsLabel: "Provider Details", + fit: BoxFit.contain, + width: Constants.shortcutIconSize, + height: Constants.shortcutIconSize), + title: "Provider Details", + onPressed: () => context.goNamed(RouteNames.PROVIDER_DETAILS), + color: Constants.labResultsColor.withOpacity(0.5), + )}, + {"type": "list", "items": [ + MenuItem( + shortcutBackgroundColor: Constants.labResultsColor, + icon: SvgPicture.asset( + "assets/images/Calendar-Splash.svg", + semanticsLabel: "Reschedule", + fit: BoxFit.contain, + width: 100, + height: 100, + ), + shortcutIcon: SvgPicture.asset("assets/images/Calendar-Splash.svg", + semanticsLabel: "Reschedule", + fit: BoxFit.contain, + width: Constants.shortcutIconSize, + height: Constants.shortcutIconSize), + title: "Appointment Reschedule Request", + onPressed: () => context.goNamed(RouteNames.REQUEST_APP_RESCHEDULE), + color: Constants.labResultsColor.withOpacity(0.5), + ), + MenuItem( + shortcutBackgroundColor: Constants.providerBgColor, + icon: SvgPicture.asset( + "assets/images/shopping-cart-dawa.svg", + semanticsLabel: "Dawa Drop Manager", + fit: BoxFit.contain, + width: 80, + height: 80, + ), + shortcutIcon: SvgPicture.asset("assets/images/shopping-cart-dawa.svg", + semanticsLabel: "Dawa Drop Manager", + fit: BoxFit.contain, + width: Constants.shortcutIconSize, + height: Constants.shortcutIconSize), + title: "Drug Order Manager", + onPressed: () => context.goNamed(RouteNames.DAWA_DROP_MANAGER), + color: Constants.providerBgColor.withOpacity(0.5), + ), + ]} + ]; + } +} + + +class ProviderMainScreen extends HookConsumerWidget { + const ProviderMainScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = Theme.of(context); + final user = ref.watch(userProvider); + final size = MediaQuery.of(context).size; + + final isProvider = user.when( + data: (provider) => provider.provider_id != "no", + error: (error, stack) => false, + loading: () => false, + ); + + final rescheduleAndManagerItems = [ + "Appointment Reschedule Request", + "Drug Order Manager", + ]; + + final rescheduleAndManagerPaths = [ + RouteNames.REQUEST_APP_RESCHEDULE, + RouteNames.DAWA_DROP_MANAGER, + ]; + + final rescheduleAndManagerIcons = [ + "assets/images/Calendar-Splash.svg", + "assets/images/shopping-cart-dawa.svg", + ]; + + + final appointmentDate = DateTime.now(); + + return Scaffold( + body: Column( + children: [ + CustomAppBar( + title: "Provider Modules 🏥", + subTitle: "Manage patient records, schedule appointments and track treatments", + color: Constants.labResultsColor, + ), + Expanded( + child: !isProvider + ? Column( + children: [ + // Replacing the Provider Details card with AppointmentCard + SizedBox( + width: size.width * 0.99, + child: Consumer( + builder: (context, ref, child) { + final userAsync = ref.watch(getProviderDetailsProvider); + return userAsync.when( + data: (provider) => AppointmentCard( + appointmentType: " ", + appointmentTime: appointmentDate, + // Add cadre display + cadre: provider.cadre != null + ? "${provider.cadre}" + : "cadre: N/A", + // cadre: provider.cadre!= null + // ? "Board Number: ${provider.board_number}" + // : "Board Number: N/A", + providerImage: "https://www.insurancejournal.com/wp-content/uploads/2014/03/hospital.jpg", + providerName: "${provider.salutation ?? ""} ${provider.family_name ?? ""} ${provider.given_name ?? ""}".trim(), + onRescheduleTap: () => context.goNamed(RouteNames.PROVIDER_DETAILS), + rescheduleButtonText: "View Profile", + + // Add board_number display + boardNumber: provider.board_number != null + ? "${provider.board_number}" + : "Board Number: N/A", + showAppointmentTime: false, + + + + ), + error: (error, stack) => Center(child: Text("Error: ${error.toString()}")), + loading: () => const Center(child: CircularProgressIndicator()), + ); + }, + ), + ), + const SizedBox(height: Constants.SPACING), + ItemList( + items: rescheduleAndManagerItems, + path: rescheduleAndManagerPaths, + svgAsset: rescheduleAndManagerIcons, + backgroundColor: Constants.bgColor, + ), + ], + ) + : MenuItemsBuilder( + crossAxisCount: 2, + items: _menuItems(context, isProvider), + itemBuilder: (item) => Card( + margin: const EdgeInsets.all(Constants.SPACING), + clipBehavior: Clip.antiAlias, + child: InkWell( + splashColor: theme.primaryColorDark.withOpacity(0.5), + onTap: item.onPressed, + child: Container( + padding: const EdgeInsets.all(Constants.SPACING), + decoration: BoxDecoration( + color: item.color ?? theme.colorScheme.primary, + image: const DecorationImage( + image: AssetImage("assets/images/contours.png"), + opacity: 0.2, + fit: BoxFit.cover, + ), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + item.icon, + const SizedBox(height: Constants.SPACING), + Text( + item.title ?? '', + style: theme.textTheme.titleMedium?.copyWith( + color: Colors.white, + fontWeight: FontWeight.normal, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ), + ), + ), + ), + ], + ), + ); + } + +} diff --git a/lib/src/features/provider/provider_registry/data/models/provider_registry.dart b/lib/src/features/provider/provider_registry/data/models/provider_registry.dart new file mode 100644 index 00000000..cd3fef08 --- /dev/null +++ b/lib/src/features/provider/provider_registry/data/models/provider_registry.dart @@ -0,0 +1,24 @@ +import 'dart:core'; + +import 'package:freezed_annotation/freezed_annotation.dart'; +part 'provider_registry.freezed.dart'; +part 'provider_registry.g.dart'; + +@Freezed() +class ProviderRegistry with _$ProviderRegistry { + const factory ProviderRegistry({ + int? id, + String? family_name, + String? given_name, + int? national_id, + String? licence_number, + String? board_number, + String? cadre, + String? gender, + String? facility_code, + int? user_id, + String? salutation, + + }) = _ProviderRegistry; + factory ProviderRegistry.fromJson(Map json)=> _$ProviderRegistryFromJson(json); +} \ No newline at end of file diff --git a/lib/src/features/provider/provider_registry/data/models/provider_registry.freezed.dart b/lib/src/features/provider/provider_registry/data/models/provider_registry.freezed.dart new file mode 100644 index 00000000..3b650801 --- /dev/null +++ b/lib/src/features/provider/provider_registry/data/models/provider_registry.freezed.dart @@ -0,0 +1,375 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'provider_registry.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +ProviderRegistry _$ProviderRegistryFromJson(Map json) { + return _ProviderRegistry.fromJson(json); +} + +/// @nodoc +mixin _$ProviderRegistry { + int? get id => throw _privateConstructorUsedError; + String? get family_name => throw _privateConstructorUsedError; + String? get given_name => throw _privateConstructorUsedError; + int? get national_id => throw _privateConstructorUsedError; + String? get licence_number => throw _privateConstructorUsedError; + String? get board_number => throw _privateConstructorUsedError; + String? get cadre => throw _privateConstructorUsedError; + String? get gender => throw _privateConstructorUsedError; + String? get facility_code => throw _privateConstructorUsedError; + int? get user_id => throw _privateConstructorUsedError; + String? get salutation => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ProviderRegistryCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProviderRegistryCopyWith<$Res> { + factory $ProviderRegistryCopyWith( + ProviderRegistry value, $Res Function(ProviderRegistry) then) = + _$ProviderRegistryCopyWithImpl<$Res, ProviderRegistry>; + @useResult + $Res call( + {int? id, + String? family_name, + String? given_name, + int? national_id, + String? licence_number, + String? board_number, + String? cadre, + String? gender, + String? facility_code, + int? user_id, + String? salutation}); +} + +/// @nodoc +class _$ProviderRegistryCopyWithImpl<$Res, $Val extends ProviderRegistry> + implements $ProviderRegistryCopyWith<$Res> { + _$ProviderRegistryCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? family_name = freezed, + Object? given_name = freezed, + Object? national_id = freezed, + Object? licence_number = freezed, + Object? board_number = freezed, + Object? cadre = freezed, + Object? gender = freezed, + Object? facility_code = freezed, + Object? user_id = freezed, + Object? salutation = freezed, + }) { + return _then(_value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, + family_name: freezed == family_name + ? _value.family_name + : family_name // ignore: cast_nullable_to_non_nullable + as String?, + given_name: freezed == given_name + ? _value.given_name + : given_name // ignore: cast_nullable_to_non_nullable + as String?, + national_id: freezed == national_id + ? _value.national_id + : national_id // ignore: cast_nullable_to_non_nullable + as int?, + licence_number: freezed == licence_number + ? _value.licence_number + : licence_number // ignore: cast_nullable_to_non_nullable + as String?, + board_number: freezed == board_number + ? _value.board_number + : board_number // ignore: cast_nullable_to_non_nullable + as String?, + cadre: freezed == cadre + ? _value.cadre + : cadre // ignore: cast_nullable_to_non_nullable + as String?, + gender: freezed == gender + ? _value.gender + : gender // ignore: cast_nullable_to_non_nullable + as String?, + facility_code: freezed == facility_code + ? _value.facility_code + : facility_code // ignore: cast_nullable_to_non_nullable + as String?, + user_id: freezed == user_id + ? _value.user_id + : user_id // ignore: cast_nullable_to_non_nullable + as int?, + salutation: freezed == salutation + ? _value.salutation + : salutation // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ProviderRegistryImplCopyWith<$Res> + implements $ProviderRegistryCopyWith<$Res> { + factory _$$ProviderRegistryImplCopyWith(_$ProviderRegistryImpl value, + $Res Function(_$ProviderRegistryImpl) then) = + __$$ProviderRegistryImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {int? id, + String? family_name, + String? given_name, + int? national_id, + String? licence_number, + String? board_number, + String? cadre, + String? gender, + String? facility_code, + int? user_id, + String? salutation}); +} + +/// @nodoc +class __$$ProviderRegistryImplCopyWithImpl<$Res> + extends _$ProviderRegistryCopyWithImpl<$Res, _$ProviderRegistryImpl> + implements _$$ProviderRegistryImplCopyWith<$Res> { + __$$ProviderRegistryImplCopyWithImpl(_$ProviderRegistryImpl _value, + $Res Function(_$ProviderRegistryImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? family_name = freezed, + Object? given_name = freezed, + Object? national_id = freezed, + Object? licence_number = freezed, + Object? board_number = freezed, + Object? cadre = freezed, + Object? gender = freezed, + Object? facility_code = freezed, + Object? user_id = freezed, + Object? salutation = freezed, + }) { + return _then(_$ProviderRegistryImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, + family_name: freezed == family_name + ? _value.family_name + : family_name // ignore: cast_nullable_to_non_nullable + as String?, + given_name: freezed == given_name + ? _value.given_name + : given_name // ignore: cast_nullable_to_non_nullable + as String?, + national_id: freezed == national_id + ? _value.national_id + : national_id // ignore: cast_nullable_to_non_nullable + as int?, + licence_number: freezed == licence_number + ? _value.licence_number + : licence_number // ignore: cast_nullable_to_non_nullable + as String?, + board_number: freezed == board_number + ? _value.board_number + : board_number // ignore: cast_nullable_to_non_nullable + as String?, + cadre: freezed == cadre + ? _value.cadre + : cadre // ignore: cast_nullable_to_non_nullable + as String?, + gender: freezed == gender + ? _value.gender + : gender // ignore: cast_nullable_to_non_nullable + as String?, + facility_code: freezed == facility_code + ? _value.facility_code + : facility_code // ignore: cast_nullable_to_non_nullable + as String?, + user_id: freezed == user_id + ? _value.user_id + : user_id // ignore: cast_nullable_to_non_nullable + as int?, + salutation: freezed == salutation + ? _value.salutation + : salutation // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProviderRegistryImpl implements _ProviderRegistry { + const _$ProviderRegistryImpl( + {this.id, + this.family_name, + this.given_name, + this.national_id, + this.licence_number, + this.board_number, + this.cadre, + this.gender, + this.facility_code, + this.user_id, + this.salutation}); + + factory _$ProviderRegistryImpl.fromJson(Map json) => + _$$ProviderRegistryImplFromJson(json); + + @override + final int? id; + @override + final String? family_name; + @override + final String? given_name; + @override + final int? national_id; + @override + final String? licence_number; + @override + final String? board_number; + @override + final String? cadre; + @override + final String? gender; + @override + final String? facility_code; + @override + final int? user_id; + @override + final String? salutation; + + @override + String toString() { + return 'ProviderRegistry(id: $id, family_name: $family_name, given_name: $given_name, national_id: $national_id, licence_number: $licence_number, board_number: $board_number, cadre: $cadre, gender: $gender, facility_code: $facility_code, user_id: $user_id, salutation: $salutation)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProviderRegistryImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.family_name, family_name) || + other.family_name == family_name) && + (identical(other.given_name, given_name) || + other.given_name == given_name) && + (identical(other.national_id, national_id) || + other.national_id == national_id) && + (identical(other.licence_number, licence_number) || + other.licence_number == licence_number) && + (identical(other.board_number, board_number) || + other.board_number == board_number) && + (identical(other.cadre, cadre) || other.cadre == cadre) && + (identical(other.gender, gender) || other.gender == gender) && + (identical(other.facility_code, facility_code) || + other.facility_code == facility_code) && + (identical(other.user_id, user_id) || other.user_id == user_id) && + (identical(other.salutation, salutation) || + other.salutation == salutation)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + id, + family_name, + given_name, + national_id, + licence_number, + board_number, + cadre, + gender, + facility_code, + user_id, + salutation); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ProviderRegistryImplCopyWith<_$ProviderRegistryImpl> get copyWith => + __$$ProviderRegistryImplCopyWithImpl<_$ProviderRegistryImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$ProviderRegistryImplToJson( + this, + ); + } +} + +abstract class _ProviderRegistry implements ProviderRegistry { + const factory _ProviderRegistry( + {final int? id, + final String? family_name, + final String? given_name, + final int? national_id, + final String? licence_number, + final String? board_number, + final String? cadre, + final String? gender, + final String? facility_code, + final int? user_id, + final String? salutation}) = _$ProviderRegistryImpl; + + factory _ProviderRegistry.fromJson(Map json) = + _$ProviderRegistryImpl.fromJson; + + @override + int? get id; + @override + String? get family_name; + @override + String? get given_name; + @override + int? get national_id; + @override + String? get licence_number; + @override + String? get board_number; + @override + String? get cadre; + @override + String? get gender; + @override + String? get facility_code; + @override + int? get user_id; + @override + String? get salutation; + @override + @JsonKey(ignore: true) + _$$ProviderRegistryImplCopyWith<_$ProviderRegistryImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/provider/provider_registry/data/models/provider_registry.g.dart b/lib/src/features/provider/provider_registry/data/models/provider_registry.g.dart new file mode 100644 index 00000000..e86c7f69 --- /dev/null +++ b/lib/src/features/provider/provider_registry/data/models/provider_registry.g.dart @@ -0,0 +1,39 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'provider_registry.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ProviderRegistryImpl _$$ProviderRegistryImplFromJson( + Map json) => + _$ProviderRegistryImpl( + id: (json['id'] as num?)?.toInt(), + family_name: json['family_name'] as String?, + given_name: json['given_name'] as String?, + national_id: (json['national_id'] as num?)?.toInt(), + licence_number: json['licence_number'] as String?, + board_number: json['board_number'] as String?, + cadre: json['cadre'] as String?, + gender: json['gender'] as String?, + facility_code: json['facility_code'] as String?, + user_id: (json['user_id'] as num?)?.toInt(), + salutation: json['salutation'] as String?, + ); + +Map _$$ProviderRegistryImplToJson( + _$ProviderRegistryImpl instance) => + { + 'id': instance.id, + 'family_name': instance.family_name, + 'given_name': instance.given_name, + 'national_id': instance.national_id, + 'licence_number': instance.licence_number, + 'board_number': instance.board_number, + 'cadre': instance.cadre, + 'gender': instance.gender, + 'facility_code': instance.facility_code, + 'user_id': instance.user_id, + 'salutation': instance.salutation, + }; diff --git a/lib/src/features/provider/provider_registry/data/providers/provider_registry_provider.dart b/lib/src/features/provider/provider_registry/data/providers/provider_registry_provider.dart new file mode 100644 index 00000000..c9cb1f98 --- /dev/null +++ b/lib/src/features/provider/provider_registry/data/providers/provider_registry_provider.dart @@ -0,0 +1,14 @@ + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:nishauri/src/features/provider/provider_registry/data/models/provider_registry.dart'; +import 'package:nishauri/src/features/provider/provider_registry/data/repositories/provider_registry_repository.dart'; +import 'package:nishauri/src/features/provider/provider_registry/data/services/provider_registry_service.dart'; + +final providerRegistryRepositoryProvider = Provider((ref) { + return ProviderRegistryRepository(ProviderRegistryService()); +}); + +final getProviderDetailsProvider = FutureProvider((ref) async { + final repository = ref.watch(providerRegistryRepositoryProvider); + return await repository.getProviderDetails(); +}); \ No newline at end of file diff --git a/lib/src/features/provider/provider_registry/data/repositories/provider_registry_repository.dart b/lib/src/features/provider/provider_registry/data/repositories/provider_registry_repository.dart new file mode 100644 index 00000000..dd4ea5a7 --- /dev/null +++ b/lib/src/features/provider/provider_registry/data/repositories/provider_registry_repository.dart @@ -0,0 +1,17 @@ + +import 'package:nishauri/src/features/provider/provider_registry/data/models/provider_registry.dart'; +import 'package:nishauri/src/features/provider/provider_registry/data/services/provider_registry_service.dart'; + +class ProviderRegistryRepository { + final ProviderRegistryService _service; + + ProviderRegistryRepository(this._service); + + Future searchProviderRegistry (Map data) async { + return await _service.searchProviderRegistry(data); + } + + Future getProviderDetails() async { + return await _service.getProviderDetails(); + } +} diff --git a/lib/src/features/provider/provider_registry/data/services/provider_registry_service.dart b/lib/src/features/provider/provider_registry/data/services/provider_registry_service.dart new file mode 100644 index 00000000..0f1933fa --- /dev/null +++ b/lib/src/features/provider/provider_registry/data/services/provider_registry_service.dart @@ -0,0 +1,89 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:http/http.dart'; +import 'package:nishauri/src/features/auth/data/respositories/auth_repository.dart'; +import 'package:nishauri/src/features/auth/data/services/AuthApiService.dart'; +import 'package:nishauri/src/features/provider/provider_registry/data/models/provider_registry.dart'; +import 'package:nishauri/src/shared/interfaces/HTTPService.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/helpers.dart'; + +class ProviderRegistryService extends HTTPService { + + final AuthRepository _repository = AuthRepository(AuthApiService()); + + Future searchProviderRegistry(Map data) async { + final response = await call (searchProviderRegistry_, data); + + if (response.statusCode == 200) { + final responseString = await response.stream.bytesToString(); + final Map responseData = json.decode(responseString); + + if (responseData["success"] == true) { + final Map provider = responseData["data"]["provider"]; + return ProviderRegistry.fromJson(provider); + } + else { + throw responseData["message"]; + } + } + else { + throw "Something went wrong contact Admin"; + } + } + + Future searchProviderRegistry_( + Map data) async { + final id = await _repository.getUserId(); + var user = {'user_id': id}; + var mergedData = {...data, ...user}; + final tokenPair = await getCachedToken(); + var headers = { + 'Authorization': "Bearer ${tokenPair.accessToken}", + 'Content-Type': 'application/json', + }; + var url = '${Constants.BASE_URL_NEW}practitioner'; + final response = await request(url: url, token: tokenPair, method: 'POST', requestHeaders: headers, data: mergedData, userId: id); + return response; + } + + Future getProviderDetails() async { + final data = await loadJsonData("assets/data/provider.json"); + final json = jsonDecode(data); + + final Map providerJson = json["provider"]; + final provider = ProviderRegistry.fromJson(providerJson); + log("$provider"); + return provider; + // final response = await call (getProviderDetails_, null); + // + // if (response.statusCode == 200) { + // final responseString = await response.stream.bytesToString(); + // final Map responseData = json.decode(responseString); + // + // if (responseData["success"] == true) { + // final Map provider = responseData["data"]["provider"]; + // return ProviderRegistry.fromJson(provider); + // } + // else { + // throw responseData["message"]; + // } + // } + // else { + // throw "Something went wrong contact Admin"; + // } + } + + // Future getProviderDetails_(dynamic args) async { + // final id = await _repository.getUserId(); + // final tokenPair = await getCachedToken(); + // var headers = { + // 'Authorization': "Bearer ${tokenPair.accessToken}", + // 'Content-Type': 'application/json', + // }; + // var url = '${Constants.BASE_URL_NEW}practitioner/user_id'; + // final response = await request(url: url, token: tokenPair, method: 'GET', requestHeaders: headers, userId: id); + // return response; + // } +} \ No newline at end of file diff --git a/lib/src/features/provider/provider_registry/presentaion/forms/forms.dart b/lib/src/features/provider/provider_registry/presentaion/forms/forms.dart new file mode 100644 index 00000000..da5dccdb --- /dev/null +++ b/lib/src/features/provider/provider_registry/presentaion/forms/forms.dart @@ -0,0 +1,3 @@ +export 'provider_registry.dart'; +export 'location.dart'; +export 'review_submit.dart'; \ No newline at end of file diff --git a/lib/src/features/provider/provider_registry/presentaion/forms/location.dart b/lib/src/features/provider/provider_registry/presentaion/forms/location.dart new file mode 100644 index 00000000..69c11c93 --- /dev/null +++ b/lib/src/features/provider/provider_registry/presentaion/forms/location.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:nishauri/src/features/user/data/providers/user_provider.dart'; +import 'package:nishauri/src/shared/styles/input_styles.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class LocationInformation extends StatefulWidget { + const LocationInformation({super.key}); + + @override + _LocationInformationState createState() => _LocationInformationState(); +} + +class _LocationInformationState extends State { + String? selectedCounty; + String? selectedSubCounty; + String? facility; + + @override + Widget build(BuildContext context) { + final counties = [ + const DropdownMenuItem(value: "NAI", child: Text("Nairobi")), + const DropdownMenuItem(value: "MSA", child: Text("Mombasa")), + const DropdownMenuItem(value: "NAK", child: Text("Nakuru")), + const DropdownMenuItem(value: "KIS", child: Text("Kisumu")), + ]; + + final subCounties = [ + const DropdownMenuItem(value: "RUA", child: Text("Ruaraka")), + const DropdownMenuItem(value: "ROY", child: Text("Roysambu")), + const DropdownMenuItem(value: "KAS", child: Text("Kasarani")), + const DropdownMenuItem(value: "LAN", child: Text("Langata")), + ]; + + final facilities = [ + const DropdownMenuItem(value: "23855", child: Text("Huruma Hospital - Mathare North Annex")), + const DropdownMenuItem(value: "29146", child: Text("Seaton Park Medical Centre")), + const DropdownMenuItem(value: "29283", child: Text("Provide Medical Centre")), + const DropdownMenuItem(value: "13246", child: Text("St. Scholastica Uzima Hospital")), + ]; + + return Consumer( + builder: (context, ref, child) { + final asyncUser = ref.watch(userProvider); + return asyncUser.when( + data: (user) => Column( + children: [ + const SizedBox(height: Constants.BUTTON_FONT_SIZE), + FormBuilderDropdown( + initialValue: selectedCounty, + name: "county", + items: counties, + onChanged: (String? newValue) { + setState(() { + selectedCounty = newValue; + selectedSubCounty = null; // Reset sub-county and facility + facility = null; + }); + }, + decoration: inputDecoration( + prefixIcon: Icons.map_sharp, + label: "County", + ), + ), + const SizedBox(height: Constants.BUTTON_FONT_SIZE), + if (selectedCounty != null) ...[ + FormBuilderDropdown( + initialValue: selectedSubCounty, + name: "sub_county", + items: subCounties, + onChanged: (String? newValue) { + setState(() { + selectedSubCounty = newValue; + facility = null; // Reset facility + }); + }, + decoration: inputDecoration( + prefixIcon: Icons.map_sharp, + label: "Sub County", + ), + ), + ], + const SizedBox(height: Constants.BUTTON_FONT_SIZE), + if (selectedSubCounty != null) ...[ + FormBuilderDropdown( + initialValue: facility, + name: "facility_mfl", + items: facilities, + onChanged: (String? newValue) { + setState(() { + facility = newValue; + }); + }, + decoration: inputDecoration( + prefixIcon: Icons.local_hospital_outlined, + label: "Facility", + ), + ), + ], + ], + ), + error: (error, _) => Center(child: Text(error.toString())), + loading: () => const Center( + child: CircularProgressIndicator(), + ), + ); + }, + ); + } +} diff --git a/lib/src/features/provider/provider_registry/presentaion/forms/provider_registry.dart b/lib/src/features/provider/provider_registry/presentaion/forms/provider_registry.dart new file mode 100644 index 00000000..44915902 --- /dev/null +++ b/lib/src/features/provider/provider_registry/presentaion/forms/provider_registry.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nishauri/src/features/provider/provider_registry/data/providers/provider_registry_provider.dart'; +import 'package:nishauri/src/hooks/use_local_avatar.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class ProviderRegistry extends HookConsumerWidget { + const ProviderRegistry({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final avatar = useLocalAvatar("images/avatar.jpg"); + return Consumer( + builder: (context, ref, child) { + final asyncProvider = ref.watch(getProviderDetailsProvider); + return asyncProvider.when( + data: (provider) => Container( + child: Wrap ( children: [ + const Divider(), + ListTile( + leading: const Icon(Icons.perm_identity, color: Constants.providerBgColor,), + title: const Text("Name"), + subtitle: Text('${provider.salutation} ${provider.family_name} ${provider.given_name}'), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.perm_identity, color: Constants.providerBgColor), + title: const Text("Gender"), + subtitle: Text(provider.gender??''), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.card_membership, color: Constants.providerBgColor), + title: const Text("National Id"), + subtitle: Text(provider.national_id.toString()), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.card_membership, color: Constants.providerBgColor), + title: const Text("Licence Number"), + subtitle: Text(provider.licence_number??''), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.card_membership, color: Constants.providerBgColor), + title: const Text("Board Number"), + subtitle: Text(provider.board_number??''), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.card_membership, color: Constants.providerBgColor), + title: const Text("Cadre"), + subtitle: Text(provider.cadre??''), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.local_hospital_outlined, color: Constants.providerBgColor), + title: const Text("Facility"), + subtitle: Text(provider.facility_code??''), + ), + const Divider(), + ], + ), + ), + error: (error, _) => Center(child: Text(error.toString())), + loading: () => const Center( + child: CircularProgressIndicator(),), + ); + }, + ); +} +} \ No newline at end of file diff --git a/lib/src/features/provider/provider_registry/presentaion/forms/review_submit.dart b/lib/src/features/provider/provider_registry/presentaion/forms/review_submit.dart new file mode 100644 index 00000000..f36bbad8 --- /dev/null +++ b/lib/src/features/provider/provider_registry/presentaion/forms/review_submit.dart @@ -0,0 +1,35 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:nishauri/src/shared/display/AppAvatar.dart'; + +class ReviewAndSubmitLocation extends StatelessWidget { + final Map formState; + + const ReviewAndSubmitLocation({Key? key, required this.formState}) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Container( + child: Wrap(children: [ + const Divider(), + + ListTile( + leading: AppAvatar(alt: Icon(Icons.person), image: formState["image"]), + title: const Text("Full Name"), + subtitle: Text("${formState['salutation'] ?? "None"} ${formState['family_name'] ?? "None"} ${formState['given_name'] ?? "None"}"), + ), + const Divider(), + + ListTile( + title: const Text("Facility"), + subtitle: Text("${formState['facility_mfl'] ?? "None"}"), + ), + const Divider(), + ]), + ); + } +} diff --git a/lib/src/features/provider/provider_registry/presentaion/pages/location_selection_screen.dart b/lib/src/features/provider/provider_registry/presentaion/pages/location_selection_screen.dart new file mode 100644 index 00000000..3ddecd83 --- /dev/null +++ b/lib/src/features/provider/provider_registry/presentaion/pages/location_selection_screen.dart @@ -0,0 +1,229 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nishauri/src/features/provider/provider_registry/data/providers/provider_registry_provider.dart'; +import 'package:nishauri/src/features/provider/provider_registry/presentaion/forms/forms.dart'; +import 'package:nishauri/src/shared/display/AppCard.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/input/Button.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class LocationSelectionScreen extends HookConsumerWidget { + + @override + Widget build(BuildContext context, WidgetRef ref) { + final formKey = useMemoized(() => GlobalKey()); + final theme = Theme.of(context); + final currentStep = useState(0); + final loading = useState(false); + final usrProvider = ref.watch(getProviderDetailsProvider); + + final providerName = usrProvider.when(data: (data) => data.family_name, error: (error, stack) => false, + loading: () => false); + final salutation = usrProvider.when(data: (data) => data.salutation, error: (error, stack) => false, + loading: () => false); + + List steps = [ + Step( + title: const Text("Provider Information"), + subtitle: const Text("Confirm your Registration details"), + content: const ProviderRegistry(), + isActive: currentStep.value == 0, + ), + Step( + title: const Text("Provider Information"), + subtitle: const Text("Confirm your Registration details"), + content: const LocationInformation(), + isActive: currentStep.value == 1, + ), + Step( + title: const Text("Review and Submit"), + content: AppCard( + child: Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + children: [ + SvgPicture.asset( + "assets/images/review.svg", + semanticsLabel: "Security", + fit: BoxFit.contain, + height: 150, + ), + const SizedBox(height: Constants.SPACING), + const Text( + "Thank you for selecting your service facility!", + ), + const SizedBox(height: Constants.SPACING), + const Text( + "Review your information for accuracy before submission.") + ], + ), + ), + ), + isActive: currentStep.value == 2, + ), + ]; + + final stepFieldsToValidate = [ + ["family_name"], + ["facility_mfl"], + ]; + + void handleSubmit() { + // Implement form submission logic here + } + + return WillPopScope( + onWillPop: () async { + return false; + }, + child: Scaffold( + body: SingleChildScrollView( // Wrap in SingleChildScrollView + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const CustomAppBar( + title: "Provider Profile Update 📋", + color: Constants.providerBgColor, + ), + const SizedBox(height: 10), + Text("Welcome ${salutation} ${providerName}", style: theme.textTheme.titleMedium), + const SizedBox(height: 20), + Text("Confirm your details and update your current location/facility.", style: theme.textTheme.bodyLarge), + const SizedBox(height: 20), + FormBuilder( + key: formKey, + child: Stepper( + connectorColor: MaterialStateProperty.resolveWith((states) { + // Return a color based on the states, or null for default + if (states.contains(MaterialState.selected)) { + return Constants.providerBgColor; // Color when selected + } + return Colors.grey; // Default color + }), + // connectorColor: MaterialStateProperty.all(Constants.providerBgColor), + currentStep: currentStep.value, + onStepCancel: () { + if (currentStep.value > 0) { + currentStep.value -= 1; + } + }, + onStepContinue: () { + bool isLastStep = (currentStep.value == steps.length - 1); + + // Validate fields + if (!isLastStep) { + final currentStepFields = stepFieldsToValidate[currentStep.value]; + if (currentStepFields.any((field) => !formKey.currentState!.fields[field]!.validate())) { + return; + } + } + + if (isLastStep) { + // Submit form + handleSubmit(); + } else { + currentStep.value += 1; + } + }, + onStepTapped: (step) { + currentStep.value = step; + }, + steps: steps, + controlsBuilder: (context, details) { + return Row( + children: [ + Expanded( + child: Builder(builder: (context) { + bool isLastStep = (currentStep.value == steps.length - 1); + if (isLastStep) { + return Button( + onPress: () async { + final results = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text("Confirm Location Details Entered"), + content: SizedBox( + width: double.maxFinite, + height: MediaQuery.of(context).size.height * 0.5, + child: SingleChildScrollView( + child: ReviewAndSubmitLocation( + formState: formKey.currentState!.instantValue, + ), + ), + ), + actions: [ + Row( + children: [ + Expanded( + child: Button( + title: "Submit", + onPress: () { + context.pop(1); + }, + ), + ), + const SizedBox(width: 8), + Expanded( + child: Button( + title: "Cancel", + onPress: context.pop, + titleStyle: theme.textTheme.titleLarge?.copyWith( + color: Constants.providerBgColor, + ), + ), + ), + ], + ), + ], + ), + ); + if (results == 1) { + details.onStepContinue!(); + } + }, + title: 'Review', + loading: loading.value, + titleStyle: theme.textTheme.titleSmall?.copyWith( + color: Constants.providerBgColor, + fontWeight: FontWeight.bold, + ), + ); + } + return Button( + onPress: details.onStepContinue, + title: 'Next', + disabled: loading.value, + titleStyle: theme.textTheme.titleSmall?.copyWith( + color: Constants.providerBgColor, + fontWeight: FontWeight.bold, + ), + ); + }), + ), + const SizedBox(width: Constants.SPACING), + Expanded( + child: Button( + onPress: details.onStepCancel, + title: 'Cancel', + disabled: loading.value, + titleStyle: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.bold), + ), + ), + const SizedBox(width: Constants.SPACING), + const Expanded(child: SizedBox()), // Placeholder for alignment + ], + ); + }, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/features/provider/provider_registry/presentaion/pages/provider_card.dart b/lib/src/features/provider/provider_registry/presentaion/pages/provider_card.dart new file mode 100644 index 00000000..24999235 --- /dev/null +++ b/lib/src/features/provider/provider_registry/presentaion/pages/provider_card.dart @@ -0,0 +1,97 @@ +import 'dart:html'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:intl/intl.dart'; +import 'package:nishauri/src/shared/display/AppCard.dart'; +import 'package:nishauri/src/shared/extensions/extensions.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/helpers.dart'; + +import '../../../../../shared/display/ProfileCard.dart'; +import '../../data/providers/provider_registry_provider.dart'; + +class ProviderCard extends StatelessWidget { + final String rescheduleButtonText; + final String appointmentType; + final DateTime appointmentTime; + final String providerImage; + final String providerName; + //final VoidCallback? onRescheduleTap; + + const ProviderCard({ + Key? key, + required this.rescheduleButtonText, + required this.appointmentType, + required this.appointmentTime, + required this.providerImage, + required this.providerName, + // this.onRescheduleTap, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.all(Constants.SPACING), + clipBehavior: Clip.antiAlias, + child: Column( + children: [ + // Provider Details Section (with builder logic) + HookConsumer( + builder: (context, ref, child) { + final userAsync = ref.watch(getProviderDetailsProvider); + return userAsync.when( + data: (provider) => ProfileCard( + height: MediaQuery.of(context).size.height * 0.2, + color: Constants.providerBgColor, + header: Text( + ("${provider.salutation ?? " "} ${provider.family_name ?? " "} ${provider.given_name ?? " "}").titleCase, + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Colors.white), + ), + ), + loading: () => const Center( + child: CircularProgressIndicator(), + ), + error: (error, stack) => Text("Error: $error"), + ); + }, + ), + const Divider(), + // Appointment Details Section + ListTile( + leading: Image.network( + providerImage, + width: 50, + height: 50, + fit: BoxFit.cover, + ), + title: Text( + appointmentType, + style: Theme.of(context).textTheme.titleMedium, + ), + subtitle: Text( + "Time: ${appointmentTime.toLocal()}".split('.')[0], + style: Theme.of(context).textTheme.bodySmall, + ), + ), + const Divider(), + // Reschedule Button Section + Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: ElevatedButton( + onPressed: (){}, + // style: ElevatedButton.styleFrom( + // backgroundColor: Constants.primaryColor, + // ), + child: Text( + rescheduleButtonText, + style: Theme.of(context).textTheme.labelLarge?.copyWith(color: Colors.white), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/features/provider/provider_registry/presentaion/pages/provider_details.dart b/lib/src/features/provider/provider_registry/presentaion/pages/provider_details.dart new file mode 100644 index 00000000..b2b80b8a --- /dev/null +++ b/lib/src/features/provider/provider_registry/presentaion/pages/provider_details.dart @@ -0,0 +1,94 @@ +import 'dart:developer'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:nishauri/src/features/provider/provider_registry/data/providers/provider_registry_provider.dart'; +import 'package:nishauri/src/hooks/use_local_avatar.dart'; +import 'package:nishauri/src/shared/display/ProfileCard.dart'; +import 'package:nishauri/src/shared/extensions/extensions.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class ProviderDetails extends HookWidget { + const ProviderDetails({super.key}); + + @override + Widget build(BuildContext context) { + final avatar = useLocalAvatar("images/avatar.jpg"); + log("*****************${avatar.toString()}*********************"); + final cardColor = Theme.of(context).colorScheme.onPrimary; + return Scaffold( + appBar: AppBar( + leading: IconButton( + onPressed: () => context.pop(), + icon: const Icon(Icons.chevron_left), + ), + title: const Text('Provider Details'), + ), + body: Consumer( + builder: (context, ref, child) { + final userAsync = ref.watch(getProviderDetailsProvider); + return userAsync.when( + data: (provider) => ProfileCard( + height: MediaQuery.of(context).size.height, + color: Constants.labResultsColor, + header: Text(("${provider.salutation ?? " "} ${provider.family_name ?? " "} ${provider.given_name ?? " "}").titleCase), + image: avatar, + // user.image, + icon: Icons.person, + buildItem: (context, item) => item, + items: [ + ListTile( + leading: const Icon(Icons.perm_identity, color: Constants.providerBgColor,), + title: const Text("Name"), + subtitle: Text('${provider.salutation} ${provider.family_name} ${provider.given_name}'), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.perm_identity, color: Constants.providerBgColor), + title: const Text("Gender"), + subtitle: Text(provider.gender??''), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.card_membership, color: Constants.providerBgColor), + title: const Text("National Id"), + subtitle: Text(provider.national_id.toString()), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.card_membership, color: Constants.providerBgColor), + title: const Text("Licence Number"), + subtitle: Text(provider.licence_number??''), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.card_membership, color: Constants.providerBgColor), + title: const Text("Board Number"), + subtitle: Text(provider.board_number??''), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.card_membership, color: Constants.providerBgColor), + title: const Text("Cadre"), + subtitle: Text(provider.cadre??''), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.local_hospital_outlined, color: Constants.providerBgColor), + title: const Text("Facility"), + subtitle: Text(provider.facility_code??''), + ), + const Divider(), + ], + ), + error: (error, _) => Center(child: Text(error.toString())), + loading: () => const Center( + child: CircularProgressIndicator(), + ), + ); + }, + ), + ); + } +} diff --git a/lib/src/features/blood_sugar/data/models/blood_sugar.dart b/lib/src/features/self_screening/blood_sugar/data/models/blood_sugar.dart similarity index 100% rename from lib/src/features/blood_sugar/data/models/blood_sugar.dart rename to lib/src/features/self_screening/blood_sugar/data/models/blood_sugar.dart diff --git a/lib/src/features/blood_sugar/data/models/blood_sugar.freezed.dart b/lib/src/features/self_screening/blood_sugar/data/models/blood_sugar.freezed.dart similarity index 100% rename from lib/src/features/blood_sugar/data/models/blood_sugar.freezed.dart rename to lib/src/features/self_screening/blood_sugar/data/models/blood_sugar.freezed.dart diff --git a/lib/src/features/blood_sugar/data/models/blood_sugar.g.dart b/lib/src/features/self_screening/blood_sugar/data/models/blood_sugar.g.dart similarity index 100% rename from lib/src/features/blood_sugar/data/models/blood_sugar.g.dart rename to lib/src/features/self_screening/blood_sugar/data/models/blood_sugar.g.dart diff --git a/lib/src/features/self_screening/blood_sugar/data/models/bs_advice.dart b/lib/src/features/self_screening/blood_sugar/data/models/bs_advice.dart new file mode 100644 index 00000000..ffbfda9a --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/models/bs_advice.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'bs_advice.freezed.dart'; +part 'bs_advice.g.dart'; + +@Freezed() +class BsAdvice with _$BsAdvice { + const factory BsAdvice({ + String? label, + String? description, + String? advice, + + }) = _BsAdvice; + factory BsAdvice.fromJson(Map json)=> _$BsAdviceFromJson(json); +} \ No newline at end of file diff --git a/lib/src/features/self_screening/blood_sugar/data/models/bs_advice.freezed.dart b/lib/src/features/self_screening/blood_sugar/data/models/bs_advice.freezed.dart new file mode 100644 index 00000000..2d3a835e --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/models/bs_advice.freezed.dart @@ -0,0 +1,186 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'bs_advice.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +BsAdvice _$BsAdviceFromJson(Map json) { + return _BsAdvice.fromJson(json); +} + +/// @nodoc +mixin _$BsAdvice { + String? get label => throw _privateConstructorUsedError; + String? get description => throw _privateConstructorUsedError; + String? get advice => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $BsAdviceCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BsAdviceCopyWith<$Res> { + factory $BsAdviceCopyWith(BsAdvice value, $Res Function(BsAdvice) then) = + _$BsAdviceCopyWithImpl<$Res, BsAdvice>; + @useResult + $Res call({String? label, String? description, String? advice}); +} + +/// @nodoc +class _$BsAdviceCopyWithImpl<$Res, $Val extends BsAdvice> + implements $BsAdviceCopyWith<$Res> { + _$BsAdviceCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? label = freezed, + Object? description = freezed, + Object? advice = freezed, + }) { + return _then(_value.copyWith( + label: freezed == label + ? _value.label + : label // ignore: cast_nullable_to_non_nullable + as String?, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + advice: freezed == advice + ? _value.advice + : advice // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$BsAdviceImplCopyWith<$Res> + implements $BsAdviceCopyWith<$Res> { + factory _$$BsAdviceImplCopyWith( + _$BsAdviceImpl value, $Res Function(_$BsAdviceImpl) then) = + __$$BsAdviceImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String? label, String? description, String? advice}); +} + +/// @nodoc +class __$$BsAdviceImplCopyWithImpl<$Res> + extends _$BsAdviceCopyWithImpl<$Res, _$BsAdviceImpl> + implements _$$BsAdviceImplCopyWith<$Res> { + __$$BsAdviceImplCopyWithImpl( + _$BsAdviceImpl _value, $Res Function(_$BsAdviceImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? label = freezed, + Object? description = freezed, + Object? advice = freezed, + }) { + return _then(_$BsAdviceImpl( + label: freezed == label + ? _value.label + : label // ignore: cast_nullable_to_non_nullable + as String?, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + advice: freezed == advice + ? _value.advice + : advice // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$BsAdviceImpl implements _BsAdvice { + const _$BsAdviceImpl({this.label, this.description, this.advice}); + + factory _$BsAdviceImpl.fromJson(Map json) => + _$$BsAdviceImplFromJson(json); + + @override + final String? label; + @override + final String? description; + @override + final String? advice; + + @override + String toString() { + return 'BsAdvice(label: $label, description: $description, advice: $advice)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BsAdviceImpl && + (identical(other.label, label) || other.label == label) && + (identical(other.description, description) || + other.description == description) && + (identical(other.advice, advice) || other.advice == advice)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, label, description, advice); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$BsAdviceImplCopyWith<_$BsAdviceImpl> get copyWith => + __$$BsAdviceImplCopyWithImpl<_$BsAdviceImpl>(this, _$identity); + + @override + Map toJson() { + return _$$BsAdviceImplToJson( + this, + ); + } +} + +abstract class _BsAdvice implements BsAdvice { + const factory _BsAdvice( + {final String? label, + final String? description, + final String? advice}) = _$BsAdviceImpl; + + factory _BsAdvice.fromJson(Map json) = + _$BsAdviceImpl.fromJson; + + @override + String? get label; + @override + String? get description; + @override + String? get advice; + @override + @JsonKey(ignore: true) + _$$BsAdviceImplCopyWith<_$BsAdviceImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/self_screening/blood_sugar/data/models/bs_advice.g.dart b/lib/src/features/self_screening/blood_sugar/data/models/bs_advice.g.dart new file mode 100644 index 00000000..bd015b2a --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/models/bs_advice.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'bs_advice.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$BsAdviceImpl _$$BsAdviceImplFromJson(Map json) => + _$BsAdviceImpl( + label: json['label'] as String?, + description: json['description'] as String?, + advice: json['advice'] as String?, + ); + +Map _$$BsAdviceImplToJson(_$BsAdviceImpl instance) => + { + 'label': instance.label, + 'description': instance.description, + 'advice': instance.advice, + }; diff --git a/lib/src/features/self_screening/blood_sugar/data/models/bs_hours.dart b/lib/src/features/self_screening/blood_sugar/data/models/bs_hours.dart new file mode 100644 index 00000000..002bafb3 --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/models/bs_hours.dart @@ -0,0 +1,17 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'bs_hours.freezed.dart'; +part 'bs_hours.g.dart'; + +@Freezed() +class BsHours with _$BsHours { + + const factory BsHours({ + String? time, + double? level, + String? condition, + }) = _BsHours; + + factory BsHours.fromJson(Map json) + => _$BsHoursFromJson(json); +} \ No newline at end of file diff --git a/lib/src/features/self_screening/blood_sugar/data/models/bs_hours.freezed.dart b/lib/src/features/self_screening/blood_sugar/data/models/bs_hours.freezed.dart new file mode 100644 index 00000000..80f93e84 --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/models/bs_hours.freezed.dart @@ -0,0 +1,183 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'bs_hours.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +BsHours _$BsHoursFromJson(Map json) { + return _BsHours.fromJson(json); +} + +/// @nodoc +mixin _$BsHours { + String? get time => throw _privateConstructorUsedError; + double? get level => throw _privateConstructorUsedError; + String? get condition => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $BsHoursCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BsHoursCopyWith<$Res> { + factory $BsHoursCopyWith(BsHours value, $Res Function(BsHours) then) = + _$BsHoursCopyWithImpl<$Res, BsHours>; + @useResult + $Res call({String? time, double? level, String? condition}); +} + +/// @nodoc +class _$BsHoursCopyWithImpl<$Res, $Val extends BsHours> + implements $BsHoursCopyWith<$Res> { + _$BsHoursCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? time = freezed, + Object? level = freezed, + Object? condition = freezed, + }) { + return _then(_value.copyWith( + time: freezed == time + ? _value.time + : time // ignore: cast_nullable_to_non_nullable + as String?, + level: freezed == level + ? _value.level + : level // ignore: cast_nullable_to_non_nullable + as double?, + condition: freezed == condition + ? _value.condition + : condition // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$BsHoursImplCopyWith<$Res> implements $BsHoursCopyWith<$Res> { + factory _$$BsHoursImplCopyWith( + _$BsHoursImpl value, $Res Function(_$BsHoursImpl) then) = + __$$BsHoursImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String? time, double? level, String? condition}); +} + +/// @nodoc +class __$$BsHoursImplCopyWithImpl<$Res> + extends _$BsHoursCopyWithImpl<$Res, _$BsHoursImpl> + implements _$$BsHoursImplCopyWith<$Res> { + __$$BsHoursImplCopyWithImpl( + _$BsHoursImpl _value, $Res Function(_$BsHoursImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? time = freezed, + Object? level = freezed, + Object? condition = freezed, + }) { + return _then(_$BsHoursImpl( + time: freezed == time + ? _value.time + : time // ignore: cast_nullable_to_non_nullable + as String?, + level: freezed == level + ? _value.level + : level // ignore: cast_nullable_to_non_nullable + as double?, + condition: freezed == condition + ? _value.condition + : condition // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$BsHoursImpl implements _BsHours { + const _$BsHoursImpl({this.time, this.level, this.condition}); + + factory _$BsHoursImpl.fromJson(Map json) => + _$$BsHoursImplFromJson(json); + + @override + final String? time; + @override + final double? level; + @override + final String? condition; + + @override + String toString() { + return 'BsHours(time: $time, level: $level, condition: $condition)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BsHoursImpl && + (identical(other.time, time) || other.time == time) && + (identical(other.level, level) || other.level == level) && + (identical(other.condition, condition) || + other.condition == condition)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, time, level, condition); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$BsHoursImplCopyWith<_$BsHoursImpl> get copyWith => + __$$BsHoursImplCopyWithImpl<_$BsHoursImpl>(this, _$identity); + + @override + Map toJson() { + return _$$BsHoursImplToJson( + this, + ); + } +} + +abstract class _BsHours implements BsHours { + const factory _BsHours( + {final String? time, + final double? level, + final String? condition}) = _$BsHoursImpl; + + factory _BsHours.fromJson(Map json) = _$BsHoursImpl.fromJson; + + @override + String? get time; + @override + double? get level; + @override + String? get condition; + @override + @JsonKey(ignore: true) + _$$BsHoursImplCopyWith<_$BsHoursImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/self_screening/blood_sugar/data/models/bs_hours.g.dart b/lib/src/features/self_screening/blood_sugar/data/models/bs_hours.g.dart new file mode 100644 index 00000000..9d5d99ee --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/models/bs_hours.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'bs_hours.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$BsHoursImpl _$$BsHoursImplFromJson(Map json) => + _$BsHoursImpl( + time: json['time'] as String?, + level: (json['level'] as num?)?.toDouble(), + condition: json['condition'] as String?, + ); + +Map _$$BsHoursImplToJson(_$BsHoursImpl instance) => + { + 'time': instance.time, + 'level': instance.level, + 'condition': instance.condition, + }; diff --git a/lib/src/features/self_screening/blood_sugar/data/models/bs_six_months.dart b/lib/src/features/self_screening/blood_sugar/data/models/bs_six_months.dart new file mode 100644 index 00000000..909a2d99 --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/models/bs_six_months.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'bs_six_months.freezed.dart'; +part 'bs_six_months.g.dart'; + +@Freezed() +class BsSixMonths with _$BsSixMonths { + + const factory BsSixMonths({ + String? month, + double? avg_level + }) = _BsSixMonths; + + factory BsSixMonths.fromJson(Map json) + => _$BsSixMonthsFromJson(json); +} diff --git a/lib/src/features/self_screening/blood_sugar/data/models/bs_six_months.freezed.dart b/lib/src/features/self_screening/blood_sugar/data/models/bs_six_months.freezed.dart new file mode 100644 index 00000000..c14b08c0 --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/models/bs_six_months.freezed.dart @@ -0,0 +1,169 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'bs_six_months.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +BsSixMonths _$BsSixMonthsFromJson(Map json) { + return _BsSixMonths.fromJson(json); +} + +/// @nodoc +mixin _$BsSixMonths { + String? get month => throw _privateConstructorUsedError; + double? get avg_level => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $BsSixMonthsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BsSixMonthsCopyWith<$Res> { + factory $BsSixMonthsCopyWith( + BsSixMonths value, $Res Function(BsSixMonths) then) = + _$BsSixMonthsCopyWithImpl<$Res, BsSixMonths>; + @useResult + $Res call({String? month, double? avg_level}); +} + +/// @nodoc +class _$BsSixMonthsCopyWithImpl<$Res, $Val extends BsSixMonths> + implements $BsSixMonthsCopyWith<$Res> { + _$BsSixMonthsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? month = freezed, + Object? avg_level = freezed, + }) { + return _then(_value.copyWith( + month: freezed == month + ? _value.month + : month // ignore: cast_nullable_to_non_nullable + as String?, + avg_level: freezed == avg_level + ? _value.avg_level + : avg_level // ignore: cast_nullable_to_non_nullable + as double?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$BsSixMonthsImplCopyWith<$Res> + implements $BsSixMonthsCopyWith<$Res> { + factory _$$BsSixMonthsImplCopyWith( + _$BsSixMonthsImpl value, $Res Function(_$BsSixMonthsImpl) then) = + __$$BsSixMonthsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String? month, double? avg_level}); +} + +/// @nodoc +class __$$BsSixMonthsImplCopyWithImpl<$Res> + extends _$BsSixMonthsCopyWithImpl<$Res, _$BsSixMonthsImpl> + implements _$$BsSixMonthsImplCopyWith<$Res> { + __$$BsSixMonthsImplCopyWithImpl( + _$BsSixMonthsImpl _value, $Res Function(_$BsSixMonthsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? month = freezed, + Object? avg_level = freezed, + }) { + return _then(_$BsSixMonthsImpl( + month: freezed == month + ? _value.month + : month // ignore: cast_nullable_to_non_nullable + as String?, + avg_level: freezed == avg_level + ? _value.avg_level + : avg_level // ignore: cast_nullable_to_non_nullable + as double?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$BsSixMonthsImpl implements _BsSixMonths { + const _$BsSixMonthsImpl({this.month, this.avg_level}); + + factory _$BsSixMonthsImpl.fromJson(Map json) => + _$$BsSixMonthsImplFromJson(json); + + @override + final String? month; + @override + final double? avg_level; + + @override + String toString() { + return 'BsSixMonths(month: $month, avg_level: $avg_level)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BsSixMonthsImpl && + (identical(other.month, month) || other.month == month) && + (identical(other.avg_level, avg_level) || + other.avg_level == avg_level)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, month, avg_level); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$BsSixMonthsImplCopyWith<_$BsSixMonthsImpl> get copyWith => + __$$BsSixMonthsImplCopyWithImpl<_$BsSixMonthsImpl>(this, _$identity); + + @override + Map toJson() { + return _$$BsSixMonthsImplToJson( + this, + ); + } +} + +abstract class _BsSixMonths implements BsSixMonths { + const factory _BsSixMonths({final String? month, final double? avg_level}) = + _$BsSixMonthsImpl; + + factory _BsSixMonths.fromJson(Map json) = + _$BsSixMonthsImpl.fromJson; + + @override + String? get month; + @override + double? get avg_level; + @override + @JsonKey(ignore: true) + _$$BsSixMonthsImplCopyWith<_$BsSixMonthsImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/self_screening/blood_sugar/data/models/bs_six_months.g.dart b/lib/src/features/self_screening/blood_sugar/data/models/bs_six_months.g.dart new file mode 100644 index 00000000..d0a48b6a --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/models/bs_six_months.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'bs_six_months.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$BsSixMonthsImpl _$$BsSixMonthsImplFromJson(Map json) => + _$BsSixMonthsImpl( + month: json['month'] as String?, + avg_level: (json['avg_level'] as num?)?.toDouble(), + ); + +Map _$$BsSixMonthsImplToJson(_$BsSixMonthsImpl instance) => + { + 'month': instance.month, + 'avg_level': instance.avg_level, + }; diff --git a/lib/src/features/self_screening/blood_sugar/data/models/bs_week.dart b/lib/src/features/self_screening/blood_sugar/data/models/bs_week.dart new file mode 100644 index 00000000..30c83d9c --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/models/bs_week.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'bs_week.freezed.dart'; +part 'bs_week.g.dart'; + +@Freezed() +class BsWeek with _$BsWeek { + + const factory BsWeek({ + required String dayName, + String? date, + double? level, + String? condition, + }) = _BsWeek; + + factory BsWeek.fromJson(Map json) + => _$BsWeekFromJson(json); +} diff --git a/lib/src/features/self_screening/blood_sugar/data/models/bs_week.freezed.dart b/lib/src/features/self_screening/blood_sugar/data/models/bs_week.freezed.dart new file mode 100644 index 00000000..df69540f --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/models/bs_week.freezed.dart @@ -0,0 +1,201 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'bs_week.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +BsWeek _$BsWeekFromJson(Map json) { + return _BsWeek.fromJson(json); +} + +/// @nodoc +mixin _$BsWeek { + String get dayName => throw _privateConstructorUsedError; + String? get date => throw _privateConstructorUsedError; + double? get level => throw _privateConstructorUsedError; + String? get condition => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $BsWeekCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BsWeekCopyWith<$Res> { + factory $BsWeekCopyWith(BsWeek value, $Res Function(BsWeek) then) = + _$BsWeekCopyWithImpl<$Res, BsWeek>; + @useResult + $Res call({String dayName, String? date, double? level, String? condition}); +} + +/// @nodoc +class _$BsWeekCopyWithImpl<$Res, $Val extends BsWeek> + implements $BsWeekCopyWith<$Res> { + _$BsWeekCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? dayName = null, + Object? date = freezed, + Object? level = freezed, + Object? condition = freezed, + }) { + return _then(_value.copyWith( + dayName: null == dayName + ? _value.dayName + : dayName // ignore: cast_nullable_to_non_nullable + as String, + date: freezed == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as String?, + level: freezed == level + ? _value.level + : level // ignore: cast_nullable_to_non_nullable + as double?, + condition: freezed == condition + ? _value.condition + : condition // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$BsWeekImplCopyWith<$Res> implements $BsWeekCopyWith<$Res> { + factory _$$BsWeekImplCopyWith( + _$BsWeekImpl value, $Res Function(_$BsWeekImpl) then) = + __$$BsWeekImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String dayName, String? date, double? level, String? condition}); +} + +/// @nodoc +class __$$BsWeekImplCopyWithImpl<$Res> + extends _$BsWeekCopyWithImpl<$Res, _$BsWeekImpl> + implements _$$BsWeekImplCopyWith<$Res> { + __$$BsWeekImplCopyWithImpl( + _$BsWeekImpl _value, $Res Function(_$BsWeekImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? dayName = null, + Object? date = freezed, + Object? level = freezed, + Object? condition = freezed, + }) { + return _then(_$BsWeekImpl( + dayName: null == dayName + ? _value.dayName + : dayName // ignore: cast_nullable_to_non_nullable + as String, + date: freezed == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as String?, + level: freezed == level + ? _value.level + : level // ignore: cast_nullable_to_non_nullable + as double?, + condition: freezed == condition + ? _value.condition + : condition // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$BsWeekImpl implements _BsWeek { + const _$BsWeekImpl( + {required this.dayName, this.date, this.level, this.condition}); + + factory _$BsWeekImpl.fromJson(Map json) => + _$$BsWeekImplFromJson(json); + + @override + final String dayName; + @override + final String? date; + @override + final double? level; + @override + final String? condition; + + @override + String toString() { + return 'BsWeek(dayName: $dayName, date: $date, level: $level, condition: $condition)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BsWeekImpl && + (identical(other.dayName, dayName) || other.dayName == dayName) && + (identical(other.date, date) || other.date == date) && + (identical(other.level, level) || other.level == level) && + (identical(other.condition, condition) || + other.condition == condition)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, dayName, date, level, condition); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$BsWeekImplCopyWith<_$BsWeekImpl> get copyWith => + __$$BsWeekImplCopyWithImpl<_$BsWeekImpl>(this, _$identity); + + @override + Map toJson() { + return _$$BsWeekImplToJson( + this, + ); + } +} + +abstract class _BsWeek implements BsWeek { + const factory _BsWeek( + {required final String dayName, + final String? date, + final double? level, + final String? condition}) = _$BsWeekImpl; + + factory _BsWeek.fromJson(Map json) = _$BsWeekImpl.fromJson; + + @override + String get dayName; + @override + String? get date; + @override + double? get level; + @override + String? get condition; + @override + @JsonKey(ignore: true) + _$$BsWeekImplCopyWith<_$BsWeekImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/self_screening/blood_sugar/data/models/bs_week.g.dart b/lib/src/features/self_screening/blood_sugar/data/models/bs_week.g.dart new file mode 100644 index 00000000..d5856383 --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/models/bs_week.g.dart @@ -0,0 +1,22 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'bs_week.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$BsWeekImpl _$$BsWeekImplFromJson(Map json) => _$BsWeekImpl( + dayName: json['dayName'] as String, + date: json['date'] as String?, + level: (json['level'] as num?)?.toDouble(), + condition: json['condition'] as String?, + ); + +Map _$$BsWeekImplToJson(_$BsWeekImpl instance) => + { + 'dayName': instance.dayName, + 'date': instance.date, + 'level': instance.level, + 'condition': instance.condition, + }; diff --git a/lib/src/features/self_screening/blood_sugar/data/models/filter_bs.dart b/lib/src/features/self_screening/blood_sugar/data/models/filter_bs.dart new file mode 100644 index 00000000..dd428f92 --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/models/filter_bs.dart @@ -0,0 +1,25 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/bs_hours.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/bs_six_months.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/bs_week.dart'; + +part 'filter_bs.freezed.dart'; +// part 'filter_bs.g.dart'; + +@Freezed() +class FilterBs with _$FilterBs { + + const factory FilterBs({ + required List hourly, + required List weekly, + required List sixMonthly, + }) = _FilterBs; + + factory FilterBs.fromJson(Map json) { + return FilterBs( + hourly: (json['hourly'] as List? ?? []).map((hr) => BsHours.fromJson(hr)).toList(), + weekly: (json['weekly'] as List? ?? []).map((wk) => BsWeek.fromJson(wk)).toList(), + sixMonthly: (json['sixMonthly']as List? ?? []).map((mnth) => BsSixMonths.fromJson(mnth)).toList(), + ); + } +} \ No newline at end of file diff --git a/lib/src/features/self_screening/blood_sugar/data/models/filter_bs.freezed.dart b/lib/src/features/self_screening/blood_sugar/data/models/filter_bs.freezed.dart new file mode 100644 index 00000000..98a6a4f9 --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/models/filter_bs.freezed.dart @@ -0,0 +1,200 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'filter_bs.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$FilterBs { + List get hourly => throw _privateConstructorUsedError; + List get weekly => throw _privateConstructorUsedError; + List get sixMonthly => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $FilterBsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $FilterBsCopyWith<$Res> { + factory $FilterBsCopyWith(FilterBs value, $Res Function(FilterBs) then) = + _$FilterBsCopyWithImpl<$Res, FilterBs>; + @useResult + $Res call( + {List hourly, + List weekly, + List sixMonthly}); +} + +/// @nodoc +class _$FilterBsCopyWithImpl<$Res, $Val extends FilterBs> + implements $FilterBsCopyWith<$Res> { + _$FilterBsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? hourly = null, + Object? weekly = null, + Object? sixMonthly = null, + }) { + return _then(_value.copyWith( + hourly: null == hourly + ? _value.hourly + : hourly // ignore: cast_nullable_to_non_nullable + as List, + weekly: null == weekly + ? _value.weekly + : weekly // ignore: cast_nullable_to_non_nullable + as List, + sixMonthly: null == sixMonthly + ? _value.sixMonthly + : sixMonthly // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$FilterBsImplCopyWith<$Res> + implements $FilterBsCopyWith<$Res> { + factory _$$FilterBsImplCopyWith( + _$FilterBsImpl value, $Res Function(_$FilterBsImpl) then) = + __$$FilterBsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {List hourly, + List weekly, + List sixMonthly}); +} + +/// @nodoc +class __$$FilterBsImplCopyWithImpl<$Res> + extends _$FilterBsCopyWithImpl<$Res, _$FilterBsImpl> + implements _$$FilterBsImplCopyWith<$Res> { + __$$FilterBsImplCopyWithImpl( + _$FilterBsImpl _value, $Res Function(_$FilterBsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? hourly = null, + Object? weekly = null, + Object? sixMonthly = null, + }) { + return _then(_$FilterBsImpl( + hourly: null == hourly + ? _value._hourly + : hourly // ignore: cast_nullable_to_non_nullable + as List, + weekly: null == weekly + ? _value._weekly + : weekly // ignore: cast_nullable_to_non_nullable + as List, + sixMonthly: null == sixMonthly + ? _value._sixMonthly + : sixMonthly // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +class _$FilterBsImpl implements _FilterBs { + const _$FilterBsImpl( + {required final List hourly, + required final List weekly, + required final List sixMonthly}) + : _hourly = hourly, + _weekly = weekly, + _sixMonthly = sixMonthly; + + final List _hourly; + @override + List get hourly { + if (_hourly is EqualUnmodifiableListView) return _hourly; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_hourly); + } + + final List _weekly; + @override + List get weekly { + if (_weekly is EqualUnmodifiableListView) return _weekly; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_weekly); + } + + final List _sixMonthly; + @override + List get sixMonthly { + if (_sixMonthly is EqualUnmodifiableListView) return _sixMonthly; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_sixMonthly); + } + + @override + String toString() { + return 'FilterBs(hourly: $hourly, weekly: $weekly, sixMonthly: $sixMonthly)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$FilterBsImpl && + const DeepCollectionEquality().equals(other._hourly, _hourly) && + const DeepCollectionEquality().equals(other._weekly, _weekly) && + const DeepCollectionEquality() + .equals(other._sixMonthly, _sixMonthly)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_hourly), + const DeepCollectionEquality().hash(_weekly), + const DeepCollectionEquality().hash(_sixMonthly)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$FilterBsImplCopyWith<_$FilterBsImpl> get copyWith => + __$$FilterBsImplCopyWithImpl<_$FilterBsImpl>(this, _$identity); +} + +abstract class _FilterBs implements FilterBs { + const factory _FilterBs( + {required final List hourly, + required final List weekly, + required final List sixMonthly}) = _$FilterBsImpl; + + @override + List get hourly; + @override + List get weekly; + @override + List get sixMonthly; + @override + @JsonKey(ignore: true) + _$$FilterBsImplCopyWith<_$FilterBsImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/self_screening/blood_sugar/data/providers/blood_sugar_provider.dart b/lib/src/features/self_screening/blood_sugar/data/providers/blood_sugar_provider.dart new file mode 100644 index 00000000..c2623a63 --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/providers/blood_sugar_provider.dart @@ -0,0 +1,38 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/bs_advice.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/blood_sugar.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/filter_bs.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/repository/blood_sugar_repository.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/repository/bs_advice_repository.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/repository/bs_filter_repository.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/services/blood_sugar_service.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/services/bs_advice_service.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/services/bs_filter_service.dart'; + +final bloodSugarProvider = Provider((ref) { + return BloodSugarRepository(BloodSugarService()); +}); + +final bloodSugarEntriesProvider = FutureProvider>((ref) async { + final repository = ref.watch(bloodSugarProvider); + return await repository.getBloodSugars(); +}); + +final bsAdviceRepositoryProvider = Provider((ref) { + return BsAdviceRepository(BsAdviceService()); +}); + +final bloodSugarListAdviceProvider = FutureProvider>((ref) async { + final repository = ref.watch(bsAdviceRepositoryProvider); + return await repository.getBloodPressuresAdvice(); +}); + +final bsFilterRepositoryProvider = Provider((ref) { + return BsFilterRepository(BsFilterService()); +}); + +final bsFilterListProvider = FutureProvider((ref) async { + final repository = ref.watch(bsFilterRepositoryProvider); + return await repository.fetchBloodSugarFilters(); +}); + diff --git a/lib/src/features/blood_sugar/data/repository/blood_sugar_repository.dart b/lib/src/features/self_screening/blood_sugar/data/repository/blood_sugar_repository.dart similarity index 61% rename from lib/src/features/blood_sugar/data/repository/blood_sugar_repository.dart rename to lib/src/features/self_screening/blood_sugar/data/repository/blood_sugar_repository.dart index e2e20392..6deb2f77 100644 --- a/lib/src/features/blood_sugar/data/repository/blood_sugar_repository.dart +++ b/lib/src/features/self_screening/blood_sugar/data/repository/blood_sugar_repository.dart @@ -1,5 +1,5 @@ -import 'package:nishauri/src/features/blood_sugar/data/models/blood_sugar.dart'; -import 'package:nishauri/src/features/blood_sugar/data/services/blood_sugar_service.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/blood_sugar.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/services/blood_sugar_service.dart'; class BloodSugarRepository { final BloodSugarService _service; diff --git a/lib/src/features/self_screening/blood_sugar/data/repository/bs_advice_repository.dart b/lib/src/features/self_screening/blood_sugar/data/repository/bs_advice_repository.dart new file mode 100644 index 00000000..3411c327 --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/repository/bs_advice_repository.dart @@ -0,0 +1,12 @@ +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/bs_advice.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/services/bs_advice_service.dart'; + +class BsAdviceRepository { + final BsAdviceService _service; + + BsAdviceRepository(this._service); + + Future> getBloodPressuresAdvice() async { + return await _service.fetchBloodSugarAdvice(); + } +} \ No newline at end of file diff --git a/lib/src/features/self_screening/blood_sugar/data/repository/bs_filter_repository.dart b/lib/src/features/self_screening/blood_sugar/data/repository/bs_filter_repository.dart new file mode 100644 index 00000000..4ee75206 --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/repository/bs_filter_repository.dart @@ -0,0 +1,12 @@ +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/filter_bs.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/services/bs_filter_service.dart'; + +class BsFilterRepository { + final BsFilterService _service; + + BsFilterRepository(this._service); + + Future fetchBloodSugarFilters() async { + return await _service.fetchBloodSugarFilters(); + } +} \ No newline at end of file diff --git a/lib/src/features/blood_sugar/data/services/blood_sugar_service.dart b/lib/src/features/self_screening/blood_sugar/data/services/blood_sugar_service.dart similarity index 97% rename from lib/src/features/blood_sugar/data/services/blood_sugar_service.dart rename to lib/src/features/self_screening/blood_sugar/data/services/blood_sugar_service.dart index 266ef6f4..db881282 100644 --- a/lib/src/features/blood_sugar/data/services/blood_sugar_service.dart +++ b/lib/src/features/self_screening/blood_sugar/data/services/blood_sugar_service.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart'; import 'package:nishauri/src/features/auth/data/respositories/auth_repository.dart'; import 'package:nishauri/src/features/auth/data/services/AuthApiService.dart'; -import 'package:nishauri/src/features/blood_sugar/data/models/blood_sugar.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/blood_sugar.dart'; import 'package:nishauri/src/shared/interfaces/HTTPService.dart'; import 'package:nishauri/src/utils/constants.dart'; diff --git a/lib/src/features/self_screening/blood_sugar/data/services/bs_advice_service.dart b/lib/src/features/self_screening/blood_sugar/data/services/bs_advice_service.dart new file mode 100644 index 00000000..a73a1263 --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/services/bs_advice_service.dart @@ -0,0 +1,35 @@ +import 'dart:convert'; +import 'package:flutter/services.dart'; +import 'package:nishauri/src/features/auth/data/respositories/auth_repository.dart'; +import 'package:nishauri/src/features/auth/data/services/AuthApiService.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/bs_advice.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/blood_pressure.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/bp_advice.dart'; +import 'package:nishauri/src/shared/interfaces/HTTPService.dart'; + +class BsAdviceService extends HTTPService { + final AuthRepository _repository = AuthRepository(AuthApiService()); + + // Future fetchBloodPressuresAdvice_(dynamic args) async { + // final id = await _repository.getUserId(); + // final tokenPair = await getCachedToken(); + // var headers = {'Authorization': 'Bearer ${tokenPair.accessToken}'}; + // var url = '${Constants.BASE_URL_NEW}get_blood_pressure?user_id=$id'; + // final response = request( + // url: url, + // token: tokenPair, + // method: 'GET', + // requestHeaders: headers, + // userId: id); + // return response; + // } + + Future> fetchBloodSugarAdvice() async { + List bs = []; + final String responseString = await rootBundle.loadString('assets/data/blood_sugar_advice.json'); + final Map responseData = json.decode(responseString); + final List jsonList = responseData["status"]; + bs.addAll(jsonList.map((json) => BsAdvice.fromJson(json))); + return bs; + } +} \ No newline at end of file diff --git a/lib/src/features/self_screening/blood_sugar/data/services/bs_filter_service.dart b/lib/src/features/self_screening/blood_sugar/data/services/bs_filter_service.dart new file mode 100644 index 00000000..cad1f9e9 --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/data/services/bs_filter_service.dart @@ -0,0 +1,53 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; +import 'package:nishauri/src/features/auth/data/respositories/auth_repository.dart'; +import 'package:nishauri/src/features/auth/data/services/AuthApiService.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/bs_hours.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/bs_six_months.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/bs_week.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/filter_bs.dart'; +import 'package:nishauri/src/shared/interfaces/HTTPService.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class BsFilterService extends HTTPService { + final AuthRepository _repository = AuthRepository(AuthApiService()); + + Future fetchBloodSugarFilters_(dynamic args) async { + final id = await _repository.getUserId(); + final tokenPair = await getCachedToken(); + var headers = {'Authorization': 'Bearer ${tokenPair.accessToken}'}; + var url = '${Constants.BASE_URL_NEW}get_blood_sugar_filter?user_id=$id'; + final response = request( + url: url, + token: tokenPair, + method: 'GET', + requestHeaders: headers, + userId: id); + return response; + } + + Future fetchBloodSugarFilters() async { + try { + final response = await call(fetchBloodSugarFilters_, null); + + if (response.statusCode == 200) { + final responseString = await response.stream.bytesToString(); + final Map responseData = json.decode(responseString); + + if (responseData.containsKey('data')) { + final filterData = responseData['data']; + final FilterBs filterBs = FilterBs.fromJson(filterData); + return filterBs; + } else { + throw "Invalid response format"; + } + } else { + throw "Something Went Wrong: ${response.statusCode}"; + } + } catch (e) { + throw 'Error occurred: $e'; + } + } +} diff --git a/lib/src/features/blood_sugar/presentation/pages/AddBloodSugarScreen.dart b/lib/src/features/self_screening/blood_sugar/presentation/pages/AddBloodSugarScreen.dart similarity index 90% rename from lib/src/features/blood_sugar/presentation/pages/AddBloodSugarScreen.dart rename to lib/src/features/self_screening/blood_sugar/presentation/pages/AddBloodSugarScreen.dart index b8bc3e16..6aa6445f 100644 --- a/lib/src/features/blood_sugar/presentation/pages/AddBloodSugarScreen.dart +++ b/lib/src/features/self_screening/blood_sugar/presentation/pages/AddBloodSugarScreen.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:nishauri/src/features/blood_sugar/data/models/blood_sugar.dart'; -import 'package:nishauri/src/features/blood_sugar/data/providers/blood_sugar_provider.dart'; -import 'package:nishauri/src/features/blood_sugar/presentation/widgets/blood_level_picker.dart'; -import 'package:nishauri/src/features/blood_sugar/presentation/widgets/blood_level_units_picker.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/blood_sugar.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/providers/blood_sugar_provider.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/presentation/widgets/blood_level_picker.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/presentation/widgets/blood_level_units_picker.dart'; import 'package:nishauri/src/utils/constants.dart'; class AddBloodSugarScreen extends HookConsumerWidget { diff --git a/lib/src/features/self_screening/blood_sugar/presentation/pages/BloodSugarScreen.dart b/lib/src/features/self_screening/blood_sugar/presentation/pages/BloodSugarScreen.dart new file mode 100644 index 00000000..c20f185f --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/presentation/pages/BloodSugarScreen.dart @@ -0,0 +1,290 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:intl/intl.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/filter_bs.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/providers/blood_sugar_provider.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/custome_filter_chart.dart'; +import 'package:nishauri/src/shared/display/daily_card.dart'; +import 'package:nishauri/src/shared/input/Button.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +import '../../../../../shared/providers/selectedIndexProvider.dart'; + +class BloodSugarScreen extends ConsumerWidget { + const BloodSugarScreen({super.key}); + + Color _getStatusColor(String status) { + switch (status) { + case 'Normal': + return Colors.green; + case 'Impaired fasting': + return Colors.yellow; + case 'Impaired Glucose Tolerance': + return Colors.orange; + case 'Diabetes': + return Colors.red; + default: + return Colors.grey; + } + } + + String _getBloodSugarStatus(double level, String condition) { + if (level > 30) level /= 18.0; + + if (condition == 'Fasting (before meals)') { + if (level < 5.6) return 'Normal'; + if (level < 7.0) return 'Impaired Fasting'; + return 'Diabetes'; + } else if (condition == 'Postprandial (after meals)') { + if (level < 7.8) return 'Normal'; + if (level < 11.1) return 'Impaired Glucose Tolerance'; + return 'Diabetes'; + } + return 'Invalid condition'; + } + + double _convertToMMOL(double level) { + return level > 30 ? double.parse((level / 18.0).toStringAsFixed(1)) : level; + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final bloodSugarListProvider = ref.watch(bloodSugarEntriesProvider); + final adviceAsync = ref.watch(bloodSugarListAdviceProvider); + final selectedIndex = ref.watch(selectedIndexProvider); + final bsFilterListAsync = ref.watch(bsFilterListProvider); + final theme = Theme.of(context); + + final filter = ["Day", "Week", "6 Months"]; + + final data = bloodSugarListProvider.when( + data: (data) => data..sort((a, b) => b.created_at.compareTo(a.created_at)), + error: (_, __) => [], + loading: () => [Center(child: CircularProgressIndicator())], + ); + + if (data.isEmpty) { + return const Center(child: CircularProgressIndicator()); + } + + final displayedData = data.first; + final status = _getBloodSugarStatus(displayedData.level, displayedData.condition); + + final advice = adviceAsync.when( + data: (adviceData) => adviceData.firstWhere((ad) => ad.label == status).advice ?? 'No advice available', + error: (_, __) => 'Error loading advice', + loading: () => 'Loading advice...', + ); + + final FilterBs filteredData = bsFilterListAsync.when( + data: (data) => data, + error: (_, __) => FilterBs( + hourly: [], + weekly: [], + sixMonthly: [], + ), + loading: () => FilterBs( + hourly: [], + weekly: [], + sixMonthly: [], + ), + ); + + final List? dataPoint; + if (filter[selectedIndex] == "Day") { + dataPoint = filteredData.hourly + ?.asMap() + .entries + .map((entry) => FlSpot( + entry.key.toDouble(), + _convertToMMOL(entry.value.level ?? 0), + )) + .toList(); + } else if (filter[selectedIndex] == "Week") { + dataPoint = filteredData.weekly + ?.asMap() + .entries + .map((entry) => FlSpot( + entry.key.toDouble(), + _convertToMMOL(entry.value.level ?? 0), + )) + .toList(); + } else if (filter[selectedIndex] == "6 Months") { + dataPoint = filteredData.sixMonthly + ?.asMap() + .entries + .map((entry) => FlSpot( + entry.key.toDouble(), + _convertToMMOL(entry.value.avg_level ?? 0), + )) + .toList(); + } else { + dataPoint = []; + } + final List dateTimeList; + if (filter[selectedIndex] == "Day") { + dateTimeList = filteredData.hourly + ?.asMap() + .entries + .map((entry) { + final timeString = entry.value.time; + final dateTime = timeString != null ? DateTime.parse(timeString) : DateTime.now(); + return DateFormat('dd/MM').format(dateTime); + }) + .whereType() + .toList() ?? []; + } else if (filter[selectedIndex] == "Week") { + dateTimeList = filteredData.weekly + ?.asMap() + .entries + .map((entry) => entry.value.dayName ) + .whereType() + .toList() ?? []; + } else if (filter[selectedIndex] == "6 Months") { + dateTimeList = filteredData.sixMonthly + ?.asMap() + .entries + .map((entry) => entry.value.month) + .whereType() + .toList() ?? []; + } else { + dateTimeList = []; + } + + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + smallTitle: "Blood Sugar", + height: Constants.SMALL_APP_BAR_HEIGHT, + color: Constants.selfScreeningBgColor, + rightBtTitle: "Add Data", + path: RouteNames.BLOOD_SUGAR_INPUT, + ), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + spacing: Constants.SIXTEEN, + runSpacing: Constants.SIXTEEN, + children: [ + FilterCard( + columnTitles:filter, + onPressed: (index) { + ref.read(selectedIndexProvider.notifier).state = index; + }, + ), + ], + ), + const SizedBox(height: Constants.SPACING), + Row( + children: [ + Text("Last Record Date: ", style: theme.textTheme.bodyLarge), + Text( + DateFormat('dd MMM yyyy').format(displayedData.created_at), + style: theme.textTheme.bodyLarge!.copyWith(color: Colors.grey, fontWeight: FontWeight.bold), + ), + ], + ), + const SizedBox(height: Constants.SPACING), + Row( + children: [ + Text( + "${_convertToMMOL(displayedData.level)}", + style: theme.textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold), + ), + const SizedBox(width: 4), + Text("MMOL/L", style: theme.textTheme.bodyMedium), + const SizedBox(width: 4), + Text(status, style: theme.textTheme.bodyLarge!.copyWith(color: _getStatusColor(status))), + ], + ), + const SizedBox(height: Constants.SPACING), + Text( + displayedData.condition, + style: theme.textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold, color: Colors.blueGrey), + ), + const SizedBox(height: Constants.SPACING), + CustomFilterLineChart( + dataPoints: dataPoint, + dateTimes: dateTimeList, + gradientColors: [ + Constants.bloodSugarColor, + Constants.bloodSugarColor.withOpacity(0.3), + ], + minX: 0, + maxX: dateTimeList.length.toDouble() - 1, + minY: 0.0, + maxY: 30.0, + leftTile: true, + bottomTile: true, + interval: 5, + filter: filter[selectedIndex], + barColor: Constants.barColor, + ), + const SizedBox(height: Constants.SPACING), + Card( + color: Constants.bgColor, + child: ListTile( + title: Text( + "Show All Data", + style: theme.textTheme.titleSmall!.copyWith(fontWeight: FontWeight.bold), + ), + trailing: const Icon(Icons.arrow_forward_ios_outlined), + onTap: () => context.goNamed(RouteNames.BLOOD_SUGAR_RECORDS, extra: data), + ), + ), + const SizedBox(height: Constants.SPACING), + Container( + color: Constants.bgColor, + child: Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: ListTile( + title: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, // Align to the start of the column + children: [ + Text( + 'Your Blood Sugar Levels are', + style: theme.textTheme.bodyMedium!.copyWith(fontWeight: FontWeight.w600), + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), // Add spacing between the lines + Text( + status, + style: theme.textTheme.bodyMedium!.copyWith(color: Colors.green), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ], + ), + subtitle: Text(advice, style: theme.textTheme.bodyMedium), + ), + ), + ), + const SizedBox(height: Constants.SPACING), + Button( + title: "More Insight", + onPress: () => context.goNamed(RouteNames.BLOOD_SUGAR_INSIGHT), + textColor: Constants.selfScreeningBgColor, + ), + ], + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/features/self_screening/blood_sugar/presentation/pages/bs_input_screen.dart b/lib/src/features/self_screening/blood_sugar/presentation/pages/bs_input_screen.dart new file mode 100644 index 00000000..9bd0154d --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/presentation/pages/bs_input_screen.dart @@ -0,0 +1,405 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/blood_sugar.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/providers/blood_sugar_provider.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class BloodSugarInputs extends ConsumerStatefulWidget { + const BloodSugarInputs({super.key}); + + @override + _BloodSugarInputsState createState() => _BloodSugarInputsState(); +} + +class _BloodSugarInputsState extends ConsumerState { + final TextEditingController _dateController = TextEditingController(); + final TextEditingController _timeController = TextEditingController(); + final TextEditingController _notesController = TextEditingController(); + final TextEditingController _bloodGlucoseController = TextEditingController(text: "5.0"); + final TextEditingController _conditionController = TextEditingController(); + + final List _dropdownOptions = [ + 'Fasting (before meals)', + 'Postprandial (after meals)' + ]; + String _selectedCondition = "Fasting (before meals)"; + + @override + void initState() { + super.initState(); + final now = DateTime.now(); + _dateController.text = DateFormat('dd MMM yyyy').format(now); + _timeController.text = "${TimeOfDay.now().hour}:${TimeOfDay.now().minute.toString().padLeft(2, '0')}"; + _conditionController.text = _selectedCondition; + } + + Future _selectDate(BuildContext context) async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(2000), + lastDate: DateTime.now(), + ); + if (picked != null) { + setState(() { + _dateController.text = DateFormat('dd MMM yyyy').format(picked); + }); + } + } + + Future _selectTime(BuildContext context) async { + final TimeOfDay? picked = await showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + ); + if (picked != null) { + setState(() { + _timeController.text = '${picked.hour}:${picked.minute.toString().padLeft(2, '0')}'; + }); + } + } + + void _reloadData() { + ref.refresh(bloodSugarProvider); + ref.refresh(bsFilterListProvider); + } + + void _submitData(double level) { + final String notes = _notesController.text; + final condition = _conditionController.text; + final DateTime measurementTime = DateTime.now(); + final bs = BloodSugar( + level: level, + condition: condition, + created_at: measurementTime, + notes: notes, + ); + + ref.read(bloodSugarProvider).saveBloodSugar(bs).then((value) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(value)), + ); + _reloadData(); + Navigator.of(context).pop(); + }).catchError((error) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(error)), + ); + }); + } + + void _saveData() { + final level = double.tryParse(_bloodGlucoseController.text); + final condition = _conditionController.text; + + if (_dateController.text.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please select a date.')), + ); + return; + } + + if (_timeController.text.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please select a time.')), + ); + return; + } + + if (level == null || level <= 0 || level > 30) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please enter valid blood sugar readings (0 < level ≤ 30).')), + ); + return; + } + + _submitData(level); + + bool isHigh = false; + bool isLow = false; + + if (condition == "Fasting (before meals)") { + if (level > 5.6) { + isHigh = true; + } else if (level < 3.9) { + isLow = true; + } + } else if (condition == "Postprandial (after meals)") { + if (level > 7.8) { + isHigh = true; + } else if (level < 5.0) { + isLow = true; + } + } + + if (isHigh) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Warning: High blood sugar detected!'), + backgroundColor: Colors.red, + ), + ); + } else if (isLow) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Warning: Low blood sugar detected!'), + backgroundColor: Colors.red, + ), + ); + } + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Data saved: $level mg/dL with condition $condition on ${_dateController.text} at ${_timeController.text}'), + ), + ); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final screenWidth = MediaQuery.of(context).size.width; + final isSmallScreen = screenWidth < 600; // Example breakpoint for small screens + final fieldWidth = isSmallScreen ? screenWidth * 0.8 : 270; + + return Scaffold( + resizeToAvoidBottomInset: true, + body: SingleChildScrollView( + child: Column( + children: [ + const CustomAppBar( + color: Constants.selfScreeningBgColor, + height: 120, + smallTitle: "Blood Sugar Input", + rightBtTitle: "Add Data", + ), + Padding( + padding: const EdgeInsets.all(1.0), + child: Container( + padding: const EdgeInsets.all(Constants.SPACING), + decoration: BoxDecoration( + color: Constants.bgColor, + borderRadius: BorderRadius.circular(1), + border: Border.all(color: Constants.bgColor), + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: screenWidth * 0.9, + decoration: BoxDecoration( + color: Constants.white, + borderRadius: BorderRadius.circular(1), + border: Border.all(color: Constants.bgColor), + ), + child: Text("mmol/L", style: theme.textTheme.bodyLarge), + ), + ], + ), + const Divider(), + const SizedBox(height: Constants.SPACING), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Date:", style: theme.textTheme.bodyLarge), + GestureDetector( + onTap: () => _selectDate(context), + child: AbsorbPointer( + child: Container( + decoration: BoxDecoration( + color: Constants.white, + borderRadius: BorderRadius.circular(15), + border: Border.all(color: Constants.bgColor), + ), + padding: const EdgeInsets.all(Constants.SPACING), + // width: fieldWidth?.toDouble(), + width :150, + height: 40, + child: TextField( + controller: _dateController, + decoration: null, + textAlign: TextAlign.center + ), + ), + ), + ), + ], + ), + const SizedBox(height: Constants.SPACING), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Time:", style: theme.textTheme.bodyLarge), + GestureDetector( + onTap: () => _selectTime(context), + child: AbsorbPointer( + child: Container( + padding: const EdgeInsets.all(Constants.SPACING), + decoration: BoxDecoration( + color: Constants.white, + borderRadius: BorderRadius.circular(15), + border: Border.all(color: Constants.bgColor), + ), + //width: fieldWidth?.toDouble(), + width: 150, + height: 40, + child: TextField( + controller: _timeController, + decoration: null, + textAlign: TextAlign.center + ), + ), + ), + ), + ], + ), + const SizedBox(height: Constants.SPACING), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + "Blood Sugar Level", + style: theme.textTheme.bodyLarge, + // Optional: Handle long text + ), + ), + Container( + padding: const EdgeInsets.all(Constants.SPACING), + decoration: BoxDecoration( + color: Constants.white, + borderRadius: BorderRadius.circular(15), + border: Border.all(color: Constants.bgColor), + ), + //width: fieldWidth?.toDouble() ?? 100, // Provide a fallback width + width: 150, + height: 40, + child: TextField( + controller: _bloodGlucoseController, + keyboardType: TextInputType.number, + decoration: null, + textAlign: TextAlign.center, + ), + ), + ], + ), + + const SizedBox(height: Constants.SPACING), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child:Text("Meal Time", style: theme.textTheme.bodyLarge),), + Container( + padding: const EdgeInsets.all(Constants.SPACING), + decoration: BoxDecoration( + color: Constants.white, + borderRadius: BorderRadius.circular(15), + border: Border.all(color: Constants.bgColor), + ), + width: fieldWidth?.toDouble(), + //width: 150, + height: 60, + child: TextField( + controller: _conditionController, + readOnly: true, + + style: theme.textTheme.bodyMedium!.copyWith(decoration: null, ), + decoration: InputDecoration( + border: const OutlineInputBorder( + gapPadding: 4, + borderRadius: BorderRadius.all(Radius.circular(10)), + borderSide: BorderSide(color: Constants.white), + ), + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide(color: Constants.white), + ), + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide(color: Constants.white), + borderRadius: BorderRadius.all(Radius.circular(15)), + ), + suffixIcon: PopupMenuButton( + icon: const Icon(Icons.arrow_drop_down), + onSelected: (String value) { + _conditionController.text = value; + setState(() { + _selectedCondition = value; + }); + }, + itemBuilder: (BuildContext context) { + return _dropdownOptions.map>((String value) { + return PopupMenuItem( + value: value, + child: Text(value), + ); + }).toList(); + }, + ), + ), + ), + ), + ], + ), + const SizedBox(height: Constants.SPACING), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.all(Constants.SPACING), + decoration: BoxDecoration( + color: Constants.white, + borderRadius: BorderRadius.circular(15), + border: Border.all(color: Constants.bgColor), + ), + width: screenWidth * 0.9, + child: TextFormField( + style: theme.textTheme.bodyMedium!.copyWith(decoration: null), + controller: _notesController, + decoration: const InputDecoration( + labelText: "Notes (optional)", + border: OutlineInputBorder( + gapPadding: 5, + borderRadius: BorderRadius.all(Radius.circular(15)), + borderSide: BorderSide(color: Constants.bgColor), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Constants.bgColor), + borderRadius: BorderRadius.all(Radius.circular(15)), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Constants.bgColor), + borderRadius: BorderRadius.all(Radius.circular(15)), + ), + labelStyle: TextStyle(color: Constants.selfScreeningBgColor), + ), + keyboardType: TextInputType.text, + maxLines: null, + ), + ), + ], + ), + const SizedBox(height: Constants.SPACING), + const Divider(), + ElevatedButton( + onPressed: _saveData, + child: const Text('Save Data', style: TextStyle(color: Constants.selfScreeningBgColor)), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } + +} diff --git a/lib/src/features/self_screening/blood_sugar/presentation/pages/bs_line_list_Screen.dart b/lib/src/features/self_screening/blood_sugar/presentation/pages/bs_line_list_Screen.dart new file mode 100644 index 00000000..f1c5feaa --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/presentation/pages/bs_line_list_Screen.dart @@ -0,0 +1,184 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:intl/intl.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/blood_sugar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/background_image_widget.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +class BloodSugarRecords extends StatelessWidget { + final List data; + + const BloodSugarRecords({ + required this.data, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + if (data.isEmpty) + { + const BackgroundImageWidget( + customAppBar: CustomAppBar( + color: Constants.selfScreeningBgColor, + height: Constants.SMALL_APP_BAR_HEIGHT, + smallTitle: "All Record Data", + rightBtTitle: "", + ), svgImage: 'assets/images/emptyself_screening.svg', + notFoundText: 'No Data Recorded Yet', + ); + } + + double _convertToMMOL(double level) { + if (level > 30) { + level = level / 18.0; + } + return double.parse(level.toStringAsFixed(1)); + } + + String _getBloodSugarStatus(double level, String condition) { + // If the level is greater than 30, assume it's in mg/dL and convert to mmol/L + if (level > 30) { + level = level / 18.0; + } + + if (condition == 'Fasting (before meals)') { + if (level < 5.6) { + return 'Normal'; + } else if (5.6 <= level && level < 7.0) { + return 'Impaired Fasting'; + } else { + return 'Diabetes'; + } + } else if (condition == 'Postprandial (after meals)') { + if (level < 7.8) { + return 'Normal'; + } else if (7.8 <= level && level < 11.1) { + return 'Impaired Glucose Tolerance'; + } else { + return 'Diabetes'; + } + } else { + return 'Invalid condition'; + } + } + + Color _getStatusColor(String status) { + switch (status) { + case 'Normal': + return Colors.green; + case 'Impaired fasting': + return Colors.yellow; + case 'Impaired Glucose Tolerance': + return Colors.orange; + case 'Diabetes': + return Colors.red; + default: + return Colors.black; + } + } + + return Scaffold( + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const CustomAppBar( + color: Constants.selfScreeningBgColor, + height: 120, + smallTitle: "All Record Data", + rightBtTitle: "Add Data", + path: RouteNames.BLOOD_SUGAR_INPUT + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "MMOL/L", + style: theme.textTheme.titleMedium?.copyWith(color: Colors.grey), + ), + Expanded( + child: Container( + color: Constants.bgColor, + child: ListView.builder( + shrinkWrap: true, + itemCount: data.length, + itemBuilder: (context, index) { + final bs = data[index]; + final status = _getBloodSugarStatus(bs.level, bs.condition); + return Column( + children: [ + ListTile( + leading: SvgPicture.asset( + "assets/images/boldDuotoneMedicinePulse.svg", + width: 20, + height: 20, + ), + title: ExpansionTile( + title:Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( // Wrap RichText with Expanded + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: '${_convertToMMOL(bs.level)}', + style: theme.textTheme.titleMedium! + .copyWith(color: _getStatusColor(status)), + ), + TextSpan( + text: ' mmol/L', + style: theme.textTheme.titleSmall, + ), + ], + ), + ), + ), + Text( + DateFormat('dd MMM yy HH:mm').format( + DateTime.parse(bs.created_at.toString()), + ), + style: theme.textTheme.bodyMedium!.copyWith(color: Colors.grey), + overflow: TextOverflow.ellipsis, // Add ellipsis if text is too long + ), + ], + ), + + children: [ + ListTile( + title: bs.condition != null && bs.condition!.isNotEmpty + ? Text('Condition: ${bs.condition}') + : null, + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Your blood sugar level is ${_getBloodSugarStatus(bs.level, bs.condition)}", style: theme.textTheme.bodyLarge!.copyWith(color: _getStatusColor(status)),), + const SizedBox(height: 10,), + Text('${bs.notes != null && bs.notes!.isNotEmpty ? 'Notes: ${bs.notes}': null}'), + ], + ), + ), + ], + ), + ), + const Divider(), // Add the Divider here + ], + ); + }, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/features/blood_sugar/presentation/widgets/blood_level_picker.dart b/lib/src/features/self_screening/blood_sugar/presentation/widgets/blood_level_picker.dart similarity index 93% rename from lib/src/features/blood_sugar/presentation/widgets/blood_level_picker.dart rename to lib/src/features/self_screening/blood_sugar/presentation/widgets/blood_level_picker.dart index 3e16224a..590e3de6 100644 --- a/lib/src/features/blood_sugar/presentation/widgets/blood_level_picker.dart +++ b/lib/src/features/self_screening/blood_sugar/presentation/widgets/blood_level_picker.dart @@ -1,6 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:nishauri/src/features/blood_sugar/presentation/widgets/blood_level_units_picker.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/presentation/widgets/blood_level_units_picker.dart'; import 'package:nishauri/src/utils/constants.dart'; class BloodLevelPicker extends StatelessWidget { diff --git a/lib/src/features/blood_sugar/presentation/widgets/blood_level_units_picker.dart b/lib/src/features/self_screening/blood_sugar/presentation/widgets/blood_level_units_picker.dart similarity index 100% rename from lib/src/features/blood_sugar/presentation/widgets/blood_level_units_picker.dart rename to lib/src/features/self_screening/blood_sugar/presentation/widgets/blood_level_units_picker.dart diff --git a/lib/src/features/blood_sugar/presentation/widgets/blood_sugar_entry_card.dart b/lib/src/features/self_screening/blood_sugar/presentation/widgets/blood_sugar_entry_card.dart similarity index 85% rename from lib/src/features/blood_sugar/presentation/widgets/blood_sugar_entry_card.dart rename to lib/src/features/self_screening/blood_sugar/presentation/widgets/blood_sugar_entry_card.dart index 42c6d7dd..7b2221f4 100644 --- a/lib/src/features/blood_sugar/presentation/widgets/blood_sugar_entry_card.dart +++ b/lib/src/features/self_screening/blood_sugar/presentation/widgets/blood_sugar_entry_card.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:nishauri/src/features/blood_sugar/data/models/blood_sugar.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/blood_sugar.dart'; class BloodSugarEntryCard extends StatelessWidget { final BloodSugar entry; diff --git a/lib/src/features/self_screening/blood_sugar/presentation/widgets/blood_suger_trend_chart.dart b/lib/src/features/self_screening/blood_sugar/presentation/widgets/blood_suger_trend_chart.dart new file mode 100644 index 00000000..0681a4d1 --- /dev/null +++ b/lib/src/features/self_screening/blood_sugar/presentation/widgets/blood_suger_trend_chart.dart @@ -0,0 +1,56 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/models/filter_bs.dart'; +import 'package:nishauri/src/shared/display/custome_filter_chart.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class BloodSugarTrendChart extends StatelessWidget { + final FilterBs data; + const BloodSugarTrendChart({ + required this.data, + Key? key, + }) : super(key: key); + @override + Widget build(BuildContext context) { + + double _convertToMMOL(double level) { + return level > 30 ? double.parse((level / 18.0).toStringAsFixed(1)) : level; + } + + final List? dataPoint = data.weekly + ?.asMap() + .entries + .map((entry) => FlSpot( + entry.key.toDouble(), + _convertToMMOL(entry.value.level ?? 0), + )).toList(); + final List dateTimeList = data.weekly + ?.asMap() + .entries + .map((entry) => entry.value.dayName ) + .whereType() + .toList() ?? []; + + return Scaffold( + body: Padding( + padding: const EdgeInsets.all(16.0), + child: CustomFilterLineChart( + dataPoints: dataPoint, + dateTimes: dateTimeList, + gradientColors: [ + Constants.bloodSugarColor, + Constants.bloodSugarColor.withOpacity(0.3), + ], + minX: 0, + maxX: dateTimeList.length.toDouble() - 1, + minY: 0.0, + maxY: 30.0, + leftTile: true, + bottomTile: true, + interval: 5, + barColor: Constants.barColor, + ), + ), + ); + } +} diff --git a/lib/src/features/bmi/data/model/bmi_log.dart b/lib/src/features/self_screening/bmi/data/model/bmi_log.dart similarity index 100% rename from lib/src/features/bmi/data/model/bmi_log.dart rename to lib/src/features/self_screening/bmi/data/model/bmi_log.dart diff --git a/lib/src/features/bmi/data/model/bmi_log.freezed.dart b/lib/src/features/self_screening/bmi/data/model/bmi_log.freezed.dart similarity index 100% rename from lib/src/features/bmi/data/model/bmi_log.freezed.dart rename to lib/src/features/self_screening/bmi/data/model/bmi_log.freezed.dart diff --git a/lib/src/features/bmi/data/model/bmi_log.g.dart b/lib/src/features/self_screening/bmi/data/model/bmi_log.g.dart similarity index 100% rename from lib/src/features/bmi/data/model/bmi_log.g.dart rename to lib/src/features/self_screening/bmi/data/model/bmi_log.g.dart diff --git a/lib/src/features/bmi/data/model/bmi_nutrition_maping.dart b/lib/src/features/self_screening/bmi/data/model/bmi_nutrition_maping.dart similarity index 100% rename from lib/src/features/bmi/data/model/bmi_nutrition_maping.dart rename to lib/src/features/self_screening/bmi/data/model/bmi_nutrition_maping.dart diff --git a/lib/src/features/bmi/data/model/bmi_nutrition_maping.freezed.dart b/lib/src/features/self_screening/bmi/data/model/bmi_nutrition_maping.freezed.dart similarity index 100% rename from lib/src/features/bmi/data/model/bmi_nutrition_maping.freezed.dart rename to lib/src/features/self_screening/bmi/data/model/bmi_nutrition_maping.freezed.dart diff --git a/lib/src/features/bmi/data/model/bmi_nutrition_maping.g.dart b/lib/src/features/self_screening/bmi/data/model/bmi_nutrition_maping.g.dart similarity index 100% rename from lib/src/features/bmi/data/model/bmi_nutrition_maping.g.dart rename to lib/src/features/self_screening/bmi/data/model/bmi_nutrition_maping.g.dart diff --git a/lib/src/features/self_screening/bmi/data/model/filter_data.dart b/lib/src/features/self_screening/bmi/data/model/filter_data.dart new file mode 100644 index 00000000..7f03525a --- /dev/null +++ b/lib/src/features/self_screening/bmi/data/model/filter_data.dart @@ -0,0 +1,23 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/model/six_months.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/model/week.dart'; + +part 'filter_data.freezed.dart'; + +@Freezed() +class FilterData with _$FilterData { + + const factory FilterData({ + required List week, + required List sixMonths, + String? user_id, + }) = _FilterData; + + factory FilterData.fromJson(Map json){ + return FilterData( + week: (json['weekly'] as List? ?? []).map((wk) => Week.fromJson(wk)).toList(), + sixMonths: (json['sixMonthly'] as List? ?? []).map((month) => SixMonths.fromJson(month)).toList(), + ); + } +} + diff --git a/lib/src/features/self_screening/bmi/data/model/filter_data.freezed.dart b/lib/src/features/self_screening/bmi/data/model/filter_data.freezed.dart new file mode 100644 index 00000000..1161d4f8 --- /dev/null +++ b/lib/src/features/self_screening/bmi/data/model/filter_data.freezed.dart @@ -0,0 +1,189 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'filter_data.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$FilterData { + List get week => throw _privateConstructorUsedError; + List get sixMonths => throw _privateConstructorUsedError; + String? get user_id => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $FilterDataCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $FilterDataCopyWith<$Res> { + factory $FilterDataCopyWith( + FilterData value, $Res Function(FilterData) then) = + _$FilterDataCopyWithImpl<$Res, FilterData>; + @useResult + $Res call({List week, List sixMonths, String? user_id}); +} + +/// @nodoc +class _$FilterDataCopyWithImpl<$Res, $Val extends FilterData> + implements $FilterDataCopyWith<$Res> { + _$FilterDataCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? week = null, + Object? sixMonths = null, + Object? user_id = freezed, + }) { + return _then(_value.copyWith( + week: null == week + ? _value.week + : week // ignore: cast_nullable_to_non_nullable + as List, + sixMonths: null == sixMonths + ? _value.sixMonths + : sixMonths // ignore: cast_nullable_to_non_nullable + as List, + user_id: freezed == user_id + ? _value.user_id + : user_id // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$FilterDataImplCopyWith<$Res> + implements $FilterDataCopyWith<$Res> { + factory _$$FilterDataImplCopyWith( + _$FilterDataImpl value, $Res Function(_$FilterDataImpl) then) = + __$$FilterDataImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({List week, List sixMonths, String? user_id}); +} + +/// @nodoc +class __$$FilterDataImplCopyWithImpl<$Res> + extends _$FilterDataCopyWithImpl<$Res, _$FilterDataImpl> + implements _$$FilterDataImplCopyWith<$Res> { + __$$FilterDataImplCopyWithImpl( + _$FilterDataImpl _value, $Res Function(_$FilterDataImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? week = null, + Object? sixMonths = null, + Object? user_id = freezed, + }) { + return _then(_$FilterDataImpl( + week: null == week + ? _value._week + : week // ignore: cast_nullable_to_non_nullable + as List, + sixMonths: null == sixMonths + ? _value._sixMonths + : sixMonths // ignore: cast_nullable_to_non_nullable + as List, + user_id: freezed == user_id + ? _value.user_id + : user_id // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$FilterDataImpl implements _FilterData { + const _$FilterDataImpl( + {required final List week, + required final List sixMonths, + this.user_id}) + : _week = week, + _sixMonths = sixMonths; + + final List _week; + @override + List get week { + if (_week is EqualUnmodifiableListView) return _week; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_week); + } + + final List _sixMonths; + @override + List get sixMonths { + if (_sixMonths is EqualUnmodifiableListView) return _sixMonths; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_sixMonths); + } + + @override + final String? user_id; + + @override + String toString() { + return 'FilterData(week: $week, sixMonths: $sixMonths, user_id: $user_id)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$FilterDataImpl && + const DeepCollectionEquality().equals(other._week, _week) && + const DeepCollectionEquality() + .equals(other._sixMonths, _sixMonths) && + (identical(other.user_id, user_id) || other.user_id == user_id)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_week), + const DeepCollectionEquality().hash(_sixMonths), + user_id); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$FilterDataImplCopyWith<_$FilterDataImpl> get copyWith => + __$$FilterDataImplCopyWithImpl<_$FilterDataImpl>(this, _$identity); +} + +abstract class _FilterData implements FilterData { + const factory _FilterData( + {required final List week, + required final List sixMonths, + final String? user_id}) = _$FilterDataImpl; + + @override + List get week; + @override + List get sixMonths; + @override + String? get user_id; + @override + @JsonKey(ignore: true) + _$$FilterDataImplCopyWith<_$FilterDataImpl> get copyWith => + throw _privateConstructorUsedError; +} \ No newline at end of file diff --git a/lib/src/features/self_screening/bmi/data/model/six_months.dart b/lib/src/features/self_screening/bmi/data/model/six_months.dart new file mode 100644 index 00000000..8b1f0ea4 --- /dev/null +++ b/lib/src/features/self_screening/bmi/data/model/six_months.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'six_months.freezed.dart'; +part 'six_months.g.dart'; + +@Freezed() +class SixMonths with _$SixMonths { + + const factory SixMonths({ + String? month, + double? avgWeight, + double? avgHeight, + double? avgResults, + }) = _SixMonths; + + factory SixMonths.fromJson(Map json) + => _$SixMonthsFromJson(json); +} diff --git a/lib/src/features/self_screening/bmi/data/model/six_months.freezed.dart b/lib/src/features/self_screening/bmi/data/model/six_months.freezed.dart new file mode 100644 index 00000000..a72922a6 --- /dev/null +++ b/lib/src/features/self_screening/bmi/data/model/six_months.freezed.dart @@ -0,0 +1,215 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'six_months.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +SixMonths _$SixMonthsFromJson(Map json) { + return _SixMonths.fromJson(json); +} + +/// @nodoc +mixin _$SixMonths { + String? get month => throw _privateConstructorUsedError; + double? get avgWeight => throw _privateConstructorUsedError; + double? get avgHeight => throw _privateConstructorUsedError; + double? get avgResults => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $SixMonthsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SixMonthsCopyWith<$Res> { + factory $SixMonthsCopyWith(SixMonths value, $Res Function(SixMonths) then) = + _$SixMonthsCopyWithImpl<$Res, SixMonths>; + @useResult + $Res call( + {String? month, + double? avgWeight, + double? avgHeight, + double? avgResults}); +} + +/// @nodoc +class _$SixMonthsCopyWithImpl<$Res, $Val extends SixMonths> + implements $SixMonthsCopyWith<$Res> { + _$SixMonthsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? month = freezed, + Object? avgWeight = freezed, + Object? avgHeight = freezed, + Object? avgResults = freezed, + }) { + return _then(_value.copyWith( + month: freezed == month + ? _value.month + : month // ignore: cast_nullable_to_non_nullable + as String?, + avgWeight: freezed == avgWeight + ? _value.avgWeight + : avgWeight // ignore: cast_nullable_to_non_nullable + as double?, + avgHeight: freezed == avgHeight + ? _value.avgHeight + : avgHeight // ignore: cast_nullable_to_non_nullable + as double?, + avgResults: freezed == avgResults + ? _value.avgResults + : avgResults // ignore: cast_nullable_to_non_nullable + as double?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SixMonthsImplCopyWith<$Res> + implements $SixMonthsCopyWith<$Res> { + factory _$$SixMonthsImplCopyWith( + _$SixMonthsImpl value, $Res Function(_$SixMonthsImpl) then) = + __$$SixMonthsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String? month, + double? avgWeight, + double? avgHeight, + double? avgResults}); +} + +/// @nodoc +class __$$SixMonthsImplCopyWithImpl<$Res> + extends _$SixMonthsCopyWithImpl<$Res, _$SixMonthsImpl> + implements _$$SixMonthsImplCopyWith<$Res> { + __$$SixMonthsImplCopyWithImpl( + _$SixMonthsImpl _value, $Res Function(_$SixMonthsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? month = freezed, + Object? avgWeight = freezed, + Object? avgHeight = freezed, + Object? avgResults = freezed, + }) { + return _then(_$SixMonthsImpl( + month: freezed == month + ? _value.month + : month // ignore: cast_nullable_to_non_nullable + as String?, + avgWeight: freezed == avgWeight + ? _value.avgWeight + : avgWeight // ignore: cast_nullable_to_non_nullable + as double?, + avgHeight: freezed == avgHeight + ? _value.avgHeight + : avgHeight // ignore: cast_nullable_to_non_nullable + as double?, + avgResults: freezed == avgResults + ? _value.avgResults + : avgResults // ignore: cast_nullable_to_non_nullable + as double?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SixMonthsImpl implements _SixMonths { + const _$SixMonthsImpl( + {this.month, this.avgWeight, this.avgHeight, this.avgResults}); + + factory _$SixMonthsImpl.fromJson(Map json) => + _$$SixMonthsImplFromJson(json); + + @override + final String? month; + @override + final double? avgWeight; + @override + final double? avgHeight; + @override + final double? avgResults; + + @override + String toString() { + return 'SixMonths(month: $month, avgWeight: $avgWeight, avgHeight: $avgHeight, avgResults: $avgResults)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SixMonthsImpl && + (identical(other.month, month) || other.month == month) && + (identical(other.avgWeight, avgWeight) || + other.avgWeight == avgWeight) && + (identical(other.avgHeight, avgHeight) || + other.avgHeight == avgHeight) && + (identical(other.avgResults, avgResults) || + other.avgResults == avgResults)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, month, avgWeight, avgHeight, avgResults); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$SixMonthsImplCopyWith<_$SixMonthsImpl> get copyWith => + __$$SixMonthsImplCopyWithImpl<_$SixMonthsImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SixMonthsImplToJson( + this, + ); + } +} + +abstract class _SixMonths implements SixMonths { + const factory _SixMonths( + {final String? month, + final double? avgWeight, + final double? avgHeight, + final double? avgResults}) = _$SixMonthsImpl; + + factory _SixMonths.fromJson(Map json) = + _$SixMonthsImpl.fromJson; + + @override + String? get month; + @override + double? get avgWeight; + @override + double? get avgHeight; + @override + double? get avgResults; + @override + @JsonKey(ignore: true) + _$$SixMonthsImplCopyWith<_$SixMonthsImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/self_screening/bmi/data/model/six_months.g.dart b/lib/src/features/self_screening/bmi/data/model/six_months.g.dart new file mode 100644 index 00000000..f611534c --- /dev/null +++ b/lib/src/features/self_screening/bmi/data/model/six_months.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'six_months.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$SixMonthsImpl _$$SixMonthsImplFromJson(Map json) => + _$SixMonthsImpl( + month: json['month'] as String?, + avgWeight: (json['avgWeight'] as num?)?.toDouble(), + avgHeight: (json['avgHeight'] as num?)?.toDouble(), + avgResults: (json['avgResults'] as num?)?.toDouble(), + ); + +Map _$$SixMonthsImplToJson(_$SixMonthsImpl instance) => + { + 'month': instance.month, + 'avgWeight': instance.avgWeight, + 'avgHeight': instance.avgHeight, + 'avgResults': instance.avgResults, + }; diff --git a/lib/src/features/self_screening/bmi/data/model/week.dart b/lib/src/features/self_screening/bmi/data/model/week.dart new file mode 100644 index 00000000..cf09a0b1 --- /dev/null +++ b/lib/src/features/self_screening/bmi/data/model/week.dart @@ -0,0 +1,19 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'week.freezed.dart'; +part 'week.g.dart'; + +@Freezed() +class Week with _$Week { + + const factory Week({ + String? dayName, + String? date, + double? weight, + double? height, + double? results, + }) = _Week; + + factory Week.fromJson(Map json) + => _$WeekFromJson(json); +} diff --git a/lib/src/features/self_screening/bmi/data/model/week.freezed.dart b/lib/src/features/self_screening/bmi/data/model/week.freezed.dart new file mode 100644 index 00000000..10b7d6c0 --- /dev/null +++ b/lib/src/features/self_screening/bmi/data/model/week.freezed.dart @@ -0,0 +1,227 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'week.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Week _$WeekFromJson(Map json) { + return _Week.fromJson(json); +} + +/// @nodoc +mixin _$Week { + String? get dayName => throw _privateConstructorUsedError; + String? get date => throw _privateConstructorUsedError; + double? get weight => throw _privateConstructorUsedError; + double? get height => throw _privateConstructorUsedError; + double? get results => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $WeekCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $WeekCopyWith<$Res> { + factory $WeekCopyWith(Week value, $Res Function(Week) then) = + _$WeekCopyWithImpl<$Res, Week>; + @useResult + $Res call( + {String? dayName, + String? date, + double? weight, + double? height, + double? results}); +} + +/// @nodoc +class _$WeekCopyWithImpl<$Res, $Val extends Week> + implements $WeekCopyWith<$Res> { + _$WeekCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? dayName = freezed, + Object? date = freezed, + Object? weight = freezed, + Object? height = freezed, + Object? results = freezed, + }) { + return _then(_value.copyWith( + dayName: freezed == dayName + ? _value.dayName + : dayName // ignore: cast_nullable_to_non_nullable + as String?, + date: freezed == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as String?, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double?, + height: freezed == height + ? _value.height + : height // ignore: cast_nullable_to_non_nullable + as double?, + results: freezed == results + ? _value.results + : results // ignore: cast_nullable_to_non_nullable + as double?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$WeekImplCopyWith<$Res> implements $WeekCopyWith<$Res> { + factory _$$WeekImplCopyWith( + _$WeekImpl value, $Res Function(_$WeekImpl) then) = + __$$WeekImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String? dayName, + String? date, + double? weight, + double? height, + double? results}); +} + +/// @nodoc +class __$$WeekImplCopyWithImpl<$Res> + extends _$WeekCopyWithImpl<$Res, _$WeekImpl> + implements _$$WeekImplCopyWith<$Res> { + __$$WeekImplCopyWithImpl(_$WeekImpl _value, $Res Function(_$WeekImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? dayName = freezed, + Object? date = freezed, + Object? weight = freezed, + Object? height = freezed, + Object? results = freezed, + }) { + return _then(_$WeekImpl( + dayName: freezed == dayName + ? _value.dayName + : dayName // ignore: cast_nullable_to_non_nullable + as String?, + date: freezed == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as String?, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as double?, + height: freezed == height + ? _value.height + : height // ignore: cast_nullable_to_non_nullable + as double?, + results: freezed == results + ? _value.results + : results // ignore: cast_nullable_to_non_nullable + as double?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$WeekImpl implements _Week { + const _$WeekImpl( + {this.dayName, this.date, this.weight, this.height, this.results}); + + factory _$WeekImpl.fromJson(Map json) => + _$$WeekImplFromJson(json); + + @override + final String? dayName; + @override + final String? date; + @override + final double? weight; + @override + final double? height; + @override + final double? results; + + @override + String toString() { + return 'Week(dayName: $dayName, date: $date, weight: $weight, height: $height, results: $results)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$WeekImpl && + (identical(other.dayName, dayName) || other.dayName == dayName) && + (identical(other.date, date) || other.date == date) && + (identical(other.weight, weight) || other.weight == weight) && + (identical(other.height, height) || other.height == height) && + (identical(other.results, results) || other.results == results)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, dayName, date, weight, height, results); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$WeekImplCopyWith<_$WeekImpl> get copyWith => + __$$WeekImplCopyWithImpl<_$WeekImpl>(this, _$identity); + + @override + Map toJson() { + return _$$WeekImplToJson( + this, + ); + } +} + +abstract class _Week implements Week { + const factory _Week( + {final String? dayName, + final String? date, + final double? weight, + final double? height, + final double? results}) = _$WeekImpl; + + factory _Week.fromJson(Map json) = _$WeekImpl.fromJson; + + @override + String? get dayName; + @override + String? get date; + @override + double? get weight; + @override + double? get height; + @override + double? get results; + @override + @JsonKey(ignore: true) + _$$WeekImplCopyWith<_$WeekImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/self_screening/bmi/data/model/week.g.dart b/lib/src/features/self_screening/bmi/data/model/week.g.dart new file mode 100644 index 00000000..cf970fdc --- /dev/null +++ b/lib/src/features/self_screening/bmi/data/model/week.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'week.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$WeekImpl _$$WeekImplFromJson(Map json) => _$WeekImpl( + dayName: json['dayName'] as String?, + date: json['date'] as String?, + weight: (json['weight'] as num?)?.toDouble(), + height: (json['height'] as num?)?.toDouble(), + results: (json['results'] as num?)?.toDouble(), + ); + +Map _$$WeekImplToJson(_$WeekImpl instance) => + { + 'dayName': instance.dayName, + 'date': instance.date, + 'weight': instance.weight, + 'height': instance.height, + 'results': instance.results, + }; diff --git a/lib/src/features/self_screening/bmi/data/providers/bmi_filter_provider.dart b/lib/src/features/self_screening/bmi/data/providers/bmi_filter_provider.dart new file mode 100644 index 00000000..4ff52dd5 --- /dev/null +++ b/lib/src/features/self_screening/bmi/data/providers/bmi_filter_provider.dart @@ -0,0 +1,13 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/model/filter_data.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/repositories/bmi_filter_repository.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/services/bmi_filter_service.dart'; + +final bmiFilterRepositoryProvider = Provider((ref) { + return BMIFilterRepository(BMIFilterService()); +}); + +final bmiFilterProvider = FutureProvider((ref) async { + final repository = ref.watch(bmiFilterRepositoryProvider); + return await repository.fetchBMIFilter(); +}); \ No newline at end of file diff --git a/lib/src/features/bmi/data/providers/bmi_log_provider.dart b/lib/src/features/self_screening/bmi/data/providers/bmi_log_provider.dart similarity index 58% rename from lib/src/features/bmi/data/providers/bmi_log_provider.dart rename to lib/src/features/self_screening/bmi/data/providers/bmi_log_provider.dart index 03f0d411..faa9a387 100644 --- a/lib/src/features/bmi/data/providers/bmi_log_provider.dart +++ b/lib/src/features/self_screening/bmi/data/providers/bmi_log_provider.dart @@ -1,8 +1,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:nishauri/src/features/bmi/data/model/bmi_log.dart'; -import 'package:nishauri/src/features/bmi/data/repositories/bmi_log_repository.dart'; -import 'package:nishauri/src/features/bmi/data/services/bmi_log_service.dart'; -import 'package:nishauri/src/features/bmi/presentation/controllers/bmi_log_controller.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/model/bmi_log.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/repositories/bmi_log_repository.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/services/bmi_log_service.dart'; +import 'package:nishauri/src/features/self_screening/bmi/presentation/controllers/bmi_log_controller.dart'; final bmiLogProvider = StateNotifierProvider>((ref) { final service = BMILogService(); diff --git a/lib/src/features/bmi/data/providers/bmi_status_nutrition_provider.dart b/lib/src/features/self_screening/bmi/data/providers/bmi_status_nutrition_provider.dart similarity index 53% rename from lib/src/features/bmi/data/providers/bmi_status_nutrition_provider.dart rename to lib/src/features/self_screening/bmi/data/providers/bmi_status_nutrition_provider.dart index 2e2434fd..0a3ecbd0 100644 --- a/lib/src/features/bmi/data/providers/bmi_status_nutrition_provider.dart +++ b/lib/src/features/self_screening/bmi/data/providers/bmi_status_nutrition_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:nishauri/src/features/bmi/data/repositories/bmi_status_nutrition_repository.dart'; -import 'package:nishauri/src/features/bmi/data/services/bmi_calculator_service.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/repositories/bmi_status_nutrition_repository.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/services/bmi_calculator_service.dart'; final bmiNutritionProvider = FutureProvider((ref)async { final repository = BMIStatusNutritionRepository(BMICalculatorService()); diff --git a/lib/src/features/self_screening/bmi/data/repositories/bmi_filter_repository.dart b/lib/src/features/self_screening/bmi/data/repositories/bmi_filter_repository.dart new file mode 100644 index 00000000..b8256333 --- /dev/null +++ b/lib/src/features/self_screening/bmi/data/repositories/bmi_filter_repository.dart @@ -0,0 +1,14 @@ +import 'package:nishauri/src/features/self_screening/bmi/data/model/bmi_log.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/model/filter_data.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/services/bmi_filter_service.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/services/bmi_log_service.dart'; + +class BMIFilterRepository { + final BMIFilterService _service; + + BMIFilterRepository(this._service); + + Future fetchBMIFilter() async { + return await _service.fetchBMIFilter(); + } +} diff --git a/lib/src/features/bmi/data/repositories/bmi_log_repository.dart b/lib/src/features/self_screening/bmi/data/repositories/bmi_log_repository.dart similarity index 61% rename from lib/src/features/bmi/data/repositories/bmi_log_repository.dart rename to lib/src/features/self_screening/bmi/data/repositories/bmi_log_repository.dart index b0e94d26..94a50295 100644 --- a/lib/src/features/bmi/data/repositories/bmi_log_repository.dart +++ b/lib/src/features/self_screening/bmi/data/repositories/bmi_log_repository.dart @@ -1,5 +1,5 @@ -import 'package:nishauri/src/features/bmi/data/model/bmi_log.dart'; -import 'package:nishauri/src/features/bmi/data/services/bmi_log_service.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/model/bmi_log.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/services/bmi_log_service.dart'; class BMILogRepository { final BMILogService _service; diff --git a/lib/src/features/bmi/data/repositories/bmi_status_nutrition_repository.dart b/lib/src/features/self_screening/bmi/data/repositories/bmi_status_nutrition_repository.dart similarity index 59% rename from lib/src/features/bmi/data/repositories/bmi_status_nutrition_repository.dart rename to lib/src/features/self_screening/bmi/data/repositories/bmi_status_nutrition_repository.dart index ba2742df..b7bf0cdd 100644 --- a/lib/src/features/bmi/data/repositories/bmi_status_nutrition_repository.dart +++ b/lib/src/features/self_screening/bmi/data/repositories/bmi_status_nutrition_repository.dart @@ -1,5 +1,5 @@ -import 'package:nishauri/src/features/bmi/data/model/bmi_nutrition_maping.dart'; -import 'package:nishauri/src/features/bmi/data/services/bmi_calculator_service.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/model/bmi_nutrition_maping.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/services/bmi_calculator_service.dart'; class BMIStatusNutritionRepository { final BMICalculatorService _service; diff --git a/lib/src/features/bmi/data/services/bmi_calculator_service.dart b/lib/src/features/self_screening/bmi/data/services/bmi_calculator_service.dart similarity index 89% rename from lib/src/features/bmi/data/services/bmi_calculator_service.dart rename to lib/src/features/self_screening/bmi/data/services/bmi_calculator_service.dart index a58c05d6..ee04bd67 100644 --- a/lib/src/features/bmi/data/services/bmi_calculator_service.dart +++ b/lib/src/features/self_screening/bmi/data/services/bmi_calculator_service.dart @@ -2,11 +2,10 @@ import 'dart:convert'; import 'dart:developer'; import 'package:http/http.dart'; -import 'package:nishauri/src/features/bmi/data/model/bmi_nutrition_maping.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/model/bmi_nutrition_maping.dart'; import 'package:nishauri/src/shared/exeptions/http_exceptions.dart'; import 'package:nishauri/src/shared/interfaces/HTTPService.dart'; - -import '../../../../utils/constants.dart'; +import 'package:nishauri/src/utils/constants.dart'; class BMICalculatorService extends HTTPService { Future getBMIStatusNutrition_(dynamic args) async { diff --git a/lib/src/features/self_screening/bmi/data/services/bmi_filter_service.dart b/lib/src/features/self_screening/bmi/data/services/bmi_filter_service.dart new file mode 100644 index 00000000..9ae9c53c --- /dev/null +++ b/lib/src/features/self_screening/bmi/data/services/bmi_filter_service.dart @@ -0,0 +1,46 @@ +import 'dart:convert'; +import 'package:http/http.dart'; +import 'package:nishauri/src/features/auth/data/respositories/auth_repository.dart'; +import 'package:nishauri/src/features/auth/data/services/AuthApiService.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/model/filter_data.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/model/six_months.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/model/week.dart'; +import 'package:nishauri/src/shared/interfaces/HTTPService.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class BMIFilterService extends HTTPService{ + final AuthRepository _repository = AuthRepository(AuthApiService()); + + Future fetchBMIFilter_(dynamic args) async { + final id = await _repository.getUserId(); + final tokenPair = await getCachedToken(); + var headers = {'Authorization': 'Bearer ${tokenPair.accessToken}'}; + var url = '${Constants.BASE_URL_NEW}get_bmi_filter?user_id=$id'; + final response = request( + url: url, + token: tokenPair, + method: 'GET', + requestHeaders: headers, + userId: id); + return response; + } + + Future fetchBMIFilter() async { + final response = await call(fetchBMIFilter_, null); + + if (response.statusCode == 200) { + final responseString = await response.stream.bytesToString(); + final Map responseData = json.decode(responseString); + + if (responseData["success"] == true) { + final Map data = responseData["data"]; + return FilterData.fromJson(data); + } else { + throw Exception(responseData["message"]); + } + } else { + throw Exception("Failed to fetch data! Status code: ${response.statusCode}"); + } + } + +} diff --git a/lib/src/features/bmi/data/services/bmi_log_service.dart b/lib/src/features/self_screening/bmi/data/services/bmi_log_service.dart similarity index 93% rename from lib/src/features/bmi/data/services/bmi_log_service.dart rename to lib/src/features/self_screening/bmi/data/services/bmi_log_service.dart index c7965871..a70922b7 100644 --- a/lib/src/features/bmi/data/services/bmi_log_service.dart +++ b/lib/src/features/self_screening/bmi/data/services/bmi_log_service.dart @@ -1,11 +1,10 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; import 'package:http/http.dart'; import 'package:nishauri/src/features/auth/data/respositories/auth_repository.dart'; import 'package:nishauri/src/features/auth/data/services/AuthApiService.dart'; -import 'package:nishauri/src/features/bmi/data/model/bmi_log.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/model/bmi_log.dart'; import 'package:nishauri/src/shared/interfaces/HTTPService.dart'; import 'package:nishauri/src/utils/constants.dart'; @@ -82,7 +81,6 @@ class BMILogService extends HTTPService{ final response = await call(fetchBMI_, null); if (response.statusCode == 200) { final responseString = await response.stream.bytesToString(); - // final String responseString = await rootBundle.loadString('assets/data/bmi_log.json'); final Map responseData = json.decode(responseString); if (responseData["success"] == true){ final List jsonList = responseData["data"]["bmi_log"]; diff --git a/lib/src/features/bmi/onboardng_step.dart b/lib/src/features/self_screening/bmi/onboardng_step.dart similarity index 100% rename from lib/src/features/bmi/onboardng_step.dart rename to lib/src/features/self_screening/bmi/onboardng_step.dart diff --git a/lib/src/features/bmi/presentation/controllers/bmi_log_controller.dart b/lib/src/features/self_screening/bmi/presentation/controllers/bmi_log_controller.dart similarity index 81% rename from lib/src/features/bmi/presentation/controllers/bmi_log_controller.dart rename to lib/src/features/self_screening/bmi/presentation/controllers/bmi_log_controller.dart index b40a9382..b1a6df70 100644 --- a/lib/src/features/bmi/presentation/controllers/bmi_log_controller.dart +++ b/lib/src/features/self_screening/bmi/presentation/controllers/bmi_log_controller.dart @@ -1,7 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:nishauri/src/features/bmi/data/model/bmi_log.dart'; -import 'package:nishauri/src/features/bmi/data/repositories/bmi_log_repository.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/repositories/bmi_log_repository.dart'; class BMILogController extends StateNotifier> { final BMILogRepository _repository; diff --git a/lib/src/features/self_screening/bmi/presentation/pages/BMICalculatorResultsScreen.dart b/lib/src/features/self_screening/bmi/presentation/pages/BMICalculatorResultsScreen.dart new file mode 100644 index 00000000..ed18b0f8 --- /dev/null +++ b/lib/src/features/self_screening/bmi/presentation/pages/BMICalculatorResultsScreen.dart @@ -0,0 +1,283 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/providers/bmi_filter_provider.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/providers/bmi_log_provider.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/providers/bmi_status_nutrition_provider.dart'; +import 'package:nishauri/src/features/self_screening/bmi/presentation/widgets/BMILineGraph.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/daily_card.dart'; +import 'package:nishauri/src/shared/providers/selectedIndexProvider.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +class BMICalculatorResultsScreen extends HookConsumerWidget { + final double? otherBMI; + final bool? isForSelf; + const BMICalculatorResultsScreen({super.key, this.otherBMI, this.isForSelf}); + + // Function to determine BMI category + String getBMICategory(double bmi) { + if (bmi < 18.5) { + return 'Malnutrition'; + } else if (bmi >= 18.5 && bmi < 24.9) { + return 'Normal'; + } else { + return 'Obese'; + } + } + + // Function to get color for the BMI segment + Color getSliderColor(double bmi) { + if (bmi < 18.5) { + return Colors.blue; + } else if (bmi < 24.9) { + return Colors.green; + } else { + return Colors.red; + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = Theme.of(context); + final bmiStatusNutritionAsync = ref.watch(bmiNutritionProvider); + final bmiListAsync = ref.watch(bmiListProvider); + final bmiFilter = ref.watch(bmiFilterProvider); + final selectedIndex = ref.watch(selectedIndexProvider); + + final filter = ["Week", "6 Months"]; + + final currentBMIEntries = bmiListAsync.when( + data: (data) { + data.sort((a, b) => b.created_at.compareTo(a.created_at)); + return data.first; + }, + error: (error, _) { + return null; + }, + loading: () { + return null; + }, + ); + + final bmi = otherBMI != null ? otherBMI : currentBMIEntries?.results; + final bmiCategory = getBMICategory(bmi!); + final sliderColor = getSliderColor(bmi); + + return Scaffold( + body: Column(children: [ + const CustomAppBar( + title: "BMI Calculator ⚖️", + color: Constants.selfScreeningBgColor, + subTitle: "Empower Your Health Journey with BMI Insights", + svgPathGroup: "assets/images/group_clinic_card.svg", + ), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + spacing: Constants.SIXTEEN, + runSpacing: Constants.SIXTEEN, + children: [ + FilterCard( + columnTitles: filter, + onPressed: (index) { + ref.read(selectedIndexProvider.notifier).state = index; + } + ), + ], + ), + const SizedBox(height: Constants.SPACING,), + Text( + "Results ${isForSelf != true ? ' for others' : ''}", + style: theme.textTheme.headlineLarge?.copyWith( + color: Constants.selfScreeningBgColor, + ), + ), + Card( + child: Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Your BMI is", style: theme.textTheme.titleMedium), + Text( + bmiCategory, + style: theme.textTheme.titleMedium?.copyWith( + color: sliderColor + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: Text( + bmi.toStringAsFixed(1), + style: theme.textTheme.titleLarge + ?.copyWith(color: sliderColor, fontWeight: FontWeight.bold), + ), + ), + Container( + height: Constants.TWENTY, + child: Stack( + alignment: Alignment.center, + children: [ + // Colored track + Positioned.fill( + child: Container( + decoration: BoxDecoration( + gradient: const LinearGradient( + colors: [ + Colors.blue, + Colors.green, + Colors.red, + ], + stops: [0.0, 0.80, 1.0], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ), + borderRadius: BorderRadius.circular(5), + ), + ), + ), + // Slider + Slider( + value: bmi, + onChanged: (value) {}, + min: 0, + max: 30, + activeColor: Colors.transparent, + inactiveColor: Colors.transparent, + ), + ], + ), + ), + ], + ), + ), + ), + const SizedBox(height: Constants.SPACING), + bmiStatusNutritionAsync.when( + data: (data) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: Constants.SPACING), + if (isForSelf == true) + SizedBox( + height: Constants.GRAPH_HEIGHT, + child: Padding( + padding: EdgeInsets.all(8.0), + child: Column( + children: [ + Expanded( + child: bmiFilter.when( + data: (bmiData) { + return BMILineGraph(data: bmiData, filter: filter[selectedIndex],); + }, + + loading: () => Center(child: CircularProgressIndicator()), + error: (error, _) => Center(child: Text("No BMI Data $error")), + ), + ), + ], + ), + ), + ), + const SizedBox(height: Constants.SPACING), + ExpansionTile( + collapsedBackgroundColor: Constants.bgColor, + backgroundColor: Constants.bgColor, + collapsedIconColor: sliderColor, + title: Text( + "Diet & Nutrition", + style: theme.textTheme.titleLarge?.copyWith( + color: Constants.labResultsColor, + ), + ), + children: [ + const SizedBox(height: Constants.SPACING), + Text( + ' Your BMI is ($bmiCategory)', + style: theme.textTheme.titleLarge?.copyWith( + color: sliderColor, fontWeight: FontWeight.bold + ), + ), + const SizedBox(height: Constants.SPACING), + Markdown( + data: data + .where((element) => + element.status == bmiCategory) + .first + .description ?? + "", + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + ), + ], + ), + const SizedBox(height: Constants.SPACING), + Card( + color: Constants.bgColor, + child: ListTile( + title: Text("Show All Data", style: theme.textTheme.titleSmall!.copyWith(fontWeight: FontWeight.bold),), + trailing: const Icon(Icons.arrow_forward_ios_outlined), + onTap: (){ + ref.refresh(bmiListProvider); + context.goNamed(RouteNames.BMI_HISTORY, extra: data); + }, + ), + ), + ], + ), + error: (e, stackTrace) => Align( + alignment: Alignment.center, child: Text(e.toString())), + loading: () => const Align( + alignment: Alignment.center, + child: CircularProgressIndicator(), + ), + ), + const SizedBox(height: Constants.SPACING), + ], + ), + ), + ), + ), + ] + ), + floatingActionButton: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + width: Constants.TWO_HUNDRED, + child: FloatingActionButton( + backgroundColor: Constants.selfScreeningBgColor, + onPressed: () { + context.goNamed(RouteNames.BMI_CALCULATOR); + }, + heroTag: null, + elevation: Constants.SMALL_SPACING, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: Constants.SMALL_SPACING), + child: Text( + "Calculate BMI", + style: theme.textTheme.bodyLarge?.copyWith(color: Colors.white) + ), + ), + ), + ), + ], + ), + + + // bottomNavigationBar: , + ); + } +} diff --git a/lib/src/features/self_screening/bmi/presentation/pages/BMICalculatorScreen.dart b/lib/src/features/self_screening/bmi/presentation/pages/BMICalculatorScreen.dart new file mode 100644 index 00000000..b1f540db --- /dev/null +++ b/lib/src/features/self_screening/bmi/presentation/pages/BMICalculatorScreen.dart @@ -0,0 +1,270 @@ +import 'dart:developer'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nishauri/src/features/auth/data/respositories/auth_repository.dart'; +import 'package:nishauri/src/features/auth/data/services/AuthApiService.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/providers/bmi_filter_provider.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/providers/bmi_log_provider.dart'; +import 'package:nishauri/src/features/self_screening/bmi/presentation/widgets/GenderPicker.dart'; +import 'package:nishauri/src/features/self_screening/bmi/presentation/widgets/HeightPicker.dart'; +import 'package:nishauri/src/features/self_screening/bmi/presentation/widgets/HeightUnitsPicker.dart'; +import 'package:nishauri/src/features/user/data/providers/user_provider.dart'; +import 'package:nishauri/src/shared/display/AppCard.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/RadioGroup.dart'; +import 'package:nishauri/src/shared/input/Button.dart'; +import 'package:nishauri/src/shared/input/QuanterSizer.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/helpers.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +class BMICalculatorScreen extends HookConsumerWidget { + const BMICalculatorScreen({super.key}); + + Future _fetchAge(WidgetRef ref) async { + try { + final authRepository = AuthRepository(AuthApiService()); + String ageString = await authRepository.getAge(); + int age = int.parse(ageString); + return age; + } catch (e) { + print("An error occurred while fetching the age: $e"); + final userAsync = ref.watch(userProvider); + final age = userAsync.when( + data: (user) { + final birthYear = int.parse(user.dateOfBirth!.split('-')[0]); + return DateTime.now().year - birthYear; + }, + loading: () => null, + error: (error, stack) { + print("Error fetching user data: $error"); + return null; + }, + ); + return age; + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = Theme.of(context); + const activeColor = Constants.selfScreeningBgColor; + final gender = useState(GenderPickerChoices.male); + final isPregnant = useState(false); + final height = useState(180); + final heightUnits = useState(HeightUnitsPickerOptions.In); + final weight = useState(65); + final isForSelf = useState(true); + final userAge = useState(18); + + useEffect(() { + _fetchAge(ref).then((fetchedAge) { + userAge.value = fetchedAge!; + }); + return null; + }, [ref]); + + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + title: "BMI Calculator ⚖️", + subTitle: "Empower Your Health Journey \nWith BMI Insights", + color: Constants.selfScreeningBgColor, + svgPathGroup: "assets/images/group_clinic_card.svg", + ), + Expanded( + child: SingleChildScrollView( + child: AppCard( + child: Padding( + padding: const EdgeInsets.all(Constants.SPACING * 0.5), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Card( + child: Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "BMI for: ", + style: theme.textTheme.titleMedium, + ), + ToggleButtons( + isSelected: [isForSelf.value, !isForSelf.value], + onPressed: (index) { + isForSelf.value = index == 0; + print(isForSelf.value); + if (isForSelf.value) { + _fetchAge(ref).then((fetchedAge) { + userAge.value = fetchedAge!; + }); + } + }, + selectedColor: Colors.white, + fillColor: activeColor, + children: const [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: Text("Myself"), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: Text("Other"), + ), + ], + ), + ], + ), + ], + ), + ), + ), + const SizedBox(height: Constants.SPACING), + Text( + "Choose your gender", + style: theme.textTheme.titleMedium, + ), + const SizedBox(height: Constants.SPACING), + if (!isForSelf.value) + GenderPicker( + gender: gender.value, + onGenderChange: (gender_) { + if (gender_ == GenderPickerChoices.female) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text("Warning!"), + content: SingleChildScrollView( + child: Wrap( + children: [ + const Text( + "BMI Calculation for pregnant ladies is highly discouraged and not supported to avoid drastic decisions. Please confirm your pregnancy status.", + ), + RadioGroup( + onValueChanged: (val) { + Navigator.of(context).pop(val == "no"); + }, + items: [ + RadioGroupItem( + value: "no", + title: "Not Pregnant", + icon: Icons.woman_rounded, + ), + RadioGroupItem( + value: "yes", + title: "Pregnant", + icon: Icons.pregnant_woman, + ), + ], + ), + ], + ), + ), + ), + ).then((isPregnant_) { + if (isPregnant_ != null) { + isPregnant.value = !isPregnant_; + gender.value = gender_; + if (!isPregnant_) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + "BMI calculation for pregnant ladies isn't supported", + ), + ), + ); + } + } + }); + } else { + gender.value = gender_; + } + }, + activeColor: activeColor, + ) + else + GenderPicker( + gender: gender.value, + onGenderChange: (gender) {}, + isEnabled: isForSelf.value, + ), + const SizedBox(height: Constants.SPACING), + HeightPicker( + activeColor: activeColor, + height: height.value, + heightUnits: heightUnits.value, + onHeightChange: (height_) { + height.value = height_; + }, + onHeightUnitsChange: (units) { + heightUnits.value = units; + }, + ), + const SizedBox(height: Constants.SPACING), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Quantizer( + min: 20, + max: 300, + value: weight.value, + onValueChange: (value) => weight.value = value, + label: "Weight", + units: "Kgs", + activeColor: activeColor, + ), + Quantizer( + min: 5, + max: 100, + value: userAge.value, + onValueChange: isForSelf.value ? (_) {} : (value) => userAge.value = value, + label: "Age", + units: "Years", + activeColor: activeColor, + ), + ], + ), + const SizedBox(height: Constants.SPACING), + Button( + title: "Calculate", + surfixIcon: SvgPicture.asset( + "assets/images/refresh-circle.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + ), + disabled: isPregnant.value, + backgroundColor: activeColor, + textColor: theme.canvasColor, + onPress: () { + final bmi = calculateBMI(height.value, weight.value); + if (isForSelf.value) { + ref.read(bmiLogProvider.notifier) + .logBMI(height.value.toString(), weight.value.toString(), bmi.toString()) + .then((_) { + context.goNamed(RouteNames.BMI_CALCULATOR_RESULTS, extra: {"bmi" : bmi, "others" : isForSelf.value}); + }); + ref.refresh(bmiListProvider); + ref.refresh(bmiFilterProvider); + } else { + context.goNamed(RouteNames.BMI_CALCULATOR_RESULTS, extra: {"bmi" : bmi, "others" : isForSelf.value}); + } + }, + ), + ], + ), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/features/bmi/presentation/pages/BMIHistoryScreen.dart b/lib/src/features/self_screening/bmi/presentation/pages/BMIHistoryScreen.dart similarity index 66% rename from lib/src/features/bmi/presentation/pages/BMIHistoryScreen.dart rename to lib/src/features/self_screening/bmi/presentation/pages/BMIHistoryScreen.dart index 8814f200..47bd0544 100644 --- a/lib/src/features/bmi/presentation/pages/BMIHistoryScreen.dart +++ b/lib/src/features/self_screening/bmi/presentation/pages/BMIHistoryScreen.dart @@ -2,12 +2,13 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:nishauri/src/features/bmi/data/providers/bmi_log_provider.dart'; -import 'package:nishauri/src/features/bmi/presentation/widgets/BMILineGraph.dart'; -import 'package:nishauri/src/features/bmi/presentation/widgets/BMILineList.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/providers/bmi_log_provider.dart'; +import 'package:nishauri/src/features/self_screening/bmi/presentation/widgets/BMILineGraph.dart'; +import 'package:nishauri/src/features/self_screening/bmi/presentation/widgets/BMILineList.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/shared/display/background_image_widget.dart'; import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/routes.dart'; class BMIHistoryScreen extends HookWidget { @override @@ -24,9 +25,11 @@ class BMIHistoryScreen extends HookWidget { return Column( children: [ const CustomAppBar( - title: "BMI Monitor 📈", - // icon: Icons.trending_up, - color: Constants.bmiCalculatorColor, + smallTitle: "All Record Data", + height: Constants.SMALL_APP_BAR_HEIGHT, + rightBtTitle: "Record BMI", + path: RouteNames.BMI_CALCULATOR, + color: Constants.selfScreeningBgColor, ), Expanded( child: Center( @@ -38,10 +41,10 @@ class BMIHistoryScreen extends HookWidget { flex: 1, child: BMILinelist(data: data), ), - Expanded( - flex: 1, - child: BMILineGraph(data: displayedData), - ), + // Expanded( + // flex: 1, + // child: BMILineGraph(data: displayedData), + // ), ], ), ), @@ -53,11 +56,13 @@ class BMIHistoryScreen extends HookWidget { loading: () => const Center(child: CircularProgressIndicator()), error: (error, _) => BackgroundImageWidget( customAppBar: const CustomAppBar( - title: "BMI Monitor 📈", + smallTitle: "All Record Data", + height: Constants.SMALL_APP_BAR_HEIGHT, + rightBtTitle: "Record BMI", // icon: Icons.trending_up, - color: Constants.bmiCalculatorColor, + color: Constants.selfScreeningBgColor, ), - svgImage: 'assets/images/lab-empty-state.svg', + svgImage: 'assets/images/lab-eBLOOD_SUGAR_INPUTmpty-state.svg', notFoundText: "No BMI Data", floatingButtonIcon1: Icons.refresh, floatingButtonAction1: () { diff --git a/lib/src/features/self_screening/bmi/presentation/widgets/BMILineGraph.dart b/lib/src/features/self_screening/bmi/presentation/widgets/BMILineGraph.dart new file mode 100644 index 00000000..874a59c4 --- /dev/null +++ b/lib/src/features/self_screening/bmi/presentation/widgets/BMILineGraph.dart @@ -0,0 +1,81 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/model/bmi_log.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/model/filter_data.dart'; +import 'package:nishauri/src/shared/charts/CustomLineChart.dart'; +import 'package:nishauri/src/shared/display/custome_filter_chart.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class BMILineGraph extends StatelessWidget { + final FilterData data; + final String? filter; +const BMILineGraph({required this.data, this.filter, Key? key}): super(key: key); + + @override + Widget build(BuildContext context) { + final List gradientColors = [ + Constants.bmiCalculatorColor.withOpacity(0.3), + Constants.bmiCalculatorShortcutBgColor.withOpacity(0), + ]; + + final List dataPoint; + + if (filter == "Week") { + dataPoint = data.week + !.asMap() + .entries + .map((entry) => FlSpot( + entry.key.toDouble(), entry.value.results ?? 0, + )).toList(); + } else if (filter == "6 Months") { + dataPoint = data.sixMonths + !.asMap() + .entries + .map((entry) => FlSpot( + entry.key.toDouble(),entry.value.avgResults ?? 0, + )) + .toList(); + } else { + dataPoint = []; + } + + final List dateTimeList; + if (filter == "Week") { + dateTimeList = data.week + ?.asMap() + .entries + .map((entry) => entry.value.dayName ) + .whereType() + .toList() ?? []; + } else if (filter == "6 Months") { + dateTimeList = data.sixMonths + ?.asMap() + .entries + .map((entry) => entry.value.month) + .whereType() + .toList() ?? []; + } else { + dateTimeList = []; + } + + return Scaffold( + body: Padding( + padding: const EdgeInsets.all(16.0), + child: CustomFilterLineChart( + dataPoints: dataPoint, + dateTimes: dateTimeList, + minX: 0, + maxY: 30.0, + maxX: dateTimeList.length - 1, + leftTile: true, + minY: 0, + barColor: Constants.bmiCalculatorColor, + gradientColors: gradientColors, + bottomTile: true, + interval: 5, + filter: filter, + ), + ), + ); + } +} diff --git a/lib/src/features/bmi/presentation/widgets/BMILineList.dart b/lib/src/features/self_screening/bmi/presentation/widgets/BMILineList.dart similarity index 89% rename from lib/src/features/bmi/presentation/widgets/BMILineList.dart rename to lib/src/features/self_screening/bmi/presentation/widgets/BMILineList.dart index a38a54c8..e311a577 100644 --- a/lib/src/features/bmi/presentation/widgets/BMILineList.dart +++ b/lib/src/features/self_screening/bmi/presentation/widgets/BMILineList.dart @@ -1,9 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; -import 'package:nishauri/src/features/bmi/data/model/bmi_log.dart'; -import 'package:nishauri/src/features/bmi/data/providers/bmi_log_provider.dart'; -import 'package:nishauri/src/features/bp/data/providers/blood_pressure_provider.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/model/bmi_log.dart'; import 'package:nishauri/src/utils/constants.dart'; class BMILinelist extends StatelessWidget { @@ -11,6 +8,7 @@ class BMILinelist extends StatelessWidget { const BMILinelist({required this.data, Key? key}) : super(key: key); @override Widget build(BuildContext context) { + data.sort((a, b) => a.created_at.compareTo(b.created_at)); return ListView.builder( itemCount: data.length + 1, // +1 for the header row itemBuilder: (context, index) { diff --git a/lib/src/features/bmi/presentation/widgets/GenderPicker.dart b/lib/src/features/self_screening/bmi/presentation/widgets/GenderPicker.dart similarity index 95% rename from lib/src/features/bmi/presentation/widgets/GenderPicker.dart rename to lib/src/features/self_screening/bmi/presentation/widgets/GenderPicker.dart index b0ca69be..636f8b3f 100644 --- a/lib/src/features/bmi/presentation/widgets/GenderPicker.dart +++ b/lib/src/features/self_screening/bmi/presentation/widgets/GenderPicker.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:nishauri/src/utils/constants.dart'; -import 'package:nishauri/src/utils/constants.dart'; -import '../../../../utils/helpers.dart'; +import 'package:nishauri/src/utils/helpers.dart'; enum GenderPickerChoices { male, female } @@ -24,7 +23,7 @@ class GenderPicker extends StatelessWidget { final screenSize = getOrientationAwareScreenSize(context); final theme = Theme.of(context); final color = theme.canvasColor; - final bgColor = activeColor ?? Constants.activeSelectionColor; + final bgColor = activeColor ?? Constants.selfScreeningBgColor; final disabledColor = Colors.grey; return Row( diff --git a/lib/src/features/bmi/presentation/widgets/HeightPicker.dart b/lib/src/features/self_screening/bmi/presentation/widgets/HeightPicker.dart similarity index 92% rename from lib/src/features/bmi/presentation/widgets/HeightPicker.dart rename to lib/src/features/self_screening/bmi/presentation/widgets/HeightPicker.dart index 1f78e79f..b99b2230 100644 --- a/lib/src/features/bmi/presentation/widgets/HeightPicker.dart +++ b/lib/src/features/self_screening/bmi/presentation/widgets/HeightPicker.dart @@ -1,7 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:nishauri/src/features/bmi/presentation/widgets/HeightUnitsPicker.dart'; -import 'package:nishauri/src/shared/display/AppCard.dart'; +import 'package:nishauri/src/features/self_screening/bmi/presentation/widgets/HeightUnitsPicker.dart'; import 'package:nishauri/src/utils/constants.dart'; class HeightPicker extends StatelessWidget { diff --git a/lib/src/features/bmi/presentation/widgets/HeightUnitsPicker.dart b/lib/src/features/self_screening/bmi/presentation/widgets/HeightUnitsPicker.dart similarity index 100% rename from lib/src/features/bmi/presentation/widgets/HeightUnitsPicker.dart rename to lib/src/features/self_screening/bmi/presentation/widgets/HeightUnitsPicker.dart diff --git a/lib/src/features/bp/data/models/blood_pressure.dart b/lib/src/features/self_screening/bp/data/models/blood_pressure.dart similarity index 100% rename from lib/src/features/bp/data/models/blood_pressure.dart rename to lib/src/features/self_screening/bp/data/models/blood_pressure.dart diff --git a/lib/src/features/bp/data/models/blood_pressure.freezed.dart b/lib/src/features/self_screening/bp/data/models/blood_pressure.freezed.dart similarity index 100% rename from lib/src/features/bp/data/models/blood_pressure.freezed.dart rename to lib/src/features/self_screening/bp/data/models/blood_pressure.freezed.dart diff --git a/lib/src/features/bp/data/models/blood_pressure.g.dart b/lib/src/features/self_screening/bp/data/models/blood_pressure.g.dart similarity index 100% rename from lib/src/features/bp/data/models/blood_pressure.g.dart rename to lib/src/features/self_screening/bp/data/models/blood_pressure.g.dart diff --git a/lib/src/features/self_screening/bp/data/models/bp_advice.dart b/lib/src/features/self_screening/bp/data/models/bp_advice.dart new file mode 100644 index 00000000..0ec77755 --- /dev/null +++ b/lib/src/features/self_screening/bp/data/models/bp_advice.dart @@ -0,0 +1,14 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'bp_advice.freezed.dart'; +part 'bp_advice.g.dart'; + +@Freezed() +class BpAdvice with _$BpAdvice { + const factory BpAdvice({ + String? status, + String? advice, + + }) = _BpAdvice; + factory BpAdvice.fromJson(Map json)=> _$BpAdviceFromJson(json); +} \ No newline at end of file diff --git a/lib/src/features/self_screening/bp/data/models/bp_advice.freezed.dart b/lib/src/features/self_screening/bp/data/models/bp_advice.freezed.dart new file mode 100644 index 00000000..9369eccd --- /dev/null +++ b/lib/src/features/self_screening/bp/data/models/bp_advice.freezed.dart @@ -0,0 +1,167 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'bp_advice.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +BpAdvice _$BpAdviceFromJson(Map json) { + return _BpAdvice.fromJson(json); +} + +/// @nodoc +mixin _$BpAdvice { + String? get status => throw _privateConstructorUsedError; + String? get advice => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $BpAdviceCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BpAdviceCopyWith<$Res> { + factory $BpAdviceCopyWith(BpAdvice value, $Res Function(BpAdvice) then) = + _$BpAdviceCopyWithImpl<$Res, BpAdvice>; + @useResult + $Res call({String? status, String? advice}); +} + +/// @nodoc +class _$BpAdviceCopyWithImpl<$Res, $Val extends BpAdvice> + implements $BpAdviceCopyWith<$Res> { + _$BpAdviceCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = freezed, + Object? advice = freezed, + }) { + return _then(_value.copyWith( + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String?, + advice: freezed == advice + ? _value.advice + : advice // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$BpAdviceImplCopyWith<$Res> + implements $BpAdviceCopyWith<$Res> { + factory _$$BpAdviceImplCopyWith( + _$BpAdviceImpl value, $Res Function(_$BpAdviceImpl) then) = + __$$BpAdviceImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String? status, String? advice}); +} + +/// @nodoc +class __$$BpAdviceImplCopyWithImpl<$Res> + extends _$BpAdviceCopyWithImpl<$Res, _$BpAdviceImpl> + implements _$$BpAdviceImplCopyWith<$Res> { + __$$BpAdviceImplCopyWithImpl( + _$BpAdviceImpl _value, $Res Function(_$BpAdviceImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = freezed, + Object? advice = freezed, + }) { + return _then(_$BpAdviceImpl( + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String?, + advice: freezed == advice + ? _value.advice + : advice // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$BpAdviceImpl implements _BpAdvice { + const _$BpAdviceImpl({this.status, this.advice}); + + factory _$BpAdviceImpl.fromJson(Map json) => + _$$BpAdviceImplFromJson(json); + + @override + final String? status; + @override + final String? advice; + + @override + String toString() { + return 'BpAdvice(status: $status, advice: $advice)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BpAdviceImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.advice, advice) || other.advice == advice)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, status, advice); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$BpAdviceImplCopyWith<_$BpAdviceImpl> get copyWith => + __$$BpAdviceImplCopyWithImpl<_$BpAdviceImpl>(this, _$identity); + + @override + Map toJson() { + return _$$BpAdviceImplToJson( + this, + ); + } +} + +abstract class _BpAdvice implements BpAdvice { + const factory _BpAdvice({final String? status, final String? advice}) = + _$BpAdviceImpl; + + factory _BpAdvice.fromJson(Map json) = + _$BpAdviceImpl.fromJson; + + @override + String? get status; + @override + String? get advice; + @override + @JsonKey(ignore: true) + _$$BpAdviceImplCopyWith<_$BpAdviceImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/self_screening/bp/data/models/bp_advice.g.dart b/lib/src/features/self_screening/bp/data/models/bp_advice.g.dart new file mode 100644 index 00000000..a8a0c54e --- /dev/null +++ b/lib/src/features/self_screening/bp/data/models/bp_advice.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'bp_advice.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$BpAdviceImpl _$$BpAdviceImplFromJson(Map json) => + _$BpAdviceImpl( + status: json['status'] as String?, + advice: json['advice'] as String?, + ); + +Map _$$BpAdviceImplToJson(_$BpAdviceImpl instance) => + { + 'status': instance.status, + 'advice': instance.advice, + }; diff --git a/lib/src/features/self_screening/bp/data/models/bp_hours.dart b/lib/src/features/self_screening/bp/data/models/bp_hours.dart new file mode 100644 index 00000000..f2844488 --- /dev/null +++ b/lib/src/features/self_screening/bp/data/models/bp_hours.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'bp_hours.freezed.dart'; +part 'bp_hours.g.dart'; + +@Freezed() +class BpHours with _$BpHours { + + const factory BpHours({ + String? time, + double? systolic, + double? diastolic, + double? pulse_rate, + }) = _BpHours; + + factory BpHours.fromJson(Map json) + => _$BpHoursFromJson(json); +} \ No newline at end of file diff --git a/lib/src/features/self_screening/bp/data/models/bp_hours.freezed.dart b/lib/src/features/self_screening/bp/data/models/bp_hours.freezed.dart new file mode 100644 index 00000000..4a07f075 --- /dev/null +++ b/lib/src/features/self_screening/bp/data/models/bp_hours.freezed.dart @@ -0,0 +1,206 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'bp_hours.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +BpHours _$BpHoursFromJson(Map json) { + return _BpHours.fromJson(json); +} + +/// @nodoc +mixin _$BpHours { + String? get time => throw _privateConstructorUsedError; + double? get systolic => throw _privateConstructorUsedError; + double? get diastolic => throw _privateConstructorUsedError; + double? get pulse_rate => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $BpHoursCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BpHoursCopyWith<$Res> { + factory $BpHoursCopyWith(BpHours value, $Res Function(BpHours) then) = + _$BpHoursCopyWithImpl<$Res, BpHours>; + @useResult + $Res call( + {String? time, double? systolic, double? diastolic, double? pulse_rate}); +} + +/// @nodoc +class _$BpHoursCopyWithImpl<$Res, $Val extends BpHours> + implements $BpHoursCopyWith<$Res> { + _$BpHoursCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? time = freezed, + Object? systolic = freezed, + Object? diastolic = freezed, + Object? pulse_rate = freezed, + }) { + return _then(_value.copyWith( + time: freezed == time + ? _value.time + : time // ignore: cast_nullable_to_non_nullable + as String?, + systolic: freezed == systolic + ? _value.systolic + : systolic // ignore: cast_nullable_to_non_nullable + as double?, + diastolic: freezed == diastolic + ? _value.diastolic + : diastolic // ignore: cast_nullable_to_non_nullable + as double?, + pulse_rate: freezed == pulse_rate + ? _value.pulse_rate + : pulse_rate // ignore: cast_nullable_to_non_nullable + as double?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$BpHoursImplCopyWith<$Res> implements $BpHoursCopyWith<$Res> { + factory _$$BpHoursImplCopyWith( + _$BpHoursImpl value, $Res Function(_$BpHoursImpl) then) = + __$$BpHoursImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String? time, double? systolic, double? diastolic, double? pulse_rate}); +} + +/// @nodoc +class __$$BpHoursImplCopyWithImpl<$Res> + extends _$BpHoursCopyWithImpl<$Res, _$BpHoursImpl> + implements _$$BpHoursImplCopyWith<$Res> { + __$$BpHoursImplCopyWithImpl( + _$BpHoursImpl _value, $Res Function(_$BpHoursImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? time = freezed, + Object? systolic = freezed, + Object? diastolic = freezed, + Object? pulse_rate = freezed, + }) { + return _then(_$BpHoursImpl( + time: freezed == time + ? _value.time + : time // ignore: cast_nullable_to_non_nullable + as String?, + systolic: freezed == systolic + ? _value.systolic + : systolic // ignore: cast_nullable_to_non_nullable + as double?, + diastolic: freezed == diastolic + ? _value.diastolic + : diastolic // ignore: cast_nullable_to_non_nullable + as double?, + pulse_rate: freezed == pulse_rate + ? _value.pulse_rate + : pulse_rate // ignore: cast_nullable_to_non_nullable + as double?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$BpHoursImpl implements _BpHours { + const _$BpHoursImpl( + {this.time, this.systolic, this.diastolic, this.pulse_rate}); + + factory _$BpHoursImpl.fromJson(Map json) => + _$$BpHoursImplFromJson(json); + + @override + final String? time; + @override + final double? systolic; + @override + final double? diastolic; + @override + final double? pulse_rate; + + @override + String toString() { + return 'BpHours(time: $time, systolic: $systolic, diastolic: $diastolic, pulse_rate: $pulse_rate)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BpHoursImpl && + (identical(other.time, time) || other.time == time) && + (identical(other.systolic, systolic) || + other.systolic == systolic) && + (identical(other.diastolic, diastolic) || + other.diastolic == diastolic) && + (identical(other.pulse_rate, pulse_rate) || + other.pulse_rate == pulse_rate)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, time, systolic, diastolic, pulse_rate); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$BpHoursImplCopyWith<_$BpHoursImpl> get copyWith => + __$$BpHoursImplCopyWithImpl<_$BpHoursImpl>(this, _$identity); + + @override + Map toJson() { + return _$$BpHoursImplToJson( + this, + ); + } +} + +abstract class _BpHours implements BpHours { + const factory _BpHours( + {final String? time, + final double? systolic, + final double? diastolic, + final double? pulse_rate}) = _$BpHoursImpl; + + factory _BpHours.fromJson(Map json) = _$BpHoursImpl.fromJson; + + @override + String? get time; + @override + double? get systolic; + @override + double? get diastolic; + @override + double? get pulse_rate; + @override + @JsonKey(ignore: true) + _$$BpHoursImplCopyWith<_$BpHoursImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/self_screening/bp/data/models/bp_hours.g.dart b/lib/src/features/self_screening/bp/data/models/bp_hours.g.dart new file mode 100644 index 00000000..eff9fff0 --- /dev/null +++ b/lib/src/features/self_screening/bp/data/models/bp_hours.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'bp_hours.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$BpHoursImpl _$$BpHoursImplFromJson(Map json) => + _$BpHoursImpl( + time: json['time'] as String?, + systolic: (json['systolic'] as num?)?.toDouble(), + diastolic: (json['diastolic'] as num?)?.toDouble(), + pulse_rate: (json['pulse_rate'] as num?)?.toDouble(), + ); + +Map _$$BpHoursImplToJson(_$BpHoursImpl instance) => + { + 'time': instance.time, + 'systolic': instance.systolic, + 'diastolic': instance.diastolic, + 'pulse_rate': instance.pulse_rate, + }; diff --git a/lib/src/features/self_screening/bp/data/models/bp_six_months.dart b/lib/src/features/self_screening/bp/data/models/bp_six_months.dart new file mode 100644 index 00000000..95cab54f --- /dev/null +++ b/lib/src/features/self_screening/bp/data/models/bp_six_months.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'bp_six_months.freezed.dart'; +part 'bp_six_months.g.dart'; + +@Freezed() +class BpSixMonths with _$BpSixMonths { + + const factory BpSixMonths({ + String? month, + double? avg_systolic, + double? avg_diastolic, + double? avg_pulse_rate, + }) = _BpSixMonths; + + factory BpSixMonths.fromJson(Map json) + => _$BpSixMonthsFromJson(json); +} diff --git a/lib/src/features/self_screening/bp/data/models/bp_six_months.freezed.dart b/lib/src/features/self_screening/bp/data/models/bp_six_months.freezed.dart new file mode 100644 index 00000000..b98e5e25 --- /dev/null +++ b/lib/src/features/self_screening/bp/data/models/bp_six_months.freezed.dart @@ -0,0 +1,216 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'bp_six_months.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +BpSixMonths _$BpSixMonthsFromJson(Map json) { + return _BpSixMonths.fromJson(json); +} + +/// @nodoc +mixin _$BpSixMonths { + String? get month => throw _privateConstructorUsedError; + double? get avg_systolic => throw _privateConstructorUsedError; + double? get avg_diastolic => throw _privateConstructorUsedError; + double? get avg_pulse_rate => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $BpSixMonthsCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BpSixMonthsCopyWith<$Res> { + factory $BpSixMonthsCopyWith( + BpSixMonths value, $Res Function(BpSixMonths) then) = + _$BpSixMonthsCopyWithImpl<$Res, BpSixMonths>; + @useResult + $Res call( + {String? month, + double? avg_systolic, + double? avg_diastolic, + double? avg_pulse_rate}); +} + +/// @nodoc +class _$BpSixMonthsCopyWithImpl<$Res, $Val extends BpSixMonths> + implements $BpSixMonthsCopyWith<$Res> { + _$BpSixMonthsCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? month = freezed, + Object? avg_systolic = freezed, + Object? avg_diastolic = freezed, + Object? avg_pulse_rate = freezed, + }) { + return _then(_value.copyWith( + month: freezed == month + ? _value.month + : month // ignore: cast_nullable_to_non_nullable + as String?, + avg_systolic: freezed == avg_systolic + ? _value.avg_systolic + : avg_systolic // ignore: cast_nullable_to_non_nullable + as double?, + avg_diastolic: freezed == avg_diastolic + ? _value.avg_diastolic + : avg_diastolic // ignore: cast_nullable_to_non_nullable + as double?, + avg_pulse_rate: freezed == avg_pulse_rate + ? _value.avg_pulse_rate + : avg_pulse_rate // ignore: cast_nullable_to_non_nullable + as double?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$BpSixMonthsImplCopyWith<$Res> + implements $BpSixMonthsCopyWith<$Res> { + factory _$$BpSixMonthsImplCopyWith( + _$BpSixMonthsImpl value, $Res Function(_$BpSixMonthsImpl) then) = + __$$BpSixMonthsImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String? month, + double? avg_systolic, + double? avg_diastolic, + double? avg_pulse_rate}); +} + +/// @nodoc +class __$$BpSixMonthsImplCopyWithImpl<$Res> + extends _$BpSixMonthsCopyWithImpl<$Res, _$BpSixMonthsImpl> + implements _$$BpSixMonthsImplCopyWith<$Res> { + __$$BpSixMonthsImplCopyWithImpl( + _$BpSixMonthsImpl _value, $Res Function(_$BpSixMonthsImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? month = freezed, + Object? avg_systolic = freezed, + Object? avg_diastolic = freezed, + Object? avg_pulse_rate = freezed, + }) { + return _then(_$BpSixMonthsImpl( + month: freezed == month + ? _value.month + : month // ignore: cast_nullable_to_non_nullable + as String?, + avg_systolic: freezed == avg_systolic + ? _value.avg_systolic + : avg_systolic // ignore: cast_nullable_to_non_nullable + as double?, + avg_diastolic: freezed == avg_diastolic + ? _value.avg_diastolic + : avg_diastolic // ignore: cast_nullable_to_non_nullable + as double?, + avg_pulse_rate: freezed == avg_pulse_rate + ? _value.avg_pulse_rate + : avg_pulse_rate // ignore: cast_nullable_to_non_nullable + as double?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$BpSixMonthsImpl implements _BpSixMonths { + const _$BpSixMonthsImpl( + {this.month, this.avg_systolic, this.avg_diastolic, this.avg_pulse_rate}); + + factory _$BpSixMonthsImpl.fromJson(Map json) => + _$$BpSixMonthsImplFromJson(json); + + @override + final String? month; + @override + final double? avg_systolic; + @override + final double? avg_diastolic; + @override + final double? avg_pulse_rate; + + @override + String toString() { + return 'BpSixMonths(month: $month, avg_systolic: $avg_systolic, avg_diastolic: $avg_diastolic, avg_pulse_rate: $avg_pulse_rate)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BpSixMonthsImpl && + (identical(other.month, month) || other.month == month) && + (identical(other.avg_systolic, avg_systolic) || + other.avg_systolic == avg_systolic) && + (identical(other.avg_diastolic, avg_diastolic) || + other.avg_diastolic == avg_diastolic) && + (identical(other.avg_pulse_rate, avg_pulse_rate) || + other.avg_pulse_rate == avg_pulse_rate)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, month, avg_systolic, avg_diastolic, avg_pulse_rate); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$BpSixMonthsImplCopyWith<_$BpSixMonthsImpl> get copyWith => + __$$BpSixMonthsImplCopyWithImpl<_$BpSixMonthsImpl>(this, _$identity); + + @override + Map toJson() { + return _$$BpSixMonthsImplToJson( + this, + ); + } +} + +abstract class _BpSixMonths implements BpSixMonths { + const factory _BpSixMonths( + {final String? month, + final double? avg_systolic, + final double? avg_diastolic, + final double? avg_pulse_rate}) = _$BpSixMonthsImpl; + + factory _BpSixMonths.fromJson(Map json) = + _$BpSixMonthsImpl.fromJson; + + @override + String? get month; + @override + double? get avg_systolic; + @override + double? get avg_diastolic; + @override + double? get avg_pulse_rate; + @override + @JsonKey(ignore: true) + _$$BpSixMonthsImplCopyWith<_$BpSixMonthsImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/self_screening/bp/data/models/bp_six_months.g.dart b/lib/src/features/self_screening/bp/data/models/bp_six_months.g.dart new file mode 100644 index 00000000..4941d398 --- /dev/null +++ b/lib/src/features/self_screening/bp/data/models/bp_six_months.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'bp_six_months.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$BpSixMonthsImpl _$$BpSixMonthsImplFromJson(Map json) => + _$BpSixMonthsImpl( + month: json['month'] as String?, + avg_systolic: (json['avg_systolic'] as num?)?.toDouble(), + avg_diastolic: (json['avg_diastolic'] as num?)?.toDouble(), + avg_pulse_rate: (json['avg_pulse_rate'] as num?)?.toDouble(), + ); + +Map _$$BpSixMonthsImplToJson(_$BpSixMonthsImpl instance) => + { + 'month': instance.month, + 'avg_systolic': instance.avg_systolic, + 'avg_diastolic': instance.avg_diastolic, + 'avg_pulse_rate': instance.avg_pulse_rate, + }; diff --git a/lib/src/features/self_screening/bp/data/models/bp_week.dart b/lib/src/features/self_screening/bp/data/models/bp_week.dart new file mode 100644 index 00000000..f2030834 --- /dev/null +++ b/lib/src/features/self_screening/bp/data/models/bp_week.dart @@ -0,0 +1,19 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'bp_week.freezed.dart'; +part 'bp_week.g.dart'; + +@Freezed() +class BpWeek with _$BpWeek { + + const factory BpWeek({ + required String dayName, + String? date, + double? systolic, + double? diastolic, + double? pulse_rate, + }) = _BpWeek; + + factory BpWeek.fromJson(Map json) + => _$BpWeekFromJson(json); +} diff --git a/lib/src/features/self_screening/bp/data/models/bp_week.freezed.dart b/lib/src/features/self_screening/bp/data/models/bp_week.freezed.dart new file mode 100644 index 00000000..391173bb --- /dev/null +++ b/lib/src/features/self_screening/bp/data/models/bp_week.freezed.dart @@ -0,0 +1,235 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'bp_week.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +BpWeek _$BpWeekFromJson(Map json) { + return _BpWeek.fromJson(json); +} + +/// @nodoc +mixin _$BpWeek { + String get dayName => throw _privateConstructorUsedError; + String? get date => throw _privateConstructorUsedError; + double? get systolic => throw _privateConstructorUsedError; + double? get diastolic => throw _privateConstructorUsedError; + double? get pulse_rate => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $BpWeekCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BpWeekCopyWith<$Res> { + factory $BpWeekCopyWith(BpWeek value, $Res Function(BpWeek) then) = + _$BpWeekCopyWithImpl<$Res, BpWeek>; + @useResult + $Res call( + {String dayName, + String? date, + double? systolic, + double? diastolic, + double? pulse_rate}); +} + +/// @nodoc +class _$BpWeekCopyWithImpl<$Res, $Val extends BpWeek> + implements $BpWeekCopyWith<$Res> { + _$BpWeekCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? dayName = null, + Object? date = freezed, + Object? systolic = freezed, + Object? diastolic = freezed, + Object? pulse_rate = freezed, + }) { + return _then(_value.copyWith( + dayName: null == dayName + ? _value.dayName + : dayName // ignore: cast_nullable_to_non_nullable + as String, + date: freezed == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as String?, + systolic: freezed == systolic + ? _value.systolic + : systolic // ignore: cast_nullable_to_non_nullable + as double?, + diastolic: freezed == diastolic + ? _value.diastolic + : diastolic // ignore: cast_nullable_to_non_nullable + as double?, + pulse_rate: freezed == pulse_rate + ? _value.pulse_rate + : pulse_rate // ignore: cast_nullable_to_non_nullable + as double?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$BpWeekImplCopyWith<$Res> implements $BpWeekCopyWith<$Res> { + factory _$$BpWeekImplCopyWith( + _$BpWeekImpl value, $Res Function(_$BpWeekImpl) then) = + __$$BpWeekImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String dayName, + String? date, + double? systolic, + double? diastolic, + double? pulse_rate}); +} + +/// @nodoc +class __$$BpWeekImplCopyWithImpl<$Res> + extends _$BpWeekCopyWithImpl<$Res, _$BpWeekImpl> + implements _$$BpWeekImplCopyWith<$Res> { + __$$BpWeekImplCopyWithImpl( + _$BpWeekImpl _value, $Res Function(_$BpWeekImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? dayName = null, + Object? date = freezed, + Object? systolic = freezed, + Object? diastolic = freezed, + Object? pulse_rate = freezed, + }) { + return _then(_$BpWeekImpl( + dayName: null == dayName + ? _value.dayName + : dayName // ignore: cast_nullable_to_non_nullable + as String, + date: freezed == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as String?, + systolic: freezed == systolic + ? _value.systolic + : systolic // ignore: cast_nullable_to_non_nullable + as double?, + diastolic: freezed == diastolic + ? _value.diastolic + : diastolic // ignore: cast_nullable_to_non_nullable + as double?, + pulse_rate: freezed == pulse_rate + ? _value.pulse_rate + : pulse_rate // ignore: cast_nullable_to_non_nullable + as double?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$BpWeekImpl implements _BpWeek { + const _$BpWeekImpl( + {required this.dayName, + this.date, + this.systolic, + this.diastolic, + this.pulse_rate}); + + factory _$BpWeekImpl.fromJson(Map json) => + _$$BpWeekImplFromJson(json); + + @override + final String dayName; + @override + final String? date; + @override + final double? systolic; + @override + final double? diastolic; + @override + final double? pulse_rate; + + @override + String toString() { + return 'BpWeek(dayName: $dayName, date: $date, systolic: $systolic, diastolic: $diastolic, pulse_rate: $pulse_rate)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BpWeekImpl && + (identical(other.dayName, dayName) || other.dayName == dayName) && + (identical(other.date, date) || other.date == date) && + (identical(other.systolic, systolic) || + other.systolic == systolic) && + (identical(other.diastolic, diastolic) || + other.diastolic == diastolic) && + (identical(other.pulse_rate, pulse_rate) || + other.pulse_rate == pulse_rate)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, dayName, date, systolic, diastolic, pulse_rate); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$BpWeekImplCopyWith<_$BpWeekImpl> get copyWith => + __$$BpWeekImplCopyWithImpl<_$BpWeekImpl>(this, _$identity); + + @override + Map toJson() { + return _$$BpWeekImplToJson( + this, + ); + } +} + +abstract class _BpWeek implements BpWeek { + const factory _BpWeek( + {required final String dayName, + final String? date, + final double? systolic, + final double? diastolic, + final double? pulse_rate}) = _$BpWeekImpl; + + factory _BpWeek.fromJson(Map json) = _$BpWeekImpl.fromJson; + + @override + String get dayName; + @override + String? get date; + @override + double? get systolic; + @override + double? get diastolic; + @override + double? get pulse_rate; + @override + @JsonKey(ignore: true) + _$$BpWeekImplCopyWith<_$BpWeekImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/self_screening/bp/data/models/bp_week.g.dart b/lib/src/features/self_screening/bp/data/models/bp_week.g.dart new file mode 100644 index 00000000..599ba2ef --- /dev/null +++ b/lib/src/features/self_screening/bp/data/models/bp_week.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'bp_week.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$BpWeekImpl _$$BpWeekImplFromJson(Map json) => _$BpWeekImpl( + dayName: json['dayName'] as String, + date: json['date'] as String?, + systolic: (json['systolic'] as num?)?.toDouble(), + diastolic: (json['diastolic'] as num?)?.toDouble(), + pulse_rate: (json['pulse_rate'] as num?)?.toDouble(), + ); + +Map _$$BpWeekImplToJson(_$BpWeekImpl instance) => + { + 'dayName': instance.dayName, + 'date': instance.date, + 'systolic': instance.systolic, + 'diastolic': instance.diastolic, + 'pulse_rate': instance.pulse_rate, + }; diff --git a/lib/src/features/self_screening/bp/data/models/filter_bp.dart b/lib/src/features/self_screening/bp/data/models/filter_bp.dart new file mode 100644 index 00000000..4e17ac5a --- /dev/null +++ b/lib/src/features/self_screening/bp/data/models/filter_bp.dart @@ -0,0 +1,24 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/bp_hours.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/bp_six_months.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/bp_week.dart'; + +part 'filter_bp.freezed.dart'; + +@Freezed() +class FilterBp with _$FilterBp { + + const factory FilterBp({ + required List hourly, + required List weekly, + required List sixMonthly, + }) = _FilterBp; + + factory FilterBp.fromJson(Map json) { + return FilterBp( + hourly: (json['hourly'] as List? ?? []).map((hr) => BpHours.fromJson(hr)).toList(), + weekly: (json['weekly'] as List? ?? []).map((wk) => BpWeek.fromJson(wk)).toList(), + sixMonthly: (json['sixMonthly']as List? ?? []).map((mnth) => BpSixMonths.fromJson(mnth)).toList(), + ); + } +} \ No newline at end of file diff --git a/lib/src/features/self_screening/bp/data/models/filter_bp.freezed.dart b/lib/src/features/self_screening/bp/data/models/filter_bp.freezed.dart new file mode 100644 index 00000000..c8017a1f --- /dev/null +++ b/lib/src/features/self_screening/bp/data/models/filter_bp.freezed.dart @@ -0,0 +1,200 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'filter_bp.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$FilterBp { + List get hourly => throw _privateConstructorUsedError; + List get weekly => throw _privateConstructorUsedError; + List get sixMonthly => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $FilterBpCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $FilterBpCopyWith<$Res> { + factory $FilterBpCopyWith(FilterBp value, $Res Function(FilterBp) then) = + _$FilterBpCopyWithImpl<$Res, FilterBp>; + @useResult + $Res call( + {List hourly, + List weekly, + List sixMonthly}); +} + +/// @nodoc +class _$FilterBpCopyWithImpl<$Res, $Val extends FilterBp> + implements $FilterBpCopyWith<$Res> { + _$FilterBpCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? hourly = null, + Object? weekly = null, + Object? sixMonthly = null, + }) { + return _then(_value.copyWith( + hourly: null == hourly + ? _value.hourly + : hourly // ignore: cast_nullable_to_non_nullable + as List, + weekly: null == weekly + ? _value.weekly + : weekly // ignore: cast_nullable_to_non_nullable + as List, + sixMonthly: null == sixMonthly + ? _value.sixMonthly + : sixMonthly // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$FilterBpImplCopyWith<$Res> + implements $FilterBpCopyWith<$Res> { + factory _$$FilterBpImplCopyWith( + _$FilterBpImpl value, $Res Function(_$FilterBpImpl) then) = + __$$FilterBpImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {List hourly, + List weekly, + List sixMonthly}); +} + +/// @nodoc +class __$$FilterBpImplCopyWithImpl<$Res> + extends _$FilterBpCopyWithImpl<$Res, _$FilterBpImpl> + implements _$$FilterBpImplCopyWith<$Res> { + __$$FilterBpImplCopyWithImpl( + _$FilterBpImpl _value, $Res Function(_$FilterBpImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? hourly = null, + Object? weekly = null, + Object? sixMonthly = null, + }) { + return _then(_$FilterBpImpl( + hourly: null == hourly + ? _value._hourly + : hourly // ignore: cast_nullable_to_non_nullable + as List, + weekly: null == weekly + ? _value._weekly + : weekly // ignore: cast_nullable_to_non_nullable + as List, + sixMonthly: null == sixMonthly + ? _value._sixMonthly + : sixMonthly // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +class _$FilterBpImpl implements _FilterBp { + const _$FilterBpImpl( + {required final List hourly, + required final List weekly, + required final List sixMonthly}) + : _hourly = hourly, + _weekly = weekly, + _sixMonthly = sixMonthly; + + final List _hourly; + @override + List get hourly { + if (_hourly is EqualUnmodifiableListView) return _hourly; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_hourly); + } + + final List _weekly; + @override + List get weekly { + if (_weekly is EqualUnmodifiableListView) return _weekly; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_weekly); + } + + final List _sixMonthly; + @override + List get sixMonthly { + if (_sixMonthly is EqualUnmodifiableListView) return _sixMonthly; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_sixMonthly); + } + + @override + String toString() { + return 'FilterBp(hourly: $hourly, weekly: $weekly, sixMonthly: $sixMonthly)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$FilterBpImpl && + const DeepCollectionEquality().equals(other._hourly, _hourly) && + const DeepCollectionEquality().equals(other._weekly, _weekly) && + const DeepCollectionEquality() + .equals(other._sixMonthly, _sixMonthly)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_hourly), + const DeepCollectionEquality().hash(_weekly), + const DeepCollectionEquality().hash(_sixMonthly)); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$FilterBpImplCopyWith<_$FilterBpImpl> get copyWith => + __$$FilterBpImplCopyWithImpl<_$FilterBpImpl>(this, _$identity); +} + +abstract class _FilterBp implements FilterBp { + const factory _FilterBp( + {required final List hourly, + required final List weekly, + required final List sixMonthly}) = _$FilterBpImpl; + + @override + List get hourly; + @override + List get weekly; + @override + List get sixMonthly; + @override + @JsonKey(ignore: true) + _$$FilterBpImplCopyWith<_$FilterBpImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/self_screening/bp/data/providers/blood_pressure_provider.dart b/lib/src/features/self_screening/bp/data/providers/blood_pressure_provider.dart new file mode 100644 index 00000000..3e365574 --- /dev/null +++ b/lib/src/features/self_screening/bp/data/providers/blood_pressure_provider.dart @@ -0,0 +1,37 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/blood_pressure.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/bp_advice.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/filter_bp.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/repository/blood_pressure_repository.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/repository/bp_advice_repository.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/repository/bp_filter_repository.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/services/blood_pressure_service.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/services/bp_advice_service.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/services/bp_filter_service.dart'; + +final bloodPressureRepositoryProvider = Provider((ref) { + return BloodPressureRepository(BloodPressureService()); +}); + +final bloodPressureListProvider = FutureProvider>((ref) async { + final repository = ref.watch(bloodPressureRepositoryProvider); + return await repository.getBloodPressures(); +}); + +final bpAdviceRepositoryProvider = Provider((ref) { + return BpAdviceRepository(BpAdviceService()); +}); + +final bloodPressureListAdviceProvider = FutureProvider>((ref) async { + final repository = ref.watch(bpAdviceRepositoryProvider); + return await repository.getBloodPressuresAdvice(); +}); + +final bsFilterRepositoryProvider = Provider((ref) { + return BpFilterRepository(BpFilterService()); +}); + +final bpFilterProvider = FutureProvider((ref) async { + final repository = ref.watch(bsFilterRepositoryProvider); + return await repository.fetchBloodPressureFilters(); +}); \ No newline at end of file diff --git a/lib/src/features/bp/data/repository/blood_pressure_repository.dart b/lib/src/features/self_screening/bp/data/repository/blood_pressure_repository.dart similarity index 64% rename from lib/src/features/bp/data/repository/blood_pressure_repository.dart rename to lib/src/features/self_screening/bp/data/repository/blood_pressure_repository.dart index b32b316e..c89e9c3a 100644 --- a/lib/src/features/bp/data/repository/blood_pressure_repository.dart +++ b/lib/src/features/self_screening/bp/data/repository/blood_pressure_repository.dart @@ -1,5 +1,5 @@ -import 'package:nishauri/src/features/bp/data/models/blood_pressure.dart'; -import 'package:nishauri/src/features/bp/data/services/blood_pressure_service.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/blood_pressure.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/services/blood_pressure_service.dart'; class BloodPressureRepository { final BloodPressureService _service; diff --git a/lib/src/features/self_screening/bp/data/repository/bp_advice_repository.dart b/lib/src/features/self_screening/bp/data/repository/bp_advice_repository.dart new file mode 100644 index 00000000..6530e6e5 --- /dev/null +++ b/lib/src/features/self_screening/bp/data/repository/bp_advice_repository.dart @@ -0,0 +1,14 @@ +import 'package:nishauri/src/features/self_screening/bp/data/models/blood_pressure.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/bp_advice.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/services/blood_pressure_service.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/services/bp_advice_service.dart'; + +class BpAdviceRepository { + final BpAdviceService _service; + + BpAdviceRepository(this._service); + + Future> getBloodPressuresAdvice() async { + return await _service.fetchBloodPressuresAdvice(); + } +} \ No newline at end of file diff --git a/lib/src/features/self_screening/bp/data/repository/bp_filter_repository.dart b/lib/src/features/self_screening/bp/data/repository/bp_filter_repository.dart new file mode 100644 index 00000000..589161bd --- /dev/null +++ b/lib/src/features/self_screening/bp/data/repository/bp_filter_repository.dart @@ -0,0 +1,12 @@ +import 'package:nishauri/src/features/self_screening/bp/data/models/filter_bp.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/services/bp_filter_service.dart'; + +class BpFilterRepository { + final BpFilterService _service; + + BpFilterRepository(this._service); + + Future fetchBloodPressureFilters() async { + return await _service.fetchBloodPressureFilters(); + } +} \ No newline at end of file diff --git a/lib/src/features/bp/data/services/ble_service.dart b/lib/src/features/self_screening/bp/data/services/ble_service.dart similarity index 100% rename from lib/src/features/bp/data/services/ble_service.dart rename to lib/src/features/self_screening/bp/data/services/ble_service.dart diff --git a/lib/src/features/bp/data/services/blood_pressure_service.dart b/lib/src/features/self_screening/bp/data/services/blood_pressure_service.dart similarity index 97% rename from lib/src/features/bp/data/services/blood_pressure_service.dart rename to lib/src/features/self_screening/bp/data/services/blood_pressure_service.dart index 4e5da5c4..988a18ea 100644 --- a/lib/src/features/bp/data/services/blood_pressure_service.dart +++ b/lib/src/features/self_screening/bp/data/services/blood_pressure_service.dart @@ -5,7 +5,7 @@ import 'package:flutter/services.dart'; import 'package:http/http.dart'; import 'package:nishauri/src/features/auth/data/respositories/auth_repository.dart'; import 'package:nishauri/src/features/auth/data/services/AuthApiService.dart'; -import 'package:nishauri/src/features/bp/data/models/blood_pressure.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/blood_pressure.dart'; import 'package:nishauri/src/shared/interfaces/HTTPService.dart'; import 'package:nishauri/src/utils/constants.dart'; diff --git a/lib/src/features/self_screening/bp/data/services/bp_advice_service.dart b/lib/src/features/self_screening/bp/data/services/bp_advice_service.dart new file mode 100644 index 00000000..bbdf1be4 --- /dev/null +++ b/lib/src/features/self_screening/bp/data/services/bp_advice_service.dart @@ -0,0 +1,34 @@ +import 'dart:convert'; +import 'package:flutter/services.dart'; +import 'package:nishauri/src/features/auth/data/respositories/auth_repository.dart'; +import 'package:nishauri/src/features/auth/data/services/AuthApiService.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/blood_pressure.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/bp_advice.dart'; +import 'package:nishauri/src/shared/interfaces/HTTPService.dart'; + +class BpAdviceService extends HTTPService { + final AuthRepository _repository = AuthRepository(AuthApiService()); + + // Future fetchBloodPressuresAdvice_(dynamic args) async { + // final id = await _repository.getUserId(); + // final tokenPair = await getCachedToken(); + // var headers = {'Authorization': 'Bearer ${tokenPair.accessToken}'}; + // var url = '${Constants.BASE_URL_NEW}get_blood_pressure?user_id=$id'; + // final response = request( + // url: url, + // token: tokenPair, + // method: 'GET', + // requestHeaders: headers, + // userId: id); + // return response; + // } + + Future> fetchBloodPressuresAdvice() async { + List bp = []; + final String responseString = await rootBundle.loadString('assets/data/blood_pressure_advice.json'); + final Map responseData = json.decode(responseString); + final List jsonList = responseData["bloodPressureCategories"]; + bp.addAll(jsonList.map((json) => BpAdvice.fromJson(json))); + return bp; + } +} \ No newline at end of file diff --git a/lib/src/features/self_screening/bp/data/services/bp_filter_service.dart b/lib/src/features/self_screening/bp/data/services/bp_filter_service.dart new file mode 100644 index 00000000..a199ca00 --- /dev/null +++ b/lib/src/features/self_screening/bp/data/services/bp_filter_service.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:nishauri/src/features/auth/data/respositories/auth_repository.dart'; +import 'package:nishauri/src/features/auth/data/services/AuthApiService.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/filter_bp.dart'; +import 'package:nishauri/src/shared/interfaces/HTTPService.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class BpFilterService extends HTTPService { + final AuthRepository _repository = AuthRepository(AuthApiService()); + + Future fetchBloodPressureFilters_(dynamic args) async { + final id = await _repository.getUserId(); + final tokenPair = await getCachedToken(); + var headers = {'Authorization': 'Bearer ${tokenPair.accessToken}'}; + var url = '${Constants.BASE_URL_NEW}get_blood_pressure_filter?user_id=$id'; + final response = request( + url: url, + token: tokenPair, + method: 'GET', + requestHeaders: headers, + userId: id); + return response; + } + + Future fetchBloodPressureFilters() async { + try { + final response = await call(fetchBloodPressureFilters_, null); + + if (response.statusCode == 200) { + final responseString = await response.stream.bytesToString(); + final Map responseData = json.decode(responseString); + + if (responseData.containsKey('data')) { + final filterData = responseData['data']; + final FilterBp filterBp = FilterBp.fromJson(filterData); + return filterBp; + } else { + throw "Invalid response format"; + } + } else { + throw "Something Went Wrong: ${response.statusCode}"; + } + } catch (e) { + throw 'Error occurred: $e'; + } + } +} diff --git a/lib/src/features/self_screening/bp/presentation/pages/BPLinelistScreen.dart b/lib/src/features/self_screening/bp/presentation/pages/BPLinelistScreen.dart new file mode 100644 index 00000000..216dae7c --- /dev/null +++ b/lib/src/features/self_screening/bp/presentation/pages/BPLinelistScreen.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:intl/intl.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/blood_pressure.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/background_image_widget.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +class BloodPressureRecords extends StatelessWidget { + final List data; + + const BloodPressureRecords({ + required this.data, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + if (data.isEmpty) + { + BackgroundImageWidget( + customAppBar: const CustomAppBar( + color: Constants.selfScreeningBgColor, + height: 120, + smallTitle: "All Record Data", + rightBtTitle: "", + ), svgImage: 'assets/images/emptyself_screening.svg', + notFoundText: 'No Data Recorded Yet', + ); + } + + return Scaffold( + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const CustomAppBar( + color: Constants.selfScreeningBgColor, + height: 120, + smallTitle: "All Record Data", + rightBtTitle: "Add Data", + path: RouteNames.BLOOD_PRESSURE_INPUT, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "MMGH", + style: theme.textTheme.titleMedium?.copyWith(color: Colors.grey), + ), + Expanded( + child: Container( + color: Constants.bgColor, + child: ListView.builder( + shrinkWrap: true, + itemCount: data.length, + itemBuilder: (context, index) { + final bp = data[index]; + return Column( + children: [ + ListTile( + leading: SvgPicture.asset( + "assets/images/boldDuotoneLikeHearts.svg", + width: 20, + height: 20, + ), + title: ExpansionTile( + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + "${bp.systolic}/${bp.diastolic}", + style: theme.textTheme.titleSmall, + ), + ), + Text( + DateFormat('dd MMM yy HH:mm').format(DateTime.parse(bp.created_at.toString()),), + style: theme.textTheme.bodyMedium!.copyWith(color: Colors.grey), + overflow: TextOverflow.ellipsis, + ), + ], + ), + children: [ + ListTile( + title: bp.notes != null && bp.notes!.isNotEmpty + ? Text('Notes: ${bp.notes}') + : null, + ), + ], + ), + ), + const Divider(), // Add the Divider here + ], + ); + }, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/features/self_screening/bp/presentation/pages/bpMonitorScreen.dart b/lib/src/features/self_screening/bp/presentation/pages/bpMonitorScreen.dart new file mode 100644 index 00000000..80c77022 --- /dev/null +++ b/lib/src/features/self_screening/bp/presentation/pages/bpMonitorScreen.dart @@ -0,0 +1,280 @@ +import 'dart:core'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:intl/intl.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/providers/blood_pressure_provider.dart'; +import 'package:nishauri/src/features/self_screening/bp/presentation/pages/trend_chart_screen.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/background_image_widget.dart'; +import 'package:nishauri/src/shared/display/daily_card.dart'; +import 'package:nishauri/src/shared/input/Button.dart'; +import 'package:nishauri/src/shared/providers/selectedIndexProvider.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +class BPMonitorScreen extends ConsumerStatefulWidget { + @override + _BPMonitorScreenState createState() => _BPMonitorScreenState(); +} + +class _BPMonitorScreenState extends ConsumerState { + + String _getBloodPressureStatus(double systolic, double diastolic) { + if (systolic < 120 && diastolic < 80) { + return 'Optimal'; + } else if (systolic < 130 && diastolic < 80) { + return 'Normal'; + } else if (systolic < 140 || diastolic < 90) { + return 'Elevated'; + } else if (systolic < 160 || diastolic < 100) { + return 'Hypertension Grade 1'; + } else if (systolic < 180 || diastolic < 110) { + return 'Hypertension Grade 2'; + } else { + return 'Hypertension Grade 3'; + } + } + + Color _getStatusColor(String status) { + switch (status) { + case 'Optimal': + return Colors.green; + case 'Normal': + return Colors.blue; + case 'Elevated': + return Colors.orange; + case 'Hypertension Grade 1': + return Colors.yellow; + case 'Hypertension Grade 2': + return Colors.red; + case 'Hypertension Grade 3': + return Colors.redAccent; + default: + return Colors.black; + } + } + + + void _reloadData() { + ref.refresh(bloodPressureListProvider); + } + + @override + Widget build(BuildContext context) { + final bloodPressureListAsync = ref.watch(bloodPressureListProvider); + final theme = Theme.of(context); + final adviceAsync = ref.watch(bloodPressureListAdviceProvider); + final selectedIndex = ref.watch(selectedIndexProvider); + final bloodPressureAsync = ref.watch(bpFilterProvider); + return bloodPressureListAsync.when( + data: (data) { + data.sort((a, b) => b.created_at.compareTo(a.created_at)); + final displayedData = data.isNotEmpty ? data.first : null; + data.sort((a, b) => a.created_at.compareTo(b.created_at)); + + final status = _getBloodPressureStatus(displayedData!.systolic, displayedData.diastolic); + + final filter = ["Day", "Week", "6 Months"]; + + final advice = adviceAsync.when( + data: (adviceData) => adviceData.firstWhere( + (ad) => ad.status == status, + // orElse: () => null, + )?.advice ?? 'No advice available', + error: (error, _) => 'Error loading advice', + loading: () => 'Loading advice...', + ); + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + // title: "Blood Pressure", + color: Constants.selfScreeningBgColor, + height: Constants.SMALL_APP_BAR_HEIGHT, + smallTitle: "Blood Pressure", + rightBtTitle: "Add Data", + path: RouteNames.BLOOD_PRESSURE_INPUT, + ), + Expanded( + child: SingleChildScrollView( + child: Stack( + children: [ + Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + spacing: Constants.SIXTEEN, + runSpacing: Constants.SIXTEEN, + children: [ + FilterCard( + columnTitles: filter, + onPressed: (index) { + ref.read(selectedIndexProvider.notifier).state = index; + }, + ), + ], + ), + const SizedBox(height: Constants.SPACING,), + Row( + children: [ + Text("Last Record Date:", style: theme.textTheme.bodyLarge), + const SizedBox(width: Constants.FOUR), + Text(DateFormat('dd MMM yyyy').format(displayedData!.created_at), style: theme.textTheme.bodyLarge!.copyWith(color: Colors.grey, fontWeight: FontWeight.bold)), + ], + ), + const SizedBox(height: Constants.SPACING,), + Row( + children: [ + Text("${displayedData.systolic}/${displayedData.diastolic}", style: theme.textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold)), + const SizedBox(width: Constants.FOUR), + Text("mmHG", style: theme.textTheme.bodyMedium), + const SizedBox(width: Constants.FOUR,), + Text(status, style: theme.textTheme.bodyLarge!.copyWith(color: _getStatusColor(status,), ),) + ], + ), + const SizedBox(height: Constants.SPACING,), + Wrap( + spacing: 1, + runSpacing: Constants.SIXTEEN, + children: [ + bloodPressureAsync.when( + data: (bpData) { + + return TrendChartScreen(data: bpData, filter: filter[selectedIndex],); + }, + loading: () => Center(child: CircularProgressIndicator()), + error: (error, _) => Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("No Blood Pressure Data"), + ], + ), + ), + ), + ], + ), + const SizedBox(height: Constants.SPACING,), + Card( + color: Constants.bgColor, + child: ListTile( + title: Text("Show All Data", style: theme.textTheme.titleSmall!.copyWith(fontWeight: FontWeight.bold),), + trailing: const Icon(Icons.arrow_forward_ios_outlined), + onTap: (){ + context.goNamed(RouteNames.BLOOD_PRESSURE_RECORDS, extra: data); + }, + ), + ), + const SizedBox(height: Constants.SPACING,), + Container( + decoration: BoxDecoration( + color: Constants.bgColor, + borderRadius: BorderRadius.circular(Constants.SPACING), + border: Border.all(color: Constants.bgColor), + ), + // color: Constants.bgColor, + // height: 250, + child: Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListTile( + title: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, // Align to the start of the column + children: [ + Text( + 'Your Blood Pressure is ', + style: theme.textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(width: Constants.FOUR,), + Text("($status)", style: theme.textTheme.bodyLarge!.copyWith(color: _getStatusColor(status,), ),) + ],), + ], + ), + subtitle: Text( + advice, + style: theme.textTheme.bodyLarge, + ), + ), + ], + ), + ), + ), + ), + const SizedBox(height: Constants.SPACING), + Button( + title: "More Insight", + onPress: (){ + context.goNamed(RouteNames.BLOOD_PRESSURE_INSIGHT); + }, + textColor: Constants.selfScreeningBgColor, + // onPressed: () { + // // implement on Pressed + // }, + // child: Text('More Insight', style: theme.textTheme.bodyLarge!.copyWith(color: Constants.selfScreeningBgColor)), + // style: TextButton.styleFrom( + // backgroundColor: Constants.bgColor, + // padding: EdgeInsets.all(10), + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(8), // Rounded corners + // ), + // ), + ), + + ], + ), + ), + ], + ), + ), + ), + ], + ), + floatingActionButton: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FloatingActionButton( + onPressed: _reloadData, + child: Icon(Icons.refresh), + heroTag: null, + ), + ], + ), + ); + }, + error: (error, _) => BackgroundImageWidget( + customAppBar: const CustomAppBar( + title: "Blood Pressure Monitor 📈", + color: Constants.bmiCalculatorColor, + ), + svgImage: 'assets/images/lab-empty-state.svg', + notFoundText: "No BP Data Available to display", + floatingButtonIcon1: Icons.refresh, + floatingButtonAction1: () { + _reloadData(); + }, + ), + loading: () => Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "Loading Blood Pressure", + style: theme.textTheme.bodySmall, + ), + const SizedBox(height: Constants.SPACING * 2), + const CircularProgressIndicator(), + ], + ), + ), + ); + } +} diff --git a/lib/src/features/self_screening/bp/presentation/pages/bp_input_screen.dart b/lib/src/features/self_screening/bp/presentation/pages/bp_input_screen.dart new file mode 100644 index 00000000..7960c1c8 --- /dev/null +++ b/lib/src/features/self_screening/bp/presentation/pages/bp_input_screen.dart @@ -0,0 +1,332 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/providers/blood_sugar_provider.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/blood_pressure.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/providers/blood_pressure_provider.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class BloodPressureInputs extends ConsumerStatefulWidget { + @override + _BloodPressureInputsState createState() => _BloodPressureInputsState(); +} + +class _BloodPressureInputsState extends ConsumerState { + final TextEditingController _dateController = TextEditingController(); + final TextEditingController _timeController = TextEditingController(); + final TextEditingController _notesController = TextEditingController(); + final TextEditingController _systolicController = TextEditingController(text: '120'); + final TextEditingController _diastolicController = TextEditingController(text: '80'); + final TextEditingController _pulseRateController = TextEditingController(text: '75'); + + @override + void initState() { + super.initState(); + final now = DateTime.now(); + _dateController.text = DateFormat('dd MMM yyyy').format(now); + _timeController.text = "${TimeOfDay.now().hour}:${TimeOfDay.now().minute.toString().padLeft(2, '0')}"; + } + + Future _selectDate(BuildContext context) async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(2000), + lastDate: DateTime.now(), + ); + if (picked != null) { + setState(() { + _dateController.text = DateFormat('dd MMM yyyy').format(picked); + }); + } + } + + Future _selectTime(BuildContext context) async { + final TimeOfDay? picked = await showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + ); + if (picked != null) { + setState(() { + _timeController.text = '${picked.hour}:${picked.minute.toString().padLeft(2, '0')}'; + }); + } + } + + void _reloadData() { + ref.refresh(bloodPressureListProvider); + ref.refresh(bpFilterProvider); + } + + void _submitData(double systolic, double diastolic, double? pulseRate){ + final String notes = _notesController.text; + final DateTime measurementTime = DateTime.now(); + final bp = BloodPressure( + systolic: systolic, + diastolic: diastolic, + pulse_rate: pulseRate!, + created_at: measurementTime, + notes: notes, + ); + + ref.read(bloodPressureRepositoryProvider).saveBloodPressure(bp).then((value) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(value)), + ); + _reloadData(); + // _clearForm(systolic, diastolic, heartRate, notesController); + Navigator.of(context).pop(); + }).catchError((error) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(error)), + ); + }); + } + + void _saveData() { + final systolic = double.parse(_systolicController.text); + final diastolic = double.parse(_diastolicController.text); + final pulseRate = double.parse(_pulseRateController.text); + _submitData(systolic, diastolic, pulseRate); + if (systolic != null && diastolic != null && systolic > 0 && diastolic > 0) { + if (systolic > 140 || diastolic > 90) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Warning: High blood pressure detected!'), + backgroundColor: Colors.red, + ), + ); + } + else if (systolic < 90 || diastolic < 60){ + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Warning: Low blood pressure detected!'), + backgroundColor: Colors.red, + ), + ); + } + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Data saved: $systolic/$diastolic on ${_dateController.text} at ${_timeController.text}'), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Please enter valid blood pressure readings.')), + ); + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Scaffold( + resizeToAvoidBottomInset: true, + body: SingleChildScrollView( + child: Column( + children: [ + const CustomAppBar( + color: Constants.selfScreeningBgColor, + height: 120, + smallTitle: "Blood Pressure Input", + rightBtTitle: "Add Data", + ), + Padding( + padding: const EdgeInsets.all(1.0), + // main container + child: Container( + padding: const EdgeInsets.all(Constants.SPACING), + decoration: BoxDecoration( + color: Constants.bgColor, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: Constants.bgColor), + ), + child: Column( + children: [ + const SizedBox(height: Constants.SPACING), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Date:", style: theme.textTheme.bodyLarge,), + GestureDetector( + onTap: () => _selectDate(context), + child: AbsorbPointer( + child: Container( + decoration: BoxDecoration( + color: Constants.white, + borderRadius: BorderRadius.circular(15), + border: Border.all(color: Constants.bgColor), + ), + padding: const EdgeInsets.all(Constants.SPACING), + width: 150, + height: 40, + child: TextField( + controller: _dateController, + decoration: null, + textAlign: TextAlign.center, + ), + ), + ), + ), + ], + ), + const SizedBox(height: Constants.SPACING), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Time:", style: theme.textTheme.bodyLarge), + GestureDetector( + onTap: () => _selectTime(context), + child: AbsorbPointer( + child: Container( + padding: const EdgeInsets.all(Constants.SPACING), + decoration: BoxDecoration( + color: Constants.white, + borderRadius: BorderRadius.circular(15), + border: Border.all(color: Constants.bgColor), + ), + width: 150, + height: 40, + child: TextField( + controller: _timeController, + decoration: null, + textAlign: TextAlign.center, + ), + ), + ), + ), + ], + ), + const SizedBox(height: Constants.SPACING), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Systolic:", style: theme.textTheme.bodyLarge), + Container( + padding: const EdgeInsets.all(Constants.SPACING), + decoration: BoxDecoration( + color: Constants.white, + borderRadius: BorderRadius.circular(15), + border: Border.all(color: Constants.bgColor), + ), + width: 150, + height: 40, + child: TextField( + controller: _systolicController, + keyboardType: TextInputType.number, + decoration: null, + textAlign: TextAlign.center, + ), + ), + ], + ), + const SizedBox(height: Constants.SPACING), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Diastolic", style: theme.textTheme.bodyLarge,), + Container( + padding: const EdgeInsets.all(Constants.SPACING), + decoration: BoxDecoration( + color: Constants.white, + borderRadius: BorderRadius.circular(15), + border: Border.all(color: Constants.bgColor), + ), + width: 150, + height: 40, + child: TextField( + + controller: _diastolicController, + keyboardType: TextInputType.number, + decoration: null, + textAlign: TextAlign.center + ), + ), + ], + ), + const SizedBox(height: Constants.SPACING), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Pulse Rate", style: theme.textTheme.bodyLarge, ), + Container( + padding: const EdgeInsets.all(Constants.SPACING), + decoration: BoxDecoration( + color: Constants.white, + borderRadius: BorderRadius.circular(15), + border: Border.all(color: Constants.bgColor), + ), + width: 150, + height: 40, + child: TextField( + + controller: _pulseRateController, + keyboardType: TextInputType.number, + decoration: null, + textAlign: TextAlign.center, + ), + ), + ], + ), + const SizedBox(height: Constants.SPACING), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.start, + + children: [ + Container( + padding: const EdgeInsets.all(Constants.SPACING), + decoration: BoxDecoration( + color: Constants.white, + borderRadius: BorderRadius.circular(15), + border: Border.all(color: Constants.bgColor), + ), + width: MediaQuery.of(context).size.width *0.9, + child: TextFormField( + //style: theme.textTheme.bodyMedium!.copyWith(decoration: null), + controller: _notesController, + decoration: const InputDecoration( + labelText: "Notes Optional", + border: OutlineInputBorder( + gapPadding: 5, + borderRadius: BorderRadius.all(Radius.circular(15)), + borderSide: BorderSide(color: Constants.bgColor), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Constants.bgColor), + borderRadius: BorderRadius.all(Radius.circular(15)), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Constants.bgColor), + borderRadius: BorderRadius.all(Radius.circular(15)), + ), + labelStyle: TextStyle(color: Constants.selfScreeningBgColor), + ), + keyboardType: TextInputType.text, + maxLines: null, + ), + + ), + ], + ), + const SizedBox(height: Constants.SPACING), + const Divider(), + ElevatedButton( + onPressed: _saveData, + child: const Text('Save Data', style: TextStyle(color: Constants.selfScreeningBgColor),), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/features/self_screening/bp/presentation/pages/trend_chart_screen.dart b/lib/src/features/self_screening/bp/presentation/pages/trend_chart_screen.dart new file mode 100644 index 00000000..93a2d6a7 --- /dev/null +++ b/lib/src/features/self_screening/bp/presentation/pages/trend_chart_screen.dart @@ -0,0 +1,152 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/models/filter_bp.dart'; +import 'package:nishauri/src/shared/charts/CustomeMultLineChart.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class TrendChartScreen extends StatelessWidget { + final FilterBp data; + final String filter; + const TrendChartScreen({ + required this.data, required this.filter, + Key? key, + }) : super(key: key); + @override + Widget build(BuildContext context) { + final List systolicSpots; + + if (filter == "Day") { + systolicSpots = data.hourly + ?.asMap() + .entries + .map((entry) => FlSpot( + entry.key.toDouble(),entry.value.systolic ?? 0, + )) + .toList() ?? []; + } else if (filter == "Week") { + systolicSpots = data.weekly + ?.asMap() + .entries + .map((entry) => FlSpot( + entry.key.toDouble(),entry.value.systolic ?? 0, + )) + .toList() ?? []; + } else if (filter == "6 Months") { + systolicSpots = data.sixMonthly + ?.asMap() + .entries + .map((entry) => FlSpot( + entry.key.toDouble(),entry.value.avg_systolic ?? 0, + )) + .toList() ?? []; + } else { + systolicSpots = []; + } + + final List diastolicSpots; + + if (filter == "Day") { + diastolicSpots = data.hourly + ?.asMap() + .entries + .map((entry) => FlSpot( + entry.key.toDouble(),entry.value.diastolic ?? 0, + )) + .toList() ?? []; + } else if (filter == "Week") { + diastolicSpots = data.weekly + ?.asMap() + .entries + .map((entry) => FlSpot( + entry.key.toDouble(),entry.value.diastolic ?? 0, + )) + .toList() ?? []; + } else if (filter == "6 Months") { + diastolicSpots = data.sixMonthly + ?.asMap() + .entries + .map((entry) => FlSpot( + entry.key.toDouble(),entry.value.avg_diastolic ?? 0, + )) + .toList() ?? []; + } else { + diastolicSpots = []; + } + + + final List dateTimeList; + if (filter == "Day") { + dateTimeList = data.hourly + ?.asMap() + .entries + .map((entry) { + final timeString = entry.value.time; + final dateTime = timeString != null ? DateTime.parse(timeString) : DateTime.now(); + return DateFormat('dd/MM').format(dateTime); + }) + .whereType() + .toList() ?? []; + } else if (filter == "Week") { + dateTimeList = data.weekly + ?.asMap() + .entries + .map((entry) => entry.value.dayName ) + .whereType() + .toList() ?? []; + } else if (filter == "6 Months") { + dateTimeList = data.sixMonthly + ?.asMap() + .entries + .map((entry) => entry.value.month) + .whereType() + .toList() ?? []; + } else { + dateTimeList = []; + } + + return Container( + height: 350, + child: Scaffold( + body: Padding( + padding: const EdgeInsets.all(16.0), + child: CustomMultiLineChart( + lineBarsData: [ + LineChartBarData( + spots: systolicSpots, + isCurved: true, + color: Constants.barColor, + barWidth: 2, + belowBarData: BarAreaData( + show: false, + gradient: LinearGradient( + colors: [Colors.red.withOpacity(0.3), Colors.red.withOpacity(0)], + ), + ), + dotData: FlDotData(show: true), + ), + LineChartBarData( + spots: diastolicSpots, + isCurved: true, + color: Constants.facilityDirectoryColor, + barWidth: 2, + belowBarData: BarAreaData( + show: false, + gradient: LinearGradient( + colors: [Colors.orange.withOpacity(0.3), Colors.orange.withOpacity(0)], + ), + ), + dotData: FlDotData(show: true), + ), + ], + minX: 0, + maxX: dateTimeList.length - 1, + dateTimes: dateTimeList, + showLeftTitles: false, + filter: filter, + ), + ), + ), + ); + } +} diff --git a/lib/src/features/self_screening/data/providers/insight_provider.dart b/lib/src/features/self_screening/data/providers/insight_provider.dart new file mode 100644 index 00000000..676dc43c --- /dev/null +++ b/lib/src/features/self_screening/data/providers/insight_provider.dart @@ -0,0 +1,18 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:nishauri/src/features/common/data/models/announcement.dart'; +import 'package:nishauri/src/features/self_screening/data/repositories/bs_repository.dart'; +import 'package:nishauri/src/features/self_screening/data/repositories/insight_repository.dart'; +import 'package:nishauri/src/features/self_screening/data/services/bs_service.dart'; +import 'package:nishauri/src/features/self_screening/data/services/insight_service.dart'; + +final insightProvider = FutureProvider>((ref) async { + final service = InsightService(); + final repo = InsightRepository(service); + return await repo.getInsights(); +}); + +final bsInsightProvider = FutureProvider>((ref) async { + final service = BsService(); + final repo = BsRepository(service); + return await repo.getBsInsights(); +}); \ No newline at end of file diff --git a/lib/src/features/self_screening/data/repositories/bs_repository.dart b/lib/src/features/self_screening/data/repositories/bs_repository.dart new file mode 100644 index 00000000..26a71cd0 --- /dev/null +++ b/lib/src/features/self_screening/data/repositories/bs_repository.dart @@ -0,0 +1,12 @@ +import 'package:nishauri/src/features/common/data/models/announcement.dart'; +import 'package:nishauri/src/features/self_screening/data/services/bs_service.dart'; + +class BsRepository { + final BsService _service; + + BsRepository(this._service); + + Future> getBsInsights() async { + return await _service.getBsInsights(); + } +} diff --git a/lib/src/features/self_screening/data/repositories/insight_repository.dart b/lib/src/features/self_screening/data/repositories/insight_repository.dart new file mode 100644 index 00000000..d0214780 --- /dev/null +++ b/lib/src/features/self_screening/data/repositories/insight_repository.dart @@ -0,0 +1,12 @@ +import 'package:nishauri/src/features/common/data/models/announcement.dart'; +import 'package:nishauri/src/features/self_screening/data/services/insight_service.dart'; + +class InsightRepository { + final InsightService _service; + + InsightRepository(this._service); + + Future> getInsights() async { + return await _service.getInsights(); + } +} diff --git a/lib/src/features/self_screening/data/services/bs_service.dart b/lib/src/features/self_screening/data/services/bs_service.dart new file mode 100644 index 00000000..4a53a553 --- /dev/null +++ b/lib/src/features/self_screening/data/services/bs_service.dart @@ -0,0 +1,18 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:nishauri/src/features/common/data/models/announcement.dart'; +import 'package:nishauri/src/shared/interfaces/HTTPService.dart'; +import 'package:nishauri/src/utils/helpers.dart'; + +class BsService extends HTTPService { + Future> getBsInsights() async { + final data = await loadJsonData("assets/data/self_screening_bs.json"); + final json = jsonDecode(data); + final announce = json + .map((an) => Announcement.fromJson(Map.from(an))).toList(); + + log("$announce"); + return [...announce]; + } +} diff --git a/lib/src/features/self_screening/data/services/insight_service.dart b/lib/src/features/self_screening/data/services/insight_service.dart new file mode 100644 index 00000000..7825b353 --- /dev/null +++ b/lib/src/features/self_screening/data/services/insight_service.dart @@ -0,0 +1,18 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:nishauri/src/features/common/data/models/announcement.dart'; +import 'package:nishauri/src/shared/interfaces/HTTPService.dart'; +import 'package:nishauri/src/utils/helpers.dart'; + +class InsightService extends HTTPService { + Future> getInsights() async { + final data = await loadJsonData("assets/data/self_screening_insight.json"); + final json = jsonDecode(data); + final announce = json + .map((an) => Announcement.fromJson(Map.from(an))).toList(); + + log("$announce"); + return [...announce]; + } +} diff --git a/lib/src/features/self_screening/presentation/common/insight_common.dart b/lib/src/features/self_screening/presentation/common/insight_common.dart new file mode 100644 index 00000000..2bdc6df9 --- /dev/null +++ b/lib/src/features/self_screening/presentation/common/insight_common.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nishauri/src/features/common/data/models/announcement.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/shared/display/background_image_widget.dart'; +import 'package:nishauri/src/utils/helpers.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +class InsightCommon extends HookConsumerWidget { + final String appBarTitle; + final Color appBarColor; + final String pathName; + final AsyncValue> insightAsync; + + const InsightCommon({ + Key? key, + required this.appBarTitle, + required this.appBarColor, + required this.insightAsync, + required this.pathName + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = Theme.of(context); + + return insightAsync.when( + data: (data) { + if (data.isEmpty) { + return const BackgroundImageWidget( + svgImage: "assets/images/appointments-empty.svg", + notFoundText: "No Content", + ); + } + return Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + CustomAppBar( + title: appBarTitle, + color: appBarColor, + ), + Expanded( + child: ListView.builder( + itemCount: data.length, + itemBuilder: (BuildContext context, int index) { + final insight = data[index]; + if (insight.id != "3") { + return Column( + children: [ + const Divider(), + InkWell( + onTap: () { + context.goNamed( + pathName, + extra: insight, + ); + }, + child: Card( + child: Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Adjusted to use Flexible + Flexible( + child: SvgPicture.asset( + insight.image, + height: getOrientationAwareScreenSize(context).height * 0.05, + fit: BoxFit.cover, + ), + ), + const SizedBox(width: Constants.SPACING), + Expanded( + child: Text( + insight.title ?? '', + style: theme.textTheme.titleSmall, + overflow: TextOverflow.visible, + maxLines: 3, + ), + ), + ], + ), + ), + ), + ), + ], + ); + } else { + return SizedBox.shrink(); // Return an empty widget if id == "3" + } + }, + ), + ), + ], + ), + ); + }, + error: (error, _) => BackgroundImageWidget( + svgImage: 'assets/images/background.svg', + notFoundText: error.toString(), + ), + loading: () => Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "Loading insight content.", + style: theme.textTheme.headline6, + ), + const SizedBox(height: Constants.SPACING * 2), + const CircularProgressIndicator(), + ], + ), + ), + ); + } +} diff --git a/lib/src/features/self_screening/presentation/pages/blood_pressure_posts.dart b/lib/src/features/self_screening/presentation/pages/blood_pressure_posts.dart new file mode 100644 index 00000000..e4bb0cc0 --- /dev/null +++ b/lib/src/features/self_screening/presentation/pages/blood_pressure_posts.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:nishauri/src/features/common/data/models/announcement.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/blog_post_widget.dart'; +import 'package:nishauri/src/shared/display/scafold_stack_body.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class BloodPressurePostScreen extends StatelessWidget { + final Announcement announcement; + + const BloodPressurePostScreen({super.key, required this.announcement}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + CustomAppBar( + title: announcement.header ?? "Did you know 💡", + color: Constants.selfScreeningBgColor, + ), + Expanded( + child: ScaffoldStackedBody( + body: BlogPostWidget( + title: announcement.title, + imageUrl: announcement.image, + description: announcement.description!, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/features/self_screening/presentation/pages/blood_sugar_posts.dart b/lib/src/features/self_screening/presentation/pages/blood_sugar_posts.dart new file mode 100644 index 00000000..5e51f8c8 --- /dev/null +++ b/lib/src/features/self_screening/presentation/pages/blood_sugar_posts.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:nishauri/src/features/common/data/models/announcement.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/blog_post_widget.dart'; +import 'package:nishauri/src/shared/display/scafold_stack_body.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class BloodSugarPostScreen extends StatelessWidget { + final Announcement announcement; + + const BloodSugarPostScreen({super.key, required this.announcement}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + CustomAppBar( + title: announcement.header ?? "Did you know 💡", + color: Constants.selfScreeningBgColor, + ), + Expanded( + child: ScaffoldStackedBody( + body: BlogPostWidget( + title: announcement.title, + imageUrl: announcement.image, + description: announcement.description!, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/features/self_screening/presentation/pages/bpInsightScreen.dart b/lib/src/features/self_screening/presentation/pages/bpInsightScreen.dart new file mode 100644 index 00000000..b6878f08 --- /dev/null +++ b/lib/src/features/self_screening/presentation/pages/bpInsightScreen.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nishauri/src/features/self_screening/data/providers/insight_provider.dart'; +import 'package:nishauri/src/features/self_screening/presentation/common/insight_common.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +class BpInsightScreen extends HookConsumerWidget { + const BpInsightScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + const title = "Blood Pressure Awareness 🌡"; + const color = Constants.selfScreeningBgColor; + final insightAsync = ref.watch(insightProvider); + + return InsightCommon( + appBarTitle: title, + appBarColor: color, + insightAsync: insightAsync, + pathName: RouteNames.BLOOD_PRESSURE_POSTS, + ); + } +} diff --git a/lib/src/features/self_screening/presentation/pages/bsInsightScreen.dart b/lib/src/features/self_screening/presentation/pages/bsInsightScreen.dart new file mode 100644 index 00000000..26604a1f --- /dev/null +++ b/lib/src/features/self_screening/presentation/pages/bsInsightScreen.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nishauri/src/features/self_screening/presentation/common/insight_common.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/features/self_screening/data/providers/insight_provider.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +class BsInsightScreen extends HookConsumerWidget { + const BsInsightScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + const title = "Blood Sugar Awareness 🌡"; + const color = Constants.selfScreeningBgColor; + final insightAsync = ref.watch(bsInsightProvider); + + return InsightCommon( + appBarTitle: title, + appBarColor: color, + insightAsync: insightAsync, + pathName: RouteNames.BLOOD_SUGAR_POSTS, + ); + } +} diff --git a/lib/src/features/self_screening/presentation/pages/insight_screen.dart b/lib/src/features/self_screening/presentation/pages/insight_screen.dart new file mode 100644 index 00000000..09afc3ac --- /dev/null +++ b/lib/src/features/self_screening/presentation/pages/insight_screen.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:nishauri/src/features/common/presentation/pages/HomeScreen.dart'; +import 'package:nishauri/src/features/dawa_drop/presentation/pages/program_appointments.dart'; +import 'package:nishauri/src/features/self_screening/presentation/pages/bpInsightScreen.dart'; +import 'package:nishauri/src/features/self_screening/presentation/pages/bsInsightScreen.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class InsightScreen extends StatelessWidget { + const InsightScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final pages = [ + // const BpInsightScreen(), + const BpInsightScreen(), + const BsInsightScreen(), + // const BpInsightScreen(), + ]; + + final theme = Theme.of(context); + + return Scaffold( + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const CustomAppBar( + title: "Insight 📖", + subTitle: "Unlock your health insights with knowledge on different self-screening tools", + color: Constants.appointmentsColor, + ), + Expanded( + child: ListView.builder( + itemCount: pages.length, + itemBuilder: (BuildContext context, int index) { + return GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => pages[index]), + ); + }, + child: Column( + children: [ + Card( + child: Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + index == 0 + ? Icons.trending_up + : index == 1 + ? Icons.bloodtype_outlined + : index == 2 + ? Icons.bloodtype_outlined + : Icons.calendar_month_sharp, + color: index == 0 + ? Constants.bpShortCutBgColor + : index == 1 + ? Constants.bloodSugarColor + : index == 2 + ? Constants.bloodSugarColor + : Constants.periodPlannerShortcutBgColor, + ), + const SizedBox(width: Constants.SPACING), + Text( + index == 0 + ? 'Blood Pressure Awareness' + : index == 1 + ? 'Blood Sugar Awareness' + : index == 2 + ? 'Blood Sugar Monitor' + : 'Period Planner', + style: theme.textTheme.titleMedium, + ), + ], + ), + ], + ), + ), + ), + const SizedBox(height: Constants.SPACING), + ], + ), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/features/self_screening/presentation/pages/self_screening_menu.dart b/lib/src/features/self_screening/presentation/pages/self_screening_menu.dart new file mode 100644 index 00000000..677483c4 --- /dev/null +++ b/lib/src/features/self_screening/presentation/pages/self_screening_menu.dart @@ -0,0 +1,205 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:nishauri/src/features/self_screening/blood_sugar/data/providers/blood_sugar_provider.dart'; +import 'package:nishauri/src/features/self_screening/bmi/data/providers/bmi_log_provider.dart'; +import 'package:nishauri/src/features/self_screening/bp/data/providers/blood_pressure_provider.dart'; +import 'package:nishauri/src/features/self_screening/presentation/widgets/health_card.dart'; +import 'package:nishauri/src/features/self_screening/presentation/widgets/health_list.dart'; +import 'package:nishauri/src/features/self_screening/presentation/widgets/image_card.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +import 'package:nishauri/src/shared/display/custom_bottom_nav_bar.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +class SelfScreening extends ConsumerStatefulWidget { + const SelfScreening({Key? key}) : super(key: key); + + @override + _SelfScreeningState createState() => _SelfScreeningState(); +} + +class _SelfScreeningState extends ConsumerState { + int _currIndex = 0; + int _messagesCount = 0; + + void _onTap(int index) { + setState(() { + _currIndex = index; + _messagesCount = 0; // Reset messages count + }); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + final bpAsync = ref.watch(bloodPressureListProvider); + final bmiListAsync = ref.watch(bmiListProvider); + + final currentBpEntries = bpAsync.when( + data: (data) { + data.sort((a, b) => b.created_at.compareTo(a.created_at)); + return data.isNotEmpty ? data.first : null; + }, + error: (error, _) => null, + loading: () => null, + ); + + final bsAsync = ref.watch(bloodSugarEntriesProvider); + + final currentBsEntries = bsAsync.when( + data: (data) { + data.sort((a, b) => b.created_at.compareTo(a.created_at)); + return data.isNotEmpty ? data.first : null; + }, + error: (error, _) => null, + loading: () => null, + ); + + final currentBMIEntries = bmiListAsync.when( + data: (data) { + data.sort((a, b) => b.created_at.compareTo(a.created_at)); + return data.isNotEmpty ? data.first : null; + }, + error: (error, _) => null, + loading: () => null, + ); + + // Create a list of items and remove entries based on available data + final List items = ["Blood Sugar", "Blood Pressure", "BMI", "Period Calendar"]; + final List paths = [RouteNames.BLOOD_PRESSURE, RouteNames.BLOOD_SUGAR, RouteNames.BMI_CALCULATOR_RESULTS]; + + if (currentBpEntries != null) { + items.remove("Blood Pressure"); + } + if (currentBsEntries != null) { + items.remove("Blood Sugar"); + } + if (currentBMIEntries != null) { + items.remove("BMI"); + } + + double _convertToMMOL(double level) { + if (level > 30) { + level = level / 18.0; + } + return double.parse(level.toStringAsFixed(1)); + } + + return Scaffold( + body: Column( + children: [ + const CustomAppBar( + title: "Self Screening", + subTitle: "Easily track your health. Stay informed \nand take control of well-being.", + color: Constants.selfScreeningBgColor, + svgPathGroup: "assets/images/group_clinic_card.svg", + ), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Cards showing health data + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Wrap( + spacing: 5, + runSpacing: 16, + children: [ + if (currentBpEntries != null) + ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: HealthCard( + svgAsset: "assets/images/boldDuotoneLikeHearts.svg", + title: "Blood Pressure", + value1: "${currentBpEntries.systolic}", + text1: "mmHG", + vName1: "Systolic", + value2: "${currentBpEntries.diastolic}", + text2: "mmHG", + vName2: "Diastolic", + value3: "${currentBpEntries.pulse_rate}", + //text3: "Pulse/Min", + text3: "BPM", + vName3: "Pulse Rate", + onPressed: () { + context.goNamed(RouteNames.BLOOD_PRESSURE); + }, + ), + ), + if (currentBsEntries != null) HealthCard( + svgAsset: "assets/images/boldDuotoneMedicinePulse.svg", + title: "Blood Sugar", + value1: "${_convertToMMOL(currentBsEntries.level)}", + text1: "mmol/L", + onPressed: () { + context.goNamed(RouteNames.BLOOD_SUGAR); + }, + ), + if (currentBMIEntries != null) HealthCard( + svgAsset: "assets/images/bmi.svg", + title: "Body Measurements", + value1: "${currentBMIEntries.weight}", + text1: "Kgs", + vName1: "Weight", + value2: "${currentBMIEntries.height}", + text2: "Cms", + vName2: "Height", + value3: "${currentBMIEntries.results}", + text3: "", + vName3: "BMI", + onPressed: () { + context.goNamed(RouteNames.BMI_CALCULATOR_RESULTS); + }, + ), + ], + ),), + const SizedBox(height: 10), + ItemList(items: items, path: paths), + const SizedBox(height: 20), + // Title for the next section + Text( + "About Self Screening", + style: theme.textTheme.titleMedium, + ), + const SizedBox(height: 10), + const Wrap( + alignment: WrapAlignment.center, + spacing: 1, + runSpacing: 16, + children: [ + ImageCard(imagePath: 'assets/images/hospital_building.svg'), + ImageCard(imagePath: 'assets/images/hospital_building.svg'), + ], + ), + const SizedBox(height: 20), + // Title for the understanding health section + Text( + "Understanding Your Health", + style: theme.textTheme.titleMedium, + ), + const SizedBox(height: 10), + Text( + "Understanding your health metrics is crucial for maintaining a healthy lifestyle. " + "Stay informed about your numbers and consult your healthcare provider when needed.", + style: theme.textTheme.bodyMedium, + ), + ], + ), + ), + ), + ), + ], + ), + bottomNavigationBar: CustomBottomNavigationBar( + currentIndex: _currIndex, + onTap: _onTap, + messagesCount: _messagesCount, + ), + ); + } +} diff --git a/lib/src/features/self_screening/presentation/self_screening_menu.dart b/lib/src/features/self_screening/presentation/self_screening_menu.dart deleted file mode 100644 index 7bc81861..00000000 --- a/lib/src/features/self_screening/presentation/self_screening_menu.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:nishauri/src/app/navigation/menu/MenuItemsBuilder.dart'; -import 'package:nishauri/src/app/navigation/menu/MenuOption.dart'; -import 'package:nishauri/src/app/navigation/menu/menuItems.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; -import 'package:nishauri/src/utils/constants.dart'; -import 'package:nishauri/src/utils/routes.dart'; - -_menuItems(BuildContext context) => [ - MenuItem( - icon: const Icon(Icons.calculate), - shortcutIcon: const Icon(Icons.calculate), - color: Constants.bmiCalculatorShortcutBgColor, - shortcutBackgroundColor: Constants.bmiCalculatorShortcutBgColor, - title: "BMI Calculator", - onPressed: () => context.goNamed(RouteNames.BMI_CALCULATOR)), - MenuItem( - icon: const Icon(Icons.trending_up), - shortcutIcon: const Icon(Icons.trending_up), - color: Constants.bpShortCutBgColor, - shortcutBackgroundColor: Constants.bpShortCutBgColor, - title: "B P Monitor", - onPressed: () => context.goNamed(RouteNames.BLOOD_PRESSURE)), - MenuItem( - // icon: FaIcon(FontAwesomeIcons.capsules, size: Constants.iconSize, color: Colors.teal[200],), - shortcutBackgroundColor: Constants.dawaDropShortcutBgColor, - icon: const Icon(Icons.sanitizer), - shortcutIcon: Icon(Icons.sanitizer), - title: MenuItemNames.BLOOD_SUGAR, - onPressed: () => context.goNamed(MenuItemNames.BLOOD_SUGAR), - color: Constants.bloodSugarColor.withOpacity(0.5), - ), - ]; - -class SelfScreening extends StatelessWidget { - const SelfScreening({super.key}); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final _items = _menuItems(context); - return Scaffold( - // appBar: AppBar( - // backgroundColor: theme.primaryColor, - // leading: IconButton( - // icon: const Icon(Icons.chevron_left), - // onPressed: () => context.pop(), - // ), - // title: const Text("HIV Program"), - // ), - body: Column( - children: [ - CustomAppBar( - title: "Self Screening🌡️📈", - // icon: Icons.add_chart_rounded, - color: Constants.bmiCalculatorColor, - ), - Expanded( - child: MenuItemsBuilder( - crossAxisCount: 3, - itemBuilder: (item) => MenuOption( - title: item.title ?? "", - icon: item.shortcutIcon, - bgColor: Constants.bmiCalculatorShortcutBgColor, - onPress: item.onPressed, - ), - items: _items, - ), - ) - ], - )); - } -} diff --git a/lib/src/features/self_screening/presentation/widgets/health_card.dart b/lib/src/features/self_screening/presentation/widgets/health_card.dart new file mode 100644 index 00000000..5c24ceba --- /dev/null +++ b/lib/src/features/self_screening/presentation/widgets/health_card.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class HealthCard extends StatelessWidget { + final String? svgAsset; + final String title; + final String? text1; + final String? text2; + final String? text3; + final String? value1; + final String? value2; + final String? value3; + final String? vName1; + final String? vName2; + final String? vName3; + final VoidCallback? onPressed; + + const HealthCard({ + this.svgAsset, + required this.title, + this.value1, + this.value2, + this.value3, + this.text1, + this.text2, + this.text3, + this.vName1, + this.vName2, + this.vName3, + this.onPressed, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Card( + color: Constants.bgColor, + elevation: 4, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (svgAsset != null) ...[ + SvgPicture.asset( + svgAsset!, + width: Constants.TWENTY, + height: Constants.TWENTY, + ), + const SizedBox(width: 10), + ], + Expanded( + child: Text( + title, + style: Theme.of(context).textTheme.titleSmall?.copyWith(color: Constants.selfScreeningBgColor), + overflow: TextOverflow.ellipsis, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: onPressed, + child: const Icon(Icons.chevron_right), + ), + ], + ), + ], + ), + // Row( + // mainAxisAlignment: MainAxisAlignment.end, + // children: [ + // TextButton( + // onPressed: onPressed, + // child: const Icon(Icons.chevron_right), + // ), + // ], + // ), + IntrinsicHeight( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: _buildColumn(value1, text1, vName1, context)), + if (value2 != null) const VerticalDivider(), + Expanded(child: _buildColumn(value2, text2, vName2, context)), + if (value3 != null) const VerticalDivider(), + Expanded(child: _buildColumn(value3, text3, vName3, context)), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildColumn(String? value, String? text, String? name, BuildContext context) { + final theme = Theme.of(context); + if (value == null || text == null) { + return const SizedBox.shrink(); + } + return Padding( + padding: const EdgeInsets.symmetric(vertical: Constants.SMALL_SPACING1), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(name ?? '', style: theme.textTheme.bodySmall), + Row( + children: [ + Text(value, style: theme.textTheme.titleSmall), + const SizedBox(width: Constants.TWO), + Text(text, style: theme.textTheme.bodySmall), + ], + ), + ], + ), + ); + } +} diff --git a/lib/src/features/self_screening/presentation/widgets/health_list.dart b/lib/src/features/self_screening/presentation/widgets/health_list.dart new file mode 100644 index 00000000..2cff4cb2 --- /dev/null +++ b/lib/src/features/self_screening/presentation/widgets/health_list.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class ItemList extends StatelessWidget { + final List items; + final Color backgroundColor; + final Color? color; + final List path; + final List? svgAsset; + final List? relationship; + const ItemList({ + Key? key, + required this.items, + required this.path, + this.svgAsset, + this.backgroundColor = Constants.bgColor, + this.color, + this.relationship + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final height = 100 + (items.length - 1) * 70 + 5; + + return Container( + color: backgroundColor, + child: SizedBox( + height: height.toDouble(), + child: ListView.separated( + itemCount: items.length + 1, + separatorBuilder: (context, index) { + return const Divider(color: Colors.grey); + }, + itemBuilder: (context, index) { + if (index < items.length) { + return ListTile( + leading: svgAsset != null + ? SvgPicture.asset( + svgAsset![index], + width: Constants.TWENTY, + height: Constants.TWENTY, + color: color, + ) + : null, + title: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(right: Constants.SPACING), + child: Text( + items[index], + overflow: TextOverflow.ellipsis, // Prevents text overflow. + ), + ), + ), + if (relationship != null) + Padding( + padding: const EdgeInsets.only(left: Constants.SPACING), + child: Card( + color: Constants.clinicCardKinColor, + child: Padding( + padding: const EdgeInsets.all(Constants.SMALL_SPACING), + child: Text( + relationship![index], + style: const TextStyle(color: Constants.programsColor), + ), + ), + ), + ), + ], + ), + + onTap: () { + context.goNamed(path[index]); + }, + trailing: const Icon(Icons.chevron_right), + ); + } else { + return const SizedBox.shrink(); + } + }, + ), + ), + ); + } +} diff --git a/lib/src/features/self_screening/presentation/widgets/image_card.dart b/lib/src/features/self_screening/presentation/widgets/image_card.dart new file mode 100644 index 00000000..428844fd --- /dev/null +++ b/lib/src/features/self_screening/presentation/widgets/image_card.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class ImageCard extends StatelessWidget { + final String imagePath; + + const ImageCard({Key? key, required this.imagePath}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: MediaQuery.of(context).size.width * 0.45, + height: 200, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: SvgPicture.asset( + imagePath, + fit: BoxFit.cover, + ), + ), + ); + } +} diff --git a/lib/src/features/user/data/models/user.dart b/lib/src/features/user/data/models/user.dart index 7c6dd01c..8e10fe8d 100644 --- a/lib/src/features/user/data/models/user.dart +++ b/lib/src/features/user/data/models/user.dart @@ -33,6 +33,11 @@ class User with _$User { String? occupation, @Default(false) bool profileUpdated, @Default(false) bool accountVerified, + @Default([]) List roles, + String? facility, + String? national_id, + String? sha_id, + String? provider_id }) = _User; diff --git a/lib/src/features/user/data/models/user.freezed.dart b/lib/src/features/user/data/models/user.freezed.dart index ceeb7fe9..b1871577 100644 --- a/lib/src/features/user/data/models/user.freezed.dart +++ b/lib/src/features/user/data/models/user.freezed.dart @@ -47,6 +47,11 @@ mixin _$User { String? get occupation => throw _privateConstructorUsedError; bool get profileUpdated => throw _privateConstructorUsedError; bool get accountVerified => throw _privateConstructorUsedError; + List get roles => throw _privateConstructorUsedError; + String? get facility => throw _privateConstructorUsedError; + String? get national_id => throw _privateConstructorUsedError; + String? get sha_id => throw _privateConstructorUsedError; + String? get provider_id => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -82,7 +87,12 @@ abstract class $UserCopyWith<$Res> { String? primaryLanguage, String? occupation, bool profileUpdated, - bool accountVerified}); + bool accountVerified, + List roles, + String? facility, + String? national_id, + String? sha_id, + String? provider_id}); } /// @nodoc @@ -122,6 +132,11 @@ class _$UserCopyWithImpl<$Res, $Val extends User> Object? occupation = freezed, Object? profileUpdated = null, Object? accountVerified = null, + Object? roles = null, + Object? facility = freezed, + Object? national_id = freezed, + Object? sha_id = freezed, + Object? provider_id = freezed, }) { return _then(_value.copyWith( id: freezed == id @@ -220,6 +235,26 @@ class _$UserCopyWithImpl<$Res, $Val extends User> ? _value.accountVerified : accountVerified // ignore: cast_nullable_to_non_nullable as bool, + roles: null == roles + ? _value.roles + : roles // ignore: cast_nullable_to_non_nullable + as List, + facility: freezed == facility + ? _value.facility + : facility // ignore: cast_nullable_to_non_nullable + as String?, + national_id: freezed == national_id + ? _value.national_id + : national_id // ignore: cast_nullable_to_non_nullable + as String?, + sha_id: freezed == sha_id + ? _value.sha_id + : sha_id // ignore: cast_nullable_to_non_nullable + as String?, + provider_id: freezed == provider_id + ? _value.provider_id + : provider_id // ignore: cast_nullable_to_non_nullable + as String?, ) as $Val); } } @@ -255,7 +290,12 @@ abstract class _$$UserImplCopyWith<$Res> implements $UserCopyWith<$Res> { String? primaryLanguage, String? occupation, bool profileUpdated, - bool accountVerified}); + bool accountVerified, + List roles, + String? facility, + String? national_id, + String? sha_id, + String? provider_id}); } /// @nodoc @@ -292,6 +332,11 @@ class __$$UserImplCopyWithImpl<$Res> Object? occupation = freezed, Object? profileUpdated = null, Object? accountVerified = null, + Object? roles = null, + Object? facility = freezed, + Object? national_id = freezed, + Object? sha_id = freezed, + Object? provider_id = freezed, }) { return _then(_$UserImpl( id: freezed == id @@ -390,6 +435,26 @@ class __$$UserImplCopyWithImpl<$Res> ? _value.accountVerified : accountVerified // ignore: cast_nullable_to_non_nullable as bool, + roles: null == roles + ? _value._roles + : roles // ignore: cast_nullable_to_non_nullable + as List, + facility: freezed == facility + ? _value.facility + : facility // ignore: cast_nullable_to_non_nullable + as String?, + national_id: freezed == national_id + ? _value.national_id + : national_id // ignore: cast_nullable_to_non_nullable + as String?, + sha_id: freezed == sha_id + ? _value.sha_id + : sha_id // ignore: cast_nullable_to_non_nullable + as String?, + provider_id: freezed == provider_id + ? _value.provider_id + : provider_id // ignore: cast_nullable_to_non_nullable + as String?, )); } } @@ -421,7 +486,13 @@ class _$UserImpl with DiagnosticableTreeMixin implements _User { this.primaryLanguage, this.occupation, this.profileUpdated = false, - this.accountVerified = false}); + this.accountVerified = false, + final List roles = const [], + this.facility, + this.national_id, + this.sha_id, + this.provider_id}) + : _roles = roles; factory _$UserImpl.fromJson(Map json) => _$$UserImplFromJson(json); @@ -479,10 +550,27 @@ class _$UserImpl with DiagnosticableTreeMixin implements _User { @override @JsonKey() final bool accountVerified; + final List _roles; + @override + @JsonKey() + List get roles { + if (_roles is EqualUnmodifiableListView) return _roles; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_roles); + } + + @override + final String? facility; + @override + final String? national_id; + @override + final String? sha_id; + @override + final String? provider_id; @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'User(id: $id, image: $image, username: $username, firstName: $firstName, lastName: $lastName, name: $name, dateOfBirth: $dateOfBirth, gender: $gender, email: $email, phoneNumber: $phoneNumber, county: $county, constituency: $constituency, bloodGroup: $bloodGroup, allergies: $allergies, disabilities: $disabilities, chronics: $chronics, weight: $weight, height: $height, maritalStatus: $maritalStatus, educationLevel: $educationLevel, primaryLanguage: $primaryLanguage, occupation: $occupation, profileUpdated: $profileUpdated, accountVerified: $accountVerified)'; + return 'User(id: $id, image: $image, username: $username, firstName: $firstName, lastName: $lastName, name: $name, dateOfBirth: $dateOfBirth, gender: $gender, email: $email, phoneNumber: $phoneNumber, county: $county, constituency: $constituency, bloodGroup: $bloodGroup, allergies: $allergies, disabilities: $disabilities, chronics: $chronics, weight: $weight, height: $height, maritalStatus: $maritalStatus, educationLevel: $educationLevel, primaryLanguage: $primaryLanguage, occupation: $occupation, profileUpdated: $profileUpdated, accountVerified: $accountVerified, roles: $roles, facility: $facility, national_id: $national_id, sha_id: $sha_id, provider_id: $provider_id)'; } @override @@ -513,7 +601,12 @@ class _$UserImpl with DiagnosticableTreeMixin implements _User { ..add(DiagnosticsProperty('primaryLanguage', primaryLanguage)) ..add(DiagnosticsProperty('occupation', occupation)) ..add(DiagnosticsProperty('profileUpdated', profileUpdated)) - ..add(DiagnosticsProperty('accountVerified', accountVerified)); + ..add(DiagnosticsProperty('accountVerified', accountVerified)) + ..add(DiagnosticsProperty('roles', roles)) + ..add(DiagnosticsProperty('facility', facility)) + ..add(DiagnosticsProperty('national_id', national_id)) + ..add(DiagnosticsProperty('sha_id', sha_id)) + ..add(DiagnosticsProperty('provider_id', provider_id)); } @override @@ -560,7 +653,15 @@ class _$UserImpl with DiagnosticableTreeMixin implements _User { (identical(other.profileUpdated, profileUpdated) || other.profileUpdated == profileUpdated) && (identical(other.accountVerified, accountVerified) || - other.accountVerified == accountVerified)); + other.accountVerified == accountVerified) && + const DeepCollectionEquality().equals(other._roles, _roles) && + (identical(other.facility, facility) || + other.facility == facility) && + (identical(other.national_id, national_id) || + other.national_id == national_id) && + (identical(other.sha_id, sha_id) || other.sha_id == sha_id) && + (identical(other.provider_id, provider_id) || + other.provider_id == provider_id)); } @JsonKey(ignore: true) @@ -590,7 +691,12 @@ class _$UserImpl with DiagnosticableTreeMixin implements _User { primaryLanguage, occupation, profileUpdated, - accountVerified + accountVerified, + const DeepCollectionEquality().hash(_roles), + facility, + national_id, + sha_id, + provider_id ]); @JsonKey(ignore: true) @@ -632,7 +738,12 @@ abstract class _User implements User { final String? primaryLanguage, final String? occupation, final bool profileUpdated, - final bool accountVerified}) = _$UserImpl; + final bool accountVerified, + final List roles, + final String? facility, + final String? national_id, + final String? sha_id, + final String? provider_id}) = _$UserImpl; factory _User.fromJson(Map json) = _$UserImpl.fromJson; @@ -687,6 +798,16 @@ abstract class _User implements User { @override bool get accountVerified; @override + List get roles; + @override + String? get facility; + @override + String? get national_id; + @override + String? get sha_id; + @override + String? get provider_id; + @override @JsonKey(ignore: true) _$$UserImplCopyWith<_$UserImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/src/features/user/data/models/user.g.dart b/lib/src/features/user/data/models/user.g.dart index fcf15a0e..ff6f59ad 100644 --- a/lib/src/features/user/data/models/user.g.dart +++ b/lib/src/features/user/data/models/user.g.dart @@ -31,6 +31,13 @@ _$UserImpl _$$UserImplFromJson(Map json) => _$UserImpl( occupation: json['occupation'] as String?, profileUpdated: json['profileUpdated'] as bool? ?? false, accountVerified: json['accountVerified'] as bool? ?? false, + roles: + (json['roles'] as List?)?.map((e) => e as String).toList() ?? + const [], + facility: json['facility'] as String?, + national_id: json['national_id'] as String?, + sha_id: json['sha_id'] as String?, + provider_id: json['provider_id'] as String?, ); Map _$$UserImplToJson(_$UserImpl instance) => @@ -59,4 +66,9 @@ Map _$$UserImplToJson(_$UserImpl instance) => 'occupation': instance.occupation, 'profileUpdated': instance.profileUpdated, 'accountVerified': instance.accountVerified, + 'roles': instance.roles, + 'facility': instance.facility, + 'national_id': instance.national_id, + 'sha_id': instance.sha_id, + 'provider_id': instance.provider_id, }; diff --git a/lib/src/features/user/data/services/UserService.dart b/lib/src/features/user/data/services/UserService.dart index a79921a0..d52897a0 100644 --- a/lib/src/features/user/data/services/UserService.dart +++ b/lib/src/features/user/data/services/UserService.dart @@ -72,7 +72,6 @@ class UserService extends HTTPService { Future getUser() async { final response = await call(getUser_, null); - print(response.statusCode); final responseString = await response.stream.bytesToString(); final userData = json.decode(responseString); final Map person = userData["data"]; @@ -102,6 +101,11 @@ class UserService extends HTTPService { "disabilities": person["profile"]["disabilities"], "chronics": person["profile"]["chronics"], "gender": person["profile"]["gender"], + "roles": ["Patient", "Provider", "admin", "Facility Admin", "Partner"], + "facility": person["profile"]["facility"], + "sha_id": "26263348-001-P-4", + "national_id": person["profile"]["national_id"], + "provider_id": "no", }); } @@ -151,7 +155,6 @@ class UserService extends HTTPService { Future saveGenderAge() async { - try{ final resp = await call(patientData_, null); if (resp.statusCode == 200) { final responseString = await resp.stream.bytesToString(); @@ -163,12 +166,17 @@ class UserService extends HTTPService { await _repository.saveAge(calculateAge(dob).toString()); } else{ - throw respData["message"]; + final profileDOB = await call(getUser_,null); + final profileString = await profileDOB.stream.bytesToString(); + final data = json.decode(profileString); + final dob = data["profile"]["dob"]; + final gender = data["profile"]["gender"]; + await _repository.saveAge(calculateAge(dob).toString()); + await _repository.saveGender(gender); } + } else { + throw "something is wrong"; } - } catch (e) { - throw e; - } } Future accountVerify(Map data) async { http.StreamedResponse response = await call(accountVerify_, data); diff --git a/lib/src/features/user/presentation/forms/Identifications.dart b/lib/src/features/user/presentation/forms/Identifications.dart new file mode 100644 index 00000000..fae5e13b --- /dev/null +++ b/lib/src/features/user/presentation/forms/Identifications.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:nishauri/src/features/user/data/providers/user_provider.dart'; +import 'package:nishauri/src/shared/styles/input_styles.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class Identifications extends StatelessWidget { + const Identifications({super.key}); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, ref, child) { + final asyncUser = ref.watch(userProvider); + + return asyncUser.when( + data: (user) => Column( + children: [ + const SizedBox(height: Constants.SPACING), + FormBuilderTextField( + initialValue: user.national_id, + name: "national_id", + keyboardType: TextInputType.text, + decoration: inputDecoration( + placeholder: "Enter your National Identification Number", + prefixIcon: Icons.card_membership, + label: "National ID", + ), + validator: FormBuilderValidators.compose([ + // FormBuilderValidators.required(), + FormBuilderValidators.email(), + ]), + ), + const SizedBox(height: Constants.SPACING), + FormBuilderTextField( + name: "sha_id", + initialValue: user.sha_id, + keyboardType: TextInputType.text, + maxLength: 10, + decoration: inputDecoration( + placeholder: "Enter your Social Health Authority ID Number", + prefixIcon: Icons.card_membership, + label: "SHA Number", + ), + validator: FormBuilderValidators.compose([ + (value) { + if (value != null && + value.isNotEmpty && + !value.startsWith('0')) { + return 'SHA Number must have not less than 10 values'; + } + return null; + }, + ]), + ), + ], + ), + error: (error, _) => Center(child: Text(error.toString())), + loading: () => const Center( + child: CircularProgressIndicator(), + ), + ); + }, + ); + } +} diff --git a/lib/src/features/user/presentation/forms/forms.dart b/lib/src/features/user/presentation/forms/forms.dart index d9fc13c9..f700f9e3 100644 --- a/lib/src/features/user/presentation/forms/forms.dart +++ b/lib/src/features/user/presentation/forms/forms.dart @@ -4,4 +4,5 @@ export 'HealthInformation.dart'; export 'LifeStyleInformation.dart'; export 'PersonalInformation.dart'; export 'PhysicalCharacteristicInformation.dart'; -export 'ReviewAndSubmit.dart'; \ No newline at end of file +export 'ReviewAndSubmit.dart'; +export 'Identifications.dart'; \ No newline at end of file diff --git a/lib/src/features/user/presentation/pages/ProfileScreen.dart b/lib/src/features/user/presentation/pages/ProfileScreen.dart index 1d7617e8..355bb15b 100644 --- a/lib/src/features/user/presentation/pages/ProfileScreen.dart +++ b/lib/src/features/user/presentation/pages/ProfileScreen.dart @@ -43,6 +43,7 @@ class ProfileScreen extends HookWidget { return userAsync.when( data: (user) => ProfileCard( height: MediaQuery.of(context).size.height, + color: Colors.black54, header: user.username == 'null null' || user.username == "" ? GestureDetector( onTap: () { @@ -76,6 +77,14 @@ class ProfileScreen extends HookWidget { : Text(user.username ?? ''), ), const Divider(), + ListTile( + leading: const Icon(Icons.perm_identity), + title: const Text("Roles"), + subtitle: user.roles.isEmpty + ? Text("") + : Text("${user.roles[0] ?? ''}, ${user.roles[1] ?? ''}"), + ), + const Divider(), ListTile( leading: const Icon(Icons.email), title: const Text("Email"), @@ -105,17 +114,12 @@ class ProfileScreen extends HookWidget { : "None", ), ), - // const Divider(), - // ListTile( - // leading: const Icon(Icons.calendar_month), - // title: const Text("Date of birth"), - // subtitle: Text( - // DateTime.tryParse(user.dateOfBirth ?? "") != null - // ? DateFormat("dd MM yyyy") - // .format(DateTime.parse(user.dateOfBirth!)) - // : "None", - // ), - // ), + const Divider(), + ListTile( + leading: const Icon(Icons.person_pin_outlined), + title: const Text("Social Health Authority Id Number"), + subtitle: Text(user.sha_id??''), + ), const Divider(), ], ), diff --git a/lib/src/features/user/presentation/pages/ProfileWizardFormScreen.dart b/lib/src/features/user/presentation/pages/ProfileWizardFormScreen.dart index 95b66fb7..4ad1bd22 100644 --- a/lib/src/features/user/presentation/pages/ProfileWizardFormScreen.dart +++ b/lib/src/features/user/presentation/pages/ProfileWizardFormScreen.dart @@ -5,7 +5,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:nishauri/src/features/auth/data/providers/auth_provider.dart'; -import 'package:nishauri/src/features/user/data/models/user.dart'; +import 'package:nishauri/src/features/provider/provider_registry/data/providers/provider_registry_provider.dart'; import 'package:nishauri/src/features/user/data/providers/user_provider.dart'; import 'package:nishauri/src/features/user/presentation/forms/forms.dart'; import 'package:nishauri/src/shared/display/AppCard.dart'; @@ -36,6 +36,7 @@ class ProfileWizardFormScreen extends HookConsumerWidget { // "county", "landmark" ], + ["national_id", "sha_id"], ["blood_group", "allergies", "disabilities", "chronics"], ["weight", "height"], ["maritalStatus", "educationLevel", "primaryLanguage", "occupation"], @@ -65,26 +66,34 @@ class ProfileWizardFormScreen extends HookConsumerWidget { content: const ContactInformation(), isActive: currentStep.value == 2, ), + Step( + title: const Text("Personal Identification Information"), + subtitle: const Text( + "Provide your identifications for comprehensive medical information", + ), + content: const Identifications(), + isActive: currentStep.value == 3, + ), Step( title: const Text("Health Information"), subtitle: const Text( " Share important health details for better healthcare assistance."), content: const HealthInformation(), - isActive: currentStep.value == 3, + isActive: currentStep.value == 4, ), Step( title: const Text("Physical Characteristics"), subtitle: const Text( "Provide information about your physical attributes for a more comprehensive"), content: const PhysicalCharacteristicInformation(), - isActive: currentStep.value == 4, + isActive: currentStep.value == 5, ), Step( title: const Text("Social Information"), subtitle: const Text( "Share aspects of your lifestyle that may influence your health."), content: const LifeStyleInformation(), - isActive: currentStep.value == 5, + isActive: currentStep.value == 6, ), Step( title: const Text("Review and Submit"), @@ -110,7 +119,7 @@ class ProfileWizardFormScreen extends HookConsumerWidget { ), ), ), - isActive: currentStep.value == 6, + isActive: currentStep.value == 7, ), ]; @@ -127,6 +136,11 @@ class ProfileWizardFormScreen extends HookConsumerWidget { // Update auth state and redirect to home return ref.read(authStateProvider.notifier).markProfileAsUpdated(); }).then((value) { + final idNumber = formKey.currentState!.instantValue["national_id"]; + if (idNumber != null) { + final data = {"national_id" : idNumber}; + ref.watch(providerRegistryRepositoryProvider).searchProviderRegistry(data); + }; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("Profile updated successfully!"))); }).catchError((e) { diff --git a/lib/src/features/user_preference/data/models/Setings.dart b/lib/src/features/user_preference/data/models/Setings.dart index a6d4b785..e1531c8e 100644 --- a/lib/src/features/user_preference/data/models/Setings.dart +++ b/lib/src/features/user_preference/data/models/Setings.dart @@ -7,6 +7,7 @@ class Settings { final bool isBiometricEnabled; final bool firstTimeInstallation; final bool firstNuruAccess; + final bool firstTimeNoProgram; Settings( {required this.userToken, @@ -16,7 +17,8 @@ class Settings { required this.isBiometricEnabled, required this.isAuthenticated, required this.firstTimeInstallation, - this.firstNuruAccess = false}); + required this.firstTimeNoProgram, + required this.firstNuruAccess}); // Create a default instance with initial values factory Settings.defaultSettings() { @@ -34,7 +36,8 @@ class Settings { // Provide the initial biometric setting firstTimeInstallation: false, // Provide the initial Nuru access setting - firstNuruAccess: false); + firstNuruAccess: false, + firstTimeNoProgram: false); } // Create an instance with values copied from another Settings instance @@ -47,6 +50,7 @@ class Settings { bool? isAuthenticated, bool? firstTimeInstallation, bool? firstNuruAccess, + bool? firstTimeNoProgram, }) { return Settings( userToken: userToken ?? this.userToken, @@ -58,6 +62,7 @@ class Settings { firstTimeInstallation: firstTimeInstallation ?? this.firstTimeInstallation, firstNuruAccess: firstNuruAccess ?? this.firstNuruAccess, + firstTimeNoProgram: firstTimeNoProgram ?? this.firstTimeNoProgram, ); } diff --git a/lib/src/features/user_preference/presentation/controlers/SettingsControler.dart b/lib/src/features/user_preference/presentation/controlers/SettingsControler.dart index ae0856eb..cf0d4785 100644 --- a/lib/src/features/user_preference/presentation/controlers/SettingsControler.dart +++ b/lib/src/features/user_preference/presentation/controlers/SettingsControler.dart @@ -13,13 +13,21 @@ class SettingsController extends StateNotifier { final theme = await LocalStorage.get("theme"); final isPrivacyEnabled = await LocalStorage.get("isPrivacyEnabled"); final firstTimeInstallation = await LocalStorage.get("initial"); + final firstTimeNoProgram = await LocalStorage.get("program_update"); + final firstNuruAccess = await LocalStorage.get("firstNuruAccess"); state = state.copyWith( theme: theme.isNotEmpty ? theme : "light", isPrivacyEnabled: isPrivacyEnabled.isEmpty ? true : isPrivacyEnabled == "1", firstTimeInstallation: firstTimeInstallation.isEmpty ? true - : firstTimeInstallation == "1"); + : firstTimeInstallation == "1", + firstTimeNoProgram: firstTimeNoProgram.isEmpty + ? true + : firstTimeNoProgram == "1", + firstNuruAccess: firstNuruAccess.isEmpty + ? true + : firstNuruAccess == "1"); } Future saveSettingConfig(Settings settings) async { @@ -30,12 +38,15 @@ class SettingsController extends StateNotifier { "initial", settings.firstTimeInstallation ? "1" : "0"); await LocalStorage.save( "firstNuruAccess", settings.firstNuruAccess ? "1" : "0"); + await LocalStorage.save("program_update", settings.firstTimeNoProgram ? "1" : "0"); } Future clearSettingConfig() async { await LocalStorage.delete("theme"); await LocalStorage.delete("isPrivacyEnabled"); await LocalStorage.delete("initial"); + await LocalStorage.delete("program_update"); + await LocalStorage.delete("firstNuruAccess"); } void updateSettings({ @@ -47,6 +58,7 @@ class SettingsController extends StateNotifier { bool? isAuthenticated, bool? firstTimeInstallation, bool? firstNuruAccess, + bool? firstTimeNoProgram, }) { state = state.copyWith( userToken: userToken, @@ -56,7 +68,8 @@ class SettingsController extends StateNotifier { isBiometricEnabled: isBiometricEnabled, isAuthenticated: isAuthenticated, firstTimeInstallation: firstTimeInstallation, - firstNuruAccess: firstNuruAccess); + firstNuruAccess: firstNuruAccess, + firstTimeNoProgram: firstTimeNoProgram); saveSettingConfig(state); } @@ -73,6 +86,7 @@ class SettingsController extends StateNotifier { bool? isAuthenticated, bool? firstTimeInstallation, bool? firstNuruAccess, + bool? firstTimeNoProgram, }) { state = state.copyWith( userToken: userToken ?? state.userToken, @@ -83,7 +97,8 @@ class SettingsController extends StateNotifier { isAuthenticated: isAuthenticated ?? state.isAuthenticated, firstTimeInstallation: firstTimeInstallation ?? state.firstTimeInstallation, - firstNuruAccess: firstNuruAccess ?? state.firstNuruAccess); + firstNuruAccess: firstNuruAccess ?? state.firstNuruAccess, + firstTimeNoProgram:firstTimeNoProgram ?? state.firstTimeNoProgram); saveSettingConfig(state); } diff --git a/lib/src/features/user_programs/presentation/pages/ProgramRegistrationScreen.dart b/lib/src/features/user_programs/presentation/pages/ProgramRegistrationScreen.dart index 1ba9094a..27a2b671 100644 --- a/lib/src/features/user_programs/presentation/pages/ProgramRegistrationScreen.dart +++ b/lib/src/features/user_programs/presentation/pages/ProgramRegistrationScreen.dart @@ -8,7 +8,7 @@ import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:go_router/go_router.dart'; import 'package:nishauri/src/features/auth/data/providers/auth_provider.dart'; import 'package:nishauri/src/features/user_programs/data/providers/program_provider.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/shared/display/LinkedRichText.dart'; import 'package:nishauri/src/shared/input/Button.dart'; import 'package:nishauri/src/shared/layouts/ResponsiveWidgetFormLayout.dart'; diff --git a/lib/src/features/user_programs/presentation/pages/ProgramUpdateScreen.dart b/lib/src/features/user_programs/presentation/pages/ProgramUpdateScreen.dart index df373cd4..dc0136fc 100644 --- a/lib/src/features/user_programs/presentation/pages/ProgramUpdateScreen.dart +++ b/lib/src/features/user_programs/presentation/pages/ProgramUpdateScreen.dart @@ -7,7 +7,7 @@ import 'package:go_router/go_router.dart'; import 'package:nishauri/src/features/auth/data/providers/auth_provider.dart'; import 'package:nishauri/src/features/user_programs/data/models/user_program.dart'; import 'package:nishauri/src/features/user_programs/data/providers/program_provider.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; import 'package:nishauri/src/shared/input/Button.dart'; import 'package:nishauri/src/shared/layouts/ResponsiveWidgetFormLayout.dart'; import 'package:nishauri/src/shared/styles/input_styles.dart'; diff --git a/lib/src/features/visits/data/models/allergy.dart b/lib/src/features/visits/data/models/allergy.dart index ba8a2150..5da933bb 100644 --- a/lib/src/features/visits/data/models/allergy.dart +++ b/lib/src/features/visits/data/models/allergy.dart @@ -6,12 +6,12 @@ part 'allergy.g.dart'; @Freezed() class Allergy with _$Allergy { const factory Allergy({ - required String uuid, - required String allergen, - required String reaction, + String? uuid, + String? allergen, + String? reaction, String? onsetDate, - required String dateRecorded, - required String severity, + String? dateRecorded, + String? severity, }) = _Allergy; factory Allergy.fromJson(Map json)=> _$AllergyFromJson(json); diff --git a/lib/src/features/visits/data/models/allergy.freezed.dart b/lib/src/features/visits/data/models/allergy.freezed.dart index 0dd689e7..bf8ca8e5 100644 --- a/lib/src/features/visits/data/models/allergy.freezed.dart +++ b/lib/src/features/visits/data/models/allergy.freezed.dart @@ -20,12 +20,12 @@ Allergy _$AllergyFromJson(Map json) { /// @nodoc mixin _$Allergy { - String get uuid => throw _privateConstructorUsedError; - String get allergen => throw _privateConstructorUsedError; - String get reaction => throw _privateConstructorUsedError; + String? get uuid => throw _privateConstructorUsedError; + String? get allergen => throw _privateConstructorUsedError; + String? get reaction => throw _privateConstructorUsedError; String? get onsetDate => throw _privateConstructorUsedError; - String get dateRecorded => throw _privateConstructorUsedError; - String get severity => throw _privateConstructorUsedError; + String? get dateRecorded => throw _privateConstructorUsedError; + String? get severity => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -38,12 +38,12 @@ abstract class $AllergyCopyWith<$Res> { _$AllergyCopyWithImpl<$Res, Allergy>; @useResult $Res call( - {String uuid, - String allergen, - String reaction, + {String? uuid, + String? allergen, + String? reaction, String? onsetDate, - String dateRecorded, - String severity}); + String? dateRecorded, + String? severity}); } /// @nodoc @@ -59,38 +59,38 @@ class _$AllergyCopyWithImpl<$Res, $Val extends Allergy> @pragma('vm:prefer-inline') @override $Res call({ - Object? uuid = null, - Object? allergen = null, - Object? reaction = null, + Object? uuid = freezed, + Object? allergen = freezed, + Object? reaction = freezed, Object? onsetDate = freezed, - Object? dateRecorded = null, - Object? severity = null, + Object? dateRecorded = freezed, + Object? severity = freezed, }) { return _then(_value.copyWith( - uuid: null == uuid + uuid: freezed == uuid ? _value.uuid : uuid // ignore: cast_nullable_to_non_nullable - as String, - allergen: null == allergen + as String?, + allergen: freezed == allergen ? _value.allergen : allergen // ignore: cast_nullable_to_non_nullable - as String, - reaction: null == reaction + as String?, + reaction: freezed == reaction ? _value.reaction : reaction // ignore: cast_nullable_to_non_nullable - as String, + as String?, onsetDate: freezed == onsetDate ? _value.onsetDate : onsetDate // ignore: cast_nullable_to_non_nullable as String?, - dateRecorded: null == dateRecorded + dateRecorded: freezed == dateRecorded ? _value.dateRecorded : dateRecorded // ignore: cast_nullable_to_non_nullable - as String, - severity: null == severity + as String?, + severity: freezed == severity ? _value.severity : severity // ignore: cast_nullable_to_non_nullable - as String, + as String?, ) as $Val); } } @@ -103,12 +103,12 @@ abstract class _$$AllergyImplCopyWith<$Res> implements $AllergyCopyWith<$Res> { @override @useResult $Res call( - {String uuid, - String allergen, - String reaction, + {String? uuid, + String? allergen, + String? reaction, String? onsetDate, - String dateRecorded, - String severity}); + String? dateRecorded, + String? severity}); } /// @nodoc @@ -122,38 +122,38 @@ class __$$AllergyImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? uuid = null, - Object? allergen = null, - Object? reaction = null, + Object? uuid = freezed, + Object? allergen = freezed, + Object? reaction = freezed, Object? onsetDate = freezed, - Object? dateRecorded = null, - Object? severity = null, + Object? dateRecorded = freezed, + Object? severity = freezed, }) { return _then(_$AllergyImpl( - uuid: null == uuid + uuid: freezed == uuid ? _value.uuid : uuid // ignore: cast_nullable_to_non_nullable - as String, - allergen: null == allergen + as String?, + allergen: freezed == allergen ? _value.allergen : allergen // ignore: cast_nullable_to_non_nullable - as String, - reaction: null == reaction + as String?, + reaction: freezed == reaction ? _value.reaction : reaction // ignore: cast_nullable_to_non_nullable - as String, + as String?, onsetDate: freezed == onsetDate ? _value.onsetDate : onsetDate // ignore: cast_nullable_to_non_nullable as String?, - dateRecorded: null == dateRecorded + dateRecorded: freezed == dateRecorded ? _value.dateRecorded : dateRecorded // ignore: cast_nullable_to_non_nullable - as String, - severity: null == severity + as String?, + severity: freezed == severity ? _value.severity : severity // ignore: cast_nullable_to_non_nullable - as String, + as String?, )); } } @@ -162,28 +162,28 @@ class __$$AllergyImplCopyWithImpl<$Res> @JsonSerializable() class _$AllergyImpl with DiagnosticableTreeMixin implements _Allergy { const _$AllergyImpl( - {required this.uuid, - required this.allergen, - required this.reaction, + {this.uuid, + this.allergen, + this.reaction, this.onsetDate, - required this.dateRecorded, - required this.severity}); + this.dateRecorded, + this.severity}); factory _$AllergyImpl.fromJson(Map json) => _$$AllergyImplFromJson(json); @override - final String uuid; + final String? uuid; @override - final String allergen; + final String? allergen; @override - final String reaction; + final String? reaction; @override final String? onsetDate; @override - final String dateRecorded; + final String? dateRecorded; @override - final String severity; + final String? severity; @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { @@ -242,27 +242,27 @@ class _$AllergyImpl with DiagnosticableTreeMixin implements _Allergy { abstract class _Allergy implements Allergy { const factory _Allergy( - {required final String uuid, - required final String allergen, - required final String reaction, + {final String? uuid, + final String? allergen, + final String? reaction, final String? onsetDate, - required final String dateRecorded, - required final String severity}) = _$AllergyImpl; + final String? dateRecorded, + final String? severity}) = _$AllergyImpl; factory _Allergy.fromJson(Map json) = _$AllergyImpl.fromJson; @override - String get uuid; + String? get uuid; @override - String get allergen; + String? get allergen; @override - String get reaction; + String? get reaction; @override String? get onsetDate; @override - String get dateRecorded; + String? get dateRecorded; @override - String get severity; + String? get severity; @override @JsonKey(ignore: true) _$$AllergyImplCopyWith<_$AllergyImpl> get copyWith => diff --git a/lib/src/features/visits/data/models/allergy.g.dart b/lib/src/features/visits/data/models/allergy.g.dart index 49ba30c2..79362116 100644 --- a/lib/src/features/visits/data/models/allergy.g.dart +++ b/lib/src/features/visits/data/models/allergy.g.dart @@ -8,12 +8,12 @@ part of 'allergy.dart'; _$AllergyImpl _$$AllergyImplFromJson(Map json) => _$AllergyImpl( - uuid: json['uuid'] as String, - allergen: json['allergen'] as String, - reaction: json['reaction'] as String, + uuid: json['uuid'] as String?, + allergen: json['allergen'] as String?, + reaction: json['reaction'] as String?, onsetDate: json['onsetDate'] as String?, - dateRecorded: json['dateRecorded'] as String, - severity: json['severity'] as String, + dateRecorded: json['dateRecorded'] as String?, + severity: json['severity'] as String?, ); Map _$$AllergyImplToJson(_$AllergyImpl instance) => diff --git a/lib/src/features/visits/data/models/complaint.dart b/lib/src/features/visits/data/models/complaint.dart deleted file mode 100644 index c21840ab..00000000 --- a/lib/src/features/visits/data/models/complaint.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:flutter/foundation.dart'; -part 'complaint.freezed.dart'; -part 'complaint.g.dart'; - -@Freezed() -class Complaint with _$Complaint { - const factory Complaint({ - required String uuid, - required String name, - String? onsetDate, - required String dateRecorded, - required String value, - }) = _Complaint; - - factory Complaint.fromJson(Map json)=> _$ComplaintFromJson(json); -} \ No newline at end of file diff --git a/lib/src/features/visits/data/models/complaint.freezed.dart b/lib/src/features/visits/data/models/complaint.freezed.dart deleted file mode 100644 index c56a8c35..00000000 --- a/lib/src/features/visits/data/models/complaint.freezed.dart +++ /dev/null @@ -1,249 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'complaint.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -Complaint _$ComplaintFromJson(Map json) { - return _Complaint.fromJson(json); -} - -/// @nodoc -mixin _$Complaint { - String get uuid => throw _privateConstructorUsedError; - String get name => throw _privateConstructorUsedError; - String? get onsetDate => throw _privateConstructorUsedError; - String get dateRecorded => throw _privateConstructorUsedError; - String get value => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $ComplaintCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $ComplaintCopyWith<$Res> { - factory $ComplaintCopyWith(Complaint value, $Res Function(Complaint) then) = - _$ComplaintCopyWithImpl<$Res, Complaint>; - @useResult - $Res call( - {String uuid, - String name, - String? onsetDate, - String dateRecorded, - String value}); -} - -/// @nodoc -class _$ComplaintCopyWithImpl<$Res, $Val extends Complaint> - implements $ComplaintCopyWith<$Res> { - _$ComplaintCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? uuid = null, - Object? name = null, - Object? onsetDate = freezed, - Object? dateRecorded = null, - Object? value = null, - }) { - return _then(_value.copyWith( - uuid: null == uuid - ? _value.uuid - : uuid // ignore: cast_nullable_to_non_nullable - as String, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - onsetDate: freezed == onsetDate - ? _value.onsetDate - : onsetDate // ignore: cast_nullable_to_non_nullable - as String?, - dateRecorded: null == dateRecorded - ? _value.dateRecorded - : dateRecorded // ignore: cast_nullable_to_non_nullable - as String, - value: null == value - ? _value.value - : value // ignore: cast_nullable_to_non_nullable - as String, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$ComplaintImplCopyWith<$Res> - implements $ComplaintCopyWith<$Res> { - factory _$$ComplaintImplCopyWith( - _$ComplaintImpl value, $Res Function(_$ComplaintImpl) then) = - __$$ComplaintImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String uuid, - String name, - String? onsetDate, - String dateRecorded, - String value}); -} - -/// @nodoc -class __$$ComplaintImplCopyWithImpl<$Res> - extends _$ComplaintCopyWithImpl<$Res, _$ComplaintImpl> - implements _$$ComplaintImplCopyWith<$Res> { - __$$ComplaintImplCopyWithImpl( - _$ComplaintImpl _value, $Res Function(_$ComplaintImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? uuid = null, - Object? name = null, - Object? onsetDate = freezed, - Object? dateRecorded = null, - Object? value = null, - }) { - return _then(_$ComplaintImpl( - uuid: null == uuid - ? _value.uuid - : uuid // ignore: cast_nullable_to_non_nullable - as String, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - onsetDate: freezed == onsetDate - ? _value.onsetDate - : onsetDate // ignore: cast_nullable_to_non_nullable - as String?, - dateRecorded: null == dateRecorded - ? _value.dateRecorded - : dateRecorded // ignore: cast_nullable_to_non_nullable - as String, - value: null == value - ? _value.value - : value // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$ComplaintImpl with DiagnosticableTreeMixin implements _Complaint { - const _$ComplaintImpl( - {required this.uuid, - required this.name, - this.onsetDate, - required this.dateRecorded, - required this.value}); - - factory _$ComplaintImpl.fromJson(Map json) => - _$$ComplaintImplFromJson(json); - - @override - final String uuid; - @override - final String name; - @override - final String? onsetDate; - @override - final String dateRecorded; - @override - final String value; - - @override - String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'Complaint(uuid: $uuid, name: $name, onsetDate: $onsetDate, dateRecorded: $dateRecorded, value: $value)'; - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('type', 'Complaint')) - ..add(DiagnosticsProperty('uuid', uuid)) - ..add(DiagnosticsProperty('name', name)) - ..add(DiagnosticsProperty('onsetDate', onsetDate)) - ..add(DiagnosticsProperty('dateRecorded', dateRecorded)) - ..add(DiagnosticsProperty('value', value)); - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$ComplaintImpl && - (identical(other.uuid, uuid) || other.uuid == uuid) && - (identical(other.name, name) || other.name == name) && - (identical(other.onsetDate, onsetDate) || - other.onsetDate == onsetDate) && - (identical(other.dateRecorded, dateRecorded) || - other.dateRecorded == dateRecorded) && - (identical(other.value, value) || other.value == value)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => - Object.hash(runtimeType, uuid, name, onsetDate, dateRecorded, value); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$ComplaintImplCopyWith<_$ComplaintImpl> get copyWith => - __$$ComplaintImplCopyWithImpl<_$ComplaintImpl>(this, _$identity); - - @override - Map toJson() { - return _$$ComplaintImplToJson( - this, - ); - } -} - -abstract class _Complaint implements Complaint { - const factory _Complaint( - {required final String uuid, - required final String name, - final String? onsetDate, - required final String dateRecorded, - required final String value}) = _$ComplaintImpl; - - factory _Complaint.fromJson(Map json) = - _$ComplaintImpl.fromJson; - - @override - String get uuid; - @override - String get name; - @override - String? get onsetDate; - @override - String get dateRecorded; - @override - String get value; - @override - @JsonKey(ignore: true) - _$$ComplaintImplCopyWith<_$ComplaintImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/src/features/visits/data/models/condition.dart b/lib/src/features/visits/data/models/condition.dart index 0ff22a12..f2c3e8a2 100644 --- a/lib/src/features/visits/data/models/condition.dart +++ b/lib/src/features/visits/data/models/condition.dart @@ -6,12 +6,12 @@ part 'condition.g.dart'; @Freezed() class Condition with _$Condition { const factory Condition({ - required String uuid, - required String name, + String? uuid, + String? name, String? onsetDate, - required String dateRecorded, - required String status, - required String value, + String? dateRecorded, + String? status, + String? value, }) = _Condition; factory Condition.fromJson(Map json)=> _$ConditionFromJson(json); diff --git a/lib/src/features/visits/data/models/condition.freezed.dart b/lib/src/features/visits/data/models/condition.freezed.dart index 24dd0e10..bf3f42b9 100644 --- a/lib/src/features/visits/data/models/condition.freezed.dart +++ b/lib/src/features/visits/data/models/condition.freezed.dart @@ -20,12 +20,12 @@ Condition _$ConditionFromJson(Map json) { /// @nodoc mixin _$Condition { - String get uuid => throw _privateConstructorUsedError; - String get name => throw _privateConstructorUsedError; + String? get uuid => throw _privateConstructorUsedError; + String? get name => throw _privateConstructorUsedError; String? get onsetDate => throw _privateConstructorUsedError; - String get dateRecorded => throw _privateConstructorUsedError; - String get status => throw _privateConstructorUsedError; - String get value => throw _privateConstructorUsedError; + String? get dateRecorded => throw _privateConstructorUsedError; + String? get status => throw _privateConstructorUsedError; + String? get value => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -39,12 +39,12 @@ abstract class $ConditionCopyWith<$Res> { _$ConditionCopyWithImpl<$Res, Condition>; @useResult $Res call( - {String uuid, - String name, + {String? uuid, + String? name, String? onsetDate, - String dateRecorded, - String status, - String value}); + String? dateRecorded, + String? status, + String? value}); } /// @nodoc @@ -60,38 +60,38 @@ class _$ConditionCopyWithImpl<$Res, $Val extends Condition> @pragma('vm:prefer-inline') @override $Res call({ - Object? uuid = null, - Object? name = null, + Object? uuid = freezed, + Object? name = freezed, Object? onsetDate = freezed, - Object? dateRecorded = null, - Object? status = null, - Object? value = null, + Object? dateRecorded = freezed, + Object? status = freezed, + Object? value = freezed, }) { return _then(_value.copyWith( - uuid: null == uuid + uuid: freezed == uuid ? _value.uuid : uuid // ignore: cast_nullable_to_non_nullable - as String, - name: null == name + as String?, + name: freezed == name ? _value.name : name // ignore: cast_nullable_to_non_nullable - as String, + as String?, onsetDate: freezed == onsetDate ? _value.onsetDate : onsetDate // ignore: cast_nullable_to_non_nullable as String?, - dateRecorded: null == dateRecorded + dateRecorded: freezed == dateRecorded ? _value.dateRecorded : dateRecorded // ignore: cast_nullable_to_non_nullable - as String, - status: null == status + as String?, + status: freezed == status ? _value.status : status // ignore: cast_nullable_to_non_nullable - as String, - value: null == value + as String?, + value: freezed == value ? _value.value : value // ignore: cast_nullable_to_non_nullable - as String, + as String?, ) as $Val); } } @@ -105,12 +105,12 @@ abstract class _$$ConditionImplCopyWith<$Res> @override @useResult $Res call( - {String uuid, - String name, + {String? uuid, + String? name, String? onsetDate, - String dateRecorded, - String status, - String value}); + String? dateRecorded, + String? status, + String? value}); } /// @nodoc @@ -124,38 +124,38 @@ class __$$ConditionImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? uuid = null, - Object? name = null, + Object? uuid = freezed, + Object? name = freezed, Object? onsetDate = freezed, - Object? dateRecorded = null, - Object? status = null, - Object? value = null, + Object? dateRecorded = freezed, + Object? status = freezed, + Object? value = freezed, }) { return _then(_$ConditionImpl( - uuid: null == uuid + uuid: freezed == uuid ? _value.uuid : uuid // ignore: cast_nullable_to_non_nullable - as String, - name: null == name + as String?, + name: freezed == name ? _value.name : name // ignore: cast_nullable_to_non_nullable - as String, + as String?, onsetDate: freezed == onsetDate ? _value.onsetDate : onsetDate // ignore: cast_nullable_to_non_nullable as String?, - dateRecorded: null == dateRecorded + dateRecorded: freezed == dateRecorded ? _value.dateRecorded : dateRecorded // ignore: cast_nullable_to_non_nullable - as String, - status: null == status + as String?, + status: freezed == status ? _value.status : status // ignore: cast_nullable_to_non_nullable - as String, - value: null == value + as String?, + value: freezed == value ? _value.value : value // ignore: cast_nullable_to_non_nullable - as String, + as String?, )); } } @@ -164,28 +164,28 @@ class __$$ConditionImplCopyWithImpl<$Res> @JsonSerializable() class _$ConditionImpl with DiagnosticableTreeMixin implements _Condition { const _$ConditionImpl( - {required this.uuid, - required this.name, + {this.uuid, + this.name, this.onsetDate, - required this.dateRecorded, - required this.status, - required this.value}); + this.dateRecorded, + this.status, + this.value}); factory _$ConditionImpl.fromJson(Map json) => _$$ConditionImplFromJson(json); @override - final String uuid; + final String? uuid; @override - final String name; + final String? name; @override final String? onsetDate; @override - final String dateRecorded; + final String? dateRecorded; @override - final String status; + final String? status; @override - final String value; + final String? value; @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { @@ -241,28 +241,28 @@ class _$ConditionImpl with DiagnosticableTreeMixin implements _Condition { abstract class _Condition implements Condition { const factory _Condition( - {required final String uuid, - required final String name, + {final String? uuid, + final String? name, final String? onsetDate, - required final String dateRecorded, - required final String status, - required final String value}) = _$ConditionImpl; + final String? dateRecorded, + final String? status, + final String? value}) = _$ConditionImpl; factory _Condition.fromJson(Map json) = _$ConditionImpl.fromJson; @override - String get uuid; + String? get uuid; @override - String get name; + String? get name; @override String? get onsetDate; @override - String get dateRecorded; + String? get dateRecorded; @override - String get status; + String? get status; @override - String get value; + String? get value; @override @JsonKey(ignore: true) _$$ConditionImplCopyWith<_$ConditionImpl> get copyWith => diff --git a/lib/src/features/visits/data/models/condition.g.dart b/lib/src/features/visits/data/models/condition.g.dart index 0d80af4a..ff0c0ba3 100644 --- a/lib/src/features/visits/data/models/condition.g.dart +++ b/lib/src/features/visits/data/models/condition.g.dart @@ -8,12 +8,12 @@ part of 'condition.dart'; _$ConditionImpl _$$ConditionImplFromJson(Map json) => _$ConditionImpl( - uuid: json['uuid'] as String, - name: json['name'] as String, + uuid: json['uuid'] as String?, + name: json['name'] as String?, onsetDate: json['onsetDate'] as String?, - dateRecorded: json['dateRecorded'] as String, - status: json['status'] as String, - value: json['value'] as String, + dateRecorded: json['dateRecorded'] as String?, + status: json['status'] as String?, + value: json['value'] as String?, ); Map _$$ConditionImplToJson(_$ConditionImpl instance) => diff --git a/lib/src/features/visits/data/models/diagnosis.dart b/lib/src/features/visits/data/models/diagnosis.dart deleted file mode 100644 index a0aa3b61..00000000 --- a/lib/src/features/visits/data/models/diagnosis.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:flutter/foundation.dart'; -part 'diagnosis.freezed.dart'; -part 'diagnosis.g.dart'; - -@Freezed() -class Diagnosis with _$Diagnosis { - const factory Diagnosis({ - required String uuid, - required String name, - required String dateRecorded, - required String value, - }) = _Diagnosis; - - factory Diagnosis.fromJson(Map json)=> _$DiagnosisFromJson(json); -} \ No newline at end of file diff --git a/lib/src/features/visits/data/models/diagnosis.freezed.dart b/lib/src/features/visits/data/models/diagnosis.freezed.dart deleted file mode 100644 index 3b5cadf4..00000000 --- a/lib/src/features/visits/data/models/diagnosis.freezed.dart +++ /dev/null @@ -1,218 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'diagnosis.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -Diagnosis _$DiagnosisFromJson(Map json) { - return _Diagnosis.fromJson(json); -} - -/// @nodoc -mixin _$Diagnosis { - String get uuid => throw _privateConstructorUsedError; - String get name => throw _privateConstructorUsedError; - String get dateRecorded => throw _privateConstructorUsedError; - String get value => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $DiagnosisCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $DiagnosisCopyWith<$Res> { - factory $DiagnosisCopyWith(Diagnosis value, $Res Function(Diagnosis) then) = - _$DiagnosisCopyWithImpl<$Res, Diagnosis>; - @useResult - $Res call({String uuid, String name, String dateRecorded, String value}); -} - -/// @nodoc -class _$DiagnosisCopyWithImpl<$Res, $Val extends Diagnosis> - implements $DiagnosisCopyWith<$Res> { - _$DiagnosisCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? uuid = null, - Object? name = null, - Object? dateRecorded = null, - Object? value = null, - }) { - return _then(_value.copyWith( - uuid: null == uuid - ? _value.uuid - : uuid // ignore: cast_nullable_to_non_nullable - as String, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - dateRecorded: null == dateRecorded - ? _value.dateRecorded - : dateRecorded // ignore: cast_nullable_to_non_nullable - as String, - value: null == value - ? _value.value - : value // ignore: cast_nullable_to_non_nullable - as String, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$DiagnosisImplCopyWith<$Res> - implements $DiagnosisCopyWith<$Res> { - factory _$$DiagnosisImplCopyWith( - _$DiagnosisImpl value, $Res Function(_$DiagnosisImpl) then) = - __$$DiagnosisImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({String uuid, String name, String dateRecorded, String value}); -} - -/// @nodoc -class __$$DiagnosisImplCopyWithImpl<$Res> - extends _$DiagnosisCopyWithImpl<$Res, _$DiagnosisImpl> - implements _$$DiagnosisImplCopyWith<$Res> { - __$$DiagnosisImplCopyWithImpl( - _$DiagnosisImpl _value, $Res Function(_$DiagnosisImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? uuid = null, - Object? name = null, - Object? dateRecorded = null, - Object? value = null, - }) { - return _then(_$DiagnosisImpl( - uuid: null == uuid - ? _value.uuid - : uuid // ignore: cast_nullable_to_non_nullable - as String, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - dateRecorded: null == dateRecorded - ? _value.dateRecorded - : dateRecorded // ignore: cast_nullable_to_non_nullable - as String, - value: null == value - ? _value.value - : value // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$DiagnosisImpl with DiagnosticableTreeMixin implements _Diagnosis { - const _$DiagnosisImpl( - {required this.uuid, - required this.name, - required this.dateRecorded, - required this.value}); - - factory _$DiagnosisImpl.fromJson(Map json) => - _$$DiagnosisImplFromJson(json); - - @override - final String uuid; - @override - final String name; - @override - final String dateRecorded; - @override - final String value; - - @override - String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'Diagnosis(uuid: $uuid, name: $name, dateRecorded: $dateRecorded, value: $value)'; - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('type', 'Diagnosis')) - ..add(DiagnosticsProperty('uuid', uuid)) - ..add(DiagnosticsProperty('name', name)) - ..add(DiagnosticsProperty('dateRecorded', dateRecorded)) - ..add(DiagnosticsProperty('value', value)); - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$DiagnosisImpl && - (identical(other.uuid, uuid) || other.uuid == uuid) && - (identical(other.name, name) || other.name == name) && - (identical(other.dateRecorded, dateRecorded) || - other.dateRecorded == dateRecorded) && - (identical(other.value, value) || other.value == value)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash(runtimeType, uuid, name, dateRecorded, value); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$DiagnosisImplCopyWith<_$DiagnosisImpl> get copyWith => - __$$DiagnosisImplCopyWithImpl<_$DiagnosisImpl>(this, _$identity); - - @override - Map toJson() { - return _$$DiagnosisImplToJson( - this, - ); - } -} - -abstract class _Diagnosis implements Diagnosis { - const factory _Diagnosis( - {required final String uuid, - required final String name, - required final String dateRecorded, - required final String value}) = _$DiagnosisImpl; - - factory _Diagnosis.fromJson(Map json) = - _$DiagnosisImpl.fromJson; - - @override - String get uuid; - @override - String get name; - @override - String get dateRecorded; - @override - String get value; - @override - @JsonKey(ignore: true) - _$$DiagnosisImplCopyWith<_$DiagnosisImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/src/features/visits/data/models/diagnosis.g.dart b/lib/src/features/visits/data/models/diagnosis.g.dart deleted file mode 100644 index 0f4c096c..00000000 --- a/lib/src/features/visits/data/models/diagnosis.g.dart +++ /dev/null @@ -1,23 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'diagnosis.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$DiagnosisImpl _$$DiagnosisImplFromJson(Map json) => - _$DiagnosisImpl( - uuid: json['uuid'] as String, - name: json['name'] as String, - dateRecorded: json['dateRecorded'] as String, - value: json['value'] as String, - ); - -Map _$$DiagnosisImplToJson(_$DiagnosisImpl instance) => - { - 'uuid': instance.uuid, - 'name': instance.name, - 'dateRecorded': instance.dateRecorded, - 'value': instance.value, - }; diff --git a/lib/src/features/visits/data/models/immunization.dart b/lib/src/features/visits/data/models/immunization.dart new file mode 100644 index 00000000..a769a67c --- /dev/null +++ b/lib/src/features/visits/data/models/immunization.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +part 'immunization.freezed.dart'; +part 'immunization.g.dart'; + +@Freezed() +class Immunization with _$Immunization { + const factory Immunization({ + String? uuid, + String? name, + String? immunizationDate, + String? manufacturer, + String? lot, + }) = _Immunization; + + factory Immunization.fromJson(Map json)=> _$ImmunizationFromJson(json); +} \ No newline at end of file diff --git a/lib/src/features/visits/data/models/immunization.freezed.dart b/lib/src/features/visits/data/models/immunization.freezed.dart new file mode 100644 index 00000000..3c44a993 --- /dev/null +++ b/lib/src/features/visits/data/models/immunization.freezed.dart @@ -0,0 +1,238 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'immunization.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Immunization _$ImmunizationFromJson(Map json) { + return _Immunization.fromJson(json); +} + +/// @nodoc +mixin _$Immunization { + String? get uuid => throw _privateConstructorUsedError; + String? get name => throw _privateConstructorUsedError; + String? get immunizationDate => throw _privateConstructorUsedError; + String? get manufacturer => throw _privateConstructorUsedError; + String? get lot => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ImmunizationCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ImmunizationCopyWith<$Res> { + factory $ImmunizationCopyWith( + Immunization value, $Res Function(Immunization) then) = + _$ImmunizationCopyWithImpl<$Res, Immunization>; + @useResult + $Res call( + {String? uuid, + String? name, + String? immunizationDate, + String? manufacturer, + String? lot}); +} + +/// @nodoc +class _$ImmunizationCopyWithImpl<$Res, $Val extends Immunization> + implements $ImmunizationCopyWith<$Res> { + _$ImmunizationCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? uuid = freezed, + Object? name = freezed, + Object? immunizationDate = freezed, + Object? manufacturer = freezed, + Object? lot = freezed, + }) { + return _then(_value.copyWith( + uuid: freezed == uuid + ? _value.uuid + : uuid // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + immunizationDate: freezed == immunizationDate + ? _value.immunizationDate + : immunizationDate // ignore: cast_nullable_to_non_nullable + as String?, + manufacturer: freezed == manufacturer + ? _value.manufacturer + : manufacturer // ignore: cast_nullable_to_non_nullable + as String?, + lot: freezed == lot + ? _value.lot + : lot // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ImmunizationImplCopyWith<$Res> + implements $ImmunizationCopyWith<$Res> { + factory _$$ImmunizationImplCopyWith( + _$ImmunizationImpl value, $Res Function(_$ImmunizationImpl) then) = + __$$ImmunizationImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String? uuid, + String? name, + String? immunizationDate, + String? manufacturer, + String? lot}); +} + +/// @nodoc +class __$$ImmunizationImplCopyWithImpl<$Res> + extends _$ImmunizationCopyWithImpl<$Res, _$ImmunizationImpl> + implements _$$ImmunizationImplCopyWith<$Res> { + __$$ImmunizationImplCopyWithImpl( + _$ImmunizationImpl _value, $Res Function(_$ImmunizationImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? uuid = freezed, + Object? name = freezed, + Object? immunizationDate = freezed, + Object? manufacturer = freezed, + Object? lot = freezed, + }) { + return _then(_$ImmunizationImpl( + uuid: freezed == uuid + ? _value.uuid + : uuid // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + immunizationDate: freezed == immunizationDate + ? _value.immunizationDate + : immunizationDate // ignore: cast_nullable_to_non_nullable + as String?, + manufacturer: freezed == manufacturer + ? _value.manufacturer + : manufacturer // ignore: cast_nullable_to_non_nullable + as String?, + lot: freezed == lot + ? _value.lot + : lot // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ImmunizationImpl implements _Immunization { + const _$ImmunizationImpl( + {this.uuid, + this.name, + this.immunizationDate, + this.manufacturer, + this.lot}); + + factory _$ImmunizationImpl.fromJson(Map json) => + _$$ImmunizationImplFromJson(json); + + @override + final String? uuid; + @override + final String? name; + @override + final String? immunizationDate; + @override + final String? manufacturer; + @override + final String? lot; + + @override + String toString() { + return 'Immunization(uuid: $uuid, name: $name, immunizationDate: $immunizationDate, manufacturer: $manufacturer, lot: $lot)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ImmunizationImpl && + (identical(other.uuid, uuid) || other.uuid == uuid) && + (identical(other.name, name) || other.name == name) && + (identical(other.immunizationDate, immunizationDate) || + other.immunizationDate == immunizationDate) && + (identical(other.manufacturer, manufacturer) || + other.manufacturer == manufacturer) && + (identical(other.lot, lot) || other.lot == lot)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, uuid, name, immunizationDate, manufacturer, lot); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ImmunizationImplCopyWith<_$ImmunizationImpl> get copyWith => + __$$ImmunizationImplCopyWithImpl<_$ImmunizationImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ImmunizationImplToJson( + this, + ); + } +} + +abstract class _Immunization implements Immunization { + const factory _Immunization( + {final String? uuid, + final String? name, + final String? immunizationDate, + final String? manufacturer, + final String? lot}) = _$ImmunizationImpl; + + factory _Immunization.fromJson(Map json) = + _$ImmunizationImpl.fromJson; + + @override + String? get uuid; + @override + String? get name; + @override + String? get immunizationDate; + @override + String? get manufacturer; + @override + String? get lot; + @override + @JsonKey(ignore: true) + _$$ImmunizationImplCopyWith<_$ImmunizationImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/visits/data/models/immunization.g.dart b/lib/src/features/visits/data/models/immunization.g.dart new file mode 100644 index 00000000..6ab33153 --- /dev/null +++ b/lib/src/features/visits/data/models/immunization.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'immunization.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ImmunizationImpl _$$ImmunizationImplFromJson(Map json) => + _$ImmunizationImpl( + uuid: json['uuid'] as String?, + name: json['name'] as String?, + immunizationDate: json['immunizationDate'] as String?, + manufacturer: json['manufacturer'] as String?, + lot: json['lot'] as String?, + ); + +Map _$$ImmunizationImplToJson(_$ImmunizationImpl instance) => + { + 'uuid': instance.uuid, + 'name': instance.name, + 'immunizationDate': instance.immunizationDate, + 'manufacturer': instance.manufacturer, + 'lot': instance.lot, + }; diff --git a/lib/src/features/visits/data/models/lab_result.dart b/lib/src/features/visits/data/models/lab_result.dart index 856ff6f0..762f468c 100644 --- a/lib/src/features/visits/data/models/lab_result.dart +++ b/lib/src/features/visits/data/models/lab_result.dart @@ -6,10 +6,12 @@ part 'lab_result.g.dart'; @Freezed() class LabResult with _$LabResult { const factory LabResult({ - required String uuid, - required String name, - required String dateRecorded, - required String value, + String? uuid, + String? name, + String? results, + String? orderedDate, + String? status, + double? plot, }) = _LabResult; factory LabResult.fromJson(Map json)=> _$LabResultFromJson(json); diff --git a/lib/src/features/visits/data/models/lab_result.freezed.dart b/lib/src/features/visits/data/models/lab_result.freezed.dart index 8d2470d2..89bfff68 100644 --- a/lib/src/features/visits/data/models/lab_result.freezed.dart +++ b/lib/src/features/visits/data/models/lab_result.freezed.dart @@ -20,10 +20,12 @@ LabResult _$LabResultFromJson(Map json) { /// @nodoc mixin _$LabResult { - String get uuid => throw _privateConstructorUsedError; - String get name => throw _privateConstructorUsedError; - String get dateRecorded => throw _privateConstructorUsedError; - String get value => throw _privateConstructorUsedError; + String? get uuid => throw _privateConstructorUsedError; + String? get name => throw _privateConstructorUsedError; + String? get results => throw _privateConstructorUsedError; + String? get orderedDate => throw _privateConstructorUsedError; + String? get status => throw _privateConstructorUsedError; + double? get plot => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -36,7 +38,13 @@ abstract class $LabResultCopyWith<$Res> { factory $LabResultCopyWith(LabResult value, $Res Function(LabResult) then) = _$LabResultCopyWithImpl<$Res, LabResult>; @useResult - $Res call({String uuid, String name, String dateRecorded, String value}); + $Res call( + {String? uuid, + String? name, + String? results, + String? orderedDate, + String? status, + double? plot}); } /// @nodoc @@ -52,28 +60,38 @@ class _$LabResultCopyWithImpl<$Res, $Val extends LabResult> @pragma('vm:prefer-inline') @override $Res call({ - Object? uuid = null, - Object? name = null, - Object? dateRecorded = null, - Object? value = null, + Object? uuid = freezed, + Object? name = freezed, + Object? results = freezed, + Object? orderedDate = freezed, + Object? status = freezed, + Object? plot = freezed, }) { return _then(_value.copyWith( - uuid: null == uuid + uuid: freezed == uuid ? _value.uuid : uuid // ignore: cast_nullable_to_non_nullable - as String, - name: null == name + as String?, + name: freezed == name ? _value.name : name // ignore: cast_nullable_to_non_nullable - as String, - dateRecorded: null == dateRecorded - ? _value.dateRecorded - : dateRecorded // ignore: cast_nullable_to_non_nullable - as String, - value: null == value - ? _value.value - : value // ignore: cast_nullable_to_non_nullable - as String, + as String?, + results: freezed == results + ? _value.results + : results // ignore: cast_nullable_to_non_nullable + as String?, + orderedDate: freezed == orderedDate + ? _value.orderedDate + : orderedDate // ignore: cast_nullable_to_non_nullable + as String?, + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String?, + plot: freezed == plot + ? _value.plot + : plot // ignore: cast_nullable_to_non_nullable + as double?, ) as $Val); } } @@ -86,7 +104,13 @@ abstract class _$$LabResultImplCopyWith<$Res> __$$LabResultImplCopyWithImpl<$Res>; @override @useResult - $Res call({String uuid, String name, String dateRecorded, String value}); + $Res call( + {String? uuid, + String? name, + String? results, + String? orderedDate, + String? status, + double? plot}); } /// @nodoc @@ -100,28 +124,38 @@ class __$$LabResultImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? uuid = null, - Object? name = null, - Object? dateRecorded = null, - Object? value = null, + Object? uuid = freezed, + Object? name = freezed, + Object? results = freezed, + Object? orderedDate = freezed, + Object? status = freezed, + Object? plot = freezed, }) { return _then(_$LabResultImpl( - uuid: null == uuid + uuid: freezed == uuid ? _value.uuid : uuid // ignore: cast_nullable_to_non_nullable - as String, - name: null == name + as String?, + name: freezed == name ? _value.name : name // ignore: cast_nullable_to_non_nullable - as String, - dateRecorded: null == dateRecorded - ? _value.dateRecorded - : dateRecorded // ignore: cast_nullable_to_non_nullable - as String, - value: null == value - ? _value.value - : value // ignore: cast_nullable_to_non_nullable - as String, + as String?, + results: freezed == results + ? _value.results + : results // ignore: cast_nullable_to_non_nullable + as String?, + orderedDate: freezed == orderedDate + ? _value.orderedDate + : orderedDate // ignore: cast_nullable_to_non_nullable + as String?, + status: freezed == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String?, + plot: freezed == plot + ? _value.plot + : plot // ignore: cast_nullable_to_non_nullable + as double?, )); } } @@ -130,26 +164,32 @@ class __$$LabResultImplCopyWithImpl<$Res> @JsonSerializable() class _$LabResultImpl with DiagnosticableTreeMixin implements _LabResult { const _$LabResultImpl( - {required this.uuid, - required this.name, - required this.dateRecorded, - required this.value}); + {this.uuid, + this.name, + this.results, + this.orderedDate, + this.status, + this.plot}); factory _$LabResultImpl.fromJson(Map json) => _$$LabResultImplFromJson(json); @override - final String uuid; + final String? uuid; @override - final String name; + final String? name; @override - final String dateRecorded; + final String? results; @override - final String value; + final String? orderedDate; + @override + final String? status; + @override + final double? plot; @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'LabResult(uuid: $uuid, name: $name, dateRecorded: $dateRecorded, value: $value)'; + return 'LabResult(uuid: $uuid, name: $name, results: $results, orderedDate: $orderedDate, status: $status, plot: $plot)'; } @override @@ -159,8 +199,10 @@ class _$LabResultImpl with DiagnosticableTreeMixin implements _LabResult { ..add(DiagnosticsProperty('type', 'LabResult')) ..add(DiagnosticsProperty('uuid', uuid)) ..add(DiagnosticsProperty('name', name)) - ..add(DiagnosticsProperty('dateRecorded', dateRecorded)) - ..add(DiagnosticsProperty('value', value)); + ..add(DiagnosticsProperty('results', results)) + ..add(DiagnosticsProperty('orderedDate', orderedDate)) + ..add(DiagnosticsProperty('status', status)) + ..add(DiagnosticsProperty('plot', plot)); } @override @@ -170,14 +212,17 @@ class _$LabResultImpl with DiagnosticableTreeMixin implements _LabResult { other is _$LabResultImpl && (identical(other.uuid, uuid) || other.uuid == uuid) && (identical(other.name, name) || other.name == name) && - (identical(other.dateRecorded, dateRecorded) || - other.dateRecorded == dateRecorded) && - (identical(other.value, value) || other.value == value)); + (identical(other.results, results) || other.results == results) && + (identical(other.orderedDate, orderedDate) || + other.orderedDate == orderedDate) && + (identical(other.status, status) || other.status == status) && + (identical(other.plot, plot) || other.plot == plot)); } @JsonKey(ignore: true) @override - int get hashCode => Object.hash(runtimeType, uuid, name, dateRecorded, value); + int get hashCode => + Object.hash(runtimeType, uuid, name, results, orderedDate, status, plot); @JsonKey(ignore: true) @override @@ -195,22 +240,28 @@ class _$LabResultImpl with DiagnosticableTreeMixin implements _LabResult { abstract class _LabResult implements LabResult { const factory _LabResult( - {required final String uuid, - required final String name, - required final String dateRecorded, - required final String value}) = _$LabResultImpl; + {final String? uuid, + final String? name, + final String? results, + final String? orderedDate, + final String? status, + final double? plot}) = _$LabResultImpl; factory _LabResult.fromJson(Map json) = _$LabResultImpl.fromJson; @override - String get uuid; + String? get uuid; + @override + String? get name; + @override + String? get results; @override - String get name; + String? get orderedDate; @override - String get dateRecorded; + String? get status; @override - String get value; + double? get plot; @override @JsonKey(ignore: true) _$$LabResultImplCopyWith<_$LabResultImpl> get copyWith => diff --git a/lib/src/features/visits/data/models/lab_result.g.dart b/lib/src/features/visits/data/models/lab_result.g.dart index ec6d475f..3c875bf9 100644 --- a/lib/src/features/visits/data/models/lab_result.g.dart +++ b/lib/src/features/visits/data/models/lab_result.g.dart @@ -8,16 +8,20 @@ part of 'lab_result.dart'; _$LabResultImpl _$$LabResultImplFromJson(Map json) => _$LabResultImpl( - uuid: json['uuid'] as String, - name: json['name'] as String, - dateRecorded: json['dateRecorded'] as String, - value: json['value'] as String, + uuid: json['uuid'] as String?, + name: json['name'] as String?, + results: json['results'] as String?, + orderedDate: json['orderedDate'] as String?, + status: json['status'] as String?, + plot: (json['plot'] as num?)?.toDouble(), ); Map _$$LabResultImplToJson(_$LabResultImpl instance) => { 'uuid': instance.uuid, 'name': instance.name, - 'dateRecorded': instance.dateRecorded, - 'value': instance.value, + 'results': instance.results, + 'orderedDate': instance.orderedDate, + 'status': instance.status, + 'plot': instance.plot, }; diff --git a/lib/src/features/visits/data/models/medication.dart b/lib/src/features/visits/data/models/medication.dart new file mode 100644 index 00000000..a8235ee5 --- /dev/null +++ b/lib/src/features/visits/data/models/medication.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:flutter/foundation.dart'; +part 'medication.freezed.dart'; +part 'medication.g.dart'; + +@Freezed() +class Medication with _$Medication { + const factory Medication({ + String? uuid, + String? name, + String? onsetDate, + String? dateRecorded, + String? value, + String? indication, + }) = _Medication; + + factory Medication.fromJson(Map json)=> _$MedicationFromJson(json); +} \ No newline at end of file diff --git a/lib/src/features/visits/data/models/medication.freezed.dart b/lib/src/features/visits/data/models/medication.freezed.dart new file mode 100644 index 00000000..e62a29a4 --- /dev/null +++ b/lib/src/features/visits/data/models/medication.freezed.dart @@ -0,0 +1,272 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'medication.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Medication _$MedicationFromJson(Map json) { + return _Medication.fromJson(json); +} + +/// @nodoc +mixin _$Medication { + String? get uuid => throw _privateConstructorUsedError; + String? get name => throw _privateConstructorUsedError; + String? get onsetDate => throw _privateConstructorUsedError; + String? get dateRecorded => throw _privateConstructorUsedError; + String? get value => throw _privateConstructorUsedError; + String? get indication => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $MedicationCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $MedicationCopyWith<$Res> { + factory $MedicationCopyWith( + Medication value, $Res Function(Medication) then) = + _$MedicationCopyWithImpl<$Res, Medication>; + @useResult + $Res call( + {String? uuid, + String? name, + String? onsetDate, + String? dateRecorded, + String? value, + String? indication}); +} + +/// @nodoc +class _$MedicationCopyWithImpl<$Res, $Val extends Medication> + implements $MedicationCopyWith<$Res> { + _$MedicationCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? uuid = freezed, + Object? name = freezed, + Object? onsetDate = freezed, + Object? dateRecorded = freezed, + Object? value = freezed, + Object? indication = freezed, + }) { + return _then(_value.copyWith( + uuid: freezed == uuid + ? _value.uuid + : uuid // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + onsetDate: freezed == onsetDate + ? _value.onsetDate + : onsetDate // ignore: cast_nullable_to_non_nullable + as String?, + dateRecorded: freezed == dateRecorded + ? _value.dateRecorded + : dateRecorded // ignore: cast_nullable_to_non_nullable + as String?, + value: freezed == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String?, + indication: freezed == indication + ? _value.indication + : indication // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$MedicationImplCopyWith<$Res> + implements $MedicationCopyWith<$Res> { + factory _$$MedicationImplCopyWith( + _$MedicationImpl value, $Res Function(_$MedicationImpl) then) = + __$$MedicationImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String? uuid, + String? name, + String? onsetDate, + String? dateRecorded, + String? value, + String? indication}); +} + +/// @nodoc +class __$$MedicationImplCopyWithImpl<$Res> + extends _$MedicationCopyWithImpl<$Res, _$MedicationImpl> + implements _$$MedicationImplCopyWith<$Res> { + __$$MedicationImplCopyWithImpl( + _$MedicationImpl _value, $Res Function(_$MedicationImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? uuid = freezed, + Object? name = freezed, + Object? onsetDate = freezed, + Object? dateRecorded = freezed, + Object? value = freezed, + Object? indication = freezed, + }) { + return _then(_$MedicationImpl( + uuid: freezed == uuid + ? _value.uuid + : uuid // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + onsetDate: freezed == onsetDate + ? _value.onsetDate + : onsetDate // ignore: cast_nullable_to_non_nullable + as String?, + dateRecorded: freezed == dateRecorded + ? _value.dateRecorded + : dateRecorded // ignore: cast_nullable_to_non_nullable + as String?, + value: freezed == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String?, + indication: freezed == indication + ? _value.indication + : indication // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$MedicationImpl with DiagnosticableTreeMixin implements _Medication { + const _$MedicationImpl( + {this.uuid, + this.name, + this.onsetDate, + this.dateRecorded, + this.value, + this.indication}); + + factory _$MedicationImpl.fromJson(Map json) => + _$$MedicationImplFromJson(json); + + @override + final String? uuid; + @override + final String? name; + @override + final String? onsetDate; + @override + final String? dateRecorded; + @override + final String? value; + @override + final String? indication; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'Medication(uuid: $uuid, name: $name, onsetDate: $onsetDate, dateRecorded: $dateRecorded, value: $value, indication: $indication)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'Medication')) + ..add(DiagnosticsProperty('uuid', uuid)) + ..add(DiagnosticsProperty('name', name)) + ..add(DiagnosticsProperty('onsetDate', onsetDate)) + ..add(DiagnosticsProperty('dateRecorded', dateRecorded)) + ..add(DiagnosticsProperty('value', value)) + ..add(DiagnosticsProperty('indication', indication)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MedicationImpl && + (identical(other.uuid, uuid) || other.uuid == uuid) && + (identical(other.name, name) || other.name == name) && + (identical(other.onsetDate, onsetDate) || + other.onsetDate == onsetDate) && + (identical(other.dateRecorded, dateRecorded) || + other.dateRecorded == dateRecorded) && + (identical(other.value, value) || other.value == value) && + (identical(other.indication, indication) || + other.indication == indication)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, uuid, name, onsetDate, dateRecorded, value, indication); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$MedicationImplCopyWith<_$MedicationImpl> get copyWith => + __$$MedicationImplCopyWithImpl<_$MedicationImpl>(this, _$identity); + + @override + Map toJson() { + return _$$MedicationImplToJson( + this, + ); + } +} + +abstract class _Medication implements Medication { + const factory _Medication( + {final String? uuid, + final String? name, + final String? onsetDate, + final String? dateRecorded, + final String? value, + final String? indication}) = _$MedicationImpl; + + factory _Medication.fromJson(Map json) = + _$MedicationImpl.fromJson; + + @override + String? get uuid; + @override + String? get name; + @override + String? get onsetDate; + @override + String? get dateRecorded; + @override + String? get value; + @override + String? get indication; + @override + @JsonKey(ignore: true) + _$$MedicationImplCopyWith<_$MedicationImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/visits/data/models/complaint.g.dart b/lib/src/features/visits/data/models/medication.g.dart similarity index 51% rename from lib/src/features/visits/data/models/complaint.g.dart rename to lib/src/features/visits/data/models/medication.g.dart index bd80b415..77d4418c 100644 --- a/lib/src/features/visits/data/models/complaint.g.dart +++ b/lib/src/features/visits/data/models/medication.g.dart @@ -1,25 +1,27 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'complaint.dart'; +part of 'medication.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** -_$ComplaintImpl _$$ComplaintImplFromJson(Map json) => - _$ComplaintImpl( - uuid: json['uuid'] as String, - name: json['name'] as String, +_$MedicationImpl _$$MedicationImplFromJson(Map json) => + _$MedicationImpl( + uuid: json['uuid'] as String?, + name: json['name'] as String?, onsetDate: json['onsetDate'] as String?, - dateRecorded: json['dateRecorded'] as String, - value: json['value'] as String, + dateRecorded: json['dateRecorded'] as String?, + value: json['value'] as String?, + indication: json['indication'] as String?, ); -Map _$$ComplaintImplToJson(_$ComplaintImpl instance) => +Map _$$MedicationImplToJson(_$MedicationImpl instance) => { 'uuid': instance.uuid, 'name': instance.name, 'onsetDate': instance.onsetDate, 'dateRecorded': instance.dateRecorded, 'value': instance.value, + 'indication': instance.indication, }; diff --git a/lib/src/features/visits/data/models/procedure.dart b/lib/src/features/visits/data/models/procedure.dart new file mode 100644 index 00000000..a726280c --- /dev/null +++ b/lib/src/features/visits/data/models/procedure.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:flutter/foundation.dart'; +part 'procedure.freezed.dart'; +part 'procedure.g.dart'; + +@Freezed() +class Procedure with _$Procedure { + const factory Procedure({ + String? uuid, + String? name, + String? dateRecorded, + String? value, + String? site, + double? repeat, + }) = _Procedure; + + factory Procedure.fromJson(Map json)=> _$ProcedureFromJson(json); +} \ No newline at end of file diff --git a/lib/src/features/visits/data/models/procedure.freezed.dart b/lib/src/features/visits/data/models/procedure.freezed.dart new file mode 100644 index 00000000..516eae41 --- /dev/null +++ b/lib/src/features/visits/data/models/procedure.freezed.dart @@ -0,0 +1,269 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'procedure.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Procedure _$ProcedureFromJson(Map json) { + return _Procedure.fromJson(json); +} + +/// @nodoc +mixin _$Procedure { + String? get uuid => throw _privateConstructorUsedError; + String? get name => throw _privateConstructorUsedError; + String? get dateRecorded => throw _privateConstructorUsedError; + String? get value => throw _privateConstructorUsedError; + String? get site => throw _privateConstructorUsedError; + double? get repeat => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ProcedureCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProcedureCopyWith<$Res> { + factory $ProcedureCopyWith(Procedure value, $Res Function(Procedure) then) = + _$ProcedureCopyWithImpl<$Res, Procedure>; + @useResult + $Res call( + {String? uuid, + String? name, + String? dateRecorded, + String? value, + String? site, + double? repeat}); +} + +/// @nodoc +class _$ProcedureCopyWithImpl<$Res, $Val extends Procedure> + implements $ProcedureCopyWith<$Res> { + _$ProcedureCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? uuid = freezed, + Object? name = freezed, + Object? dateRecorded = freezed, + Object? value = freezed, + Object? site = freezed, + Object? repeat = freezed, + }) { + return _then(_value.copyWith( + uuid: freezed == uuid + ? _value.uuid + : uuid // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + dateRecorded: freezed == dateRecorded + ? _value.dateRecorded + : dateRecorded // ignore: cast_nullable_to_non_nullable + as String?, + value: freezed == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String?, + site: freezed == site + ? _value.site + : site // ignore: cast_nullable_to_non_nullable + as String?, + repeat: freezed == repeat + ? _value.repeat + : repeat // ignore: cast_nullable_to_non_nullable + as double?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ProcedureImplCopyWith<$Res> + implements $ProcedureCopyWith<$Res> { + factory _$$ProcedureImplCopyWith( + _$ProcedureImpl value, $Res Function(_$ProcedureImpl) then) = + __$$ProcedureImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String? uuid, + String? name, + String? dateRecorded, + String? value, + String? site, + double? repeat}); +} + +/// @nodoc +class __$$ProcedureImplCopyWithImpl<$Res> + extends _$ProcedureCopyWithImpl<$Res, _$ProcedureImpl> + implements _$$ProcedureImplCopyWith<$Res> { + __$$ProcedureImplCopyWithImpl( + _$ProcedureImpl _value, $Res Function(_$ProcedureImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? uuid = freezed, + Object? name = freezed, + Object? dateRecorded = freezed, + Object? value = freezed, + Object? site = freezed, + Object? repeat = freezed, + }) { + return _then(_$ProcedureImpl( + uuid: freezed == uuid + ? _value.uuid + : uuid // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + dateRecorded: freezed == dateRecorded + ? _value.dateRecorded + : dateRecorded // ignore: cast_nullable_to_non_nullable + as String?, + value: freezed == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String?, + site: freezed == site + ? _value.site + : site // ignore: cast_nullable_to_non_nullable + as String?, + repeat: freezed == repeat + ? _value.repeat + : repeat // ignore: cast_nullable_to_non_nullable + as double?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProcedureImpl with DiagnosticableTreeMixin implements _Procedure { + const _$ProcedureImpl( + {this.uuid, + this.name, + this.dateRecorded, + this.value, + this.site, + this.repeat}); + + factory _$ProcedureImpl.fromJson(Map json) => + _$$ProcedureImplFromJson(json); + + @override + final String? uuid; + @override + final String? name; + @override + final String? dateRecorded; + @override + final String? value; + @override + final String? site; + @override + final double? repeat; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'Procedure(uuid: $uuid, name: $name, dateRecorded: $dateRecorded, value: $value, site: $site, repeat: $repeat)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'Procedure')) + ..add(DiagnosticsProperty('uuid', uuid)) + ..add(DiagnosticsProperty('name', name)) + ..add(DiagnosticsProperty('dateRecorded', dateRecorded)) + ..add(DiagnosticsProperty('value', value)) + ..add(DiagnosticsProperty('site', site)) + ..add(DiagnosticsProperty('repeat', repeat)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProcedureImpl && + (identical(other.uuid, uuid) || other.uuid == uuid) && + (identical(other.name, name) || other.name == name) && + (identical(other.dateRecorded, dateRecorded) || + other.dateRecorded == dateRecorded) && + (identical(other.value, value) || other.value == value) && + (identical(other.site, site) || other.site == site) && + (identical(other.repeat, repeat) || other.repeat == repeat)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => + Object.hash(runtimeType, uuid, name, dateRecorded, value, site, repeat); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ProcedureImplCopyWith<_$ProcedureImpl> get copyWith => + __$$ProcedureImplCopyWithImpl<_$ProcedureImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ProcedureImplToJson( + this, + ); + } +} + +abstract class _Procedure implements Procedure { + const factory _Procedure( + {final String? uuid, + final String? name, + final String? dateRecorded, + final String? value, + final String? site, + final double? repeat}) = _$ProcedureImpl; + + factory _Procedure.fromJson(Map json) = + _$ProcedureImpl.fromJson; + + @override + String? get uuid; + @override + String? get name; + @override + String? get dateRecorded; + @override + String? get value; + @override + String? get site; + @override + double? get repeat; + @override + @JsonKey(ignore: true) + _$$ProcedureImplCopyWith<_$ProcedureImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/features/visits/data/models/procedure.g.dart b/lib/src/features/visits/data/models/procedure.g.dart new file mode 100644 index 00000000..85b7c6ba --- /dev/null +++ b/lib/src/features/visits/data/models/procedure.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'procedure.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ProcedureImpl _$$ProcedureImplFromJson(Map json) => + _$ProcedureImpl( + uuid: json['uuid'] as String?, + name: json['name'] as String?, + dateRecorded: json['dateRecorded'] as String?, + value: json['value'] as String?, + site: json['site'] as String?, + repeat: (json['repeat'] as num?)?.toDouble(), + ); + +Map _$$ProcedureImplToJson(_$ProcedureImpl instance) => + { + 'uuid': instance.uuid, + 'name': instance.name, + 'dateRecorded': instance.dateRecorded, + 'value': instance.value, + 'site': instance.site, + 'repeat': instance.repeat, + }; diff --git a/lib/src/features/visits/data/models/visit.dart b/lib/src/features/visits/data/models/visit.dart index 1f3102f0..ec725399 100644 --- a/lib/src/features/visits/data/models/visit.dart +++ b/lib/src/features/visits/data/models/visit.dart @@ -1,9 +1,10 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:flutter/foundation.dart'; import 'package:nishauri/src/features/visits/data/models/allergy.dart'; -import 'package:nishauri/src/features/visits/data/models/complaint.dart'; +import 'package:nishauri/src/features/visits/data/models/medication.dart'; import 'package:nishauri/src/features/visits/data/models/condition.dart'; -import 'package:nishauri/src/features/visits/data/models/diagnosis.dart'; +import 'package:nishauri/src/features/visits/data/models/procedure.dart'; +import 'package:nishauri/src/features/visits/data/models/immunization.dart'; import 'package:nishauri/src/features/visits/data/models/lab_result.dart'; import 'package:nishauri/src/features/visits/data/models/vital.dart'; part 'visit.freezed.dart'; @@ -14,11 +15,13 @@ class Visit with _$Visit { const factory Visit({ required String uuid, @Default([]) List allergies, - @Default([]) List complaints, + @Default([]) List medications, @Default([]) List conditions, - @Default([]) List diagnosis, + @Default([]) List procedures, @Default([]) List labResults, @Default([]) List vitals, + @Default([]) List immunization, + String? facility, required String visitDate, }) = _Visit; diff --git a/lib/src/features/visits/data/models/visit.freezed.dart b/lib/src/features/visits/data/models/visit.freezed.dart index f2371743..5d15707d 100644 --- a/lib/src/features/visits/data/models/visit.freezed.dart +++ b/lib/src/features/visits/data/models/visit.freezed.dart @@ -22,11 +22,13 @@ Visit _$VisitFromJson(Map json) { mixin _$Visit { String get uuid => throw _privateConstructorUsedError; List get allergies => throw _privateConstructorUsedError; - List get complaints => throw _privateConstructorUsedError; + List get medications => throw _privateConstructorUsedError; List get conditions => throw _privateConstructorUsedError; - List get diagnosis => throw _privateConstructorUsedError; + List get procedures => throw _privateConstructorUsedError; List get labResults => throw _privateConstructorUsedError; List get vitals => throw _privateConstructorUsedError; + List get immunization => throw _privateConstructorUsedError; + String? get facility => throw _privateConstructorUsedError; String get visitDate => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @@ -42,11 +44,13 @@ abstract class $VisitCopyWith<$Res> { $Res call( {String uuid, List allergies, - List complaints, + List medications, List conditions, - List diagnosis, + List procedures, List labResults, List vitals, + List immunization, + String? facility, String visitDate}); } @@ -65,11 +69,13 @@ class _$VisitCopyWithImpl<$Res, $Val extends Visit> $Res call({ Object? uuid = null, Object? allergies = null, - Object? complaints = null, + Object? medications = null, Object? conditions = null, - Object? diagnosis = null, + Object? procedures = null, Object? labResults = null, Object? vitals = null, + Object? immunization = null, + Object? facility = freezed, Object? visitDate = null, }) { return _then(_value.copyWith( @@ -81,18 +87,18 @@ class _$VisitCopyWithImpl<$Res, $Val extends Visit> ? _value.allergies : allergies // ignore: cast_nullable_to_non_nullable as List, - complaints: null == complaints - ? _value.complaints - : complaints // ignore: cast_nullable_to_non_nullable - as List, + medications: null == medications + ? _value.medications + : medications // ignore: cast_nullable_to_non_nullable + as List, conditions: null == conditions ? _value.conditions : conditions // ignore: cast_nullable_to_non_nullable as List, - diagnosis: null == diagnosis - ? _value.diagnosis - : diagnosis // ignore: cast_nullable_to_non_nullable - as List, + procedures: null == procedures + ? _value.procedures + : procedures // ignore: cast_nullable_to_non_nullable + as List, labResults: null == labResults ? _value.labResults : labResults // ignore: cast_nullable_to_non_nullable @@ -101,6 +107,14 @@ class _$VisitCopyWithImpl<$Res, $Val extends Visit> ? _value.vitals : vitals // ignore: cast_nullable_to_non_nullable as List, + immunization: null == immunization + ? _value.immunization + : immunization // ignore: cast_nullable_to_non_nullable + as List, + facility: freezed == facility + ? _value.facility + : facility // ignore: cast_nullable_to_non_nullable + as String?, visitDate: null == visitDate ? _value.visitDate : visitDate // ignore: cast_nullable_to_non_nullable @@ -119,11 +133,13 @@ abstract class _$$VisitImplCopyWith<$Res> implements $VisitCopyWith<$Res> { $Res call( {String uuid, List allergies, - List complaints, + List medications, List conditions, - List diagnosis, + List procedures, List labResults, List vitals, + List immunization, + String? facility, String visitDate}); } @@ -140,11 +156,13 @@ class __$$VisitImplCopyWithImpl<$Res> $Res call({ Object? uuid = null, Object? allergies = null, - Object? complaints = null, + Object? medications = null, Object? conditions = null, - Object? diagnosis = null, + Object? procedures = null, Object? labResults = null, Object? vitals = null, + Object? immunization = null, + Object? facility = freezed, Object? visitDate = null, }) { return _then(_$VisitImpl( @@ -156,18 +174,18 @@ class __$$VisitImplCopyWithImpl<$Res> ? _value._allergies : allergies // ignore: cast_nullable_to_non_nullable as List, - complaints: null == complaints - ? _value._complaints - : complaints // ignore: cast_nullable_to_non_nullable - as List, + medications: null == medications + ? _value._medications + : medications // ignore: cast_nullable_to_non_nullable + as List, conditions: null == conditions ? _value._conditions : conditions // ignore: cast_nullable_to_non_nullable as List, - diagnosis: null == diagnosis - ? _value._diagnosis - : diagnosis // ignore: cast_nullable_to_non_nullable - as List, + procedures: null == procedures + ? _value._procedures + : procedures // ignore: cast_nullable_to_non_nullable + as List, labResults: null == labResults ? _value._labResults : labResults // ignore: cast_nullable_to_non_nullable @@ -176,6 +194,14 @@ class __$$VisitImplCopyWithImpl<$Res> ? _value._vitals : vitals // ignore: cast_nullable_to_non_nullable as List, + immunization: null == immunization + ? _value._immunization + : immunization // ignore: cast_nullable_to_non_nullable + as List, + facility: freezed == facility + ? _value.facility + : facility // ignore: cast_nullable_to_non_nullable + as String?, visitDate: null == visitDate ? _value.visitDate : visitDate // ignore: cast_nullable_to_non_nullable @@ -190,18 +216,21 @@ class _$VisitImpl with DiagnosticableTreeMixin implements _Visit { const _$VisitImpl( {required this.uuid, final List allergies = const [], - final List complaints = const [], + final List medications = const [], final List conditions = const [], - final List diagnosis = const [], + final List procedures = const [], final List labResults = const [], final List vitals = const [], + final List immunization = const [], + this.facility, required this.visitDate}) : _allergies = allergies, - _complaints = complaints, + _medications = medications, _conditions = conditions, - _diagnosis = diagnosis, + _procedures = procedures, _labResults = labResults, - _vitals = vitals; + _vitals = vitals, + _immunization = immunization; factory _$VisitImpl.fromJson(Map json) => _$$VisitImplFromJson(json); @@ -217,13 +246,13 @@ class _$VisitImpl with DiagnosticableTreeMixin implements _Visit { return EqualUnmodifiableListView(_allergies); } - final List _complaints; + final List _medications; @override @JsonKey() - List get complaints { - if (_complaints is EqualUnmodifiableListView) return _complaints; + List get medications { + if (_medications is EqualUnmodifiableListView) return _medications; // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_complaints); + return EqualUnmodifiableListView(_medications); } final List _conditions; @@ -235,13 +264,13 @@ class _$VisitImpl with DiagnosticableTreeMixin implements _Visit { return EqualUnmodifiableListView(_conditions); } - final List _diagnosis; + final List _procedures; @override @JsonKey() - List get diagnosis { - if (_diagnosis is EqualUnmodifiableListView) return _diagnosis; + List get procedures { + if (_procedures is EqualUnmodifiableListView) return _procedures; // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_diagnosis); + return EqualUnmodifiableListView(_procedures); } final List _labResults; @@ -262,12 +291,23 @@ class _$VisitImpl with DiagnosticableTreeMixin implements _Visit { return EqualUnmodifiableListView(_vitals); } + final List _immunization; + @override + @JsonKey() + List get immunization { + if (_immunization is EqualUnmodifiableListView) return _immunization; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_immunization); + } + + @override + final String? facility; @override final String visitDate; @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'Visit(uuid: $uuid, allergies: $allergies, complaints: $complaints, conditions: $conditions, diagnosis: $diagnosis, labResults: $labResults, vitals: $vitals, visitDate: $visitDate)'; + return 'Visit(uuid: $uuid, allergies: $allergies, medications: $medications, conditions: $conditions, procedures: $procedures, labResults: $labResults, vitals: $vitals, immunization: $immunization, facility: $facility, visitDate: $visitDate)'; } @override @@ -277,11 +317,13 @@ class _$VisitImpl with DiagnosticableTreeMixin implements _Visit { ..add(DiagnosticsProperty('type', 'Visit')) ..add(DiagnosticsProperty('uuid', uuid)) ..add(DiagnosticsProperty('allergies', allergies)) - ..add(DiagnosticsProperty('complaints', complaints)) + ..add(DiagnosticsProperty('medications', medications)) ..add(DiagnosticsProperty('conditions', conditions)) - ..add(DiagnosticsProperty('diagnosis', diagnosis)) + ..add(DiagnosticsProperty('procedures', procedures)) ..add(DiagnosticsProperty('labResults', labResults)) ..add(DiagnosticsProperty('vitals', vitals)) + ..add(DiagnosticsProperty('immunization', immunization)) + ..add(DiagnosticsProperty('facility', facility)) ..add(DiagnosticsProperty('visitDate', visitDate)); } @@ -294,14 +336,18 @@ class _$VisitImpl with DiagnosticableTreeMixin implements _Visit { const DeepCollectionEquality() .equals(other._allergies, _allergies) && const DeepCollectionEquality() - .equals(other._complaints, _complaints) && + .equals(other._medications, _medications) && const DeepCollectionEquality() .equals(other._conditions, _conditions) && const DeepCollectionEquality() - .equals(other._diagnosis, _diagnosis) && + .equals(other._procedures, _procedures) && const DeepCollectionEquality() .equals(other._labResults, _labResults) && const DeepCollectionEquality().equals(other._vitals, _vitals) && + const DeepCollectionEquality() + .equals(other._immunization, _immunization) && + (identical(other.facility, facility) || + other.facility == facility) && (identical(other.visitDate, visitDate) || other.visitDate == visitDate)); } @@ -312,11 +358,13 @@ class _$VisitImpl with DiagnosticableTreeMixin implements _Visit { runtimeType, uuid, const DeepCollectionEquality().hash(_allergies), - const DeepCollectionEquality().hash(_complaints), + const DeepCollectionEquality().hash(_medications), const DeepCollectionEquality().hash(_conditions), - const DeepCollectionEquality().hash(_diagnosis), + const DeepCollectionEquality().hash(_procedures), const DeepCollectionEquality().hash(_labResults), const DeepCollectionEquality().hash(_vitals), + const DeepCollectionEquality().hash(_immunization), + facility, visitDate); @JsonKey(ignore: true) @@ -337,11 +385,13 @@ abstract class _Visit implements Visit { const factory _Visit( {required final String uuid, final List allergies, - final List complaints, + final List medications, final List conditions, - final List diagnosis, + final List procedures, final List labResults, final List vitals, + final List immunization, + final String? facility, required final String visitDate}) = _$VisitImpl; factory _Visit.fromJson(Map json) = _$VisitImpl.fromJson; @@ -351,16 +401,20 @@ abstract class _Visit implements Visit { @override List get allergies; @override - List get complaints; + List get medications; @override List get conditions; @override - List get diagnosis; + List get procedures; @override List get labResults; @override List get vitals; @override + List get immunization; + @override + String? get facility; + @override String get visitDate; @override @JsonKey(ignore: true) diff --git a/lib/src/features/visits/data/models/visit.g.dart b/lib/src/features/visits/data/models/visit.g.dart index 3bb336e8..42ccd8a2 100644 --- a/lib/src/features/visits/data/models/visit.g.dart +++ b/lib/src/features/visits/data/models/visit.g.dart @@ -12,16 +12,16 @@ _$VisitImpl _$$VisitImplFromJson(Map json) => _$VisitImpl( ?.map((e) => Allergy.fromJson(e as Map)) .toList() ?? const [], - complaints: (json['complaints'] as List?) - ?.map((e) => Complaint.fromJson(e as Map)) + medications: (json['medications'] as List?) + ?.map((e) => Medication.fromJson(e as Map)) .toList() ?? const [], conditions: (json['conditions'] as List?) ?.map((e) => Condition.fromJson(e as Map)) .toList() ?? const [], - diagnosis: (json['diagnosis'] as List?) - ?.map((e) => Diagnosis.fromJson(e as Map)) + procedures: (json['procedures'] as List?) + ?.map((e) => Procedure.fromJson(e as Map)) .toList() ?? const [], labResults: (json['labResults'] as List?) @@ -32,6 +32,11 @@ _$VisitImpl _$$VisitImplFromJson(Map json) => _$VisitImpl( ?.map((e) => Vital.fromJson(e as Map)) .toList() ?? const [], + immunization: (json['immunization'] as List?) + ?.map((e) => Immunization.fromJson(e as Map)) + .toList() ?? + const [], + facility: json['facility'] as String?, visitDate: json['visitDate'] as String, ); @@ -39,10 +44,12 @@ Map _$$VisitImplToJson(_$VisitImpl instance) => { 'uuid': instance.uuid, 'allergies': instance.allergies, - 'complaints': instance.complaints, + 'medications': instance.medications, 'conditions': instance.conditions, - 'diagnosis': instance.diagnosis, + 'procedures': instance.procedures, 'labResults': instance.labResults, 'vitals': instance.vitals, + 'immunization': instance.immunization, + 'facility': instance.facility, 'visitDate': instance.visitDate, }; diff --git a/lib/src/features/visits/data/models/vital.dart b/lib/src/features/visits/data/models/vital.dart index 2ce83093..c989e313 100644 --- a/lib/src/features/visits/data/models/vital.dart +++ b/lib/src/features/visits/data/models/vital.dart @@ -6,10 +6,17 @@ part 'vital.g.dart'; @Freezed() class Vital with _$Vital { const factory Vital({ - required String uuid, - required String name, - required String dateRecorded, - required String value, + String? uuid, + String? name, + String? dateRecorded, + String? weight, + String? temp, + String? systolic, + String? diastolic, + String? respiratory, + String? oxygenSaturation, + String? height, + String? complain, }) = _Vital; factory Vital.fromJson(Map json)=> _$VitalFromJson(json); diff --git a/lib/src/features/visits/data/models/vital.freezed.dart b/lib/src/features/visits/data/models/vital.freezed.dart index 0d4c6b14..777aa940 100644 --- a/lib/src/features/visits/data/models/vital.freezed.dart +++ b/lib/src/features/visits/data/models/vital.freezed.dart @@ -20,10 +20,17 @@ Vital _$VitalFromJson(Map json) { /// @nodoc mixin _$Vital { - String get uuid => throw _privateConstructorUsedError; - String get name => throw _privateConstructorUsedError; - String get dateRecorded => throw _privateConstructorUsedError; - String get value => throw _privateConstructorUsedError; + String? get uuid => throw _privateConstructorUsedError; + String? get name => throw _privateConstructorUsedError; + String? get dateRecorded => throw _privateConstructorUsedError; + String? get weight => throw _privateConstructorUsedError; + String? get temp => throw _privateConstructorUsedError; + String? get systolic => throw _privateConstructorUsedError; + String? get diastolic => throw _privateConstructorUsedError; + String? get respiratory => throw _privateConstructorUsedError; + String? get oxygenSaturation => throw _privateConstructorUsedError; + String? get height => throw _privateConstructorUsedError; + String? get complain => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -35,7 +42,18 @@ abstract class $VitalCopyWith<$Res> { factory $VitalCopyWith(Vital value, $Res Function(Vital) then) = _$VitalCopyWithImpl<$Res, Vital>; @useResult - $Res call({String uuid, String name, String dateRecorded, String value}); + $Res call( + {String? uuid, + String? name, + String? dateRecorded, + String? weight, + String? temp, + String? systolic, + String? diastolic, + String? respiratory, + String? oxygenSaturation, + String? height, + String? complain}); } /// @nodoc @@ -51,28 +69,63 @@ class _$VitalCopyWithImpl<$Res, $Val extends Vital> @pragma('vm:prefer-inline') @override $Res call({ - Object? uuid = null, - Object? name = null, - Object? dateRecorded = null, - Object? value = null, + Object? uuid = freezed, + Object? name = freezed, + Object? dateRecorded = freezed, + Object? weight = freezed, + Object? temp = freezed, + Object? systolic = freezed, + Object? diastolic = freezed, + Object? respiratory = freezed, + Object? oxygenSaturation = freezed, + Object? height = freezed, + Object? complain = freezed, }) { return _then(_value.copyWith( - uuid: null == uuid + uuid: freezed == uuid ? _value.uuid : uuid // ignore: cast_nullable_to_non_nullable - as String, - name: null == name + as String?, + name: freezed == name ? _value.name : name // ignore: cast_nullable_to_non_nullable - as String, - dateRecorded: null == dateRecorded + as String?, + dateRecorded: freezed == dateRecorded ? _value.dateRecorded : dateRecorded // ignore: cast_nullable_to_non_nullable - as String, - value: null == value - ? _value.value - : value // ignore: cast_nullable_to_non_nullable - as String, + as String?, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as String?, + temp: freezed == temp + ? _value.temp + : temp // ignore: cast_nullable_to_non_nullable + as String?, + systolic: freezed == systolic + ? _value.systolic + : systolic // ignore: cast_nullable_to_non_nullable + as String?, + diastolic: freezed == diastolic + ? _value.diastolic + : diastolic // ignore: cast_nullable_to_non_nullable + as String?, + respiratory: freezed == respiratory + ? _value.respiratory + : respiratory // ignore: cast_nullable_to_non_nullable + as String?, + oxygenSaturation: freezed == oxygenSaturation + ? _value.oxygenSaturation + : oxygenSaturation // ignore: cast_nullable_to_non_nullable + as String?, + height: freezed == height + ? _value.height + : height // ignore: cast_nullable_to_non_nullable + as String?, + complain: freezed == complain + ? _value.complain + : complain // ignore: cast_nullable_to_non_nullable + as String?, ) as $Val); } } @@ -84,7 +137,18 @@ abstract class _$$VitalImplCopyWith<$Res> implements $VitalCopyWith<$Res> { __$$VitalImplCopyWithImpl<$Res>; @override @useResult - $Res call({String uuid, String name, String dateRecorded, String value}); + $Res call( + {String? uuid, + String? name, + String? dateRecorded, + String? weight, + String? temp, + String? systolic, + String? diastolic, + String? respiratory, + String? oxygenSaturation, + String? height, + String? complain}); } /// @nodoc @@ -98,28 +162,63 @@ class __$$VitalImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? uuid = null, - Object? name = null, - Object? dateRecorded = null, - Object? value = null, + Object? uuid = freezed, + Object? name = freezed, + Object? dateRecorded = freezed, + Object? weight = freezed, + Object? temp = freezed, + Object? systolic = freezed, + Object? diastolic = freezed, + Object? respiratory = freezed, + Object? oxygenSaturation = freezed, + Object? height = freezed, + Object? complain = freezed, }) { return _then(_$VitalImpl( - uuid: null == uuid + uuid: freezed == uuid ? _value.uuid : uuid // ignore: cast_nullable_to_non_nullable - as String, - name: null == name + as String?, + name: freezed == name ? _value.name : name // ignore: cast_nullable_to_non_nullable - as String, - dateRecorded: null == dateRecorded + as String?, + dateRecorded: freezed == dateRecorded ? _value.dateRecorded : dateRecorded // ignore: cast_nullable_to_non_nullable - as String, - value: null == value - ? _value.value - : value // ignore: cast_nullable_to_non_nullable - as String, + as String?, + weight: freezed == weight + ? _value.weight + : weight // ignore: cast_nullable_to_non_nullable + as String?, + temp: freezed == temp + ? _value.temp + : temp // ignore: cast_nullable_to_non_nullable + as String?, + systolic: freezed == systolic + ? _value.systolic + : systolic // ignore: cast_nullable_to_non_nullable + as String?, + diastolic: freezed == diastolic + ? _value.diastolic + : diastolic // ignore: cast_nullable_to_non_nullable + as String?, + respiratory: freezed == respiratory + ? _value.respiratory + : respiratory // ignore: cast_nullable_to_non_nullable + as String?, + oxygenSaturation: freezed == oxygenSaturation + ? _value.oxygenSaturation + : oxygenSaturation // ignore: cast_nullable_to_non_nullable + as String?, + height: freezed == height + ? _value.height + : height // ignore: cast_nullable_to_non_nullable + as String?, + complain: freezed == complain + ? _value.complain + : complain // ignore: cast_nullable_to_non_nullable + as String?, )); } } @@ -128,26 +227,47 @@ class __$$VitalImplCopyWithImpl<$Res> @JsonSerializable() class _$VitalImpl with DiagnosticableTreeMixin implements _Vital { const _$VitalImpl( - {required this.uuid, - required this.name, - required this.dateRecorded, - required this.value}); + {this.uuid, + this.name, + this.dateRecorded, + this.weight, + this.temp, + this.systolic, + this.diastolic, + this.respiratory, + this.oxygenSaturation, + this.height, + this.complain}); factory _$VitalImpl.fromJson(Map json) => _$$VitalImplFromJson(json); @override - final String uuid; + final String? uuid; @override - final String name; + final String? name; @override - final String dateRecorded; + final String? dateRecorded; @override - final String value; + final String? weight; + @override + final String? temp; + @override + final String? systolic; + @override + final String? diastolic; + @override + final String? respiratory; + @override + final String? oxygenSaturation; + @override + final String? height; + @override + final String? complain; @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'Vital(uuid: $uuid, name: $name, dateRecorded: $dateRecorded, value: $value)'; + return 'Vital(uuid: $uuid, name: $name, dateRecorded: $dateRecorded, weight: $weight, temp: $temp, systolic: $systolic, diastolic: $diastolic, respiratory: $respiratory, oxygenSaturation: $oxygenSaturation, height: $height, complain: $complain)'; } @override @@ -158,7 +278,14 @@ class _$VitalImpl with DiagnosticableTreeMixin implements _Vital { ..add(DiagnosticsProperty('uuid', uuid)) ..add(DiagnosticsProperty('name', name)) ..add(DiagnosticsProperty('dateRecorded', dateRecorded)) - ..add(DiagnosticsProperty('value', value)); + ..add(DiagnosticsProperty('weight', weight)) + ..add(DiagnosticsProperty('temp', temp)) + ..add(DiagnosticsProperty('systolic', systolic)) + ..add(DiagnosticsProperty('diastolic', diastolic)) + ..add(DiagnosticsProperty('respiratory', respiratory)) + ..add(DiagnosticsProperty('oxygenSaturation', oxygenSaturation)) + ..add(DiagnosticsProperty('height', height)) + ..add(DiagnosticsProperty('complain', complain)); } @override @@ -170,12 +297,36 @@ class _$VitalImpl with DiagnosticableTreeMixin implements _Vital { (identical(other.name, name) || other.name == name) && (identical(other.dateRecorded, dateRecorded) || other.dateRecorded == dateRecorded) && - (identical(other.value, value) || other.value == value)); + (identical(other.weight, weight) || other.weight == weight) && + (identical(other.temp, temp) || other.temp == temp) && + (identical(other.systolic, systolic) || + other.systolic == systolic) && + (identical(other.diastolic, diastolic) || + other.diastolic == diastolic) && + (identical(other.respiratory, respiratory) || + other.respiratory == respiratory) && + (identical(other.oxygenSaturation, oxygenSaturation) || + other.oxygenSaturation == oxygenSaturation) && + (identical(other.height, height) || other.height == height) && + (identical(other.complain, complain) || + other.complain == complain)); } @JsonKey(ignore: true) @override - int get hashCode => Object.hash(runtimeType, uuid, name, dateRecorded, value); + int get hashCode => Object.hash( + runtimeType, + uuid, + name, + dateRecorded, + weight, + temp, + systolic, + diastolic, + respiratory, + oxygenSaturation, + height, + complain); @JsonKey(ignore: true) @override @@ -193,21 +344,42 @@ class _$VitalImpl with DiagnosticableTreeMixin implements _Vital { abstract class _Vital implements Vital { const factory _Vital( - {required final String uuid, - required final String name, - required final String dateRecorded, - required final String value}) = _$VitalImpl; + {final String? uuid, + final String? name, + final String? dateRecorded, + final String? weight, + final String? temp, + final String? systolic, + final String? diastolic, + final String? respiratory, + final String? oxygenSaturation, + final String? height, + final String? complain}) = _$VitalImpl; factory _Vital.fromJson(Map json) = _$VitalImpl.fromJson; @override - String get uuid; + String? get uuid; + @override + String? get name; + @override + String? get dateRecorded; + @override + String? get weight; + @override + String? get temp; + @override + String? get systolic; + @override + String? get diastolic; + @override + String? get respiratory; @override - String get name; + String? get oxygenSaturation; @override - String get dateRecorded; + String? get height; @override - String get value; + String? get complain; @override @JsonKey(ignore: true) _$$VitalImplCopyWith<_$VitalImpl> get copyWith => diff --git a/lib/src/features/visits/data/models/vital.g.dart b/lib/src/features/visits/data/models/vital.g.dart index f526f08d..dac7db85 100644 --- a/lib/src/features/visits/data/models/vital.g.dart +++ b/lib/src/features/visits/data/models/vital.g.dart @@ -7,10 +7,17 @@ part of 'vital.dart'; // ************************************************************************** _$VitalImpl _$$VitalImplFromJson(Map json) => _$VitalImpl( - uuid: json['uuid'] as String, - name: json['name'] as String, - dateRecorded: json['dateRecorded'] as String, - value: json['value'] as String, + uuid: json['uuid'] as String?, + name: json['name'] as String?, + dateRecorded: json['dateRecorded'] as String?, + weight: json['weight'] as String?, + temp: json['temp'] as String?, + systolic: json['systolic'] as String?, + diastolic: json['diastolic'] as String?, + respiratory: json['respiratory'] as String?, + oxygenSaturation: json['oxygenSaturation'] as String?, + height: json['height'] as String?, + complain: json['complain'] as String?, ); Map _$$VitalImplToJson(_$VitalImpl instance) => @@ -18,5 +25,12 @@ Map _$$VitalImplToJson(_$VitalImpl instance) => 'uuid': instance.uuid, 'name': instance.name, 'dateRecorded': instance.dateRecorded, - 'value': instance.value, + 'weight': instance.weight, + 'temp': instance.temp, + 'systolic': instance.systolic, + 'diastolic': instance.diastolic, + 'respiratory': instance.respiratory, + 'oxygenSaturation': instance.oxygenSaturation, + 'height': instance.height, + 'complain': instance.complain, }; diff --git a/lib/src/features/visits/presentations/pages/FacilityVisitDetailScreen.dart b/lib/src/features/visits/presentations/pages/FacilityVisitDetailScreen.dart index 26b0a121..1165c745 100644 --- a/lib/src/features/visits/presentations/pages/FacilityVisitDetailScreen.dart +++ b/lib/src/features/visits/presentations/pages/FacilityVisitDetailScreen.dart @@ -1,63 +1,111 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; -import 'package:nishauri/src/features/visits/data/providers/visits_provider.dart'; -import 'package:nishauri/src/features/visits/presentations/widgets/AllergiesTab.dart'; -import 'package:nishauri/src/features/visits/presentations/widgets/ComplaintsTab.dart'; -import 'package:nishauri/src/features/visits/presentations/widgets/ConditionsTab.dart'; -import 'package:nishauri/src/features/visits/presentations/widgets/Diagnosis.dart'; -import 'package:nishauri/src/features/visits/presentations/widgets/LabResultsTab.dart'; -import 'package:nishauri/src/features/visits/presentations/widgets/VitalsTab.dart'; - -class FacilityVisitDetailScreen extends ConsumerWidget { - final String visitId; - - const FacilityVisitDetailScreen({super.key, required this.visitId}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final visitsAsync = ref.watch(visitProvider); - final theme = Theme.of(context); - - return visitsAsync.when( - data: (data) { - final visitDetail = - data.where((element) => element.uuid == visitId).first; - return DefaultTabController( - length: 6, - child: Scaffold( - appBar: AppBar( - leading: IconButton( - onPressed: () => context.pop(), - icon: const Icon(Icons.chevron_left), - ), - title: const Text("Facility visit"), - bottom: const TabBar(tabs: [ - Tab(text: "Vitals"), - Tab(text: "Allergies"), - Tab(text: "Complaints"), - Tab(text: "Conditions"), - Tab(text: "Lab Results"), - Tab(text: "Diagnosis"), - ]), - ), - body: TabBarView( - children: [ - VitalsTab(vitals: visitDetail.vitals), - AllergiesTab(allergies: visitDetail.allergies), - ComplaintsTab(complaints: visitDetail.complaints), - ConditionsTab(conditions: visitDetail.conditions), - LabResultsTab(labResult: visitDetail.labResults), - DiagnosisTab(diagnosis: visitDetail.diagnosis), - ], - ), - ), - ); - }, - error: (error, _) => Center(child: Text(error.toString())), - loading: () => const Center( - child: CircularProgressIndicator(), - ), - ); - } -} +// import 'dart:ffi'; +// +// import 'package:flutter/material.dart'; +// import 'package:flutter_hooks/flutter_hooks.dart'; +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:hooks_riverpod/hooks_riverpod.dart'; +// import 'package:nishauri/src/features/visits/data/providers/visits_provider.dart'; +// import 'package:nishauri/src/features/visits/presentations/widgets/AllergiesTab.dart'; +// import 'package:nishauri/src/features/visits/presentations/widgets/ComplaintsTab.dart'; +// import 'package:nishauri/src/features/visits/presentations/widgets/ConditionsTab.dart'; +// import 'package:nishauri/src/features/visits/presentations/widgets/Diagnosis.dart'; +// import 'package:nishauri/src/features/visits/presentations/widgets/LabResultsTab.dart'; +// import 'package:nishauri/src/features/visits/presentations/widgets/VitalsTab.dart'; +// import 'package:nishauri/src/shared/display/CustomAppBar.dart'; +// import 'package:nishauri/src/shared/display/CustomTabBar.dart'; +// import 'package:nishauri/src/shared/display/background_image_widget.dart'; +// +// class FacilityVisitDetailScreen extends HookConsumerWidget { +// final String visitId; +// +// const FacilityVisitDetailScreen({super.key, required this.visitId}); +// +// @override +// Widget build(BuildContext context, WidgetRef ref) { +// final visitsAsync = ref.watch(visitProvider); +// final theme = Theme.of(context); +// final currentIndex = useState(0); +// +// final tabItems = [ +// CustomTabBarItem(title: "Vitals"), +// CustomTabBarItem(title: "Allergies"), +// CustomTabBarItem(title: "Complaints"), +// CustomTabBarItem(title: "Conditions"), +// CustomTabBarItem(title: "Lab Results"), +// CustomTabBarItem(title: "Diagnosis"), +// ]; +// +// return visitsAsync.when( +// data: (data) { +// final visitDetail = +// data.where((element) => element.uuid == visitId).first; +// +// final screen = [ +// visitDetail.allergies.isEmpty ? +// const Center( +// child: BackgroundImageWidget( +// svgImage: 'assets/images/lab-empty-state.svg', +// notFoundText: "No vital records"), +// ) : +// +// VitalsTab(vitals: visitDetail.vitals), +// +// visitDetail.allergies.isEmpty ? const Center( +// child: BackgroundImageWidget( +// svgImage: 'assets/images/lab-empty-state.svg', +// notFoundText: "No Allergies records"), +// ): +// AllergiesTab(allergies: visitDetail.allergies), +// // visitDetail.complaints.isEmpty ? const Center( +// // child: BackgroundImageWidget( +// // svgImage: 'assets/images/lab-empty-state.svg', +// // notFoundText: "No complaints records"), +// // ): +// // ComplaintsTab(complaints: visitDetail.complaints), +// visitDetail.conditions.isEmpty ? const Center( +// child: BackgroundImageWidget( +// svgImage: 'assets/images/lab-empty-state.svg', +// notFoundText: "No condition records."), +// ): +// ConditionsTab(conditions: visitDetail.conditions), +// visitDetail.labResults.isEmpty ? const Center( +// child: BackgroundImageWidget( +// svgImage: 'assets/images/lab-empty-state.svg', +// notFoundText: "No Lab result records."), +// ): +// LabResultsTab(labResult: visitDetail.labResults), +// // visitDetail.diagnosis.isEmpty ? const Center( +// // child: BackgroundImageWidget( +// // svgImage: 'assets/images/lab-empty-state.svg', +// // notFoundText: "No Diagnosis records."), +// // ): +// // DiagnosisTab(diagnosis: visitDetail.diagnosis), +// ]; +// +// return Scaffold( +// body: Column( +// mainAxisAlignment: MainAxisAlignment.start, +// children: [ +// CustomAppBar( +// title: "Shared Health Records", +// color: theme.primaryColor, +// ), +// CustomTabBar(onTap: (item, index){ +// currentIndex.value = index; +// }, +// activeColor: theme.primaryColor, +// activeIndex: currentIndex.value, +// items: tabItems, +// ), +// Expanded(child: screen[currentIndex.value]), +// ], +// ), +// ); +// }, +// error: (error, _) => Center(child: Text(error.toString())), +// loading: () => const Center( +// child: CircularProgressIndicator(), +// ), +// ); +// } +// } diff --git a/lib/src/features/visits/presentations/pages/FacilityVisitsScreen.dart b/lib/src/features/visits/presentations/pages/FacilityVisitsScreen.dart index 8ef90ecf..e6851407 100644 --- a/lib/src/features/visits/presentations/pages/FacilityVisitsScreen.dart +++ b/lib/src/features/visits/presentations/pages/FacilityVisitsScreen.dart @@ -1,79 +1,221 @@ import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; +import 'package:nishauri/src/features/auth/data/providers/auth_provider.dart'; +import 'package:nishauri/src/features/auth/data/respositories/auth_repository.dart'; +import 'package:nishauri/src/features/user/data/providers/user_provider.dart'; import 'package:nishauri/src/features/visits/data/providers/visits_provider.dart'; -import 'package:nishauri/src/shared/display/AppCard.dart'; +import 'package:nishauri/src/hooks/uset_timer.dart'; import 'package:nishauri/src/shared/display/AppSearch.dart'; +import 'package:nishauri/src/shared/display/LinkedRichText.dart'; +import 'package:nishauri/src/shared/display/Logo.dart'; +import 'package:nishauri/src/shared/display/label_input_container.dart'; +import 'package:nishauri/src/shared/display/scafold_stack_body.dart'; +import 'package:nishauri/src/shared/input/Button.dart'; import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/helpers.dart'; import 'package:nishauri/src/utils/routes.dart'; +import 'package:pinput/pinput.dart'; -class FacilityVisitsScreen extends StatelessWidget { +class FacilityVisitsScreen extends HookConsumerWidget { const FacilityVisitsScreen({super.key}); @override - Widget build(BuildContext context) { - final theme = Theme.of(context); + Widget build(BuildContext context, WidgetRef ref) { + final AuthRepository _repository; + final formKey = useMemoized(() => GlobalKey()); + final requestVerificationStateNotifier = ref.read(userProvider.notifier); + final authStateNotifier = ref.read(authStateProvider.notifier); + final userAsync = ref.watch(userProvider); + final timer = useTime(const Duration(seconds: 60)); + final loading = useState(false); + + final user = userAsync.when(data: (data) => data.phoneNumber, error: (error, _) => "", loading: () => null); + + final otpController = useTextEditingController(); + + // Call the getOTPCode method when the form opens + useEffect(() { + requestVerificationStateNotifier.getOTPCode("sms"); + return null; + }, []); + + void handleSubmit() { + context.goNamed( + RouteNames.FACILITY_VISIT_DETAIL, + pathParameters: {"visitId": "1"}); + // if (formKey.currentState!.saveAndValidate()) { + // loading.value = true; + // final userStateNotifier = ref.read(userProvider.notifier); + // userStateNotifier.verify({ + // "otp": otpController.text, + // "mode": "sms" + // }).then((value) { + // context.goNamed( + // RouteNames.FACILITY_VISIT_DETAIL, + // pathParameters: {"visitId": "1"}); + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar(content: Text(value)), + // ); + // }) .whenComplete(() { + // loading.value = false; + // }); + // .catchError((err) { + // handleResponseError(context, formKey.currentState!.fields, err, + // authStateNotifier.logout); + // }).whenComplete(() { + // loading.value = false; + // }); + // } + } + + var theme = Theme.of(context); + return Scaffold( - appBar: AppBar( - leading: IconButton( - onPressed: () => context.pop(), - icon: const Icon(Icons.chevron_left), - ), - title: const Text("Facility visits"), - ), - body: Consumer( - builder: (context, ref, child) { - final visitsAsync = ref.watch(visitProvider); - return visitsAsync.when( - data: (data) => Column( - children: [ - const Padding( - padding: EdgeInsets.all(Constants.SPACING), - child: AppSearch(), + body: ScaffoldStackedBody( + body: Column( + children: [ + AppBar( + backgroundColor: Colors.transparent, + leading: IconButton( + onPressed: () => context.pop(), + icon: SvgPicture.asset( + "assets/images/reply-dark.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + width: 40, + height: 40, ), - Expanded( - child: ListView.builder( - itemCount: data.length, - itemBuilder: (BuildContext context, int index) => Column( + ), + ), + Expanded( + child: FormBuilder( + key: formKey, + child: SingleChildScrollView( + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Divider(), - ListTile( - onTap: () => context.goNamed( - RouteNames.FACILITY_VISIT_DETAIL, - pathParameters: {"visitId": data[index].uuid}, + const SizedBox(height: Constants.SMALL_SPACING), + const DecoratedBox( + decoration: BoxDecoration(), + child: Logo( + size: 100, + ), + ), + const SizedBox(height: Constants.SMALL_SPACING), + const Text( + "SHR Pull ✅", + style: TextStyle(fontSize: 40), + ), + const SizedBox(height: Constants.SPACING), + RichText( + text: TextSpan( + text: "Code has been sent to ", + style: TextStyle(color: theme.colorScheme.onSurface), + children: [ + TextSpan( + text: user, + style: TextStyle( + color: theme.colorScheme.primary, ), - leading: const Icon( - Icons.move_down, + ), + const TextSpan( + text: "\n\nEnter the code to pull Your Facility visit", + ), + ], + ), + ), + const SizedBox(height: Constants.SPACING * 3), + LabelInputContainer( + label: "Enter Code", + child: Pinput( + length: 5, + controller: otpController, + defaultPinTheme: PinTheme( + width: 56, + height: 56, + textStyle: const TextStyle( + fontSize: 22, + color: Color.fromRGBO(30, 60, 87, 1), + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(19), + border: Border.all(color: Constants.programsColor), + ), ), - title: const Text("Mbagathi Referal Hospital"), - subtitle: Text( - "Visit Date: ${DateFormat("yyyy MMM dd").format(DateTime.parse(data[index].visitDate))}"), - trailing: const Icon(Icons.chevron_right)), + androidSmsAutofillMethod: AndroidSmsAutofillMethod.smsUserConsentApi, + cursor: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + margin: const EdgeInsets.only(bottom: 9), + width: 22, + height: 1, + color: Constants.programsColor, + ) + ], + ), + validator: (value) { + if (value == null || value.length != 5) { + return 'Check the code should be 5 digits'; + } + return null; + }, + pinputAutovalidateMode: PinputAutovalidateMode.onSubmit, + ), + ), + const SizedBox(height: Constants.SPACING * 6), + Button( + title: "Verify OTP", + backgroundColor: theme.colorScheme.primary, + textColor: Colors.white, + onPress: handleSubmit, + loading: loading.value, + ), + const SizedBox(height: Constants.SPACING * 2), + if (timer.remainingTime == 0) + LinkedRichText( + linked: "Don't receive code?", + unlinked: "Resend code", + mainAxisAlignment: MainAxisAlignment.start, + onPress: () { + timer.reset(); + loading.value = true; + requestVerificationStateNotifier + .getOTPCode("sms") + .then((value) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(value)), + ); + }).whenComplete(() { + loading.value = false; + }); + }, + ), + const SizedBox(height: Constants.SPACING), + LinkedRichText( + linked: "Send code in ", + unlinked: "00:${timer.remainingTime}", + mainAxisAlignment: MainAxisAlignment.start, + ), + const SizedBox(height: Constants.SPACING), ], ), ), ), - ], - ), - error: (error, _) => Center(child: Text(error.toString())), - loading: () => Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - "Loading your visits", - style: theme.textTheme.headlineSmall, - ), - const SizedBox(height: Constants.SPACING * 2), - const CircularProgressIndicator(), - ], ), - ), - ); - }, + ) + ], + ), ), ); } -} +} \ No newline at end of file diff --git a/lib/src/features/visits/presentations/widgets/AllergiesTab.dart b/lib/src/features/visits/presentations/widgets/AllergiesTab.dart index 913c428f..98df0d43 100644 --- a/lib/src/features/visits/presentations/widgets/AllergiesTab.dart +++ b/lib/src/features/visits/presentations/widgets/AllergiesTab.dart @@ -1,58 +1,58 @@ -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:nishauri/src/features/visits/data/models/allergy.dart'; -import 'package:nishauri/src/shared/display/AppCard.dart'; -import 'package:nishauri/src/utils/constants.dart'; - -class AllergiesTab extends StatelessWidget { - final List allergies; - - const AllergiesTab({super.key, this.allergies = const []}); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Column( - children: [ - Padding( - padding: const EdgeInsets.all(Constants.SPACING), - child: Text( - "Allergies", - style: theme.textTheme.headlineMedium, - ), - ), - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: AppCard( - child: DataTable( - columns: [ - DataColumn(label: Text("Allergen", style: theme.textTheme.titleMedium)), - DataColumn(label: Text("Reaction", style: theme.textTheme.titleMedium)), - DataColumn(label: Text("Severity", style: theme.textTheme.titleMedium)), - DataColumn(label: Text("Date Recorded", style: theme.textTheme.titleMedium)) - ], - rows: allergies - .map( - (e) => DataRow( - cells: [ - DataCell(Text(e.allergen)), - DataCell(Text(e.reaction)), - DataCell(Text(e.severity)), - DataCell( - Text( - DateFormat("dd MMM yyy").format( - DateTime.parse(e.dateRecorded), - ), - ), - ), - ], - ), - ) - .toList(), - ), - ), - ), - ], - ); - } -} +// import 'package:flutter/material.dart'; +// import 'package:intl/intl.dart'; +// import 'package:nishauri/src/features/visits/data/models/allergy.dart'; +// import 'package:nishauri/src/shared/display/AppCard.dart'; +// import 'package:nishauri/src/utils/constants.dart'; +// +// class AllergiesTab extends StatelessWidget { +// final List allergies; +// +// const AllergiesTab({super.key, this.allergies = const []}); +// +// @override +// Widget build(BuildContext context) { +// final theme = Theme.of(context); +// return Column( +// children: [ +// Padding( +// padding: const EdgeInsets.all(Constants.SPACING), +// child: Text( +// "Allergies", +// style: theme.textTheme.headlineMedium, +// ), +// ), +// SingleChildScrollView( +// scrollDirection: Axis.horizontal, +// child: AppCard( +// child: DataTable( +// columns: [ +// DataColumn(label: Text("Allergen", style: theme.textTheme.titleMedium)), +// DataColumn(label: Text("Reaction", style: theme.textTheme.titleMedium)), +// DataColumn(label: Text("Severity", style: theme.textTheme.titleMedium)), +// DataColumn(label: Text("Date Recorded", style: theme.textTheme.titleMedium)) +// ], +// rows: allergies +// .map( +// (e) => DataRow( +// cells: [ +// DataCell(Text(e.allergen)), +// DataCell(Text(e.reaction)), +// DataCell(Text(e.severity)), +// DataCell( +// Text( +// DateFormat("dd MMM yyy").format( +// DateTime.parse(e.dateRecorded), +// ), +// ), +// ), +// ], +// ), +// ) +// .toList(), +// ), +// ), +// ), +// ], +// ); +// } +// } diff --git a/lib/src/features/visits/presentations/widgets/ComplaintsTab.dart b/lib/src/features/visits/presentations/widgets/ComplaintsTab.dart index 776f714d..5ebd3b2e 100644 --- a/lib/src/features/visits/presentations/widgets/ComplaintsTab.dart +++ b/lib/src/features/visits/presentations/widgets/ComplaintsTab.dart @@ -1,64 +1,64 @@ -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:nishauri/src/features/visits/data/models/complaint.dart'; -import 'package:nishauri/src/shared/display/AppCard.dart'; -import 'package:nishauri/src/utils/constants.dart'; - -class ComplaintsTab extends StatelessWidget { - final List complaints; - const ComplaintsTab({super.key, this.complaints=const[]}); - - @override - Widget build(BuildContext context) { - final theme= Theme.of(context); - return Column( - children: [ - Padding( - padding: const EdgeInsets.all(Constants.SPACING), - child: Text( - "Complaints", - style: theme.textTheme.headlineMedium, - ), - ), - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: AppCard( - variant: CardVariant.OUTLINED, - child: DataTable( - columns: [ - DataColumn(label: Text("Name", style: theme.textTheme.titleMedium)), - DataColumn(label: Text("Value", style: theme.textTheme.titleMedium)), - DataColumn(label: Text("On set Date", style: theme.textTheme.titleMedium)), - DataColumn(label: Text("Date Recorded", style: theme.textTheme.titleMedium)) - ], - rows: complaints - .map( - (e) => DataRow( - cells: [ - DataCell(Text(e.name)), - DataCell(Text(e.value)), - DataCell( - e.onsetDate != null && e.onsetDate?.isNotEmpty == true? Text( - DateFormat("dd MMM yyy").format( - DateTime.parse(e.onsetDate!), - ), - ): const Text("-"), - ), - DataCell( - Text( - DateFormat("dd MMM yyy").format( - DateTime.parse(e.dateRecorded), - ), - ), - ), - ], - ), - ) - .toList(), - ), - ), - ), - ], - ); - } -} +// import 'package:flutter/material.dart'; +// import 'package:intl/intl.dart'; +// import 'package:nishauri/src/features/visits/data/models/medication.dart'; +// import 'package:nishauri/src/shared/display/AppCard.dart'; +// import 'package:nishauri/src/utils/constants.dart'; +// +// class ComplaintsTab extends StatelessWidget { +// final List complaints; +// const ComplaintsTab({super.key, this.complaints=const[]}); +// +// @override +// Widget build(BuildContext context) { +// final theme= Theme.of(context); +// return Column( +// children: [ +// Padding( +// padding: const EdgeInsets.all(Constants.SPACING), +// child: Text( +// "Complaints", +// style: theme.textTheme.headlineMedium, +// ), +// ), +// SingleChildScrollView( +// scrollDirection: Axis.horizontal, +// child: AppCard( +// variant: CardVariant.OUTLINED, +// child: DataTable( +// columns: [ +// DataColumn(label: Text("Name", style: theme.textTheme.titleMedium)), +// DataColumn(label: Text("Value", style: theme.textTheme.titleMedium)), +// DataColumn(label: Text("On set Date", style: theme.textTheme.titleMedium)), +// DataColumn(label: Text("Date Recorded", style: theme.textTheme.titleMedium)) +// ], +// rows: complaints +// .map( +// (e) => DataRow( +// cells: [ +// DataCell(Text(e.name)), +// DataCell(Text(e.value)), +// DataCell( +// e.onsetDate != null && e.onsetDate?.isNotEmpty == true? Text( +// DateFormat("dd MMM yyy").format( +// DateTime.parse(e.onsetDate!), +// ), +// ): const Text("-"), +// ), +// DataCell( +// Text( +// DateFormat("dd MMM yyy").format( +// DateTime.parse(e.dateRecorded), +// ), +// ), +// ), +// ], +// ), +// ) +// .toList(), +// ), +// ), +// ), +// ], +// ); +// } +// } diff --git a/lib/src/features/visits/presentations/widgets/ConditionsTab.dart b/lib/src/features/visits/presentations/widgets/ConditionsTab.dart index 9b0e6338..e4bb448b 100644 --- a/lib/src/features/visits/presentations/widgets/ConditionsTab.dart +++ b/lib/src/features/visits/presentations/widgets/ConditionsTab.dart @@ -1,67 +1,67 @@ -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:nishauri/src/features/visits/data/models/condition.dart'; -import 'package:nishauri/src/shared/display/AppCard.dart'; -import 'package:nishauri/src/utils/constants.dart'; - -class ConditionsTab extends StatelessWidget { - final List conditions; - - const ConditionsTab({super.key, this.conditions = const []}); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Column( - children: [ - Padding( - padding: const EdgeInsets.all(Constants.SPACING), - child: Text( - "Conditions", - style: theme.textTheme.headlineMedium, - ), - ), - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: AppCard( - variant: CardVariant.ELEVETED, - child: DataTable( - columns: [ - DataColumn(label: Text("Name", style: theme.textTheme.titleMedium)), - DataColumn(label: Text("Value", style: theme.textTheme.titleMedium)), - DataColumn(label: Text("Status", style: theme.textTheme.titleMedium)), - DataColumn(label: Text("On set Date", style: theme.textTheme.titleMedium)), - DataColumn(label: Text("Date Recorded", style: theme.textTheme.titleMedium)) - ], - rows: conditions - .map( - (e) => DataRow( - cells: [ - DataCell(Text(e.name)), - DataCell(Text(e.value)), - DataCell(Text(e.status)), - DataCell( - e.onsetDate != null && e.onsetDate?.isNotEmpty == true? Text( - DateFormat("dd MMM yyy").format( - DateTime.parse(e.onsetDate!), - ), - ): const Text("-"), - ), - DataCell( - Text( - DateFormat("dd MMM yyy").format( - DateTime.parse(e.dateRecorded), - ), - ), - ), - ], - ), - ) - .toList(), - ), - ), - ), - ], - ); - } -} +// import 'package:flutter/material.dart'; +// import 'package:intl/intl.dart'; +// import 'package:nishauri/src/features/visits/data/models/condition.dart'; +// import 'package:nishauri/src/shared/display/AppCard.dart'; +// import 'package:nishauri/src/utils/constants.dart'; +// +// class ConditionsTab extends StatelessWidget { +// final List conditions; +// +// const ConditionsTab({super.key, this.conditions = const []}); +// +// @override +// Widget build(BuildContext context) { +// final theme = Theme.of(context); +// return Column( +// children: [ +// Padding( +// padding: const EdgeInsets.all(Constants.SPACING), +// child: Text( +// "Conditions", +// style: theme.textTheme.headlineMedium, +// ), +// ), +// SingleChildScrollView( +// scrollDirection: Axis.horizontal, +// child: AppCard( +// variant: CardVariant.ELEVETED, +// child: DataTable( +// columns: [ +// DataColumn(label: Text("Name", style: theme.textTheme.titleMedium)), +// DataColumn(label: Text("Value", style: theme.textTheme.titleMedium)), +// DataColumn(label: Text("Status", style: theme.textTheme.titleMedium)), +// DataColumn(label: Text("On set Date", style: theme.textTheme.titleMedium)), +// DataColumn(label: Text("Date Recorded", style: theme.textTheme.titleMedium)) +// ], +// rows: conditions +// .map( +// (e) => DataRow( +// cells: [ +// DataCell(Text(e.name)), +// DataCell(Text(e.value)), +// DataCell(Text(e.status)), +// DataCell( +// e.onsetDate != null && e.onsetDate?.isNotEmpty == true? Text( +// DateFormat("dd MMM yyy").format( +// DateTime.parse(e.onsetDate!), +// ), +// ): const Text("-"), +// ), +// DataCell( +// Text( +// DateFormat("dd MMM yyy").format( +// DateTime.parse(e.dateRecorded), +// ), +// ), +// ), +// ], +// ), +// ) +// .toList(), +// ), +// ), +// ), +// ], +// ); +// } +// } diff --git a/lib/src/features/visits/presentations/widgets/Diagnosis.dart b/lib/src/features/visits/presentations/widgets/Diagnosis.dart index f87265e8..ea9db140 100644 --- a/lib/src/features/visits/presentations/widgets/Diagnosis.dart +++ b/lib/src/features/visits/presentations/widgets/Diagnosis.dart @@ -1,56 +1,56 @@ -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:nishauri/src/features/visits/data/models/diagnosis.dart'; -import 'package:nishauri/src/shared/display/AppCard.dart'; -import 'package:nishauri/src/utils/constants.dart'; - -class DiagnosisTab extends StatelessWidget { - final List diagnosis; - const DiagnosisTab({super.key, this.diagnosis =const[]}); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Column( - children: [ - Padding( - padding: const EdgeInsets.all(Constants.SPACING), - child: Text( - "Diagnosis", - style: theme.textTheme.headlineMedium, - ), - ), - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: AppCard( - variant: CardVariant.OUTLINED, - child: DataTable( - columns: [ - DataColumn(label: Text("Name", style: theme.textTheme.titleMedium)), - DataColumn(label: Text("Value", style: theme.textTheme.titleMedium)), - DataColumn(label: Text("Date Recorded", style: theme.textTheme.titleMedium)) - ], - rows: diagnosis - .map( - (e) => DataRow( - cells: [ - DataCell(Text(e.name)), - DataCell(Text(e.value)), - DataCell( - Text( - DateFormat("dd MMM yyy").format( - DateTime.parse(e.dateRecorded), - ), - ), - ), - ], - ), - ) - .toList(), - ), - ), - ), - ], - ); - } -} +// import 'package:flutter/material.dart'; +// import 'package:intl/intl.dart'; +// import 'package:nishauri/src/features/visits/data/models/procedure.dart'; +// import 'package:nishauri/src/shared/display/AppCard.dart'; +// import 'package:nishauri/src/utils/constants.dart'; +// +// class DiagnosisTab extends StatelessWidget { +// final List diagnosis; +// const DiagnosisTab({super.key, this.diagnosis =const[]}); +// +// @override +// Widget build(BuildContext context) { +// final theme = Theme.of(context); +// return Column( +// children: [ +// Padding( +// padding: const EdgeInsets.all(Constants.SPACING), +// child: Text( +// "Diagnosis", +// style: theme.textTheme.headlineMedium, +// ), +// ), +// SingleChildScrollView( +// scrollDirection: Axis.horizontal, +// child: AppCard( +// variant: CardVariant.OUTLINED, +// child: DataTable( +// columns: [ +// DataColumn(label: Text("Name", style: theme.textTheme.titleMedium)), +// DataColumn(label: Text("Value", style: theme.textTheme.titleMedium)), +// DataColumn(label: Text("Date Recorded", style: theme.textTheme.titleMedium)) +// ], +// rows: diagnosis +// .map( +// (e) => DataRow( +// cells: [ +// DataCell(Text(e.name)), +// DataCell(Text(e.value)), +// DataCell( +// Text( +// DateFormat("dd MMM yyy").format( +// DateTime.parse(e.dateRecorded), +// ), +// ), +// ), +// ], +// ), +// ) +// .toList(), +// ), +// ), +// ), +// ], +// ); +// } +// } diff --git a/lib/src/features/visits/presentations/widgets/LabResultsTab.dart b/lib/src/features/visits/presentations/widgets/LabResultsTab.dart index 5a85c651..c88ec8e9 100644 --- a/lib/src/features/visits/presentations/widgets/LabResultsTab.dart +++ b/lib/src/features/visits/presentations/widgets/LabResultsTab.dart @@ -1,55 +1,55 @@ -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:nishauri/src/features/visits/data/models/lab_result.dart'; -import 'package:nishauri/src/shared/display/AppCard.dart'; -import 'package:nishauri/src/utils/constants.dart'; - -class LabResultsTab extends StatelessWidget { - final List labResult; - const LabResultsTab({super.key, this.labResult=const[]}); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Column( - children: [ - Padding( - padding: const EdgeInsets.all(Constants.SPACING), - child: Text( - "Lab results", - style: theme.textTheme.headlineMedium, - ), - ), - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: AppCard( - child: DataTable( - columns: [ - DataColumn(label: Text("Name", style: theme.textTheme.titleMedium)), - DataColumn(label: Text("Value", style: theme.textTheme.titleMedium)), - DataColumn(label: Text("Date Recorded", style: theme.textTheme.titleMedium)) - ], - rows: labResult - .map( - (e) => DataRow( - cells: [ - DataCell(Text(e.name)), - DataCell(Text(e.value)), - DataCell( - Text( - DateFormat("dd MMM yyy").format( - DateTime.parse(e.dateRecorded), - ), - ), - ), - ], - ), - ) - .toList(), - ), - ), - ), - ], - ); - } -} +// import 'package:flutter/material.dart'; +// import 'package:intl/intl.dart'; +// import 'package:nishauri/src/features/visits/data/models/lab_result.dart'; +// import 'package:nishauri/src/shared/display/AppCard.dart'; +// import 'package:nishauri/src/utils/constants.dart'; +// +// class LabResultsTab extends StatelessWidget { +// final List labResult; +// const LabResultsTab({super.key, this.labResult=const[]}); +// +// @override +// Widget build(BuildContext context) { +// final theme = Theme.of(context); +// return Column( +// children: [ +// Padding( +// padding: const EdgeInsets.all(Constants.SPACING), +// child: Text( +// "Lab results", +// style: theme.textTheme.headlineMedium, +// ), +// ), +// SingleChildScrollView( +// scrollDirection: Axis.horizontal, +// child: AppCard( +// child: DataTable( +// columns: [ +// DataColumn(label: Text("Name", style: theme.textTheme.titleMedium)), +// DataColumn(label: Text("Value", style: theme.textTheme.titleMedium)), +// DataColumn(label: Text("Date Recorded", style: theme.textTheme.titleMedium)) +// ], +// rows: labResult +// .map( +// (e) => DataRow( +// cells: [ +// DataCell(Text(e.name)), +// DataCell(Text(e.value)), +// DataCell( +// Text( +// DateFormat("dd MMM yyy").format( +// DateTime.parse(e.dateRecorded), +// ), +// ), +// ), +// ], +// ), +// ) +// .toList(), +// ), +// ), +// ), +// ], +// ); +// } +// } diff --git a/lib/src/features/visits/presentations/widgets/VitalsTab.dart b/lib/src/features/visits/presentations/widgets/VitalsTab.dart index 4b56e424..5b353a34 100644 --- a/lib/src/features/visits/presentations/widgets/VitalsTab.dart +++ b/lib/src/features/visits/presentations/widgets/VitalsTab.dart @@ -1,59 +1,59 @@ -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:nishauri/src/features/visits/data/models/vital.dart'; -import 'package:nishauri/src/shared/display/AppCard.dart'; -import 'package:nishauri/src/utils/constants.dart'; - -class VitalsTab extends StatelessWidget { - final List vitals; - - const VitalsTab({super.key, this.vitals = const []}); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return SingleChildScrollView( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(Constants.SPACING), - child: Text( - "Vitals", - style: theme.textTheme.headlineMedium, - ), - ), - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: AppCard( - variant: CardVariant.OUTLINED, - child: DataTable( - columns: [ - DataColumn(label: Text("Name", style: theme.textTheme.titleMedium)), - DataColumn(label: Text("Value", style: theme.textTheme.titleMedium)), - DataColumn(label: Text("Date Recorded", style: theme.textTheme.titleMedium)) - ], - rows: vitals - .map( - (e) => DataRow( - cells: [ - DataCell(Text(e.name)), - DataCell(Text(e.value)), - DataCell( - Text( - DateFormat("dd MMM yyy").format( - DateTime.parse(e.dateRecorded), - ), - ), - ), - ], - ), - ) - .toList(), - ), - ), - ), - ], - ), - ); - } -} +// import 'package:flutter/material.dart'; +// import 'package:intl/intl.dart'; +// import 'package:nishauri/src/features/visits/data/models/vital.dart'; +// import 'package:nishauri/src/shared/display/AppCard.dart'; +// import 'package:nishauri/src/utils/constants.dart'; +// +// class VitalsTab extends StatelessWidget { +// final List vitals; +// +// const VitalsTab({super.key, this.vitals = const []}); +// +// @override +// Widget build(BuildContext context) { +// final theme = Theme.of(context); +// return SingleChildScrollView( +// child: Column( +// children: [ +// Padding( +// padding: const EdgeInsets.all(Constants.SPACING), +// child: Text( +// "Vitals", +// style: theme.textTheme.headlineMedium, +// ), +// ), +// SingleChildScrollView( +// scrollDirection: Axis.horizontal, +// child: AppCard( +// variant: CardVariant.OUTLINED, +// child: DataTable( +// columns: [ +// DataColumn(label: Text("Name", style: theme.textTheme.titleMedium)), +// DataColumn(label: Text("Value", style: theme.textTheme.titleMedium)), +// DataColumn(label: Text("Date Recorded", style: theme.textTheme.titleMedium)) +// ], +// rows: vitals +// .map( +// (e) => DataRow( +// cells: [ +// DataCell(Text(e.name)), +// DataCell(Text(e.value)), +// DataCell( +// Text( +// DateFormat("dd MMM yyy").format( +// DateTime.parse(e.dateRecorded), +// ), +// ), +// ), +// ], +// ), +// ) +// .toList(), +// ), +// ), +// ), +// ], +// ), +// ); +// } +// } diff --git a/lib/src/shared/charts/CustomLineChart.dart b/lib/src/shared/charts/CustomLineChart.dart index d11052b7..0d82aa76 100644 --- a/lib/src/shared/charts/CustomLineChart.dart +++ b/lib/src/shared/charts/CustomLineChart.dart @@ -18,6 +18,7 @@ class CustomLineChart extends StatelessWidget { final List gradientColors; final double? interval; final String dateFormat; + final String? filter; const CustomLineChart({ Key? key, @@ -35,106 +36,110 @@ class CustomLineChart extends StatelessWidget { required this.bottomTile, this.interval, required this.dateFormat, + this.filter }) : super(key: key); @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: LineChart( - LineChartData( - lineBarsData: [ - LineChartBarData( - spots: dataPoints, - isCurved: true, - color: barColor, - barWidth: 6, - belowBarData: BarAreaData( - show: true, - gradient: LinearGradient( - colors: gradientColors - .map((color) => color.withOpacity(0.3)) - .toList(), + return Container( + height: 300, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: LineChart( + LineChartData( + lineBarsData: [ + LineChartBarData( + spots: dataPoints, + isCurved: true, + color: barColor, + barWidth: 2, + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: gradientColors + .map((color) => color.withOpacity(0.3)) + .toList(), + ), ), + dotData: const FlDotData(show: true), ), - dotData: const FlDotData(show: true), - ), - ], - titlesData: FlTitlesData( - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: bottomTile, - getTitlesWidget: (value, meta) { - int index = value.toInt(); - if (index >= 0 && index < dateTimes.length) { - DateTime date = DateTime.parse(dateTimes[index]); - return Padding( - padding: const EdgeInsets.all(4.0), - child: Transform.rotate( - angle: -45 * - (3.14 / - 180), // Rotate the text by -45 degrees - child: Text(DateFormat(dateFormat).format(date)), - ), + ], + titlesData: FlTitlesData( + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: bottomTile, + getTitlesWidget: (value, meta) { + int index = value.toInt(); + if (index >= 0 && index < dateTimes.length) { + DateTime date = DateTime.parse(dateTimes[index]); + return Padding( + padding: const EdgeInsets.all(4.0), + child: Transform.rotate( + angle: -45 * + (3.14 / + 180), + child: Text(DateFormat(dateFormat).format(date)), + ), + ); + } + return const Text(''); + }, + reservedSize: 30, + interval: 1, + ), + axisNameWidget: Text(xAxisLabel ?? ''), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: leftTile, + getTitlesWidget: (value, meta) { + return Text( + value.toString(), + style: const TextStyle(fontSize: 10), ); - } - return const Text(''); - }, - reservedSize: 30, - interval: 1, // Show labels at an interval of 1 unit + }, + reservedSize: 30, + interval: + interval ?? 1, // Show labels at an interval of 1 unit + ), + axisNameWidget: Text(yAxisLabel ?? ''), ), - axisNameWidget: Text(xAxisLabel ?? ''), - ), - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: leftTile, - getTitlesWidget: (value, meta) { - return Text( - value.toString(), - style: const TextStyle(fontSize: 10), - ); - }, - reservedSize: 30, - interval: - interval ?? 1, // Show labels at an interval of 1 unit + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), ), - axisNameWidget: Text(yAxisLabel ?? ''), - ), - topTitles: const AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - rightTitles: const AxisTitles( - sideTitles: SideTitles(showTitles: false), ), - ), - minX: minX, - maxX: maxX, - minY: minY, - maxY: maxY, - borderData: FlBorderData(show: true), - gridData: FlGridData( - show: true, - drawVerticalLine: true, - drawHorizontalLine: true, - getDrawingHorizontalLine: (value) { - return const FlLine(strokeWidth: 1, color: Colors.grey); - }, - getDrawingVerticalLine: (value) { - if (value.toInt() % 1 == 0) { + minX: minX, + maxX: maxX, + minY: minY, + maxY: maxY, + borderData: FlBorderData(show: true), + gridData: FlGridData( + show: true, + drawVerticalLine: true, + drawHorizontalLine: true, + getDrawingHorizontalLine: (value) { return const FlLine(strokeWidth: 1, color: Colors.grey); - } - return const FlLine( - strokeWidth: 0); // Hide lines for non-integer values - }, + }, + getDrawingVerticalLine: (value) { + if (value.toInt() % 1 == 0) { + return const FlLine(strokeWidth: 1, color: Colors.grey); + } + return const FlLine( + strokeWidth: 0); + }, + ), ), ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/src/shared/charts/CustomeMultLineChart.dart b/lib/src/shared/charts/CustomeMultLineChart.dart index 55853adc..206839e8 100644 --- a/lib/src/shared/charts/CustomeMultLineChart.dart +++ b/lib/src/shared/charts/CustomeMultLineChart.dart @@ -12,6 +12,7 @@ class CustomMultiLineChart extends StatelessWidget { final double? minY; final double? maxY; final bool showLeftTitles; + final String? filter; const CustomMultiLineChart({ Key? key, @@ -24,6 +25,7 @@ class CustomMultiLineChart extends StatelessWidget { this.maxY, required this.dateTimes, required this.showLeftTitles, + this.filter }) : super(key: key); @override @@ -43,13 +45,22 @@ class CustomMultiLineChart extends StatelessWidget { showTitles: true, getTitlesWidget: (value, meta) { int index = value.toInt(); + + // return Padding( + // padding: const EdgeInsets.all(4.0), + // child: Text(label), + // ); if (index >= 0 && index < dateTimes.length) { - DateTime date = DateTime.parse(dateTimes[index]); + + + String label = dateTimes[index]; + return Padding( padding: const EdgeInsets.all(4.0), child: Transform.rotate( - angle: -45 * (3.14 / 180), // Rotate the text by -45 degrees - child: Text(DateFormat('HH:mm-dd/MM').format(date)), + angle: -45 * (3.14 / 180), + child: Text(label), + // child: Text(DateFormat('HH:mm-dd/MM').format(date)), ), ); } @@ -82,17 +93,17 @@ class CustomMultiLineChart extends StatelessWidget { maxX: maxX, minY: minY, maxY: maxY, - borderData: FlBorderData(show: true), + borderData: FlBorderData(show: false), gridData: FlGridData( show: true, - drawVerticalLine: true, + drawVerticalLine: false, drawHorizontalLine: true, - getDrawingHorizontalLine: (value) { - return FlLine(strokeWidth: 1, color: Colors.grey); - }, - getDrawingVerticalLine: (value) { - return FlLine(strokeWidth: 1, color: Colors.grey); - }, + // getDrawingHorizontalLine: (value) { + // return FlLine(strokeWidth: 1, color: Colors.grey); + // }, + // getDrawingVerticalLine: (value) { + // return FlLine(strokeWidth: 1, color: Colors.grey); + // }, ), ), ), diff --git a/lib/src/shared/dialog/dialog.dart b/lib/src/shared/dialog/dialog.dart new file mode 100644 index 00000000..7a80c19b --- /dev/null +++ b/lib/src/shared/dialog/dialog.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:material_dialogs/material_dialogs.dart'; +import 'package:material_dialogs/widgets/buttons/icon_button.dart'; +import 'package:material_dialogs/widgets/buttons/icon_outline_button.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/routes.dart'; + +class HealthProgramDialog { + final BuildContext context; + final msg; + final tittle; + final color; + final okBtnTxt; + final okBtnColor; + final xBtnTxt; + final xBtnColor; + final Function()? okOnTap; + final Function()? xOnTap; + + HealthProgramDialog(this.context, this.msg, this.tittle, this.color, this.okBtnTxt,this.okBtnColor, this.xBtnTxt, this.xBtnColor, this.okOnTap, this.xOnTap); + + void show() { + Dialogs.bottomMaterialDialog( + msg: msg, + msgStyle: const TextStyle(color: Constants.labResultsColor), + context: context, + color: color, + title: tittle, + titleStyle: const TextStyle(color: Constants.labResultsColor, fontWeight: FontWeight.w600), + actions: [ + IconsOutlineButton( + onPressed: () { + if (okOnTap != null) okOnTap!(); + Navigator.of(context).pop(); + }, + text: okBtnTxt, + iconData: Icons.add, + textStyle: const TextStyle(color: Colors.white), + color: xBtnColor, + iconColor: Colors.white, + ), + IconsButton( + onPressed: () { + if (xOnTap != null) xOnTap!(); + Navigator.of(context).pop(); + }, + text: xBtnTxt, + iconData: Icons.cancel_outlined, + color: xBtnColor, + textStyle: const TextStyle(color: Colors.white), + iconColor: Colors.white, + ), + ], + ); + } +} diff --git a/lib/src/shared/display/CustomAppBar.dart b/lib/src/shared/display/CustomAppBar.dart new file mode 100644 index 00000000..b14db7f6 --- /dev/null +++ b/lib/src/shared/display/CustomAppBar.dart @@ -0,0 +1,146 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:nishauri/src/shared/input/Button.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +import '../../utils/helpers.dart'; + +class CustomAppBar extends StatelessWidget { + final String? title; + final String? subTitle; + final IconData? icon; + final Color? color; + final Widget? bottom; + final double? height; + final String? smallTitle; + final String? rightBtTitle; + final String? path; + final String? svgPathGroup; + + const CustomAppBar({ + super.key, + this.title, + this.subTitle, + this.icon, + this.bottom, + this.color, + this.height, + this.smallTitle, + this.rightBtTitle, + this.path, + this.svgPathGroup, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Container( + height: height, + width: double.infinity, // Use double.infinity for responsive width + padding: const EdgeInsets.symmetric( + horizontal: Constants.SPACING, + vertical: Constants.SPACING * 3, + ), + decoration: BoxDecoration( + color: color ?? theme.colorScheme.primary, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(Constants.SPACING), + bottomRight: Radius.circular(Constants.SPACING), + ), + // image: const DecorationImage( + // image: AssetImage("assets/images/contours.png"), + // opacity: 0.1, + // fit: BoxFit.cover, + // ), + ), + child: Stack( + children: [ + Positioned( + top: 0, + right: 0, + child: SvgPicture.asset( + svgPathGroup ?? '', + semanticsLabel: "Background SVG", + fit: BoxFit.contain, + width: 110, + height: 110, + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: Constants.SPACING), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + InkWell( + child: SvgPicture.asset( + "assets/images/reply.svg", + semanticsLabel: "Back", + fit: BoxFit.contain, + width: 25, + height: 25, + ), + onTap: () => context.pop(), + ), + if (smallTitle != null) + Text( + smallTitle ?? '', + style: theme.textTheme.bodyMedium?.copyWith(color: Colors.white), + ), + if (rightBtTitle != null) + TextButton( + onPressed: (){ + context.goNamed(path??''); + }, + child: Text(rightBtTitle??'', style: theme.textTheme.bodyMedium!.copyWith(color: Constants.bgColor),) + // Container( + // height: 10, + // width: 150, + // child: Button( + // backgroundColor: color, + // textColor: Constants.bgColor, + // title: rightBtTitle ?? '', + // onPress: () { + // // Add your onPress logic here + // }, + // ), + ) + ], + ), + if (title != null) + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: Constants.SPACING), + Text( + title ?? '', + style: theme.textTheme.headlineMedium?.copyWith(color: Colors.white), + ), + const SizedBox(width: Constants.SPACING), + if (icon != null) + Icon( + icon, + color: theme.canvasColor, + ), + ], + ), + if (subTitle != null) + const SizedBox(height: Constants.SPACING * 2), + if (subTitle != null) + Text( + subTitle!, + style: theme.textTheme.titleLarge?.copyWith(color: Colors.white), + ), + if (bottom != null) + const SizedBox(height: Constants.SPACING * 2), + Center(child: bottom), + ], + ), + ], + ), + ); + } +} diff --git a/lib/src/shared/display/CustomTabBar.dart b/lib/src/shared/display/CustomTabBar.dart index 16ae9426..a8ce1eff 100644 --- a/lib/src/shared/display/CustomTabBar.dart +++ b/lib/src/shared/display/CustomTabBar.dart @@ -4,8 +4,9 @@ import 'package:nishauri/src/utils/constants.dart'; class CustomTabBarItem { final String title; final IconData? icon; + final Widget? trailing; - const CustomTabBarItem({required this.title, this.icon}); + const CustomTabBarItem({required this.title, this.icon, this.trailing}); } class CustomTabBar extends StatelessWidget { @@ -14,12 +15,13 @@ class CustomTabBar extends StatelessWidget { final Color activeColor; final int? activeIndex; - const CustomTabBar( - {super.key, - this.items = const [], - required this.onTap, - this.activeColor = Constants.activeSelectionColor, - this.activeIndex}); + const CustomTabBar({ + super.key, + this.items = const [], + required this.onTap, + this.activeColor = Constants.activeSelectionColor, + this.activeIndex, + }); @override Widget build(BuildContext context) { @@ -32,52 +34,53 @@ class CustomTabBar extends StatelessWidget { mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, - children: items - .asMap() - .entries - .map( - (e) => Card( - clipBehavior: Clip.antiAlias, - elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(Constants.ROUNDNESS * 10), - ), - child: InkWell( - onTap: () { - onTap( - e.value, - e.key, - ); - }, - child: Container( - padding: const EdgeInsets.symmetric(horizontal:Constants.SPACING ,vertical: Constants.SPACING* 0.5), - decoration: BoxDecoration( - color: e.key == activeIndex - ? activeColor ?? theme.colorScheme.primary - : null), - child: Row( - children: [ - if (e.value.icon != null) - Icon( - e.value.icon, - color: activeIndex == e.key ? Colors.white : null, - ), - if (e.value.icon != null) - const SizedBox(width: Constants.SPACING), - Text( - e.value.title, - style: theme.textTheme.labelLarge?.copyWith( - color: activeIndex == e.key ? Colors.white : null, - ), + children: items.asMap().entries.map( + (e) { + return Card( + clipBehavior: Clip.antiAlias, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(Constants.ROUNDNESS * 10), + ), + child: InkWell( + onTap: () { + onTap(e.value, e.key); + }, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: Constants.SPACING, + vertical: Constants.SPACING * 0.5, + ), + decoration: BoxDecoration( + color: e.key == activeIndex + ? activeColor ?? theme.colorScheme.primary + : null, + ), + child: Row( + children: [ + if (e.value.icon != null) + Icon( + e.value.icon, + color: activeIndex == e.key ? Colors.white : null, + ), + if (e.value.icon != null) + const SizedBox(width: Constants.SPACING), + Text( + e.value.title, + style: theme.textTheme.labelLarge?.copyWith( + color: activeIndex == e.key ? Colors.white : null, ), - ], - ), + ), + if (e.value.trailing != null) + const SizedBox(width: Constants.SPACING), + if (e.value.trailing != null) e.value.trailing!, + ], ), ), ), - ) - .toList(), + ); + }, + ).toList(), ), ), ); diff --git a/lib/src/shared/display/CustomeAppBar.dart b/lib/src/shared/display/CustomeAppBar.dart deleted file mode 100644 index bff68d9b..00000000 --- a/lib/src/shared/display/CustomeAppBar.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:go_router/go_router.dart'; -import 'package:nishauri/src/utils/constants.dart'; - -import '../../utils/helpers.dart'; - -class CustomAppBar extends StatelessWidget { - final String title; - final String? subTitle; - final IconData? icon; - final Color? color; - final Widget? bottom; - - const CustomAppBar( - {super.key, - required this.title, - this.subTitle, - this.icon, - this.bottom, - this.color}); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - - return Container( - padding: const EdgeInsets.symmetric( - horizontal: Constants.SPACING, vertical: Constants.SPACING * 3), - decoration: BoxDecoration( - color: color ?? theme.colorScheme.primary, - // gradient: LinearGradient( - // begin: Alignment.topLeft, - // end: Alignment.bottomRight, - // colors: [ - // color ?? theme.colorScheme.primary, - // theme.colorScheme.onSurface - // ]), - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(Constants.SPACING), - bottomRight: Radius.circular(Constants.SPACING), - ), - image: const DecorationImage( - image: AssetImage("assets/images/contours.png"), - opacity: 0.1, - fit: BoxFit.cover, - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: Constants.SPACING), - InkWell( - child: SvgPicture.asset( - "assets/images/reply.svg", - semanticsLabel: "Doctors", - fit: BoxFit.contain, - width: 40, - height: 40, - ), - onTap: () => context.pop(), - ), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - title, - style: theme.textTheme.headlineMedium - ?.copyWith(color: Colors.white), - ), - const SizedBox(width: Constants.SPACING), - Icon( - icon, - color: theme.canvasColor, - ) - ], - ), - if (subTitle != null) const SizedBox(height: Constants.SPACING * 2), - if (subTitle != null) - Text( - subTitle!, - style: theme.textTheme.titleLarge?.copyWith(color: Colors.white), - ), - if (bottom != null) const SizedBox(height: Constants.SPACING * 2), - if (bottom != null) bottom! - ], - ), - ); - } -} diff --git a/lib/src/shared/display/ProfileCard.dart b/lib/src/shared/display/ProfileCard.dart index ea5ec4af..d02f3262 100644 --- a/lib/src/shared/display/ProfileCard.dart +++ b/lib/src/shared/display/ProfileCard.dart @@ -13,6 +13,7 @@ class ProfileCard extends StatelessWidget { final double headerFactor; final String? image; final IconData icon; + final Color color; const ProfileCard({super.key, this.height = 600, @@ -26,6 +27,7 @@ class ProfileCard extends StatelessWidget { this.headerFactor = 0.2, this.image, this.icon=Icons.person, + required this.color, }); @override @@ -46,7 +48,7 @@ class ProfileCard extends StatelessWidget { flex: 3, child: Container( decoration: BoxDecoration( - color: Colors.black54, + color: color, backgroundBlendMode: BlendMode.darken, image: coverPhoto != null ? DecorationImage( fit: BoxFit.cover, diff --git a/lib/src/shared/display/background_image_widget.dart b/lib/src/shared/display/background_image_widget.dart index 7b90b562..7f8aab72 100644 --- a/lib/src/shared/display/background_image_widget.dart +++ b/lib/src/shared/display/background_image_widget.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:nishauri/src/shared/display/CustomeAppBar.dart'; +import 'package:nishauri/src/shared/display/CustomAppBar.dart'; class BackgroundImageWidget extends StatelessWidget { final String svgImage; diff --git a/lib/src/shared/display/blog_post_widget.dart b/lib/src/shared/display/blog_post_widget.dart new file mode 100644 index 00000000..0cf851f7 --- /dev/null +++ b/lib/src/shared/display/blog_post_widget.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nishauri/src/utils/constants.dart'; +import 'package:nishauri/src/utils/helpers.dart'; + +class BlogPostWidget extends StatelessWidget { + final String title; + final String imageUrl; + final String description; + + const BlogPostWidget({ + Key? key, + required this.title, + required this.imageUrl, + required this.description, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: theme.textTheme.headlineMedium), + SvgPicture.asset( + imageUrl, + width: double.infinity, + height: getOrientationAwareScreenSize(context).height * 0.30, + fit: BoxFit.cover, + ), + Expanded(child: Markdown(data: description)), + ], + ), + ); + } +} diff --git a/lib/src/shared/display/custom_bottom_nav_bar.dart b/lib/src/shared/display/custom_bottom_nav_bar.dart new file mode 100644 index 00000000..dacd4fdb --- /dev/null +++ b/lib/src/shared/display/custom_bottom_nav_bar.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:nishauri/src/features/common/presentation/pages/chat_feeback_form.dart'; + +class CustomBottomNavigationBar extends StatelessWidget { + final int currentIndex; + final ValueChanged onTap; + final int messagesCount; + + const CustomBottomNavigationBar({ + Key? key, + required this.currentIndex, + required this.onTap, + required this.messagesCount, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return BottomNavigationBar( + elevation: 0, + selectedItemColor: Theme.of(context).colorScheme.primary, + unselectedItemColor: Theme.of(context).disabledColor, + items: [ + BottomNavigationBarItem( + icon: SvgPicture.asset("assets/images/Home.svg"), + label: "Home", + activeIcon: SvgPicture.asset("assets/images/Home-Active.svg"), + ), + BottomNavigationBarItem( + icon: SvgPicture.asset("assets/images/Modules.svg"), + label: "Apps", + activeIcon: SvgPicture.asset("assets/images/Modules-active.svg"), + ), + BottomNavigationBarItem( + icon: SvgPicture.asset("assets/images/Chatbot.svg"), + label: "Ask Nuru", + activeIcon: SvgPicture.asset("assets/images/Chatbot-Active.svg"), + ), + BottomNavigationBarItem( + icon: SvgPicture.asset("assets/images/Settings.svg"), + label: "Settings", + activeIcon: SvgPicture.asset("assets/images/Settings-Active.svg"), + ), + ], + currentIndex: currentIndex, + onTap: (index) async { + if (currentIndex == 2 && index != 2 && messagesCount > 2) { + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => AlertDialog( + content: Stack( + children: [ + const ChatFeedbackForm(), + Positioned( + right: 0, + top: 0, + child: IconButton( + onPressed: () => Navigator.of(context).pop(), + icon: const FaIcon(FontAwesomeIcons.xmark), + ), + ), + ], + ), + ), + ); + } + // Update current index + onTap(index); + }, + ); + } +} diff --git a/lib/src/shared/display/custome_filter_chart.dart b/lib/src/shared/display/custome_filter_chart.dart new file mode 100644 index 00000000..b89307b7 --- /dev/null +++ b/lib/src/shared/display/custome_filter_chart.dart @@ -0,0 +1,124 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class CustomFilterLineChart extends StatelessWidget { + final List? dataPoints; + final String? xAxisLabel; + final String? yAxisLabel; + final double? minX; + final double? maxX; + final double? minY; + final double? maxY; + final bool leftTile; + final Color? barColor; + final bool bottomTile; + final List gradientColors; + final double? interval; + final String? filter; + final List dateTimes; + + const CustomFilterLineChart({ + Key? key, + required this.dataPoints, + this.xAxisLabel, + this.yAxisLabel, + this.minX, + this.maxX, + this.minY, + this.maxY, + required this.leftTile, + this.barColor, + required this.gradientColors, + required this.bottomTile, + this.interval, + this.filter, + required this.dateTimes, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + height: 300, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: LineChart( + LineChartData( + lineBarsData: [ + LineChartBarData( + spots: dataPoints!, + isCurved: true, + color: barColor, + barWidth: 2, + belowBarData: BarAreaData(show: false), + dotData: FlDotData(show: true), + ), + ], + titlesData: FlTitlesData( + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: bottomTile, + getTitlesWidget: (value, meta) { + int index = value.toInt(); + String label = dateTimes[index]; + return Padding( + padding: const EdgeInsets.all(4.0), + child: Transform.rotate( + angle: -45 * (3.14 / 180), + child: Text(label), + // child: Text(DateFormat('HH:mm-dd/MM').format(date)), + ), + ); + }, + reservedSize: 30, + interval: 1, + ), + axisNameWidget: Text(xAxisLabel ?? ''), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: leftTile, + getTitlesWidget: (value, meta) { + return Text( + value.toString(), + style: const TextStyle(fontSize: 10), + ); + }, + reservedSize: 30, + interval: interval ?? 1, + ), + axisNameWidget: Text(yAxisLabel ?? ''), + ), + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + ), + minX: minX, + maxX: maxX, + minY: minY, + maxY: maxY, + borderData: FlBorderData(show: false), + gridData: FlGridData( + show: true, + drawVerticalLine: false, + drawHorizontalLine: true, + // getDrawingHorizontalLine: (value) { + // return const FlLine(strokeWidth: 1, color: Colors.grey); + // }, + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/shared/display/daily_card.dart b/lib/src/shared/display/daily_card.dart new file mode 100644 index 00000000..df8f1c33 --- /dev/null +++ b/lib/src/shared/display/daily_card.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +import '../../features/self_screening/blood_sugar/presentation/pages/BloodSugarScreen.dart'; +import '../providers/selectedIndexProvider.dart'; + +class FilterCard extends HookConsumerWidget { + final List? columnTitles; + final Function(int selectedIndex)? onPressed; + + const FilterCard({ + Key? key, + this.columnTitles, + this.onPressed, + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final selectedIndex = ref.watch(selectedIndexProvider); + + return Card( + color: Constants.bgColor, + elevation: Constants.FOUR, + child: Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + IntrinsicHeight( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildColumns(context, ref, selectedIndex), + ), + ), + ], + ), + ), + ); + } + + List _buildColumns(BuildContext context, WidgetRef ref, int selectedIndex) { + if (columnTitles!.isEmpty) { + return []; + } + + List columns = []; + for (int i = 0; i < columnTitles!.length; i++) { + String title = columnTitles![i]; + columns.add( + Expanded( + child: _buildColumn(title, i, context, ref, selectedIndex), + ), + ); + if (i < columnTitles!.length - 1) { + columns.add(const VerticalDivider(thickness: 1, color: Colors.grey)); + } + } + return columns; + } + + Widget _buildColumn(String title, int index, BuildContext context, WidgetRef ref, int selectedIndex) { + final theme = Theme.of(context); + + return Padding( + padding: const EdgeInsets.symmetric(vertical: Constants.SMALL_SPACING), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SizedBox( + width: Constants.SMALL_APP_BAR_HEIGHT2, + child: TextButton( + style: TextButton.styleFrom( + backgroundColor: index == selectedIndex ? Constants.white : null, + ), + onPressed: () => onPressed!(index), + child: Text( + title, + style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.bold), + ), + ), + ), + ], + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/shared/display/heath_filter_button.dart b/lib/src/shared/display/heath_filter_button.dart new file mode 100644 index 00000000..3ecf709e --- /dev/null +++ b/lib/src/shared/display/heath_filter_button.dart @@ -0,0 +1,95 @@ + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class HealthButton extends StatefulWidget { + final Function(DateFilter) onFilterSelected; + + const HealthButton({Key? key, required this.onFilterSelected}) : super(key: key); + + @override + _HealthButtonState createState() => _HealthButtonState(); +} + +class _HealthButtonState extends State { + DateFilter? selectedMenu; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Container( + width: MediaQuery.of(context).size.width * 0.1, + child: MenuAnchor( + builder: (BuildContext context, MenuController controller, Widget? child) { + return IconButton( + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + icon: SvgPicture.asset( + "assets/images/clinic_menu.svg", + semanticsLabel: "Doctors", + fit: BoxFit.contain, + height: 40, + width: 40, + ), + tooltip: 'Show menu', + ); + }, + menuChildren: List.generate( + DateFilter.values.length, + (int index) { + IconData iconData; + String label; + + switch (DateFilter.values[index]) { + case DateFilter.all: + iconData = Icons.calendar_today; + label = 'All'; + break; + case DateFilter.today: + iconData = Icons.today; + label = 'Today'; + break; + case DateFilter.currentWeek: + iconData = Icons.calendar_view_week; + label = 'This Week'; + break; + case DateFilter.currentMonth: + iconData = Icons.calendar_view_month; + label = 'This Month'; + break; + case DateFilter.dateRange: + iconData = Icons.date_range; + label = 'Date Range'; + break; + default: + iconData = Icons.help; + label = 'Unknown'; + } + + return MenuItemButton( + onPressed: () { + widget.onFilterSelected(DateFilter.values[index]); + }, + child: Row( + children: [ + Icon(iconData, size: Constants.BUTTON_FONT_SIZE, color: Constants.clinicCardBgColor), + SizedBox(width: Constants.SPACING), + Text(label, style: theme.textTheme.bodyMedium,), + ], + ), + ); + }, + ), + ), + ); + } +} + +enum DateFilter { all, today, currentWeek, currentMonth, dateRange } + diff --git a/lib/src/shared/display/profile_app_bar.dart b/lib/src/shared/display/profile_app_bar.dart new file mode 100644 index 00000000..a4dbc5ed --- /dev/null +++ b/lib/src/shared/display/profile_app_bar.dart @@ -0,0 +1,135 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class ProfileAppBar extends StatelessWidget { + final String? title; + final String? subTitle; + final IconData? icon; + final String? age; + final String? address; + final Color? color; + final Widget? bottom; + final double? height; + final String? smallTitle; + final String? rightBtTitle; + final String? path; + final String? svgPath; + final String? svgPathGroup; + + const ProfileAppBar({ + super.key, + this.title, + this.subTitle, + this.icon, + this.bottom, + this.color, + this.height, + this.smallTitle, + this.rightBtTitle, + this.path, + this.age, + this.address, + this.svgPath, + this.svgPathGroup, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Container( + height: height, + width: double.infinity, // Use double.infinity for responsive width + padding: const EdgeInsets.symmetric( + horizontal: Constants.SPACING, + vertical: Constants.SPACING * 3, + ), + decoration: BoxDecoration( + color: color ?? theme.colorScheme.primary, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(Constants.SPACING), + bottomRight: Radius.circular(Constants.SPACING), + ), + ), + child: Stack( + children: [ + // SVG Image positioned at the top-right corner + Positioned( + top: 0, + right: 0, + child: SvgPicture.asset( + svgPathGroup ?? '', + semanticsLabel: "Background SVG", + fit: BoxFit.contain, + width: 180, + height: 180, + ), + ), + // Main content + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: Constants.SPACING), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + InkWell( + child: SvgPicture.asset( + "assets/images/reply.svg", + semanticsLabel: "Back", + fit: BoxFit.contain, + width: 25, + height: 25, + ), + onTap: () => context.pop(), + ), + ], + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: Constants.SPACING), + SvgPicture.asset( + svgPath ?? '', + semanticsLabel: "Profile", + fit: BoxFit.contain, + width: 25, + height: 25, + ), + const SizedBox(width: 4), + Text( + title ?? '', + style: theme.textTheme.headlineMedium?.copyWith(color: Colors.white), + ), + const SizedBox(width: Constants.SPACING), + if (icon != null) + Icon( + icon, + color: theme.canvasColor, + ), + ], + ), + const SizedBox(height: Constants.SPACING * 2), + Text( + subTitle!, + style: theme.textTheme.titleLarge?.copyWith(color: Colors.white), + ), + const SizedBox(height: Constants.SPACING * 2), + Text( + age!, + style: theme.textTheme.titleLarge?.copyWith(color: Colors.white), + ), + const SizedBox(height: Constants.SPACING * 2), + Text( + address!, + style: theme.textTheme.titleLarge?.copyWith(color: Colors.white), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/src/shared/helper/health_record_filter.dart b/lib/src/shared/helper/health_record_filter.dart new file mode 100644 index 00000000..64595589 --- /dev/null +++ b/lib/src/shared/helper/health_record_filter.dart @@ -0,0 +1,92 @@ + +import 'package:flutter/material.dart'; +import 'package:nishauri/src/features/clinic_card/data/models/health_test.dart'; +import 'package:nishauri/src/shared/display/heath_filter_button.dart'; + +DateFilter? selectedFilter; + +// Helper function for applying filters +List applyFilter( + List records, + DateFilter selectedFilter, + DateTime? selectedDateRangeStart, + DateTime? selectedDateRangeEnd) { + + DateTime now = DateTime.now(); + List filteredRecords = []; + + switch (selectedFilter) { + case DateFilter.today: + filteredRecords = records.where((record) { + DateTime visitDate = DateTime.parse(record.visitDate); + return visitDate.isAtSameMomentAs(now); + }).toList(); + break; + case DateFilter.currentWeek: + filteredRecords = records.where((record) { + DateTime visitDate = DateTime.parse(record.visitDate); + return visitDate.isAfter(now.subtract(Duration(days: now.weekday - 1))) && + visitDate.isBefore(now.add(Duration(days: 7 - now.weekday))); + }).toList(); + break; + case DateFilter.currentMonth: + filteredRecords = records.where((record) { + DateTime visitDate = DateTime.parse(record.visitDate); + return visitDate.month == now.month && visitDate.year == now.year; + }).toList(); + break; + case DateFilter.dateRange: + if (selectedDateRangeStart != null && selectedDateRangeEnd != null) { + filteredRecords = records.where((record) { + DateTime visitDate = DateTime.parse(record.visitDate); + return visitDate.isAfter(selectedDateRangeStart) && + visitDate.isBefore(selectedDateRangeEnd); + }).toList(); + } + break; + case DateFilter.all: + default: + filteredRecords = records; + break; + } + + return filteredRecords; +} + +Future selectDateRange({ + required BuildContext context, + DateTimeRange? initialDateRange, + DateTime? firstDate, + String? saveText, + Color? primaryColor, + Color? barrierColor, +}) async { + final DateTime now = DateTime.now(); + + DateTime effectiveLastDate = now; + + DateTime effectiveFirstDate = firstDate ?? DateTime(now.year - 5); // Default to 5 years ago + + DateTimeRange? picked = await showDateRangePicker( + context: context, + firstDate: effectiveFirstDate, // Prevent selecting dates earlier than 5 years ago + lastDate: effectiveLastDate, // Prevent selecting dates in the future + initialDateRange: initialDateRange, + saveText: saveText ?? 'Done', // Default save text 'Done' + barrierColor: barrierColor ?? Colors.black45, // Default barrier color + builder: (BuildContext context, Widget? child) { + return Theme( + data: ThemeData.light().copyWith( + primaryColor: primaryColor ?? Colors.blue, // Default to blue if not provided + colorScheme: ColorScheme.light(primary: primaryColor ?? Colors.blue), + buttonTheme: ButtonThemeData(textTheme: ButtonTextTheme.normal), + ), + child: child!, + ); + }, + ); + + return picked; +} + + diff --git a/lib/src/shared/input/Search.dart b/lib/src/shared/input/Search.dart index 2b9247f1..1848aa09 100644 --- a/lib/src/shared/input/Search.dart +++ b/lib/src/shared/input/Search.dart @@ -1,15 +1,25 @@ import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; class Search extends StatelessWidget { - const Search({super.key}); + String searchText; + Search({required this.searchText, super.key}); @override Widget build(BuildContext context) { - return const TextField( + return TextField( decoration: InputDecoration( - hintText: "Search ...", - prefixIcon: Icon(Icons.search), - border: InputBorder.none, + hintText: searchText, + hintStyle: TextStyle(color: Colors.grey.shade600), + prefixIcon: Icon(Icons.search,color: Colors.grey.shade600, size: 20,), + filled: true, + contentPadding: EdgeInsets.all(8), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide( + color: Colors.grey.shade100 + ) + ), ), ); } diff --git a/lib/src/shared/list_view_builder/list_view_builder.dart b/lib/src/shared/list_view_builder/list_view_builder.dart new file mode 100644 index 00000000..664cf3e9 --- /dev/null +++ b/lib/src/shared/list_view_builder/list_view_builder.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:nishauri/src/utils/constants.dart'; + +class RowViewList extends StatelessWidget { + final List> details; + final Function(int index)? onRowTap; + + const RowViewList({ + Key? key, + required this.details, this.onRowTap + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(Constants.SPACING), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: details.asMap().entries.map((entry) { + int index = entry.key; + var detail = entry.value; + return GestureDetector( + onTap: () => onRowTap?.call(index), + child: _buildDetailRow(detail['icon'], detail['text']), + ); + }).toList(), + ), + ), + ); + } + + Row _buildDetailRow(IconData? icon, String? text, {int maxLines = 1}) { + return Row( + children: [ + Icon(icon, color: Constants.providerBgColor.withOpacity(0.5)), + const SizedBox(width: Constants.SPACING), + Expanded(child: Text(text??'', maxLines: maxLines)), + ], + ); + } +} diff --git a/lib/src/shared/notifications/count_budge.dart b/lib/src/shared/notifications/count_budge.dart new file mode 100644 index 00000000..7cd88bdd --- /dev/null +++ b/lib/src/shared/notifications/count_budge.dart @@ -0,0 +1,34 @@ + +import 'package:flutter/material.dart'; + +class CountBadge extends StatelessWidget { + final int count; + + const CountBadge({Key? key, required this.count}) : super(key: key); + + @override + Widget build(BuildContext context) { + if (count <= 0) return const SizedBox.shrink(); + + return Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + color: Colors.red, + borderRadius: BorderRadius.circular(10), + ), + constraints: const BoxConstraints( + minWidth: 16, + minHeight: 16, + ), + child: Text( + count.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ); + } +} diff --git a/lib/src/shared/providers/selectedIndexProvider.dart b/lib/src/shared/providers/selectedIndexProvider.dart new file mode 100644 index 00000000..cbd75107 --- /dev/null +++ b/lib/src/shared/providers/selectedIndexProvider.dart @@ -0,0 +1,3 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final selectedIndexProvider = StateProvider((ref) => 1); \ No newline at end of file diff --git a/lib/src/utils/constants.dart b/lib/src/utils/constants.dart index 865b99f1..60572c93 100644 --- a/lib/src/utils/constants.dart +++ b/lib/src/utils/constants.dart @@ -19,12 +19,22 @@ class Constants { // static const String BASE_URL = "http://192.168.100.40:5000"; static const ROUNDNESS = 10.0; static const SMALL_SPACING = 8.0; + static const SMALL_SPACING1= 4.0; static const TOKEN_KEY = 'jwt_token_key'; static const SPACING = 10.0; + static const SPACINGA = 10.0; static const MEDIUM_SCREEN_WIDTH = 600.00; static const BUTTON_FONT_SIZE = 20.0; + static const TWENTY = 20.0; static const SIDE_SPACE = 0.0; static const TIME_OUT = 300; + static const TWO_HUNDRED = 200.0; + static const GRAPH_HEIGHT = 350.0; + static const SMALL_APP_BAR_HEIGHT = 120.0; + static const SMALL_APP_BAR_HEIGHT2 = 92.0; + static const FOUR = 4.0; + static const TWO = 2.0; + static const SIXTEEN = 16.0; static const facilityDirectoryColor = Color.fromARGB(255, 106, 121, 141); static const labResultsColor = Color.fromARGB(255, 31, 37, 94); static const appointmentsColor = Color.fromARGB(255, 75, 127, 117); @@ -38,9 +48,19 @@ class Constants { static const clinicCardColor = Color.fromARGB(255,13,71,161); static const announcement = Color.fromARGB(255, 64, 87, 162); static const frequentlyAskedQuestions = Color.fromARGB(255, 123, 83, 162); + static const periodPlanner = Color.fromARGB(255, 244, 143, 177); + static const periodPlannerShortcutBgColor = Color.fromARGB(255, 255, 182, 193); static const iconSize = 70.0; static const shortcutIconSize = 30.0; static const bpShortCutBgColor = Color.fromARGB(255, 255, 205, 210); static const bpBgColor = Color.fromARGB(255, 255, 83, 80); static const bloodSugarColor = Color.fromARGB(255, 180, 216, 20); + static const providerBgColor = Color.fromARGB(255, 37, 102, 92); + static const selfScreeningBgColor = Color.fromARGB(255, 151, 57, 54); + static const bgColor = Color.fromARGB(255, 245, 245, 245); + static const barColor = Color.fromARGB(255, 4, 191, 218); + static const white = Color.fromARGB(255, 255, 255, 255); + static const clinicCardBgColor = Color.fromARGB(255, 0, 122, 141); + static const clinicCardKinColor = Color.fromARGB(255, 167, 240, 186); + static const backgroundGray = Color.fromARGB(255, 238, 238, 240); } diff --git a/lib/src/utils/routes.dart b/lib/src/utils/routes.dart index b580d7c5..e146dff5 100644 --- a/lib/src/utils/routes.dart +++ b/lib/src/utils/routes.dart @@ -45,8 +45,24 @@ class RouteNames { static const LAB_RESULTS = "lab-results"; static const MY_CLINIC_CARD = "clinic-card"; - static const CHAT_HCW = "chat-bot"; + static const MY_DEPENDANT_CLINIC_CARD = "dependant-clinic-card"; + static const HEALTH_RECORD = "health-record"; + static const HEALTH_RSHIP_RECORD = "health-rship-record"; + static const IMMUNIZATION_RECORD = "immunization-record"; + static const IMMUNIZATION_RSHIP_RECORD = "immunization-rship-record"; + static const PROCEDURE_RSHIP_RECORD = "procedure-rship-record"; + + static const MEDICATION_RECORD = "medication-record"; + static const MEDICATION_RSHIP_RECORD = "medication-rship-record"; + static const CHAT_BOT = "chat-bot"; static const SETTINGS = "settings"; + static const DEPENDANT_PROFILE = "dependant-profile"; + static const VITAL_HEALTH_RECORD= "vital-health-record"; + static const VITAL_HEALTH_RSHIP_RECORD= "vital-health-rship-record"; + static const ALLERGY_HEALTH_RECORD= "allergy-health-record"; + static const ALLERGY_HEALTH_RSHIP_RECORD= "allergy-health-rship-record"; + static const LAB_RESULTS_HEALTH_RECORD= "blood-result-health-record"; + static const LAB_RESULTS_HEALTH_RSHIP_RECORD= "lab-result-rship-record"; static const Facility_Directory = "facility-directory"; static const PROGRAM_MENU = "program-menu"; @@ -57,8 +73,36 @@ class RouteNames { static const DISPATCHED_DRUGS = "dispatched-drugs"; static const REMOVE_PROGRAM = "remove-program"; static const BLOG_POST = "blog-post"; + static const BLOOD_PRESSURE = "blood-pressure"; static const SELF_SCREENING = "self-screening"; + + static const PERIOD_PLANNER = "Period Planner"; + static const PERIOD_PLANNER_SCREEN = "Period Planner Screen"; + static const PERIOD_PLANNER_MENU = "Period Planner Menu"; + static const PERIOD_PLANNER_CALENDAR = "Period Planner Calendar"; + static const PERIOD_PLANNER_LOG_PERIODS = "period-planner-log-periods"; + static const PERIOD_PLANNER_EDIT_PERIODS = "period-planner-edit-periods"; + static const PERIOD_PLANNER_PERIOD_HISTORY = "period-planner-period-history"; + static const CHAT_HCW = "chat-hcw"; + static const CHAT_DETAIL = "chat-detail"; + static const CHAT_USER = "chat-user"; + static const INSIGHT = "insight"; + static const BP_INSIGHT = "bp-insight"; + static const PROVIDER_MAIN_SCREEN = "provider-main-screen"; + static const REQUEST_APP_RESCHEDULE = "request-app-reschedule"; + static const DAWA_DROP_MANAGER = "dawa-drop-manager"; + static const LOCATION_SELECTION = "location-selection"; + static const PROVIDER_DETAILS = "provider-details"; + static const BLOOD_SUGAR = "blood-sugar"; + static const BLOOD_SUGAR_INSIGHT = 'blood-sugar-insight'; + static const BLOOD_SUGAR_POSTS = 'blood-sugar-posts'; + static const BLOOD_PRESSURE_INSIGHT = 'blood-pressure-insight'; + static const BLOOD_PRESSURE_POSTS = 'blood-pressure-posts'; + static const BLOOD_PRESSURE_RECORDS = 'blood-pressure-records'; + static const BLOOD_SUGAR_RECORDS = 'blood-sugar-records'; + static const BLOOD_PRESSURE_INPUT= 'blood-pressure-input'; + static const BLOOD_SUGAR_INPUT= 'blood-sugar-input'; } class MenuItemNames { @@ -77,7 +121,7 @@ class MenuItemNames { static const FACILITY_VISITS = "Visits"; static const LAB_RESULTS = "Lab Results"; static const MY_CLINIC_CARD = "My Clinic Card"; - static const CHAT_HCW = "Ask Nuru"; + static const CHAT_BOT = "Ask Nuru"; static const FACILITY_DIRECTORY = "Facility Directory"; static const PROGRAM_MENU = "Program Menu"; static const DAWA_DROP = "Dawa Drop"; @@ -85,9 +129,19 @@ class MenuItemNames { static const REQUEST_DRUGS = "Request Drugs"; static const DISPATCHED_DRUGS = "Dispatched Drugs"; static const REMOVE_PROGRAM = "Remove Program"; + static const BLOOD_PRESSURE = "Blood Pressure Monitor"; static const SELF_SCREENING = "Self Screening"; static const BLOOD_SUGAR = "Blood Sugar"; + + static const PERIOD_PLANNER = "Period Planner"; + static const CHAT_HCW = "Chat with HCW"; + static const CHAT_DETAIL = "Chat Detail"; + static const INSIGHT = "Insight"; + static const PROVIDER_MAIN_SCREEN = "Provider Modules"; + static const REQUEST_APP_RESCHEDULE = "Request Appointment Reschedule"; + static const PROVIDER_DETAILS = "Provider Details"; + } class ProgramCodeNameIds { diff --git a/pubspec.lock b/pubspec.lock index 2ea9362c..39f99bbd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1285,6 +1285,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + lottie: + dependency: transitive + description: + name: lottie + sha256: "6a24ade5d3d918c306bb1c21a6b9a04aab0489d51a2582522eea820b4093b62b" + url: "https://pub.dev" + source: hosted + version: "3.1.2" markdown: dependency: transitive description: @@ -1309,6 +1317,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.8.0" + material_dialogs: + dependency: "direct main" + description: + name: material_dialogs + sha256: f072d8c11b3392a2aa6a2f279239e5063654113e1e851b62ef009b500d512f17 + url: "https://pub.dev" + source: hosted + version: "1.1.5" meta: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 842f7bdb..dbad9051 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.0.0+1 +version: 2.0.1+1 environment: sdk: '>=3.1.3 <4.0.0' @@ -80,6 +80,7 @@ dependencies: flutter_secure_storage: ^9.2.2 flutter_blue_plus: ^1.32.4 + material_dialogs: ^1.1.5 dev_dependencies: flutter_test: @@ -114,6 +115,7 @@ flutter: assets: - assets/images/ - assets/data/ + - assets/images/chat/ # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg