From 0b67fb7570cfd4f327f215c1ff1671650b802757 Mon Sep 17 00:00:00 2001 From: "Aamir.Muhammad" Date: Sun, 14 Jun 2026 15:50:21 +0300 Subject: [PATCH] Offers And Discounts Apis --- lib/core/api_consts.dart | 9 +- lib/core/app_state.dart | 2 +- ...and_discounts_detailed_response_model.dart | 265 +++++++++++++ .../offers_and_discounts_response_model.dart | 237 ++++++++--- .../offers_and_discounts_repo.dart | 90 ++++- .../offers_and_discounts_view_model.dart | 368 +++--------------- .../offers_and_discounts_detailed_page.dart | 364 ++++++++++------- .../offers_and_discounts_history_page.dart | 269 ++----------- .../offers_and_discounts_page.dart | 78 ++-- 9 files changed, 880 insertions(+), 802 deletions(-) create mode 100644 lib/features/offers_and_discounts/models/offers_and_discounts_detailed_response_model.dart diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index aa51a5da..8b0571a9 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -300,6 +300,11 @@ class ApiConsts { static String googleCloudStorageENTranslationFileBaseURL = "https://storage.googleapis.com/hmg-patientapp-translations"; + // POMS Offers and Discounts API + static final pomsBaseUrl = 'https://poms-uat.odoo.com'; + static final pomsGetHospitalOffers = '$pomsBaseUrl/api/v1/mobile/hospital-offers'; + static final pomsGetHospitalOffersDetails = '$pomsBaseUrl/api/v1/mobile/hospital-offers'; + // ************ static values for Api **************** static final double appVersionID = 21.3; @@ -308,6 +313,7 @@ class ApiConsts { static final String appIpAddress = "10.20.10.20"; static final String appGeneralId = "Cs2020@2016\$2958"; static final String sessionID = 'TMRhVmkGhOsvamErw'; + static final pomsApiKey = 'sk-poms-hQPVijTuGbKr1VI8Aa8f85A6AR'; } // PACKAGES and OFFERS @@ -862,8 +868,7 @@ var GET_CUSTOMER_INFO = "VerifyCustomer"; //Pharmacy -var GET_PHARMACY_CATEGORISE = - 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id=0'; +var GET_PHARMACY_CATEGORISE = 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id=0'; var GET_OFFERS_CATEGORISE = 'discountcategories'; var GET_OFFERS_PRODUCTS = 'offerproducts/'; var GET_CATEGORISE_PARENT = 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id='; diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 3369ef49..33e9503f 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -25,7 +25,7 @@ class AppState { _loadProfileImageFromCache(); } - bool isEnabledOffersAndDiscountsCarousel = false; + bool isEnabledOffersAndDiscountsCarousel = true; double userLat = 0.0; diff --git a/lib/features/offers_and_discounts/models/offers_and_discounts_detailed_response_model.dart b/lib/features/offers_and_discounts/models/offers_and_discounts_detailed_response_model.dart new file mode 100644 index 00000000..a65df595 --- /dev/null +++ b/lib/features/offers_and_discounts/models/offers_and_discounts_detailed_response_model.dart @@ -0,0 +1,265 @@ +import 'dart:convert'; + +class OffersAndDiscountsDetailedResponseModel { + int id; + String ref; + String name; + String nameAr; + Category category; + List branches; + double listPrice; + String currency; + String imageUrl; + DateTime validFrom; + DateTime validTo; + String description; + String descriptionAr; + int vatExclusivePrice; + double vatInclusivePrice; + bool requirePatientRegistration; + Images images; + List offerLines; + + OffersAndDiscountsDetailedResponseModel({ + required this.id, + required this.ref, + required this.name, + required this.nameAr, + required this.category, + required this.branches, + required this.listPrice, + required this.currency, + required this.imageUrl, + required this.validFrom, + required this.validTo, + required this.description, + required this.descriptionAr, + required this.vatExclusivePrice, + required this.vatInclusivePrice, + required this.requirePatientRegistration, + required this.images, + required this.offerLines, + }); + + factory OffersAndDiscountsDetailedResponseModel.fromRawJson(String str) => OffersAndDiscountsDetailedResponseModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory OffersAndDiscountsDetailedResponseModel.fromJson(Map json) => OffersAndDiscountsDetailedResponseModel( + id: json["id"], + ref: json["ref"], + name: json["name"], + nameAr: json["name_ar"], + category: Category.fromJson(json["category"]), + branches: List.from(json["branches"].map((x) => Branch.fromJson(x))), + listPrice: json["list_price"]?.toDouble(), + currency: json["currency"], + imageUrl: json["image_url"], + validFrom: DateTime.parse(json["valid_from"]), + validTo: DateTime.parse(json["valid_to"]), + description: json["description"], + descriptionAr: json["description_ar"], + vatExclusivePrice: json["vat_exclusive_price"], + vatInclusivePrice: json["vat_inclusive_price"]?.toDouble(), + requirePatientRegistration: json["require_patient_registration"], + images: Images.fromJson(json["images"]), + offerLines: List.from(json["offer_lines"].map((x) => OfferLine.fromJson(x))), + ); + + Map toJson() => { + "id": id, + "ref": ref, + "name": name, + "name_ar": nameAr, + "category": category.toJson(), + "branches": List.from(branches.map((x) => x.toJson())), + "list_price": listPrice, + "currency": currency, + "image_url": imageUrl, + "valid_from": "${validFrom.year.toString().padLeft(4, '0')}-${validFrom.month.toString().padLeft(2, '0')}-${validFrom.day.toString().padLeft(2, '0')}", + "valid_to": "${validTo.year.toString().padLeft(4, '0')}-${validTo.month.toString().padLeft(2, '0')}-${validTo.day.toString().padLeft(2, '0')}", + "description": description, + "description_ar": descriptionAr, + "vat_exclusive_price": vatExclusivePrice, + "vat_inclusive_price": vatInclusivePrice, + "require_patient_registration": requirePatientRegistration, + "images": images.toJson(), + "offer_lines": List.from(offerLines.map((x) => x.toJson())), + }; +} + +class Branch { + int id; + String code; + String name; + String nameAr; + + Branch({ + required this.id, + required this.code, + required this.name, + required this.nameAr, + }); + + factory Branch.fromRawJson(String str) => Branch.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Branch.fromJson(Map json) => Branch( + id: json["id"], + code: json["code"], + name: json["name"], + nameAr: json["name_ar"], + ); + + Map toJson() => { + "id": id, + "code": code, + "name": name, + "name_ar": nameAr, + }; +} + +class Category { + int id; + bool code; + String name; + String nameAr; + + Category({ + required this.id, + required this.code, + required this.name, + required this.nameAr, + }); + + factory Category.fromRawJson(String str) => Category.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Category.fromJson(Map json) => Category( + id: json["id"], + code: json["code"], + name: json["name"], + nameAr: json["name_ar"], + ); + + Map toJson() => { + "id": id, + "code": code, + "name": name, + "name_ar": nameAr, + }; +} + +class Images { + List main; + List banners; + List details; + + Images({ + required this.main, + required this.banners, + required this.details, + }); + + factory Images.fromRawJson(String str) => Images.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Images.fromJson(Map json) => Images( + main: List.from(json["main"].map((x) => Banner.fromJson(x))), + banners: List.from(json["banners"].map((x) => Banner.fromJson(x))), + details: List.from(json["details"].map((x) => Banner.fromJson(x))), + ); + + Map toJson() => { + "main": List.from(main.map((x) => x.toJson())), + "banners": List.from(banners.map((x) => x.toJson())), + "details": List.from(details.map((x) => x.toJson())), + }; +} + +class Banner { + int id; + String url; + int sequence; + + Banner({ + required this.id, + required this.url, + required this.sequence, + }); + + factory Banner.fromRawJson(String str) => Banner.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Banner.fromJson(Map json) => Banner( + id: json["id"], + url: json["url"], + sequence: json["sequence"], + ); + + Map toJson() => { + "id": id, + "url": url, + "sequence": sequence, + }; +} + +class OfferLine { + int sequence; + String lineType; + String procedureCode; + String name; + String nameAr; + int basePrice; + int discountedPrice; + String discountType; + int discountValue; + int quantity; + + OfferLine({ + required this.sequence, + required this.lineType, + required this.procedureCode, + required this.name, + required this.nameAr, + required this.basePrice, + required this.discountedPrice, + required this.discountType, + required this.discountValue, + required this.quantity, + }); + + factory OfferLine.fromRawJson(String str) => OfferLine.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory OfferLine.fromJson(Map json) => OfferLine( + sequence: json["sequence"], + lineType: json["line_type"], + procedureCode: json["procedure_code"], + name: json["name"], + nameAr: json["name_ar"], + basePrice: json["base_price"], + discountedPrice: json["discounted_price"], + discountType: json["discount_type"], + discountValue: json["discount_value"], + quantity: json["quantity"], + ); + + Map toJson() => { + "sequence": sequence, + "line_type": lineType, + "procedure_code": procedureCode, + "name": name, + "name_ar": nameAr, + "base_price": basePrice, + "discounted_price": discountedPrice, + "discount_type": discountType, + "discount_value": discountValue, + "quantity": quantity, + }; +} \ No newline at end of file diff --git a/lib/features/offers_and_discounts/models/offers_and_discounts_response_model.dart b/lib/features/offers_and_discounts/models/offers_and_discounts_response_model.dart index 4a1a250f..f1e84717 100644 --- a/lib/features/offers_and_discounts/models/offers_and_discounts_response_model.dart +++ b/lib/features/offers_and_discounts/models/offers_and_discounts_response_model.dart @@ -1,52 +1,201 @@ +import 'dart:convert'; + class OffersAndDiscountsResponseModel { - int? id; - String? title; - String? description; - String? imageUrl; - String? facilityType; // HMC, HMG, or Both - String? startDate; - String? endDate; - String? discount; - bool? isActive; + List data; + Meta meta; OffersAndDiscountsResponseModel({ - this.id, - this.title, - this.description, - this.imageUrl, - this.facilityType, - this.startDate, - this.endDate, - this.discount, - this.isActive, + required this.data, + required this.meta, }); - factory OffersAndDiscountsResponseModel.fromJson(Map json) { - return OffersAndDiscountsResponseModel( - id: json['ID'] as int?, - title: json['Title'] as String?, - description: json['Description'] as String?, - imageUrl: json['ImageURL'] as String?, - facilityType: json['FacilityType'] as String?, - startDate: json['StartDate'] as String?, - endDate: json['EndDate'] as String?, - discount: json['Discount'] as String?, - isActive: json['IsActive'] as bool?, - ); - } + factory OffersAndDiscountsResponseModel.fromRawJson(String str) => OffersAndDiscountsResponseModel.fromJson(json.decode(str)); - Map toJson() { - return { - 'ID': id, - 'Title': title, - 'Description': description, - 'ImageURL': imageUrl, - 'FacilityType': facilityType, - 'StartDate': startDate, - 'EndDate': endDate, - 'Discount': discount, - 'IsActive': isActive, - }; - } + String toRawJson() => json.encode(toJson()); + + factory OffersAndDiscountsResponseModel.fromJson(Map json) => OffersAndDiscountsResponseModel( + data: List.from(json["data"].map((x) => OfferData.fromJson(x))), + meta: Meta.fromJson(json["meta"]), + ); + + Map toJson() => { + "data": List.from(data.map((x) => x.toJson())), + "meta": meta.toJson(), + }; +} + +class OfferData { + int id; + String ref; + String name; + String nameAr; + Category category; + List branches; + double listPrice; + Currency currency; + String imageUrl; + DateTime validFrom; + DateTime validTo; + + OfferData({ + required this.id, + required this.ref, + required this.name, + required this.nameAr, + required this.category, + required this.branches, + required this.listPrice, + required this.currency, + required this.imageUrl, + required this.validFrom, + required this.validTo, + }); + + factory OfferData.fromRawJson(String str) => OfferData.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory OfferData.fromJson(Map json) => OfferData( + id: json["id"], + ref: json["ref"], + name: json["name"], + nameAr: json["name_ar"], + category: Category.fromJson(json["category"]), + branches: List.from(json["branches"].map((x) => Branch.fromJson(x))), + listPrice: json["list_price"]?.toDouble(), + currency: currencyValues.map[json["currency"]]!, + imageUrl: json["image_url"], + validFrom: DateTime.parse(json["valid_from"]), + validTo: DateTime.parse(json["valid_to"]), + ); + + Map toJson() => { + "id": id, + "ref": ref, + "name": name, + "name_ar": nameAr, + "category": category.toJson(), + "branches": List.from(branches.map((x) => x.toJson())), + "list_price": listPrice, + "currency": currencyValues.reverse[currency], + "image_url": imageUrl, + "valid_from": "${validFrom.year.toString().padLeft(4, '0')}-${validFrom.month.toString().padLeft(2, '0')}-${validFrom.day.toString().padLeft(2, '0')}", + "valid_to": "${validTo.year.toString().padLeft(4, '0')}-${validTo.month.toString().padLeft(2, '0')}-${validTo.day.toString().padLeft(2, '0')}", + }; } +class Branch { + int id; + String code; + String name; + String nameAr; + + Branch({ + required this.id, + required this.code, + required this.name, + required this.nameAr, + }); + + factory Branch.fromRawJson(String str) => Branch.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Branch.fromJson(Map json) => Branch( + id: json["id"], + code: json["code"], + name: json["name"], + nameAr: json["name_ar"], + ); + + Map toJson() => { + "id": id, + "code": code, + "name": name, + "name_ar": nameAr, + }; +} + +class Category { + int id; + bool code; + String name; + String nameAr; + + Category({ + required this.id, + required this.code, + required this.name, + required this.nameAr, + }); + + factory Category.fromRawJson(String str) => Category.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Category.fromJson(Map json) => Category( + id: json["id"], + code: json["code"], + name: json["name"], + nameAr: json["name_ar"], + ); + + Map toJson() => { + "id": id, + "code": code, + "name": name, + "name_ar": nameAr, + }; +} + +enum Currency { + SAR +} + +final currencyValues = EnumValues({ + "SAR": Currency.SAR +}); + +class Meta { + int total; + int page; + int perPage; + int pages; + + Meta({ + required this.total, + required this.page, + required this.perPage, + required this.pages, + }); + + factory Meta.fromRawJson(String str) => Meta.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Meta.fromJson(Map json) => Meta( + total: json["total"], + page: json["page"], + perPage: json["per_page"], + pages: json["pages"], + ); + + Map toJson() => { + "total": total, + "page": page, + "per_page": perPage, + "pages": pages, + }; +} + +class EnumValues { + Map map; + late Map reverseMap; + + EnumValues(this.map); + + Map get reverse { + reverseMap = map.map((k, v) => MapEntry(v, k)); + return reverseMap; + } +} \ No newline at end of file diff --git a/lib/features/offers_and_discounts/offers_and_discounts_repo.dart b/lib/features/offers_and_discounts/offers_and_discounts_repo.dart index 53f26a6f..953ce194 100644 --- a/lib/features/offers_and_discounts/offers_and_discounts_repo.dart +++ b/lib/features/offers_and_discounts/offers_and_discounts_repo.dart @@ -4,10 +4,14 @@ 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/offers_and_discounts/models/offers_and_discounts_response_model.dart'; +import 'package:hmg_patient_app_new/features/offers_and_discounts/models/offers_and_discounts_detailed_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class OffersAndDiscountsRepo { - Future>>> getOffersAndDiscounts(); + Future>> getOffersAndDiscounts(); + + Future>> getOfferDetails(int offerId); + Future>>> getOffersAndDiscountsHistory(); } @@ -18,42 +22,91 @@ class OffersAndDiscountsRepoImp implements OffersAndDiscountsRepo { OffersAndDiscountsRepoImp({required this.loggerService, required this.apiClient}); @override - Future>>> getOffersAndDiscounts() async { - Map mapDevice = {}; + Future>> getOffersAndDiscounts() async { + Map headers = { + 'Content-Type': 'application/json', + 'X-POMS-CHANNEL-API-KEY': ApiConsts.pomsApiKey, + }; try { - GenericApiModel>? apiResponse; + GenericApiModel? apiResponse; Failure? failure; - await apiClient.post( - GET_OFFERS_AND_DISCOUNTS, // TODO: Replace with actual API endpoint - body: mapDevice, - onFailure: (error, statusCode, {messageStatus, failureType}) { - failure = failureType; + + await apiClient.get( + ApiConsts.pomsGetHospitalOffers, + apiHeaders: headers, + isExternal: true, + isAllowAny: true, + onFailure: (error, statusCode) { + failure = ServerFailure(error); }, - onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + onSuccess: (response, statusCode) { try { - final list = response['List_OffersAndDiscounts']; - if (list == null || list.isEmpty) { - throw Exception("offers and discounts list is empty"); - } + final offersResponse = OffersAndDiscountsResponseModel.fromJson(response); + + apiResponse = GenericApiModel( + messageStatus: 1, + statusCode: statusCode, + errorMessage: null, + data: offersResponse, + ); + } catch (e) { + loggerService.logError("Error parsing offers response: $e"); + failure = DataParsingFailure(e.toString()); + } + }, + ); - final offers = list.map((item) => OffersAndDiscountsResponseModel.fromJson(item as Map)).toList().cast(); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + loggerService.logError("Exception in getOffersAndDiscounts: $e"); + return Left(UnknownFailure(e.toString())); + } + } - apiResponse = GenericApiModel>( - messageStatus: messageStatus, + @override + Future>> getOfferDetails(int offerId) async { + Map headers = { + 'Content-Type': 'application/json', + 'X-POMS-CHANNEL-API-KEY': ApiConsts.pomsApiKey, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.get( + '${ApiConsts.pomsGetHospitalOffersDetails}/$offerId', + apiHeaders: headers, + isExternal: true, + isAllowAny: true, + onFailure: (error, statusCode) { + failure = ServerFailure(error); + }, + onSuccess: (response, statusCode) { + try { + final offerDetails = OffersAndDiscountsDetailedResponseModel.fromJson(response); + + apiResponse = GenericApiModel( + messageStatus: 1, statusCode: statusCode, errorMessage: null, - data: offers, + data: offerDetails, ); } catch (e) { + loggerService.logError("Error parsing offer details response: $e"); failure = DataParsingFailure(e.toString()); } }, ); + if (failure != null) return Left(failure!); if (apiResponse == null) return Left(ServerFailure("Unknown error")); return Right(apiResponse!); } catch (e) { + loggerService.logError("Exception in getOfferDetails: $e"); return Left(UnknownFailure(e.toString())); } } @@ -99,4 +152,3 @@ class OffersAndDiscountsRepoImp implements OffersAndDiscountsRepo { } } } - diff --git a/lib/features/offers_and_discounts/offers_and_discounts_view_model.dart b/lib/features/offers_and_discounts/offers_and_discounts_view_model.dart index 88e15ce6..c3f77056 100644 --- a/lib/features/offers_and_discounts/offers_and_discounts_view_model.dart +++ b/lib/features/offers_and_discounts/offers_and_discounts_view_model.dart @@ -1,320 +1,37 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/features/offers_and_discounts/models/offers_and_discounts_response_model.dart'; +import 'package:hmg_patient_app_new/features/offers_and_discounts/models/offers_and_discounts_detailed_response_model.dart'; import 'package:hmg_patient_app_new/features/offers_and_discounts/offers_and_discounts_repo.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class OffersAndDiscountsViewModel extends ChangeNotifier { bool isOffersLoading = false; bool isHistoryLoading = false; + bool isOfferDetailsLoading = false; List selectedFacilities = ['All Offers']; // Default to 'All Offers' String searchQuery = ''; OffersAndDiscountsRepo offersAndDiscountsRepo; ErrorHandlerService errorHandlerService; - List offersList = []; + List offersList = []; List historyList = []; + OffersAndDiscountsDetailedResponseModel? offerDetails; + Meta? meta; OffersAndDiscountsViewModel({ required this.offersAndDiscountsRepo, required this.errorHandlerService, }) { - _initializeDummyData(); - } - - // Initialize with dummy data for testing - void _initializeDummyData() { - offersList = [ - OffersAndDiscountsResponseModel( - id: 1, - title: 'Summer Health Checkup Package', - description: 'Complete health screening with blood tests, X-ray, and consultation. Get 30% off on all diagnostic services.', - imageUrl: 'https://images.unsplash.com/photo-1576091160399-112ba8d25d1d?w=800', - facilityType: 'Female', - startDate: '2026-05-01', - endDate: '2026-08-31', - discount: '30% OFF', - isActive: true, - ), - OffersAndDiscountsResponseModel( - id: 2, - title: 'Maternity Care Special', - description: 'Comprehensive prenatal care package including ultrasound, consultations, and postnatal support.', - imageUrl: 'https://images.unsplash.com/photo-1584820927498-cfe5211fd8bf?w=800', - facilityType: 'OB-Gyne', - startDate: '2026-05-01', - endDate: '2026-12-31', - discount: '25% OFF', - isActive: true, - ), - OffersAndDiscountsResponseModel( - id: 3, - title: 'Skin Treatment Package', - description: 'Advanced dermatology services including acne treatment, anti-aging procedures, and skin rejuvenation.', - imageUrl: 'https://images.unsplash.com/photo-1570172619644-dfd03ed5d881?w=800', - facilityType: 'Dermatology', - startDate: '2026-05-15', - endDate: '2026-07-15', - discount: '40% OFF', - isActive: true, - ), - OffersAndDiscountsResponseModel( - id: 4, - title: 'CT & MRI Scanning Discount', - description: 'State-of-the-art imaging services with latest technology. Book your scan today!', - imageUrl: 'https://images.unsplash.com/photo-1516549655169-df83a0774514?w=800', - facilityType: 'Radiology', - startDate: '2026-05-01', - endDate: '2026-06-30', - discount: '20% OFF', - isActive: true, - ), - OffersAndDiscountsResponseModel( - id: 5, - title: 'Women\'s Health Screening', - description: 'Comprehensive health package designed specifically for women including mammography, bone density scan, and hormone tests.', - imageUrl: 'https://images.unsplash.com/photo-1559757175-5700dde675bc?w=800', - facilityType: 'Female', - startDate: '2026-05-10', - endDate: '2026-09-30', - discount: '35% OFF', - isActive: true, - ), - OffersAndDiscountsResponseModel( - id: 6, - title: 'Gynecology Consultation Special', - description: 'Free follow-up consultation with every initial gynecology visit. Expert care for all women\'s health needs.', - imageUrl: 'https://images.unsplash.com/photo-1631217868264-e5b90bb7e133?w=800', - facilityType: 'OB-Gyne', - startDate: '2026-05-01', - endDate: '2026-07-31', - discount: 'FREE Follow-up', - isActive: true, - ), - OffersAndDiscountsResponseModel( - id: 7, - title: 'Laser Hair Removal Package', - description: 'Professional laser hair removal treatment with latest technology. Multiple sessions available.', - imageUrl: 'https://images.unsplash.com/photo-1612349317150-e413f6a5b16d?w=800', - facilityType: 'Dermatology', - startDate: '2026-05-20', - endDate: '2026-10-20', - discount: '50% OFF', - isActive: true, - ), - OffersAndDiscountsResponseModel( - id: 8, - title: 'X-Ray & Ultrasound Combo', - description: 'Get both X-ray and ultrasound services at a discounted price. Quick results guaranteed.', - imageUrl: 'https://images.unsplash.com/photo-1530497610245-94d3c16cda28?w=800', - facilityType: 'Radiology', - startDate: '2026-05-01', - endDate: '2026-08-15', - discount: '15% OFF', - isActive: true, - ), - OffersAndDiscountsResponseModel( - id: 9, - title: 'Complete Wellness Package', - description: 'Full body checkup including all major tests, consultations, and health assessment report.', - imageUrl: 'https://images.unsplash.com/photo-1505751172876-fa1923c5c528?w=800', - facilityType: 'Both', - startDate: '2026-05-01', - endDate: '2026-12-31', - discount: '45% OFF', - isActive: true, - ), - OffersAndDiscountsResponseModel( - id: 10, - title: 'Botox & Filler Treatment', - description: 'Anti-aging treatments with certified dermatologists. Natural-looking results guaranteed.', - imageUrl: 'https://images.unsplash.com/photo-1515377905703-c4788e51af15?w=800', - facilityType: 'Dermatology', - startDate: '2026-05-15', - endDate: '2026-06-15', - discount: '30% OFF', - isActive: true, - ), - ]; - // Past offers for history - historyList = [ - OffersAndDiscountsResponseModel( - id: 101, - title: 'Winter Health Campaign', - description: 'Flu shots and winter wellness packages that were offered during the winter season.', - imageUrl: 'https://images.unsplash.com/photo-1584308666744-24d5c474f2ae?w=800', - facilityType: 'Both', - startDate: '2025-12-01', - endDate: '2026-02-28', - discount: '25% OFF', - isActive: false, - ), - OffersAndDiscountsResponseModel( - id: 102, - title: 'Valentine\'s Day Couple Checkup', - description: 'Special couple health screening packages offered for Valentine\'s Day.', - imageUrl: 'https://images.unsplash.com/photo-1516549655169-df83a0774514?w=800', - facilityType: 'Both', - startDate: '2026-02-01', - endDate: '2026-02-14', - discount: '40% OFF', - isActive: false, - ), - OffersAndDiscountsResponseModel( - id: 103, - title: 'Spring Skin Renewal', - description: 'Spring special dermatology treatments for skin rejuvenation.', - imageUrl: 'https://images.unsplash.com/photo-1556228720-195a672e8a03?w=800', - facilityType: 'Dermatology', - startDate: '2026-03-01', - endDate: '2026-04-30', - discount: '30% OFF', - isActive: false, - ), - OffersAndDiscountsResponseModel( - id: 104, - title: 'Summer Wellness Package - Active', - description: 'Active offer for comprehensive summer health checkup with multiple tests.', - imageUrl: 'https://images.unsplash.com/photo-1576091160399-112ba8d25d1d?w=800', - facilityType: 'Female', - startDate: '2026-05-01', - endDate: '2026-08-31', - discount: '35% OFF', - isActive: true, - ), - OffersAndDiscountsResponseModel( - id: 105, - title: 'Kids Health Screening - Active', - description: 'Currently active pediatric health screening package for children.', - imageUrl: 'https://images.unsplash.com/photo-1559839734-2b71ea197ec2?w=800', - facilityType: 'Both', - startDate: '2026-05-05', - endDate: '2026-09-30', - discount: '20% OFF', - isActive: true, - ), - OffersAndDiscountsResponseModel( - id: 106, - title: 'Laser Treatment Offer - Expired', - description: 'Laser hair removal package that has expired.', - imageUrl: 'https://images.unsplash.com/photo-1612349317150-e413f6a5b16d?w=800', - facilityType: 'Dermatology', - startDate: '2026-01-01', - endDate: '2026-03-31', - discount: '50% OFF', - isActive: false, - ), - OffersAndDiscountsResponseModel( - id: 107, - title: 'Cardiology Consultation Package', - description: 'Heart health checkup with ECG and consultation.', - imageUrl: 'https://images.unsplash.com/photo-1628348068343-c6a848d2b6dd?w=800', - facilityType: 'Both', - startDate: '2025-11-01', - endDate: '2026-01-31', - discount: '25% OFF', - isActive: false, - ), - OffersAndDiscountsResponseModel( - id: 108, - title: 'Dental Care Special - Active', - description: 'Comprehensive dental checkup and cleaning package.', - imageUrl: 'https://images.unsplash.com/photo-1606811971618-4486d14f3f99?w=800', - facilityType: 'Both', - startDate: '2026-04-01', - endDate: '2026-12-31', - discount: '30% OFF', - isActive: true, - ), - OffersAndDiscountsResponseModel( - id: 109, - title: 'Eye Care Package - Expired', - description: 'Complete eye examination with vision testing.', - imageUrl: 'https://images.unsplash.com/photo-1516534775068-ba3e7458af70?w=800', - facilityType: 'Both', - startDate: '2025-10-01', - endDate: '2025-12-31', - discount: '20% OFF', - isActive: false, - ), - OffersAndDiscountsResponseModel( - id: 110, - title: 'Maternity Care Premium', - description: 'Premium prenatal and postnatal care package.', - imageUrl: 'https://images.unsplash.com/photo-1584820927498-cfe5211fd8bf?w=800', - facilityType: 'OB-Gyne', - startDate: '2026-01-15', - endDate: '2026-06-30', - discount: '40% OFF', - isActive: true, - ), - OffersAndDiscountsResponseModel( - id: 111, - title: 'Blood Test Special - Expired', - description: 'Comprehensive blood work panel at discounted rates.', - imageUrl: 'https://images.unsplash.com/photo-1579154204601-01588f351e67?w=800', - facilityType: 'Both', - startDate: '2025-09-01', - endDate: '2025-11-30', - discount: '15% OFF', - isActive: false, - ), - OffersAndDiscountsResponseModel( - id: 112, - title: 'Vaccination Drive', - description: 'Special vaccination package for adults and children.', - imageUrl: 'https://images.unsplash.com/photo-1587854692152-cbe660dbde88?w=800', - facilityType: 'Both', - startDate: '2026-03-01', - endDate: '2026-05-05', - discount: '10% OFF', - isActive: false, - ), - OffersAndDiscountsResponseModel( - id: 113, - title: 'Radiology Imaging Package - Active', - description: 'CT, MRI, and X-ray services at reduced prices.', - imageUrl: 'https://images.unsplash.com/photo-1516549655169-df83a0774514?w=800', - facilityType: 'Radiology', - startDate: '2026-05-01', - endDate: '2026-10-31', - discount: '25% OFF', - isActive: false, // Changed to false - will show as "Availed" - ), - OffersAndDiscountsResponseModel( - id: 114, - title: 'Physiotherapy Sessions', - description: 'Package of 10 physiotherapy sessions.', - imageUrl: 'https://images.unsplash.com/photo-1576091160550-2173dba999ef?w=800', - facilityType: 'Both', - startDate: '2025-12-01', - endDate: '2026-04-01', - discount: '35% OFF', - isActive: false, - ), - OffersAndDiscountsResponseModel( - id: 115, - title: 'Nutrition Consultation Bundle - Active', - description: 'Series of nutrition and diet consultation sessions.', - imageUrl: 'https://images.unsplash.com/photo-1490645935967-10de6ba17061?w=800', - facilityType: 'Both', - startDate: '2026-05-01', - endDate: '2026-11-30', - discount: '20% OFF', - isActive: true, - ), - ]; } // For loading state compatibility with existing code bool get isRegionListLoading => isOffersLoading; initOffersAndDiscounts() { - // Don't clear the list - keep dummy data until API returns - // Don't show loading state since we have dummy data - // offersList.clear(); - // isOffersLoading = true; + offersList.clear(); + isOffersLoading = true; notifyListeners(); getOffersAndDiscounts(); } @@ -340,6 +57,9 @@ class OffersAndDiscountsViewModel extends ChangeNotifier { } Future getOffersAndDiscounts({Function(dynamic)? onSuccess, Function(String)? onError}) async { + isOffersLoading = true; + notifyListeners(); + final result = await offersAndDiscountsRepo.getOffersAndDiscounts(); result.fold( @@ -347,15 +67,16 @@ class OffersAndDiscountsViewModel extends ChangeNotifier { isOffersLoading = false; notifyListeners(); // Keep dummy data if API fails - don't clear the list - // await errorHandlerService.handleError(failure: failure); + await errorHandlerService.handleError(failure: failure); if (onError != null) { onError(failure.toString()); } }, (response) { isOffersLoading = false; - if (response.data != null && response.data!.isNotEmpty) { - offersList = response.data!; + if (response.data != null) { + offersList = response.data!.data; + meta = response.data!.meta; } // If API returns empty or null, keep the dummy data notifyListeners(); @@ -366,6 +87,34 @@ class OffersAndDiscountsViewModel extends ChangeNotifier { ); } + Future getOfferDetails(int offerId, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + isOfferDetailsLoading = true; + notifyListeners(); + + final result = await offersAndDiscountsRepo.getOfferDetails(offerId); + + result.fold( + (failure) async { + isOfferDetailsLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (response) { + isOfferDetailsLoading = false; + if (response.data != null) { + offerDetails = response.data; + } + notifyListeners(); + if (onSuccess != null) { + onSuccess(response); + } + }, + ); + } + Future getOffersAndDiscountsHistory({Function(dynamic)? onSuccess, Function(String)? onError}) async { isHistoryLoading = true; notifyListeners(); @@ -396,25 +145,24 @@ class OffersAndDiscountsViewModel extends ChangeNotifier { ); } - List get filteredOffers { - List filtered = offersList; + List get filteredOffers { + List filtered = offersList; // Filter by facility types (multi-select support) if (!selectedFacilities.contains('All Offers')) { filtered = filtered.where((offer) { - // Check if offer's facility type matches any of the selected types - return selectedFacilities.any((selectedType) => - offer.facilityType == selectedType || offer.facilityType == 'Both' - ); + // Check if offer's branches match any of the selected facility types + // You can customize this logic based on how you want to filter by facility + return true; // For now, show all when filtering is needed }).toList(); } // Filter by search query if (searchQuery.isNotEmpty) { filtered = filtered.where((offer) { - final titleMatch = offer.title?.toLowerCase().contains(searchQuery.toLowerCase()) ?? false; - final descriptionMatch = offer.description?.toLowerCase().contains(searchQuery.toLowerCase()) ?? false; - return titleMatch || descriptionMatch; + final titleMatch = offer.name.toLowerCase().contains(searchQuery.toLowerCase()); + final titleArMatch = offer.nameAr.toLowerCase().contains(searchQuery.toLowerCase()); + return titleMatch || titleArMatch; }).toList(); } @@ -424,26 +172,14 @@ class OffersAndDiscountsViewModel extends ChangeNotifier { List get filteredHistory { List filtered = historyList; - // Filter by facility types (multi-select support) - if (!selectedFacilities.contains('All Offers')) { - filtered = filtered.where((offer) { - // Check if offer's facility type matches any of the selected types - return selectedFacilities.any((selectedType) => - offer.facilityType == selectedType || offer.facilityType == 'Both' - ); - }).toList(); - } - // Filter by search query if (searchQuery.isNotEmpty) { filtered = filtered.where((offer) { - final titleMatch = offer.title?.toLowerCase().contains(searchQuery.toLowerCase()) ?? false; - final descriptionMatch = offer.description?.toLowerCase().contains(searchQuery.toLowerCase()) ?? false; - return titleMatch || descriptionMatch; + // Assuming history list uses the old structure - update when API is available + return true; // Placeholder for now }).toList(); } return filtered; } } - diff --git a/lib/presentation/offers_and_discounts/offers_and_discounts_detailed_page.dart b/lib/presentation/offers_and_discounts/offers_and_discounts_detailed_page.dart index ed78dffb..76e24eea 100644 --- a/lib/presentation/offers_and_discounts/offers_and_discounts_detailed_page.dart +++ b/lib/presentation/offers_and_discounts/offers_and_discounts_detailed_page.dart @@ -1,53 +1,61 @@ +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/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/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/offers_and_discounts/models/offers_and_discounts_response_model.dart'; +import 'package:hmg_patient_app_new/features/offers_and_discounts/offers_and_discounts_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/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:provider/provider.dart'; -class OffersAndDiscountsDetailedPage extends StatelessWidget { - final OffersAndDiscountsResponseModel offer; +class OffersAndDiscountsDetailedPage extends StatefulWidget { + final int offerId; const OffersAndDiscountsDetailedPage({ super.key, - required this.offer, + required this.offerId, }); - String _formatDateString(String? dateString) { - if (dateString == null || dateString.isEmpty) return ''; + @override + State createState() => _OffersAndDiscountsDetailedPageState(); +} + +class _OffersAndDiscountsDetailedPageState extends State { + late OffersAndDiscountsViewModel viewModel; + late AppState appState; + + @override + void initState() { + super.initState(); + scheduleMicrotask(() { + viewModel.getOfferDetails(widget.offerId); + }); + } + + String _formatDateString(DateTime date) { try { - DateTime date = DateTime.parse(dateString); return DateFormat('d MMM, yyyy').format(date); } catch (e) { - return dateString; + return ''; } } - // Determine the status of the offer based on dates and isActive flag - String _getOfferStatus(String? endDate, bool? isActive) { - if (endDate == null || endDate.isEmpty) return 'Expired'; - try { - DateTime end = DateTime.parse(endDate); - DateTime now = DateTime.now(); - - if (now.isAfter(end)) { - return 'Expired'; - } else { - if (isActive == true) { - return 'Active'; - } else { - return 'Availed'; - } - } - } catch (e) { + // Determine the status of the offer based on dates + String _getOfferStatus(DateTime validTo) { + DateTime now = DateTime.now(); + if (now.isAfter(validTo)) { return 'Expired'; + } else { + return 'Active'; } } @@ -55,8 +63,6 @@ class OffersAndDiscountsDetailedPage extends StatelessWidget { switch (status) { case 'Active': return AppColors.successColor.withValues(alpha: 0.1); - case 'Availed': - return AppColors.infoColor.withValues(alpha: 0.1); case 'Expired': return AppColors.errorColor.withValues(alpha: 0.1); default: @@ -68,8 +74,6 @@ class OffersAndDiscountsDetailedPage extends StatelessWidget { switch (status) { case 'Active': return AppColors.successColor; - case 'Availed': - return AppColors.infoColor; case 'Expired': return AppColors.errorColor; default: @@ -79,22 +83,57 @@ class OffersAndDiscountsDetailedPage extends StatelessWidget { @override Widget build(BuildContext context) { - final status = _getOfferStatus(offer.endDate, offer.isActive); + appState = getIt.get(); + viewModel = Provider.of(context, listen: false); return CollapsingListView( title: "${LocaleKeys.offers.tr(context: context)} ${LocaleKeys.details.tr(context: context)}", - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Offer Image - if (offer.imageUrl != null) - Stack( - children: [ + child: Consumer( + builder: (context, vm, child) { + if (vm.isOfferDetailsLoading) { + return Center(child: Utils.getLoadingWidget()); + } + + final offer = vm.offerDetails; + if (offer == null) { + return Center(child: Text(LocaleKeys.noDataAvailable.tr(context: context))); + } + + final status = _getOfferStatus(offer.validTo); + + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Offer Image Carousel or Single Image + if (offer.images.banners.isNotEmpty) + ClipRRect( + borderRadius: BorderRadius.circular(24.h), + child: Utils.buildImgWithNetwork( + url: offer.images.banners.first.url, + iconColor: AppColors.blackBgColor, + width: double.infinity, + height: 250.h, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Container( + height: 250.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.greyColor, + borderRadius: 24.h, + ), + child: Center( + child: Icon(Icons.image_not_supported, size: 64.h), + ), + ); + }, + ), + ) + else if (offer.imageUrl.isNotEmpty) ClipRRect( borderRadius: BorderRadius.circular(24.h), child: Utils.buildImgWithNetwork( - url: offer.imageUrl!, + url: offer.imageUrl, iconColor: AppColors.blackBgColor, width: double.infinity, height: 250.h, @@ -113,112 +152,169 @@ class OffersAndDiscountsDetailedPage extends StatelessWidget { }, ), ), - // Discount badge at top right on image - // if (offer.discount != null) - // Positioned( - // top: 16.h, - // right: 16.w, - // child: Container( - // padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h), - // decoration: BoxDecoration( - // color: AppColors.primaryRedColor, - // borderRadius: BorderRadius.circular(8.r), - // ), - // child: Text( - // offer.discount!, - // style: TextStyle( - // fontSize: 14.f, - // fontWeight: FontWeight.w700, - // color: AppColors.whiteColor, - // ), - // ), - // ), - // ), - ], - ), - SizedBox(height: 24.h), + SizedBox(height: 24.h), - // Title - if (offer.title != null) - offer.title!.toText24( - isBold: true, - fontWeight: FontWeight.w700, - ), + // Title + (appState.getLanguageID() == 1 ? offer.nameAr : offer.name).toText24( + isBold: true, + fontWeight: FontWeight.w700, + ), - SizedBox(height: 16.h), + SizedBox(height: 16.h), - // Chips Row (Valid Till and Status) - Wrap( - spacing: 12.w, - runSpacing: 12.h, - children: [ - // Valid till chip - if (offer.endDate != null) - AppCustomChipWidget( - labelText: "Valid till ${_formatDateString(offer.endDate)}", - backgroundColor: AppColors.chipBgColor, - textColor: AppColors.textColor, - icon: AppAssets.calendar, - iconSize: 14.h, - iconColor: AppColors.textColor, - isEnglishOnly: true, - ), - // Status chip - AppCustomChipWidget( - labelText: status, - backgroundColor: _getStatusBgColor(status), - textColor: _getStatusTextColor(status), + // Price Information + Row( + children: [ + "Price: ".toText18( + isBold: true, + weight: FontWeight.w700, + ), + "${offer.listPrice} ${offer.currency}".toText18( + color: AppColors.primaryRedColor, + isBold: true, + weight: FontWeight.w700, + ), + ], + ), + + SizedBox(height: 16.h), + + // Chips Row (Valid Till and Status) + Wrap( + spacing: 12.w, + runSpacing: 12.h, + children: [ + // Valid till chip + AppCustomChipWidget( + labelText: "Valid till ${_formatDateString(offer.validTo)}", + backgroundColor: AppColors.chipBgColor, + textColor: AppColors.textColor, + icon: AppAssets.calendar, + iconSize: 14.h, + iconColor: AppColors.textColor, + isEnglishOnly: true, + ), + // Status chip + AppCustomChipWidget( + labelText: status, + backgroundColor: _getStatusBgColor(status), + textColor: _getStatusTextColor(status), + ), + // Category chip + AppCustomChipWidget( + labelText: appState.getLanguageID() == 1 ? offer.category.nameAr : offer.category.name, + backgroundColor: AppColors.chipBgColor, + textColor: AppColors.textColor, + ), + ], ), - // Facility type chip - if (offer.facilityType != null) - AppCustomChipWidget( - labelText: offer.facilityType!, - backgroundColor: AppColors.chipBgColor, - textColor: AppColors.textColor, - ), - ], - ), - SizedBox(height: 32.h), + SizedBox(height: 24.h), - // Description Heading - LocaleKeys.description.tr(context: context).toText18( + // Branches Section + if (offer.branches.isNotEmpty) ...[ + "Available at".toText18( + isBold: true, + weight: FontWeight.w700, + ), + SizedBox(height: 12.h), + Wrap( + spacing: 8.w, + runSpacing: 8.h, + children: offer.branches.map((branch) { + return AppCustomChipWidget( + labelText: appState.getLanguageID() == 1 ? branch.nameAr : branch.name, + backgroundColor: AppColors.chipBgColor, + textColor: AppColors.textColor, + ); + }).toList(), + ), + SizedBox(height: 24.h), + ], + + // Description Heading + LocaleKeys.description.tr(context: context).toText18( isBold: true, weight: FontWeight.w700, ), - SizedBox(height: 12.h), - - // Description Content - if (offer.description != null) - offer.description!.toText16( - color: AppColors.greyTextColor, - height: 1.5, - ), - - SizedBox(height: 32.h), - - // Buy Now Button - CustomButton( - text: "Buy Now", - icon: AppAssets.shoppingCart, - iconColor: AppColors.whiteColor, - onPressed: () { - // Handle buy now action - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, - fontSize: 16.f, - isBold: true, - borderRadius: 12.r, - height: 56.h, - ), - - SizedBox(height: 24.h), - ], - ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 12.h), + + // Description Content + (appState.getLanguageID() == 1 ? offer.descriptionAr : offer.description).toText16( + color: AppColors.greyTextColor, + height: 1.5, + ), + + SizedBox(height: 24.h), + + // Offer Lines Section + if (offer.offerLines.isNotEmpty) ...[ + "Included Services".toText18( + isBold: true, + weight: FontWeight.w700, + ), + SizedBox(height: 12.h), + ...offer.offerLines.map((line) { + return Container( + margin: EdgeInsets.only(bottom: 12.h), + padding: EdgeInsets.all(12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.h, + hasShadow: true, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (appState.getLanguageID() == 1 ? line.nameAr : line.name).toText16( + isBold: true, + weight: FontWeight.w600, + ), + SizedBox(height: 4.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Quantity: ${line.quantity}".toText14( + color: AppColors.greyTextColor, + ), + if (line.discountValue > 0) + "${line.discountValue}% OFF".toText14( + color: AppColors.primaryRedColor, + isBold: true, + ), + ], + ), + ], + ), + ); + }), + SizedBox(height: 24.h), + ], + + // Buy Now Button + CustomButton( + text: "Buy Now", + icon: AppAssets.shoppingCart, + iconColor: AppColors.whiteColor, + onPressed: () { + // Handle buy now action + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + isBold: true, + borderRadius: 12.r, + height: 56.h, + ), + + SizedBox(height: 24.h), + ], + ).paddingSymmetrical(24.h, 0.h), + ); + }, ), ); } diff --git a/lib/presentation/offers_and_discounts/offers_and_discounts_history_page.dart b/lib/presentation/offers_and_discounts/offers_and_discounts_history_page.dart index 74ef2426..cd219e2f 100644 --- a/lib/presentation/offers_and_discounts/offers_and_discounts_history_page.dart +++ b/lib/presentation/offers_and_discounts/offers_and_discounts_history_page.dart @@ -2,22 +2,13 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; -import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; -import 'package:hmg_patient_app_new/core/utils/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/offers_and_discounts/offers_and_discounts_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/offers_and_discounts/offers_and_discounts_detailed_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; -import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; -import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; -import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; class OffersAndDiscountsHistoryPage extends StatefulWidget { @@ -34,69 +25,11 @@ class _OffersAndDiscountsHistoryPageState extends State(); @@ -109,179 +42,37 @@ class _OffersAndDiscountsHistoryPageState extends State SizedBox(height: 16.h), ), + ], + ), + ), SizedBox(height: 24.h), ], ); diff --git a/lib/presentation/offers_and_discounts/offers_and_discounts_page.dart b/lib/presentation/offers_and_discounts/offers_and_discounts_page.dart index 2a26ac24..1b8962d2 100644 --- a/lib/presentation/offers_and_discounts/offers_and_discounts_page.dart +++ b/lib/presentation/offers_and_discounts/offers_and_discounts_page.dart @@ -150,7 +150,7 @@ class _OffersAndDiscountsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (offersAndDiscountVM.filteredOffers[index].imageUrl != null) + if (offersAndDiscountVM.filteredOffers[index].imageUrl.isNotEmpty) Expanded( flex: 3, child: Stack( @@ -159,7 +159,7 @@ class _OffersAndDiscountsPageState extends State { borderRadius: BorderRadius.vertical(top: Radius.circular(24.h)), child: SizedBox.expand( child: Utils.buildImgWithNetwork( - url: offersAndDiscountVM.filteredOffers[index].imageUrl!, + url: offersAndDiscountVM.filteredOffers[index].imageUrl, iconColor: AppColors.blackBgColor, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { @@ -176,32 +176,11 @@ class _OffersAndDiscountsPageState extends State { Navigator.of(context).push( CustomPageRoute( page: OffersAndDiscountsDetailedPage( - offer: offersAndDiscountVM.filteredOffers[index], + offerId: offersAndDiscountVM.filteredOffers[index].id, ), ), ); }), - // Discount badge at top right - // if (offersAndDiscountVM.filteredOffers[index].discount != null) - // Positioned( - // top: 8.h, - // right: 8.w, - // child: Container( - // padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), - // decoration: BoxDecoration( - // color: AppColors.primaryRedColor, - // borderRadius: BorderRadius.circular(6.r), - // ), - // child: Text( - // offersAndDiscountVM.filteredOffers[index].discount!, - // style: TextStyle( - // fontSize: 10.f, - // fontWeight: FontWeight.w700, - // color: AppColors.whiteColor, - // ), - // ), - // ), - // ), ], ), ), @@ -213,38 +192,43 @@ class _OffersAndDiscountsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - if (offersAndDiscountVM.filteredOffers[index].title != null) - Text( - offersAndDiscountVM.filteredOffers[index].title!, - style: TextStyle( - fontSize: 14.f, - fontWeight: FontWeight.w700, - color: AppColors.textColor, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, + Text( + appState.getLanguageID() == 1 + ? offersAndDiscountVM.filteredOffers[index].nameAr + : offersAndDiscountVM.filteredOffers[index].name, + style: TextStyle( + fontSize: 14.f, + fontWeight: FontWeight.w700, + color: AppColors.textColor, ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), SizedBox(height: 8.h), // Valid till chip - if (offersAndDiscountVM.filteredOffers[index].endDate != null) - AppCustomChipWidget( - labelText: "Valid till ${Utils.formatDateToDisplay(offersAndDiscountVM.filteredOffers[index].endDate ?? "")}", - // labelText: "Valid till ${_formatDateString(offersAndDiscountVM.filteredOffers[index].endDate)}", - backgroundColor: AppColors.chipBgColor, - textColor: AppColors.textColor, - icon: AppAssets.calendar, - iconSize: 12.h, - iconColor: AppColors.textColor, - isEnglishOnly: true, - labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 8.w), - ), + AppCustomChipWidget( + labelText: "Valid till ${Utils.formatDateToDisplay(offersAndDiscountVM.filteredOffers[index].validTo.toString())}", + backgroundColor: AppColors.chipBgColor, + textColor: AppColors.textColor, + icon: AppAssets.calendar, + iconSize: 12.h, + iconColor: AppColors.textColor, + isEnglishOnly: true, + labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 8.w), + ), SizedBox(height: 20.h), CustomButton( text: 'Buy Now', icon: AppAssets.shoppingCart, iconColor: AppColors.whiteColor, onPressed: () { - // Handle buy now action + Navigator.of(context).push( + CustomPageRoute( + page: OffersAndDiscountsDetailedPage( + offerId: offersAndDiscountVM.filteredOffers[index].id, + ), + ), + ); }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor,