diff --git a/.gitignore b/.gitignore index 79c113f..3ada50c 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release +/android/ diff --git a/assets/images/svg/cancel_circle.svg b/assets/images/svg/cancel_circle.svg new file mode 100644 index 0000000..3ceda6f --- /dev/null +++ b/assets/images/svg/cancel_circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/close_bottom_sheet_icon.svg b/assets/images/svg/close_bottom_sheet_icon.svg new file mode 100644 index 0000000..599ce85 --- /dev/null +++ b/assets/images/svg/close_bottom_sheet_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/insurance_history_icon.svg b/assets/images/svg/insurance_history_icon.svg new file mode 100644 index 0000000..4ae2a1c --- /dev/null +++ b/assets/images/svg/insurance_history_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/update_insurance_card.svg b/assets/images/svg/update_insurance_card.svg new file mode 100644 index 0000000..9b21aab --- /dev/null +++ b/assets/images/svg/update_insurance_card.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/json/countriesList.json b/assets/json/countriesList.json new file mode 100644 index 0000000..25a65b3 --- /dev/null +++ b/assets/json/countriesList.json @@ -0,0 +1,1212 @@ +[ + { + "ID": "AFG", + "Name": "Afghan", + "NameN": "أفغاني" + }, + { + "ID": "CAF", + "Name": "African", + "NameN": "أفريقي" + }, + { + "ID": "ALB", + "Name": "Albanian", + "NameN": "ألباني" + }, + { + "ID": "DZA", + "Name": "Algerian", + "NameN": "جزائري" + }, + { + "ID": "USA", + "Name": "American", + "NameN": "أمريكي" + }, + { + "ID": "UMI", + "Name": "American (Minor Outlying Islands)", + "NameN": "أمريكي (الجزر الصغرى النائية)" + }, + { + "ID": "ASM", + "Name": "American Samoa", + "NameN": "ساموا الأمريكية" + }, + { + "ID": "AND", + "Name": "Andorrian", + "NameN": "أندوري" + }, + { + "ID": "AGO", + "Name": "Angolan", + "NameN": "أنغولي" + }, + { + "ID": "AIA", + "Name": "Anguillan", + "NameN": "أنغيلي" + }, + { + "ID": "ATA", + "Name": "Antarctica", + "NameN": "أنتاركتيكا" + }, + { + "ID": "ATG", + "Name": "Antiguans, Barbudans", + "NameN": "أنتيغوي، باربودي" + }, + { + "ID": "ARG", + "Name": "Argentine", + "NameN": "أرجنتيني" + }, + { + "ID": "ARM", + "Name": "Armenian", + "NameN": "أرمني" + }, + { + "ID": "ABW", + "Name": "Arubian", + "NameN": "أروبي" + }, + { + "ID": "AUS", + "Name": "Australian", + "NameN": "أسترالي" + }, + { + "ID": "AUT", + "Name": "Austrian", + "NameN": "نمساوي" + }, + { + "ID": "AZE", + "Name": "Azeri", + "NameN": "أذربيجاني" + }, + { + "ID": "BHS", + "Name": "Bahamian", + "NameN": "باهامي" + }, + { + "ID": "BHR", + "Name": "Bahraini", + "NameN": "بحريني" + }, + { + "ID": "BRB", + "Name": "Barbadian or Bajuns", + "NameN": "باربادوسي" + }, + { + "ID": "BLR", + "Name": "Belarusian", + "NameN": "بيلاروسي" + }, + { + "ID": "BEL", + "Name": "Belgian", + "NameN": "بلجيكي" + }, + { + "ID": "BLZ", + "Name": "Belizean", + "NameN": "بليزي" + }, + { + "ID": "BGD", + "Name": "Bengali", + "NameN": "بنغلاديشي" + }, + { + "ID": "BEN", + "Name": "Beninese", + "NameN": "بنيني" + }, + { + "ID": "BMU", + "Name": "Bermudian", + "NameN": "برمودي" + }, + { + "ID": "BTN", + "Name": "Bhutanese", + "NameN": "بوتاني" + }, + { + "ID": "BOL", + "Name": "Bolivian", + "NameN": "بوليفي" + }, + { + "ID": "BIH", + "Name": "Bosnian And Herzegovinian", + "NameN": "بوسني وهرسكي" + }, + { + "ID": "BWA", + "Name": "Botswanian", + "NameN": "بوتسواني" + }, + { + "ID": "BVT", + "Name": "Bouvet Island (Bouvetoya)", + "NameN": "جزيرة بوفيه" + }, + { + "ID": "BRA", + "Name": "Brazilian", + "NameN": "برازيلي" + }, + { + "ID": "GBR", + "Name": "British", + "NameN": "بريطاني" + }, + { + "ID": "IOT", + "Name": "British (Chagos Archipelago)", + "NameN": "بريطاني (أرخبيل شاغوس)" + }, + { + "ID": "VGB", + "Name": "British (Virgin Islands)", + "NameN": "بريطاني (الجزر العذراء)" + }, + { + "ID": "BRN", + "Name": "Bruneian", + "NameN": "بروني" + }, + { + "ID": "BGR", + "Name": "Bulgarian", + "NameN": "بلغاري" + }, + { + "ID": "MMR", + "Name": "Burmese or Myanmarese", + "NameN": "بورمي أو ميانماري" + }, + { + "ID": "BFA", + "Name": "Burnikabe", + "NameN": "بوركيني" + }, + { + "ID": "BDI", + "Name": "Burundi", + "NameN": "بوروندي" + }, + { + "ID": "KHM", + "Name": "Cambodian", + "NameN": "كمبودي" + }, + { + "ID": "CMR", + "Name": "Cameroonian", + "NameN": "كاميروني" + }, + { + "ID": "CAN", + "Name": "Canadian", + "NameN": "كندي" + }, + { + "ID": "CPV", + "Name": "Cape Verdian or Cape Verdean", + "NameN": "رأس أخضري" + }, + { + "ID": "CYM", + "Name": "Cayman Islander", + "NameN": "جزر كايماني" + }, + { + "ID": "TCD", + "Name": "Chadian", + "NameN": "تشادي" + }, + { + "ID": "CHL", + "Name": "Chilean", + "NameN": "تشيلي" + }, + { + "ID": "HKG", + "Name": "Chinese (Hong Kong)", + "NameN": "صيني (هونغ كونغ)" + }, + { + "ID": "MAC", + "Name": "Chinese (Macao)", + "NameN": "صيني (ماكاو)" + }, + { + "ID": "CHN", + "Name": "Chinese", + "NameN": "صيني" + }, + { + "ID": "CXR", + "Name": "Christmas Island", + "NameN": "جزيرة الكريسماس" + }, + { + "ID": "CCK", + "Name": "Cocos (Keeling) Islands", + "NameN": "جزر كوكوس (كيلنغ)" + }, + { + "ID": "COL", + "Name": "Colombian", + "NameN": "كولومبي" + }, + { + "ID": "COM", + "Name": "Comoran", + "NameN": "قمري" + }, + { + "ID": "COG", + "Name": "Congolese", + "NameN": "كونغولي" + }, + { + "ID": "COD", + "Name": "Congolese (Zaire)", + "NameN": "كونغولي (زائير)" + }, + { + "ID": "COK", + "Name": "Cook Islander", + "NameN": "جزر كوك" + }, + { + "ID": "CRI", + "Name": "Costa Rican", + "NameN": "كوستاريكي" + }, + { + "ID": "HRV", + "Name": "Croat", + "NameN": "كرواتي" + }, + { + "ID": "CUB", + "Name": "Cuban", + "NameN": "كوبي" + }, + { + "ID": "CYP", + "Name": "Cypriot, Greek", + "NameN": "قبرصي، يوناني" + }, + { + "ID": "CZE", + "Name": "Czech", + "NameN": "تشيكي" + }, + { + "ID": "DNK", + "Name": "Danish", + "NameN": "دنماركي" + }, + { + "ID": "DJI", + "Name": "Djibouti", + "NameN": "جيبوتي" + }, + { + "ID": "DOM", + "Name": "Dominican", + "NameN": "دومينيكاني" + }, + { + "ID": "DMA", + "Name": "Dominican (Commonwealth)", + "NameN": "دومينيكي (الكومنولث)" + }, + { + "ID": "ANT", + "Name": "Dutch (Antilles)", + "NameN": "هولندي (الأنتيل)" + }, + { + "ID": "NLD", + "Name": "Dutch (Netherlands)", + "NameN": "هولندي (هولندا)" + }, + { + "ID": "ECU", + "Name": "Ecuadorean", + "NameN": "إكوادوري" + }, + { + "ID": "EGY", + "Name": "Egyptians", + "NameN": "مصري" + }, + { + "ID": "ARE", + "Name": "Emirati", + "NameN": "إماراتي" + }, + { + "ID": "GNQ", + "Name": "Equatorial Guinean or Equatoguinean", + "NameN": "غيني استوائي" + }, + { + "ID": "ERI", + "Name": "Eritrean", + "NameN": "إريتري" + }, + { + "ID": "EST", + "Name": "Estonian", + "NameN": "إستوني" + }, + { + "ID": "ETH", + "Name": "Ethiopian", + "NameN": "إثيوبي" + }, + { + "ID": "FRO", + "Name": "Faeroe Islander", + "NameN": "جزر فارو" + }, + { + "ID": "FLK", + "Name": "Falkland Islander", + "NameN": "جزر فوكلاند" + }, + { + "ID": "FJI", + "Name": "Fijian", + "NameN": "فيجي" + }, + { + "ID": "PHL", + "Name": "Filipino", + "NameN": "فلبيني" + }, + { + "ID": "FIN", + "Name": "Finnish", + "NameN": "فنلندي" + }, + { + "ID": "FRA", + "Name": "French", + "NameN": "فرنسي" + }, + { + "ID": "GUF", + "Name": "French Guianian", + "NameN": "غويانا الفرنسية" + }, + { + "ID": "PYF", + "Name": "French Polynesian", + "NameN": "بولينيزيا الفرنسية" + }, + { + "ID": "ATF", + "Name": "French Southern Territories", + "NameN": "الأقاليم الجنوبية الفرنسية" + }, + { + "ID": "GAB", + "Name": "Gabonese", + "NameN": "غابوني" + }, + { + "ID": "GMB", + "Name": "Gambian", + "NameN": "غامبي" + }, + { + "ID": "GEO", + "Name": "Georgian", + "NameN": "جورجي" + }, + { + "ID": "SGS", + "Name": "Georgian (South)", + "NameN": "جورجي (جنوبي)" + }, + { + "ID": "DEU", + "Name": "German", + "NameN": "ألماني" + }, + { + "ID": "GHA", + "Name": "Ghanaian", + "NameN": "غاني" + }, + { + "ID": "GIB", + "Name": "Gibraltarian", + "NameN": "جبل طارقي" + }, + { + "ID": "GRC", + "Name": "Greek", + "NameN": "يوناني" + }, + { + "ID": "GRL", + "Name": "Greenlander", + "NameN": "غرينلاندي" + }, + { + "ID": "GRD", + "Name": "Grenadian or Grenadan", + "NameN": "غرينادي" + }, + { + "ID": "GLP", + "Name": "Guadeloupian", + "NameN": "غوادلوبي" + }, + { + "ID": "GUM", + "Name": "Guamanian", + "NameN": "غوامي" + }, + { + "ID": "GTM", + "Name": "Guatemalan", + "NameN": "غواتيمالي" + }, + { + "ID": "GNB", + "Name": "Guinea-Bissauan", + "NameN": "غيني بيساوي" + }, + { + "ID": "GIN", + "Name": "Guinean", + "NameN": "غيني" + }, + { + "ID": "GUY", + "Name": "Guyanese", + "NameN": "غيانيزي" + }, + { + "ID": "HTI", + "Name": "Haitian", + "NameN": "هايتيني" + }, + { + "ID": "HMD", + "Name": "Heard and McDonald Islands", + "NameN": "جزر هيرد وماكدونالد" + }, + { + "ID": "HND", + "Name": "Honduran", + "NameN": "هندوراسي" + }, + { + "ID": "HUN", + "Name": "Hungarian", + "NameN": "مجري" + }, + { + "ID": "ISL", + "Name": "Icelander", + "NameN": "آيسلندي" + }, + { + "ID": "KIR", + "Name": "I-Kiribati", + "NameN": "كيريباتي" + }, + { + "ID": "IND", + "Name": "Indian", + "NameN": "هندي" + }, + { + "ID": "IDN", + "Name": "Indonesian", + "NameN": "إندونيسي" + }, + { + "ID": "IRN", + "Name": "Iranian", + "NameN": "إيراني" + }, + { + "ID": "IRQ", + "Name": "Iraqi", + "NameN": "عراقي" + }, + { + "ID": "IRL", + "Name": "Irish", + "NameN": "إيرلندي" + }, + { + "ID": "ITA", + "Name": "Italian", + "NameN": "إيطالي" + }, + { + "ID": "CIV", + "Name": "Ivoirian", + "NameN": "إيفواري" + }, + { + "ID": "JAM", + "Name": "Jamaican", + "NameN": "جامايكي" + }, + { + "ID": "JPN", + "Name": "Japanese", + "NameN": "ياباني" + }, + { + "ID": "JOR", + "Name": "Jordanian", + "NameN": "أردني" + }, + { + "ID": "KAZ", + "Name": "Kazakhstani", + "NameN": "كازاخستاني" + }, + { + "ID": "KEN", + "Name": "Kenyan", + "NameN": "كيني" + }, + { + "ID": "KGZ", + "Name": "Kirghiz", + "NameN": "قيرغيزي" + }, + { + "ID": "KOR", + "Name": "Korean", + "NameN": "كوري" + }, + { + "ID": "PRK", + "Name": "Korean", + "NameN": "كوري" + }, + { + "ID": "KWT", + "Name": "Kuwaiti", + "NameN": "كويتي" + }, + { + "ID": "LAO", + "Name": "Laotian", + "NameN": "لاوسي" + }, + { + "ID": "LVA", + "Name": "Latvian", + "NameN": "لاتفي" + }, + { + "ID": "LBN", + "Name": "Lebanese", + "NameN": "لبناني" + }, + { + "ID": "LBR", + "Name": "Liberian", + "NameN": "ليبيري" + }, + { + "ID": "LBY", + "Name": "Libyan", + "NameN": "ليبي" + }, + { + "ID": "LIE", + "Name": "Liechtensteiner", + "NameN": "ليختنشتايني" + }, + { + "ID": "LTU", + "Name": "Lithuanian", + "NameN": "ليتواني" + }, + { + "ID": "LUX", + "Name": "Luxembourger", + "NameN": "لوكسمبورغي" + }, + { + "ID": "MKD", + "Name": "Macedonia, the former Yugoslav Republic of", + "NameN": "مقدوني، جمهورية يوغوسلافيا السابقة" + }, + { + "ID": "MYT", + "Name": "Mahorais", + "NameN": "مايوتي" + }, + { + "ID": "MDG", + "Name": "Malagasy", + "NameN": "مدغشقري" + }, + { + "ID": "MWI", + "Name": "Malawian", + "NameN": "مالاوي" + }, + { + "ID": "MYS", + "Name": "Malaysian", + "NameN": "ماليزي" + }, + { + "ID": "MDV", + "Name": "Maldivan", + "NameN": "مالديفي" + }, + { + "ID": "MLI", + "Name": "Malian", + "NameN": "مالي" + }, + { + "ID": "MLT", + "Name": "Maltese", + "NameN": "مالطي" + }, + { + "ID": "MHL", + "Name": "Marshallese", + "NameN": "مارشالي" + }, + { + "ID": "MTQ", + "Name": "Martiniquais", + "NameN": "مارتينيكي" + }, + { + "ID": "MRT", + "Name": "Mauritanian", + "NameN": "موريتاني" + }, + { + "ID": "MUS", + "Name": "Mauritian", + "NameN": "موريشيوسي" + }, + { + "ID": "MEX", + "Name": "Mexican", + "NameN": "مكسيكي" + }, + { + "ID": "FSM", + "Name": "Micronesian", + "NameN": "ميكرونيزي" + }, + { + "ID": "MDA", + "Name": "Moldovian", + "NameN": "مولدوفي" + }, + { + "ID": "MCO", + "Name": "Monegasque or Monacan", + "NameN": "موناكي" + }, + { + "ID": "MNG", + "Name": "Mongolian", + "NameN": "منغولي" + }, + { + "ID": "MNE", + "Name": "Montenegrin", + "NameN": "جبل أسودي" + }, + { + "ID": "MSR", + "Name": "Montserratian", + "NameN": "مونتسيراتي" + }, + { + "ID": "MAR", + "Name": "Moroccan", + "NameN": "مغربي" + }, + { + "ID": "LSO", + "Name": "Mosotho", + "NameN": "ليسوتي" + }, + { + "ID": "MOZ", + "Name": "Mozambican", + "NameN": "موزمبيقي" + }, + { + "ID": "NAM", + "Name": "Namibian", + "NameN": "ناميبي" + }, + { + "ID": "NRU", + "Name": "Nauruan", + "NameN": "ناوروي" + }, + { + "ID": "NPL", + "Name": "Nepalese", + "NameN": "نيبالي" + }, + { + "ID": "NCL", + "Name": "New Caledonian", + "NameN": "كاليدوني جديد" + }, + { + "ID": "NZL", + "Name": "New Zealand", + "NameN": "نيوزيلندي" + }, + { + "ID": "VUT", + "Name": "Ni- Vanuatu", + "NameN": "فانواتي" + }, + { + "ID": "NIC", + "Name": "Nicaraguan", + "NameN": "نيكاراغوي" + }, + { + "ID": "NGA", + "Name": "Nigerian", + "NameN": "نيجيري" + }, + { + "ID": "NER", + "Name": "Nigerien", + "NameN": "نيجري" + }, + { + "ID": "NIU", + "Name": "Niuean", + "NameN": "نيوي" + }, + { + "ID": "VAT", + "Name": "None (Vatican City)", + "NameN": "لا شيء (مدينة الفاتيكان)" + }, + { + "ID": "NFK", + "Name": "Norfolk Islander", + "NameN": "جزيرة نورفولك" + }, + { + "ID": "MNP", + "Name": "Northern Mariana Islander", + "NameN": "جزر ماريانا الشمالية" + }, + { + "ID": "NOR", + "Name": "Norwegean", + "NameN": "نرويجي" + }, + { + "ID": "OMN", + "Name": "Omani", + "NameN": "عماني" + }, + { + "ID": "OTH", + "Name": "OTHERS", + "NameN": "آخرون" + }, + { + "ID": "PAK", + "Name": "Pakistani", + "NameN": "باكستاني" + }, + { + "ID": "PLW", + "Name": "Palauan", + "NameN": "بالاوي" + }, + { + "ID": "PSE", + "Name": "Palestinian", + "NameN": "فلسطيني" + }, + { + "ID": "PAN", + "Name": "Panamanian", + "NameN": "بنمي" + }, + { + "ID": "PNG", + "Name": "Papua New Guinean", + "NameN": "بابوا غيني جديد" + }, + { + "ID": "PRY", + "Name": "Paraguayan", + "NameN": "باراغوايي" + }, + { + "ID": "PER", + "Name": "Peruvian", + "NameN": "بيروفي" + }, + { + "ID": "PCN", + "Name": "Pitcairn Islander", + "NameN": "جزر بيتكايرن" + }, + { + "ID": "POL", + "Name": "Polish", + "NameN": "بولندي" + }, + { + "ID": "PRT", + "Name": "Portuguese", + "NameN": "برتغالي" + }, + { + "ID": "PRI", + "Name": "Puerto Rican", + "NameN": "بورتوريكي" + }, + { + "ID": "QAT", + "Name": "Qatari", + "NameN": "قطري" + }, + { + "ID": "RKS", + "Name": "Republic of Kosovo", + "NameN": "جمهورية كوسوفو" + }, + { + "ID": "REU", + "Name": "Reunionese", + "NameN": "ريونيوني" + }, + { + "ID": "ROU", + "Name": "Romanian", + "NameN": "روماني" + }, + { + "ID": "RUS", + "Name": "Russian", + "NameN": "روسي" + }, + { + "ID": "RWA", + "Name": "Rwandan", + "NameN": "رواندي" + }, + { + "ID": "ESH", + "Name": "Sahrawi", + "NameN": "صحراوي" + }, + { + "ID": "SLV", + "Name": "Salvadoran", + "NameN": "سلفادوري" + }, + { + "ID": "SMR", + "Name": "Sammarinese", + "NameN": "سان مارينو" + }, + { + "ID": "WSM", + "Name": "Samoan", + "NameN": "ساموي" + }, + { + "ID": "STP", + "Name": "Sao Tomean", + "NameN": "ساو تومي" + }, + { + "ID": "SAU", + "Name": "Saudi", + "NameN": "سعودي" + }, + { + "ID": "SCT", + "Name": "Scottish", + "NameN": "اسكتلندي" + }, + { + "ID": "SEN", + "Name": "Senegalese", + "NameN": "سنغالي" + }, + { + "ID": "SRB", + "Name": "Serbian", + "NameN": "صربي" + }, + { + "ID": "SYC", + "Name": "Seychellois", + "NameN": "سيشيلي" + }, + { + "ID": "SLE", + "Name": "Sierra Leonean", + "NameN": "سيراليوني" + }, + { + "ID": "SGP", + "Name": "Singaporian", + "NameN": "سنغافوري" + }, + { + "ID": "SVK", + "Name": "Slovak", + "NameN": "سلوفاكي" + }, + { + "ID": "SVN", + "Name": "Slovene", + "NameN": "سلوفيني" + }, + { + "ID": "SLB", + "Name": "Solomon Islander", + "NameN": "جزر سليمان" + }, + { + "ID": "SOM", + "Name": "Somalian", + "NameN": "صومالي" + }, + { + "ID": "ZAF", + "Name": "South African", + "NameN": "جنوب أفريقي" + }, + { + "ID": "ESP", + "Name": "Spanish", + "NameN": "إسباني" + }, + { + "ID": "LKA", + "Name": "Sri Lankan", + "NameN": "سريلانكي" + }, + { + "ID": "SHN", + "Name": "St. Helena", + "NameN": "سانت هيلينا" + }, + { + "ID": "KNA", + "Name": "St. Kitts and Nevis", + "NameN": "سانت كيتس ونيفيس" + }, + { + "ID": "LCA", + "Name": "St. Lucia", + "NameN": "سانت لوسيا" + }, + { + "ID": "SPM", + "Name": "St. Pierre and Miquelon", + "NameN": "سانت بيير وميكيلون" + }, + { + "ID": "VCT", + "Name": "St. Vincent and the Grenadines", + "NameN": "سانت فنسنت والغرينادين" + }, + { + "ID": "SDN", + "Name": "Sudanese", + "NameN": "سوداني" + }, + { + "ID": "SUR", + "Name": "Surinamer", + "NameN": "سورينامي" + }, + { + "ID": "SJM", + "Name": "Svalbard and Jan Mayen islander", + "NameN": "سفالبارد وجان ماين" + }, + { + "ID": "SWZ", + "Name": "Swazi", + "NameN": "سوازي" + }, + { + "ID": "SWE", + "Name": "Swedish", + "NameN": "سويدي" + }, + { + "ID": "CHE", + "Name": "Swiss", + "NameN": "سويسري" + }, + { + "ID": "SYR", + "Name": "Syrian", + "NameN": "سوري" + }, + { + "ID": "TWN", + "Name": "Taiwan, Province of China", + "NameN": "تايوان، مقاطعة الصين" + }, + { + "ID": "TJK", + "Name": "Tajik", + "NameN": "طاجيكي" + }, + { + "ID": "TZA", + "Name": "Tanzania, United Republic o", + "NameN": "تنزاني، الجمهورية المتحدة" + }, + { + "ID": "THA", + "Name": "Thai", + "NameN": "تايلندي" + }, + { + "ID": "TLS", + "Name": "Timorese", + "NameN": "تيموري" + }, + { + "ID": "TGO", + "Name": "Togolese", + "NameN": "توغولي" + }, + { + "ID": "TKL", + "Name": "Tokelauan", + "NameN": "توكيلاوي" + }, + { + "ID": "TON", + "Name": "Tongan", + "NameN": "تونغي" + }, + { + "ID": "TTO", + "Name": "Trinidadian", + "NameN": "ترينيدادي" + }, + { + "ID": "TUN", + "Name": "Tunisian", + "NameN": "تونسي" + }, + { + "ID": "TCA", + "Name": "Turk", + "NameN": "تركي" + }, + { + "ID": "TUR", + "Name": "Turkish", + "NameN": "تركي" + }, + { + "ID": "TKM", + "Name": "Turkmen", + "NameN": "تركماني" + }, + { + "ID": "TUV", + "Name": "Tuvaluan", + "NameN": "توفالوي" + }, + { + "ID": "UGA", + "Name": "Ugandan", + "NameN": "أوغندي" + }, + { + "ID": "UKR", + "Name": "Ukrainian", + "NameN": "أوكراني" + }, + { + "ID": "URY", + "Name": "Uruguayan", + "NameN": "أوروغوايي" + }, + { + "ID": "UZB", + "Name": "Uzbek", + "NameN": "أوزبكي" + }, + { + "ID": "VEN", + "Name": "Venenzuelan", + "NameN": "فنزويلي" + }, + { + "ID": "VNM", + "Name": "Vietnamese", + "NameN": "فيتنامي" + }, + { + "ID": "VIR", + "Name": "Virgin Islander", + "NameN": "جزر عذراء" + }, + { + "ID": "WLF", + "Name": "Wallis and Futuna Islander", + "NameN": "واليس وفوتونا" + }, + { + "ID": "YEM", + "Name": "Yemeni", + "NameN": "يمني" + }, + { + "ID": "ZMB", + "Name": "Zambian", + "NameN": "زامبي" + }, + { + "ID": "ZWE", + "Name": "Zimbabwean", + "NameN": "زيمبابوي" + } +] \ No newline at end of file diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 91d5d4f..ede5e64 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -782,8 +782,29 @@ "resultsPending": "النتائج معلقة", "resultsAvailable": "النتائج متاحة", "viewReport": "عرض التقرير", - "prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم.", - "checkAvailability": "التحقق من التوفر", - "readInstructions": "قراءة التعليمات" + "readInstructions": "قراءة التعليمات", + "searchLabReport" : "ابحث عن تقرير المختبر", + "prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم.", + "receiveOtpToast": "أين تود تلقي رمز التحقق OTP؟", + "enterPhoneNumber": "أدخل رقم الهاتف", + "enterEmailDesc": "أدخل عنوان بريدك الإلكتروني لإكمال عملية إنشاء ملف طبي", + "enterPhoneDesc": "أدخل رقم هاتفك لتلقي رمز التحقق ", + "pleaseChooseOption": "الرجاء اختيار من الخيارات أدناه لتلقي رمز التحقق OTP", + "prepareToElevate": "هل أنت مستعد لتحسين صحتك ورفاهتك؟", + "iAcceptTermsConditions": "أوافق على الشروط والأحكام", + "alreadyHaveAccount": "هل لديك حساب بالفعل؟", + "loginNow": "تسجيل الدخول الآن", + "notice": "إشعار", + "oR": "أو", + "sendOTPWHATSAPP": "أرسل لي OTP عبر واتساب", + "sendOTPSMS": "أرسل لي OTP عبر الرسائل القصيرة", + "fullName": "الاسم الكامل", + "married": "متزوج", + "uae": "الإمارات العربية المتحدة", + "malE": "ذكر", + "loginBy": "تسجيل الدخول بواسطة", + "loginByOTP": "تسجيل الدخول بواسطة OTP", + "guest": "زائر", + "switchAccount": "تبديل الحساب" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index f91d98b..8ccd2c4 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -772,13 +772,35 @@ "aboutApp": "About the app", "aboutPoints": "Online Appointment Booking & rescheduling, Insurance approval status, Find A doctor, Ask your doctor, Medical prescriptions, Lab results, Hospitals contact numbers, Doctor profiles, Hospitals locations, Pharmacies Locations, Hospital's Virtual Tour, Official Social Media, Vaccines Schedule, Health Calculators, Other Services", "termsConditions": "These Online Services Terms of Use (Service Terms) govern certain online services provided by Dr Sulaiman Al Habib Medical Services Group Company (HMG, we, us, our)...", + "receiveOtpToast": "Where would you like to receive OTP?", + "enterPhoneNumber": "Enter Phone Number", + "enterEmailDesc": "Enter your email address to complete the process of creating a medical file", + "enterPhoneDesc": "Enter your phone number to receive OTP verification code", + "pleaseChooseOption": "Please select from the below options to receive OTP", "dontHaveAccount": "Don't have an account?", "loginOrRegister": "Login or Register", "myFiles": "My Files", "resultsPending": "Results Pending", "resultsAvailable": "Results Available", "viewReport": "View Report", - "prescriptionDeliveryError": "This clinic doesn't support refill", "checkAvailability": "Check Availability", - "readInstructions": "Read Instructions" + "readInstructions": "Read Instructions", + "searchLabReport" : "Search Lab Report", + "prescriptionDeliveryError": "This clinic doesn't support refill", + "prepareToElevate": "Prepared to elevate your health and well-being?", + "iAcceptTermsConditions": "I Accept the Terms and Conditions", + "alreadyHaveAccount": "Already have an account?", + "loginNow": "Login Now", + "notice": "Notice", + "oR": "OR", + "sendOTPWHATSAPP": "Send me OTP on Whatsapp", + "sendOTPSMS": "Send me OTP on SMS", + "fullName": "Full Name", + "married": "Married", + "uae" : "United Arab Emirates", + "malE": "Male", + "loginBy": "Login By", + "loginByOTP": "Login By OTP", + "guest": "Guest", + "switchAccount": "Switch Account" } \ No newline at end of file diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index b071112..ac3cfe6 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -68,17 +68,21 @@ class AppAssets { static const String doctor_calendar_icon = '$svgBasePath/doctor_calendar_icon.svg'; static const String prescription_remarks_icon = '$svgBasePath/prescription_remarks_icon.svg'; static const String prescription_reminder_icon = '$svgBasePath/prescription_reminder_icon.svg'; + static const String insurance_history_icon = '$svgBasePath/insurance_history_icon.svg'; + static const String cancel_circle_icon = '$svgBasePath/cancel_circle.svg'; + static const String update_insurance_card_icon = '$svgBasePath/update_insurance_card.svg'; + static const String close_bottom_sheet_icon = '$svgBasePath/close_bottom_sheet_icon.svg'; static const String insurance = '$svgBasePath/insurance.svg'; static const String requests = '$svgBasePath/requests.svg'; static const String more = '$svgBasePath/more.svg'; //bottom navigation// - static const String homeBottom = '$svgBasePath/home_bottom.svg'; static const String bookAppoBottom = '$svgBasePath/book_appo_bottom.svg'; static const String myFilesBottom = '$svgBasePath/my_files_bottom.svg'; static const String toDoBottom = '$svgBasePath/todo_bottom.svg'; static const String servicesBottom = '$svgBasePath/services_bottom.svg'; + static const String closeBottomNav = '$svgBasePath/close_bottom_nav.svg'; // PNGS // static const String hmg_logo = '$pngBasePath/hmg_logo.png'; diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 7c42042..eb9315c 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -22,13 +22,8 @@ class AppState { set setUserLong(v) => userLong = v; - final PostParamsModel _postParamsInitConfig = PostParamsModel( - channel: 3, - versionID: ApiConsts.VERSION_ID, - ipAddress: '10.20.10.20', - generalId: 'Cs2020@2016\$2958', - deviceTypeID: "2", - sessionID: 'TMRhVmkGhOsvamErw'); + final PostParamsModel _postParamsInitConfig = + PostParamsModel(channel: 3, versionID: ApiConsts.VERSION_ID, ipAddress: '10.20.10.20', generalId: 'Cs2020@2016\$2958', deviceTypeID: "2", sessionID: 'TMRhVmkGhOsvamErw'); void setPostParamsInitConfig() { isAuthenticated = false; @@ -50,7 +45,9 @@ class AppState { bool isArabic() => EasyLocalization.of(navigationService.navigatorKey.currentContext!)?.locale.languageCode == "ar"; - int getLanguageID(context) => EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2; + int getLanguageID() => EasyLocalization.of(navigationService.navigatorKey.currentContext!)?.locale.languageCode == "ar" ? 1 : 2; + + String? getLanguageCode() => EasyLocalization.of(navigationService.navigatorKey.currentContext!)?.locale.languageCode; AuthenticatedUser? _authenticatedUser; diff --git a/lib/core/common_models/nationality_country_model.dart b/lib/core/common_models/nationality_country_model.dart new file mode 100644 index 0000000..fb3961b --- /dev/null +++ b/lib/core/common_models/nationality_country_model.dart @@ -0,0 +1,29 @@ +import 'dart:convert'; + +class NationalityCountries { + String? id; + String? name; + String? nameN; + + NationalityCountries({ + this.id, + this.name, + this.nameN, + }); + + factory NationalityCountries.fromRawJson(String str) => NationalityCountries.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory NationalityCountries.fromJson(Map json) => NationalityCountries( + id: json["ID"], + name: json["Name"], + nameN: json["NameN"], + ); + + Map toJson() => { + "ID": id, + "Name": name, + "NameN": nameN, + }; +} diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 65fd2c6..aa32fa6 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -6,6 +6,8 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_repo. import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/common/common_repo.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; @@ -65,6 +67,7 @@ class AppDependencies { getIt.registerLazySingleton(() => LabRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => RadiologyRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => PrescriptionsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => InsuranceRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -90,6 +93,13 @@ class AppDependencies { ), ); + getIt.registerLazySingleton( + () => InsuranceViewModel( + insuranceRepo: getIt(), + errorHandlerService: getIt(), + ), + ); + getIt.registerLazySingleton( () => AuthenticationViewModel( authenticationRepo: getIt(), diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 2fd5867..34aa911 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -22,12 +22,6 @@ enum ViewStateEnum { errorLocal, } -enum LoginTypeEnum { - fromLogin, - silentLogin, - silentWithOTP, -} - enum OTPTypeEnum { sms, whatsapp } enum CountryEnum { saudiArabia, unitedArabEmirates } @@ -40,6 +34,51 @@ enum MaritalStatusTypeEnum { single, married, divorced, widowed } enum ChipTypeEnum { success, error, alert, info, warning } +enum LoginTypeEnum { sms, whatsapp, face, fingerprint } + +extension LoginTypeExtension on LoginTypeEnum { + int get toInt { + switch (this) { + case LoginTypeEnum.sms: + return 1; + case LoginTypeEnum.whatsapp: + return 4; + case LoginTypeEnum.face: + return 3; + case LoginTypeEnum.fingerprint: + return 2; + } + } + + String get displayName { + switch (this) { + case LoginTypeEnum.sms: + return 'SMS'; + case LoginTypeEnum.whatsapp: + return 'WhatsApp'; + case LoginTypeEnum.face: + return 'Face Recognition'; + case LoginTypeEnum.fingerprint: + return 'Fingerprint'; + } + } + + static LoginTypeEnum? fromValue(int value) { + switch (value) { + case 1: + return LoginTypeEnum.sms; + case 2: + return LoginTypeEnum.fingerprint; + case 3: + return LoginTypeEnum.face; + case 4: + return LoginTypeEnum.whatsapp; + default: + return null; + } + } +} + extension OTPTypeEnumExtension on OTPTypeEnum { /// Convert enum to int int toInt() { diff --git a/lib/core/utils/size_utils.dart b/lib/core/utils/size_utils.dart index 0b37082..6ad1835 100644 --- a/lib/core/utils/size_utils.dart +++ b/lib/core/utils/size_utils.dart @@ -11,6 +11,10 @@ extension ResponsiveExtension on num { double get h => ((this * _width) / FIGMA_DESIGN_WIDTH); double get fSize => ((this * _width) / FIGMA_DESIGN_WIDTH); + static double get screenHeight => SizeUtils.height; + + /// Full screen width + static double get screenWidth => SizeUtils.width; } extension FormatExtension on double { diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index bdca5ff..ed4770d 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -13,6 +13,7 @@ import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/dialogs/confirm_dialog.dart'; @@ -59,14 +60,13 @@ class Utils { // )); return !isAddHours ? DateFormat('hh:mm a', appState.isArabic() ? "ar_SA" : "en_US") - .format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.toLocal()) + .format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.toLocal()) : DateFormat('hh:mm a', appState.isArabic() ? "ar_SA" : "en_US") - .format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.add( - Duration( - hours: isAddHours ? 3 : 0, - ), - )); - ; + .format(DateTime.tryParse(startTime.contains("T") ? startTime : convertStringToDateTime(startTime))!.add( + Duration( + hours: isAddHours ? 3 : 0, + ), + )); } static String convertStringToDateTime(String dateTimeString) { @@ -209,12 +209,12 @@ class Utils { builder: (BuildContext context) => LoadingDialog(), ) .then((value) { - _isLoadingVisible = false; - }) + _isLoadingVisible = false; + }) .catchError((e) {}) .onError( (error, stackTrace) {}, - ); + ); } static void hideLoading() { @@ -236,11 +236,12 @@ class Utils { showDialog( barrierDismissible: false, context: context, - builder: (cxt) => ConfirmDialog( - title: title!, - message: message!, - onTap: onTap, - ), + builder: (cxt) => + ConfirmDialog( + title: title!, + message: message!, + onTap: onTap, + ), ); } @@ -371,7 +372,9 @@ class Utils { static String formatHijriDateToDisplay(String hijriDateString) { try { // Assuming hijriDateString is in the format yyyy-MM-dd - final datePart = hijriDateString.split("T").first; + final datePart = hijriDateString + .split("T") + .first; final parts = datePart.split('-'); if (parts.length != 3) return ""; @@ -429,8 +432,14 @@ class Utils { void Function(LottieComposition)? onLoaded, }) { return Lottie.asset(assetPath, - height: height ?? MediaQuery.of(context).size.height * 0.26, - width: width ?? MediaQuery.of(context).size.width, + height: height ?? MediaQuery + .of(context) + .size + .height * 0.26, + width: width ?? MediaQuery + .of(context) + .size + .width, fit: fit, alignment: alignment, repeat: repeat, @@ -487,9 +496,13 @@ class Utils { ], ); } + static Future isGoogleServicesAvailable() async { GooglePlayServicesAvailability availability = await GoogleApiAvailability.instance.checkGooglePlayServicesAvailability(); - String status = availability.toString().split('.').last; + String status = availability + .toString() + .split('.') + .last; if (status == "success") { return true; } @@ -510,3 +523,26 @@ class Utils { return crypto.md5.convert(utf8.encode(input)).toString(); } } + +class ValidationUtils { + static DialogService dialogService = getIt.get(); + + + static bool isValidatePhoneAndId({ + String? nationalId, + String? phoneNumber + }) { + if (nationalId == null || nationalId.isEmpty) { + dialogService.showErrorDialog(message: "Please enter a valid national ID or file number", onOkPressed: () {}); + return false; + } + + if (phoneNumber == null || phoneNumber.isEmpty) { + dialogService.showErrorDialog(message: "Please enter a valid phone number", onOkPressed: () {}); + return false; + } + return true; + } +} + + diff --git a/lib/extensions/context_extensions.dart b/lib/extensions/context_extensions.dart index 5783ce3..4e9da0d 100644 --- a/lib/extensions/context_extensions.dart +++ b/lib/extensions/context_extensions.dart @@ -2,15 +2,32 @@ import 'package:flutter/material.dart'; extension ContextUtils on BuildContext { double get screenHeight => MediaQuery.of(this).size.height; + double get screenWidth => MediaQuery.of(this).size.width; + ThemeData get theme => Theme.of(this); + TextTheme get textTheme => theme.textTheme; - // TextStyle get headline1 => textTheme.headline1!; - // TextStyle get headline2 => textTheme.headline2!; - // TextStyle get headline3 => textTheme.headline3!; - // TextStyle get headline4 => textTheme.headline4!; - // TextStyle get headline5 => textTheme.headline5!; - // TextStyle get headline6 => textTheme.headline6!; - // TextStyle get bodyText1 => textTheme.bodyText1!; - // TextStyle get bodyText2 => textTheme.bodyText2!; +// TextStyle get headline1 => textTheme.headline1!; +// TextStyle get headline2 => textTheme.headline2!; +// TextStyle get headline3 => textTheme.headline3!; +// TextStyle get headline4 => textTheme.headline4!; +// TextStyle get headline5 => textTheme.headline5!; +// TextStyle get headline6 => textTheme.headline6!; +// TextStyle get bodyText1 => textTheme.bodyText1!; +// TextStyle get bodyText2 => textTheme.bodyText2!; +} + +extension ShowBottomSheet on BuildContext { + Future showBottomSheet({isScrollControlled = true, isDismissible = false, required Widget child, Color? backgroundColor, enableDra = false, useSafeArea = false}) { + return showModalBottomSheet( + context: this, + isScrollControlled: isScrollControlled, + isDismissible: isDismissible, + enableDrag: enableDra, + useSafeArea: useSafeArea, + backgroundColor: backgroundColor ?? Colors.transparent, + builder: (_) => child, + ); + } } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index a224526..d986de1 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -1,3 +1,5 @@ +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/cupertino.dart'; @@ -11,16 +13,13 @@ extension CapExtension on String { String get allInCaps => this.toUpperCase(); - String get capitalizeFirstofEach => - this.trim().length > 0 ? this.trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : ""; + String get capitalizeFirstofEach => this.trim().length > 0 ? this.trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : ""; } extension EmailValidator on String { Widget get toWidget => Text(this); - Widget toText8( - {Color? color, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => - Text( + Widget toText8({Color? color, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text( this, maxLines: maxlines, overflow: textOverflow, @@ -33,14 +32,7 @@ extension EmailValidator on String { ), ); - Widget toText10( - {Color? color, - bool isBold = false, - bool isUnderLine = false, - int? maxlines, - FontStyle? fontStyle, - TextOverflow? textOverflow}) => - Text( + Widget toText10({Color? color, bool isBold = false, bool isUnderLine = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text( this, maxLines: maxlines, overflow: textOverflow, @@ -54,15 +46,7 @@ extension EmailValidator on String { decorationColor: color ?? AppColors.blackColor), ); - Widget toText11( - {Color? color, - FontWeight? weight, - bool isUnderLine = false, - bool isCenter = false, - bool isBold = false, - int maxLine = 0, - double letterSpacing = 0.64}) => - Text( + Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isCenter = false, bool isBold = false, int maxLine = 0, double letterSpacing = 0.64}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, @@ -76,20 +60,11 @@ extension EmailValidator on String { ), ); - Widget toText12( - {Color? color, - bool isUnderLine = false, - bool isBold = false, - FontWeight? fontWeight, - bool isCenter = false, - double? height, - int maxLine = 0}) => - Text( + Widget toText12({Color? color, bool isUnderLine = false, bool isBold = false, FontWeight? fontWeight, bool isCenter = false, double? height, int maxLine = 0}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, style: TextStyle( - fontSize: 12.fSize, fontWeight: fontWeight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, @@ -100,9 +75,7 @@ extension EmailValidator on String { ), ); - Widget toText12Auto( - {Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => - AutoSizeText( + Widget toText12Auto({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => AutoSizeText( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, @@ -163,14 +136,7 @@ extension EmailValidator on String { decoration: isUnderLine ? TextDecoration.underline : null), ); - Widget toText14( - {Color? color, - bool isUnderLine = false, - bool isBold = false, - bool isCenter = false, - FontWeight? weight, - int? maxlines}) => - Text( + Widget toText14({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: maxlines, @@ -182,14 +148,7 @@ extension EmailValidator on String { decoration: isUnderLine ? TextDecoration.underline : null), ); - Widget toText15( - {Color? color, - bool isUnderLine = false, - bool isBold = false, - bool isCenter = false, - FontWeight? weight, - int? maxlines}) => - Text( + Widget toText15({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines}) => Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: maxlines, @@ -227,93 +186,68 @@ extension EmailValidator on String { Widget toText17({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle( - color: color ?? AppColors.blackColor, - fontSize: 17.fSize, - letterSpacing: -0.4, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText18({Color? color, bool isBold = false, bool isCenter = false, int? maxlines}) => Text( maxLines: maxlines, textAlign: isCenter ? TextAlign.center : null, this, - style: TextStyle( - fontSize: 18.fSize, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal, - color: color ?? AppColors.blackColor, - letterSpacing: -0.4), + style: TextStyle(fontSize: 18.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); Widget toText19({Color? color, bool isBold = false}) => Text( this, - style: TextStyle( - fontSize: 19.fSize, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal, - color: color ?? AppColors.blackColor, - letterSpacing: -0.4), + style: TextStyle(fontSize: 19.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); Widget toText20({Color? color, bool isBold = false}) => Text( this, - style: TextStyle( - fontSize: 20.fSize, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal, - color: color ?? AppColors.blackColor, - letterSpacing: -0.4), + style: TextStyle(fontSize: 20.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), ); Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text( this, maxLines: maxlines, - style: TextStyle( - color: color ?? AppColors.blackColor, - fontSize: 21.fSize, - letterSpacing: -0.4, - fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), + style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 21.fSize, letterSpacing: -0.4, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), ); Widget toText22({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle( - height: 1, - color: color ?? AppColors.blackColor, - fontSize: 22.fSize, - letterSpacing: -0.4, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: 1, color: color ?? AppColors.blackColor, fontSize: 22.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText24({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle( - height: 23 / 24, - color: color ?? AppColors.blackColor, - fontSize: 24.fSize, - letterSpacing: -0.4, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); + Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false}) => Text( + this, + textAlign: isCenter ? TextAlign.center : null, + style: TextStyle(height: height ?? 23 / 26, color: color ?? AppColors.blackColor, fontSize: 26.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + ); + + + Widget toText28({Color? color, bool isBold = false, double? height, bool isCenter = false}) => Text( + this, + textAlign: isCenter ? TextAlign.center : null, + style: TextStyle(height: height ?? 23 / 28, color: color ?? AppColors.blackColor, fontSize: 28.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + ); + + + Widget toText32({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle( - height: 32 / 32, - color: color ?? AppColors.blackColor, - fontSize: 32.fSize, - letterSpacing: -0.4, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText44({Color? color, bool isBold = false}) => Text( this, - style: TextStyle( - height: 32 / 32, - color: color ?? AppColors.blackColor, - fontSize: 44.fSize, - letterSpacing: -0.4, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) { @@ -349,9 +283,7 @@ extension EmailValidator on String { } bool isValidEmail() { - return RegExp( - r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$') - .hasMatch(this); + return RegExp(r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$').hasMatch(this); } String toFormattedDate() { @@ -453,3 +385,155 @@ class FontUtils { return isArabic ? 'Cairo' : 'Poppins'; } } + +extension CountryExtension on CountryEnum { + String get displayName { + switch (this) { + case CountryEnum.saudiArabia: + return "Kingdom Of Saudi Arabia"; + case CountryEnum.unitedArabEmirates: + return "United Arab Emirates"; + } + } + + String get nameArabic { + switch (this) { + case CountryEnum.saudiArabia: + return "المملكة العربية السعودية"; + case CountryEnum.unitedArabEmirates: + return "الإمارات العربية المتحدة"; + } + } + + String get iconPath { + switch (this) { + case CountryEnum.saudiArabia: + return AppAssets.ksa; + case CountryEnum.unitedArabEmirates: + return AppAssets.uae; + } + } + + String get countryCode { + switch (this) { + case CountryEnum.saudiArabia: + return "966"; + case CountryEnum.unitedArabEmirates: + return "971"; + } + } + + static CountryEnum fromDisplayName(String name) { + switch (name) { + case "Kingdom Of Saudi Arabia": + case "المملكة العربية السعودية": + return CountryEnum.saudiArabia; + case "United Arab Emirates": + case "الإمارات العربية المتحدة": + return CountryEnum.unitedArabEmirates; + default: + throw Exception("Invalid country name"); + } + } +} + +extension GenderTypeExtension on GenderTypeEnum { + String get value => this == GenderTypeEnum.male ? "M" : "F"; + + String get type => this == GenderTypeEnum.male ? "Male" : "Female"; + + String get typeAr => this == GenderTypeEnum.male ? "ذكر" : "أنثى"; + + static GenderTypeEnum? fromValue(String? value) { + switch (value) { + case "M": + return GenderTypeEnum.male; + case "F": + return GenderTypeEnum.female; + default: + return null; + } + } + + static GenderTypeEnum? fromType(String? type) { + switch (type) { + case "Male": + return GenderTypeEnum.male; + case "Female": + return GenderTypeEnum.female; + default: + return null; + } + } +} + +extension MaritalStatusTypeExtension on MaritalStatusTypeEnum { + String get value { + switch (this) { + case MaritalStatusTypeEnum.single: + return "U"; + case MaritalStatusTypeEnum.married: + return "M"; + case MaritalStatusTypeEnum.divorced: + return "D"; + case MaritalStatusTypeEnum.widowed: + return "W"; + } + } + + String get type { + switch (this) { + case MaritalStatusTypeEnum.single: + return "Single"; + case MaritalStatusTypeEnum.married: + return "Married"; + case MaritalStatusTypeEnum.divorced: + return "Divorced"; + case MaritalStatusTypeEnum.widowed: + return "Widowed"; + } + } + + String get typeAr { + switch (this) { + case MaritalStatusTypeEnum.single: + return "أعزب"; + case MaritalStatusTypeEnum.married: + return "متزوج"; + case MaritalStatusTypeEnum.divorced: + return "مطلق"; + case MaritalStatusTypeEnum.widowed: + return "أرمل"; + } + } + + static MaritalStatusTypeEnum? fromValue(String? value) { + switch (value) { + case "U": + return MaritalStatusTypeEnum.single; + case "M": + return MaritalStatusTypeEnum.married; + case "D": + return MaritalStatusTypeEnum.divorced; + case "W": + return MaritalStatusTypeEnum.widowed; + default: + return null; + } + } + + static MaritalStatusTypeEnum? fromType(String? type) { + switch (type) { + case "Single": + return MaritalStatusTypeEnum.single; + case "Married": + return MaritalStatusTypeEnum.married; + case "Divorced": + return MaritalStatusTypeEnum.divorced; + case "Widowed": + return MaritalStatusTypeEnum.widowed; + default: + return null; + } + } +} diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 7307013..0be6eaf 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -20,8 +20,7 @@ abstract class AuthenticationRepo { required CheckPatientAuthenticationReq checkPatientAuthenticationReq, }); - Future>> sendActivationCodeRegister( - {required CheckPatientAuthenticationReq checkPatientAuthenticationReq, String? languageID}); + Future>> sendActivationCodeRegister({required CheckPatientAuthenticationReq checkPatientAuthenticationReq, String? languageID}); } class AuthenticationRepoImp implements AuthenticationRepo { @@ -114,8 +113,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { } @override - Future>> sendActivationCodeRegister( - {required CheckPatientAuthenticationReq checkPatientAuthenticationReq, String? languageID}) async { + Future>> sendActivationCodeRegister({required CheckPatientAuthenticationReq checkPatientAuthenticationReq, String? languageID}) async { int isOutKsa = (checkPatientAuthenticationReq.zipCode == '966' || checkPatientAuthenticationReq.zipCode == '+966') ? 0 : 1; //TODO : We will use all these from AppState directly in the ApiClient diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index ed77aca..c34c339 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -1,7 +1,12 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/request_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; import 'package:hmg_patient_app_new/features/authentication/models/request_models/check_patient_authentication_request_model.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; @@ -20,12 +25,67 @@ class AuthenticationViewModel extends ChangeNotifier { required this.dialogService, }); - final TextEditingController nationalIdController = TextEditingController(); - final TextEditingController phoneNumberController = TextEditingController(); + final TextEditingController nationalIdController = TextEditingController(), phoneNumberController = TextEditingController(), dobController = TextEditingController(); + CountryEnum selectedCountrySignup = CountryEnum.saudiArabia; + MaritalStatusTypeEnum? maritalStatus; + GenderTypeEnum? genderType; + bool isTermsAccepted = false; + List? countriesList; + NationalityCountries? pickedCountryByUAEUser; + + void login() { + if (ValidationUtils.isValidatePhoneAndId(nationalId: nationalIdController.text, phoneNumber: phoneNumberController.text)) { + } else {} + } + + void clearDefaults() { + nationalIdController.clear(); + phoneNumberController.clear(); + dobController.clear(); + maritalStatus = null; + genderType = null; + isTermsAccepted = false; + selectedCountrySignup = CountryEnum.saudiArabia; + pickedCountryByUAEUser = null; + } + + void onCountryChange(CountryEnum country) { + selectedCountrySignup = country; + notifyListeners(); + } + + void loadCountriesData({required BuildContext context}) async { + final String response = await DefaultAssetBundle.of(context).loadString('assets/json/countriesList.json'); + final List data = json.decode(response); + countriesList = data.map((e) => NationalityCountries.fromJson(e)).toList(); + } + + void onMaritalStatusChange(String? status) { + maritalStatus = MaritalStatusTypeExtension.fromType(status)!; + notifyListeners(); + } + + void onGenderChange(String? status) { + genderType = GenderTypeExtension.fromType(status)!; + notifyListeners(); + } + + void onUAEUserCountrySelection(String? value) { + pickedCountryByUAEUser = countriesList!.firstWhere((element) => element.name == value); + notifyListeners(); + } + + void onPhoneNumberChange(String? phoneNumber) { + phoneNumberController.text = phoneNumber!; + } + + void onTermAccepted() { + isTermsAccepted = !isTermsAccepted; + notifyListeners(); + } Future selectDeviceImei({Function(dynamic)? onSuccess, Function(String)? onError}) async { - String firebaseToken = - "dOGRRszQQMGe_9wA5Hx3kO:APA91bFV5IcIJXvcCXXk0tc2ddtZgWwCPq7sGSuPr-YW7iiJpQZKgFGN9GAzCVOWL8MfheaP1slE8MdxB7lczdPBGdONQ7WbMmhgHcsUCUktq-hsapGXXqc"; + String firebaseToken = "dOGRRszQQMGe_9wA5Hx3kO:APA91bFV5IcIJXvcCXXk0tc2ddtZgWwCPq7sGSuPr-YW7iiJpQZKgFGN9GAzCVOWL8MfheaP1slE8MdxB7lczdPBGdONQ7WbMmhgHcsUCUktq-hsapGXXqc"; final result = await authenticationRepo.selectDeviceByImei(firebaseToken: firebaseToken); result.fold( @@ -40,96 +100,96 @@ class AuthenticationViewModel extends ChangeNotifier { ); } - // Future checkUserAuthentication({Function(dynamic)? onSuccess, Function(String)? onError}) async { - // CheckPatientAuthenticationReq checkPatientAuthenticationReq = RequestUtils.getCommonRequestWelcome( - // phoneNumber: '0567184134', - // otpTypeEnum: OTPTypeEnum.sms, - // deviceToken: 'dummyDeviceToken123', - // patientOutSA: true, - // loginTokenID: 'dummyLoginToken456', - // registeredData: null, - // patientId: 12345, - // nationIdText: '1234567890', - // countryCode: 'SA', - // ); - // - // final result = await authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq); - // result.fold( - // (failure) async => await errorHandlerService.handleError(failure: failure), - // (apiResponse) { - // if (apiResponse.data['isSMSSent']) { - // // TODO: set this in AppState - // // sharedPref.setString(LOGIN_TOKEN_ID, value['LogInTokenID']); - // // loginTokenID = value['LogInTokenID'], - // // sharedPref.setObject(REGISTER_DATA_FOR_LOGIIN, request), - // sendActivationCode(type); - // } else { - // if (apiResponse.data['IsAuthenticated']) { - // checkActivationCode(onWrongActivationCode: (String? message) {}); - // } - // } - // }, - // ); - // } - - // Future sendActivationCode({required OTPTypeEnum otpTypeEnum}) async { - // var request = RequestUtils.getCommonRequestAuthProvider( - // otpTypeEnum: otpTypeEnum, - // registeredData: null, - // deviceToken: "dummyLoginToken456", - // mobileNumber: "0567184134", - // zipCode: "SA", - // patientOutSA: true, - // loginTokenID: "dummyLoginToken456", - // selectedOption: selectedOption, - // patientId: 12345, - // ); - // - // request.sMSSignature = await SMSOTP.getSignature(); - // selectedOption = type; - // // GifLoaderDialogUtils.showMyDialog(context); - // if (healthId != null || isDubai) { - // if (!isDubai) { - // request.dob = dob; //isHijri == 1 ? dob : dateFormat2.format(dateFormat.parse(dob)); - // } - // request.healthId = healthId; - // request.isHijri = isHijri; - // await this.apiClient.sendActivationCodeRegister(request).then((result) { - // // GifLoaderDialogUtils.hideDialog(context); - // if (result != null && result['isSMSSent'] == true) { - // this.startSMSService(type); - // } - // }).catchError((r) { - // GifLoaderDialogUtils.hideDialog(context); - // context.showBottomSheet( - // child: ExceptionBottomSheet( - // message: r.toString(), - // onOkPressed: () { - // Navigator.of(context).pop(); - // }, - // )); - // // AppToast.showErrorToast(message: r); - // }); - // } else { - // request.dob = ""; - // request.healthId = ""; - // request.isHijri = 0; - // await this.authService.sendActivationCode(request).then((result) { - // GifLoaderDialogUtils.hideDialog(context); - // if (result != null && result['isSMSSent'] == true) { - // this.startSMSService(type); - // } - // }).catchError((r) { - // GifLoaderDialogUtils.hideDialog(context); - // context.showBottomSheet( - // child: ExceptionBottomSheet( - // message: r.toString(), - // onOkPressed: () { - // Navigator.of(context).pop(); - // }, - // )); - // // AppToast.showErrorToast(message: r.toString()); - // }); - // } - // } +// Future checkUserAuthentication({Function(dynamic)? onSuccess, Function(String)? onError}) async { +// CheckPatientAuthenticationReq checkPatientAuthenticationReq = RequestUtils.getCommonRequestWelcome( +// phoneNumber: '0567184134', +// otpTypeEnum: OTPTypeEnum.sms, +// deviceToken: 'dummyDeviceToken123', +// patientOutSA: true, +// loginTokenID: 'dummyLoginToken456', +// registeredData: null, +// patientId: 12345, +// nationIdText: '1234567890', +// countryCode: 'SA', +// ); +// +// final result = await authenticationRepo.checkPatientAuthentication(checkPatientAuthenticationReq: checkPatientAuthenticationReq); +// result.fold( +// (failure) async => await errorHandlerService.handleError(failure: failure), +// (apiResponse) { +// if (apiResponse.data['isSMSSent']) { +// // TODO: set this in AppState +// // sharedPref.setString(LOGIN_TOKEN_ID, value['LogInTokenID']); +// // loginTokenID = value['LogInTokenID'], +// // sharedPref.setObject(REGISTER_DATA_FOR_LOGIIN, request), +// sendActivationCode(type); +// } else { +// if (apiResponse.data['IsAuthenticated']) { +// checkActivationCode(onWrongActivationCode: (String? message) {}); +// } +// } +// }, +// ); +// } + +// Future sendActivationCode({required OTPTypeEnum otpTypeEnum}) async { +// var request = RequestUtils.getCommonRequestAuthProvider( +// otpTypeEnum: otpTypeEnum, +// registeredData: null, +// deviceToken: "dummyLoginToken456", +// mobileNumber: "0567184134", +// zipCode: "SA", +// patientOutSA: true, +// loginTokenID: "dummyLoginToken456", +// selectedOption: selectedOption, +// patientId: 12345, +// ); +// +// request.sMSSignature = await SMSOTP.getSignature(); +// selectedOption = type; +// // GifLoaderDialogUtils.showMyDialog(context); +// if (healthId != null || isDubai) { +// if (!isDubai) { +// request.dob = dob; //isHijri == 1 ? dob : dateFormat2.format(dateFormat.parse(dob)); +// } +// request.healthId = healthId; +// request.isHijri = isHijri; +// await this.apiClient.sendActivationCodeRegister(request).then((result) { +// // GifLoaderDialogUtils.hideDialog(context); +// if (result != null && result['isSMSSent'] == true) { +// this.startSMSService(type); +// } +// }).catchError((r) { +// GifLoaderDialogUtils.hideDialog(context); +// context.showBottomSheet( +// child: ExceptionBottomSheet( +// message: r.toString(), +// onOkPressed: () { +// Navigator.of(context).pop(); +// }, +// )); +// // AppToast.showErrorToast(message: r); +// }); +// } else { +// request.dob = ""; +// request.healthId = ""; +// request.isHijri = 0; +// await this.authService.sendActivationCode(request).then((result) { +// GifLoaderDialogUtils.hideDialog(context); +// if (result != null && result['isSMSSent'] == true) { +// this.startSMSService(type); +// } +// }).catchError((r) { +// GifLoaderDialogUtils.hideDialog(context); +// context.showBottomSheet( +// child: ExceptionBottomSheet( +// message: r.toString(), +// onOkPressed: () { +// Navigator.of(context).pop(); +// }, +// )); +// // AppToast.showErrorToast(message: r.toString()); +// }); +// } +// } } diff --git a/lib/features/insurance/insurance_repo.dart b/lib/features/insurance/insurance_repo.dart new file mode 100644 index 0000000..a5c624f --- /dev/null +++ b/lib/features/insurance/insurance_repo.dart @@ -0,0 +1,77 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class InsuranceRepo { + Future>>> getPatientInsuranceDetails({required String patientId}); +} + +class InsuranceRepoImp implements InsuranceRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + InsuranceRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getPatientInsuranceDetails({required String patientId}) async { + final mapDevice = { + "isDentalAllowedBackend": false, + "VersionID": 50.0, + "Channel": 3, + "LanguageID": 2, + "IPAdress": "10.20.10.20", + "generalid": "Cs2020@2016\$2958", + "Latitude": 0.0, + "Longitude": 0.0, + "DeviceTypeID": 1, + "PatientType": 1, + "PatientTypeID": 1, + "TokenID": "@dm!n", + "PatientID": "3628599", + "PatientOutSA": "0", + "SessionID": "03478TYC02N80874CTYN04883475!?" + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_PAtIENTS_INSURANCE, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus}) { + try { + final list = response['List_PatientInsuranceCard']; + if (list == null || list.isEmpty) { + throw Exception("insurance list is empty"); + } + + final labOrders = list.map((item) => PatientInsuranceDetailsResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: labOrders, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + + throw UnimplementedError(); + } +} diff --git a/lib/features/insurance/insurance_view_model.dart b/lib/features/insurance/insurance_view_model.dart new file mode 100644 index 0000000..fac09da --- /dev/null +++ b/lib/features/insurance/insurance_view_model.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; +import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_model.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class InsuranceViewModel extends ChangeNotifier { + bool isInsuranceLoading = false; + bool isInsuranceHistoryLoading = false; + + InsuranceRepo insuranceRepo; + ErrorHandlerService errorHandlerService; + + List patientInsuranceList = []; + + InsuranceViewModel({required this.insuranceRepo, required this.errorHandlerService}); + + initInsuranceProvider() { + patientInsuranceList.clear(); + isInsuranceLoading = true; + isInsuranceHistoryLoading = true; + getPatientInsuranceDetails(); + notifyListeners(); + } + + setIsInsuranceHistoryLoading(bool val) { + isInsuranceHistoryLoading = val; + notifyListeners(); + } + + Future getPatientInsuranceDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await insuranceRepo.getPatientInsuranceDetails(patientId: "1231755"); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientInsuranceList = apiResponse.data!; + isInsuranceLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } +} diff --git a/lib/features/insurance/models/resp_models/patient_insurance_details_response_model.dart b/lib/features/insurance/models/resp_models/patient_insurance_details_response_model.dart new file mode 100644 index 0000000..b734abc --- /dev/null +++ b/lib/features/insurance/models/resp_models/patient_insurance_details_response_model.dart @@ -0,0 +1,96 @@ +class PatientInsuranceDetailsResponseModel { + String? setupID; + int? projectID; + bool? isActive; + int? patientID; + int? companyID; + int? subCategoryID; + dynamic companyType; + String? patientCardID; + String? cardValidTo; + int? patientCreditLimit; + String? subPolicyNo; + String? companyName; + String? companyNameN; + String? subCategoryDesc; + dynamic subCategoryDescN; + bool? isElectronicClaim; + String? subCategoryValidTo; + dynamic groupID; + String? groupName; + dynamic groupNameN; + String? insurancePolicyNo; + + PatientInsuranceDetailsResponseModel( + {this.setupID, + this.projectID, + this.isActive, + this.patientID, + this.companyID, + this.subCategoryID, + this.companyType, + this.patientCardID, + this.cardValidTo, + this.patientCreditLimit, + this.subPolicyNo, + this.companyName, + this.companyNameN, + this.subCategoryDesc, + this.subCategoryDescN, + this.isElectronicClaim, + this.subCategoryValidTo, + this.groupID, + this.groupName, + this.groupNameN, + this.insurancePolicyNo}); + + PatientInsuranceDetailsResponseModel.fromJson(Map json) { + setupID = json['SetupID']; + projectID = json['ProjectID']; + isActive = json['IsActive']; + patientID = json['PatientID']; + companyID = json['CompanyID']; + subCategoryID = json['SubCategoryID']; + companyType = json['CompanyType']; + patientCardID = json['PatientCardID']; + cardValidTo = json['CardValidTo']; + patientCreditLimit = json['PatientCreditLimit']; + subPolicyNo = json['SubPolicyNo']; + companyName = json['CompanyName']; + companyNameN = json['CompanyNameN']; + subCategoryDesc = json['SubCategoryDesc']; + subCategoryDescN = json['SubCategoryDescN']; + isElectronicClaim = json['IsElectronicClaim']; + subCategoryValidTo = json['SubCategoryValidTo']; + groupID = json['GroupID']; + groupName = json['GroupName']; + groupNameN = json['GroupNameN']; + insurancePolicyNo = json['InsurancePolicyNo']; + } + + Map toJson() { + final Map data = new Map(); + data['SetupID'] = this.setupID; + data['ProjectID'] = this.projectID; + data['IsActive'] = this.isActive; + data['PatientID'] = this.patientID; + data['CompanyID'] = this.companyID; + data['SubCategoryID'] = this.subCategoryID; + data['CompanyType'] = this.companyType; + data['PatientCardID'] = this.patientCardID; + data['CardValidTo'] = this.cardValidTo; + data['PatientCreditLimit'] = this.patientCreditLimit; + data['SubPolicyNo'] = this.subPolicyNo; + data['CompanyName'] = this.companyName; + data['CompanyNameN'] = this.companyNameN; + data['SubCategoryDesc'] = this.subCategoryDesc; + data['SubCategoryDescN'] = this.subCategoryDescN; + data['IsElectronicClaim'] = this.isElectronicClaim; + data['SubCategoryValidTo'] = this.subCategoryValidTo; + data['GroupID'] = this.groupID; + data['GroupName'] = this.groupName; + data['GroupNameN'] = this.groupNameN; + data['InsurancePolicyNo'] = this.insurancePolicyNo; + return data; + } +} diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index acd87eb..115b28d 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -12,6 +12,10 @@ class LabViewModel extends ChangeNotifier { List patientLabOrders = []; + List labSuggestionsList =[]; + + get labSuggestions => labSuggestionsList; + LabViewModel({required this.labRepo, required this.errorHandlerService}); initLabProvider() { @@ -42,4 +46,8 @@ class LabViewModel extends ChangeNotifier { }, ); } + + filterSuggestions(){ + + } } diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index cd59c15..7831616 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -781,7 +781,29 @@ abstract class LocaleKeys { static const resultsAvailable = 'resultsAvailable'; static const viewReport = 'viewReport'; static const prescriptionDeliveryError = 'prescriptionDeliveryError'; + static const receiveOtpToast = 'receiveOtpToast'; + static const enterPhoneNumber = 'enterPhoneNumber'; + static const enterEmailDesc = 'enterEmailDesc'; + static const enterPhoneDesc = 'enterPhoneDesc'; + static const pleaseChooseOption = 'pleaseChooseOption'; + static const prepareToElevate = 'prepareToElevate'; + static const iAcceptTermsConditions = 'iAcceptTermsConditions'; + static const alreadyHaveAccount = 'alreadyHaveAccount'; + static const loginNow = 'loginNow'; + static const notice = 'notice'; + static const oR = 'oR'; + static const sendOTPWHATSAPP = 'sendOTPWHATSAPP'; + static const sendOTPSMS = 'sendOTPSMS'; + static const fullName = 'fullName'; + static const married = 'married'; + static const uae = 'uae'; + static const malE = 'malE'; + static const loginBy = 'loginBy'; + static const loginByOTP = 'loginByOTP'; + static const guest = 'guest'; + static const switchAccount = 'switchAccount'; static const checkAvailability = 'checkAvailability'; static const readInstructions = 'readInstructions'; + static const searchLabReport = 'searchLabReport'; } diff --git a/lib/main.dart b/lib/main.dart index 035c6bf..ce69f5d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; @@ -71,6 +72,12 @@ void main() async { errorHandlerService: getIt(), ), ), + ChangeNotifierProvider( + create: (_) => InsuranceViewModel( + insuranceRepo: getIt(), + errorHandlerService: getIt(), + ), + ), ChangeNotifierProvider( create: (_) => AuthenticationViewModel( authenticationRepo: getIt(), diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart index fb88ba1..aef877f 100644 --- a/lib/presentation/authentication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -1,17 +1,21 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/gestures.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/context_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/authentication/register.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; +import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; -import 'package:sizer/sizer.dart'; // Import sizer +import 'package:provider/provider.dart'; class LoginScreen extends StatefulWidget { @override @@ -31,99 +35,165 @@ class _LoginScreen extends State { @override Widget build(BuildContext context) { - return Sizer(// Wrap with Sizer - builder: (context, orientation, deviceType) { - return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - appBar: CustomAppBar( - onBackPressed: () { - }, - onLanguageChanged: (String value) { - print(value); - context.setLocale(value == 'en' ? Locale('ar', 'SA') : Locale('en', 'US')); - }, + AuthenticationViewModel authVm = context.read(); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + appBar: CustomAppBar( + onBackPressed: () { + Navigator.of(context).pop(); + }, + onLanguageChanged: (String value) { + // context.setLocale(value == 'en' ? Locale('ar', 'SA') : Locale('en', 'US')); + }, + ), + body: GestureDetector( + onTap: () { + FocusScope.of(context).unfocus(); // Dismiss the keyboard when tapping outside + }, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.showLottie(context: context, assetPath: AppAnimations.login, width: 200.h, height: 200.h, repeat: true, fit: BoxFit.cover), + SizedBox(height: 130.h), // Adjusted to sizer unit + LocaleKeys.welcomeToDrSulaiman.tr().toText32(isBold: true, color: AppColors.textColor), + SizedBox(height: 32.h), + TextInputWidget( + labelText: "${LocaleKeys.nationalId.tr()} / ${LocaleKeys.fileNo.tr()}", + hintText: "xxxxxxxxx", + controller: authVm.nationalIdController, + keyboardType: TextInputType.number, + isEnable: true, + prefix: null, + autoFocus: true, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 10.h), + leadingIcon: AppAssets.student_card, + errorMessage: "Please enter a valid national ID or file number", + hasError: true, + ), + SizedBox(height: 16.h), // Adjusted to sizer unit (approx 16px) + CustomButton( + text: LocaleKeys.login.tr(), + icon: AppAssets.login1, + iconColor: Colors.white, + onPressed: () { + showLoginModel(context: context, authVM: authVm); + // if (nationIdController.text.isNotEmpty) { + + // } else { + // showBottomSheet( + // child: ExceptionBottomSheet( + // message: TranslationBase.of(context).pleaseEnterNationalIdOrFileNo, + // showCancel: false, + // onOkPressed: () { + // Navigator.of(context).pop(); + // }, + // ), + // ); + // } + }, + ), + SizedBox(height: 10.h), // Adjusted to sizer unit (approx 14px) + Center( + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: context.dynamicTextStyle( + color: Colors.black, + fontSize: 14.fSize, // Adjusted to sizer unit + height: 26 / 16, // This height is a ratio, may need re-evaluation + fontWeight: FontWeight.w500, + ), + children: [ + TextSpan(text: LocaleKeys.dontHaveAccount.tr(), style: context.dynamicTextStyle()), + TextSpan(text: " "), + TextSpan( + text: LocaleKeys.registernow.tr(), + style: context.dynamicTextStyle( + color: AppColors.primaryRedColor, + fontSize: 14.fSize, // Adjusted to sizer unit + height: 26 / 16, // Ratio + fontWeight: FontWeight.w500, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + Navigator.of(context).push( + MaterialPageRoute(builder: (BuildContext context) => RegisterNew()), + ); + }, + ), + ], + ), + ).withVerticalPadding(2), // Adjusted to sizer unit + ), + ], + ), + ), ), - body: GestureDetector( - onTap: () { - FocusScope.of(context).unfocus(); // Dismiss the keyboard when tapping outside - }, - child: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.only(left: 6.w, right: 6.w), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Utils.showLottie(context: context, assetPath: AppAnimations.login, width: 45.w, height: 22.h, repeat: true, fit: BoxFit.cover), - SizedBox(height: 19.h), // Adjusted to sizer unit - LocaleKeys.welcomeToDrSulaiman.tr().toText22(isBold: true, color: AppColors.textColor), - // Text( - // LocaleKeys.welcomeToDrSulaiman.tr(), - // style: context.dynamicTextStyle( - // fontSize: 22, - // fontWeight: FontWeight.w600, - // color: AppColors.textColor, - // letterSpacing: -0.4, - // height: 40 / 28, - // ), - // ), - SizedBox(height: 4.h), // Adjusted to sizer unit (approx 32px) - TextInputWidget( - labelText: "${LocaleKeys.nationalId.tr()} / ${LocaleKeys.fileNo.tr()}", - hintText: "xxxxxxxxx", - controller: TextEditingController(), - keyboardType: TextInputType.number, - isEnable: true, - prefix: null, - autoFocus: true, - isBorderAllowed: false, - isAllowLeadingIcon: true, - padding: EdgeInsets.symmetric(vertical: 1.h, horizontal: 2.w), - leadingIcon: AppAssets.student_card, - errorMessage: "Please enter a valid national ID or file number", - hasError: true, - ), - SizedBox(height: 2.h), // Adjusted to sizer unit (approx 16px) - CustomButton( - text: LocaleKeys.login.tr(), - icon: AppAssets.login1, - iconColor: Colors.white, - onPressed: () {}, + ), + ); + } + + void showLoginModel({required BuildContext context, required AuthenticationViewModel authVM}) { + context.showBottomSheet( + isScrollControlled: true, + isDismissible: false, + useSafeArea: true, + backgroundColor: Colors.transparent, + child: StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) { + return Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: SingleChildScrollView( + child: GenericBottomSheet( + countryCode: authVM.selectedCountrySignup.countryCode, + initialPhoneNumber: "", + textController: authVM.phoneNumberController, + isEnableCountryDropdown: true, + onCountryChange: authVM.onCountryChange, + onChange: authVM.onPhoneNumberChange, + buttons: [ + Padding( + padding: EdgeInsets.only(bottom: 10.h), + child: CustomButton( + text: LocaleKeys.sendOTPSMS.tr(), + onPressed: () {}, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedBorderColor, + textColor: AppColors.whiteColor, + icon: AppAssets.message, + ), ), - SizedBox(height: 1.8.h), // Adjusted to sizer unit (approx 14px) - Center( - child: RichText( - textAlign: TextAlign.center, - text: TextSpan( - style: context.dynamicTextStyle( - color: Colors.black, - fontSize: 14.sp, // Adjusted to sizer unit - height: 26 / 16, // This height is a ratio, may need re-evaluation - fontWeight: FontWeight.w500, - ), - children: [ - TextSpan(text: LocaleKeys.dontHaveAccount.tr(), style: context.dynamicTextStyle()), - TextSpan(text: " "), - TextSpan( - text: LocaleKeys.registernow.tr(), - style: context.dynamicTextStyle( - color: AppColors.primaryRedColor, - fontSize: 14.sp, // Adjusted to sizer unit - height: 26 / 16, // Ratio - fontWeight: FontWeight.w500, - ), - recognizer: TapGestureRecognizer()..onTap = () {}, - ), - ], + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 8.h), + child: LocaleKeys.oR.tr().toText16(color: AppColors.textColor), ), - ).withVerticalPadding(2.h), // Adjusted to sizer unit + ], + ), + Padding( + padding: EdgeInsets.only(bottom: 10.h, top: 10.h), + child: CustomButton( + text: LocaleKeys.sendOTPWHATSAPP.tr(), + onPressed: () {}, + backgroundColor: Colors.white, + borderColor: AppColors.borderOnlyColor, + textColor: AppColors.textColor, + icon: AppAssets.whatsapp, + ), ), ], ), ), - ), - ), - ); - }); + ); + })); } } diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart new file mode 100644 index 0000000..4756cba --- /dev/null +++ b/lib/presentation/authentication/register.dart @@ -0,0 +1,316 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; +import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart' show CustomButton; +import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart'; +import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/otp/otp.dart'; +import 'package:provider/provider.dart'; + +class RegisterNew extends StatefulWidget { + @override + _RegisterNew createState() => _RegisterNew(); +} + +class _RegisterNew extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + AppState appState = getIt.get(); + AuthenticationViewModel authVm = context.read(); + + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + appBar: CustomAppBar( + onBackPressed: () { + Navigator.of(context).pop(); + }, + onLanguageChanged: (String value) { + // context.setLocale(value == 'en' ? Locale('ar', 'SA') : Locale('en', 'US')); + }, + ), + body: GestureDetector( + onTap: () { + FocusScope.of(context).unfocus(); + }, + child: ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith(overscroll: false, physics: const ClampingScrollPhysics()), + child: NotificationListener( + onNotification: (notification) { + notification.disallowIndicator(); + return true; + }, + child: SingleChildScrollView( + physics: ClampingScrollPhysics(), + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.showLottie(context: context, assetPath: 'assets/animations/lottie/register.json', width: 200.h, height: 200.h, fit: BoxFit.cover, repeat: true), + SizedBox(height: 16.h), + LocaleKeys.prepareToElevate.tr().toText32(isBold: true), + SizedBox(height: 24.h), + Directionality( + textDirection: Directionality.of(context), + child: Container( + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(24)), + padding: EdgeInsets.symmetric(horizontal: 16.h), + child: Column( + children: [ + CustomCountryDropdown( + countryList: CountryEnum.values, + onCountryChange: authVm.onCountryChange, + isRtl: Directionality.of(context) == TextDirection.LTR, + ).withVerticalPadding(8.h), + Divider(height: 1.h), + TextInputWidget( + labelText: LocaleKeys.nationalIdNumber.tr(), + hintText: "xxxxxxxxx", + controller: authVm.nationalIdController, + isEnable: true, + prefix: null, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + autoFocus: true, + padding: EdgeInsets.symmetric(vertical: 8.h), + leadingIcon: AppAssets.student_card, + // onChange: (value) { + // print(value); + // } + ).withVerticalPadding(8), + Divider(height: 1), + TextInputWidget( + labelText: LocaleKeys.dob.tr(), + hintText: "11 July, 1994", + controller: authVm.dobController, + isEnable: true, + prefix: null, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + padding: EdgeInsets.symmetric(vertical: 8.h), + leadingIcon: AppAssets.birthday_cake, + selectionType: SelectionTypeEnum.calendar, + ).withVerticalPadding(8), + ], + ), + ), + ), + SizedBox(height: 25.h), + GestureDetector( + onTap: authVm.onTermAccepted, + child: Row( + children: [ + Selector( + selector: (_, viewModel) => viewModel.isTermsAccepted, + shouldRebuild: (previous, next) => previous != next, + builder: (context, isTermsAccepted, child) { + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + height: 24.h, + width: 24.h, + decoration: BoxDecoration( + color: isTermsAccepted ? AppColors.primaryRedColor : Colors.transparent, + borderRadius: BorderRadius.circular(6), + border: Border.all(color: isTermsAccepted ? AppColors.primaryRedBorderColor : AppColors.greyColor, width: 2.h), + ), + child: isTermsAccepted ? Icon(Icons.check, size: 16.fSize, color: Colors.white) : null, + ); + }, + ), + SizedBox(width: 12.h), + Expanded( + child: Text( + LocaleKeys.iAcceptTermsConditions.tr(), + style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)), + ), + ), + ], + ), + ), + SizedBox(height: 25.h), + CustomButton( + text: "Register", + icon: AppAssets.note_edit, + onPressed: () { + showRegisterModel(context: context, authVM: authVm); + }, + ), + SizedBox(height: 14), + Center( + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: context.dynamicTextStyle( + color: Colors.black, + fontSize: 16.fSize, + height: 26 / 16, + fontWeight: FontWeight.w500, + ), + children: [ + TextSpan(text: LocaleKeys.alreadyHaveAccount.tr(), style: context.dynamicTextStyle()), + TextSpan(text: " "), + TextSpan( + text: LocaleKeys.loginNow.tr(), + style: context.dynamicTextStyle( + color: AppColors.primaryRedColor, + fontSize: 16.fSize, + height: 26 / 16, + fontWeight: FontWeight.w500, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + Navigator.of(context).pop(); + }, + ), + ], + ), + ), + ), + SizedBox(height: 30), + ], + ), + ), + ), + ), + )); + } + + void showRegisterModel({required BuildContext context, required AuthenticationViewModel authVM}) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: false, + useSafeArea: true, + backgroundColor: Colors.transparent, + builder: (bottomSheetContext) => Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom), + child: SingleChildScrollView( + child: GenericBottomSheet( + countryCode: authVM.selectedCountrySignup.countryCode, + initialPhoneNumber: "", + textController: authVM.phoneNumberController, + isEnableCountryDropdown: true, + onCountryChange: authVM.onCountryChange, + onChange: authVM.onPhoneNumberChange, + buttons: [ + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: CustomButton( + text: LocaleKeys.sendOTPSMS.tr(), + onPressed: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (BuildContext context) => OTPVerificationPage( + phoneNumber: '12234567', + ))); + + // if (mobileNo.isEmpty) { + // context.showBottomSheet( + // child: ExceptionBottomSheet( + // message: TranslationBase.of(context).pleaseEnterMobile, + // showCancel: false, + // onOkPressed: () { + // Navigator.of(context).pop(); + // }, + // ), + // ); + // } else if (!Utils.validateMobileNumber(mobileNo)) { + // context.showBottomSheet( + // child: ExceptionBottomSheet( + // message: TranslationBase.of(context).pleaseEnterValidMobile, + // showCancel: false, + // onOkPressed: () { + // Navigator.of(context).pop(); + // }, + // ), + // ); + // } else { + // registerUser(1); + // } + Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => OTPVerificationPage(phoneNumber: '12234567'))); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedBorderColor, + textColor: AppColors.whiteColor, + icon: AppAssets.message, + ), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 8.h), + child: LocaleKeys.oR.tr().toText16(color: AppColors.textColor), + ), + ], + ), + Padding( + padding: EdgeInsets.only(bottom: 10.h, top: 10.h), + child: CustomButton( + text: LocaleKeys.sendOTPWHATSAPP.tr(), + onPressed: () { + // if (mobileNo.isEmpty) { + // context.showBottomSheet( + // child: ExceptionBottomSheet( + // message: TranslationBase.of(context).pleaseEnterMobile, + // showCancel: false, + // onOkPressed: () { + // Navigator.of(context).pop(); + // }, + // ), + // ); + // } else if (!Utils.validateMobileNumber(mobileNo)) { + // context.showBottomSheet( + // child: ExceptionBottomSheet( + // message: TranslationBase.of(context).pleaseEnterValidMobile, + // showCancel: false, + // onOkPressed: () { + // Navigator.of(context).pop(); + // }, + // ), + // ); + // } else { + // registerUser(4); + // } + // int? val = Utils.onOtpBtnPressed(OTPType.whatsapp, mobileNo, context); + // registerUser(val); + }, + backgroundColor: AppColors.whiteColor, + borderColor: AppColors.borderOnlyColor, + textColor: AppColors.textColor, + icon: AppAssets.whatsapp, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart new file mode 100644 index 0000000..483ce5c --- /dev/null +++ b/lib/presentation/authentication/register_step2.dart @@ -0,0 +1,353 @@ +import 'dart:convert'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/common_models/nationality_country_model.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/context_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; +import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:provider/provider.dart'; + +class RegisterNewStep2 extends StatefulWidget { + var nHICData; + var payload; + + RegisterNewStep2(this.nHICData, this.payload, {Key? key}) : super(key: key); + + @override + _RegisterNew createState() => _RegisterNew(); +} + +class _RegisterNew extends State { + bool isFromDubai = true; + AuthenticationViewModel? authVM; + + @override + void initState() { + super.initState(); + authVM = context.read(); + authVM!.loadCountriesData(context: context); + // isFromDubai = widget.payload.zipCode!.contains("971") || widget.payload.zipCode!.contains("+971"); + } + + @override + void dispose() { + super.dispose(); + authVM!.clearDefaults(); + } + + @override + Widget build(BuildContext context) { + AppState appState = getIt.get(); + return Scaffold( + appBar: CustomAppBar( + onBackPressed: () { + Navigator.of(context).pop(); + authVM!.clearDefaults(); + }, + onLanguageChanged: (lang) {}, + hideLogoAndLang: true, + ), + body: SingleChildScrollView( + reverse: false, + padding: EdgeInsets.only(left: 24.h, right: 24.h, top: 24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Directionality( + textDirection: Directionality.of(context), + child: Container( + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(24)), + padding: EdgeInsets.only(left: 16.h, right: 16.h), + child: Column( + children: [ + TextInputWidget( + labelText: isFromDubai ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(), + hintText: isFromDubai ? "name" ?? "" : (widget.nHICData!.firstNameEn!.toUpperCase() + " " + widget.nHICData!.lastNameEn!.toUpperCase()), + controller: null, + isEnable: true, + prefix: null, + isAllowRadius: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + isAllowLeadingIcon: true, + isReadOnly: isFromDubai ? false : true, + leadingIcon: AppAssets.user_circle) + .paddingSymmetrical(0.h, 16.h), + Divider(height: 1, color: AppColors.greyColor), + TextInputWidget( + labelText: LocaleKeys.nationalIdNumber.tr(), + hintText: isFromDubai ? "widget.payload.nationalID!" : (widget.nHICData!.idNumber ?? ""), + controller: null, + isEnable: true, + prefix: null, + isAllowRadius: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isReadOnly: true, + leadingIcon: AppAssets.student_card) + .paddingSymmetrical(0.h, 16.h), + Divider(height: 1, color: AppColors.greyColor), + isFromDubai + ? Selector( + selector: (_, authViewModel) => authViewModel.genderType, + shouldRebuild: (previous, next) => previous != next, + builder: (context, genderType, child) { + final authVM = context.read(); + return DropdownWidget( + labelText: LocaleKeys.gender.tr(), + hintText: LocaleKeys.malE.tr(), + isEnable: true, + dropdownItems: GenderTypeEnum.values.map((e) => appState!.isArabic() ? e.typeAr : e.type).toList(), + selectedValue: genderType != null ? (appState!.isArabic() ? genderType!.typeAr : genderType!.type) : "", + onChange: authVM.onGenderChange, + isBorderAllowed: false, + hasSelectionCustomIcon: true, + isAllowRadius: false, + padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), + selectionCustomIcon: AppAssets.arrow_down, + leadingIcon: AppAssets.user_full, + ).withVerticalPadding(8); + }) + : TextInputWidget( + labelText: LocaleKeys.gender.tr(), + hintText: (widget.nHICData!.gender ?? ""), + controller: null, + isEnable: true, + prefix: null, + isAllowRadius: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isReadOnly: isFromDubai ? false : true, + leadingIcon: AppAssets.user_full, + onChange: (value) {}) + .paddingSymmetrical(0.h, 16.h), + Divider(height: 1, color: AppColors.greyColor), + isFromDubai + ? Selector( + selector: (_, authViewModel) => authViewModel.maritalStatus, + shouldRebuild: (previous, next) => previous != next, + builder: (context, maritalStatus, child) { + final authVM = context.read(); // For onChange + return DropdownWidget( + labelText: LocaleKeys.maritalStatus.tr(), + hintText: LocaleKeys.married.tr(), + isEnable: true, + dropdownItems: MaritalStatusTypeEnum.values.map((e) => appState!.isArabic() ? e.typeAr : e.type).toList(), + selectedValue: maritalStatus != null ? (appState!.isArabic() ? maritalStatus.typeAr : maritalStatus.type) : "", + onChange: authVM.onMaritalStatusChange, + isBorderAllowed: false, + hasSelectionCustomIcon: true, + isAllowRadius: false, + padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), + selectionCustomIcon: AppAssets.arrow_down, + leadingIcon: AppAssets.smart_phone, + ).withVerticalPadding(8); + }, + ) + : TextInputWidget( + labelText: LocaleKeys.maritalStatus.tr(), + hintText: appState!.isArabic() + ? (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.typeAr) + : (MaritalStatusTypeExtension.fromValue(widget.nHICData!.maritalStatusCode)!.type), + isEnable: true, + prefix: null, + isAllowRadius: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isReadOnly: true, + leadingIcon: AppAssets.smart_phone, + onChange: (value) {}) + .paddingSymmetrical(0.h, 16.h), + Divider(height: 1, color: AppColors.greyColor), + isFromDubai + ? Selector? countriesList, NationalityCountries? selectedCountry, bool isArabic})>( + selector: (context, authViewModel) { + final appState = getIt.get(); + return (countriesList: authViewModel.countriesList, selectedCountry: authViewModel.pickedCountryByUAEUser, isArabic: appState.isArabic()); + }, + shouldRebuild: (previous, next) => previous.countriesList != next.countriesList || previous.selectedCountry != next.selectedCountry || previous.isArabic != next.isArabic, + builder: (context, data, child) { + final authVM = context.read(); + return DropdownWidget( + labelText: LocaleKeys.country.tr(), + hintText: LocaleKeys.uae.tr(), + isEnable: true, + dropdownItems: (data.countriesList ?? []).map((e) => data.isArabic ? e.nameN ?? "" : e.name ?? "").toList(), + selectedValue: data.selectedCountry != null + ? data.isArabic + ? data.selectedCountry!.nameN ?? "" + : data.selectedCountry!.name ?? "" + : "", + onChange: authVM.onUAEUserCountrySelection, + isBorderAllowed: false, + hasSelectionCustomIcon: true, + isAllowRadius: false, + padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0), + selectionCustomIcon: AppAssets.arrow_down, + leadingIcon: AppAssets.globe, + ).withVerticalPadding(8); + }, + ) + : TextInputWidget( + labelText: LocaleKeys.nationality.tr(), + hintText: appState.isArabic() + ? (authVM!.countriesList!.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).nameN ?? "") + : (authVM!.countriesList!.firstWhere((e) => e.id == (widget.nHICData!.nationalityCode ?? ""), orElse: () => NationalityCountries()).name ?? ""), + isEnable: true, + prefix: null, + isAllowRadius: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isReadOnly: true, + leadingIcon: AppAssets.globe, + onChange: (value) {}) + .paddingSymmetrical(0.h, 16.h), + Divider( + height: 1, + color: AppColors.greyColor, + ), + TextInputWidget( + labelText: LocaleKeys.mobileNumber.tr(), + hintText: ("widget.payload.mobileNo" ?? ""), + controller: authVM!.phoneNumberController, + isEnable: true, + prefix: null, + isAllowRadius: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isReadOnly: true, + leadingIcon: AppAssets.call, + onChange: (value) {}) + .paddingSymmetrical(0.h, 16.h), + Divider( + height: 1, + color: AppColors.greyColor, + ), + TextInputWidget( + labelText: LocaleKeys.dob.tr(), + hintText: isFromDubai ? "widget.payload.dob!" : (widget.nHICData!.dateOfBirth ?? ""), + controller: authVM!.dobController, + isEnable: true, + prefix: null, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isReadOnly: true, + leadingIcon: AppAssets.birthday_cake, + selectionType: SelectionTypeEnum.calendar, + ).paddingSymmetrical(0.h, 16.h), + ], + ), + ), + ), + SizedBox(height: 50.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(), + icon: AppAssets.cancel, + onPressed: () { + Navigator.of(context).pop(); + authVM!.clearDefaults(); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + iconColor: AppColors.primaryRedColor, + ), + ), + SizedBox( + width: 16, + ), + Expanded( + child: CustomButton( + backgroundColor: AppColors.lightGreenColor, + borderColor: AppColors.lightGreenColor, + textColor: AppColors.textGreenColor, + text: LocaleKeys.confirm.tr(), + icon: AppAssets.confirm, + iconColor: AppColors.textGreenColor, + onPressed: () { + // if (isFromDubai) { + // if (name == null) { + // AppToast.showErrorToast(message: LocaleKeys.enterFullName); + // return; + // } + // if (!name!.contains(" ")) if (selectedGenderType == null) { + // AppToast.showErrorToast(message: TranslationBase.of(context).enterFullName); + // return; + // } + // if (selectedMaritalStatusType == null) { + // AppToast.showErrorToast(message: TranslationBase.of(context).chooseMaritalStatus); + // return; + // } + // if (selectedCountry == null) { + // AppToast.showErrorToast(message: TranslationBase.of(context).chooseCountry); + // return; + // } + // } + + showModel(context: context); + }, + ), + ) + ], + ), + ], + ), + ), + ); + } + + void showModel({required BuildContext context}) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: false, + backgroundColor: Colors.transparent, + builder: (bottomSheetContext) => Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom), + child: SingleChildScrollView( + child: GenericBottomSheet( + textController: TextEditingController(), + isForEmail: true, + buttons: [ + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: CustomButton( + text: LocaleKeys.submit, + onPressed: () { + // if (emailAddress.text.isEmpty) { + // Utils.showErrorToast(TranslationBase.of(context).enterEmailAddress); + // return; + // } else { + // Navigator.of(context).pop(); + // registerNow(); + // } + }, + backgroundColor: AppColors.bgGreenColor, + borderColor: AppColors.bgGreenColor, + textColor: AppColors.whiteColor), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart new file mode 100644 index 0000000..da04c40 --- /dev/null +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -0,0 +1,289 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; +import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; + +class SavedLogin extends StatefulWidget { + // final SelectDeviceIMEIRES savedLoginData; + + // const SavedLogin(this.savedLoginData, {Key? key}) : super(key: key); + const SavedLogin(); + + @override + _SavedLogin createState() => _SavedLogin(); +} + +class _SavedLogin extends State { + LoginTypeEnum loginType = LoginTypeEnum.sms; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar( + onBackPressed: () {}, + onLanguageChanged: (lang) {}, + ), + body: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Spacer(flex: 2), + // Welcome back text + LocaleKeys.welcomeBack.tr().toText16(color: AppColors.inputLabelTextColor), + SizedBox(height: 16.h), + ("widget.savedLoginData.name!.toLowerCase().capitalizeFirstofEach").toText26(isBold: true, height: 26 / 36, color: AppColors.textColor), + SizedBox(height: 24.h), + Container( + padding: EdgeInsets.all(16.h), + decoration: BoxDecoration( + color: AppColors.whiteColor, + border: Border.all(color: AppColors.whiteColor), + borderRadius: BorderRadius.circular(24.h), + boxShadow: [ + BoxShadow(color: Color(0x0D000000), blurRadius: 16, offset: Offset(0, 0), spreadRadius: 5), + ], + ), + child: Column( + children: [ + // Last login info + ('LocaleKeys.lastloginBy.tr()' + ' {getType(widget.savedLoginData.logInType!, context)}').toText14(isBold: true, color: AppColors.greyTextColor), + ('widget.savedLoginData.createdOn != null ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(widget.savedLoginData.createdOn!), "d MMMM, y at HH:mm") : --') + .toText16(isBold: true, color: AppColors.textColor), + + Container(margin: EdgeInsets.all(16.h), child: Utils.buildSvgWithAssets(icon: getTypeIcons(loginType.toInt), iconColor: loginType.toInt == 4 ? null : AppColors.primaryRedColor)), + // Face ID login button + Container( + height: 45, + child: CustomButton( + text: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}", + onPressed: () { + if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { + // loginWithFingerPrintFace(loginType.toInt, widget.savedLoginData.iMEI!); + } else { + int? val = loginType.toInt; + //checkUserAuthentication(val); + } + }, + backgroundColor: Color(0xffED1C2B), + borderColor: Color(0xffFEE9EA), + textColor: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(0, 10, 0, 10), + icon: AppAssets.apple_finder, + ), + ), + ], + ), + ), + const SizedBox(height: 24), + Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: Text( + LocaleKeys.oR.tr(), + style: context.dynamicTextStyle(fontSize: 16, fontWeight: FontWeight.w500), + ), + ), + const SizedBox(height: 24), + // OTP login button + loginType != null && loginType.toInt != 1 + ? Column( + children: [ + loginType.toInt != 1 + ? CustomButton( + text: LocaleKeys.loginByOTP.tr(), + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: false, + useSafeArea: true, + backgroundColor: Colors.transparent, + enableDrag: false, + // Prevent dragging to avoid focus conflicts + builder: (bottomSheetContext) => StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) { + return Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom), + child: SingleChildScrollView( + child: GenericBottomSheet( + countryCode: "966", + initialPhoneNumber: "", + textController: TextEditingController(), + isFromSavedLogin: true, + isEnableCountryDropdown: true, + onCountryChange: (value) {}, + onChange: (String? value) {}, + buttons: [ + Padding( + padding: EdgeInsets.only(bottom: 10.h), + child: CustomButton( + text: LocaleKeys.sendOTPSMS.tr(), + onPressed: () { + Navigator.of(context).pop(); + loginType = LoginTypeEnum.sms; + int? val = loginType.toInt; + // checkUserAuthentication(val); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedBorderColor, + textColor: AppColors.whiteColor, + icon: AppAssets.message, + ), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding(padding: EdgeInsets.symmetric(horizontal: 8.h), child: (LocaleKeys.oR.tr()).toText16(color: AppColors.textColor)), + ], + ), + Padding( + padding: const EdgeInsets.only(bottom: 10, top: 10), + child: CustomButton( + text: LocaleKeys.sendOTPWHATSAPP.tr(), + onPressed: () { + Navigator.of(context).pop(); + loginType = LoginTypeEnum.whatsapp; + int? val = loginType.toInt; + // checkUserAuthentication(val); + }, + backgroundColor: AppColors.transparent, + borderColor: AppColors.textColor, + textColor: AppColors.textColor, + icon: AppAssets.whatsapp, + ), + ), + ], + ), + ), + ); + }), + ); + }, + backgroundColor: AppColors.whiteColor, + borderColor: AppColors.borderOnlyColor, + textColor: AppColors.textColor, + borderWidth: 2, + padding: EdgeInsets.fromLTRB(0, 14, 0, 14), + icon: AppAssets.password_validation, + ) + : Container(), + SizedBox( + height: 20, + ), + ], + ) + : CustomButton( + text: "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}", + onPressed: () { + if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { + // loginWithFingerPrintFace(loginType.toInt, "iMEI"); + } else { + loginType = LoginTypeEnum.whatsapp; + int? val = loginType.toInt; + // checkUserAuthentication(val); + } + }, + backgroundColor: AppColors.whiteColor, + borderColor: Color(0xFF2E3039), + textColor: Color(0xFF2E3039), + borderWidth: 2, + padding: EdgeInsets.fromLTRB(0, 14, 0, 14), + icon: AppAssets.whatsapp, + ), + const Spacer(flex: 2), + // OR divider + + const SizedBox(height: 24), + // Guest and Switch account + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + height: 56, + child: CustomButton( + text: LocaleKeys.guest.tr(), + onPressed: () { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (BuildContext context) => LoginScreen()), + ); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(0, 10, 0, 10), + // icon: "assets/images/svg/apple-finder.svg", + ), + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.05, + ), + Expanded( + child: Container( + height: 56, + child: CustomButton( + text: LocaleKeys.switchAccount.tr(), + onPressed: () { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (BuildContext context) => LoginScreen()), + ); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 15, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(0, 10, 0, 10), + // icon: "assets/images/svg/apple-finder.svg", + ), + ), + ), + ], + ), + const SizedBox(height: 20), + ], + ), + ), + ), + ); + } + + String getTypeIcons(int type) { + final types = { + 1: AppAssets.sms, + 2: AppAssets.fingerprint, + 3: AppAssets.fingerprint, + 4: AppAssets.whatsapp, + }; + + if (types.containsKey(type)) { + return types[type]!; + } else { + throw Exception('Invalid login type: $type'); + } + } +} diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 301e8bb..e863dab 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; @@ -50,7 +51,8 @@ class _LandingPageState extends State { CustomButton( text: LocaleKeys.loginOrRegister.tr(context: context), onPressed: () async { - await authenticationViewModel.selectDeviceImei(); + // await authenticationViewModel.selectDeviceImei(); + Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => LoginScreen())); }, backgroundColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA), @@ -179,10 +181,7 @@ class _LandingPageState extends State { ) : Container( height: 127.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), child: Padding( padding: EdgeInsets.all(16.h), child: Column( diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart index f3e088b..152bbd2 100644 --- a/lib/presentation/home/navigation_screen.dart +++ b/lib/presentation/home/navigation_screen.dart @@ -20,22 +20,19 @@ class _LandingNavigationState extends State { body: PageView( controller: _pageController, physics: const NeverScrollableScrollPhysics(), - children: const [ - LandingPage(), + children: [ + const LandingPage(), MedicalFilePage(), - LandingPage(), - LandingPage(), - LandingPage(), + const LandingPage(), + const LandingPage(), + const LandingPage(), ], ), bottomNavigationBar: BottomNavigation( currentIndex: _currentIndex, onTap: (index) { setState(() => _currentIndex = index); - _pageController.animateToPage( - index, - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut); + _pageController.animateToPage(index, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut); }, ), ); diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index 297ed2b..f988e18 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; @@ -78,6 +79,11 @@ class SmallServiceCard extends StatelessWidget { ); break; case "insurance_update": + Navigator.of(context).push( + FadePage( + page: InsuranceHomePage(), + ), + ); break; default: // Handle unknown service diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart new file mode 100644 index 0000000..9f9271a --- /dev/null +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -0,0 +1,90 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; +import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:provider/provider.dart'; + +import 'widgets/insurance_history.dart'; + +class InsuranceHomePage extends StatefulWidget { + const InsuranceHomePage({super.key}); + + @override + State createState() => _InsuranceHomePageState(); +} + +class _InsuranceHomePageState extends State { + late InsuranceViewModel insuranceViewModel; + + @override + void initState() { + scheduleMicrotask(() { + insuranceViewModel.initInsuranceProvider(); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + insuranceViewModel = Provider.of(context); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + appBar: AppBar( + title: LocaleKeys.insurance.tr(context: context).toText18(), + backgroundColor: AppColors.bgScaffoldColor, + ), + body: SingleChildScrollView( + child: Consumer(builder: (context, insuranceVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}".toText24(isBold: true), + CustomButton( + icon: AppAssets.insurance_history_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 21.h, + text: LocaleKeys.history.tr(context: context), + onPressed: () { + insuranceVM.setIsInsuranceHistoryLoading(true); + showCommonBottomSheet(context, + child: InsuranceHistory(), callBackFunc: () {}, title: "", height: ResponsiveExtension.screenHeight * 0.5, isCloseButtonVisible: false, isFullScreen: false); + }, + backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), + borderColor: AppColors.primaryRedColor.withOpacity(0.0), + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w600, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ).paddingSymmetrical(24.h, 24.h), + insuranceVM.isInsuranceLoading + ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0) + : PatientInsuranceCard( + insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, + isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))), + ], + ); + }), + ), + ); + } +} diff --git a/lib/presentation/insurance/widgets/insurance_history.dart b/lib/presentation/insurance/widgets/insurance_history.dart new file mode 100644 index 0000000..0708c7c --- /dev/null +++ b/lib/presentation/insurance/widgets/insurance_history.dart @@ -0,0 +1,39 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:provider/provider.dart'; + +class InsuranceHistory extends StatelessWidget { + InsuranceHistory({super.key}); + + late InsuranceViewModel insuranceViewModel; + + @override + Widget build(BuildContext context) { + insuranceViewModel = Provider.of(context); + return Consumer(builder: (context, insuranceVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.history.tr(context: context).toText24(isBold: true), + Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon).onPress(() { + Navigator.of(context).pop(); + }), + ], + ).paddingSymmetrical(24.h, 24.h), + insuranceVM.isInsuranceHistoryLoading ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 24.h) : Container() + ], + ); + }); + } +} diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart new file mode 100644 index 0000000..2701233 --- /dev/null +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -0,0 +1,133 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; +import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_details_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:provider/provider.dart'; + +class PatientInsuranceCard extends StatelessWidget { + PatientInsuranceCard({super.key, required this.insuranceCardDetailsModel, required this.isInsuranceExpired}); + + PatientInsuranceDetailsResponseModel insuranceCardDetailsModel; + bool isInsuranceExpired = false; + + late InsuranceViewModel insuranceViewModel; + + @override + Widget build(BuildContext context) { + insuranceViewModel = Provider.of(context); + return Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Haroon Amjad".toText18(isBold: true), + "Policy: ${insuranceCardDetailsModel.insurancePolicyNo}".toText12(isBold: true, color: AppColors.lightGrayColor), + ], + ), + CustomButton( + icon: isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon, + iconColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, + iconSize: 13.h, + text: isInsuranceExpired ? "Insurance Expired" : "Insurance Active", + onPressed: () {}, + backgroundColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.15) : AppColors.successColor.withOpacity(0.15), + borderColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.01) : AppColors.successColor.withOpacity(0.01), + textColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + SizedBox(height: 12.h), + insuranceCardDetailsModel.groupName!.toText12(isBold: true), + insuranceCardDetailsModel.companyName!.toText12(isBold: true), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, + children: [ + Row( + children: [ + CustomButton( + icon: AppAssets.doctor_calendar_icon, + iconColor: AppColors.blackColor, + iconSize: 13.h, + text: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + children: [ + CustomButton( + text: "Patient Card ID: ${insuranceCardDetailsModel.patientCardID}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + ], + ), + SizedBox(height: 10.h), + isInsuranceExpired + ? CustomButton( + icon: AppAssets.update_insurance_card_icon, + iconColor: AppColors.successColor, + iconSize: 15.h, + text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", + onPressed: () {}, + backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), + borderColor: AppColors.bgGreenColor.withOpacity(0.0), + textColor: AppColors.bgGreenColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ) + : Container(), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h); + } +} diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 8a0ce26..e829c93 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -5,14 +5,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_config.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:provider/provider.dart'; @@ -25,7 +30,7 @@ class LabOrdersPage extends StatefulWidget { class _LabOrdersPageState extends State { late LabViewModel labProvider; - + List?> labSuggestions = []; int? expandedIndex; @override @@ -57,7 +62,17 @@ class _LabOrdersPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.labResults.tr(context: context).toText24(isBold: true), - Utils.buildSvgWithAssets(icon: AppAssets.search_icon), + Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(() { + if(model.isLabOrdersLoading){ + return; + } else { + labSuggestions = getLabSuggestions(model); + showCommonBottomSheet(context, child: SearchLabResultsContent(), + callBackFunc: () {}, + title: LocaleKeys.searchLabReport.tr(), + height: ResponsiveExtension.screenHeight, + isCloseButtonVisible: true); + } }), ], ), SizedBox(height: 16.h), @@ -262,4 +277,13 @@ class _LabOrdersPageState extends State { return ""; } } + getLabSuggestions(LabViewModel model) { + if(model.patientLabOrders.isEmpty){ + return []; + } + return model.patientLabOrders.map((m) => m.testDetails).toList(); + + + } + } diff --git a/lib/presentation/lab/search_lab_report.dart b/lib/presentation/lab/search_lab_report.dart new file mode 100644 index 0000000..1c05174 --- /dev/null +++ b/lib/presentation/lab/search_lab_report.dart @@ -0,0 +1,116 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:sizer/sizer.dart'; + +class SearchLabResultsContent extends StatelessWidget { + const SearchLabResultsContent({super.key}); + + final List _chipLabels = const [ + "Blood Test", + "X-Ray", + "MRI Scan", + "CT Scan", + "Ultrasound", + "Urine Test", + "Allergy Test", + "Cholesterol Test", + "Diabetes Test", + "Thyroid Test", + ]; + + @override + Widget build(BuildContext context) { + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextInputWidget( + labelText:"Search lab results", + hintText: "Type test name", + controller: TextEditingController(), + isEnable: true, + prefix: null, + autoFocus: true, + isBorderAllowed: false, + padding: EdgeInsets.symmetric(vertical:ResponsiveExtension(10).h, horizontal: ResponsiveExtension(15).h), + + ), + SizedBox(height: ResponsiveExtension(20).h), + "Suggestions".toText16(isBold: true), + const SizedBox(height: 12), + ], + ), + ), + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Wrap( + alignment: WrapAlignment.start, + spacing: 10, + runSpacing: 10, + children: _chipLabels + .map((label) => SuggestionChip( + label: label, + onTap: () {}, + )) + .toList(), + ), + ), + ), + Container( + color: Colors.white, + padding: EdgeInsets.all(ResponsiveExtension(20).h), + child: CustomButton( + text: LocaleKeys.search.tr(), + icon: AppAssets.search_icon, + iconColor: Colors.white, + onPressed: () => Navigator.pop(context), + ), + ), + ], + ); + } +} + +class SuggestionChip extends StatelessWidget { + final String label; + final bool isSelected; + final VoidCallback? onTap; + + const SuggestionChip({ + super.key, + required this.label, + this.isSelected = false, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, // optional tap callback + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + decoration: BoxDecoration( + color: isSelected ? AppColors.primaryRedColor : AppColors.whiteColor, + borderRadius: BorderRadius.circular(8), + ), + child: label.toText12( + color: isSelected ? Colors.white : Colors.black87, + fontWeight: FontWeight.w500, + ), + ), + ); + } +} diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 703e0b7..36e4892 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -1,21 +1,29 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:provider/provider.dart'; class MedicalFilePage extends StatelessWidget { - const MedicalFilePage({super.key}); + MedicalFilePage({super.key}); + + late InsuranceViewModel insuranceViewModel; @override Widget build(BuildContext context) { + insuranceViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, appBar: AppBar( @@ -42,7 +50,6 @@ class MedicalFilePage extends StatelessWidget { isAllowLeadingIcon: true, padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), leadingIcon: AppAssets.student_card, - hasError: true, ), SizedBox(height: 16.h), Container( @@ -175,101 +182,13 @@ class MedicalFilePage extends StatelessWidget { ), SizedBox(height: 16.h), //Insurance Tab Data - Container( - // height: 150.h, - width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Haroon Amjad".toText18(isBold: true), - "Policy: 223123345".toText12(isBold: true, color: AppColors.lightGrayColor), - ], - ), - CustomButton( - icon: AppAssets.cross_circle, - iconColor: AppColors.primaryRedColor, - iconSize: 13.h, - text: "Insurance Expired", - onPressed: () {}, - backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - borderColor: AppColors.primaryRedColor.withOpacity(0.0), - textColor: AppColors.primaryRedColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - SizedBox(height: 12.h), - "NCCI".toText12(isBold: true), - "NC_Dr Sulaiman Al Habib Medical Group".toText12(isBold: true), - SizedBox(height: 8.h), - Row( - children: [ - CustomButton( - icon: AppAssets.cross_circle, - iconColor: AppColors.primaryRedColor, - iconSize: 13.h, - text: "Expiry: 18 Mar, 2025", - onPressed: () {}, - backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - borderColor: AppColors.primaryRedColor.withOpacity(0.0), - textColor: AppColors.primaryRedColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - SizedBox(width: 5.h), - CustomButton( - text: "Patient Card ID: 3628599", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.normal, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - SizedBox(height: 10.h), - CustomButton( - icon: AppAssets.cross_circle, - iconColor: AppColors.primaryRedColor, - iconSize: 13.h, - text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", - onPressed: () {}, - backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), - borderColor: AppColors.bgGreenColor.withOpacity(0.0), - textColor: AppColors.bgGreenColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - ], - ), - ), - ), + Consumer(builder: (context, insuranceVM, child) { + return insuranceVM.isInsuranceLoading + ? const MoviesShimmerWidget() + : PatientInsuranceCard( + insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, + isInsuranceExpired: DateTime.now().isBefore(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))); + }), SizedBox(height: 10.h), GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13), diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 61938b0..5cb9ced 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -9,7 +9,6 @@ class AppColors { static const buttonColor = Color(0xFF6A46F5); static const splashBgColor = Color(0xFF3C355D); - static const blackColor = Color(0xFF000000); static const lightGray = Color(0xFFF4F5F7); static const lightPurple = Color(0xFFB7A3E6); static const scaffoldBgColor = Color(0xFFF8F8F8); @@ -33,15 +32,23 @@ class AppColors { static const Color borderOnlyColor = Color(0xFF2E3039); static const Color dividerColor = Color(0xFFD2D2D2); static const Color warningColorYellow = Color(0xFFF4A308); + static const Color blackBgColor = Color(0xFF2E3039); + static const blackColor = textColor; + static const inputLabelTextColor = Color(0xff898A8D); + static const greyTextColor = Color(0xFF8F9AA3); + + + static const lightGreenColor = Color(0xFF0ccedde); + static const textGreenColor = Color(0xFF18C273); static const Color ratingColorYellow = Color(0xFFFFAF15); //Chips -static const Color successColor = Color(0xff18C273); -static const Color errorColor = Color(0xFFED1C2B); -static const Color alertColor = Color(0xFFD48D05); -static const Color infoColor = Color(0xFF0B85F7); -static const Color warningColor = Color(0xFFFFCC00); -static const Color greyColor = Color(0xFFEFEFF0); + static const Color successColor = Color(0xff18C273); + static const Color errorColor = Color(0xFFED1C2B); + static const Color alertColor = Color(0xFFD48D05); + static const Color infoColor = Color(0xFF0B85F7); + static const Color warningColor = Color(0xFFFFCC00); + static const Color greyColor = Color(0xFFEFEFF0); static const Color successLightColor = Color(0xFF18C273); static const Color errorLightColor = Color(0xFFED1C2B); diff --git a/lib/widgets/appbar/app_bar_widget.dart b/lib/widgets/appbar/app_bar_widget.dart index dde02ed..4bc08a9 100644 --- a/lib/widgets/appbar/app_bar_widget.dart +++ b/lib/widgets/appbar/app_bar_widget.dart @@ -1,7 +1,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/widgets/language_switcher.dart'; import '../../generated/locale_keys.g.dart'; @@ -9,11 +11,13 @@ import '../../generated/locale_keys.g.dart'; class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { final VoidCallback onBackPressed; final ValueChanged onLanguageChanged; + bool hideLogoAndLang; - const CustomAppBar({ + CustomAppBar({ Key? key, required this.onBackPressed, required this.onLanguageChanged, + this.hideLogoAndLang = false, }) : super(key: key); @override @@ -24,45 +28,45 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { return AppBar( backgroundColor: Colors.transparent, leading: null, - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // Arrow Back with click handler - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: GestureDetector( - onTap: onBackPressed, - child: Utils.buildSvgWithAssets( - icon: AppAssets.arrow_back, - width: 32, - height: 32, + automaticallyImplyLeading: false, + title: Padding( + padding: EdgeInsets.symmetric(horizontal: 10.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Align( + alignment: Alignment.centerLeft, + child: GestureDetector( + onTap: onBackPressed, + child: Utils.buildSvgWithAssets(icon: AppAssets.arrow_back, width: 32.h, height: 32.h), ), ), ), - ), - // Logo - Utils.buildSvgWithAssets( - icon: AppAssets.habiblogo, - ), + // Logo + if (!hideLogoAndLang) + Utils.buildSvgWithAssets( + icon: AppAssets.habiblogo, + ), - // Language Selector - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: LanguageSelector( - currentLanguage: context.locale.languageCode, - showOnlyIcon: false, - onLanguageChanged: onLanguageChanged, - languages: [ - {'code': 'ar', 'name': LocaleKeys.arabic.tr()}, - {'code': 'en', 'name': LocaleKeys.english.tr()} - ], + if (!hideLogoAndLang) + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: LanguageSelector( + currentLanguage: context.locale.languageCode, + showOnlyIcon: false, + onLanguageChanged: onLanguageChanged, + languages: [ + {'code': 'ar', 'name': LocaleKeys.arabic.tr()}, + {'code': 'en', 'name': LocaleKeys.english.tr()} + ], + ), + ), ), - ), - ), - ], + ], + ), ), centerTitle: true, ); diff --git a/lib/widgets/bottomsheet/exception_bottom_sheet.dart b/lib/widgets/bottomsheet/exception_bottom_sheet.dart new file mode 100644 index 0000000..f0ea651 --- /dev/null +++ b/lib/widgets/bottomsheet/exception_bottom_sheet.dart @@ -0,0 +1,111 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; + +class ExceptionBottomSheet extends StatelessWidget { + String message; + bool showOKButton; + bool showCancel; + Function() onOkPressed; + Function()? onCancelPressed; + + ExceptionBottomSheet({Key? key, required this.message, this.showOKButton = true, this.showCancel = false, required this.onOkPressed, this.onCancelPressed}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SafeArea( + bottom: Platform.isIOS ? false : true, // Adjust for iOS to avoid bottom padding + child: GestureDetector( + onTap: () { + FocusScope.of(context).unfocus(); // Dismiss the keyboard when tapping outside + }, + child: Builder(builder: (context) { + return Directionality( + textDirection: Directionality.of(context), + child: Container( + padding: EdgeInsets.all(24.h), + decoration: BoxDecoration( + color: Color(0xFFF8F8FA), + borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.notice.tr().toText28(), + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: Utils.buildSvgWithAssets(icon: AppAssets.cross_circle), + ) + ], + ), + SizedBox(height: 10.h), + (message ?? "").toText16(isBold: false, color: AppColors.textColor), + SizedBox(height: 10.h), + SizedBox(height: 24.h), + if (showOKButton && showCancel) + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(), + onPressed: onCancelPressed != null + ? onCancelPressed! + : () { + Navigator.of(context).pop(); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + icon: AppAssets.cancel, + iconColor: AppColors.primaryRedColor, + ), + ), + SizedBox(width: 5.h), + Expanded( + child: CustomButton( + text: showCancel ? LocaleKeys.confirm.tr() : LocaleKeys.ok.tr(), + onPressed: onOkPressed, + backgroundColor: AppColors.bgGreenColor, + borderColor: AppColors.bgGreenColor, + textColor: Colors.white, + icon: AppAssets.confirm, + ), + ), + ], + ), + if (showOKButton && !showCancel) + Padding( + padding: EdgeInsets.only(bottom: 10.h), + child: CustomButton( + text: LocaleKeys.ok.tr(), + onPressed: onOkPressed, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedBorderColor, + textColor: Colors.white, + icon: AppAssets.confirm, + ), + ), + ], + ), + ), + ); + }), + ), + ); + } +} diff --git a/lib/widgets/bottomsheet/generic_bottom_sheet.dart b/lib/widgets/bottomsheet/generic_bottom_sheet.dart new file mode 100644 index 0000000..08bf849 --- /dev/null +++ b/lib/widgets/bottomsheet/generic_bottom_sheet.dart @@ -0,0 +1,148 @@ +import 'dart:io'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; + +class GenericBottomSheet extends StatefulWidget { + String? countryCode; + String? initialPhoneNumber; + final List buttons; + TextEditingController? textController; + final bool isForEmail; + Function(CountryEnum)? onCountryChange; + final bool isEnableCountryDropdown; + final bool isFromSavedLogin; + Function(String?)? onChange; + + // FocusNode myFocusNode; + + GenericBottomSheet({ + this.countryCode = "", + this.initialPhoneNumber = "", + required this.buttons, + this.textController, + this.isForEmail = false, + this.onCountryChange, + this.isEnableCountryDropdown = false, + this.isFromSavedLogin = false, + this.onChange, + // required this.myFocusNode + }); + + @override + _GenericBottomSheetState createState() => _GenericBottomSheetState(); +} + +class _GenericBottomSheetState extends State { + @override + void initState() { + super.initState(); + + if (!widget.isForEmail && widget.textController != null) { + widget.textController!.text = widget.initialPhoneNumber!; + } + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + top: false, + bottom: Platform.isIOS ? false : true, + child: GestureDetector( + onTap: () { + FocusScope.of(context).unfocus(); + }, + child: Directionality( + textDirection: Directionality.of(context), + child: Container( + padding: EdgeInsets.all(24.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.bgScaffoldColor, borderRadius: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Title + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: widget.isFromSavedLogin + ? LocaleKeys.receiveOtpToast.tr().toText24() + : widget.isForEmail + ? LocaleKeys.enterEmail.tr().toText24() + : LocaleKeys.enterPhoneNumber.tr().toText24()), + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: Padding( + padding: EdgeInsets.only(top: 10.h), + child: Utils.buildSvgWithAssets(icon: AppAssets.cross_circle), + ), + ), + ], + ), + SizedBox(height: 8.h), + // Subtitle + widget.isFromSavedLogin + ? LocaleKeys.pleaseChooseOption.tr().toText16() + : widget.isForEmail + ? LocaleKeys.enterEmailDesc.tr().toText16() + : LocaleKeys.enterPhoneDesc.tr().toText16(), + + if (widget.isFromSavedLogin) + ...[] + else ...[ + widget.textController != null + ? TextInputWidget( + labelText: widget.isForEmail ? LocaleKeys.email : LocaleKeys.phoneNumber, + hintText: widget.isForEmail ? "demo@gmail.com" : "5xxxxxxxx", + controller: widget.textController!, + padding: EdgeInsets.all(8.h), + keyboardType: widget.isForEmail ? TextInputType.emailAddress : TextInputType.number, + onChange: (value) { + if (widget.onChange != null) { + widget.onChange!(value); + } + }, + onCountryChange: (value) { + if (widget.onCountryChange != null) { + widget.onCountryChange!(value); + } + }, + isEnable: true, + isReadOnly: widget.isFromSavedLogin, + prefix: widget.isForEmail ? null : widget.countryCode, + isBorderAllowed: false, + isAllowLeadingIcon: true, + isCountryDropDown: widget.isEnableCountryDropdown, + leadingIcon: widget.isForEmail ? AppAssets.email : AppAssets.smart_phone, + ) + : SizedBox(), + ], + + SizedBox(height: 24.h), + ...widget.buttons, + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart new file mode 100644 index 0000000..f6039eb --- /dev/null +++ b/lib/widgets/common_bottom_sheet.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +void showCommonBottomSheet(BuildContext context, + {required Widget child, required VoidCallback callBackFunc, String? title, required double height, bool isCloseButtonVisible = true, bool isFullScreen = true}) { + showModalBottomSheet( + sheetAnimationStyle: AnimationStyle( + duration: Duration(milliseconds: 500), // Custom animation duration + reverseDuration: Duration(milliseconds: 300), // Custom reverse animation duration + ), + context: context, + isScrollControlled: true, + showDragHandle: false, + backgroundColor: AppColors.scaffoldBgColor, + builder: (BuildContext context) { + return Container( + height: height, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.scaffoldBgColor, borderRadius: 24.h), + child: ButtonSheetContent( + title: title!, + isCloseButtonVisible: isCloseButtonVisible, + isFullScreen: isFullScreen, + child: child, + ), + ); + }).then((value) { + callBackFunc(); + }); +} + +class ButtonSheetContent extends StatelessWidget { + final Widget child; + final String title; + final bool isCloseButtonVisible; + final bool isFullScreen; + + const ButtonSheetContent({super.key, required this.child, required this.isCloseButtonVisible, required this.title, required this.isFullScreen}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // SizedBox( + // height: 20.h, + // ), + // Center( + // child: Container( + // margin: const EdgeInsets.only(top: 18, bottom: 12), + // height: 4, + // width: 40.h, + // decoration: BoxDecoration( + // color: Colors.grey[400], + // borderRadius: BorderRadius.circular(2), + // ), + // ), + // ), + + // Close button + isCloseButtonVisible && isFullScreen + ? Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Utils.buildSvgWithAssets(icon: AppAssets.closeBottomNav, width: 32, height: 32).onPress(() { + Navigator.of(context).pop(); + }), + ) + : SizedBox(), + + isFullScreen + ? Column( + children: [ + SizedBox(height: 20.h), + Padding(padding: EdgeInsets.symmetric(horizontal: 16.h), child: title.toText24(isBold: true)), + SizedBox(height: 16.h), + ], + ) + : SizedBox(), + + Expanded(child: child) + ], + ); + } +} diff --git a/lib/widgets/dropdown/country_dropdown_widget.dart b/lib/widgets/dropdown/country_dropdown_widget.dart new file mode 100644 index 0000000..3d28e23 --- /dev/null +++ b/lib/widgets/dropdown/country_dropdown_widget.dart @@ -0,0 +1,210 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; + +class CustomCountryDropdown extends StatefulWidget { + final List countryList; + final Function(CountryEnum)? onCountryChange; + final Function(String)? onPhoneNumberChanged; + final bool isRtl; + final bool isFromBottomSheet; + final bool isEnableTextField; + Widget? textField; + + CustomCountryDropdown({ + Key? key, + required this.countryList, + this.onCountryChange, + this.onPhoneNumberChanged, + required this.isRtl, + this.isFromBottomSheet = false, + this.isEnableTextField = false, + this.textField, + }) : super(key: key); + + @override + _CustomCountryDropdownState createState() => _CustomCountryDropdownState(); +} + +class _CustomCountryDropdownState extends State { + CountryEnum? selectedCountry; + late OverlayEntry _overlayEntry; + bool _isDropdownOpen = false; + FocusNode textFocusNode = new FocusNode(); + + @override + void initState() { + super.initState(); + selectedCountry = CountryEnum.saudiArabia; + + if (widget.isEnableTextField && widget.isFromBottomSheet) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted && textFocusNode.canRequestFocus) { + FocusScope.of(context).requestFocus(textFocusNode); + } + }); + } + } + + @override + void dispose() { + textFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + height: 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 10.h), + child: Row( + children: [ + GestureDetector( + onTap: () { + if (_isDropdownOpen) { + _closeDropdown(); + } else { + _openDropdown(); + } + }, + child: Utils.buildSvgWithAssets(icon: selectedCountry != null ? selectedCountry!.iconPath : AppAssets.ksa, width: 40.h, height: 40.h)), + SizedBox(width: 8.h), + Utils.buildSvgWithAssets(icon: _isDropdownOpen ? AppAssets.dropdow_icon : AppAssets.dropdow_icon), + SizedBox(width: 4.h), + if (widget.isFromBottomSheet) + GestureDetector( + onTap: () { + if (widget.isEnableTextField && textFocusNode.canRequestFocus) { + FocusScope.of(context).requestFocus(textFocusNode); + } + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + Text( + LocaleKeys.phoneNumber.tr(), + style: TextStyle(fontSize: 12.fSize, height: 21 / 12, fontWeight: FontWeight.w500, letterSpacing: -1), + ), + ], + ), + Row( + children: [ + Text( + selectedCountry!.countryCode, + style: TextStyle(fontSize: 12.fSize, height: 21 / 18, fontWeight: FontWeight.w600, letterSpacing: -0.2), + ), + SizedBox(width: 4.h), + if (widget.isEnableTextField) + SizedBox( + height: 20, + width: 200, + child: TextField( + focusNode: textFocusNode, + decoration: InputDecoration(hintText: "", isDense: true, border: InputBorder.none), + keyboardType: TextInputType.phone, + onChanged: widget.onPhoneNumberChanged), + ), + ], + ) + ], + ), + ), + if (!widget.isFromBottomSheet) + Text( + selectedCountry != null ? selectedCountry!.displayName : "Select Country", + style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, letterSpacing: -0.2), + ), + ], + ), + ); + } + + void _openDropdown() { + if (textFocusNode.hasFocus) { + textFocusNode.unfocus(); + } + + RenderBox renderBox = context.findRenderObject() as RenderBox; + Offset offset = renderBox.localToGlobal(Offset.zero); + + _overlayEntry = OverlayEntry( + builder: (context) => Stack( + children: [ + // Dismiss dropdown when tapping outside + Positioned.fill( + child: GestureDetector( + onTap: _closeDropdown, + behavior: HitTestBehavior.translucent, + child: Container(), + ), + ), + Positioned( + top: offset.dy + renderBox.size.height, + left: widget.isRtl ? offset.dx + 6.h : offset.dx - 6.h, + width: renderBox.size.width, + child: Material( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: Colors.white, borderRadius: 12), + child: Column( + children: widget.countryList + .map( + (country) => GestureDetector( + onTap: () { + setState(() { + selectedCountry = country; + }); + widget.onCountryChange?.call(country); + _closeDropdown(); + }, + child: Container( + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 16.h), + child: Row( + children: [ + Utils.buildSvgWithAssets(icon: country.iconPath, width: 38.h, height: 38.h), + if (!widget.isFromBottomSheet) SizedBox(width: 12.h), + if (!widget.isFromBottomSheet) Text(country.displayName, style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, letterSpacing: -0.2)), + ], + ), + ), + ), + ) + .toList(), + ), + ), + ), + ), + ], + ), + ); + + Overlay.of(context)?.insert(_overlayEntry); + setState(() { + _isDropdownOpen = true; + }); + } + + void _closeDropdown() { + _overlayEntry.remove(); + setState(() { + _isDropdownOpen = false; + }); + + if (widget.isEnableTextField && widget.isFromBottomSheet) { + Future.delayed(Duration(milliseconds: 100), () { + if (mounted && textFocusNode.canRequestFocus) { + FocusScope.of(context).requestFocus(textFocusNode); + } + }); + } + } +} diff --git a/lib/widgets/dropdown/dropdown_widget.dart b/lib/widgets/dropdown/dropdown_widget.dart new file mode 100644 index 0000000..2a0cad3 --- /dev/null +++ b/lib/widgets/dropdown/dropdown_widget.dart @@ -0,0 +1,154 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart' show Icons, PopupMenuItem, showMenu, Colors; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class DropdownWidget extends StatelessWidget { + final String labelText; + final String hintText; + final List dropdownItems; + final String? selectedValue; + final Function(String?)? onChange; + final bool isEnable; + final bool isBorderAllowed; + final bool isAllowRadius; + final EdgeInsetsGeometry? padding; + final bool hasSelectionCustomIcon; + final String? selectionCustomIcon; + final String? leadingIcon; + + const DropdownWidget({ + Key? key, + required this.labelText, + required this.hintText, + required this.dropdownItems, + this.selectedValue, + this.onChange, + this.isEnable = true, + this.isBorderAllowed = true, + this.isAllowRadius = true, + this.padding, + this.hasSelectionCustomIcon = false, + this.selectionCustomIcon, + this.leadingIcon, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + Widget content = Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [_buildLabelText(), _buildDropdown(context)], + ); + + return Container( + padding: padding, + alignment: Alignment.center, // This might need adjustment based on layout + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Colors.white, + borderRadius: isAllowRadius ? 15.h : null, + side: isBorderAllowed ? BorderSide(color: const Color(0xffefefef), width: 1) : null, + ), + child: Row( + // Wrap with a Row + crossAxisAlignment: CrossAxisAlignment.center, // Align items vertically in the center + children: [ + if (leadingIcon != null) ...[ + _buildLeadingIcon(), + SizedBox(width: 3.h), + ], + Expanded(child: content), + ], + ), + ); + } + + Widget _buildLeadingIcon() { + return Container( + height: 40.h, + width: 40.h, + margin: EdgeInsets.only(right: 10.h), + padding: EdgeInsets.all(8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 10.h, color: AppColors.greyColor), + child: Utils.buildSvgWithAssets(icon: leadingIcon!)); + } + + Widget _buildLabelText() { + return Text( + labelText, + style: TextStyle( + fontSize: 12.fSize, + fontWeight: FontWeight.w500, + color: Color(0xff898A8D), + letterSpacing: -0.2, + height: 18 / 12, + ), + ); + } + + Widget _buildDropdown(BuildContext context) { + return GestureDetector( + onTap: isEnable + ? () async { + final renderBox = context.findRenderObject() as RenderBox; + final offset = renderBox.localToGlobal(Offset.zero); + final selected = await showMenu( + context: context, + position: RelativeRect.fromLTRB( + offset.dx, + offset.dy + renderBox.size.height, + offset.dx + renderBox.size.width, + 0, + ), + items: dropdownItems + .map( + (e) => PopupMenuItem( + value: e, + child: Text( + e, + style: TextStyle( + fontSize: 14.fSize, + height: 21 / 14, + fontWeight: FontWeight.w500, + letterSpacing: -0.2, + ), + ), + ), + ) + .toList(), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ); + + if (selected != null && onChange != null) { + onChange!(selected); + } + } + : null, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Text( + (selectedValue == null || selectedValue!.isEmpty) ? hintText : selectedValue!, + textAlign: TextAlign.left, + textDirection: TextDirection.ltr, + style: TextStyle( + fontSize: 14.fSize, + height: 21 / 14, + fontWeight: FontWeight.w500, + color: (selectedValue != null && selectedValue!.isNotEmpty) ? const Color(0xff2E3039) : const Color(0xffB0B0B0), + letterSpacing: -0.2, + ), + ), + ), + if (hasSelectionCustomIcon && selectionCustomIcon != null) Utils.buildSvgWithAssets(icon: selectionCustomIcon!) else const Icon(Icons.keyboard_arrow_down_outlined), + ], + ), + ); + } +} diff --git a/lib/widgets/dropdown_widget.dart b/lib/widgets/dropdown_widget.dart deleted file mode 100644 index a778f2e..0000000 --- a/lib/widgets/dropdown_widget.dart +++ /dev/null @@ -1,132 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart' show Icons, PopupMenuItem, showMenu, Colors; -import 'package:hmg_patient_app_new/core/utils/utils.dart'; -import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; - -class DropdownWidget extends StatelessWidget { - final String labelText; - final String hintText; - final List dropdownItems; - final String? selectedValue; - final Function(String?)? onChange; - final bool isEnable; - final bool isBorderAllowed; - final bool isAllowRadius; - final EdgeInsetsGeometry? padding; - final bool hasSelectionCustomIcon; - final String? selectionCustomIcon; - - const DropdownWidget({ - Key? key, - required this.labelText, - required this.hintText, - required this.dropdownItems, - this.selectedValue, - this.onChange, - this.isEnable = true, - this.isBorderAllowed = true, - this.isAllowRadius = true, - this.padding, - this.hasSelectionCustomIcon = false, - this.selectionCustomIcon, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - padding: padding, - alignment: Alignment.center, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: Colors.white, - borderRadius: isAllowRadius ? 15 : null, - side: isBorderAllowed - ? BorderSide(color: const Color(0xffefefef), width: 1) - : null, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildLabelText(), - _buildDropdown(context), - ], - ), - ); - } - - Widget _buildLabelText() { - return Text( - labelText, - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Color(0xff898A8D), - letterSpacing: -0.2, - height: 18 / 12, - ), - ); - } - - Widget _buildDropdown(BuildContext context) { - return GestureDetector( - onTap: isEnable - ? () async { - final renderBox = context.findRenderObject() as RenderBox; - final offset = renderBox.localToGlobal(Offset.zero); - final selected = await showMenu( - context: context, - position: RelativeRect.fromLTRB( - offset.dx, - offset.dy + renderBox.size.height, - offset.dx + renderBox.size.width, - 0, - ), - items: dropdownItems - .map( - (e) => PopupMenuItem( - value: e, - child: Text(e), - ), - ) - .toList(), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ); - - if (selected != null && onChange != null) { - onChange!(selected); - } - } - : null, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: Text( - (selectedValue == null || selectedValue!.isEmpty) - ? hintText - : selectedValue!, - textAlign: TextAlign.left, - textDirection: TextDirection.ltr, - style: TextStyle( - fontSize: 14, - height: 21 / 14, - fontWeight: FontWeight.w500, - color: (selectedValue != null && selectedValue!.isNotEmpty) - ? const Color(0xff2E3039) - : const Color(0xffB0B0B0), - letterSpacing: -0.2, - ), - ), - ), - if (hasSelectionCustomIcon && selectionCustomIcon != null) - Utils.buildSvgWithAssets(icon: selectionCustomIcon!) - else - const Icon(Icons.keyboard_arrow_down_outlined), - ], - ), - ); - } -} \ No newline at end of file diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index f2446a7..0f520c6 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -1,6 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart'; + +import '../core/dependencies.dart'; // TODO: Import AppColors if bgRedColor is defined there // import 'package:hmg_patient_app_new/core/ui_utils/app_colors.dart'; @@ -24,11 +33,13 @@ class TextInputWidget extends StatelessWidget { final bool isCountryDropDown; final bool hasError; final String? errorMessage; + Function(CountryEnum)? onCountryChange; + SelectionTypeEnum? selectionType; // final List countryList; // final Function(Country)? onCountryChange; - const TextInputWidget({ + TextInputWidget({ Key? key, required this.labelText, required this.hintText, @@ -48,15 +59,15 @@ class TextInputWidget extends StatelessWidget { this.isCountryDropDown = false, this.hasError = false, this.errorMessage, + this.onCountryChange, + this.selectionType, // this.countryList = const [], // this.onCountryChange, }) : super(key: key); @override Widget build(BuildContext context) { - // Assuming AppColors.bgRedColor exists, otherwise using Colors.red - final errorColor = Colors.red; // Replace with AppColors.bgRedColor if available - + final errorColor = AppColors.primaryRedColor; return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -66,34 +77,45 @@ class TextInputWidget extends StatelessWidget { alignment: Alignment.center, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: Colors.white, - borderRadius: isAllowRadius ? 15 : null, + borderRadius: isAllowRadius ? 12 : null, side: isBorderAllowed ? BorderSide(color: hasError ? errorColor : const Color(0xffefefef), width: 1) : null, ), child: Row( textDirection: Directionality.of(context), children: [ - if (isAllowLeadingIcon && leadingIcon != null) _buildLeadingIcon(context), - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildLabelText(), - _buildTextField(context), - ], - ), - ), + if (isAllowLeadingIcon && leadingIcon != null && !isCountryDropDown) _buildLeadingIcon(context), + isCountryDropDown + ? CustomCountryDropdown( + countryList: CountryEnum.values, + onCountryChange: onCountryChange, + isRtl: Directionality.of(context) == TextDirection.rtl, + isFromBottomSheet: isCountryDropDown, + isEnableTextField: true, + onPhoneNumberChanged: onChange, + // textField: _buildTextField(context), + ) + : Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildLabelText(), + _buildTextField(context), + ], + ), + ), + if (selectionType == SelectionTypeEnum.calendar) _buildTrailingIcon(context), ], ), ), if (hasError && errorMessage != null) Padding( - padding: const EdgeInsets.only(top: 4.0, left: 12.0), // Adjust padding as needed + padding: EdgeInsets.only(top: 4.h, left: 12.h), // Adjust padding as needed child: Text( errorMessage!, style: TextStyle( color: errorColor, - fontSize: 12, + fontSize: 12.fSize, ), ), ), @@ -103,21 +125,56 @@ class TextInputWidget extends StatelessWidget { Widget _buildLeadingIcon(BuildContext context) { return Container( - height: 40, - width: 40, - margin: const EdgeInsets.only(right: 10), - padding: const EdgeInsets.all(8), - decoration: const BoxDecoration(color: Color(0xFFEFEFF0), borderRadius: BorderRadius.all(Radius.circular(10))), + height: 40.h, + width: 40.h, + margin: EdgeInsets.only(right: 10.h), + padding: EdgeInsets.all(8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + borderRadius: 10.h, + color: AppColors.greyColor, + ), child: Utils.buildSvgWithAssets(icon: leadingIcon!)); } + Widget _buildTrailingIcon(BuildContext context) { + final AppState appState = getIt.get(); + return Container( + height: 40.h, + width: 40.h, + margin: EdgeInsets.zero, + padding: EdgeInsets.all(8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 10.h, color: AppColors.whiteColor), + child: GestureDetector( + onTap: () async { + bool isGregorian = true; + final picked = await showHijriGregBottomSheet(context, + switcherIcon: Utils.buildSvgWithAssets(icon: AppAssets.language, width: 24.h, height: 24.h), + language: appState.getLanguageCode()!, + initialDate: DateTime.now(), + okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)), + cancelWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)), + onCalendarTypeChanged: (bool value) { + isGregorian = value; + }); + if (picked != null && onChange != null) { + // if (onCalendarTypeChanged != null) { + // onCalendarTypeChanged.call(isGregorian); + // } + onChange!(picked.toIso8601String()); + } + }, + child: Utils.buildSvgWithAssets(icon: AppAssets.calendar), + ), + ); + } + Widget _buildLabelText() { return Text( labelText, - style: const TextStyle( - fontSize: 12, + style: TextStyle( + fontSize: 12.fSize, fontWeight: FontWeight.w500, - color: Color(0xff898A8D), + color: AppColors.inputLabelTextColor, letterSpacing: -0.2, height: 18 / 12, ), @@ -137,30 +194,18 @@ class TextInputWidget extends StatelessWidget { onChanged: onChange, focusNode: focusNode, autofocus: autoFocus, - style: const TextStyle( - fontSize: 14, - height: 21 / 14, - fontWeight: FontWeight.w500, - color: Color(0xff2E3039), - letterSpacing: -0.2, - ), + style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, color: AppColors.textColor, letterSpacing: -0.2), decoration: InputDecoration( isDense: true, hintText: hintText, - hintStyle: const TextStyle( - fontSize: 14, - height: 21 / 16, - fontWeight: FontWeight.w500, - color: Color(0xff898A8D), - letterSpacing: -0.2, - ), - prefixIconConstraints: const BoxConstraints(minWidth: 45), + hintStyle: TextStyle(fontSize: 14.fSize, height: 21 / 16, fontWeight: FontWeight.w500, color: Color(0xff898A8D), letterSpacing: -0.2), + prefixIconConstraints: BoxConstraints(minWidth: 45.h), prefixIcon: prefix == null ? null : Text( "+" + prefix!, - style: const TextStyle( - fontSize: 14, + style: TextStyle( + fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, color: Color(0xff2E303A), diff --git a/lib/widgets/language_switcher.dart b/lib/widgets/language_switcher.dart index 4c621b7..4a81217 100644 --- a/lib/widgets/language_switcher.dart +++ b/lib/widgets/language_switcher.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -51,18 +52,18 @@ class _LanguageSelectorState extends State { widget.onLanguageChanged(newLanguage); }, child: Container( - padding: EdgeInsets.all(8), + padding: EdgeInsets.all(8.h), decoration: BoxDecoration(borderRadius: BorderRadius.circular(12)), child: Row( mainAxisSize: MainAxisSize.min, children: [ Utils.buildSvgWithAssets(icon: AppAssets.language), - const SizedBox(width: 6), + SizedBox(width: 6.h), Text( currentLangData['name']?.toUpperCase() ?? 'EN', style: context.dynamicTextStyle( fontWeight: FontWeight.w500, - fontSize: 14, + fontSize: 14.fSize, color: AppColors.primaryRedColor, letterSpacing: 0.1, isLanguageSwitcher: true, diff --git a/lib/widgets/otp/otp.dart b/lib/widgets/otp/otp.dart new file mode 100644 index 0000000..81c375b --- /dev/null +++ b/lib/widgets/otp/otp.dart @@ -0,0 +1,230 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/presentation/authentication/register_step2.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart'; + +class OTPVerificationPage extends StatefulWidget { + final String phoneNumber; + + const OTPVerificationPage({Key? key, required this.phoneNumber}) : super(key: key); + + @override + State createState() => _OTPVerificationPageState(); +} + +class _OTPVerificationPageState extends State { + final int _otpLength = 4; + late final List _controllers; + late final List _focusNodes; + + Timer? _resendTimer; + int _resendTime = 60; + bool _isOtpComplete = false; + + @override + void initState() { + super.initState(); + _controllers = List.generate(_otpLength, (_) => TextEditingController()); + _focusNodes = List.generate(_otpLength, (_) => FocusNode()); + _startResendTimer(); + + // Focus the first field once the screen is built + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_focusNodes.isNotEmpty) { + FocusScope.of(context).requestFocus(_focusNodes[0]); + } + }); + } + + @override + void dispose() { + for (final c in _controllers) c.dispose(); + for (final f in _focusNodes) f.dispose(); + _resendTimer?.cancel(); + super.dispose(); + } + + void _startResendTimer() { + _resendTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + if (_resendTime > 0) { + setState(() => _resendTime--); + } else { + timer.cancel(); + } + }); + } + + void _onOtpChanged(int index, String value) { + if (value.length == 1 && index < _otpLength - 1) { + _focusNodes[index + 1].requestFocus(); + } else if (value.isEmpty && index > 0) { + _focusNodes[index - 1].requestFocus(); + } + _checkOtpCompletion(); + } + + void _checkOtpCompletion() { + final isComplete = _controllers.every((c) => c.text.isNotEmpty); + + if (isComplete != _isOtpComplete) { + setState(() => _isOtpComplete = isComplete); + + if (isComplete) { + _verifyOtp(); + } + } + } + + void _resendOtp() { + if (_resendTime == 0) { + setState(() => _resendTime = 60); + _startResendTimer(); + autoFillOtp("1234"); + + // call resend API here + } + } + + String _getMaskedPhoneNumber() { + final phone = widget.phoneNumber; + return phone.length > 4 ? '05xxxxxx${phone.substring(phone.length - 2)}' : phone; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar( + hideLogoAndLang: true, + onBackPressed: () { + Navigator.of(context).pop(); + }, + onLanguageChanged: (lang) {}, + ), + body: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 40.h), + Text( + 'OTP Verification', + style: TextStyle(fontSize: 24.fSize, fontWeight: FontWeight.bold), + ), + SizedBox(height: 16.h), + Text( + 'We have sent you the OTP code on ${_getMaskedPhoneNumber()} via SMS for registration verification', + style: TextStyle(fontSize: 16.fSize, color: Colors.grey), + ), + SizedBox(height: 40.h), + + // OTP Input Fields + SizedBox( + height: 100, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: List.generate(_otpLength, (index) { + return ValueListenableBuilder( + valueListenable: _controllers[index], + builder: (context, value, _) { + final hasText = value.text.isNotEmpty; + + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + width: 70.h, + margin: EdgeInsets.symmetric(horizontal: 4.h), + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration(color: _isOtpComplete ? AppColors.successColor : (hasText ? AppColors.blackBgColor : AppColors.whiteColor), borderRadius: 16), + child: Center( + child: TextField( + controller: _controllers[index], + focusNode: _focusNodes[index], + textAlign: TextAlign.center, + keyboardType: TextInputType.number, + maxLength: 1, + style: TextStyle( + fontSize: 40.fSize, + fontWeight: FontWeight.bold, + color: AppColors.whiteColor, + ), + decoration: InputDecoration( + counterText: '', + filled: true, + fillColor: Colors.transparent, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(18), + borderSide: BorderSide.none, + ), + ), + onChanged: (v) => _onOtpChanged(index, v), + ), + ), + ); + }, + ); + }), + ), + ), + + const SizedBox(height: 32), + + // Resend OTP + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text("Didn't receive it? "), + if (_resendTime > 0) + Text( + 'resend in (${_resendTime.toString().padLeft(2, '0')}:00). ', + style: const TextStyle(color: Colors.grey), + ) + else + GestureDetector( + onTap: _resendOtp, + child: const Text( + 'Resend', + style: TextStyle( + color: AppColors.primaryRedColor, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ], + ), + ), + ), + ); + } + + void _verifyOtp() { + final otp = _controllers.map((c) => c.text).join(); + debugPrint('Verifying OTP: $otp'); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Verifying OTP: $otp')), + ); + + Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) => RegisterNewStep2(null, {"nationalID": "12345678654321"}))); + } + + /// Auto fill OTP into text fields + void autoFillOtp(String otp) { + if (otp.length != _otpLength) return; + + for (int i = 0; i < _otpLength; i++) { + _controllers[i].text = otp[i]; + } + + // Move focus to the last field + _focusNodes[_otpLength - 1].requestFocus(); + + // Trigger completion check and color update + _checkOtpCompletion(); + } +} diff --git a/pubspec.lock b/pubspec.lock index c503e1b..e2ebb3b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" audio_session: dependency: transitive description: @@ -742,6 +742,14 @@ packages: url: "https://pub.dev" source: hosted version: "13.1.3" + hijri_gregorian_calendar: + dependency: "direct main" + description: + name: hijri_gregorian_calendar + sha256: "9d23b52192783c1ad134b1ac001be7977342cb579c6b380647b6494fbd464d29" + url: "https://pub.dev" + source: hosted + version: "0.0.4" html: dependency: transitive description: @@ -1591,10 +1599,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" web: dependency: "direct main" description: @@ -1636,5 +1644,5 @@ packages: source: hosted version: "6.5.0" sdks: - dart: ">=3.8.0-0 <4.0.0" + dart: ">=3.8.1 <4.0.0" flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index 1263e33..cfb94d4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -68,6 +68,7 @@ dependencies: web: any flutter_staggered_animations: ^1.1.1 smooth_corner: ^1.1.1 + hijri_gregorian_calendar: ^0.0.4 dev_dependencies: flutter_test: @@ -81,6 +82,7 @@ flutter: - assets/ - assets/fonts/ - assets/langs/ + - assets/json/ - assets/images/ - assets/images/svg/ - assets/images/png/