From 516df96e212546e15657ceef3ca266b114d5bae0 Mon Sep 17 00:00:00 2001 From: Zohaib Iqbal Kambrani <> Date: Tue, 23 Feb 2021 16:07:23 +0300 Subject: [PATCH 1/7] no message --- lib/config/config.dart | 4 + lib/core/model/pharmacies/order_model.dart | 15 +- .../parmacyModule/order-preview-service.dart | 24 ++++ .../order_model_view_model.dart | 3 +- lib/pages/pharmacy/order/OrderDetails.dart | 18 ++- lib/pages/pharmacy/order/TrackDriver.dart | 130 +++++++++++++++--- pubspec.yaml | 4 + 7 files changed, 169 insertions(+), 29 deletions(-) diff --git a/lib/config/config.dart b/lib/config/config.dart index be5dd440..cf1836ac 100644 --- a/lib/config/config.dart +++ b/lib/config/config.dart @@ -26,6 +26,10 @@ const GET_PROJECT = 'Services/Lists.svc/REST/GetProject'; ///Geofencing const GET_GEO_ZONES = 'Services/Patients.svc/REST/GeoF_GetAllPoints'; const LOG_GEO_ZONES = 'Services/Patients.svc/REST/GeoF_InsertPatientFileInfo'; + +// Delivery Driver +const DRIVER_LOCATION = 'Services/Patients.svc/REST/PatientER_GetDriverLocation'; + //weather const WEATHER_INDICATOR = 'Services/Weather.svc/REST/GetCityInfo'; diff --git a/lib/core/model/pharmacies/order_model.dart b/lib/core/model/pharmacies/order_model.dart index 8872aaf8..776c21ec 100644 --- a/lib/core/model/pharmacies/order_model.dart +++ b/lib/core/model/pharmacies/order_model.dart @@ -73,6 +73,8 @@ class OrderModel { this.preferDeliveryDate, this.preferDeliveryTime, this.preferDeliveryTimen, + this.driverID, + this.driverOTP, }); String id; @@ -138,6 +140,8 @@ class OrderModel { DateTime preferDeliveryDate; PreferDeliveryTime preferDeliveryTime; PreferDeliveryTimen preferDeliveryTimen; + String driverOTP; + String driverID; factory OrderModel.fromJson(Map json) => OrderModel( id: json["id"], @@ -203,6 +207,11 @@ class OrderModel { preferDeliveryDate: json["prefer_delivery_date"] == null ? null : DateTime.parse(json["prefer_delivery_date"]), preferDeliveryTime: json["prefer_delivery_time"] == null ? null : preferDeliveryTimeValues.map[json["prefer_delivery_time"]], preferDeliveryTimen: json["prefer_delivery_timen"] == null ? null : preferDeliveryTimenValues.map[json["prefer_delivery_timen"]], + + // Driver Detail + driverID: json["DriverID"], + driverOTP: json["DriverOTP"], + ); Map toJson() => { @@ -1364,7 +1373,7 @@ final titleValues = EnumValues({ "ممتاز": Title.TITLE }); -enum OrderStatus { ORDER_SUBMITTED, PENDING, ORDER_IN_PROGRESS, ORDER_COMPLETED, CANCELLED, PROCESSING, ORDER_REFUNDED, COMPLETE } +enum OrderStatus { ORDER_SUBMITTED, PENDING, ORDER_IN_PROGRESS,ORDER_SENT_FOR_DELIVERY, ORDER_COMPLETED, CANCELLED, PROCESSING, ORDER_REFUNDED, COMPLETE } final orderStatusValues = EnumValues({ "Cancelled": OrderStatus.CANCELLED, @@ -1374,7 +1383,8 @@ final orderStatusValues = EnumValues({ "OrderRefunded": OrderStatus.ORDER_REFUNDED, "OrderSubmitted": OrderStatus.ORDER_SUBMITTED, "Pending": OrderStatus.PENDING, - "Processing": OrderStatus.PROCESSING + "Processing": OrderStatus.PROCESSING, + "orderSentForDelivery": OrderStatus.ORDER_SENT_FOR_DELIVERY }); enum OrderStatusn { ORDER_SUBMITTED, EMPTY, ORDER_IN_PROGRESS, ORDER_COMPLETED, ORDER_STATUSN, PURPLE, FLUFFY, TENTACLED } @@ -1387,6 +1397,7 @@ final orderStatusnValues = EnumValues({ "ملغي": OrderStatusn.ORDER_STATUSN, "Order Submitted": OrderStatusn.ORDER_SUBMITTED, "قيد التنفيذ": OrderStatusn.PURPLE, + "مكتمل": OrderStatusn.TENTACLED, "مكتمل": OrderStatusn.TENTACLED }); diff --git a/lib/core/service/parmacyModule/order-preview-service.dart b/lib/core/service/parmacyModule/order-preview-service.dart index 18cfa87e..8bfda569 100644 --- a/lib/core/service/parmacyModule/order-preview-service.dart +++ b/lib/core/service/parmacyModule/order-preview-service.dart @@ -6,6 +6,8 @@ import 'package:diplomaticquarterapp/core/model/pharmacies/ShoppingCart.dart'; import 'package:diplomaticquarterapp/core/model/pharmacies/payment-checkout-data.dart'; import 'package:diplomaticquarterapp/core/service/base_service.dart'; import 'package:diplomaticquarterapp/core/viewModels/pharmacyModule/OrderPreviewViewModel.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; class OrderPreviewService extends BaseService { bool isFinished = true; @@ -245,4 +247,26 @@ class OrderPreviewService extends BaseService { return ""; } } + + Future getDriverLocation(dynamic driverId) async{ + Map jsonBody = Map(); + jsonBody['DriverID'] = driverId; + + LatLng coordinates; + await baseAppClient.post(DRIVER_LOCATION, + onSuccess: (response, statusCode) async { + if(statusCode == 200){ + dynamic locationObject = response['PatientER_GetDriverLocationList'][0]; + double lat = locationObject['Latitude']; + double lon = locationObject['Longitude']; + if(lat != null && lon != null){ + coordinates = LatLng(lat,lon); + } + } + }, onFailure: (String error, int statusCode) { + + }, body: jsonBody); + + return coordinates; + } } diff --git a/lib/core/viewModels/pharmacyModule/order_model_view_model.dart b/lib/core/viewModels/pharmacyModule/order_model_view_model.dart index f434db54..90881d7e 100644 --- a/lib/core/viewModels/pharmacyModule/order_model_view_model.dart +++ b/lib/core/viewModels/pharmacyModule/order_model_view_model.dart @@ -50,7 +50,7 @@ class OrderModelViewModel extends BaseViewModel { } } - Future getOrderDetails(OrderId) async { + Future getOrderDetails(OrderId) async { setState(ViewState.Busy); await _orderDetailsService.getOrderDetails(OrderId); if (_orderDetailsService.hasError) { @@ -59,6 +59,7 @@ class OrderModelViewModel extends BaseViewModel { } else { setState(ViewState.Idle); } + return _orderDetailsService.orderList.first; } Future getProductReview() async { diff --git a/lib/pages/pharmacy/order/OrderDetails.dart b/lib/pages/pharmacy/order/OrderDetails.dart index 670cb951..f3ee5447 100644 --- a/lib/pages/pharmacy/order/OrderDetails.dart +++ b/lib/pages/pharmacy/order/OrderDetails.dart @@ -48,7 +48,7 @@ class _OrderDetailsPageState extends State { var model; var isCancel = false; var isRefund = false; - var isActiveDelivery = true; + var isActiveDelivery = false; var dataIsCancel; var dataIsRefund; @@ -66,7 +66,13 @@ class _OrderDetailsPageState extends State { @override Widget build(BuildContext context) { return BaseView( - onModelReady: (model) => model.getOrderDetails(widget.orderModel.id), + onModelReady: (model){ + model.getOrderDetails(widget.orderModel.id).then((value){ + setState(() { + isActiveDelivery = (value.orderStatusId == 995 && (value.driverID != null && value.driverID.isNotEmpty)); + }); + }); + }, builder: (_, model, wi) => AppScaffold( appBarTitle: TranslationBase.of(context).orderDetail, isShowAppBar: true, @@ -596,10 +602,10 @@ class _OrderDetailsPageState extends State { isActiveDelivery ? InkWell( onTap: () { - // Navigator.push( - // context, - // MaterialPageRoute(builder: (context) => TrackDriver(order: widget.orderModel), - // )); + Navigator.push( + context, + MaterialPageRoute(builder: (context) => TrackDriver(order: model.orderListModel.first), + )); }, child: Container( height: 50.0, diff --git a/lib/pages/pharmacy/order/TrackDriver.dart b/lib/pages/pharmacy/order/TrackDriver.dart index b9a8056f..5a2e20c8 100644 --- a/lib/pages/pharmacy/order/TrackDriver.dart +++ b/lib/pages/pharmacy/order/TrackDriver.dart @@ -1,12 +1,19 @@ import 'dart:async'; +import 'package:async/async.dart'; import 'package:diplomaticquarterapp/config/config.dart'; import 'package:diplomaticquarterapp/core/model/pharmacies/order_model.dart'; +import 'package:diplomaticquarterapp/core/service/parmacyModule/order-preview-service.dart'; +import 'package:diplomaticquarterapp/locator.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_animarker/lat_lng_interpolation.dart'; +import 'package:flutter_animarker/models/lat_lng_delta.dart'; +import 'package:flutter_animarker/models/lat_lng_info.dart'; import 'package:flutter_polyline_points/flutter_polyline_points.dart'; +import 'package:geolocator/geolocator.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:location/location.dart'; - +import 'package:flutter_animarker/streams/lat_lng_stream.dart'; class TrackDriver extends StatefulWidget { final OrderModel order; @@ -17,12 +24,13 @@ class TrackDriver extends StatefulWidget { } class _TrackDriverState extends State { + OrderPreviewService _orderServices = locator(); OrderModel _order; Completer _controller = Completer(); - double CAMERA_ZOOM = 16; + double CAMERA_ZOOM = 14; double CAMERA_TILT = 0; double CAMERA_BEARING = 30; LatLng SOURCE_LOCATION = null; @@ -39,17 +47,63 @@ class _TrackDriverState extends State { BitmapDescriptor destinationIcon; // for my custom marker pins Location location;// wrapper around the location API + + int locationUpdateFreq = 2; + LatLngInterpolationStream _latLngStream; + StreamGroup subscriptions; + @override void initState() { + _order = widget.order; DEST_LOCATION = _order.shippingAddress.getLocation(); location = new Location(); polylinePoints = PolylinePoints(); setSourceAndDestinationIcons(); + + initMarkerUpdateStream(); + startUpdatingDriverLocation(); + + super.initState(); + } + + @override + void dispose() { + super.dispose(); + subscriptions.close(); + _latLngStream.cancel(); + stopUpdatingDriverLocation(); + } + + initMarkerUpdateStream(){ + _latLngStream = LatLngInterpolationStream(movementDuration: Duration(seconds: locationUpdateFreq+1)); + subscriptions = StreamGroup(); + + subscriptions.add(_latLngStream.getAnimatedPosition('sourcePin')); + subscriptions.stream.listen((LatLngDelta delta) { + //Update the marker with animation + setState(() { + //Get the marker Id for this animation + var markerId = MarkerId(delta.markerId); + Marker sourceMarker = Marker( + markerId: markerId, + // rotation: delta.rotation, + icon: sourceIcon, + position: LatLng( + delta.from.latitude, + delta.from.longitude, + ), + ); + + _markers.removeWhere((m) => m.markerId.value == 'sourcePin'); + _markers.add(sourceMarker); + }); + }); } @override Widget build(BuildContext context) { + return new Scaffold( body: GoogleMap( myLocationEnabled: true, @@ -63,11 +117,11 @@ class _TrackDriverState extends State { showPinsOnMap(); }, ), - floatingActionButton: FloatingActionButton.extended( - onPressed: _goToDriver, - label: Text('To the lake!'), - icon: Icon(Icons.directions_boat), - ), + // floatingActionButton: FloatingActionButton.extended( + // onPressed: _goToDriver, + // label: Text('To the lake!'), + // icon: Icon(Icons.directions_boat), + // ), ); } @@ -130,6 +184,7 @@ class _TrackDriverState extends State { if(SOURCE_LOCATION != null){ setState(() { var pinPosition = SOURCE_LOCATION; + _markers.removeWhere((m) => m.markerId.value == 'sourcePin'); _markers.add(Marker( markerId: MarkerId('sourcePin'), position: pinPosition, @@ -142,6 +197,7 @@ class _TrackDriverState extends State { if(DEST_LOCATION != null){ setState(() { var destPosition = DEST_LOCATION; + _markers.removeWhere((m) => m.markerId.value == 'destPin'); _markers.add(Marker( markerId: MarkerId('destPin'), position: destPosition, @@ -166,21 +222,26 @@ class _TrackDriverState extends State { ); final GoogleMapController controller = await _controller.future; controller.animateCamera(CameraUpdate.newCameraPosition(cPosition)); + + _latLngStream.addLatLng(LatLngInfo(SOURCE_LOCATION.latitude, SOURCE_LOCATION.longitude, "sourcePin")); + // do this inside the setState() so Flutter gets notified // that a widget update is due - setState(() { - // updated position - var pinPosition = SOURCE_LOCATION; - - // the trick is to remove the marker (by id) - // and add it again at the updated location - _markers.removeWhere((m) => m.markerId.value == 'sourcePin'); - _markers.add(Marker( - markerId: MarkerId('sourcePin'), - position: pinPosition, // updated position - icon: sourceIcon - )); - }); + // setState(() { + // // updated position + // var pinPosition = SOURCE_LOCATION; + // + // // the trick is to remove the marker (by id) + // // and add it again at the updated location + // _markers.removeWhere((m) => m.markerId.value == 'sourcePin'); + // _markers.add(Marker( + // markerId: MarkerId('sourcePin'), + // position: pinPosition, // updated position + // icon: sourceIcon + // )); + // }); + + drawRoute(); } void drawRoute() async { @@ -208,4 +269,33 @@ class _TrackDriverState extends State { }); } } + + bool isLocationUpdating = false; + startUpdatingDriverLocation({int frequencyInSeconds = 2}) async{ + isLocationUpdating = true; + int driverId = int.tryParse(_order.driverID); + + Future.doWhile(() async{ + await Future.delayed(Duration(seconds: frequencyInSeconds)); + if(isLocationUpdating){ + LatLng driverLocation = (await _orderServices.getDriverLocation(driverId)); + if(driverLocation != null){ + if(SOURCE_LOCATION == null || DEST_LOCATION == null){ + SOURCE_LOCATION = driverLocation; + DEST_LOCATION = _order.shippingAddress.getLocation(); + showPinsOnMap(); + } + SOURCE_LOCATION = driverLocation; + updatePinOnMap(); + } + } + return isLocationUpdating; + + }); + + } + + stopUpdatingDriverLocation(){ + isLocationUpdating = false; + } } \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 2c07d0c9..3b44f01f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: # http client http: ^0.12.1 connectivity: ^0.4.9+3 + async: ^2.4.2 # State Management provider: ^4.3.2+2 @@ -170,6 +171,9 @@ dependencies: # Dep by Zohaib shimmer: ^1.1.2 + # Marker Animation + flutter_animarker: ^1.0.0 + dev_dependencies: flutter_test: sdk: flutter From 63663f1b24969e6eb3a8f24abdea75e427b2f5b1 Mon Sep 17 00:00:00 2001 From: Zohaib Iqbal Kambrani <> Date: Wed, 24 Feb 2021 16:35:10 +0300 Subject: [PATCH 2/7] Track Delivery Driver Completed --- ios/Runner.xcodeproj/project.pbxproj | 18 +++++++++++ ios/Runner/AppDelegate.swift | 2 +- lib/config/localized_values.dart | 1 + lib/pages/pharmacy/order/TrackDriver.dart | 34 ++++++++++++++++----- lib/uitl/translations_delegate_base.dart | 1 + lib/widgets/others/app_scaffold_widget.dart | 7 ++++- 6 files changed, 53 insertions(+), 10 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 366647c0..a48231b2 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -220,6 +220,7 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, EFDAD5E1235DCA1DB6187148 /* [CP] Embed Pods Frameworks */, + A22727229BC544AD6D5B2B5F /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -333,6 +334,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + A22727229BC544AD6D5B2B5F /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; EFDAD5E1235DCA1DB6187148 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 2dc828d1..c4a74c80 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -11,7 +11,7 @@ var userNotificationCenterDelegate:UNUserNotificationCenterDelegate? = nil override func application( _ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - GMSServices.provideAPIKey("AIzaSyCiiJiHkocPbcziHt9O8rGWavDrxHRQys8") + GMSServices.provideAPIKey("AIzaSyCmevVlr2Bh-c8W1VUzo8gt8JRY7n5PANw") GeneratedPluginRegistrant.register(with: self) initializePlatformChannel() diff --git a/lib/config/localized_values.dart b/lib/config/localized_values.dart index a6b4d219..614f3a54 100644 --- a/lib/config/localized_values.dart +++ b/lib/config/localized_values.dart @@ -378,6 +378,7 @@ const Map localizedValues = { "History": {"en": "History", "ar": "السجلات"}, "OrderNo": {"en": "Order No", "ar": "رقم الطلب"}, "OrderDetails": {"en": "Order Details", "ar": "تفاصيل الطلب"}, + "DeliveryDriverTrack": {"en": "Driver Tracking", "ar": "تتبع السائق"}, "VitalSign": {"en": "Vital Sign", "ar": "العلامة حيوية"}, "MonthlyReports": {"en": "Monthly Reports", "ar": "تقارير شهرية"}, "km": {"en": "KMs:", "ar": "كم"}, diff --git a/lib/pages/pharmacy/order/TrackDriver.dart b/lib/pages/pharmacy/order/TrackDriver.dart index 5a2e20c8..89e75e19 100644 --- a/lib/pages/pharmacy/order/TrackDriver.dart +++ b/lib/pages/pharmacy/order/TrackDriver.dart @@ -1,11 +1,16 @@ import 'dart:async'; +import 'dart:typed_data'; +import 'dart:ui' as ui; import 'package:async/async.dart'; import 'package:diplomaticquarterapp/config/config.dart'; import 'package:diplomaticquarterapp/core/model/pharmacies/order_model.dart'; import 'package:diplomaticquarterapp/core/service/parmacyModule/order-preview-service.dart'; import 'package:diplomaticquarterapp/locator.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_animarker/lat_lng_interpolation.dart'; import 'package:flutter_animarker/models/lat_lng_delta.dart'; import 'package:flutter_animarker/models/lat_lng_info.dart'; @@ -104,7 +109,12 @@ class _TrackDriverState extends State { @override Widget build(BuildContext context) { - return new Scaffold( + return AppScaffold( + appBarTitle: TranslationBase.of(context).deliveryDriverTrack, + isShowAppBar: true, + isPharmacy: true, + showPharmacyCart: false, + showHomeAppBarIcon: false, body: GoogleMap( myLocationEnabled: true, compassEnabled: true, @@ -127,13 +137,10 @@ class _TrackDriverState extends State { void setSourceAndDestinationIcons() async { - sourceIcon = await BitmapDescriptor.fromAssetImage( - ImageConfiguration(devicePixelRatio: 2.5), - 'assets/images/map_markers/source_map_marker.png'); - - destinationIcon = await BitmapDescriptor.fromAssetImage( - ImageConfiguration(devicePixelRatio: 2.5), - 'assets/images/map_markers/destination_map_marker.png'); + final Uint8List srcMarkerBytes = await getBytesFromAsset('assets/images/map_markers/source_map_marker.png', getMarkerIconSize()); + final Uint8List destMarkerBytes = await getBytesFromAsset('assets/images/map_markers/destination_map_marker.png', getMarkerIconSize()); + sourceIcon = await BitmapDescriptor.fromBytes(srcMarkerBytes); + destinationIcon = await BitmapDescriptor.fromBytes(destMarkerBytes); } CameraPosition _orderDeliveryLocationCamera(){ @@ -298,4 +305,15 @@ class _TrackDriverState extends State { stopUpdatingDriverLocation(){ isLocationUpdating = false; } + + Future getBytesFromAsset(String path, int width) async { + ByteData data = await rootBundle.load(path); + ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(), targetWidth: width); + ui.FrameInfo fi = await codec.getNextFrame(); + return (await fi.image.toByteData(format: ui.ImageByteFormat.png)).buffer.asUint8List(); + } + + int getMarkerIconSize(){ + return 140; + } } \ No newline at end of file diff --git a/lib/uitl/translations_delegate_base.dart b/lib/uitl/translations_delegate_base.dart index a3d67127..557dc6f7 100644 --- a/lib/uitl/translations_delegate_base.dart +++ b/lib/uitl/translations_delegate_base.dart @@ -562,6 +562,7 @@ class TranslationBase { String get reviewComment => localizedValues['reviewComment'][locale.languageCode]; String get shippedMethod => localizedValues['shippedMethod'][locale.languageCode]; String get orderDetail => localizedValues['orderDetail'][locale.languageCode]; + String get deliveryDriverTrack => localizedValues['DeliveryDriverTrack'][locale.languageCode]; String get subtotal => localizedValues['subtotal'][locale.languageCode]; String get shipping => localizedValues['shipping'][locale.languageCode]; String get shipBy => localizedValues['shipBy'][locale.languageCode]; diff --git a/lib/widgets/others/app_scaffold_widget.dart b/lib/widgets/others/app_scaffold_widget.dart index e5381ccb..ca91efe2 100644 --- a/lib/widgets/others/app_scaffold_widget.dart +++ b/lib/widgets/others/app_scaffold_widget.dart @@ -38,6 +38,7 @@ class AppScaffold extends StatelessWidget { final bool isBottomBar; final Widget floatingActionButton; final bool isPharmacy; + final bool showPharmacyCart; final String title; final String description; final bool isShowDecPage; @@ -61,6 +62,7 @@ class AppScaffold extends StatelessWidget { this.baseViewModel, this.floatingActionButton, this.isPharmacy = false, + this.showPharmacyCart = true, this.title, this.description, this.isShowDecPage = true, @@ -84,6 +86,7 @@ class AppScaffold extends StatelessWidget { appBarIcons: appBarIcons, showHomeAppBarIcon: showHomeAppBarIcon, isPharmacy: isPharmacy, + showPharmacyCart: showPharmacyCart, isShowDecPage: isShowDecPage, ) : null, @@ -119,6 +122,7 @@ class AppBarWidget extends StatelessWidget with PreferredSizeWidget { final bool showHomeAppBarIcon; final List appBarIcons; final bool isPharmacy; + final bool showPharmacyCart; final bool isShowDecPage; AppBarWidget( @@ -126,6 +130,7 @@ class AppBarWidget extends StatelessWidget with PreferredSizeWidget { this.showHomeAppBarIcon, this.appBarIcons, this.isPharmacy = true, + this.showPharmacyCart = true, this.isShowDecPage = true}); @override @@ -157,7 +162,7 @@ class AppBarWidget extends StatelessWidget with PreferredSizeWidget { ), centerTitle: true, actions: [ - isPharmacy + (isPharmacy && showPharmacyCart) ? IconButton( icon: Icon(Icons.shopping_cart), color: Colors.white, From 15ec3ec9b98dc68dbe204c96d26298ddf5c4170f Mon Sep 17 00:00:00 2001 From: Zohaib Iqbal Kambrani <> Date: Wed, 24 Feb 2021 18:18:26 +0300 Subject: [PATCH 3/7] Translation Track Driver, enhancement, initial camera --- lib/config/localized_values.dart | 2 + lib/pages/pharmacy/order/TrackDriver.dart | 142 +++++++++++++--------- lib/uitl/translations_delegate_base.dart | 2 + 3 files changed, 87 insertions(+), 59 deletions(-) diff --git a/lib/config/localized_values.dart b/lib/config/localized_values.dart index 614f3a54..6ab1a723 100644 --- a/lib/config/localized_values.dart +++ b/lib/config/localized_values.dart @@ -379,6 +379,8 @@ const Map localizedValues = { "OrderNo": {"en": "Order No", "ar": "رقم الطلب"}, "OrderDetails": {"en": "Order Details", "ar": "تفاصيل الطلب"}, "DeliveryDriverTrack": {"en": "Driver Tracking", "ar": "تتبع السائق"}, + "DeliveryLocation": {"en": "Delivery Location", "ar": "موقع التسليم"}, + "Driver": {"en": "Driver", "ar": "السائق"}, "VitalSign": {"en": "Vital Sign", "ar": "العلامة حيوية"}, "MonthlyReports": {"en": "Monthly Reports", "ar": "تقارير شهرية"}, "km": {"en": "KMs:", "ar": "كم"}, diff --git a/lib/pages/pharmacy/order/TrackDriver.dart b/lib/pages/pharmacy/order/TrackDriver.dart index 89e75e19..3b8779dd 100644 --- a/lib/pages/pharmacy/order/TrackDriver.dart +++ b/lib/pages/pharmacy/order/TrackDriver.dart @@ -7,6 +7,7 @@ import 'package:diplomaticquarterapp/config/config.dart'; import 'package:diplomaticquarterapp/core/model/pharmacies/order_model.dart'; import 'package:diplomaticquarterapp/core/service/parmacyModule/order-preview-service.dart'; import 'package:diplomaticquarterapp/locator.dart'; +import 'package:diplomaticquarterapp/uitl/gif_loader_dialog_utils.dart'; import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; import 'package:flutter/material.dart'; @@ -59,6 +60,7 @@ class _TrackDriverState extends State { @override void initState() { + super.initState(); _order = widget.order; DEST_LOCATION = _order.shippingAddress.getLocation(); @@ -69,7 +71,6 @@ class _TrackDriverState extends State { initMarkerUpdateStream(); startUpdatingDriverLocation(); - super.initState(); } @override @@ -98,6 +99,7 @@ class _TrackDriverState extends State { delta.from.latitude, delta.from.longitude, ), + onTap: onSourceMarkerTap ); _markers.removeWhere((m) => m.markerId.value == 'sourcePin'); @@ -121,10 +123,10 @@ class _TrackDriverState extends State { markers: _markers, polylines: _polylines, mapType: MapType.normal, - initialCameraPosition: _orderDeliveryLocationCamera(), + initialCameraPosition: CameraPosition(target: DEST_LOCATION, zoom: 4), onMapCreated: (GoogleMapController controller) { _controller.complete(controller); - showPinsOnMap(); + // showPinsOnMap(); }, ), // floatingActionButton: FloatingActionButton.extended( @@ -144,22 +146,27 @@ class _TrackDriverState extends State { } CameraPosition _orderDeliveryLocationCamera(){ - - final CameraPosition orderDeliveryLocCamera = CameraPosition( - bearing: CAMERA_BEARING, - target: DEST_LOCATION, - tilt: CAMERA_TILT, - zoom: CAMERA_ZOOM); - return orderDeliveryLocCamera; + if(DEST_LOCATION != null){ + final CameraPosition orderDeliveryLocCamera = CameraPosition( + bearing: CAMERA_BEARING, + target: DEST_LOCATION, + tilt: CAMERA_TILT, + zoom: CAMERA_ZOOM); + return orderDeliveryLocCamera; + } + return null; } CameraPosition _driverLocationCamera(){ - final CameraPosition driverLocCamera = CameraPosition( - bearing: CAMERA_BEARING, - target: SOURCE_LOCATION, - tilt: CAMERA_TILT, - zoom: CAMERA_ZOOM); - return driverLocCamera; + if(DEST_LOCATION != null) { + final CameraPosition driverLocCamera = CameraPosition( + bearing: CAMERA_BEARING, + target: SOURCE_LOCATION, + tilt: CAMERA_TILT, + zoom: CAMERA_ZOOM); + return driverLocCamera; + } + return null; } @@ -176,16 +183,6 @@ class _TrackDriverState extends State { } - Future _fitCameraBetweenBothPoints() async { - final GoogleMapController controller = await _controller.future; - final CameraPosition driverLocCamera = CameraPosition( - bearing: CAMERA_BEARING, - target: SOURCE_LOCATION, - tilt: CAMERA_TILT, - zoom: CAMERA_ZOOM); - controller.animateCamera(CameraUpdate.newCameraPosition(driverLocCamera)); - } - void showPinsOnMap() { // source pin if(SOURCE_LOCATION != null){ @@ -195,7 +192,9 @@ class _TrackDriverState extends State { _markers.add(Marker( markerId: MarkerId('sourcePin'), position: pinPosition, - icon: sourceIcon + icon: sourceIcon, + infoWindow: InfoWindow(title: TranslationBase.of(context).driver), + onTap: onSourceMarkerTap )); }); } @@ -208,7 +207,9 @@ class _TrackDriverState extends State { _markers.add(Marker( markerId: MarkerId('destPin'), position: destPosition, - icon: destinationIcon + icon: destinationIcon, + infoWindow: InfoWindow(title: TranslationBase.of(context).deliveryLocation), + onTap: onDestinationMarkerTap )); }); } @@ -218,36 +219,7 @@ class _TrackDriverState extends State { } void updatePinOnMap() async { - // create a new CameraPosition instance - // every time the location changes, so the camera - // follows the pin as it moves with an animation - CameraPosition cPosition = CameraPosition( - zoom: CAMERA_ZOOM, - tilt: CAMERA_TILT, - bearing: CAMERA_BEARING, - target: SOURCE_LOCATION, - ); - final GoogleMapController controller = await _controller.future; - controller.animateCamera(CameraUpdate.newCameraPosition(cPosition)); - _latLngStream.addLatLng(LatLngInfo(SOURCE_LOCATION.latitude, SOURCE_LOCATION.longitude, "sourcePin")); - - // do this inside the setState() so Flutter gets notified - // that a widget update is due - // setState(() { - // // updated position - // var pinPosition = SOURCE_LOCATION; - // - // // the trick is to remove the marker (by id) - // // and add it again at the updated location - // _markers.removeWhere((m) => m.markerId.value == 'sourcePin'); - // _markers.add(Marker( - // markerId: MarkerId('sourcePin'), - // position: pinPosition, // updated position - // icon: sourceIcon - // )); - // }); - drawRoute(); } @@ -283,9 +255,14 @@ class _TrackDriverState extends State { int driverId = int.tryParse(_order.driverID); Future.doWhile(() async{ - await Future.delayed(Duration(seconds: frequencyInSeconds)); if(isLocationUpdating){ + + await Future.delayed(Duration(seconds: frequencyInSeconds)); + + showLoading(); LatLng driverLocation = (await _orderServices.getDriverLocation(driverId)); + hideLoading(); + if(driverLocation != null){ if(SOURCE_LOCATION == null || DEST_LOCATION == null){ SOURCE_LOCATION = driverLocation; @@ -294,12 +271,26 @@ class _TrackDriverState extends State { } SOURCE_LOCATION = driverLocation; updatePinOnMap(); + updateMapCamera(); + }else{ + GifLoaderDialogUtils.hideDialog(context); } } return isLocationUpdating; }); + } + showLoading(){ + if(SOURCE_LOCATION == null){ + GifLoaderDialogUtils.showMyDialog(context); + } + } + + hideLoading(){ + if(SOURCE_LOCATION == null){ + GifLoaderDialogUtils.hideDialog(context); + } } stopUpdatingDriverLocation(){ @@ -316,4 +307,37 @@ class _TrackDriverState extends State { int getMarkerIconSize(){ return 140; } -} \ No newline at end of file + + updateMapCamera() async{ + if(SOURCE_LOCATION != null && DEST_LOCATION != null){ + + // 'package:google_maps_flutter_platform_interface/src/types/location.dart': Failed assertion: line 72 pos 16: 'southwest.latitude <= northeast.latitude': is not true. + LatLngBounds bound; + if(SOURCE_LOCATION.latitude <= DEST_LOCATION.latitude){ + bound = LatLngBounds(southwest: SOURCE_LOCATION, northeast: DEST_LOCATION); + }else{ + bound = LatLngBounds(southwest: DEST_LOCATION, northeast: SOURCE_LOCATION); + } + + if(bound == null) + return; + + CameraUpdate camera = CameraUpdate.newLatLngBounds(bound, 50); + final GoogleMapController controller = await _controller.future; + controller.animateCamera(camera); + } + } + + bool showSrcMarkerTitle = false; + onSourceMarkerTap() async{ + // showSrcMarkerTitle = !showSrcMarkerTitle; + } + + bool showDestMarkerTitle = false; + onDestinationMarkerTap() async{ + // showDestMarkerTitle = !showDestMarkerTitle; + // Marker m = _markers.firstWhere((m) => m.markerId.value == 'destPin'); + // if(showDestMarkerTitle){ + // } + } +} diff --git a/lib/uitl/translations_delegate_base.dart b/lib/uitl/translations_delegate_base.dart index 557dc6f7..9ed7b478 100644 --- a/lib/uitl/translations_delegate_base.dart +++ b/lib/uitl/translations_delegate_base.dart @@ -563,6 +563,8 @@ class TranslationBase { String get shippedMethod => localizedValues['shippedMethod'][locale.languageCode]; String get orderDetail => localizedValues['orderDetail'][locale.languageCode]; String get deliveryDriverTrack => localizedValues['DeliveryDriverTrack'][locale.languageCode]; + String get deliveryLocation => localizedValues['DeliveryLocation'][locale.languageCode]; + String get driver => localizedValues['Driver'][locale.languageCode]; String get subtotal => localizedValues['subtotal'][locale.languageCode]; String get shipping => localizedValues['shipping'][locale.languageCode]; String get shipBy => localizedValues['shipBy'][locale.languageCode]; From f410ce20caf1f4c64caa679924de53548da43a0e Mon Sep 17 00:00:00 2001 From: Zohaib Iqbal Kambrani <> Date: Wed, 10 Mar 2021 18:00:53 +0300 Subject: [PATCH 4/7] Packages and Offers in Progress --- assets/images/discount_ar.png | Bin 0 -> 4615 bytes assets/images/discount_en.png | Bin 0 -> 5958 bytes assets/images/svg/discount_ar.svg | 37 + assets/images/svg/discount_en.svg | 43 + ios/Flutter/.last_build_id | 2 +- ios/Podfile.lock | 786 +++++++++++++++++- lib/config/config.dart | 4 +- lib/config/localized_values.dart | 1 + .../AddProductToCartRequestModel.dart | 26 + .../requests/CreateCustomerRequestModel.dart | 21 + .../OffersCategoriesRequestModel.dart | 8 +- .../requests/OffersProductsRequestModel.dart | 20 +- .../PackagesCartItemsResponseModel.dart | 132 +++ ...t => PackagesCategoriesResponseModel.dart} | 16 +- .../PackagesCustomerResponseModel.dart | 199 +++++ ...eModel.dart => PackagesResponseModel.dart} | 15 +- lib/core/service/client/base_app_client.dart | 107 ++- .../PackagesOffersServices.dart | 275 +++++- .../PackagesOffersViewModel.dart | 17 +- .../OfferCategoriesResponseModel_helper.dart | 8 +- .../OfferProductsResponseModel_helper.dart | 8 +- .../json/base/json_convert_content.dart | 36 +- lib/locator.dart | 9 +- lib/main.dart | 3 +- .../ClinicOfferAndPackagesPage.dart | 80 ++ .../OfferAndPackageDetailPage.dart | 228 +++++ .../OfferAndPackagesCartPage.dart | 376 +++++++++ .../packages_offers/OfferAndPackagesPage.dart | 416 +++++++++ lib/routes.dart | 9 +- lib/uitl/gif_loader_dialog_utils.dart | 6 +- lib/uitl/translations_delegate_base.dart | 5 + lib/uitl/utils.dart | 38 +- lib/widgets/CounterView.dart | 188 +++++ .../carousel_indicator.dart | 197 +++++ .../offers_packages/PackagesCartItemCard.dart | 217 +++++ .../offers_packages/PackagesOfferCard.dart | 157 ++++ .../offers_packages/offers_packages.dart | 1 - lib/widgets/others/StarRating.dart | 38 +- lib/widgets/others/app_scaffold_widget.dart | 44 +- pubspec.yaml | 7 +- 40 files changed, 3687 insertions(+), 93 deletions(-) create mode 100644 assets/images/discount_ar.png create mode 100644 assets/images/discount_en.png create mode 100644 assets/images/svg/discount_ar.svg create mode 100644 assets/images/svg/discount_en.svg create mode 100644 lib/core/model/packages_offers/requests/AddProductToCartRequestModel.dart create mode 100644 lib/core/model/packages_offers/requests/CreateCustomerRequestModel.dart create mode 100644 lib/core/model/packages_offers/responses/PackagesCartItemsResponseModel.dart rename lib/core/model/packages_offers/responses/{OfferCategoriesResponseModel.dart => PackagesCategoriesResponseModel.dart} (82%) create mode 100644 lib/core/model/packages_offers/responses/PackagesCustomerResponseModel.dart rename lib/core/model/packages_offers/responses/{OfferProductsResponseModel.dart => PackagesResponseModel.dart} (95%) create mode 100644 lib/pages/packages_offers/ClinicOfferAndPackagesPage.dart create mode 100644 lib/pages/packages_offers/OfferAndPackageDetailPage.dart create mode 100644 lib/pages/packages_offers/OfferAndPackagesCartPage.dart create mode 100644 lib/pages/packages_offers/OfferAndPackagesPage.dart create mode 100644 lib/widgets/CounterView.dart create mode 100644 lib/widgets/carousel_indicator/carousel_indicator.dart create mode 100644 lib/widgets/offers_packages/PackagesCartItemCard.dart create mode 100644 lib/widgets/offers_packages/PackagesOfferCard.dart diff --git a/assets/images/discount_ar.png b/assets/images/discount_ar.png new file mode 100644 index 0000000000000000000000000000000000000000..854d1c1f903e8e0d59bc54d20ec4dcaf2512208b GIT binary patch literal 4615 zcmbW5`9GB1|HqMi9o5)!Tca|D%2LRZHCr1l7m;bKiCcGw#+t2REOoobPOFr;Fk^`b zO$~~WG^8j+_Uua$KIiKDAAEn9hsXPz_v`t7o#i^`JS3k!X(b{kBgnzQA!2>P{456t zr#kxM=LId-qS8abAA!p!T!J|`gm$7o&UZu6zd1O>pIVzAcMLE1F=QWr?U#=Injv3v zLgVyxo4pOkRd%Mhiwme+*Qmcp%(J)$9i49V5~FB%)=xe>+5zdfOO{aUQ(RQl90wfz zIaN+1`#WEx>`urx7r-A)|3(a^%_Z5+epoUde^YRK?Us^7tJ{}puk)P+dcj>!&R6V< z4U8%JIoq>R^!tL@sCe|iqsi5i(ZO_kPZg!0AA_n9zvM+j2PJQPk7$fp+!jM*ST^RX zi}{v+$p2{H^*2N%Z9 zIG#*0Hm{vR#i-|)B5xhfAPEE}H%M=1f%H5j$gea2G~E>dO{Hkl6x!sE?9LdwbJ&5Q ze^5u|M)zjiHb>F5?V0Y;k;FvHp!$^4cw_6;rHUqL;LmsR7V&tGSr`<#Gd?CheSPJmmOH(&v#sT>i@=RpKg7qXLV zWD0QkKoR<99vuN)nvoKp0Y;E`fu$ze)C#=uqDko>iJy^@G=~n3h`oUUa@^k_ zF;Gw)01;8pK>%W*psxT#MnNx7&?Nv$L_uQ!B#44$0O-3h7!Ywq?!+KuHB?fIn*=5RjdW;=XgDe5X``UOJjWHKWT~BHv7qc4atS3C(3=)cJs63~h=( z%hQcQm2+Gdd!q!c%&Xrub4z8n;`hR7B|&8ELO4maAus4;}E3{|o=y&m-Qm!90PgBIx^LTo3^9r zO_faa=Cd7k&2x-gXRo%|p|qH>)^?li0Na<}4T#=yO#j@VOw*O|Wsf5x93A@m^Xr3G z{I`RDlAc%~GYT+X@FH)+c`mu4LTXfIx9f&9>B-9M@_{+tv1F>D-6cP3zl={G(y8xk z?HWDxj&>|@GAb##vv__ybi>C-(6cyfdzpi65S!n9b5+h*V?%YH?H6m=NS_^i&zxB8 zMj8TmAw0CPZ=ZMMY=3S{dGFH7ssmnlF$6kv%&3EW?``lNpp zwKQn9G1+UrK57|yPPse3606Bh5mGNJ_p(%omZ$8;Tc;W)CtY`pPUCQ!-B&zU*)QhU zUlNPHKaGQHC>E=K!ty0Ac$%SdBeMsalG z%+PanmfCd~s{r#R$l=^oRexML-BzAg;4V(%COUC3{r4w{D2Le7wDk{EYY?d>h>a2~ z!m4aY(|Dde_IGC$4sBCYfOQzy?Dr`G3>yUqyP$qSgwZl}KasMjlC~mkQi#*ZIp!Mr zI|Ww3&}723OO(2hCO2M}61|Dz=XC$nXPeX26rdFhD9YyWgjK|8B-E@wnE>!pijV-S zog>aE7G|3vJIPcaQuS zaui0nS>lMvnuu&+^y9@Zy+6JN73AX^Z*ikNc(M_EH&8ytEBd@;t9;P1{*mt6b94Bd z@yAEL+;ry?8;cfW#9chYM}fV2Kd2Y!DT7ZT@K;gtmxAo5?qd!=*OIYz6kYhPJFlEO zd{BbMIn?rNXwo6s)?8!9F0xa{y@Ec>w;ZQ4$oh1JN8`tBtN61?G#hYoxe@G1vB#=#bLi4qbV;* z9yU8WHvFEUnbvo|dzkcphaGuQW>dvy{ovbwrM?rCHO87q$qxzT&SaUueNd)ezl99gsVX>zE zT$>Q%V=Ant_K46@{qu9W;kgp||D+>q{_K>OaRL&!M4Pf4bZ339Q;5S}K|)N&S%Ypd zmZV$kmKK`|dvr(fJrjc_81DwgN&P3Y(+{LT)JK050u}LCGlVyzeZpVZ`<@doy6SQZ zy6~ZvDO$z2)ur&v68fH$sIvqW zFOPvz)RGPb=YezzthoCm7>-kI)0NuyUlbvJ>v5(M#GcfO&)=8)5XAN+AYPHxH*lyc zKE^O5>kCPM&r^B^_&RLR@U!l2tBvI6J-IpIb-u%rwOJd}NQDObQ>RbWg2(3BRpT|c z*)+SMzq^%(cbWgL8@y*#gG| zy84Ip8V&(*Mm|fE-#e~syel@pyj(RU?bg!`Qj%8);xzxhtZc2e7rLK*&S*Vdo!QGU z)kb(J@IY`(p#A6Qt;FczdQ0T3#HBC+PYSz`=KTGOZ;=~zxydi>+G23%!*_5R!E>jP#Oat?sv<@MIEui?7Kf5)n1Ru^8hydew~7-)u- zyFLD$7pMqLxQ?)8h(-0Mee6p+L*gD~kg;xW_rr|tDw)r9)#}czcS{wZ%N9QfEpoK$ zR^0{VzpElmk>P(`nSz0Ao~WBo_d|Q6eJ%Cnif`XnFSN^Rsae1gdHNkiA+jxNcUk=% zz6mQKNv&-+W2;=h$pN;fbgxY5Ok6K7kw?-=5F%6N{!*`dut)Yzy{w(w=CNxP#SsEi z-oAYwT`VfTs1kXcsYN%i-zr;Wa;#4ITKOz-lKgxuElWJwqwEXwi!+bi7(ewRzu-Ts zhbMhnOS>X9&H?|B8hp2$xwZV=HrMv4dUvzx5g*@O9#u8Omk}lBuM)vc#0*W$P5}E- zvuCl7(5!c)_WSho=J+q4!+gWjZ>2D}I|YM;#An$4at7m#aW7wL)tK#M@#lu#l2mhB z=UDz&fB)ZLPE^gi zfh?op$fzlcE+kgw$+6#di23TWe660xR79wLoZO(kV#xOBgBioG?(qthhG~ zk`?hTa_Sk;1r1VoV&dDGpRc!q8h_UWE*6&F#yzmUyJqyud~5A>$>udjmk^HV{@S+D zC(0XK65zZzQ$|QU8t-1BFu~o8xo3f3N?o%P_|A^)Pn@qylY2utJ^s(;;!Rl5E3Nj| zza~VT;oxdA*3?%O)|)Qec-7`7j$=JPK{p+B0L{eX^eaCxz(EcM;nJ`IwspB_+>-YJ zRXNV-U)vrfh>T^HK2JndsRSoSRo5pN94no`zA1@6m+6;`HKF_4QY08$GvH+-3;>*~ zfdgYgY(H&CQnD=I=y4==Zx>xy0WvY*&x$8wZ>AIUl3-5oS~mkh20Ch0Ai=Cm0D-tV+QY{@40={E>Yyq?l6e^Ptct6HLT^Rm*cb-aM>Dz%u+_j1 z*|P-w{HTQAmJTE);c=+s#cpunU@_%)fFzdAgGv%K z$*4s1Gb&k_Mq8hDyGTqE#@O6{w`{8Y+2C0Sb<9W}rQmvm_o=7Y^11y5~M8 z!2!*AVgxM`tA3IP1g}Qy&_D^jyg2vYUqY4Mp)!DSC#4HD?#Q-A6ZBohK(W)#G*Fo% z&kql9CJtq&=pd5>5;z>t!=LluYgaVrMbI8PGn^jKTDq^&5{V6$7Rl;_ha_)_^+z+xH7WIoyL;Y4izKZ&N!p@tF9vMUs4ZIGC2FyCubfN7U z)lHp}|DSJ5DmjV*i*Knm=#14n zq6=rzZ-dVH<>MDng7YYfyKL=)2K^8I1U*?OuG|J?Umke*CzwW8exBJCs1)&fxkn(mz^H*@CY5@{{HG?J>KKt0c~5Go7@a~z09(I1{)6l zuqJ;lq2!#|-(;34O2Rh%Roo{8p(7>l&h>VOj4OIhH(yx~Kd0q3tQ9?Lz}BBx>bldP z77MmflPCorDd7iEt6Qj-i%5J9XA~|-w!J-kRv(UUaty#e8~BsWN#&H?w~I zj@hgaYN8@_LZRNAf{En!TxP%bF{k#P!1GDRM5>qzp%oF7TiOUyYA+w4O27lEYH{!g zMlfAWeFT7*IZ>jpbPzChiqAF<LadTLP14_FG=I^nIA}X!56n5Z=-fGX zXLy{Zr9;zWr??1mU0t(PK`gW#Jv}{G*iS>*PBtAmCqu5M2oY7>t7(34rJ}+%-&H zq6olM0hcA9cngYyU^+z!>Q!>kb~!8j0tf3aOSH0|DId;Z7+39ECM}JOj0nsi&qrar z<9AG!$&o5b5o@f)X3mFpJ-_HLtvuqyWj8dRJtaY$^g)d1RuP?uc_mJIQXmJWHEC5u zC1TQ)kxwr}5S*C7#r{9)M?-1_hVESXcu?V&tX1=)oqS}c)D`0nirDG7LvWpj@z5VM zG4chcvtB8C+ba-#-@Ty8TnXB(&0F&m<4xXS1h1s=fQawUzc%AOb2bB>VqjKOqjW~j zN|8Vs?bdC<hN)<@JqTP!G39}Ltx1cm}Q)-Fxk{U6)2_Ug=Hr)z2+I5qzC<1I6MMwLkj zmmTsn-jN;+pMM?}yjV>%4nwg0$(ATsc?Um!P~8%lR8Z}^v0oVRdHl_ms>U=$V0z>uHUsiyT396mO?`-k>-h))lGdINi#p0r3@0 zH$s4R4Zy62RIp8R-Hz+e3F95-QANY)Ko@<~&hARsivH*>XVK61mdP)gPT8Lb)!c0T z#XI`uM|sPPjm~*r7ZkEGMu#+S^lZrls|)oI=$FXdH1*tUNR~xmQn*jktKWMeHa)3T zv7_n*_5$$cS_t)IVcH5T;7~Y~BmeVr3*KlHHBjdh0C#z9e10vUTtlwQT9jF-FpGI5 z7vXV4L#Hax=hs+dqR`K;^hgpJxaRTLsuwK<4P=sPeN53Lqv}MsI1wjK0y%JA^y1XL zTUCTDU3t~K&CA{%%h17{Ab%}j9;Y8*-dC9MM`*OD!~harjKp#K^I~-sqj2!}V+2Qs z+>+Lrv~PfW_cKXcR}35>PLgtP8oK7LQorb1XHMB`PuNayK*qw{Z$ta(#jvDR&X|zq zr{U(U@Vd}Ryixex$U;?(1!d2Qs`_o<^?#Qx`YHD6hZ21c;G9QCV$;5fC1?YOrp1(c zSYRmqWh?M#Miuu?NJ!o-?d&EwHSEs-b{q%-uDrw$Rgj0K3RV;($%xj%zjuc z?s#7bn_cn6{H(xnx+$6%XOA%^>cOF4mQo#CI-rjc%&Hr}4ZYnb9|MLz`_#zn7Dp|5 zdXApobl<37j~jSvoRQ;>bN^EQ)g=mudqknF1Be+?st5rubtAVln>U>Wq z5TvPpyr@8o2Hk5nLbC!~d!756#EIJ{PmqosPYO7p(ccK05P-k_ClSt2V>Jj4lWF}R za<>LvqP!rlEnojvYoXyYxtD4=zOD^nb!-0BCX}i4D^!6YP}Zcu-pYp{$u?TzN`O=s zz^Nim8o(FEn#~*EQ1N`f5Va_3pAFuB|BpX1L{-~rFkgtNsX+iT+@Wl8?3i(&k)d5v z9RKuwo9$?ESbplPRCRu5=jg*xbl7`zsDbL(@9TiK862kAEUFPI7X2$&tC=}A5}znBs@^c!N7{= zq~%bE?S(}h-Qn4}_qi1s;e@7i%v;av1TJ^`%<8vnyQV;9LEU4{yA*JiS4P*Nkl)$o z*dmU3@$jYHTe8kOlnu`f&QE)RM+Q!R`u5_%LGJ6WQaPTd6RfN`pQksLynd~&uV?Mv z3$LhGO(vJ$O|(MiaKOB3!AESVZmvVVxSiJz^&{;{U5z)t_oXCt!)W!?le@`(;@3Q( zhM~HpoDbZ#qZH=@ilktD=FEk*=6@D)d{w|LUnKQ(^_l-AyF;n zk+6G%b^G8ViB*F@SoM$4TmeuFpET*FT-W*K{gUC~>GLO8)Sm6Y6LMiIN9ir^6!Oz- zC^#jS_S13PzO~uCF{R+1lvkD$@6~vPEbxhN8&TsQx=VW{H9_9G;}^P~E@wWdZfO$Y z6*^^Np;pC`y3(L+6kI#Buvf!AD!;(cdl{n!nl!$f{dk`%}G)@j!zIadN3tk+V_rmzEC!~|SOA9&J}7%BRsvnVeDqNigeCrNe2(fip|mq!CwiaHmZf>| zRo%j5DGXw$6N^zaq9su;M!CK2zKYfX@4rZBcy2cplHq{o%Ls5i zAkQGv?CP_qEZV>+JYR-EBK&@X&PzD0W;EZ%3d@PVE$%7;S3&raQn3}Zf77dzKZkiB z-_Ot=y7CHzQ{>d~RcJ@$JWU^o=AIO)gcoGM@cWmQ~v`n6A|-01E3hK=LlW zs?=Maq#IVvRWo!&24Zv3%7|5qGAvN(S|{qihm6}+Coum$Q!2Lu`*k>9<0<7>JOhCC zZ9ioLVH++?Il=O>&wuM#zHvT_8UEG34;`qgo^PD7vT;p2gh1l((&c@xdb&~+Pmp%M~%KrP7TOlY-i%PN30P3_(JisLUCQGk2bbU zDNoeX@+5X;h6?}=Zox*9VLw1}ZG1YSmodRlpizu-+rc5__7U%C2Wu2%4*$TC2ma}p=r#ooLMNcHKH6yL2u6x;DVh} z31r*WwY8QhM3|+=J8idmZMUG|J|m-iD}Rp-TbS3;YlvVk?xa8bEJnx+Y zwZ)E!K7P>y)N=yN5%Vxrx)wwTJ63Gdyg$OQ(e-fELG-w6Wj2N_-2A?{BuF=XS8btI zc<+#ZUt^r*-5^^@n#6z(ssj%7YFt_P)*?zXh{IzP@3^w$ifWzBn9H{`T&FOePv;AQ zwHyUNrWpjP1IwkzYpuAw6iCrNWuEI1Ma`{C1RsO4=MdIkh5`G2vsn?xJSjN2QZK`o zy2|pbWs^WuZzN*9P@L1QC%aoHYOxiMQ3M&vL(}RIk%gfMHuGnZcj9Ijvie}UwQjw^ zdDmL4acbZ$r?u*?aU377(D~6FoLP>~`uesEaqk9{2E9?sy|pqLv`uFS@QS*kp0GUXa6rdrjh|s@Ln*;gU_!3fiJ2Sb@+2r?nS;w4>BXNc=Xa_p zMJ&Ih$T&5b-sjF|L1)YiJ8#4cT0Pj14}TIevGjg*e*UsO?bXJ`WUqZFPIFs{WB8C( z`vRi~C*;j6{#PA2VHwa(HK7czm2c+^91G8b4ewpELKI;>-xWhEA>u~K#>T6m6Qi^n z`_%59pl#OEB{?TByR|Z=I9a}vC3JjxabmYAY&rV)Gay@?Sx|RP6Mw-npm*=RrQ%enF)5A+&W<3ZZ()L+;{E?RB z-oU0y3?;Q~5lFs-l~7o85@kf`#JnYydBk>7)i9D0*L^FE^u3aRQPjC^Ws8jQbR{^O zq2HADC}_p<;W`=SVXnB{DaeHjqZ9ixBEZP9MAq$C4H7|%D~6N z2fr`)i`e{el5b39l?wlu?hVk7Y8u~+yAM>V3yrz`eN0080`Ois#qarYoWWN0hcvp~ zpRewo^%EEt0DTLl;s)!I5*9QTWQy`NAYXig?6<{$n)}JUMnbyfECIAzDcXNM1A1BZ zeysqA($O_L)o0qkU1R;&law4f{Y}=6N8^>OowE>$5F8Vm=Ie2WQC(a? z!2H$L7!Oawa7*umM}*-Hp~S*T1JtU*Bd1z$Ox_VZ-^*ZeQYpO@XwAbb87-SjL6o(F zZxeyGfv0&xPW=m=;ZX}khfHkG9Xx#S+Tqwl%wPL#9VD7ht+>43dUvWhw0+w^c(`gO z7|$2+kWY zV&FG9!27;lWSGo;oSd&|MtQ<%+}@yc`o4Ogi6+;n0-5w~qf_yqMeRn4EJTC&DSt6F zS#(j5ejs!GzD47*FHmEgv)jm!^_ft?^HIOE-p5wA6n369@vB>p(*Vg)yX~}YV)oA9I+~(Y@D||clwHB z9ZdUH4Znh%iEQG;NffV8ob5H{Kbb)8ycC8{Ouu;;=nSWh-J#ww1&)DOIVN8v##bDy z@W8~%))EN&lWo2VJI^D+QP9lRx~NNgczKT*0nqapY~Bp8Y>ox>z5?RXpE$S$uh8n> z%+e>oekdT1ih)09nwRm?5A2x%%^v96={y*B1Ze00K`&1y3J{c$S%5+QJix}Alk&jl zw=+7~}a0QFTXMlj2LIbm&Xt`JbBriW^aYtZ5;sE`d((!!YunsUi zxfAL`5`o#h=9&OMPo^JG!SKBiF6Ihjejq^jz<-)6c(IKDb5E{jbA6(a-gD{D^C!73 zI1;S@-TXg;+^=9g!!tk=a{0>(Qf|{Ch80|?=xINLT=d~pz{2yY{%pTq(*HR&(p)10 z=Ak?ay-eY$?fYCD`@dMY%!p@v*ioyR6vBQIS literal 0 HcmV?d00001 diff --git a/assets/images/svg/discount_ar.svg b/assets/images/svg/discount_ar.svg new file mode 100644 index 00000000..d9d6ec27 --- /dev/null +++ b/assets/images/svg/discount_ar.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + مصخ + + + + diff --git a/assets/images/svg/discount_en.svg b/assets/images/svg/discount_en.svg new file mode 100644 index 00000000..294cf19c --- /dev/null +++ b/assets/images/svg/discount_en.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Flutter/.last_build_id b/ios/Flutter/.last_build_id index b8024672..3b4ea73f 100644 --- a/ios/Flutter/.last_build_id +++ b/ios/Flutter/.last_build_id @@ -1 +1 @@ -59a6c452ee075b50114918f17f1ad8f5 \ No newline at end of file +45a7d43b55124e1b314759554f9a14d3 \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 1df50aa4..0d6f024b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,25 +1,809 @@ PODS: + - abseil/algorithm (0.20200225.0): + - abseil/algorithm/algorithm (= 0.20200225.0) + - abseil/algorithm/container (= 0.20200225.0) + - abseil/algorithm/algorithm (0.20200225.0): + - abseil/base/config + - abseil/algorithm/container (0.20200225.0): + - abseil/algorithm/algorithm + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/base (0.20200225.0): + - abseil/base/atomic_hook (= 0.20200225.0) + - abseil/base/base (= 0.20200225.0) + - abseil/base/base_internal (= 0.20200225.0) + - abseil/base/bits (= 0.20200225.0) + - abseil/base/config (= 0.20200225.0) + - abseil/base/core_headers (= 0.20200225.0) + - abseil/base/dynamic_annotations (= 0.20200225.0) + - abseil/base/endian (= 0.20200225.0) + - abseil/base/errno_saver (= 0.20200225.0) + - abseil/base/exponential_biased (= 0.20200225.0) + - abseil/base/log_severity (= 0.20200225.0) + - abseil/base/malloc_internal (= 0.20200225.0) + - abseil/base/periodic_sampler (= 0.20200225.0) + - abseil/base/pretty_function (= 0.20200225.0) + - abseil/base/raw_logging_internal (= 0.20200225.0) + - abseil/base/spinlock_wait (= 0.20200225.0) + - abseil/base/throw_delegate (= 0.20200225.0) + - abseil/base/atomic_hook (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/base (0.20200225.0): + - abseil/base/atomic_hook + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/log_severity + - abseil/base/raw_logging_internal + - abseil/base/spinlock_wait + - abseil/meta/type_traits + - abseil/base/base_internal (0.20200225.0): + - abseil/base/config + - abseil/meta/type_traits + - abseil/base/bits (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/config (0.20200225.0) + - abseil/base/core_headers (0.20200225.0): + - abseil/base/config + - abseil/base/dynamic_annotations (0.20200225.0) + - abseil/base/endian (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/errno_saver (0.20200225.0): + - abseil/base/config + - abseil/base/exponential_biased (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/log_severity (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/malloc_internal (0.20200225.0): + - abseil/base/base + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/raw_logging_internal + - abseil/base/periodic_sampler (0.20200225.0): + - abseil/base/core_headers + - abseil/base/exponential_biased + - abseil/base/pretty_function (0.20200225.0) + - abseil/base/raw_logging_internal (0.20200225.0): + - abseil/base/atomic_hook + - abseil/base/config + - abseil/base/core_headers + - abseil/base/log_severity + - abseil/base/spinlock_wait (0.20200225.0): + - abseil/base/base_internal + - abseil/base/core_headers + - abseil/base/errno_saver + - abseil/base/throw_delegate (0.20200225.0): + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/container/compressed_tuple (0.20200225.0): + - abseil/utility/utility + - abseil/container/inlined_vector (0.20200225.0): + - abseil/algorithm/algorithm + - abseil/base/core_headers + - abseil/base/throw_delegate + - abseil/container/inlined_vector_internal + - abseil/memory/memory + - abseil/container/inlined_vector_internal (0.20200225.0): + - abseil/base/core_headers + - abseil/container/compressed_tuple + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/types/span + - abseil/memory (0.20200225.0): + - abseil/memory/memory (= 0.20200225.0) + - abseil/memory/memory (0.20200225.0): + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/meta (0.20200225.0): + - abseil/meta/type_traits (= 0.20200225.0) + - abseil/meta/type_traits (0.20200225.0): + - abseil/base/config + - abseil/numeric/int128 (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/strings/internal (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/base/raw_logging_internal + - abseil/meta/type_traits + - abseil/strings/str_format (0.20200225.0): + - abseil/strings/str_format_internal + - abseil/strings/str_format_internal (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/numeric/int128 + - abseil/strings/strings + - abseil/types/span + - abseil/strings/strings (0.20200225.0): + - abseil/base/base + - abseil/base/bits + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/base/raw_logging_internal + - abseil/base/throw_delegate + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/numeric/int128 + - abseil/strings/internal + - abseil/time (0.20200225.0): + - abseil/time/internal (= 0.20200225.0) + - abseil/time/time (= 0.20200225.0) + - abseil/time/internal (0.20200225.0): + - abseil/time/internal/cctz (= 0.20200225.0) + - abseil/time/internal/cctz (0.20200225.0): + - abseil/time/internal/cctz/civil_time (= 0.20200225.0) + - abseil/time/internal/cctz/time_zone (= 0.20200225.0) + - abseil/time/internal/cctz/civil_time (0.20200225.0): + - abseil/base/config + - abseil/time/internal/cctz/time_zone (0.20200225.0): + - abseil/base/config + - abseil/time/internal/cctz/civil_time + - abseil/time/time (0.20200225.0): + - abseil/base/base + - abseil/base/core_headers + - abseil/base/raw_logging_internal + - abseil/numeric/int128 + - abseil/strings/strings + - abseil/time/internal/cctz/civil_time + - abseil/time/internal/cctz/time_zone + - abseil/types (0.20200225.0): + - abseil/types/any (= 0.20200225.0) + - abseil/types/bad_any_cast (= 0.20200225.0) + - abseil/types/bad_any_cast_impl (= 0.20200225.0) + - abseil/types/bad_optional_access (= 0.20200225.0) + - abseil/types/bad_variant_access (= 0.20200225.0) + - abseil/types/compare (= 0.20200225.0) + - abseil/types/optional (= 0.20200225.0) + - abseil/types/span (= 0.20200225.0) + - abseil/types/variant (= 0.20200225.0) + - abseil/types/any (0.20200225.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/types/bad_any_cast + - abseil/utility/utility + - abseil/types/bad_any_cast (0.20200225.0): + - abseil/base/config + - abseil/types/bad_any_cast_impl + - abseil/types/bad_any_cast_impl (0.20200225.0): + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/types/bad_optional_access (0.20200225.0): + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/types/bad_variant_access (0.20200225.0): + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/types/compare (0.20200225.0): + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/types/optional (0.20200225.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/types/bad_optional_access + - abseil/utility/utility + - abseil/types/span (0.20200225.0): + - abseil/algorithm/algorithm + - abseil/base/core_headers + - abseil/base/throw_delegate + - abseil/meta/type_traits + - abseil/types/variant (0.20200225.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/types/bad_variant_access + - abseil/utility/utility + - abseil/utility/utility (0.20200225.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/meta/type_traits + - android_intent (0.0.1): + - Flutter + - barcode_scan_fix (0.0.1): + - Flutter + - MTBBarcodeScanner + - BoringSSL-GRPC (0.0.7): + - BoringSSL-GRPC/Implementation (= 0.0.7) + - BoringSSL-GRPC/Interface (= 0.0.7) + - BoringSSL-GRPC/Implementation (0.0.7): + - BoringSSL-GRPC/Interface (= 0.0.7) + - BoringSSL-GRPC/Interface (0.0.7) + - cloud_firestore (0.14.4): + - Firebase/CoreOnly (~> 6.33.0) + - Firebase/Firestore (~> 6.33.0) + - firebase_core + - Flutter + - cloud_firestore_web (0.1.0): + - Flutter + - connectivity (0.0.1): + - Flutter + - Reachability + - connectivity_for_web (0.1.0): + - Flutter + - connectivity_macos (0.0.1): + - Flutter + - device_calendar (0.0.1): + - Flutter + - device_info (0.0.1): + - Flutter + - DKImagePickerController/Core (4.3.2): + - DKImagePickerController/ImageDataManager + - DKImagePickerController/Resource + - DKImagePickerController/ImageDataManager (4.3.2) + - DKImagePickerController/PhotoGallery (4.3.2): + - DKImagePickerController/Core + - DKPhotoGallery + - DKImagePickerController/Resource (4.3.2) + - DKPhotoGallery (0.0.17): + - DKPhotoGallery/Core (= 0.0.17) + - DKPhotoGallery/Model (= 0.0.17) + - DKPhotoGallery/Preview (= 0.0.17) + - DKPhotoGallery/Resource (= 0.0.17) + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Core (0.0.17): + - DKPhotoGallery/Model + - DKPhotoGallery/Preview + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Model (0.0.17): + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Preview (0.0.17): + - DKPhotoGallery/Model + - DKPhotoGallery/Resource + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Resource (0.0.17): + - SDWebImage + - SwiftyGif + - file_picker (0.0.1): + - DKImagePickerController/PhotoGallery + - Flutter + - file_picker_web (0.0.1): + - Flutter + - Firebase/CoreOnly (6.33.0): + - FirebaseCore (= 6.10.3) + - Firebase/Firestore (6.33.0): + - Firebase/CoreOnly + - FirebaseFirestore (~> 1.18.0) + - Firebase/Messaging (6.33.0): + - Firebase/CoreOnly + - FirebaseMessaging (~> 4.7.0) + - firebase_core (0.5.3): + - Firebase/CoreOnly (~> 6.33.0) + - Flutter + - firebase_core_web (0.1.0): + - Flutter + - firebase_messaging (7.0.3): + - Firebase/CoreOnly (~> 6.33.0) + - Firebase/Messaging (~> 6.33.0) + - firebase_core + - Flutter + - FirebaseCore (6.10.3): + - FirebaseCoreDiagnostics (~> 1.6) + - GoogleUtilities/Environment (~> 6.7) + - GoogleUtilities/Logger (~> 6.7) + - FirebaseCoreDiagnostics (1.7.0): + - GoogleDataTransport (~> 7.4) + - GoogleUtilities/Environment (~> 6.7) + - GoogleUtilities/Logger (~> 6.7) + - nanopb (~> 1.30906.0) + - FirebaseFirestore (1.18.0): + - abseil/algorithm (= 0.20200225.0) + - abseil/base (= 0.20200225.0) + - abseil/memory (= 0.20200225.0) + - abseil/meta (= 0.20200225.0) + - abseil/strings/strings (= 0.20200225.0) + - abseil/time (= 0.20200225.0) + - abseil/types (= 0.20200225.0) + - FirebaseCore (~> 6.10) + - "gRPC-C++ (~> 1.28.0)" + - leveldb-library (~> 1.22) + - nanopb (~> 1.30906.0) + - FirebaseInstallations (1.7.0): + - FirebaseCore (~> 6.10) + - GoogleUtilities/Environment (~> 6.7) + - GoogleUtilities/UserDefaults (~> 6.7) + - PromisesObjC (~> 1.2) + - FirebaseInstanceID (4.8.0): + - FirebaseCore (~> 6.10) + - FirebaseInstallations (~> 1.6) + - GoogleUtilities/Environment (~> 6.7) + - GoogleUtilities/UserDefaults (~> 6.7) + - FirebaseMessaging (4.7.1): + - FirebaseCore (~> 6.10) + - FirebaseInstanceID (~> 4.7) + - GoogleUtilities/AppDelegateSwizzler (~> 6.7) + - GoogleUtilities/Environment (~> 6.7) + - GoogleUtilities/Reachability (~> 6.7) + - GoogleUtilities/UserDefaults (~> 6.7) + - Protobuf (>= 3.9.2, ~> 3.9) - Flutter (1.0.0) + - flutter_email_sender (0.0.1): + - Flutter + - flutter_flexible_toast (0.0.1): + - Flutter + - flutter_inappwebview (0.0.1): + - Flutter + - flutter_local_notifications (0.0.1): + - Flutter + - flutter_plugin_android_lifecycle (0.0.1): + - Flutter + - flutter_tts (0.0.1): + - Flutter + - FMDB (2.7.5): + - FMDB/standard (= 2.7.5) + - FMDB/standard (2.7.5) + - geolocator (6.2.0): + - Flutter + - google_maps_flutter (0.0.1): + - Flutter + - GoogleMaps (< 3.10) + - GoogleDataTransport (7.5.1): + - nanopb (~> 1.30906.0) + - GoogleMaps (3.9.0): + - GoogleMaps/Maps (= 3.9.0) + - GoogleMaps/Base (3.9.0) + - GoogleMaps/Maps (3.9.0): + - GoogleMaps/Base + - GoogleUtilities/AppDelegateSwizzler (6.7.2): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (6.7.2): + - PromisesObjC (~> 1.2) + - GoogleUtilities/Logger (6.7.2): + - GoogleUtilities/Environment + - GoogleUtilities/Network (6.7.2): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (6.7.2)" + - GoogleUtilities/Reachability (6.7.2): + - GoogleUtilities/Logger + - GoogleUtilities/UserDefaults (6.7.2): + - GoogleUtilities/Logger + - "gRPC-C++ (1.28.2)": + - "gRPC-C++/Implementation (= 1.28.2)" + - "gRPC-C++/Interface (= 1.28.2)" + - "gRPC-C++/Implementation (1.28.2)": + - abseil/container/inlined_vector (= 0.20200225.0) + - abseil/memory/memory (= 0.20200225.0) + - abseil/strings/str_format (= 0.20200225.0) + - abseil/strings/strings (= 0.20200225.0) + - abseil/types/optional (= 0.20200225.0) + - "gRPC-C++/Interface (= 1.28.2)" + - gRPC-Core (= 1.28.2) + - "gRPC-C++/Interface (1.28.2)" + - gRPC-Core (1.28.2): + - gRPC-Core/Implementation (= 1.28.2) + - gRPC-Core/Interface (= 1.28.2) + - gRPC-Core/Implementation (1.28.2): + - abseil/container/inlined_vector (= 0.20200225.0) + - abseil/memory/memory (= 0.20200225.0) + - abseil/strings/str_format (= 0.20200225.0) + - abseil/strings/strings (= 0.20200225.0) + - abseil/types/optional (= 0.20200225.0) + - BoringSSL-GRPC (= 0.0.7) + - gRPC-Core/Interface (= 1.28.2) + - gRPC-Core/Interface (1.28.2) + - hexcolor (0.0.1): + - Flutter + - image_cropper (0.0.3): + - Flutter + - TOCropViewController (~> 2.5.4) + - image_picker (0.0.1): + - Flutter + - just_audio (0.0.1): + - Flutter + - leveldb-library (1.22) + - local_auth (0.0.1): + - Flutter + - location (0.0.1): + - Flutter + - manage_calendar_events (0.0.1): + - Flutter + - map_launcher (0.0.1): + - Flutter + - maps_launcher (0.0.1): + - Flutter + - MTBBarcodeScanner (5.0.11) + - nanopb (1.30906.0): + - nanopb/decode (= 1.30906.0) + - nanopb/encode (= 1.30906.0) + - nanopb/decode (1.30906.0) + - nanopb/encode (1.30906.0) + - native_device_orientation (0.0.1): + - Flutter - NVActivityIndicatorView (5.1.1): - NVActivityIndicatorView/Base (= 5.1.1) - NVActivityIndicatorView/Base (5.1.1) + - path_provider (0.0.1): + - Flutter + - path_provider_linux (0.0.1): + - Flutter + - path_provider_macos (0.0.1): + - Flutter + - path_provider_windows (0.0.1): + - Flutter + - "permission_handler (5.1.0+2)": + - Flutter + - PromisesObjC (1.2.12) + - Protobuf (3.14.0) + - Reachability (3.2) + - screen (0.0.1): + - Flutter + - SDWebImage (5.10.4): + - SDWebImage/Core (= 5.10.4) + - SDWebImage/Core (5.10.4) + - shared_preferences (0.0.1): + - Flutter + - shared_preferences_linux (0.0.1): + - Flutter + - shared_preferences_macos (0.0.1): + - Flutter + - shared_preferences_web (0.0.1): + - Flutter + - shared_preferences_windows (0.0.1): + - Flutter + - speech_to_text (0.0.1): + - Flutter + - Try + - sqflite (0.0.2): + - Flutter + - FMDB (>= 2.7.5) + - SwiftyGif (5.4.0) + - TOCropViewController (2.5.5) + - Try (2.1.1) + - "twilio_programmable_video (0.5.0+4)": + - Flutter + - TwilioVideo (~> 3.4) + - TwilioVideo (3.8.0) + - url_launcher (0.0.1): + - Flutter + - url_launcher_linux (0.0.1): + - Flutter + - url_launcher_macos (0.0.1): + - Flutter + - url_launcher_web (0.0.1): + - Flutter + - url_launcher_windows (0.0.1): + - Flutter + - vibration (1.7.3): + - Flutter + - vibration_web (1.6.2): + - Flutter + - video_player (0.0.1): + - Flutter + - video_player_web (0.0.1): + - Flutter + - wakelock (0.0.1): + - Flutter + - webview_flutter (0.0.1): + - Flutter + - wifi (0.0.1): + - Flutter DEPENDENCIES: + - android_intent (from `.symlinks/plugins/android_intent/ios`) + - barcode_scan_fix (from `.symlinks/plugins/barcode_scan_fix/ios`) + - cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`) + - cloud_firestore_web (from `.symlinks/plugins/cloud_firestore_web/ios`) + - connectivity (from `.symlinks/plugins/connectivity/ios`) + - connectivity_for_web (from `.symlinks/plugins/connectivity_for_web/ios`) + - connectivity_macos (from `.symlinks/plugins/connectivity_macos/ios`) + - device_calendar (from `.symlinks/plugins/device_calendar/ios`) + - device_info (from `.symlinks/plugins/device_info/ios`) + - file_picker (from `.symlinks/plugins/file_picker/ios`) + - file_picker_web (from `.symlinks/plugins/file_picker_web/ios`) + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) + - firebase_core_web (from `.symlinks/plugins/firebase_core_web/ios`) + - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - Flutter (from `Flutter`) + - flutter_email_sender (from `.symlinks/plugins/flutter_email_sender/ios`) + - flutter_flexible_toast (from `.symlinks/plugins/flutter_flexible_toast/ios`) + - flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`) + - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) + - flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`) + - flutter_tts (from `.symlinks/plugins/flutter_tts/ios`) + - geolocator (from `.symlinks/plugins/geolocator/ios`) + - google_maps_flutter (from `.symlinks/plugins/google_maps_flutter/ios`) + - hexcolor (from `.symlinks/plugins/hexcolor/ios`) + - image_cropper (from `.symlinks/plugins/image_cropper/ios`) + - image_picker (from `.symlinks/plugins/image_picker/ios`) + - just_audio (from `.symlinks/plugins/just_audio/ios`) + - local_auth (from `.symlinks/plugins/local_auth/ios`) + - location (from `.symlinks/plugins/location/ios`) + - manage_calendar_events (from `.symlinks/plugins/manage_calendar_events/ios`) + - map_launcher (from `.symlinks/plugins/map_launcher/ios`) + - maps_launcher (from `.symlinks/plugins/maps_launcher/ios`) + - native_device_orientation (from `.symlinks/plugins/native_device_orientation/ios`) - NVActivityIndicatorView + - path_provider (from `.symlinks/plugins/path_provider/ios`) + - path_provider_linux (from `.symlinks/plugins/path_provider_linux/ios`) + - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) + - path_provider_windows (from `.symlinks/plugins/path_provider_windows/ios`) + - permission_handler (from `.symlinks/plugins/permission_handler/ios`) + - screen (from `.symlinks/plugins/screen/ios`) + - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) + - shared_preferences_linux (from `.symlinks/plugins/shared_preferences_linux/ios`) + - shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`) + - shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`) + - shared_preferences_windows (from `.symlinks/plugins/shared_preferences_windows/ios`) + - speech_to_text (from `.symlinks/plugins/speech_to_text/ios`) + - sqflite (from `.symlinks/plugins/sqflite/ios`) + - twilio_programmable_video (from `.symlinks/plugins/twilio_programmable_video/ios`) + - url_launcher (from `.symlinks/plugins/url_launcher/ios`) + - url_launcher_linux (from `.symlinks/plugins/url_launcher_linux/ios`) + - url_launcher_macos (from `.symlinks/plugins/url_launcher_macos/ios`) + - url_launcher_web (from `.symlinks/plugins/url_launcher_web/ios`) + - url_launcher_windows (from `.symlinks/plugins/url_launcher_windows/ios`) + - vibration (from `.symlinks/plugins/vibration/ios`) + - vibration_web (from `.symlinks/plugins/vibration_web/ios`) + - video_player (from `.symlinks/plugins/video_player/ios`) + - video_player_web (from `.symlinks/plugins/video_player_web/ios`) + - wakelock (from `.symlinks/plugins/wakelock/ios`) + - webview_flutter (from `.symlinks/plugins/webview_flutter/ios`) + - wifi (from `.symlinks/plugins/wifi/ios`) SPEC REPOS: trunk: + - abseil + - BoringSSL-GRPC + - DKImagePickerController + - DKPhotoGallery + - Firebase + - FirebaseCore + - FirebaseCoreDiagnostics + - FirebaseFirestore + - FirebaseInstallations + - FirebaseInstanceID + - FirebaseMessaging + - FMDB + - GoogleDataTransport + - GoogleMaps + - GoogleUtilities + - "gRPC-C++" + - gRPC-Core + - leveldb-library + - MTBBarcodeScanner + - nanopb - NVActivityIndicatorView + - PromisesObjC + - Protobuf + - Reachability + - SDWebImage + - SwiftyGif + - TOCropViewController + - Try + - TwilioVideo EXTERNAL SOURCES: + android_intent: + :path: ".symlinks/plugins/android_intent/ios" + barcode_scan_fix: + :path: ".symlinks/plugins/barcode_scan_fix/ios" + cloud_firestore: + :path: ".symlinks/plugins/cloud_firestore/ios" + cloud_firestore_web: + :path: ".symlinks/plugins/cloud_firestore_web/ios" + connectivity: + :path: ".symlinks/plugins/connectivity/ios" + connectivity_for_web: + :path: ".symlinks/plugins/connectivity_for_web/ios" + connectivity_macos: + :path: ".symlinks/plugins/connectivity_macos/ios" + device_calendar: + :path: ".symlinks/plugins/device_calendar/ios" + device_info: + :path: ".symlinks/plugins/device_info/ios" + file_picker: + :path: ".symlinks/plugins/file_picker/ios" + file_picker_web: + :path: ".symlinks/plugins/file_picker_web/ios" + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" + firebase_core_web: + :path: ".symlinks/plugins/firebase_core_web/ios" + firebase_messaging: + :path: ".symlinks/plugins/firebase_messaging/ios" Flutter: :path: Flutter + flutter_email_sender: + :path: ".symlinks/plugins/flutter_email_sender/ios" + flutter_flexible_toast: + :path: ".symlinks/plugins/flutter_flexible_toast/ios" + flutter_inappwebview: + :path: ".symlinks/plugins/flutter_inappwebview/ios" + flutter_local_notifications: + :path: ".symlinks/plugins/flutter_local_notifications/ios" + flutter_plugin_android_lifecycle: + :path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios" + flutter_tts: + :path: ".symlinks/plugins/flutter_tts/ios" + geolocator: + :path: ".symlinks/plugins/geolocator/ios" + google_maps_flutter: + :path: ".symlinks/plugins/google_maps_flutter/ios" + hexcolor: + :path: ".symlinks/plugins/hexcolor/ios" + image_cropper: + :path: ".symlinks/plugins/image_cropper/ios" + image_picker: + :path: ".symlinks/plugins/image_picker/ios" + just_audio: + :path: ".symlinks/plugins/just_audio/ios" + local_auth: + :path: ".symlinks/plugins/local_auth/ios" + location: + :path: ".symlinks/plugins/location/ios" + manage_calendar_events: + :path: ".symlinks/plugins/manage_calendar_events/ios" + map_launcher: + :path: ".symlinks/plugins/map_launcher/ios" + maps_launcher: + :path: ".symlinks/plugins/maps_launcher/ios" + native_device_orientation: + :path: ".symlinks/plugins/native_device_orientation/ios" + path_provider: + :path: ".symlinks/plugins/path_provider/ios" + path_provider_linux: + :path: ".symlinks/plugins/path_provider_linux/ios" + path_provider_macos: + :path: ".symlinks/plugins/path_provider_macos/ios" + path_provider_windows: + :path: ".symlinks/plugins/path_provider_windows/ios" + permission_handler: + :path: ".symlinks/plugins/permission_handler/ios" + screen: + :path: ".symlinks/plugins/screen/ios" + shared_preferences: + :path: ".symlinks/plugins/shared_preferences/ios" + shared_preferences_linux: + :path: ".symlinks/plugins/shared_preferences_linux/ios" + shared_preferences_macos: + :path: ".symlinks/plugins/shared_preferences_macos/ios" + shared_preferences_web: + :path: ".symlinks/plugins/shared_preferences_web/ios" + shared_preferences_windows: + :path: ".symlinks/plugins/shared_preferences_windows/ios" + speech_to_text: + :path: ".symlinks/plugins/speech_to_text/ios" + sqflite: + :path: ".symlinks/plugins/sqflite/ios" + twilio_programmable_video: + :path: ".symlinks/plugins/twilio_programmable_video/ios" + url_launcher: + :path: ".symlinks/plugins/url_launcher/ios" + url_launcher_linux: + :path: ".symlinks/plugins/url_launcher_linux/ios" + url_launcher_macos: + :path: ".symlinks/plugins/url_launcher_macos/ios" + url_launcher_web: + :path: ".symlinks/plugins/url_launcher_web/ios" + url_launcher_windows: + :path: ".symlinks/plugins/url_launcher_windows/ios" + vibration: + :path: ".symlinks/plugins/vibration/ios" + vibration_web: + :path: ".symlinks/plugins/vibration_web/ios" + video_player: + :path: ".symlinks/plugins/video_player/ios" + video_player_web: + :path: ".symlinks/plugins/video_player_web/ios" + wakelock: + :path: ".symlinks/plugins/wakelock/ios" + webview_flutter: + :path: ".symlinks/plugins/webview_flutter/ios" + wifi: + :path: ".symlinks/plugins/wifi/ios" SPEC CHECKSUMS: + abseil: 6c8eb7892aefa08d929b39f9bb108e5367e3228f + android_intent: 367df2f1277a74e4a90e14a8ab3df3112d087052 + barcode_scan_fix: 80dd65de55f27eec6591dd077c8b85f2b79e31f1 + BoringSSL-GRPC: 8edf627ee524575e2f8d19d56f068b448eea3879 + cloud_firestore: b8c0e15fa49dfff87c2817d288b577e5dca2df13 + cloud_firestore_web: 9ec3dc7f5f98de5129339802d491c1204462bfec + connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467 + connectivity_for_web: 2b8584556930d4bd490d82b836bcf45067ce345b + connectivity_macos: e2e9731b6b22dda39eb1b128f6969d574460e191 + device_calendar: 23b28a5f1ab3bf77e34542fb1167e1b8b29a98f5 + device_info: d7d233b645a32c40dfdc212de5cf646ca482f175 + DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d + DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 + file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 + file_picker_web: 37b10786e88885124fac99dc899866e78a132ef3 + Firebase: 8db6f2d1b2c5e2984efba4949a145875a8f65fe5 + firebase_core: 5d6a02f3d85acd5f8321c2d6d62877626a670659 + firebase_core_web: d501d8b946b60c8af265428ce483b0fff5ad52d1 + firebase_messaging: 0aea2cd5885b65e19ede58ee3507f485c992cc75 + FirebaseCore: d889d9e12535b7f36ac8bfbf1713a0836a3012cd + FirebaseCoreDiagnostics: 770ac5958e1372ce67959ae4b4f31d8e127c3ac1 + FirebaseFirestore: adff4877869ca91a11250cc0989a6cd56bad163f + FirebaseInstallations: 466c7b4d1f58fe16707693091da253726a731ed2 + FirebaseInstanceID: bd3ffc24367f901a43c063b36c640b345a4a5dd1 + FirebaseMessaging: 5eca4ef173de76253352511aafef774caa1cba2a Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + flutter_email_sender: f787522d0e82f50e5766c1213dbffff22fdcf009 + flutter_flexible_toast: 0547e740cae0c33bb7c51bcd931233f4584e1143 + flutter_inappwebview: 69dfbac46157b336ffbec19ca6dfd4638c7bf189 + flutter_local_notifications: 9e4738ce2471c5af910d961a6b7eadcf57c50186 + flutter_plugin_android_lifecycle: dc0b544e129eebb77a6bfb1239d4d1c673a60a35 + flutter_tts: 0f492aab6accf87059b72354fcb4ba934304771d + FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + geolocator: f5e3de65e241caba7ce3e8a618803387bda73384 + google_maps_flutter: c7f9c73576de1fbe152a227bfd6e6c4ae8088619 + GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833 + GoogleMaps: 4b5346bddfe6911bb89155d43c903020170523ac + GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3 + "gRPC-C++": 13d8ccef97d5c3c441b7e3c529ef28ebee86fad2 + gRPC-Core: 4afa11bfbedf7cdecd04de535a9e046893404ed5 + hexcolor: fdfb9c4258ad96e949c2dbcdf790a62194b8aa89 + image_cropper: c8f9b4157933c7bb965a66d1c5e6c8fd408c6eb4 + image_picker: 9c3312491f862b28d21ecd8fdf0ee14e601b3f09 + just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa + leveldb-library: 55d93ee664b4007aac644a782d11da33fba316f7 + local_auth: 25938960984c3a7f6e3253e3f8d962fdd16852bd + location: 3a2eed4dd2fab25e7b7baf2a9efefe82b512d740 + manage_calendar_events: 0338d505ea26cdfd20cd883279bc28afa11eca34 + map_launcher: e325db1261d029ff33e08e03baccffe09593ffea + maps_launcher: eae38ee13a9c3f210fa04e04bb4c073fa4c6ed92 + MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb + nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc + native_device_orientation: e24d00be281de72996640885d80e706142707660 NVActivityIndicatorView: 1f6c5687f1171810aa27a3296814dc2d7dec3667 + path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c + path_provider_linux: 4d630dc393e1f20364f3e3b4a2ff41d9674a84e4 + path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 + path_provider_windows: a2b81600c677ac1959367280991971cb9a1edb3b + permission_handler: ccb20a9fad0ee9b1314a52b70b76b473c5f8dab0 + PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97 + Protobuf: 0cde852566359049847168e51bd1c690e0f70056 + Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 + screen: abd91ca7bf3426e1cc3646d27e9b2358d6bf07b0 + SDWebImage: c666b97e1fa9c64b4909816a903322018f0a9c84 + shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d + shared_preferences_linux: afefbfe8d921e207f01ede8b60373d9e3b566b78 + shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087 + shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9 + shared_preferences_windows: 36b76d6f54e76ead957e60b49e2f124b4cd3e6ae + speech_to_text: b43a7d99aef037bd758ed8e45d79bbac035d2dfe + sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 + SwiftyGif: 5d4af95df24caf1c570dbbcb32a3b8a0763bc6d7 + TOCropViewController: da59f531f8ac8a94ef6d6c0fc34009350f9e8bfe + Try: 5ef669ae832617b3cee58cb2c6f99fb767a4ff96 + twilio_programmable_video: 6a41593640f3d86af60b22541fd457b22deaae7f + TwilioVideo: c13a51ceca375e91620eb7578d2573c90cf53b46 + url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef + url_launcher_linux: ac237cb7a8058736e4aae38bdbcc748a4b394cc0 + url_launcher_macos: fd7894421cd39320dce5f292fc99ea9270b2a313 + url_launcher_web: e5527357f037c87560776e36436bf2b0288b965c + url_launcher_windows: 683d7c283894db8d1914d3ab2223b20cc1ad95d5 + vibration: b5a33e764c3f609a975b9dca73dce20fdde627dc + vibration_web: 0ba303d92469ba34d71c612a228b315908d7fcd9 + video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e + video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7 + wakelock: 0d4a70faf8950410735e3f61fb15d517c8a6efc4 + webview_flutter: d2b4d6c66968ad042ad94cbb791f5b72b4678a96 + wifi: d7d77c94109e36c4175d845f0a5964eadba71060 -PODFILE CHECKSUM: d94bd40f28772938199c67fcced06ffe96096c14 +PODFILE CHECKSUM: 5a17be3f8af73a757fa4439c77cf6ab2db29a6e7 COCOAPODS: 1.10.1 diff --git a/lib/config/config.dart b/lib/config/config.dart index cf1836ac..a167e1b5 100644 --- a/lib/config/config.dart +++ b/lib/config/config.dart @@ -8,9 +8,11 @@ import 'package:diplomaticquarterapp/widgets/mobile-no/mobile_no.dart'; const MAX_SMALL_SCREEN = 660; // PACKAGES and OFFERS -const EXA_CART_API_BASE_URL = 'https://mdlaboratories.com/exacartapi'; +const EXA_CART_API_BASE_URL = 'http://10.200.101.75:9000'; const PACKAGES_CATEGORIES = '/api/categories'; const PACKAGES_PRODUCTS = '/api/products'; +const PACKAGES_SHOPPING_CART = '/api/shopping_cart_items'; +const PACKAGES_CUSTOMER = '/api/customers'; const BASE_URL = 'https://uat.hmgwebservices.com/'; // const BASE_URL = 'https://hmgwebservices.com/'; diff --git a/lib/config/localized_values.dart b/lib/config/localized_values.dart index 6ab1a723..fa4d06f7 100644 --- a/lib/config/localized_values.dart +++ b/lib/config/localized_values.dart @@ -578,6 +578,7 @@ const Map localizedValues = { "bestSellers": {"en": "Best Sellers", "ar": "أفضل البائعين"}, "deleteAllItems": {"en": "Delete All Items", "ar": "حذف كافة العناصر"}, "total": {"en": "Total", "ar": "المجموع"}, + "totalWithColonRight": {"en": "Total:", "ar": ":المجموع"}, "selectAddress": {"en": "Select Address", "ar": "حدد العنوان"}, "shippingAddress": {"en": "SHIPPING ADDRESS", "ar": "عنوان الشحن"}, "changeAddress": {"en": "Change Address", "ar": "تغيير العنوان"}, diff --git a/lib/core/model/packages_offers/requests/AddProductToCartRequestModel.dart b/lib/core/model/packages_offers/requests/AddProductToCartRequestModel.dart new file mode 100644 index 00000000..69e56ad7 --- /dev/null +++ b/lib/core/model/packages_offers/requests/AddProductToCartRequestModel.dart @@ -0,0 +1,26 @@ +import 'package:flutter/cupertino.dart'; + +class AddProductToCartRequestModel { + int quantity; + int product_id; + String shopping_cart_type; + int customer_id; + + AddProductToCartRequestModel({@required this.product_id, this.customer_id, this.shopping_cart_type = "ShoppingCart", this.quantity = 1}); + + Map json() { + return { + "shopping_cart_item" : { + "quantity": quantity, + "product_id": product_id, + "shopping_cart_type": shopping_cart_type, + "customer_id": customer_id + } + }; + } +} + +class UpdateProductToCartRequestModel extends AddProductToCartRequestModel{ + UpdateProductToCartRequestModel({@required int product_id, @required int customer_id, String shopping_cart_type = "ShoppingCart", int quantity = 1}) + : super(customer_id: customer_id, product_id: product_id, quantity: quantity, shopping_cart_type: shopping_cart_type); +} diff --git a/lib/core/model/packages_offers/requests/CreateCustomerRequestModel.dart b/lib/core/model/packages_offers/requests/CreateCustomerRequestModel.dart new file mode 100644 index 00000000..116545bd --- /dev/null +++ b/lib/core/model/packages_offers/requests/CreateCustomerRequestModel.dart @@ -0,0 +1,21 @@ +import 'package:flutter/cupertino.dart'; + +class PackagesCustomerRequestModel { + + String email; + String phoneNumber; + + PackagesCustomerRequestModel({@required this.email, @required this.phoneNumber}); + + Map json() { + return { + "customer" : { + "email": email, + "addresses": [{ + "email": email, + "phone_number": phoneNumber + }] + } + }; + } +} \ No newline at end of file diff --git a/lib/core/model/packages_offers/requests/OffersCategoriesRequestModel.dart b/lib/core/model/packages_offers/requests/OffersCategoriesRequestModel.dart index f2f4af11..13dba945 100644 --- a/lib/core/model/packages_offers/requests/OffersCategoriesRequestModel.dart +++ b/lib/core/model/packages_offers/requests/OffersCategoriesRequestModel.dart @@ -6,6 +6,12 @@ class OffersCategoriesRequestModel { OffersCategoriesRequestModel({this.limit, this.page, this.sinceId}); Map toFlatMap() { - return {"limit": limit.toString(), "page": page.toString(), "sinceId": sinceId.toString()}; + return { + if(limit != null && limit > 0) + "limit": limit.toString(), + + if(page != null && page > 0) + "page": page.toString(), + }; } } diff --git a/lib/core/model/packages_offers/requests/OffersProductsRequestModel.dart b/lib/core/model/packages_offers/requests/OffersProductsRequestModel.dart index 84862568..583932bb 100644 --- a/lib/core/model/packages_offers/requests/OffersProductsRequestModel.dart +++ b/lib/core/model/packages_offers/requests/OffersProductsRequestModel.dart @@ -1,12 +1,24 @@ class OffersProductsRequestModel { final int categoryId; final int limit; - final int page; - final int sinceId; + // final int page; + int sinceId; - OffersProductsRequestModel({this.categoryId, this.limit, this.page, this.sinceId}); + OffersProductsRequestModel({this.categoryId, this.limit = 50}); Map toFlatMap() { - return {"limit": limit.toString(), "page": page.toString(), "sinceId": sinceId.toString(), "categoryId": categoryId.toString()}; + return { + + if(limit != null && limit > 0) + "limit": limit.toString(), + + // if(page != null && page > 0) + // "page": page.toString(), + + if(categoryId != null && categoryId > 0) + "categoryId": categoryId.toString(), + + "sinceId": sinceId != null ? sinceId.toString() : "0", + }; } } diff --git a/lib/core/model/packages_offers/responses/PackagesCartItemsResponseModel.dart b/lib/core/model/packages_offers/responses/PackagesCartItemsResponseModel.dart new file mode 100644 index 00000000..9514976f --- /dev/null +++ b/lib/core/model/packages_offers/responses/PackagesCartItemsResponseModel.dart @@ -0,0 +1,132 @@ + +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart'; + +class CartProductResponseModel { + int _quantity; + + set quantity(int value) { + _quantity = value; + } + + String _shoppingCartType; + int _productId; + PackagesResponseModel _product; + int _id; + + int get quantity => _quantity; + String get shoppingCartType => _shoppingCartType; + int get productId => _productId; + PackagesResponseModel get product => _product; + int get id => _id; + + CartProductResponseModel({ + int quantity, + String shoppingCartType, + int productId, + PackagesResponseModel product, + int id}){ + _quantity = quantity; + _shoppingCartType = shoppingCartType; + _productId = productId; + _product = product; + _id = id; +} + + CartProductResponseModel.fromJson(dynamic json) { + _quantity = json["quantity"]; + _shoppingCartType = json["shopping_cart_type"]; + _productId = json["product_id"]; + _product = json["product"] != null ? PackagesResponseModel().fromJson(json["product"]) : null; + _id = json["id"]; + } + + Map toJson() { + var map = {}; + map["quantity"] = _quantity; + map["shopping_cart_type"] = _shoppingCartType; + map["product_id"] = _productId; + if (_product != null) { + map["product"] = _product.toJson(); + } + map["id"] = _id; + return map; + } + +} + +class Images { + int _id; + int _pictureId; + int _position; + String _src; + dynamic _attachment; + + int get id => _id; + int get pictureId => _pictureId; + int get position => _position; + String get src => _src; + dynamic get attachment => _attachment; + + Images({ + int id, + int pictureId, + int position, + String src, + dynamic attachment}){ + _id = id; + _pictureId = pictureId; + _position = position; + _src = src; + _attachment = attachment; +} + + Images.fromJson(dynamic json) { + _id = json["id"]; + _pictureId = json["picture_id"]; + _position = json["position"]; + _src = json["src"]; + _attachment = json["attachment"]; + } + + Map toJson() { + var map = {}; + map["id"] = _id; + map["picture_id"] = _pictureId; + map["position"] = _position; + map["src"] = _src; + map["attachment"] = _attachment; + return map; + } + +} + +/// language_id : 1 +/// localized_name : "Dermatology testing" + +class Localized_names { + int _languageId; + String _localizedName; + + int get languageId => _languageId; + String get localizedName => _localizedName; + + Localized_names({ + int languageId, + String localizedName}){ + _languageId = languageId; + _localizedName = localizedName; +} + + Localized_names.fromJson(dynamic json) { + _languageId = json["language_id"]; + _localizedName = json["localized_name"]; + } + + Map toJson() { + var map = {}; + map["language_id"] = _languageId; + map["localized_name"] = _localizedName; + return map; + } + +} \ No newline at end of file diff --git a/lib/core/model/packages_offers/responses/OfferCategoriesResponseModel.dart b/lib/core/model/packages_offers/responses/PackagesCategoriesResponseModel.dart similarity index 82% rename from lib/core/model/packages_offers/responses/OfferCategoriesResponseModel.dart rename to lib/core/model/packages_offers/responses/PackagesCategoriesResponseModel.dart index 087f1961..292022c0 100644 --- a/lib/core/model/packages_offers/responses/OfferCategoriesResponseModel.dart +++ b/lib/core/model/packages_offers/responses/PackagesCategoriesResponseModel.dart @@ -1,8 +1,8 @@ import 'package:diplomaticquarterapp/generated/json/base/json_convert_content.dart'; import 'package:diplomaticquarterapp/generated/json/base/json_field.dart'; -class OfferCategoriesResponseModel with JsonConvert { - String id; +class PackagesCategoriesResponseModel with JsonConvert { + int id; String name; String namen; @JSONField(name: "localized_names") @@ -49,6 +49,18 @@ class OfferCategoriesResponseModel with JsonConvert { diff --git a/lib/core/model/packages_offers/responses/PackagesCustomerResponseModel.dart b/lib/core/model/packages_offers/responses/PackagesCustomerResponseModel.dart new file mode 100644 index 00000000..314b0ad3 --- /dev/null +++ b/lib/core/model/packages_offers/responses/PackagesCustomerResponseModel.dart @@ -0,0 +1,199 @@ +class PackagesCustomerResponseModel { + List _shoppingCartItems; + dynamic _billingAddress; + dynamic _shippingAddress; + List _addresses; + String _customerGuid; + dynamic _username; + String _email; + dynamic _firstName; + dynamic _lastName; + dynamic _languageId; + dynamic _dateOfBirth; + dynamic _gender; + dynamic _adminComment; + bool _isTaxExempt; + bool _hasShoppingCartItems; + bool _active; + bool _deleted; + bool _isSystemAccount; + dynamic _systemName; + dynamic _lastIpAddress; + String _createdOnUtc; + dynamic _lastLoginDateUtc; + String _lastActivityDateUtc; + int _registeredInStoreId; + bool _subscribedToNewsletter; + List _roleIds; + int _id; + + List get shoppingCartItems => _shoppingCartItems; + dynamic get billingAddress => _billingAddress; + dynamic get shippingAddress => _shippingAddress; + List get addresses => _addresses; + String get customerGuid => _customerGuid; + dynamic get username => _username; + String get email => _email; + dynamic get firstName => _firstName; + dynamic get lastName => _lastName; + dynamic get languageId => _languageId; + dynamic get dateOfBirth => _dateOfBirth; + dynamic get gender => _gender; + dynamic get adminComment => _adminComment; + bool get isTaxExempt => _isTaxExempt; + bool get hasShoppingCartItems => _hasShoppingCartItems; + bool get active => _active; + bool get deleted => _deleted; + bool get isSystemAccount => _isSystemAccount; + dynamic get systemName => _systemName; + dynamic get lastIpAddress => _lastIpAddress; + String get createdOnUtc => _createdOnUtc; + dynamic get lastLoginDateUtc => _lastLoginDateUtc; + String get lastActivityDateUtc => _lastActivityDateUtc; + int get registeredInStoreId => _registeredInStoreId; + bool get subscribedToNewsletter => _subscribedToNewsletter; + List get roleIds => _roleIds; + int get id => _id; + + PackagesCustomerResponseModel({ + List shoppingCartItems, + dynamic billingAddress, + dynamic shippingAddress, + List addresses, + String customerGuid, + dynamic username, + String email, + dynamic firstName, + dynamic lastName, + dynamic languageId, + dynamic dateOfBirth, + dynamic gender, + dynamic adminComment, + bool isTaxExempt, + bool hasShoppingCartItems, + bool active, + bool deleted, + bool isSystemAccount, + dynamic systemName, + dynamic lastIpAddress, + String createdOnUtc, + dynamic lastLoginDateUtc, + String lastActivityDateUtc, + int registeredInStoreId, + bool subscribedToNewsletter, + List roleIds, + int id}){ + _shoppingCartItems = shoppingCartItems; + _billingAddress = billingAddress; + _shippingAddress = shippingAddress; + _addresses = addresses; + _customerGuid = customerGuid; + _username = username; + _email = email; + _firstName = firstName; + _lastName = lastName; + _languageId = languageId; + _dateOfBirth = dateOfBirth; + _gender = gender; + _adminComment = adminComment; + _isTaxExempt = isTaxExempt; + _hasShoppingCartItems = hasShoppingCartItems; + _active = active; + _deleted = deleted; + _isSystemAccount = isSystemAccount; + _systemName = systemName; + _lastIpAddress = lastIpAddress; + _createdOnUtc = createdOnUtc; + _lastLoginDateUtc = lastLoginDateUtc; + _lastActivityDateUtc = lastActivityDateUtc; + _registeredInStoreId = registeredInStoreId; + _subscribedToNewsletter = subscribedToNewsletter; + _roleIds = roleIds; + _id = id; +} + + PackagesCustomerResponseModel.fromJson(dynamic json) { + _billingAddress = json["billing_address"]; + _shippingAddress = json["shipping_address"]; + _customerGuid = json["customer_guid"]; + _username = json["username"]; + _email = json["email"]; + _firstName = json["first_name"]; + _lastName = json["last_name"]; + _languageId = json["language_id"]; + _dateOfBirth = json["date_of_birth"]; + _gender = json["gender"]; + _adminComment = json["admin_comment"]; + _isTaxExempt = json["is_tax_exempt"]; + _hasShoppingCartItems = json["has_shopping_cart_items"]; + _active = json["active"]; + _deleted = json["deleted"]; + _isSystemAccount = json["is_system_account"]; + _systemName = json["system_name"]; + _lastIpAddress = json["last_ip_address"]; + _createdOnUtc = json["created_on_utc"]; + _lastLoginDateUtc = json["last_login_date_utc"]; + _lastActivityDateUtc = json["last_activity_date_utc"]; + _registeredInStoreId = json["registered_in_store_id"]; + _subscribedToNewsletter = json["subscribed_to_newsletter"]; + _id = json["id"]; + + // if (json["role_ids"] != null) { + // _roleIds = []; + // json["role_ids"].forEach((v) { + // _roleIds.add(dynamic.fromJson(v)); + // }); + // } + // if (json["addresses"] != null) { + // _addresses = []; + // json["addresses"].forEach((v) { + // _addresses.add(dynamic.fromJson(v)); + // }); + // } + // if (json["shopping_cart_items"] != null) { + // _shoppingCartItems = []; + // json["shopping_cart_items"].forEach((v) { + // _shoppingCartItems.add(dynamic.fromJson(v)); + // }); + // } + } + + Map toJson() { + var map = {}; + if (_shoppingCartItems != null) { + map["shopping_cart_items"] = _shoppingCartItems.map((v) => v.toJson()).toList(); + } + map["billing_address"] = _billingAddress; + map["shipping_address"] = _shippingAddress; + if (_addresses != null) { + map["addresses"] = _addresses.map((v) => v.toJson()).toList(); + } + map["customer_guid"] = _customerGuid; + map["username"] = _username; + map["email"] = _email; + map["first_name"] = _firstName; + map["last_name"] = _lastName; + map["language_id"] = _languageId; + map["date_of_birth"] = _dateOfBirth; + map["gender"] = _gender; + map["admin_comment"] = _adminComment; + map["is_tax_exempt"] = _isTaxExempt; + map["has_shopping_cart_items"] = _hasShoppingCartItems; + map["active"] = _active; + map["deleted"] = _deleted; + map["is_system_account"] = _isSystemAccount; + map["system_name"] = _systemName; + map["last_ip_address"] = _lastIpAddress; + map["created_on_utc"] = _createdOnUtc; + map["last_login_date_utc"] = _lastLoginDateUtc; + map["last_activity_date_utc"] = _lastActivityDateUtc; + map["registered_in_store_id"] = _registeredInStoreId; + map["subscribed_to_newsletter"] = _subscribedToNewsletter; + if (_roleIds != null) { + map["role_ids"] = _roleIds.map((v) => v.toJson()).toList(); + } + map["id"] = _id; + return map; + } + +} \ No newline at end of file diff --git a/lib/core/model/packages_offers/responses/OfferProductsResponseModel.dart b/lib/core/model/packages_offers/responses/PackagesResponseModel.dart similarity index 95% rename from lib/core/model/packages_offers/responses/OfferProductsResponseModel.dart rename to lib/core/model/packages_offers/responses/PackagesResponseModel.dart index 58481a8b..7297a20a 100644 --- a/lib/core/model/packages_offers/responses/OfferProductsResponseModel.dart +++ b/lib/core/model/packages_offers/responses/PackagesResponseModel.dart @@ -1,8 +1,8 @@ import 'package:diplomaticquarterapp/generated/json/base/json_convert_content.dart'; import 'package:diplomaticquarterapp/generated/json/base/json_field.dart'; -class OfferProductsResponseModel with JsonConvert { - String id; +class PackagesResponseModel with JsonConvert { + int id; @JSONField(name: "visible_individually") bool visibleIndividually; String name; @@ -205,6 +205,17 @@ class OfferProductsResponseModel with JsonConvert { int vendorId; @JSONField(name: "se_name") String seName; + + String getName() { + if(localizedNames.length == 2){ + if(localizedNames.first.languageId == 2) + return localizedNames.first.localizedName ?? name; + + else if(localizedNames.first.languageId == 1) + return localizedNames.last.localizedName ?? name; + } + return name; + } } class OfferProductsResponseModelLocalizedName with JsonConvert { diff --git a/lib/core/service/client/base_app_client.dart b/lib/core/service/client/base_app_client.dart index 429303bd..24fd202f 100644 --- a/lib/core/service/client/base_app_client.dart +++ b/lib/core/service/client/base_app_client.dart @@ -300,11 +300,45 @@ class BaseAppClient { } } + simplePost(String fullUrl, + { Map body, + Function(dynamic response, int statusCode) onSuccess, + Function(String error, int statusCode) onFailure,}) async { + + String url = fullUrl; + print("URL Query String: $url"); + + if (await Utils.checkConnection()) { + final response = await http.post( + url.trim(), + body: json.encode(body), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + ); + + final int statusCode = response.statusCode; + print("statusCode :$statusCode"); + + if (statusCode < 200 || statusCode >= 400 || json == null) { + onFailure('Error While Fetching data', statusCode); + } else { + onSuccess(response.body.toString(), statusCode); + } + } else { + onFailure('Please Check The Internet Connection', -1); + } + } + simpleGet(String fullUrl, {Function(dynamic response, int statusCode) onSuccess, - Function(String error, int statusCode) onFailure, - Map queryParams}) async { + Function(String error, int statusCode) onFailure, + Map queryParams}) async { + String url = fullUrl; + print("URL Query String: $url"); + var haveParams = (queryParams != null); if (haveParams) { String queryString = Uri(queryParameters: queryParams).query; @@ -334,6 +368,75 @@ class BaseAppClient { } } + + simplePut(String fullUrl, + { Map body, + Function(dynamic response, int statusCode) onSuccess, + Function(String error, int statusCode) onFailure}) async { + + String url = fullUrl; + print("URL Query String: $url"); + + if (await Utils.checkConnection()) { + final response = await http.put( + url.trim(), + body: json.encode(body), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + ); + + final int statusCode = response.statusCode; + print("statusCode :$statusCode"); + + if (statusCode < 200 || statusCode >= 400 || json == null) { + onFailure('Error While Fetching data', statusCode); + } else { + onSuccess(response.body.toString(), statusCode); + } + } else { + onFailure('Please Check The Internet Connection', -1); + } + } + + simpleDelete(String fullUrl, + {Function(dynamic response, int statusCode) onSuccess, + Function(String error, int statusCode) onFailure, + Map queryParams}) async { + + String url = fullUrl; + print("URL Query String: $url"); + + var haveParams = (queryParams != null); + if (haveParams) { + String queryString = Uri(queryParameters: queryParams).query; + url += '?' + queryString; + print("URL Query String: $url"); + } + + if (await Utils.checkConnection()) { + final response = await http.delete( + url.trim(), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + ); + + final int statusCode = response.statusCode; + print("statusCode :$statusCode"); + + if (statusCode < 200 || statusCode >= 400 || json == null) { + onFailure('Error While Fetching data', statusCode); + } else { + onSuccess(response.body.toString(), statusCode); + } + } else { + onFailure('Please Check The Internet Connection', -1); + } + } + logout() async { await sharedPref.remove(LOGIN_TOKEN_ID); await sharedPref.remove(PHARMACY_CUSTOMER_ID); diff --git a/lib/core/service/packages_offers/PackagesOffersServices.dart b/lib/core/service/packages_offers/PackagesOffersServices.dart index e58c3fa3..c0c7bc94 100644 --- a/lib/core/service/packages_offers/PackagesOffersServices.dart +++ b/lib/core/service/packages_offers/PackagesOffersServices.dart @@ -1,27 +1,42 @@ import 'dart:convert'; import 'dart:developer'; +import 'dart:ui'; import 'package:diplomaticquarterapp/config/config.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/requests/AddProductToCartRequestModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/requests/CreateCustomerRequestModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersCategoriesRequestModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersProductsRequestModel.dart'; -import 'package:diplomaticquarterapp/core/model/packages_offers/responses/OfferCategoriesResponseModel.dart'; -import 'package:diplomaticquarterapp/core/model/packages_offers/responses/OfferProductsResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCartItemsResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCategoriesResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCustomerResponseModel.dart'; import 'package:diplomaticquarterapp/core/service/base_service.dart'; import 'package:diplomaticquarterapp/core/service/client/base_app_client.dart'; +import 'package:diplomaticquarterapp/uitl/gif_loader_dialog_utils.dart'; +import 'package:flutter/cupertino.dart'; import '../../../locator.dart'; class OffersAndPackagesServices extends BaseService { - List categoryList = List(); + List categoryList = List(); + List productList = List(); + List latestOffersList = List(); + List bestSellerList = List(); + List bannersList = List(); + List cartItemList = List(); + + PackagesCustomerResponseModel customer; + + Future> getAllCategories(OffersCategoriesRequestModel request) async { + Future errorThrow; - Future> getAllCategories(OffersCategoriesRequestModel request) async { - hasError = false; var url = EXA_CART_API_BASE_URL + PACKAGES_CATEGORIES; await baseAppClient.simpleGet(url, onSuccess: (dynamic stringResponse, int statusCode) { if (statusCode == 200) { var jsonResponse = json.decode(stringResponse); jsonResponse['categories'].forEach((json) { - categoryList.add(OfferCategoriesResponseModel().fromJson(json)); + categoryList.add(PackagesCategoriesResponseModel().fromJson(json)); }); } }, onFailure: (String error, int statusCode) { @@ -31,15 +46,18 @@ class OffersAndPackagesServices extends BaseService { return categoryList; } - List productList = List(); - Future> getAllProducts(OffersProductsRequestModel request) async { - hasError = false; + Future> getAllProducts({@required OffersProductsRequestModel request, @required BuildContext context, @required bool showLoading = true}) async { + Future errorThrow; + + request.sinceId = (productList.isNotEmpty) ? productList.last.id : 0; + + productList = List(); var url = EXA_CART_API_BASE_URL + PACKAGES_PRODUCTS; await baseAppClient.simpleGet(url, onSuccess: (dynamic stringResponse, int statusCode) { if (statusCode == 200) { var jsonResponse = json.decode(stringResponse); jsonResponse['products'].forEach((json) { - productList.add(OfferProductsResponseModel().fromJson(json)); + productList.add(PackagesResponseModel().fromJson(json)); }); } }, onFailure: (String error, int statusCode) { @@ -48,4 +66,241 @@ class OffersAndPackagesServices extends BaseService { return productList; } + + Future> getLatestOffers({@required OffersProductsRequestModel request, @required BuildContext context, @required bool showLoading = true}) async { + + var url = EXA_CART_API_BASE_URL + PACKAGES_PRODUCTS; + await baseAppClient.simpleGet(url, onSuccess: (dynamic stringResponse, int statusCode) { + if (statusCode == 200) { + var jsonResponse = json.decode(stringResponse); + jsonResponse['products'].forEach((json) { + latestOffersList.add(PackagesResponseModel().fromJson(json)); + }); + } + }, onFailure: (String error, int statusCode) { + log(error); + }, queryParams: request.toFlatMap()); + + return latestOffersList; + } + + Future> getBestSellers({@required OffersProductsRequestModel request, @required BuildContext context, @required bool showLoading = true}) async { + + var url = EXA_CART_API_BASE_URL + PACKAGES_PRODUCTS; + await baseAppClient.simpleGet(url, onSuccess: (dynamic stringResponse, int statusCode) { + if (statusCode == 200) { + var jsonResponse = json.decode(stringResponse); + jsonResponse['products'].forEach((json) { + bestSellerList.add(PackagesResponseModel().fromJson(json)); + }); + } + }, onFailure: (String error, int statusCode) { + log(error); + }, queryParams: request.toFlatMap()); + + return bestSellerList; + } + + + Future> getBanners({@required OffersProductsRequestModel request, @required BuildContext context, @required bool showLoading = true}) async { + var url = EXA_CART_API_BASE_URL + PACKAGES_PRODUCTS; + await baseAppClient.simpleGet(url, onSuccess: (dynamic stringResponse, int statusCode) { + if (statusCode == 200) { + var jsonResponse = json.decode(stringResponse); + jsonResponse['products'].forEach((json) { + bannersList.add(PackagesResponseModel().fromJson(json)); + }); + } + }, onFailure: (String error, int statusCode) { + log(error); + }, queryParams: request.toFlatMap()); + + return bannersList; + } + + Future loadOffersPackagesDataForMainPage({@required BuildContext context, bool showLoading = true, Function completion }) async { + var finished = 0; + var totalCalls = 3; + + completedAll(){ + + finished++; + if(completion != null && finished == totalCalls) { + _hideLoading(context, showLoading); + completion(); + } + } + + _showLoading(context, showLoading); + + // Performing Parallel Request on same time + // # 1 + getBestSellers(request: OffersProductsRequestModel(), context: context, showLoading: false).then((value){ + completedAll(); + }); + + // # 2 + getLatestOffers(request: OffersProductsRequestModel(), context: context, showLoading: false).then((value){ + completedAll(); + }); + + // # 3 + getBanners(request: OffersProductsRequestModel(), context: context, showLoading: false).then((value){ + completedAll(); + }); + + } + + // -------------------- + // Create Customer + // -------------------- + Future createCustomer(PackagesCustomerRequestModel request, {@required BuildContext context, bool showLoading = true, Function(bool) completion }) async{ + if(customer != null) + return Future.value(customer); + + hasError = false; + var url = EXA_CART_API_BASE_URL + PACKAGES_CUSTOMER; + + customer = null; + _showLoading(context, showLoading); + await baseAppClient.simplePost(url, body: request.json(), onSuccess: (dynamic stringResponse, int statusCode){ + _hideLoading(context, showLoading); + + var jsonResponse = json.decode(stringResponse); + var customerJson = jsonResponse['customers'].first; + customer = PackagesCustomerResponseModel.fromJson(customerJson); + + }, onFailure: (String error, int statusCode){ + _hideLoading(context, showLoading); + log(error); + }); + + return customer; + } + + + + // -------------------- + // Shopping Cart + // -------------------- + Future> cartItems({@required BuildContext context, bool showLoading = true}) async{ + Future errorThrow; + + cartItemList.clear(); + _showLoading(context, showLoading); + var url = EXA_CART_API_BASE_URL + PACKAGES_SHOPPING_CART + '/${customer.id}'; + await baseAppClient.simpleGet(url, onSuccess: (dynamic stringResponse, int statusCode) { + _hideLoading(context, showLoading); + + var jsonResponse = json.decode(stringResponse); + jsonResponse['shopping_carts'].forEach((json) { + cartItemList.add(CartProductResponseModel.fromJson(json)); + }); + + }, onFailure: (String error, int statusCode) { + _hideLoading(context, showLoading); + log(error); + errorThrow = Future.error({"error":error, "statusCode":statusCode}); + }, queryParams: null); + + return errorThrow ?? cartItemList; + } + + Future addProductToCart(AddProductToCartRequestModel request, {@required BuildContext context, bool showLoading = true}) async{ + Future errorThrow; + + request.customer_id = customer.id; + + _showLoading(context, showLoading); + var url = EXA_CART_API_BASE_URL + PACKAGES_SHOPPING_CART; + await baseAppClient.simplePost(url, body: request.json(), onSuccess: (dynamic stringResponse, int statusCode){ + _hideLoading(context, showLoading); + + var jsonResponse = json.decode(stringResponse); + + }, onFailure: (String error, int statusCode){ + _hideLoading(context, showLoading); + log(error); + errorThrow = Future.error(error); + }); + + return errorThrow ?? true; + } + + Future updateProductToCart(int cartItemID, {UpdateProductToCartRequestModel request, @required BuildContext context, bool showLoading = true}) async{ + Future errorThrow; + + _showLoading(context, showLoading); + var url = EXA_CART_API_BASE_URL + PACKAGES_SHOPPING_CART + '/$cartItemID'; + await baseAppClient.simplePut(url, body: request.json(), onSuccess: (dynamic stringResponse, int statusCode){ + _hideLoading(context, showLoading); + + var jsonResponse = json.decode(stringResponse); + + }, onFailure: (String error, int statusCode){ + _hideLoading(context, showLoading); + log(error); + errorThrow = Future.error({"error":error, "statusCode":statusCode}); + }); + + return errorThrow ?? bannersList; + } + + + Future deleteProductFromCart(int cartItemID, {@required BuildContext context, bool showLoading = true}) async{ + Future errorThrow; + + _showLoading(context, showLoading); + var url = EXA_CART_API_BASE_URL + PACKAGES_SHOPPING_CART + '/$cartItemID'; + await baseAppClient.simpleDelete(url, onSuccess: (dynamic stringResponse, int statusCode){ + _hideLoading(context, showLoading); + // var jsonResponse = json.decode(stringResponse); + + }, onFailure: (String error, int statusCode){ + _hideLoading(context, showLoading); + log(error); + errorThrow = Future.error({"error":error, "statusCode":statusCode}); + }); + + return errorThrow ?? true; + } + + // -------------------- + // Place Order + // -------------------- + Future placeOrder({@required BuildContext context, bool showLoading = true}) async{ + Future errorThrow; + + var jsonBody = { + "order": { + "customer_id" : customer.id + } + }; + + _showLoading(context, showLoading); + var url = EXA_CART_API_BASE_URL + PACKAGES_SHOPPING_CART; + await baseAppClient.simplePost(url, body: jsonBody, onSuccess: (dynamic stringResponse, int statusCode){ + _hideLoading(context, showLoading); + + var jsonResponse = json.decode(stringResponse); + + }, onFailure: (String error, int statusCode){ + _hideLoading(context, showLoading); + log(error); + errorThrow = Future.error(error); + }); + + return errorThrow ?? true; + } + } + +_showLoading(BuildContext context, bool flag){ + if(flag) + GifLoaderDialogUtils.showMyDialog(context); +} + +_hideLoading(BuildContext context, bool flag){ + if(flag) + GifLoaderDialogUtils.hideDialog(context); +} \ No newline at end of file diff --git a/lib/core/viewModels/packages_offers/PackagesOffersViewModel.dart b/lib/core/viewModels/packages_offers/PackagesOffersViewModel.dart index 21df52bc..83e104dc 100644 --- a/lib/core/viewModels/packages_offers/PackagesOffersViewModel.dart +++ b/lib/core/viewModels/packages_offers/PackagesOffersViewModel.dart @@ -1,6 +1,7 @@ import 'package:diplomaticquarterapp/core/enum/viewstate.dart'; -import 'package:diplomaticquarterapp/core/model/packages_offers/responses/OfferCategoriesResponseModel.dart'; -import 'package:diplomaticquarterapp/core/model/packages_offers/responses/OfferProductsResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCartItemsResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCategoriesResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart'; import 'package:diplomaticquarterapp/core/service/base_service.dart'; import 'package:diplomaticquarterapp/core/service/client/base_app_client.dart'; import 'package:diplomaticquarterapp/core/service/packages_offers/PackagesOffersServices.dart'; @@ -10,10 +11,16 @@ import 'package:diplomaticquarterapp/locator.dart'; class OfferCategoriesViewModel extends BaseViewModel { OffersAndPackagesServices service = locator(); - List get list => service.categoryList; + get categoryList => service.categoryList; + get productList => service.categoryList; } -class OfferProductsViewModel extends BaseViewModel { +class PackagesViewModel extends BaseViewModel { OffersAndPackagesServices service = locator(); - List get list => service.productList; + List get categoryList => service.categoryList; + List get productList => service.productList; + List get latestOffersList => service.latestOffersList; + List get bestSellerList => service.bestSellerList; + List get bannersList => service.bannersList; + List get cartItemList => service.cartItemList; } diff --git a/lib/generated/json/OfferCategoriesResponseModel_helper.dart b/lib/generated/json/OfferCategoriesResponseModel_helper.dart index e5ebc914..1a28d179 100644 --- a/lib/generated/json/OfferCategoriesResponseModel_helper.dart +++ b/lib/generated/json/OfferCategoriesResponseModel_helper.dart @@ -1,8 +1,8 @@ -import 'package:diplomaticquarterapp/core/model/packages_offers/responses/OfferCategoriesResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCategoriesResponseModel.dart'; -offerCategoriesResponseModelFromJson(OfferCategoriesResponseModel data, Map json) { +offerCategoriesResponseModelFromJson(PackagesCategoriesResponseModel data, Map json) { if (json['id'] != null) { - data.id = json['id']?.toString(); + data.id = json['id']; } if (json['name'] != null) { data.name = json['name']?.toString(); @@ -91,7 +91,7 @@ offerCategoriesResponseModelFromJson(OfferCategoriesResponseModel data, Map offerCategoriesResponseModelToJson(OfferCategoriesResponseModel entity) { +Map offerCategoriesResponseModelToJson(PackagesCategoriesResponseModel entity) { final Map data = new Map(); data['id'] = entity.id; data['name'] = entity.name; diff --git a/lib/generated/json/OfferProductsResponseModel_helper.dart b/lib/generated/json/OfferProductsResponseModel_helper.dart index dc0edeca..d180af67 100644 --- a/lib/generated/json/OfferProductsResponseModel_helper.dart +++ b/lib/generated/json/OfferProductsResponseModel_helper.dart @@ -1,8 +1,8 @@ -import 'package:diplomaticquarterapp/core/model/packages_offers/responses/OfferProductsResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart'; -offerProductsResponseModelFromJson(OfferProductsResponseModel data, Map json) { +offerProductsResponseModelFromJson(PackagesResponseModel data, Map json) { if (json['id'] != null) { - data.id = json['id']?.toString(); + data.id = json['id']; } if (json['visible_individually'] != null) { data.visibleIndividually = json['visible_individually']; @@ -353,7 +353,7 @@ offerProductsResponseModelFromJson(OfferProductsResponseModel data, Map offerProductsResponseModelToJson(OfferProductsResponseModel entity) { +Map offerProductsResponseModelToJson(PackagesResponseModel entity) { final Map data = new Map(); data['id'] = entity.id; data['visible_individually'] = entity.visibleIndividually; diff --git a/lib/generated/json/base/json_convert_content.dart b/lib/generated/json/base/json_convert_content.dart index 9fab946e..ae4eadeb 100644 --- a/lib/generated/json/base/json_convert_content.dart +++ b/lib/generated/json/base/json_convert_content.dart @@ -3,11 +3,11 @@ // ignore_for_file: prefer_single_quotes // This file is automatically generated. DO NOT EDIT, all your changes would be lost. -import 'package:diplomaticquarterapp/core/model/packages_offers/responses/OfferCategoriesResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCategoriesResponseModel.dart'; import 'package:diplomaticquarterapp/generated/json/OfferCategoriesResponseModel_helper.dart'; import 'package:diplomaticquarterapp/core/model/geofencing/responses/LogGeoZoneResponseModel.dart'; import 'package:diplomaticquarterapp/generated/json/log_geo_zone_response_model_entity_helper.dart'; -import 'package:diplomaticquarterapp/core/model/packages_offers/responses/OfferProductsResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart'; import 'package:diplomaticquarterapp/generated/json/OfferProductsResponseModel_helper.dart'; import 'package:diplomaticquarterapp/core/model/geofencing/responses/GeoZonesResponseModel.dart'; import 'package:diplomaticquarterapp/generated/json/GeoZonesResponseModel_helper.dart'; @@ -23,16 +23,16 @@ class JsonConvert { static _getFromJson(Type type, data, json) { switch (type) { - case OfferCategoriesResponseModel: - return offerCategoriesResponseModelFromJson(data as OfferCategoriesResponseModel, json) as T; + case PackagesCategoriesResponseModel: + return offerCategoriesResponseModelFromJson(data as PackagesCategoriesResponseModel, json) as T; case OfferCategoriesResponseModelLocalizedName: return offerCategoriesResponseModelLocalizedNameFromJson(data as OfferCategoriesResponseModelLocalizedName, json) as T; case OfferCategoriesResponseModelImage: return offerCategoriesResponseModelImageFromJson(data as OfferCategoriesResponseModelImage, json) as T; case LogGeoZoneResponseModel: return logGeoZoneResponseModelEntityFromJson(data as LogGeoZoneResponseModel, json) as T; - case OfferProductsResponseModel: - return offerProductsResponseModelFromJson(data as OfferProductsResponseModel, json) as T; + case PackagesResponseModel: + return offerProductsResponseModelFromJson(data as PackagesResponseModel, json) as T; case OfferProductsResponseModelLocalizedName: return offerProductsResponseModelLocalizedNameFromJson(data as OfferProductsResponseModelLocalizedName, json) as T; case OfferProductsResponseModelImage: @@ -47,16 +47,16 @@ class JsonConvert { static _getToJson(Type type, data) { switch (type) { - case OfferCategoriesResponseModel: - return offerCategoriesResponseModelToJson(data as OfferCategoriesResponseModel); + case PackagesCategoriesResponseModel: + return offerCategoriesResponseModelToJson(data as PackagesCategoriesResponseModel); case OfferCategoriesResponseModelLocalizedName: return offerCategoriesResponseModelLocalizedNameToJson(data as OfferCategoriesResponseModelLocalizedName); case OfferCategoriesResponseModelImage: return offerCategoriesResponseModelImageToJson(data as OfferCategoriesResponseModelImage); case LogGeoZoneResponseModel: return logGeoZoneResponseModelEntityToJson(data as LogGeoZoneResponseModel); - case OfferProductsResponseModel: - return offerProductsResponseModelToJson(data as OfferProductsResponseModel); + case PackagesResponseModel: + return offerProductsResponseModelToJson(data as PackagesResponseModel); case OfferProductsResponseModelLocalizedName: return offerProductsResponseModelLocalizedNameToJson(data as OfferProductsResponseModelLocalizedName); case OfferProductsResponseModelImage: @@ -72,16 +72,16 @@ class JsonConvert { //Go back to a single instance by type static _fromJsonSingle(json) { String type = M.toString(); - if (type == (OfferCategoriesResponseModel).toString()) { - return OfferCategoriesResponseModel().fromJson(json); + if (type == (PackagesCategoriesResponseModel).toString()) { + return PackagesCategoriesResponseModel().fromJson(json); } else if (type == (OfferCategoriesResponseModelLocalizedName).toString()) { return OfferCategoriesResponseModelLocalizedName().fromJson(json); } else if (type == (OfferCategoriesResponseModelImage).toString()) { return OfferCategoriesResponseModelImage().fromJson(json); } else if (type == (LogGeoZoneResponseModel).toString()) { return LogGeoZoneResponseModel().fromJson(json); - } else if (type == (OfferProductsResponseModel).toString()) { - return OfferProductsResponseModel().fromJson(json); + } else if (type == (PackagesResponseModel).toString()) { + return PackagesResponseModel().fromJson(json); } else if (type == (OfferProductsResponseModelLocalizedName).toString()) { return OfferProductsResponseModelLocalizedName().fromJson(json); } else if (type == (OfferProductsResponseModelImage).toString()) { @@ -96,16 +96,16 @@ class JsonConvert { //list is returned by type static M _getListChildType(List data) { - if (List() is M) { - return data.map((e) => OfferCategoriesResponseModel().fromJson(e)).toList() as M; + if (List() is M) { + return data.map((e) => PackagesCategoriesResponseModel().fromJson(e)).toList() as M; } else if (List() is M) { return data.map((e) => OfferCategoriesResponseModelLocalizedName().fromJson(e)).toList() as M; } else if (List() is M) { return data.map((e) => OfferCategoriesResponseModelImage().fromJson(e)).toList() as M; } else if (List() is M) { return data.map((e) => LogGeoZoneResponseModel().fromJson(e)).toList() as M; - } else if (List() is M) { - return data.map((e) => OfferProductsResponseModel().fromJson(e)).toList() as M; + } else if (List() is M) { + return data.map((e) => PackagesResponseModel().fromJson(e)).toList() as M; } else if (List() is M) { return data.map((e) => OfferProductsResponseModelLocalizedName().fromJson(e)).toList() as M; } else if (List() is M) { diff --git a/lib/locator.dart b/lib/locator.dart index 4d7f1e50..bf97537d 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -282,12 +282,9 @@ void setupLocator() { // Offer And Packages //---------------------- - locator.registerLazySingleton( - () => OffersAndPackagesServices()); // offerPackagesServices Service - locator.registerFactory( - () => OfferCategoriesViewModel()); // Categories View Model - locator - .registerFactory(() => OfferProductsViewModel()); // Products View Model + locator.registerLazySingleton(() => OffersAndPackagesServices()); // offerPackagesServices Service + locator.registerFactory(() => OfferCategoriesViewModel()); // Categories View Model + locator.registerFactory(() => PackagesViewModel()); // Products View Model // Geofencing // --------------------- diff --git a/lib/main.dart b/lib/main.dart index e0444a32..05615a68 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -115,7 +115,8 @@ class MyApp extends StatelessWidget { ), ), ), - initialRoute: SPLASH, + // initialRoute: SPLASH, + initialRoute: PACKAGES_OFFERS, routes: routes, debugShowCheckedModeBanner: false, ), diff --git a/lib/pages/packages_offers/ClinicOfferAndPackagesPage.dart b/lib/pages/packages_offers/ClinicOfferAndPackagesPage.dart new file mode 100644 index 00000000..b1e447ef --- /dev/null +++ b/lib/pages/packages_offers/ClinicOfferAndPackagesPage.dart @@ -0,0 +1,80 @@ +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersCategoriesRequestModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersProductsRequestModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCategoriesResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart'; +import 'package:diplomaticquarterapp/core/viewModels/packages_offers/PackagesOffersViewModel.dart'; +import 'package:diplomaticquarterapp/core/viewModels/pharmacyModule/order_model_view_model.dart'; +import 'package:diplomaticquarterapp/locator.dart'; +import 'package:diplomaticquarterapp/pages/base/base_view.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackageDetailPage.dart'; +import 'package:diplomaticquarterapp/uitl/gif_loader_dialog_utils.dart'; +import 'package:diplomaticquarterapp/uitl/utils.dart' as utils; +import 'package:diplomaticquarterapp/widgets/loadings/ShimmerLoading.dart'; +import 'package:diplomaticquarterapp/widgets/offers_packages/PackagesOfferCard.dart'; +import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_material_pickers/flutter_material_pickers.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; + +dynamic languageID; + +class ClinicPackagesPage extends StatefulWidget { + List products; + ClinicPackagesPage({@required this.products}); + + @override + _ClinicPackagesPageState createState() => _ClinicPackagesPageState(); + + +} + +class _ClinicPackagesPageState extends State { + List get _products => widget.products; + + PackagesViewModel viewModel; + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + + return BaseView( + allowAny: true, + onModelReady: (model){ + viewModel = model; + }, + builder: (_, model, wi) => AppScaffold( + appBarTitle: TranslationBase.of(context).offerAndPackages, + isShowAppBar: true, + isPharmacy: false, + showPharmacyCart: false, + showHomeAppBarIcon: false, + isOfferPackages: true, + showOfferPackagesCart: true, + isShowDecPage: false, + body: Padding( + padding: const EdgeInsets.all(5), + child: StaggeredGridView.countBuilder( + crossAxisCount:4, + itemCount: _products.length, + itemBuilder: (BuildContext context, int index) => new Container( + color: Colors.transparent, + child: PackagesItemCard( itemContentPadding: 10,itemModel: _products[index],) + ), + staggeredTileBuilder: (int index) => StaggeredTile.fit(2), + mainAxisSpacing: 20, + crossAxisSpacing: 10, + ) + ), + ), + ); + } + +} diff --git a/lib/pages/packages_offers/OfferAndPackageDetailPage.dart b/lib/pages/packages_offers/OfferAndPackageDetailPage.dart new file mode 100644 index 00000000..a6711ded --- /dev/null +++ b/lib/pages/packages_offers/OfferAndPackageDetailPage.dart @@ -0,0 +1,228 @@ +import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCategoriesResponseModel.dart'; +import 'package:diplomaticquarterapp/core/viewModels/packages_offers/PackagesOffersViewModel.dart'; +import 'package:diplomaticquarterapp/core/viewModels/pharmacyModule/order_model_view_model.dart'; +import 'package:diplomaticquarterapp/pages/base/base_view.dart'; +import 'package:diplomaticquarterapp/pages/pharmacies/ProductCheckTypeWidget.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:diplomaticquarterapp/uitl/utils.dart' as utils; +import 'package:diplomaticquarterapp/widgets/others/StarRating.dart'; +import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import 'ClinicOfferAndPackagesPage.dart'; + +class OfferAndPackagesDetail extends StatefulWidget{ + final dynamic model; + + const OfferAndPackagesDetail({@required this.model, Key key}) : super(key: key); + + @override + State createState() => OfferAndPackagesDetailState(); +} + + +class OfferAndPackagesDetailState extends State{ + + PackagesViewModel viewModel; + + @override + Widget build(BuildContext context) { + getLanguageID(); + + return BaseView( + onModelReady: (model){ + viewModel = model; + }, + builder: (_, model, wi) => AppScaffold( + appBarTitle: TranslationBase.of(context).offerAndPackages, + isShowAppBar: true, + isPharmacy: false, + showPharmacyCart: false, + showHomeAppBarIcon: false, + isOfferPackages: true, + showOfferPackagesCart: true, + isShowDecPage: false, + body: Stack( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 60), + child: ListView( + children: [ + Padding( + padding: const EdgeInsets.all(5), + child: Stack( + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: AspectRatio( + aspectRatio: 1/1, + child: utils.applyShadow( + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: utils.Utils.loadNetworkImage(url: "https://wallpaperaccess.com/full/30103.jpg",) + ), + ) + ), + ), + Align( + alignment: Alignment.topLeft, + child: Image.asset( + 'assets/images/discount_${'en'}.png', + height: 70, + width: 70, + ), + ), + + ], + + ), + ), + + Padding( + padding: const EdgeInsets.only(left: 20, right: 20, bottom: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Child Dental Offer", + style: TextStyle( + fontWeight: FontWeight.normal, + color: Colors.black, + fontSize: 25 + ) + ), + + Stack( + children: [ + Text( + "200 SAR", + style: TextStyle( + fontWeight: FontWeight.normal, + decoration: TextDecoration.lineThrough, + color: Colors.grey, + fontSize: 12 + ) + ), + Padding( + padding: const EdgeInsets.only(top: 15), + child: Text( + "894.99 SAR", + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.green, + fontSize: 18 + ) + ), + ), + ], + ), + + StarRating( + size: 20, + totalCount: null, + totalAverage: 5, + forceStars: true), + + + SizedBox(height: 20,), + + + Text( + "Details", + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.grey, + fontSize: 20 + ) + ), + + + + AspectRatio( + aspectRatio: 2/1, + child: Container( + color: Colors.grey[300], + child: Padding( + padding: const EdgeInsets.all(10), + child: Text("Detail of offers written here ss"), + ) + ), + ), + + + SizedBox(height: 10,), + ], + ), + ), + ], + ), + ), + + Padding( + padding: const EdgeInsets.all(10), + child: Align( + alignment: Alignment.bottomRight, + child: Row( + children: [ + + Expanded( + child: RaisedButton.icon( + padding: EdgeInsets.only(top: 5, bottom: 5, left: 0, right: 0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + side: BorderSide(color: Colors.red, width: 0.5) + ), + color: Colors.red, + icon: Icon( + Icons.add_shopping_cart_outlined, + size: 25, + color: Colors.white, + ), + label: Text( + "Add to Cart", + style: TextStyle(fontSize: 17, color: Colors.white, fontWeight: FontWeight.normal), + ), + onPressed: (){},), + ), + + SizedBox(width: 15,), + + Expanded( + child: OutlineButton.icon( + padding: EdgeInsets.only(top: 5, bottom: 5, left: 0, right: 0), + borderSide: BorderSide(width: 1.0, color: Colors.red), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + color: Colors.white, + icon: Icon( + Icons.favorite_rounded, + size: 25, + color: Colors.red, + ), + label: Text( + "Add to Favorites", + style: TextStyle(fontSize: 17, color: Colors.red, fontWeight: FontWeight.normal), + ), + onPressed: (){ + + },), + ), + + ], + ), + ), + ) + + + + ], + ) + ), + ); + } +} + diff --git a/lib/pages/packages_offers/OfferAndPackagesCartPage.dart b/lib/pages/packages_offers/OfferAndPackagesCartPage.dart new file mode 100644 index 00000000..98f237f6 --- /dev/null +++ b/lib/pages/packages_offers/OfferAndPackagesCartPage.dart @@ -0,0 +1,376 @@ +import 'package:after_layout/after_layout.dart'; +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/requests/AddProductToCartRequestModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersCategoriesRequestModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersProductsRequestModel.dart'; +import 'package:diplomaticquarterapp/core/viewModels/packages_offers/PackagesOffersViewModel.dart'; +import 'package:diplomaticquarterapp/core/viewModels/pharmacyModule/order_model_view_model.dart'; +import 'package:diplomaticquarterapp/locator.dart'; +import 'package:diplomaticquarterapp/pages/base/base_view.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/ClinicOfferAndPackagesPage.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackageDetailPage.dart'; +import 'package:diplomaticquarterapp/pages/pharmacies/screens/pharmacy-terms-conditions-page.dart'; +import 'package:diplomaticquarterapp/uitl/gif_loader_dialog_utils.dart'; +import 'package:diplomaticquarterapp/uitl/utils.dart' as utils; +import 'package:diplomaticquarterapp/widgets/Loader/gif_loader_container.dart'; +import 'package:diplomaticquarterapp/widgets/carousel_indicator/carousel_indicator.dart'; +import 'package:diplomaticquarterapp/widgets/loadings/ShimmerLoading.dart'; +import 'package:diplomaticquarterapp/widgets/offers_packages/PackagesCartItemCard.dart'; +import 'package:diplomaticquarterapp/widgets/offers_packages/PackagesOfferCard.dart'; +import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:diplomaticquarterapp/widgets/transitions/fade_page.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_material_pickers/flutter_material_pickers.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +dynamic languageID; +const _columnCount = 1; + +AnimationController _animationController; + +class PackagesCartPage extends StatefulWidget { + PackagesCartPage(); + + @override + _PackagesCartPageState createState() => _PackagesCartPageState(); +} + +class _PackagesCartPageState extends State with AfterLayoutMixin, SingleTickerProviderStateMixin { + getLanguageID() async { + languageID = await sharedPref.getString(APP_LANGUAGE); + } + + @override + void initState() { + _agreeTerms = false; + _selectedPaymentMethod = null; + _animationController = AnimationController(vsync: this, duration: Duration(seconds: 500)); + super.initState(); + } + + @override + void dispose() { + _animationController.dispose(); + viewModel.cartItemList.clear(); + super.dispose(); + } + + PackagesViewModel viewModel; + + bool loadWidgets = false; + + onTermsClick(bool isAgree) { + setState(() => _agreeTerms = isAgree); + } + + onTermsInfoClick() { + Navigator.push(context, FadePage(page: PharmacyTermsConditions())); + } + + onPayNowClick() async{ + await viewModel.service.placeOrder(context: context); + } + + @override + void afterFirstLayout(BuildContext context) { + fetchData(); + } + + @override + Widget build(BuildContext context) { + return BaseView( + allowAny: true, + onModelReady: (model) => viewModel = model, + builder: (_, model, wi) { + return AppScaffold( + appBarTitle: TranslationBase.of(context).offerAndPackages, + isShowAppBar: true, + isPharmacy: false, + showPharmacyCart: false, + showHomeAppBarIcon: false, + isOfferPackages: true, + showOfferPackagesCart: false, + isShowDecPage: false, + body: Column( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(5), + child: StaggeredGridView.countBuilder( + crossAxisCount: (_columnCount * _columnCount), + itemCount: viewModel.cartItemList.length, + itemBuilder: (BuildContext context, int index) { + + var item = viewModel.cartItemList[index]; + return Dismissible( + key: Key(index.toString()), + direction: DismissDirection.startToEnd, + background: _cartItemDeleteContainer(), + secondaryBackground: _cartItemDeleteContainer(), + confirmDismiss: (direction) async { + bool status = await viewModel.service.deleteProductFromCart(item.id, context: context, showLoading: false); + return status; + }, + onDismissed: (direction) { + debugPrint('Index: $index'); + viewModel.cartItemList.removeAt(index); + }, + child: PackagesCartItemCard( + itemModel: item, + shouldStepperChangeApply: (apply,total) async{ + var request = AddProductToCartRequestModel(product_id: item.productId, quantity: apply); + bool success = await viewModel.service.addProductToCart(request, context: context, showLoading: false).catchError((error){ + utils.Utils.showErrorToast(error); + }); + return success ?? false; + }, + ) + ); + }, + staggeredTileBuilder: (int index) => StaggeredTile.fit(_columnCount), + mainAxisSpacing: 0, + crossAxisSpacing: 10, + )), + ), + Container( + height: 0.25, + color: Theme.of(context).primaryColor, + ), + Container( + color: Colors.white, + child: Column( + children: [ + Text( + TranslationBase.of(context).selectPaymentOption, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold + ), + ), + + Container(height: 0.25, width: 100, color: Colors.grey[300],), + + _paymentOptions(context, (paymentMethod) { + setState(() => _selectedPaymentMethod = paymentMethod); + }), + + Container(height: 0.25, color: Colors.grey[300],), + + Container(height: 40, + child: _termsAndCondition(context, onSelected: onTermsClick, onInfoClick: onTermsInfoClick) + ), + + Container(height: 0.25, color: Colors.grey[300],), + _payNow(context, onPayNowClick: onPayNowClick) + ], + ), + ) + ], + ), + ); + }); + } + + fetchData() async { + await viewModel.service.cartItems(context: context).catchError((error) {}); + setState((){}); + } +} + +// /* Payment Footer Widgets */ +// --------------------------- +String _selectedPaymentMethod; +Widget _paymentOptions(BuildContext context, Function(String) onSelected) { + double height = 22; + + Widget buttonContent(bool isSelected, String imageName) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: isSelected ? Colors.green[50] : Colors.grey[200], + blurRadius: 1, + spreadRadius: 2, + ), + ], + borderRadius: BorderRadius.all(Radius.circular(5)), + border: Border.all(color: isSelected ? Colors.green : Colors.grey, width: isSelected ? 1 : 0.5) + ), + child: Padding( + padding: const EdgeInsets.all(4), + child: Image.asset('assets/images/new-design/$imageName'), + ) + ); + } + + return Padding( + padding: const EdgeInsets.all(5), + child: Container( + height: height, + color: Colors.transparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InkWell( + child: buttonContent(_selectedPaymentMethod == "mada", 'mada.png'), + onTap: () { + onSelected("mada"); + }, + ), + SizedBox( + width: 5, + ), + InkWell( + child: buttonContent(_selectedPaymentMethod == "visa", 'visa.png'), + onTap: () { + onSelected("visa"); + }, + ), + SizedBox( + width: 5, + ), + InkWell( + child: buttonContent(_selectedPaymentMethod == "mastercard", 'mastercard.png'), + onTap: () { + onSelected("mastercard"); + }, + ), + SizedBox( + width: 5, + ), + InkWell( + child: buttonContent(_selectedPaymentMethod == "installment", 'installment.png'), + onTap: () { + onSelected("installment"); + }, + ), + ], + ), + ), + ); +} + +bool _agreeTerms = false; +Widget _termsAndCondition(BuildContext context, {@required Function(bool) onSelected, @required VoidCallback onInfoClick}) { + return Padding( + padding: const EdgeInsets.all(5), + child: Row( + children: [ + InkWell( + child: Icon( + _agreeTerms ? Icons.check_circle : Icons.radio_button_unchecked_sharp, + size: 20, + color: _agreeTerms ? Colors.green[600] : Colors.grey[400], + ), + onTap: () { + onSelected(!_agreeTerms); + }, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + TranslationBase.of(context).pharmacyServiceTermsCondition, + style: TextStyle(height: 1, fontWeight: FontWeight.normal, fontSize: 13), + ), + )), + InkWell( + child: Icon( + Icons.info, + size: 20, + color: Colors.grey[600], + ), + onTap: () { + onInfoClick(); + }, + ), + ], + ), + ); +} + +Widget _payNow(BuildContext context, {@required VoidCallback onPayNowClick}) { + bool isPayNowAQctive = (_agreeTerms && (_selectedPaymentMethod != null)); + + return Padding( + padding: const EdgeInsets.all(5), + child: Container( + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.all(5), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('${TranslationBase.of(context).subtotal}: ${'999.9'} ${TranslationBase.of(context).sar}', + style: TextStyle(height: 1.5, fontWeight: FontWeight.bold, color: Colors.grey, fontSize: 8)), + Text('${TranslationBase.of(context).vat}: ${'14.9'} ${TranslationBase.of(context).sar}', style: TextStyle(height: 1.5, fontWeight: FontWeight.bold, color: Colors.grey, fontSize: 8)), + Padding( + padding: const EdgeInsets.all(3), + child: Container( + height: 0.25, + width: 120, + color: Colors.grey[300], + ), + ), + Text(' ${TranslationBase.of(context).total}: 999.99 ${TranslationBase.of(context).sar}', + style: TextStyle(height: 1.5, fontWeight: FontWeight.bold, color: Colors.black54, fontSize: 15)) + ], + ), + ), + Expanded(child: Container()), + RaisedButton( + child: Text( + TranslationBase.of(context).payNow, + style: TextStyle(fontSize: 15, color: Colors.white, fontWeight: FontWeight.bold), + ), + padding: EdgeInsets.only(top: 5, bottom: 5, left: 0, right: 0), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5), side: BorderSide(color: Theme.of(context).primaryColor, width: 0.5)), + color: Theme.of(context).primaryColor, + onPressed: isPayNowAQctive ? onPayNowClick : null, + ), + ], + )), + ); +} +// ------------------- + +Widget _cartItemDeleteContainer() { + _animationController.duration = Duration(milliseconds: 500); + _animationController.repeat(reverse: true); + return FadeTransition( + opacity: _animationController, + child: Padding( + padding: const EdgeInsets.all(5), + child: Container( + decoration: BoxDecoration( + color: Colors.red, + boxShadow: [ + BoxShadow( + color: Colors.grey[500], + blurRadius: 2, + spreadRadius: 1, + ), + ], + borderRadius: BorderRadius.all(Radius.circular(5)), + ), + child: Center( + child: Text( + "Deleting...", + style: TextStyle( + fontWeight: FontWeight.normal, + fontSize: 15, + color: Colors.white, + ), + )), + ), + ), + ); +} diff --git a/lib/pages/packages_offers/OfferAndPackagesPage.dart b/lib/pages/packages_offers/OfferAndPackagesPage.dart new file mode 100644 index 00000000..0a83c683 --- /dev/null +++ b/lib/pages/packages_offers/OfferAndPackagesPage.dart @@ -0,0 +1,416 @@ +import 'package:after_layout/after_layout.dart'; +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/requests/AddProductToCartRequestModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/requests/CreateCustomerRequestModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersCategoriesRequestModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersProductsRequestModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart'; +import 'package:diplomaticquarterapp/core/viewModels/packages_offers/PackagesOffersViewModel.dart'; +import 'package:diplomaticquarterapp/core/viewModels/pharmacyModule/order_model_view_model.dart'; +import 'package:diplomaticquarterapp/locator.dart'; +import 'package:diplomaticquarterapp/pages/base/base_view.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/ClinicOfferAndPackagesPage.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackageDetailPage.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackagesCartPage.dart'; +import 'package:diplomaticquarterapp/uitl/gif_loader_dialog_utils.dart'; +import 'package:diplomaticquarterapp/uitl/utils.dart' as utils; +import 'package:diplomaticquarterapp/widgets/Loader/gif_loader_container.dart'; +import 'package:diplomaticquarterapp/widgets/carousel_indicator/carousel_indicator.dart'; +import 'package:diplomaticquarterapp/widgets/loadings/ShimmerLoading.dart'; +import 'package:diplomaticquarterapp/widgets/offers_packages/PackagesOfferCard.dart'; +import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_material_pickers/flutter_material_pickers.dart'; + +dynamic languageID; + +class PackagesHomePage extends StatefulWidget { + dynamic offersModel; + PackagesHomePage({@required this.offersModel}); + + @override + _PackagesHomePageState createState() => _PackagesHomePageState(); + +} + +class _PackagesHomePageState extends State with AfterLayoutMixin{ + getLanguageID() async { + languageID = await sharedPref.getString(APP_LANGUAGE); + } + + @override + void initState() { + super.initState(); + getLanguageID(); + } + + @override + void afterFirstLayout(BuildContext context) async{ + viewModel.service.loadOffersPackagesDataForMainPage(context: context, completion: (){ + setState((){}); + }); + } + + // Controllers + var _searchTextController = TextEditingController(); + var _filterTextController = TextEditingController(); + var _carouselController = CarouselController(); + + + int carouselIndicatorIndex = 0; + CarouselSlider _bannerCarousel; + TextField _textFieldSearch; + TextField _textFieldFilterSelection; + + ListView _listViewLatestOffers; + ListView _listViewBestSeller; + + PackagesViewModel viewModel; + + onCartClick(){ + if (viewModel.service.customer == null){ + utils.Utils.showErrorToast("Cart is empty for your current session"); + return; + } + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => PackagesCartPage() + ) + ); + } + + onProductCartClick(PackagesResponseModel product) async { + if(viewModel.service.customer == null) + await viewModel.service.createCustomer(PackagesCustomerRequestModel(email: "zikambrani@gmail.com", phoneNumber: "0500409598"), context: context); + + if(viewModel.service.customer != null){ + var request = AddProductToCartRequestModel(product_id: product.id, customer_id: viewModel.service.customer.id); + await viewModel.service.addProductToCart(request, context: context).catchError((error) { + utils.Utils.showErrorToast(error); + }); + } + } + + @override + Widget build(BuildContext context) { + + return BaseView( + allowAny: true, + onModelReady: (model) => viewModel = model, + builder: (_, model, wi){ + return + AppScaffold( + appBarTitle: TranslationBase.of(context).offerAndPackages, + isShowAppBar: true, + isPharmacy: false, + showPharmacyCart: false, + showHomeAppBarIcon: false, + isOfferPackages: true, + showOfferPackagesCart: true, + isShowDecPage: false, + body: ListView( + children: [ + + // Top Banner Carousel + AspectRatio( + aspectRatio: 2.2/1, + child: bannerCarousel() + ), + + Center( + child: CarouselIndicator( + activeColor: Theme.of(context).appBarTheme.color, + color: Colors.grey[300], + cornerRadius: 15, + width: 15, height: 15, + count: _bannerCarousel.itemCount, + index: carouselIndicatorIndex, + onClick: (index){ + debugPrint('onClick at ${index}'); + }, + ), + ), + + SizedBox(height: 10,), + + Padding( + padding: const EdgeInsets.all(15), + child: Column( + children: [ + // Search Textfield + searchTextField(), + + SizedBox(height: 10,), + + // Filter Selection + filterOptionSelection(), + + SizedBox(height: 20,), + + // Horizontal Scrollable Cards + Text( + "Latest offers", + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black87, + fontSize: 20 + ), + ), + + // Latest Offers Horizontal Scrollable List + AspectRatio( + aspectRatio: 1.3/1, + child: LayoutBuilder(builder: (context, constraints){ + double itemContentPadding = 10; + double itemWidth = (constraints.maxWidth/2) - (itemContentPadding*2); + return latestOfferListView(itemWidth: itemWidth, itemContentPadding: itemContentPadding); + }), + ), + + SizedBox(height: 10,), + + Text( + "Best sellers", + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black87, + fontSize: 20 + ), + ), + + + // Best Seller Horizontal Scrollable List + AspectRatio( + aspectRatio: 1.3/1, + child: LayoutBuilder(builder: (context, constraints){ + double itemContentPadding = 10; // 10 is content padding in each item + double itemWidth = (constraints.maxWidth/2) - (itemContentPadding*2 /* 2 = LeftRight */); + return bestSellerListView(itemWidth: itemWidth, itemContentPadding: itemContentPadding); + }), + ) + + ],), + ), + ], + ), + ) + .setOnAppBarCartClick(onCartClick); + } + ); + } + + + + + showClinicSelectionList() async { + var clinics = viewModel.service.categoryList; + if(clinics.isEmpty) { + GifLoaderDialogUtils.showMyDialog(context); + clinics = await viewModel.service.getAllCategories(OffersCategoriesRequestModel()); + GifLoaderDialogUtils.hideDialog(context); + } + + List options = clinics.map((e) => e.toString()).toList(); + + showMaterialSelectionPicker( + context: context, + title: "Select Clinic", + items: options, + selectedItem: options.first, + onChanged: (value) async { + var selectedClinic = clinics.firstWhere((element) => element.toString() == value); + var clinicProducts = await viewModel.service.getAllProducts(request: OffersProductsRequestModel(categoryId: selectedClinic.id), context: context, showLoading: true); + if(clinicProducts.isNotEmpty) + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => ClinicPackagesPage(products: clinicProducts) + ) + ); + else + utils.Utils.showErrorToast("No offers available for this clinic"); + }, + ); + } + + //---------------------------------- + // Main Widgets of Page + //---------------------------------- + + CarouselSlider bannerCarousel(){ + _bannerCarousel = CarouselSlider.builder( + carouselController: _carouselController, + itemCount: 10, + itemBuilder: (BuildContext context, int itemIndex) { + return Padding( + padding: const EdgeInsets.only(top: 10, bottom: 10, left: 15, right: 15), + child: FractionallySizedBox( + widthFactor: 1, + heightFactor: 1, + child: utils.applyShadow( + spreadRadius: 1, + blurRadius: 5, + child: InkWell( + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: utils.Utils.loadNetworkImage(url: "https://wallpaperaccess.com/full/30103.jpg",) + ), + onTap: (){ + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => OfferAndPackagesDetail(model: "",) + ) + ); + }, + ) + ), + ), + ); + }, + options: CarouselOptions( + autoPlayInterval: Duration(milliseconds: 3500), + enlargeStrategy: CenterPageEnlargeStrategy.scale, + enlargeCenterPage: true, + autoPlay: false, + autoPlayCurve: Curves.fastOutSlowIn, + enableInfiniteScroll: true, + autoPlayAnimationDuration: Duration(milliseconds: 1500), + viewportFraction: 1, + onPageChanged: (page, reason){ + setState(() { + carouselIndicatorIndex = page; + }); + }, + ), + ); + return _bannerCarousel; + } + + TextField searchTextField(){ + return _textFieldSearch = + TextField( + controller: _searchTextController, + decoration: InputDecoration( + contentPadding: EdgeInsets.only(top: 0.0, bottom: 0.0, left: 10, right: 10), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( width: 0.5, color: Colors.grey), + borderRadius: const BorderRadius.all( + const Radius.circular(10.0), + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( width: 1, color: Colors.grey), + borderRadius: const BorderRadius.all( + const Radius.circular(10.0), + ), + ), + filled: true, + fillColor: Colors.white, + hintText: "Search", + hintStyle: TextStyle(color: Colors.grey[350], fontWeight: FontWeight.bold), + suffixIcon: IconButton( + onPressed: (){ + // viewModel.search(text: _searchTextController.text); + }, + icon: Icon(Icons.search_rounded, size: 35,), + ), + ), + ); + + } + + Widget filterOptionSelection(){ + _textFieldFilterSelection = + TextField( + enabled: false, + controller: _searchTextController, + decoration: InputDecoration( + contentPadding: EdgeInsets.only(top: 0.0, bottom: 0.0, left: 10, right: 10), + border: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey, width: 1), + borderRadius: const BorderRadius.all( + const Radius.circular(10.0), + ), + ), + disabledBorder: OutlineInputBorder( + borderSide: BorderSide( width: 0.5, color: Colors.grey), + borderRadius: const BorderRadius.all( + const Radius.circular(10.0), + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all( + const Radius.circular(10.0), + ), + ), + filled: true, + fillColor: Colors.white, + hintText: "Browse offers by Clinic", + hintStyle: TextStyle(color: Colors.grey[350], fontWeight: FontWeight.bold), + suffixIcon: IconButton( + onPressed: (){ + showClinicSelectionList(); + }, + icon: Icon(Icons.keyboard_arrow_down_rounded, size: 35, color: Colors.grey,), + ), + ), + ); + + return InkWell( + child: _textFieldFilterSelection, + onTap: (){ + showClinicSelectionList(); + }, + ); + + } + + Widget latestOfferListView({@required double itemWidth, @required double itemContentPadding}){ + return _listViewLatestOffers = + ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: viewModel.bestSellerList.length, + itemBuilder: (BuildContext context, int index) { + return PackagesItemCard(itemWidth: itemWidth, itemContentPadding: itemContentPadding, itemModel: viewModel.bestSellerList[index], onCartClick: onProductCartClick,); + }, + separatorBuilder: separator, + ); + + } + + Widget bestSellerListView({@required double itemWidth, @required double itemContentPadding}){ + return _listViewLatestOffers = + ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: viewModel.bestSellerList.length, + itemBuilder: (BuildContext context, int index) { + return PackagesItemCard(itemWidth: itemWidth, itemContentPadding: itemContentPadding, itemModel: viewModel.bestSellerList[index], onCartClick: onProductCartClick,); + }, + separatorBuilder: separator, + ); + + } + + + Widget separator(BuildContext context, int index){ + return Container( + width: 1, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment(-1.0, -2.0), + end: Alignment(1.0, 4.0), + colors: [ + Colors.grey, + Colors.grey[100], + Colors.grey[200], + Colors.grey[300], + Colors.grey[400], + Colors.grey[500] + ] + )), + ); + } +} diff --git a/lib/routes.dart b/lib/routes.dart index b11025c2..57452796 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -10,6 +10,8 @@ import 'package:diplomaticquarterapp/pages/login/welcome.dart'; import 'package:diplomaticquarterapp/pages/login/login-type.dart'; import 'package:diplomaticquarterapp/pages/login/login.dart'; import 'package:diplomaticquarterapp/pages/login/register.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackagesCartPage.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackagesPage.dart'; import 'package:diplomaticquarterapp/pages/settings/settings.dart'; import 'package:diplomaticquarterapp/pages/symptom-checker/info.dart'; import 'package:diplomaticquarterapp/pages/symptom-checker/select-gender.dart'; @@ -35,6 +37,9 @@ const String SYMPTOM_CHECKER = 'symptom-checker'; const String SYMPTOM_CHECKER_INFO = 'symptom-checker-info'; const String SELECT_GENDER = 'select-gender'; const String SETTINGS = 'settings'; +const String PACKAGES_OFFERS = 'packages-offers'; +const String PACKAGES_OFFERS_CART = 'packages-offers-cart'; + var routes = { SPLASH: (_) => SplashScreen(), HOME: (_) => LandingPage(), @@ -52,5 +57,7 @@ var routes = { SYMPTOM_CHECKER: (_) => SymptomChecker(), SYMPTOM_CHECKER_INFO: (_) => SymptomInfo(), SELECT_GENDER: (_) => SelectGender(), - SETTINGS: (_) => Settings() + SETTINGS: (_) => Settings(), + PACKAGES_OFFERS: (_) => PackagesHomePage(), + PACKAGES_OFFERS_CART: (_) => PackagesCartPage(), }; diff --git a/lib/uitl/gif_loader_dialog_utils.dart b/lib/uitl/gif_loader_dialog_utils.dart index ac37cdfb..785a6c6c 100644 --- a/lib/uitl/gif_loader_dialog_utils.dart +++ b/lib/uitl/gif_loader_dialog_utils.dart @@ -8,6 +8,10 @@ class GifLoaderDialogUtils { } static hideDialog(BuildContext context) { - Navigator.of(context).pop(); + try{ + Navigator.of(context).pop(); + }catch(error){ + Future.delayed(Duration(milliseconds: 250)).then((value) => Navigator.of(context).pop()); + } } } diff --git a/lib/uitl/translations_delegate_base.dart b/lib/uitl/translations_delegate_base.dart index 9ed7b478..97399f31 100644 --- a/lib/uitl/translations_delegate_base.dart +++ b/lib/uitl/translations_delegate_base.dart @@ -1110,6 +1110,11 @@ class TranslationBase { String get submitncontinue => localizedValues["submitncontinue"][locale.languageCode]; String get areyousure => localizedValues["areyousure"][locale.languageCode]; String get preferredunit => localizedValues["preferredunit"][locale.languageCode]; + + + // Offer And Packahes + String get subT=> localizedValues['OffersAndPackages'][locale.languageCode]; + String get totalWithColonRight => localizedValues['totalWithColonRight'][locale.languageCode]; } class TranslationBaseDelegate extends LocalizationsDelegate { diff --git a/lib/uitl/utils.dart b/lib/uitl/utils.dart index 85e97429..4a7eaead 100644 --- a/lib/uitl/utils.dart +++ b/lib/uitl/utils.dart @@ -3,6 +3,7 @@ import 'dart:core'; import 'dart:typed_data'; import 'package:badges/badges.dart'; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:connectivity/connectivity.dart'; import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; import 'package:diplomaticquarterapp/core/viewModels/project_view_model.dart'; @@ -35,7 +36,6 @@ import 'package:diplomaticquarterapp/widgets/dialogs/alert_dialog.dart'; import 'package:diplomaticquarterapp/widgets/transitions/fade_page.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import '../Constants.dart'; import 'app_shared_preferences.dart'; @@ -534,8 +534,44 @@ class Utils { return medical; } + + static Widget loadNetworkImage({@required String url, BoxFit fitting = BoxFit.cover}){ + return CachedNetworkImage( + placeholderFadeInDuration: Duration(milliseconds: 250), + fit: fitting, + imageUrl: url, + placeholder: (context, url) => Container( + child: Center( + child: CircularProgressIndicator() + ) + ), + errorWidget: (context, url, error){ + return Icon(Icons.error, color: Colors.red, size: 50,); + } + ); + } } + + + +Widget applyShadow({ Color color = Colors.grey, double shadowOpacity = 0.5, double spreadRadius = 2, double blurRadius = 7, Offset offset = const Offset(2, 2), @required Widget child}){ + return Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: color.withOpacity(shadowOpacity), + spreadRadius: spreadRadius, + blurRadius: blurRadius, + offset: offset, // changes position of shadow + ), + ], + ), + child: child, + ); +} + + Future userData() async { var userData = AuthenticatedUser.fromJson(await AppSharedPreferences().getObject(MAIN_USER)); return userData; diff --git a/lib/widgets/CounterView.dart b/lib/widgets/CounterView.dart new file mode 100644 index 00000000..6ba77c15 --- /dev/null +++ b/lib/widgets/CounterView.dart @@ -0,0 +1,188 @@ +import 'package:flutter/material.dart'; + +typedef StepperCallbackFuture = Future Function(int apply, int total); + +class StepperView extends StatefulWidget { + final double height; + final Color foregroundColor; + final Color backgroundColor; + final double buttonPadding; + + final int initialNumber; + final int maxNumber; + final int minNumber; + final StepperCallbackFuture counterCallback; + final Function increaseCallback; + final Function decreaseCallback; + + StepperView({this.initialNumber = 1, this.minNumber = 1, this.maxNumber, @required this.counterCallback, this.increaseCallback, this.decreaseCallback, this.height = 25, @required this.foregroundColor, @required this.backgroundColor, this.buttonPadding = 1}){ + assert((this.initialNumber >= this.minNumber && this.initialNumber <= this.maxNumber)); + } + @override + _StepperViewState createState() => _StepperViewState(); +} + +class _StepperViewState extends State { + int _currentCount; + StepperCallbackFuture _counterCallback; + Function _increaseCallback; + Function _decreaseCallback; + + @override + void initState() { + _currentCount = widget.initialNumber ?? 1; + _counterCallback = widget.counterCallback; + _increaseCallback = widget.increaseCallback ?? () {}; + _decreaseCallback = widget.decreaseCallback ?? () {}; + super.initState(); + } + + bool loadingInc = false; + bool loadingDec = false; + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(widget.buttonPadding), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular((widget.height/2) + (widget.buttonPadding*2)), + color: widget.backgroundColor, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _createDecrementButton( + (_currentCount > widget.minNumber) ? () => _decrement() : null, + ), + Container( + width: 25, + child: Center( + child: Text( + _currentCount.toString(), + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.normal, + height: 1.5, + color: Colors.black, + ) + ), + ) + ), + _createIncrementButton( + (_currentCount < widget.maxNumber) ? () => _increment() : null + ), + ], + ), + ); + } + + void _increment() async{ + doInc({@required bool can}){ + if(can) + setState(() { + _currentCount++; + _increaseCallback(); + }); + } + + if (_currentCount < widget.maxNumber){ + if(_counterCallback == null) + doInc(can: true); + else { + setState(() => loadingInc = true); + var result = (await _counterCallback(1,_currentCount)); + doInc(can: result); + setState(() => loadingInc = false); + } + } + } + + void _decrement() async{ + doDec({@required bool can}){ + if(can) + setState(() { + _currentCount--; + _decreaseCallback(); + }); + } + if (_currentCount > widget.minNumber) { + if(_counterCallback == null) + doDec(can: true); + else { + setState(() => loadingDec = true); + var result = (await _counterCallback(-1,_currentCount)); + doDec(can: result); + setState(() => loadingDec = false); + } + } + } + + Widget _createIncrementButton(Function onPressed,) { + return Stack( + children: [ + RawMaterialButton( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + constraints: BoxConstraints(minWidth: widget.height, minHeight: widget.height), + onPressed: onPressed, + elevation: 2.0, + fillColor: widget.foregroundColor, + child: + Icon( + Icons.add, + color: (_currentCount < widget.maxNumber) ? Colors.grey[700] : Colors.grey[400], + size: 12.0, + ), + shape: CircleBorder(), + ), + + if(loadingInc) + Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.8), + borderRadius: BorderRadius.circular(widget.height/2), + ), + height: widget.height, + width: widget.height, + child: CircularProgressIndicator( + strokeWidth: 3, + valueColor: AlwaysStoppedAnimation(Colors.green) + ) + ) + ], + ); + } + + Widget _createDecrementButton(Function onPressed) { + return Stack( + children: [ + RawMaterialButton( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + constraints: BoxConstraints(minWidth: widget.height, minHeight: widget.height), + onPressed: onPressed, + elevation: 2.0, + fillColor: widget.foregroundColor, + child: Icon( + Icons.remove, + color: (_currentCount > widget.minNumber) ? Colors.grey[700] : Colors.grey[400], + size: 12.0, + ), + shape: CircleBorder(), + ), + + if(loadingDec) + Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.8), + borderRadius: BorderRadius.circular(widget.height/2), + ), + height: widget.height, + width: widget.height, + child: CircularProgressIndicator( + strokeWidth: 3, + valueColor: AlwaysStoppedAnimation(Colors.red) + ) + ) + ], + ); + } +} diff --git a/lib/widgets/carousel_indicator/carousel_indicator.dart b/lib/widgets/carousel_indicator/carousel_indicator.dart new file mode 100644 index 00000000..020c44d8 --- /dev/null +++ b/lib/widgets/carousel_indicator/carousel_indicator.dart @@ -0,0 +1,197 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + + +class CarouselIndicator extends StatefulWidget { + /// width of the indicator + final double width; + + /// height of the indicator + final double height; + + /// space between indicators. + final double space; + + /// count of indicator + final int count; + + /// active color + final Color activeColor; + + /// normal color + final Color color; + + /// use this to give some radius to the corner indicator + final double cornerRadius; + + /// duration for slide animation + final int animationDuration; + + final int index; + + final Function(int index) onClick; + + CarouselIndicator({ + Key key, + this.width: 20.0, + this.height: 6, + this.space: 5.0, + this.count, + this.cornerRadius: 6, + this.animationDuration: 300, + this.color: Colors.white30, + this.index, + this.activeColor: Colors.white, + this.onClick + }) : assert(count != null && count != 0), + assert(index != null && index >= 0), + super(key: key); + + @override + State createState() { + return new _CarouselIndicatorState(); + } +} + +class _CarouselIndicatorState extends State + with TickerProviderStateMixin { + /// [Tween] object of type double + Tween _tween; + + /// [AnimationController] object + AnimationController _animationController; + + /// [Aniamtion] object + Animation _animation; + + /// [Paint] object to paint our indicator + Paint _paint = new Paint(); + + /// Method to initilize [BasePainter] to paint indicators. + BasePainter _createPainer() { + return SlidePainter(widget, _animation.value, _paint); + } + + @override + Widget build(BuildContext context) { + Widget child = new SizedBox( + width: widget.count * widget.width + (widget.count - 1) * widget.space, + height: widget.height, + child: CustomPaint( + painter: _createPainer(), + ), + ); + + return InkWell( + child: child, + onTap: (){ + if(widget.onClick != null) + widget.onClick(0); + }, + ); + } + + @override + void initState() { + /// for initial index=0 we do not want to change any value so setting [_tween] to (0.0,0.0), + createAnimation(0.0, 0.0); + super.initState(); + } + + @override + void didUpdateWidget(CarouselIndicator oldWidget) { + if (widget.index != oldWidget.index) { + if (widget.index != 0) { + _animationController.reset(); + + /// for each new index we want to change value so setting [_tween] to (oldWidget.index,widget.index) so animation tween from old position to new position rather not start from 0.0 again and again. + createAnimation(oldWidget.index.toDouble(), widget.index.toDouble()); + _animationController.forward(); + } else { + _animationController.reset(); + createAnimation(oldWidget.index.toDouble(), 0.0); + _animationController.forward(); + } + } + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + void createAnimation(double begin, double end) { + _tween = Tween(begin: begin, end: end); + _animationController = AnimationController( + vsync: this, + duration: Duration(milliseconds: widget.animationDuration)); + _animation = _tween.animate(_animationController) + ..addListener(() { + setState(() {}); + }); + } +} + + +/// Base Painter class to draw indicator +abstract class BasePainter extends CustomPainter { + final CarouselIndicator widget; + final double page; + final Paint _paint; + + BasePainter(this.widget, this.page, this._paint); + + /// This method will get body to class extending [BasePainter] and this method will draw the sliding indicator which slide over changing index. + void draw(Canvas canvas, double space, double width, double height, + double radius, double cornerRadius); + + @override + void paint(Canvas canvas, Size size) { + _paint.color = widget.color; + double space = widget.space; + double width = widget.width; + double height = widget.height; + double distance = width + space; + double radius = width / 2; + for (int i = 0, c = widget.count; i < c; ++i) { + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromCenter( + center: Offset((i * distance) + radius, radius), + width: width, + height: height), + Radius.circular(widget.cornerRadius)), + _paint); + } + + _paint.color = widget.activeColor; + draw(canvas, space, width, height, radius, widget.cornerRadius); + } + + @override + bool shouldRepaint(BasePainter oldDelegate) { + return oldDelegate.page != page; + } +} + +/// This class we draw the indicator which slides. +class SlidePainter extends BasePainter { + SlidePainter(CarouselIndicator widget, double page, Paint paint) + : super(widget, page, paint); + + @override + void draw(Canvas canvas, double space, double width, double height, + double radius, double cornerRadius) { + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromCenter( + center: Offset(radius + (page * (width + space)), radius), + width: width, + height: height), + Radius.circular(cornerRadius)), + _paint); + } +} + diff --git a/lib/widgets/offers_packages/PackagesCartItemCard.dart b/lib/widgets/offers_packages/PackagesCartItemCard.dart new file mode 100644 index 00000000..72ce7475 --- /dev/null +++ b/lib/widgets/offers_packages/PackagesCartItemCard.dart @@ -0,0 +1,217 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCartItemsResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:diplomaticquarterapp/uitl/utils.dart'; +import 'package:diplomaticquarterapp/widgets/CounterView.dart'; +import 'package:diplomaticquarterapp/widgets/others/StarRating.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + + +bool wide = true; + +class PackagesCartItemCard extends StatefulWidget { + final CartProductResponseModel itemModel; + final StepperCallbackFuture shouldStepperChangeApply ; + + const PackagesCartItemCard( + { + @required this.itemModel, + @required this.shouldStepperChangeApply, + Key key}) + : super(key: key); + + @override + State createState() => PackagesCartItemCardState(); +} + +class PackagesCartItemCardState extends State { + + @override + Widget build(BuildContext context) { + + wide = !wide; + return Container( + color: Colors.transparent, + child: Card( + elevation: 3, + shadowColor: Colors.grey[100], + color: Colors.white, + child: Stack( + children: [ + Container( + height: 100, + child: Row( + children: [ + _image(widget.itemModel.product), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _itemName(widget.itemModel.product.getName()), + Row( + children: [ + _itemPrice(widget.itemModel.product.price, context: context), + _priceSeperator(), + _itemOldPrice(widget.itemModel.product.oldPrice, context: context), + ], + ), + Row( + children: [ + _itemCounter( + widget.itemModel.quantity, + minQuantity: widget.itemModel.product.orderMinimumQuantity, + maxQuantity: widget.itemModel.product.orderMaximumQuantity, + shouldStepperChangeApply: (apply,total) async{ + bool success = await widget.shouldStepperChangeApply(apply,total); + if(success == true) + setState(() => widget.itemModel.quantity = total); + return success; + } + ), + ], + ), + ], + ) + ], + ), + ), + + Positioned( + bottom: 8, + left: 10, + child: Row( + children: [ + _totalPrice((widget.itemModel.product.price * widget.itemModel.quantity), context: context), + _totalLabel(context: context), + ], + ), + ) + ], + ) + ) + ); + } +} + + +// -------------------- +// Product Image +// -------------------- +Widget _image(PackagesResponseModel model) => AspectRatio( + aspectRatio: 1/1, + child: Padding( + padding: const EdgeInsets.all(10), + child: Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.grey[300], width: 0.25), + boxShadow: [ + BoxShadow(color: Colors.grey[200], blurRadius: 2.0, spreadRadius: 1, offset: Offset(1,1.5)) + ], + borderRadius: BorderRadius.circular(8), + color: Colors.white, + shape: BoxShape.rectangle, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: (model.images.isNotEmpty) + ? Utils.loadNetworkImage(url: model.images.first.src, fitting:BoxFit.fill) + : Container(color: Colors.grey[200]) + ), + ), + ), +); + +// -------------------- +// Product Name +// -------------------- +Widget _itemName(String name) => Padding( + padding: const EdgeInsets.all(0), + child: Text( + name, + style: TextStyle( + fontWeight: FontWeight.normal, + color: Colors.black, + fontSize: 15)) +); + + +Widget _itemPrice(double price, {@required BuildContext context}) => Padding( + padding: const EdgeInsets.all(0), + child: Text( + '${price} ${TranslationBase.of(context).sar}', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.green, + fontSize: 15)) +); + + +// -------------------- +// Price Seperator +// -------------------- +Widget _priceSeperator() => Padding( + padding: const EdgeInsets.only(left: 3, right: 3), + child: Container(height: 0.5, width: 5, color: Colors.grey[100],), +); + + +// -------------------- +// Product Price +// -------------------- +Widget _itemOldPrice(double oldPrice, {@required BuildContext context}) => Padding( + padding: const EdgeInsets.all(0), + child: Text( + '${oldPrice} ${TranslationBase.of(context).sar}', + style: TextStyle( + fontWeight: FontWeight.normal, + decoration: TextDecoration.lineThrough, + color: Colors.grey, + fontSize: 10 + ) + ) +); + +// -------------------- +// Product Price +// -------------------- +Widget _itemCounter(int quantity, {int minQuantity, int maxQuantity, StepperCallbackFuture shouldStepperChangeApply}) => Padding( + padding: const EdgeInsets.all(0), + child: StepperView( + height: 25, + backgroundColor: Colors.grey[300], + foregroundColor: Colors.grey[200], + initialNumber: quantity, + minNumber: minQuantity, + maxNumber: maxQuantity, + counterCallback: shouldStepperChangeApply, + decreaseCallback: (){}, + increaseCallback: (){}, + ) +); + + +Widget _totalLabel({@required BuildContext context}) => Padding( + padding: const EdgeInsets.all(0), + child: Text( + '${TranslationBase.of(context).totalWithColonRight}', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.grey[600], + fontSize: 13 + ) + ) +); + + +Widget _totalPrice(double totalPrice, {@required BuildContext context}) => Padding( + padding: const EdgeInsets.all(0), + child: Text( + '${totalPrice.toStringAsFixed(2)} ${TranslationBase.of(context).sar}', + style: TextStyle( + fontWeight: FontWeight.normal, + color: Colors.green, + fontSize: 12)) +); + diff --git a/lib/widgets/offers_packages/PackagesOfferCard.dart b/lib/widgets/offers_packages/PackagesOfferCard.dart new file mode 100644 index 00000000..af0341c5 --- /dev/null +++ b/lib/widgets/offers_packages/PackagesOfferCard.dart @@ -0,0 +1,157 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart'; +import 'package:diplomaticquarterapp/uitl/utils.dart'; +import 'package:diplomaticquarterapp/widgets/others/StarRating.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + + +bool wide = true; + +class PackagesItemCard extends StatefulWidget { + final double itemWidth; + final double itemHeight; + final double itemContentPadding; + final PackagesResponseModel itemModel; + final Function(PackagesResponseModel product) onCartClick; + + const PackagesItemCard( + { + this.itemWidth, + this.itemHeight, + @required this.itemModel, + @required this.itemContentPadding, + @required this.onCartClick, + Key key}) + : super(key: key); + + @override + State createState() => PackagesItemCardState(); +} + +class PackagesItemCardState extends State { + + @override + Widget build(BuildContext context) { + wide = !wide; + return Directionality( + textDirection: TextDirection.rtl, + child: Stack( + children: [ + Padding( + padding: EdgeInsets.only( + left: widget.itemContentPadding, + right: widget.itemContentPadding, + top: widget.itemContentPadding + 5), + child: Container( + width: widget.itemWidth, + color: Colors.transparent, + child: Stack( + children: [ + Column( + mainAxisSize: MainAxisSize.max, + children: [ + AspectRatio( + aspectRatio:1 / 1, + child: applyShadow( + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Utils.loadNetworkImage( + url: + "https://wallpaperaccess.com/full/30103.jpg", + )), + )), + Text( + widget.itemModel.getName(), + style: TextStyle( + fontWeight: FontWeight.normal, + color: Colors.black, + fontSize: 15)), + Padding( + padding: const EdgeInsets.only(left: 10, right: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.max, + children: [ + Stack( + children: [ + Text( + '${widget.itemModel.oldPrice} ${'SAR'}', + style: TextStyle( + fontWeight: FontWeight.normal, + decoration: TextDecoration.lineThrough, + color: Colors.grey, + fontSize: 12)), + Padding( + padding: const EdgeInsets.only(top: 8), + child: Text( + '${widget.itemModel.price} ${'SAR'}', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.green, + fontSize: 18)), + ), + Padding( + padding: const EdgeInsets.only(top: 35), + child: StarRating( + size: 15, + totalCount: null, + totalAverage: widget.itemModel.approvedRatingSum.toDouble(), + forceStars: true), + ) + ], + ), + Spacer( + flex: 1, + ), + InkWell( + child: Icon( + Icons.add_shopping_cart_rounded, + size: 30.0, + color: Colors.grey, + ), + onTap: () { + widget.onCartClick(widget.itemModel); + }, + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + Positioned( + top: 0, + right: 0, + child: Visibility( + visible: false, + child: InkWell( + child: Icon( + Icons.favorite, + size: 40.0, + color: Colors.red, + ), + onTap: () { + + }, + ), + ), + ), + + Positioned( + top: 7, + left: 2, + child: Image.asset( + 'assets/images/discount_${'en'}.png', + height: 60, + width: 60, + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/offers_packages/offers_packages.dart b/lib/widgets/offers_packages/offers_packages.dart index 6834d516..8fae69b4 100644 --- a/lib/widgets/offers_packages/offers_packages.dart +++ b/lib/widgets/offers_packages/offers_packages.dart @@ -1,6 +1,5 @@ import 'dart:developer'; -import 'package:carousel_pro/carousel_pro.dart'; import 'package:carousel_slider/carousel_slider.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersCategoriesRequestModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersProductsRequestModel.dart'; diff --git a/lib/widgets/others/StarRating.dart b/lib/widgets/others/StarRating.dart index 2eaa7f91..5620b21a 100644 --- a/lib/widgets/others/StarRating.dart +++ b/lib/widgets/others/StarRating.dart @@ -14,26 +14,26 @@ class StarRating extends StatelessWidget { @override Widget build(BuildContext context) { return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - if (!forceStars && (totalAverage==null || totalAverage==0)) - Texts("New", style: "caption"), - if (forceStars || (totalAverage!=null && totalAverage>0)) - ...List.generate(5, (index) => - Padding( - padding: EdgeInsets.only(right: 1.0), - child: Icon( - (index+1) <= (totalAverage ?? 0) ? EvaIcons.star : EvaIcons.starOutline, - size: size, - color: (index+1) <= (totalAverage ?? 0) ? Color.fromRGBO(255, 186, 0, 1.0) : Theme.of(context).hintColor + mainAxisAlignment: MainAxisAlignment.start, + children: [ + if (!forceStars && (totalAverage==null || totalAverage==0)) + Texts("New", style: "caption"), + if (forceStars || (totalAverage!=null && totalAverage>0)) + ...List.generate(5, (index) => + Padding( + padding: EdgeInsets.only(right: 1.0), + child: Icon( + (index+1) <= (totalAverage ?? 0) ? EvaIcons.star : EvaIcons.starOutline, + size: size, + color: (index+1) <= (totalAverage ?? 0) ? Color.fromRGBO(255, 186, 0, 1.0) : Theme.of(context).hintColor + ), + ) ), - ) - ), - if (totalCount!=null) - SizedBox(width: 9.0), - if (totalCount!=null) - Texts("("+totalCount.toString()+")", style: "overline", color: Colors.grey[400],) - ] + if (totalCount!=null) + SizedBox(width: 9.0), + if (totalCount!=null) + Texts("("+totalCount.toString()+")", style: "overline", color: Colors.grey[400],) + ] ); } } \ No newline at end of file diff --git a/lib/widgets/others/app_scaffold_widget.dart b/lib/widgets/others/app_scaffold_widget.dart index ca91efe2..af61052e 100644 --- a/lib/widgets/others/app_scaffold_widget.dart +++ b/lib/widgets/others/app_scaffold_widget.dart @@ -27,6 +27,8 @@ import 'arrow_back.dart'; import 'network_base_view.dart'; import 'not_auh_page.dart'; +VoidCallback _onCartClick; + class AppScaffold extends StatelessWidget { final String appBarTitle; final Widget body; @@ -38,7 +40,9 @@ class AppScaffold extends StatelessWidget { final bool isBottomBar; final Widget floatingActionButton; final bool isPharmacy; + final bool isOfferPackages; final bool showPharmacyCart; + final bool showOfferPackagesCart; final String title; final String description; final bool isShowDecPage; @@ -63,6 +67,8 @@ class AppScaffold extends StatelessWidget { this.floatingActionButton, this.isPharmacy = false, this.showPharmacyCart = true, + this.isOfferPackages = false, + this.showOfferPackagesCart = false, this.title, this.description, this.isShowDecPage = true, @@ -74,6 +80,11 @@ class AppScaffold extends StatelessWidget { this.infoList, this.imagesInfo}); + AppScaffold setOnAppBarCartClick(VoidCallback onClick){ + _onCartClick = onClick; + return this; + } + @override Widget build(BuildContext context) { AppGlobal.context = context; @@ -87,6 +98,8 @@ class AppScaffold extends StatelessWidget { showHomeAppBarIcon: showHomeAppBarIcon, isPharmacy: isPharmacy, showPharmacyCart: showPharmacyCart, + isOfferPackages: isOfferPackages, + showOfferPackagesCart: showOfferPackagesCart, isShowDecPage: isShowDecPage, ) : null, @@ -122,7 +135,9 @@ class AppBarWidget extends StatelessWidget with PreferredSizeWidget { final bool showHomeAppBarIcon; final List appBarIcons; final bool isPharmacy; + final bool isOfferPackages; final bool showPharmacyCart; + final bool showOfferPackagesCart; final bool isShowDecPage; AppBarWidget( @@ -131,6 +146,8 @@ class AppBarWidget extends StatelessWidget with PreferredSizeWidget { this.appBarIcons, this.isPharmacy = true, this.showPharmacyCart = true, + this.isOfferPackages = false, + this.showOfferPackagesCart = false, this.isShowDecPage = true}); @override @@ -164,12 +181,25 @@ class AppBarWidget extends StatelessWidget with PreferredSizeWidget { actions: [ (isPharmacy && showPharmacyCart) ? IconButton( - icon: Icon(Icons.shopping_cart), - color: Colors.white, - onPressed: () { - Navigator.of(context).popUntil(ModalRoute.withName('/')); - }) + icon: Icon(Icons.shopping_cart), + color: Colors.white, + onPressed: () { + Navigator.of(context).popUntil(ModalRoute.withName('/')); + }) : Container(), + + (isOfferPackages && showOfferPackagesCart) + ? IconButton( + icon: Icon(Icons.shopping_cart), + color: Colors.white, + onPressed: () { + // Cart Click Event + if(_onCartClick != null) + _onCartClick(); + + }) + : Container(), + if (showHomeAppBarIcon) IconButton( icon: Icon(FontAwesomeIcons.home), @@ -179,6 +209,10 @@ class AppBarWidget extends StatelessWidget with PreferredSizeWidget { context, MaterialPageRoute(builder: (context) => LandingPage()), (Route r) => false); + + // Cart Click Event + if(_onCartClick != null) + _onCartClick(); }, ), if (appBarIcons != null) ...appBarIcons diff --git a/pubspec.yaml b/pubspec.yaml index 3b44f01f..54d08a71 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -101,9 +101,6 @@ dependencies: # Location Helper map_launcher: ^0.8.1 - #carousel slider - carousel_slider: ^2.3.1 - #Calendar Events manage_calendar_events: ^1.0.2 @@ -170,6 +167,10 @@ dependencies: # Dep by Zohaib shimmer: ^1.1.2 + cached_network_image: ^2.5.0 + carousel_slider: ^2.3.1 + flutter_material_pickers: 1.7.4 + flutter_staggered_grid_view: 0.3.4 # Marker Animation flutter_animarker: ^1.0.0 From 6f2c947687a4b67122335e0e2d55f39652ba8ff6 Mon Sep 17 00:00:00 2001 From: Zohaib Iqbal Kambrani <> Date: Tue, 16 Mar 2021 12:06:54 +0300 Subject: [PATCH 5/7] Packages And offers.. --- assets/images/svg/robort_svg.svg | 1 + assets/images/svg/success.svg | 67 +++ lib/config/config.dart | 3 +- lib/core/enum/PaymentOptions.dart | 34 ++ lib/core/model/ResponseModel.dart | 7 + .../PackagesCartItemsResponseModel.dart | 6 +- .../PackagesCustomerResponseModel.dart | 218 +++++++- .../responses/order_response_model.dart | 323 ++++++++++++ .../PackagesOffersServices.dart | 71 ++- .../PackagesOffersViewModel.dart | 15 +- lib/main.dart | 7 +- lib/pages/landing/home_page.dart | 29 ++ .../ClinicOfferAndPackagesPage.dart | 36 +- .../CreateCustomerDailogPage.dart | 206 ++++++++ .../OfferAndPackagesCartPage.dart | 40 +- .../packages_offers/OfferAndPackagesPage.dart | 201 ++++---- .../PackageOrderCompletedPage.dart | 146 ++++++ lib/routes.dart | 3 + lib/widgets/AnimatedTextFields.dart | 347 +++++++++++++ lib/widgets/LoadingButton.dart | 465 ++++++++++++++++++ lib/widgets/TextFieldInertiaDirection.java | 343 +++++++++++++ lib/widgets/in_app_browser/InAppBrowser.dart | 53 +- .../offers_packages/PackagesCartItemCard.dart | 2 +- .../offers_packages/PackagesOfferCard.dart | 4 +- lib/widgets/others/app_scaffold_widget.dart | 91 ++-- 25 files changed, 2522 insertions(+), 196 deletions(-) create mode 100644 assets/images/svg/success.svg create mode 100644 lib/core/enum/PaymentOptions.dart create mode 100644 lib/core/model/ResponseModel.dart create mode 100644 lib/core/model/packages_offers/responses/order_response_model.dart create mode 100644 lib/pages/packages_offers/CreateCustomerDailogPage.dart create mode 100644 lib/pages/packages_offers/PackageOrderCompletedPage.dart create mode 100644 lib/widgets/AnimatedTextFields.dart create mode 100644 lib/widgets/LoadingButton.dart create mode 100644 lib/widgets/TextFieldInertiaDirection.java diff --git a/assets/images/svg/robort_svg.svg b/assets/images/svg/robort_svg.svg index 07d3d4b4..8b4875a1 100644 --- a/assets/images/svg/robort_svg.svg +++ b/assets/images/svg/robort_svg.svg @@ -1,3 +1,4 @@ + diff --git a/assets/images/svg/success.svg b/assets/images/svg/success.svg new file mode 100644 index 00000000..343589bd --- /dev/null +++ b/assets/images/svg/success.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/config/config.dart b/lib/config/config.dart index a167e1b5..3c7e1e9a 100644 --- a/lib/config/config.dart +++ b/lib/config/config.dart @@ -11,8 +11,9 @@ const MAX_SMALL_SCREEN = 660; const EXA_CART_API_BASE_URL = 'http://10.200.101.75:9000'; const PACKAGES_CATEGORIES = '/api/categories'; const PACKAGES_PRODUCTS = '/api/products'; -const PACKAGES_SHOPPING_CART = '/api/shopping_cart_items'; const PACKAGES_CUSTOMER = '/api/customers'; +const PACKAGES_SHOPPING_CART = '/api/shopping_cart_items'; +const PACKAGES_ORDERS = '/api/orders'; const BASE_URL = 'https://uat.hmgwebservices.com/'; // const BASE_URL = 'https://hmgwebservices.com/'; diff --git a/lib/core/enum/PaymentOptions.dart b/lib/core/enum/PaymentOptions.dart new file mode 100644 index 00000000..bbf43cb2 --- /dev/null +++ b/lib/core/enum/PaymentOptions.dart @@ -0,0 +1,34 @@ +enum PaymentOptions { + VISA, + MASTERCARD, + MADA, + INSTALLMENT, + APPLEPAY +} + +extension PaymentOptions_ on PaymentOptions{ + String value(){ + switch(this){ + case PaymentOptions.VISA: + return "VISA"; + break; + + case PaymentOptions.MASTERCARD: + return "MASTERCARD"; + break; + + case PaymentOptions.MADA: + return "MADA"; + break; + + case PaymentOptions.INSTALLMENT: + return "INSTALLMENT"; + break; + case PaymentOptions.APPLEPAY: + return "APPLEPAY"; + break; + } + + return null; + } +} diff --git a/lib/core/model/ResponseModel.dart b/lib/core/model/ResponseModel.dart new file mode 100644 index 00000000..f34abf7d --- /dev/null +++ b/lib/core/model/ResponseModel.dart @@ -0,0 +1,7 @@ +class ResponseModel{ + final bool status; + final String error; + final T data; + + ResponseModel({this.status, this.data, this.error}); +} \ No newline at end of file diff --git a/lib/core/model/packages_offers/responses/PackagesCartItemsResponseModel.dart b/lib/core/model/packages_offers/responses/PackagesCartItemsResponseModel.dart index 9514976f..99c8a8b4 100644 --- a/lib/core/model/packages_offers/responses/PackagesCartItemsResponseModel.dart +++ b/lib/core/model/packages_offers/responses/PackagesCartItemsResponseModel.dart @@ -1,7 +1,7 @@ import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart'; -class CartProductResponseModel { +class PackagesCartItemsResponseModel { int _quantity; set quantity(int value) { @@ -19,7 +19,7 @@ class CartProductResponseModel { PackagesResponseModel get product => _product; int get id => _id; - CartProductResponseModel({ + PackagesCartItemsResponseModel({ int quantity, String shoppingCartType, int productId, @@ -32,7 +32,7 @@ class CartProductResponseModel { _id = id; } - CartProductResponseModel.fromJson(dynamic json) { + PackagesCartItemsResponseModel.fromJson(dynamic json) { _quantity = json["quantity"]; _shoppingCartType = json["shopping_cart_type"]; _productId = json["product_id"]; diff --git a/lib/core/model/packages_offers/responses/PackagesCustomerResponseModel.dart b/lib/core/model/packages_offers/responses/PackagesCustomerResponseModel.dart index 314b0ad3..245973dd 100644 --- a/lib/core/model/packages_offers/responses/PackagesCustomerResponseModel.dart +++ b/lib/core/model/packages_offers/responses/PackagesCustomerResponseModel.dart @@ -1,8 +1,38 @@ +import 'PackagesCartItemsResponseModel.dart'; + +/// shopping_cart_items : [] +/// billing_address : null +/// shipping_address : null +/// addresses : [{"first_name":null,"last_name":null,"email":"a2zzuhaib@gmil.com","company":null,"country_id":null,"country":null,"state_province_id":null,"city":null,"address1":null,"address2":null,"zip_postal_code":null,"phone_number":"0500409598","fax_number":null,"customer_attributes":null,"created_on_utc":"2021-03-11T09:40:23.8091261Z","province":null,"id":0}] +/// customer_guid : "1367e5c7-be3b-43cc-ad81-ff1fc8d3b130" +/// username : null +/// email : "a2zzuhaib@gmil.com" +/// first_name : null +/// last_name : null +/// language_id : null +/// date_of_birth : null +/// gender : null +/// admin_comment : null +/// is_tax_exempt : false +/// has_shopping_cart_items : false +/// active : true +/// deleted : false +/// is_system_account : false +/// system_name : null +/// last_ip_address : null +/// created_on_utc : "2021-03-11T09:40:23.7535859Z" +/// last_login_date_utc : null +/// last_activity_date_utc : "2021-03-11T09:40:23.7535892Z" +/// registered_in_store_id : 0 +/// subscribed_to_newsletter : false +/// role_ids : [] +/// id : 76823 + class PackagesCustomerResponseModel { - List _shoppingCartItems; + List _shoppingCartItems; dynamic _billingAddress; dynamic _shippingAddress; - List _addresses; + List _addresses; String _customerGuid; dynamic _username; String _email; @@ -24,13 +54,13 @@ class PackagesCustomerResponseModel { String _lastActivityDateUtc; int _registeredInStoreId; bool _subscribedToNewsletter; - List _roleIds; + List _roleIds; int _id; List get shoppingCartItems => _shoppingCartItems; dynamic get billingAddress => _billingAddress; dynamic get shippingAddress => _shippingAddress; - List get addresses => _addresses; + List get addresses => _addresses; String get customerGuid => _customerGuid; dynamic get username => _username; String get email => _email; @@ -59,7 +89,7 @@ class PackagesCustomerResponseModel { List shoppingCartItems, dynamic billingAddress, dynamic shippingAddress, - List addresses, + List addresses, String customerGuid, dynamic username, String email, @@ -115,6 +145,12 @@ class PackagesCustomerResponseModel { PackagesCustomerResponseModel.fromJson(dynamic json) { _billingAddress = json["billing_address"]; _shippingAddress = json["shipping_address"]; + if (json["addresses"] != null) { + _addresses = []; + json["addresses"].forEach((v) { + _addresses.add(Addresses.fromJson(v)); + }); + } _customerGuid = json["customer_guid"]; _username = json["username"]; _email = json["email"]; @@ -136,26 +172,22 @@ class PackagesCustomerResponseModel { _lastActivityDateUtc = json["last_activity_date_utc"]; _registeredInStoreId = json["registered_in_store_id"]; _subscribedToNewsletter = json["subscribed_to_newsletter"]; - _id = json["id"]; - // if (json["role_ids"] != null) { - // _roleIds = []; - // json["role_ids"].forEach((v) { - // _roleIds.add(dynamic.fromJson(v)); - // }); - // } - // if (json["addresses"] != null) { - // _addresses = []; - // json["addresses"].forEach((v) { - // _addresses.add(dynamic.fromJson(v)); - // }); - // } - // if (json["shopping_cart_items"] != null) { - // _shoppingCartItems = []; - // json["shopping_cart_items"].forEach((v) { - // _shoppingCartItems.add(dynamic.fromJson(v)); - // }); - // } + if (json["role_ids"] != null) { + _roleIds = []; + json["role_ids"].forEach((v) { + _roleIds.add(v); + }); + } + + if (json["shopping_cart_items"] != null) { + _shoppingCartItems = []; + json["shopping_cart_items"].forEach((v) { + _shoppingCartItems.add(PackagesCartItemsResponseModel.fromJson(v)); + }); + } + + _id = json["id"]; } Map toJson() { @@ -190,10 +222,146 @@ class PackagesCustomerResponseModel { map["registered_in_store_id"] = _registeredInStoreId; map["subscribed_to_newsletter"] = _subscribedToNewsletter; if (_roleIds != null) { - map["role_ids"] = _roleIds.map((v) => v.toJson()).toList(); + map["role_ids"] = _roleIds.map((v) => v).toList(); } map["id"] = _id; return map; } +} + +/// first_name : null +/// last_name : null +/// email : "a2zzuhaib@gmil.com" +/// company : null +/// country_id : null +/// country : null +/// state_province_id : null +/// city : null +/// address1 : null +/// address2 : null +/// zip_postal_code : null +/// phone_number : "0500409598" +/// fax_number : null +/// customer_attributes : null +/// created_on_utc : "2021-03-11T09:40:23.8091261Z" +/// province : null +/// id : 0 + +class Addresses { + dynamic _firstName; + dynamic _lastName; + String _email; + dynamic _company; + dynamic _countryId; + dynamic _country; + dynamic _stateProvinceId; + dynamic _city; + dynamic _address1; + dynamic _address2; + dynamic _zipPostalCode; + String _phoneNumber; + dynamic _faxNumber; + dynamic _customerAttributes; + String _createdOnUtc; + dynamic _province; + int _id; + + dynamic get firstName => _firstName; + dynamic get lastName => _lastName; + String get email => _email; + dynamic get company => _company; + dynamic get countryId => _countryId; + dynamic get country => _country; + dynamic get stateProvinceId => _stateProvinceId; + dynamic get city => _city; + dynamic get address1 => _address1; + dynamic get address2 => _address2; + dynamic get zipPostalCode => _zipPostalCode; + String get phoneNumber => _phoneNumber; + dynamic get faxNumber => _faxNumber; + dynamic get customerAttributes => _customerAttributes; + String get createdOnUtc => _createdOnUtc; + dynamic get province => _province; + int get id => _id; + + Addresses({ + dynamic firstName, + dynamic lastName, + String email, + dynamic company, + dynamic countryId, + dynamic country, + dynamic stateProvinceId, + dynamic city, + dynamic address1, + dynamic address2, + dynamic zipPostalCode, + String phoneNumber, + dynamic faxNumber, + dynamic customerAttributes, + String createdOnUtc, + dynamic province, + int id}){ + _firstName = firstName; + _lastName = lastName; + _email = email; + _company = company; + _countryId = countryId; + _country = country; + _stateProvinceId = stateProvinceId; + _city = city; + _address1 = address1; + _address2 = address2; + _zipPostalCode = zipPostalCode; + _phoneNumber = phoneNumber; + _faxNumber = faxNumber; + _customerAttributes = customerAttributes; + _createdOnUtc = createdOnUtc; + _province = province; + _id = id; +} + + Addresses.fromJson(dynamic json) { + _firstName = json["first_name"]; + _lastName = json["last_name"]; + _email = json["email"]; + _company = json["company"]; + _countryId = json["country_id"]; + _country = json["country"]; + _stateProvinceId = json["state_province_id"]; + _city = json["city"]; + _address1 = json["address1"]; + _address2 = json["address2"]; + _zipPostalCode = json["zip_postal_code"]; + _phoneNumber = json["phone_number"]; + _faxNumber = json["fax_number"]; + _customerAttributes = json["customer_attributes"]; + _createdOnUtc = json["created_on_utc"]; + _province = json["province"]; + _id = json["id"]; + } + + Map toJson() { + var map = {}; + map["first_name"] = _firstName; + map["last_name"] = _lastName; + map["email"] = _email; + map["company"] = _company; + map["country_id"] = _countryId; + map["country"] = _country; + map["state_province_id"] = _stateProvinceId; + map["city"] = _city; + map["address1"] = _address1; + map["address2"] = _address2; + map["zip_postal_code"] = _zipPostalCode; + map["phone_number"] = _phoneNumber; + map["fax_number"] = _faxNumber; + map["customer_attributes"] = _customerAttributes; + map["created_on_utc"] = _createdOnUtc; + map["province"] = _province; + map["id"] = _id; + return map; + } + } \ No newline at end of file diff --git a/lib/core/model/packages_offers/responses/order_response_model.dart b/lib/core/model/packages_offers/responses/order_response_model.dart new file mode 100644 index 00000000..ed53a765 --- /dev/null +++ b/lib/core/model/packages_offers/responses/order_response_model.dart @@ -0,0 +1,323 @@ + + +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCustomerResponseModel.dart'; +import 'PackagesCartItemsResponseModel.dart'; + +class PackagesOrderResponseModel { + String _customOrderNumber; + int _storeId; + dynamic _pickUpInStore; + String _paymentMethodSystemName; + String _customerCurrencyCode; + double _currencyRate; + int _customerTaxDisplayTypeId; + dynamic _vatNumber; + double _orderSubtotalInclTax; + double _orderSubtotalExclTax; + double _orderSubTotalDiscountInclTax; + double _orderSubTotalDiscountExclTax; + double _orderShippingInclTax; + double _orderShippingExclTax; + double _paymentMethodAdditionalFeeInclTax; + double _paymentMethodAdditionalFeeExclTax; + String _taxRates; + double _orderTax; + double _orderDiscount; + double _orderTotal; + double _refundedAmount; + dynamic _rewardPointsWereAdded; + String _checkoutAttributeDescription; + int _customerLanguageId; + int _affiliateId; + String _customerIp; + dynamic _authorizationTransactionId; + dynamic _authorizationTransactionCode; + dynamic _authorizationTransactionResult; + dynamic _captureTransactionId; + dynamic _captureTransactionResult; + dynamic _subscriptionTransactionId; + dynamic _paidDateUtc; + dynamic _shippingMethod; + dynamic _shippingRateComputationMethodSystemName; + String _customValuesXml; + dynamic _paymentOption; + bool _deleted; + String _createdOnUtc; + PackagesCustomerResponseModel _customer; + int _customerId; + dynamic _billingAddress; + dynamic _shippingAddress; + List _orderItems; + String _orderStatus; + String _paymentStatus; + String _shippingStatus; + String _customerTaxDisplayType; + int _id; + + String get customOrderNumber => _customOrderNumber; + int get storeId => _storeId; + dynamic get pickUpInStore => _pickUpInStore; + String get paymentMethodSystemName => _paymentMethodSystemName; + String get customerCurrencyCode => _customerCurrencyCode; + double get currencyRate => _currencyRate; + int get customerTaxDisplayTypeId => _customerTaxDisplayTypeId; + dynamic get vatNumber => _vatNumber; + double get orderSubtotalInclTax => _orderSubtotalInclTax; + double get orderSubtotalExclTax => _orderSubtotalExclTax; + double get orderSubTotalDiscountInclTax => _orderSubTotalDiscountInclTax; + double get orderSubTotalDiscountExclTax => _orderSubTotalDiscountExclTax; + double get orderShippingInclTax => _orderShippingInclTax; + double get orderShippingExclTax => _orderShippingExclTax; + double get paymentMethodAdditionalFeeInclTax => _paymentMethodAdditionalFeeInclTax; + double get paymentMethodAdditionalFeeExclTax => _paymentMethodAdditionalFeeExclTax; + String get taxRates => _taxRates; + double get orderTax => _orderTax; + double get orderDiscount => _orderDiscount; + double get orderTotal => _orderTotal; + double get refundedAmount => _refundedAmount; + dynamic get rewardPointsWereAdded => _rewardPointsWereAdded; + String get checkoutAttributeDescription => _checkoutAttributeDescription; + int get customerLanguageId => _customerLanguageId; + int get affiliateId => _affiliateId; + String get customerIp => _customerIp; + dynamic get authorizationTransactionId => _authorizationTransactionId; + dynamic get authorizationTransactionCode => _authorizationTransactionCode; + dynamic get authorizationTransactionResult => _authorizationTransactionResult; + dynamic get captureTransactionId => _captureTransactionId; + dynamic get captureTransactionResult => _captureTransactionResult; + dynamic get subscriptionTransactionId => _subscriptionTransactionId; + dynamic get paidDateUtc => _paidDateUtc; + dynamic get shippingMethod => _shippingMethod; + dynamic get shippingRateComputationMethodSystemName => _shippingRateComputationMethodSystemName; + String get customValuesXml => _customValuesXml; + dynamic get paymentOption => _paymentOption; + bool get deleted => _deleted; + String get createdOnUtc => _createdOnUtc; + PackagesCustomerResponseModel get customer => _customer; + int get customerId => _customerId; + dynamic get billingAddress => _billingAddress; + dynamic get shippingAddress => _shippingAddress; + List get orderItems => _orderItems; + String get orderStatus => _orderStatus; + String get paymentStatus => _paymentStatus; + String get shippingStatus => _shippingStatus; + String get customerTaxDisplayType => _customerTaxDisplayType; + int get id => _id; + + OrderResponseModel({ + String customOrderNumber, + int storeId, + dynamic pickUpInStore, + String paymentMethodSystemName, + String customerCurrencyCode, + double currencyRate, + int customerTaxDisplayTypeId, + dynamic vatNumber, + double orderSubtotalInclTax, + double orderSubtotalExclTax, + double orderSubTotalDiscountInclTax, + double orderSubTotalDiscountExclTax, + double orderShippingInclTax, + double orderShippingExclTax, + double paymentMethodAdditionalFeeInclTax, + double paymentMethodAdditionalFeeExclTax, + String taxRates, + double orderTax, + double orderDiscount, + double orderTotal, + double refundedAmount, + dynamic rewardPointsWereAdded, + String checkoutAttributeDescription, + int customerLanguageId, + int affiliateId, + String customerIp, + dynamic authorizationTransactionId, + dynamic authorizationTransactionCode, + dynamic authorizationTransactionResult, + dynamic captureTransactionId, + dynamic captureTransactionResult, + dynamic subscriptionTransactionId, + dynamic paidDateUtc, + dynamic shippingMethod, + dynamic shippingRateComputationMethodSystemName, + String customValuesXml, + dynamic paymentOption, + bool deleted, + String createdOnUtc, + PackagesCustomerResponseModel customer, + int customerId, + dynamic billingAddress, + dynamic shippingAddress, + List orderItems, + String orderStatus, + String paymentStatus, + String shippingStatus, + String customerTaxDisplayType, + int id}){ + _customOrderNumber = customOrderNumber; + _storeId = storeId; + _pickUpInStore = pickUpInStore; + _paymentMethodSystemName = paymentMethodSystemName; + _customerCurrencyCode = customerCurrencyCode; + _currencyRate = currencyRate; + _customerTaxDisplayTypeId = customerTaxDisplayTypeId; + _vatNumber = vatNumber; + _orderSubtotalInclTax = orderSubtotalInclTax; + _orderSubtotalExclTax = orderSubtotalExclTax; + _orderSubTotalDiscountInclTax = orderSubTotalDiscountInclTax; + _orderSubTotalDiscountExclTax = orderSubTotalDiscountExclTax; + _orderShippingInclTax = orderShippingInclTax; + _orderShippingExclTax = orderShippingExclTax; + _paymentMethodAdditionalFeeInclTax = paymentMethodAdditionalFeeInclTax; + _paymentMethodAdditionalFeeExclTax = paymentMethodAdditionalFeeExclTax; + _taxRates = taxRates; + _orderTax = orderTax; + _orderDiscount = orderDiscount; + _orderTotal = orderTotal; + _refundedAmount = refundedAmount; + _rewardPointsWereAdded = rewardPointsWereAdded; + _checkoutAttributeDescription = checkoutAttributeDescription; + _customerLanguageId = customerLanguageId; + _affiliateId = affiliateId; + _customerIp = customerIp; + _authorizationTransactionId = authorizationTransactionId; + _authorizationTransactionCode = authorizationTransactionCode; + _authorizationTransactionResult = authorizationTransactionResult; + _captureTransactionId = captureTransactionId; + _captureTransactionResult = captureTransactionResult; + _subscriptionTransactionId = subscriptionTransactionId; + _paidDateUtc = paidDateUtc; + _shippingMethod = shippingMethod; + _shippingRateComputationMethodSystemName = shippingRateComputationMethodSystemName; + _customValuesXml = customValuesXml; + _paymentOption = paymentOption; + _deleted = deleted; + _createdOnUtc = createdOnUtc; + _customer = customer; + _customerId = customerId; + _billingAddress = billingAddress; + _shippingAddress = shippingAddress; + _orderItems = orderItems; + _orderStatus = orderStatus; + _paymentStatus = paymentStatus; + _shippingStatus = shippingStatus; + _customerTaxDisplayType = customerTaxDisplayType; + _id = id; +} + + PackagesOrderResponseModel.fromJson(dynamic json) { + _customOrderNumber = json["custom_order_number"]; + _storeId = json["store_id"]; + _pickUpInStore = json["pick_up_in_store"]; + _paymentMethodSystemName = json["payment_method_system_name"]; + _customerCurrencyCode = json["customer_currency_code"]; + _currencyRate = json["currency_rate"]; + _customerTaxDisplayTypeId = json["customer_tax_display_type_id"]; + _vatNumber = json["vat_number"]; + _orderSubtotalInclTax = json["order_subtotal_incl_tax"]; + _orderSubtotalExclTax = json["order_subtotal_excl_tax"]; + _orderSubTotalDiscountInclTax = json["order_sub_total_discount_incl_tax"]; + _orderSubTotalDiscountExclTax = json["order_sub_total_discount_excl_tax"]; + _orderShippingInclTax = json["order_shipping_incl_tax"]; + _orderShippingExclTax = json["order_shipping_excl_tax"]; + _paymentMethodAdditionalFeeInclTax = json["payment_method_additional_fee_incl_tax"]; + _paymentMethodAdditionalFeeExclTax = json["payment_method_additional_fee_excl_tax"]; + _taxRates = json["tax_rates"]; + _orderTax = json["order_tax"]; + _orderDiscount = json["order_discount"]; + _orderTotal = json["order_total"]; + _refundedAmount = json["refunded_amount"]; + _rewardPointsWereAdded = json["reward_points_were_added"]; + _checkoutAttributeDescription = json["checkout_attribute_description"]; + _customerLanguageId = json["customer_language_id"]; + _affiliateId = json["affiliate_id"]; + _customerIp = json["customer_ip"]; + _authorizationTransactionId = json["authorization_transaction_id"]; + _authorizationTransactionCode = json["authorization_transaction_code"]; + _authorizationTransactionResult = json["authorization_transaction_result"]; + _captureTransactionId = json["capture_transaction_id"]; + _captureTransactionResult = json["capture_transaction_result"]; + _subscriptionTransactionId = json["subscription_transaction_id"]; + _paidDateUtc = json["paid_date_utc"]; + _shippingMethod = json["shipping_method"]; + _shippingRateComputationMethodSystemName = json["shipping_rate_computation_method_system_name"]; + _customValuesXml = json["custom_values_xml"]; + _paymentOption = json["payment_option"]; + _deleted = json["deleted"]; + _createdOnUtc = json["created_on_utc"]; + _customer = json["customer"] != null ? PackagesCustomerResponseModel.fromJson(json["customer"]) : null; + _customerId = json["customer_id"]; + _billingAddress = json["billing_address"]; + _shippingAddress = json["shipping_address"]; + if (json["order_items"] != null) { + _orderItems = []; + json["order_items"].forEach((v) { + _orderItems.add(PackagesCartItemsResponseModel.fromJson(v)); + }); + } + _orderStatus = json["order_status"]; + _paymentStatus = json["payment_status"]; + _shippingStatus = json["shipping_status"]; + _customerTaxDisplayType = json["customer_tax_display_type"]; + _id = json["id"]; + } + + Map toJson() { + var map = {}; + map["custom_order_number"] = _customOrderNumber; + map["store_id"] = _storeId; + map["pick_up_in_store"] = _pickUpInStore; + map["payment_method_system_name"] = _paymentMethodSystemName; + map["customer_currency_code"] = _customerCurrencyCode; + map["currency_rate"] = _currencyRate; + map["customer_tax_display_type_id"] = _customerTaxDisplayTypeId; + map["vat_number"] = _vatNumber; + map["order_subtotal_incl_tax"] = _orderSubtotalInclTax; + map["order_subtotal_excl_tax"] = _orderSubtotalExclTax; + map["order_sub_total_discount_incl_tax"] = _orderSubTotalDiscountInclTax; + map["order_sub_total_discount_excl_tax"] = _orderSubTotalDiscountExclTax; + map["order_shipping_incl_tax"] = _orderShippingInclTax; + map["order_shipping_excl_tax"] = _orderShippingExclTax; + map["payment_method_additional_fee_incl_tax"] = _paymentMethodAdditionalFeeInclTax; + map["payment_method_additional_fee_excl_tax"] = _paymentMethodAdditionalFeeExclTax; + map["tax_rates"] = _taxRates; + map["order_tax"] = _orderTax; + map["order_discount"] = _orderDiscount; + map["order_total"] = _orderTotal; + map["refunded_amount"] = _refundedAmount; + map["reward_points_were_added"] = _rewardPointsWereAdded; + map["checkout_attribute_description"] = _checkoutAttributeDescription; + map["customer_language_id"] = _customerLanguageId; + map["affiliate_id"] = _affiliateId; + map["customer_ip"] = _customerIp; + map["authorization_transaction_id"] = _authorizationTransactionId; + map["authorization_transaction_code"] = _authorizationTransactionCode; + map["authorization_transaction_result"] = _authorizationTransactionResult; + map["capture_transaction_id"] = _captureTransactionId; + map["capture_transaction_result"] = _captureTransactionResult; + map["subscription_transaction_id"] = _subscriptionTransactionId; + map["paid_date_utc"] = _paidDateUtc; + map["shipping_method"] = _shippingMethod; + map["shipping_rate_computation_method_system_name"] = _shippingRateComputationMethodSystemName; + map["custom_values_xml"] = _customValuesXml; + map["payment_option"] = _paymentOption; + map["deleted"] = _deleted; + map["created_on_utc"] = _createdOnUtc; + if (_customer != null) { + map["customer"] = _customer.toJson(); + } + map["customer_id"] = _customerId; + map["billing_address"] = _billingAddress; + map["shipping_address"] = _shippingAddress; + if (_orderItems != null) { + map["order_items"] = _orderItems.map((v) => v.toJson()).toList(); + } + map["order_status"] = _orderStatus; + map["payment_status"] = _paymentStatus; + map["shipping_status"] = _shippingStatus; + map["customer_tax_display_type"] = _customerTaxDisplayType; + map["id"] = _id; + return map; + } + +} diff --git a/lib/core/service/packages_offers/PackagesOffersServices.dart b/lib/core/service/packages_offers/PackagesOffersServices.dart index c0c7bc94..15811b82 100644 --- a/lib/core/service/packages_offers/PackagesOffersServices.dart +++ b/lib/core/service/packages_offers/PackagesOffersServices.dart @@ -3,6 +3,8 @@ import 'dart:developer'; import 'dart:ui'; import 'package:diplomaticquarterapp/config/config.dart'; +import 'package:diplomaticquarterapp/core/enum/PaymentOptions.dart'; +import 'package:diplomaticquarterapp/core/model/ResponseModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/requests/AddProductToCartRequestModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/requests/CreateCustomerRequestModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersCategoriesRequestModel.dart'; @@ -11,6 +13,7 @@ import 'package:diplomaticquarterapp/core/model/packages_offers/responses/Packag import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCategoriesResponseModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCustomerResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/order_response_model.dart'; import 'package:diplomaticquarterapp/core/service/base_service.dart'; import 'package:diplomaticquarterapp/core/service/client/base_app_client.dart'; import 'package:diplomaticquarterapp/uitl/gif_loader_dialog_utils.dart'; @@ -24,7 +27,8 @@ class OffersAndPackagesServices extends BaseService { List latestOffersList = List(); List bestSellerList = List(); List bannersList = List(); - List cartItemList = List(); + List cartItemList = List(); + String cartItemCount = ""; PackagesCustomerResponseModel customer; @@ -154,15 +158,15 @@ class OffersAndPackagesServices extends BaseService { // -------------------- // Create Customer // -------------------- - Future createCustomer(PackagesCustomerRequestModel request, {@required BuildContext context, bool showLoading = true, Function(bool) completion }) async{ + Future createCustomer(PackagesCustomerRequestModel request, {@required BuildContext context, bool showLoading = true, Function(bool) completion }) async{ if(customer != null) return Future.value(customer); - hasError = false; - var url = EXA_CART_API_BASE_URL + PACKAGES_CUSTOMER; - customer = null; + Future errorThrow; + _showLoading(context, showLoading); + var url = EXA_CART_API_BASE_URL + PACKAGES_CUSTOMER; await baseAppClient.simplePost(url, body: request.json(), onSuccess: (dynamic stringResponse, int statusCode){ _hideLoading(context, showLoading); @@ -172,10 +176,13 @@ class OffersAndPackagesServices extends BaseService { }, onFailure: (String error, int statusCode){ _hideLoading(context, showLoading); + errorThrow = Future.error(error); log(error); }); - return customer; + await Future.delayed(Duration(seconds: 1)); + + return errorThrow ?? customer; } @@ -183,7 +190,7 @@ class OffersAndPackagesServices extends BaseService { // -------------------- // Shopping Cart // -------------------- - Future> cartItems({@required BuildContext context, bool showLoading = true}) async{ + Future> cartItems({@required BuildContext context, bool showLoading = true}) async{ Future errorThrow; cartItemList.clear(); @@ -194,7 +201,7 @@ class OffersAndPackagesServices extends BaseService { var jsonResponse = json.decode(stringResponse); jsonResponse['shopping_carts'].forEach((json) { - cartItemList.add(CartProductResponseModel.fromJson(json)); + cartItemList.add(PackagesCartItemsResponseModel.fromJson(json)); }); }, onFailure: (String error, int statusCode) { @@ -206,8 +213,9 @@ class OffersAndPackagesServices extends BaseService { return errorThrow ?? cartItemList; } - Future addProductToCart(AddProductToCartRequestModel request, {@required BuildContext context, bool showLoading = true}) async{ + Future> addProductToCart(AddProductToCartRequestModel request, {@required BuildContext context, bool showLoading = true}) async{ Future errorThrow; + ResponseModel response; request.customer_id = customer.id; @@ -217,14 +225,16 @@ class OffersAndPackagesServices extends BaseService { _hideLoading(context, showLoading); var jsonResponse = json.decode(stringResponse); + var jsonCartItem = jsonResponse["shopping_carts"][0]; + response = ResponseModel(status: true, data: PackagesCartItemsResponseModel.fromJson(jsonCartItem), error: null); + cartItemCount = response.data.quantity.toString(); }, onFailure: (String error, int statusCode){ _hideLoading(context, showLoading); - log(error); - errorThrow = Future.error(error); + errorThrow = Future.error(ResponseModel(status: true, data: null, error: error)); }); - return errorThrow ?? true; + return errorThrow ?? response; } Future updateProductToCart(int cartItemID, {UpdateProductToCartRequestModel request, @required BuildContext context, bool showLoading = true}) async{ @@ -268,21 +278,29 @@ class OffersAndPackagesServices extends BaseService { // -------------------- // Place Order // -------------------- - Future placeOrder({@required BuildContext context, bool showLoading = true}) async{ + Future placeOrder({@required String paymentOption, @required BuildContext context, bool showLoading = true}) async{ Future errorThrow; var jsonBody = { "order": { - "customer_id" : customer.id + "customer_id" : customer.id, + "billing_address": { + "email": customer.email, + "phone_number": customer.addresses.first.phoneNumber + }, + "payment_method_system_name": "Payments.PayFort", + "payment_option": paymentOption } }; + int order_id; _showLoading(context, showLoading); - var url = EXA_CART_API_BASE_URL + PACKAGES_SHOPPING_CART; + var url = EXA_CART_API_BASE_URL + PACKAGES_ORDERS; await baseAppClient.simplePost(url, body: jsonBody, onSuccess: (dynamic stringResponse, int statusCode){ _hideLoading(context, showLoading); var jsonResponse = json.decode(stringResponse); + order_id = jsonResponse['orders'][0]['id']; }, onFailure: (String error, int statusCode){ _hideLoading(context, showLoading); @@ -290,7 +308,28 @@ class OffersAndPackagesServices extends BaseService { errorThrow = Future.error(error); }); - return errorThrow ?? true; + return errorThrow ?? order_id; + } + + Future> getOrderById(int id, {@required BuildContext context, bool showLoading = true}) async{ + Future errorThrow; + ResponseModel response; + + _showLoading(context, showLoading); + var url = EXA_CART_API_BASE_URL + PACKAGES_ORDERS + '/$id'; + await baseAppClient.simpleGet(url, onSuccess: (dynamic stringResponse, int statusCode) { + _hideLoading(context, showLoading); + + var jsonResponse = json.decode(stringResponse); + var jsonOrder = jsonResponse['orders'][0]; + response = ResponseModel(status: true, data: PackagesOrderResponseModel.fromJson(jsonOrder)); + + }, onFailure: (String error, int statusCode) { + _hideLoading(context, showLoading); + errorThrow = Future.error(ResponseModel(status: false,error: error)); + }, queryParams: null); + + return errorThrow ?? response; } } diff --git a/lib/core/viewModels/packages_offers/PackagesOffersViewModel.dart b/lib/core/viewModels/packages_offers/PackagesOffersViewModel.dart index 83e104dc..1029ed4d 100644 --- a/lib/core/viewModels/packages_offers/PackagesOffersViewModel.dart +++ b/lib/core/viewModels/packages_offers/PackagesOffersViewModel.dart @@ -9,10 +9,11 @@ import 'package:diplomaticquarterapp/core/viewModels/base_view_model.dart'; import 'package:flutter/cupertino.dart'; import 'package:diplomaticquarterapp/locator.dart'; -class OfferCategoriesViewModel extends BaseViewModel { +class OfferCategoriesViewModel extends BaseViewModel{ OffersAndPackagesServices service = locator(); get categoryList => service.categoryList; get productList => service.categoryList; + } class PackagesViewModel extends BaseViewModel { @@ -22,5 +23,15 @@ class PackagesViewModel extends BaseViewModel { List get latestOffersList => service.latestOffersList; List get bestSellerList => service.bestSellerList; List get bannersList => service.bannersList; - List get cartItemList => service.cartItemList; + List get cartItemList => service.cartItemList; + + + String _cartItemCount = ""; + + String get cartItemCount => _cartItemCount; + + set cartItemCount(String value) { + _cartItemCount = value; + notifyListeners(); + } } diff --git a/lib/main.dart b/lib/main.dart index 05615a68..aaffe3c3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -60,7 +60,6 @@ class MyApp extends StatelessWidget { ], child: Consumer( builder: (context, projectProvider, child) => MaterialApp( - showSemanticsDebugger: false, title: 'Diplomatic Quarter App', locale: projectProvider.appLocal, @@ -91,6 +90,7 @@ class MyApp extends StatelessWidget { color: Color(0xffB8382C), ), ), + floatingActionButtonTheme: FloatingActionButtonThemeData(highlightElevation: 2, disabledElevation: 0, elevation: 2), disabledColor: Colors.grey[300], errorColor: Color.fromRGBO(235, 80, 60, 1.0), scaffoldBackgroundColor: Color(0xffE9E9E9), // Colors.grey[100], @@ -115,8 +115,9 @@ class MyApp extends StatelessWidget { ), ), ), - // initialRoute: SPLASH, - initialRoute: PACKAGES_OFFERS, + initialRoute: SPLASH, + // initialRoute: PACKAGES_OFFERS, + // initialRoute: PACKAGES_ORDER_COMPLETED, routes: routes, debugShowCheckedModeBanner: false, ), diff --git a/lib/pages/landing/home_page.dart b/lib/pages/landing/home_page.dart index e3156331..94ebbc06 100644 --- a/lib/pages/landing/home_page.dart +++ b/lib/pages/landing/home_page.dart @@ -10,6 +10,7 @@ import 'package:diplomaticquarterapp/pages/Covid-DriveThru/covid-drivethru-locat import 'package:diplomaticquarterapp/pages/ErService/ErOptions.dart'; import 'package:diplomaticquarterapp/pages/base/base_view.dart'; import 'package:diplomaticquarterapp/pages/livecare/livecare_home.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackagesPage.dart'; import 'package:diplomaticquarterapp/pages/paymentService/payment_service.dart'; import 'package:diplomaticquarterapp/pages/pharmacies/screens/pharmacy_module_page.dart'; import 'package:diplomaticquarterapp/uitl/date_uitl.dart'; @@ -410,6 +411,34 @@ class _HomePageState extends State { ), ], ), + + Padding( + padding: const EdgeInsets.only(bottom: 15, right: 15, left: 15), + child: InkWell( + onTap: (){ + Navigator.of(context).push(MaterialPageRoute(builder: (context) => PackagesHomePage())); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Color(0xffB8382C), + ), + child: Padding( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + Text( + TranslationBase.of(context).offerAndPackages, + style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.normal), + ), + Spacer(), + Image.asset("assets/images/offer_icon.png"), + ], + ), + ), + ), + ), + ), if(projectViewModel.havePrivilege(64)||projectViewModel.havePrivilege(65)||projectViewModel.havePrivilege(67)) Container( margin: EdgeInsets.only(left: 15, right: 15), diff --git a/lib/pages/packages_offers/ClinicOfferAndPackagesPage.dart b/lib/pages/packages_offers/ClinicOfferAndPackagesPage.dart index b1e447ef..76688606 100644 --- a/lib/pages/packages_offers/ClinicOfferAndPackagesPage.dart +++ b/lib/pages/packages_offers/ClinicOfferAndPackagesPage.dart @@ -1,4 +1,6 @@ +import 'package:after_layout/after_layout.dart'; import 'package:carousel_slider/carousel_slider.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/requests/AddProductToCartRequestModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersCategoriesRequestModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersProductsRequestModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCategoriesResponseModel.dart'; @@ -21,6 +23,8 @@ import 'package:flutter/rendering.dart'; import 'package:flutter_material_pickers/flutter_material_pickers.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'CreateCustomerDailogPage.dart'; + dynamic languageID; class ClinicPackagesPage extends StatefulWidget { @@ -33,15 +37,39 @@ class ClinicPackagesPage extends StatefulWidget { } -class _ClinicPackagesPageState extends State { - List get _products => widget.products; +class _ClinicPackagesPageState extends State with AfterLayoutMixin{ + AppScaffold appScaffold; + List get _products => widget.products; PackagesViewModel viewModel; + + + onProductCartClick(PackagesResponseModel product) async { + if(viewModel.service.customer == null) + viewModel.service.customer = await CreateCustomerDialogPage(context: context).show(); + + if(viewModel.service.customer != null) { + var request = AddProductToCartRequestModel(product_id: product.id, customer_id: viewModel.service.customer.id); + await viewModel.service.addProductToCart(request, context: context).then((response){ + appScaffold.appBar.badgeUpdater(viewModel.service.cartItemCount); + }).catchError((error) { + utils.Utils.showErrorToast(error); + }); + } + } + + + @override + void afterFirstLayout(BuildContext context) async{ + appScaffold.appBar.badgeUpdater(viewModel.service.cartItemCount); + } + @override void initState() { super.initState(); } + @override Widget build(BuildContext context) { @@ -50,7 +78,7 @@ class _ClinicPackagesPageState extends State { onModelReady: (model){ viewModel = model; }, - builder: (_, model, wi) => AppScaffold( + builder: (_, model, wi) => appScaffold = AppScaffold( appBarTitle: TranslationBase.of(context).offerAndPackages, isShowAppBar: true, isPharmacy: false, @@ -66,7 +94,7 @@ class _ClinicPackagesPageState extends State { itemCount: _products.length, itemBuilder: (BuildContext context, int index) => new Container( color: Colors.transparent, - child: PackagesItemCard( itemContentPadding: 10,itemModel: _products[index],) + child: PackagesItemCard( itemContentPadding: 10,itemModel: _products[index], onCartClick: onProductCartClick,) ), staggeredTileBuilder: (int index) => StaggeredTile.fit(2), mainAxisSpacing: 20, diff --git a/lib/pages/packages_offers/CreateCustomerDailogPage.dart b/lib/pages/packages_offers/CreateCustomerDailogPage.dart new file mode 100644 index 00000000..4cf5fa5b --- /dev/null +++ b/lib/pages/packages_offers/CreateCustomerDailogPage.dart @@ -0,0 +1,206 @@ +import 'package:after_layout/after_layout.dart'; +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:diplomaticquarterapp/config/size_config.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/requests/AddProductToCartRequestModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/requests/CreateCustomerRequestModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersCategoriesRequestModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersProductsRequestModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCustomerResponseModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart'; +import 'package:diplomaticquarterapp/core/viewModels/packages_offers/PackagesOffersViewModel.dart'; +import 'package:diplomaticquarterapp/core/viewModels/pharmacyModule/order_model_view_model.dart'; +import 'package:diplomaticquarterapp/locator.dart'; +import 'package:diplomaticquarterapp/pages/base/base_view.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/ClinicOfferAndPackagesPage.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackageDetailPage.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackagesCartPage.dart'; +import 'package:diplomaticquarterapp/uitl/gif_loader_dialog_utils.dart'; +import 'package:diplomaticquarterapp/uitl/utils.dart' as utils; +import 'package:diplomaticquarterapp/widgets/AnimatedTextFields.dart'; +import 'package:diplomaticquarterapp/widgets/Loader/gif_loader_container.dart'; +import 'package:diplomaticquarterapp/widgets/LoadingButton.dart'; +import 'package:diplomaticquarterapp/widgets/carousel_indicator/carousel_indicator.dart'; +import 'package:diplomaticquarterapp/widgets/loadings/ShimmerLoading.dart'; +import 'package:diplomaticquarterapp/widgets/offers_packages/PackagesOfferCard.dart'; +import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_material_pickers/flutter_material_pickers.dart'; + +dynamic languageID; +var emailRegex = RegExp(r'^[^\s@]+@[^\s@]+\.[^\s@]+$'); + +class CreateCustomerDialogPage extends StatefulWidget { + final BuildContext context; + CreateCustomerDialogPage({this.context}); + PackagesViewModel viewModel; + + Future show() async{ + await showDialog(context: context, builder: (context ){ + return AlertDialog(content: this, shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20) + ), elevation: 5, ); + }); + return viewModel.service.customer; + } + + @override + _CreateCustomerDialogPageState createState() => _CreateCustomerDialogPageState(); + +} + +class _CreateCustomerDialogPageState extends State with AfterLayoutMixin, TickerProviderStateMixin{ + AnimationController _loadingController; + AnimationController _submitController; + bool _enableInput = true; + + Interval _nameTextFieldLoadingAnimationInterval = const Interval(0, .85); + + final _phoneFocusNode = FocusNode(); + + @override + void initState() { + _submitController = AnimationController(vsync: this, duration: Duration(milliseconds: 1000),); + super.initState(); + } + + @override + void afterFirstLayout(BuildContext context) async{ + } + + // Controllers + TextEditingController _emailTextController = TextEditingController(); + TextEditingController _phoneTextController = TextEditingController(); + TextEditingController _emailPinTextController = TextEditingController(); + TextEditingController _phonePinTextController = TextEditingController(); + + bool verifyPin = false; + + PackagesViewModel viewModel() => widget.viewModel; + + @override + Widget build(BuildContext context) { + + return BaseView( + allowAny: true, + onModelReady: (model) => widget.viewModel = model, + builder: (_, model, wi) => verifyPin ? verifyPinWidget() : userDetailWidget() + ); + } + + Widget verifyPinWidget(){ + + } + + + Widget userDetailWidget(){ + return + Container( + width: SizeConfig.realScreenWidth * 0.8, + height: 270, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text("Create Guest Customer"), + SizedBox(height: 30,), + AnimatedTextFormField( + enabled: _enableInput, + controller: _emailTextController, + width: 100, + loadingController: _loadingController, + interval: _nameTextFieldLoadingAnimationInterval, + labelText: "Email", + prefixIcon: Icon(Icons.email), + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.next, + onFieldSubmitted: (value) { + FocusScope.of(context).requestFocus(_phoneFocusNode); + }, + validator: (value){ + return (value.isEmpty || !emailRegex.hasMatch(value)) + ? 'Invalid email!' + : null; + }, + ), + + SizedBox(height: 30,), + AnimatedTextFormField( + enabled: _enableInput, + controller: _phoneTextController, + width: 100, + loadingController: _loadingController, + interval: _nameTextFieldLoadingAnimationInterval, + labelText: "Mobile Number", + prefixIcon: Icon(Icons.phone_android), + keyboardType: TextInputType.phone, + textInputAction: TextInputAction.next, + onFieldSubmitted: (value) { + FocusScope.of(context).requestFocus(_phoneFocusNode); + }, + validator: (value){ + return (value.isEmpty || !emailRegex.hasMatch(value)) + ? 'Invalid email!' + : null; + }, + ), + Spacer(flex: 1,), + + AnimatedButton( + color: Theme.of(context).primaryColor, + loadingColor: Theme.of(context).primaryColor, + controller: _submitController, + text: TranslationBase.of(context).done, + onPressed: (){ + createCustomer(); + }, + ) + + // RaisedButton( + // child: Text( + // TranslationBase.of(context).done, + // style: TextStyle(fontSize: 15, color: Colors.white, fontWeight: FontWeight.bold), + // ), + // padding: EdgeInsets.only(top: 5, bottom: 5, left: 0, right: 0), + // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5), side: BorderSide(color: Theme.of(context).primaryColor, width: 0.5)), + // color: Theme.of(context).primaryColor, + // onPressed: (){ + // + // }, + // ) + , + ], + ), + ); + } + + createCustomer() async{ + setState(() => _enableInput = false); + + loading(true); + var request = PackagesCustomerRequestModel(email: _emailTextController.text, phoneNumber: _phoneTextController.text); + viewModel().service + .createCustomer(request, context: context, showLoading: false) + .then((value) => success()) + .catchError((error) => showError(error)); + + } + + success() async{ + loading(false); + await Future.delayed(Duration(seconds: 2)); + Navigator.of(context).pop(); + } + + showError(String errorMessage) async{ + loading(false); + setState(() => _enableInput = true); + } + + loading(bool can){ + can ? _submitController.forward() : _submitController.reverse(); + } + +} diff --git a/lib/pages/packages_offers/OfferAndPackagesCartPage.dart b/lib/pages/packages_offers/OfferAndPackagesCartPage.dart index 98f237f6..72a82d0a 100644 --- a/lib/pages/packages_offers/OfferAndPackagesCartPage.dart +++ b/lib/pages/packages_offers/OfferAndPackagesCartPage.dart @@ -1,5 +1,7 @@ import 'package:after_layout/after_layout.dart'; import 'package:carousel_slider/carousel_slider.dart'; +import 'package:diplomaticquarterapp/core/enum/PaymentOptions.dart'; +import 'package:diplomaticquarterapp/core/model/ResponseModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/requests/AddProductToCartRequestModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersCategoriesRequestModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersProductsRequestModel.dart'; @@ -9,11 +11,13 @@ import 'package:diplomaticquarterapp/locator.dart'; import 'package:diplomaticquarterapp/pages/base/base_view.dart'; import 'package:diplomaticquarterapp/pages/packages_offers/ClinicOfferAndPackagesPage.dart'; import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackageDetailPage.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/PackageOrderCompletedPage.dart'; import 'package:diplomaticquarterapp/pages/pharmacies/screens/pharmacy-terms-conditions-page.dart'; import 'package:diplomaticquarterapp/uitl/gif_loader_dialog_utils.dart'; import 'package:diplomaticquarterapp/uitl/utils.dart' as utils; import 'package:diplomaticquarterapp/widgets/Loader/gif_loader_container.dart'; import 'package:diplomaticquarterapp/widgets/carousel_indicator/carousel_indicator.dart'; +import 'package:diplomaticquarterapp/widgets/in_app_browser/InAppBrowser.dart'; import 'package:diplomaticquarterapp/widgets/loadings/ShimmerLoading.dart'; import 'package:diplomaticquarterapp/widgets/offers_packages/PackagesCartItemCard.dart'; import 'package:diplomaticquarterapp/widgets/offers_packages/PackagesOfferCard.dart'; @@ -73,7 +77,21 @@ class _PackagesCartPageState extends State with AfterLayoutMix } onPayNowClick() async{ - await viewModel.service.placeOrder(context: context); + await viewModel.service.placeOrder(context: context,paymentOption: _selectedPaymentMethod.toUpperCase()).then((orderId){ + if(orderId.runtimeType == int){ // result == order_id + var browser = MyInAppBrowser( + context: context, + onExitCallback: (data, isDone) => paymentClosed(orderId: orderId, withStatus: isDone, data: data) + ); + browser.openPackagesPaymentBrowser(customer_id: viewModel.service.customer.id, order_id: orderId); + + }else{ + utils.Utils.showErrorToast('Failed to place order, please try again later'); + } + + }).catchError((error){ + utils.Utils.showErrorToast(error); + }); } @override @@ -124,10 +142,10 @@ class _PackagesCartPageState extends State with AfterLayoutMix itemModel: item, shouldStepperChangeApply: (apply,total) async{ var request = AddProductToCartRequestModel(product_id: item.productId, quantity: apply); - bool success = await viewModel.service.addProductToCart(request, context: context, showLoading: false).catchError((error){ + ResponseModel response = await viewModel.service.addProductToCart(request, context: context, showLoading: false).catchError((error){ utils.Utils.showErrorToast(error); }); - return success ?? false; + return response.status ?? false; }, ) ); @@ -180,13 +198,27 @@ class _PackagesCartPageState extends State with AfterLayoutMix await viewModel.service.cartItems(context: context).catchError((error) {}); setState((){}); } + + paymentClosed({@required int orderId, @required bool withStatus, dynamic data}) async{ + viewModel.service.getOrderById(orderId, context: context).then((value){ + var heading = withStatus ? "Success" : "Failed"; + var title = "Your order has been placed successfully"; + var subTitle = "Order# ${value.data.customOrderNumber}"; + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (context) => PackageOrderCompletedPage(heading: heading, title: title, subTitle: subTitle)) + ); + + }).catchError((error){ + debugPrint(error); + }); + } } // /* Payment Footer Widgets */ // --------------------------- String _selectedPaymentMethod; Widget _paymentOptions(BuildContext context, Function(String) onSelected) { - double height = 22; + double height = 30; Widget buttonContent(bool isSelected, String imageName) { return Container( diff --git a/lib/pages/packages_offers/OfferAndPackagesPage.dart b/lib/pages/packages_offers/OfferAndPackagesPage.dart index 0a83c683..c908cba4 100644 --- a/lib/pages/packages_offers/OfferAndPackagesPage.dart +++ b/lib/pages/packages_offers/OfferAndPackagesPage.dart @@ -4,12 +4,14 @@ import 'package:diplomaticquarterapp/core/model/packages_offers/requests/AddProd import 'package:diplomaticquarterapp/core/model/packages_offers/requests/CreateCustomerRequestModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersCategoriesRequestModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/requests/OffersProductsRequestModel.dart'; +import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesCustomerResponseModel.dart'; import 'package:diplomaticquarterapp/core/model/packages_offers/responses/PackagesResponseModel.dart'; import 'package:diplomaticquarterapp/core/viewModels/packages_offers/PackagesOffersViewModel.dart'; import 'package:diplomaticquarterapp/core/viewModels/pharmacyModule/order_model_view_model.dart'; import 'package:diplomaticquarterapp/locator.dart'; import 'package:diplomaticquarterapp/pages/base/base_view.dart'; import 'package:diplomaticquarterapp/pages/packages_offers/ClinicOfferAndPackagesPage.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/CreateCustomerDailogPage.dart'; import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackageDetailPage.dart'; import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackagesCartPage.dart'; import 'package:diplomaticquarterapp/uitl/gif_loader_dialog_utils.dart'; @@ -85,120 +87,123 @@ class _PackagesHomePageState extends State with AfterLayoutMix onProductCartClick(PackagesResponseModel product) async { if(viewModel.service.customer == null) - await viewModel.service.createCustomer(PackagesCustomerRequestModel(email: "zikambrani@gmail.com", phoneNumber: "0500409598"), context: context); + viewModel.service.customer = await CreateCustomerDialogPage(context: context).show(); - if(viewModel.service.customer != null){ + if(viewModel.service.customer != null) { var request = AddProductToCartRequestModel(product_id: product.id, customer_id: viewModel.service.customer.id); - await viewModel.service.addProductToCart(request, context: context).catchError((error) { + await viewModel.service.addProductToCart(request, context: context).then((response){ + appScaffold.appBar.badgeUpdater(viewModel.service.cartItemCount); + }).catchError((error) { utils.Utils.showErrorToast(error); }); } } + AppScaffold appScaffold; @override Widget build(BuildContext context) { - return BaseView( allowAny: true, onModelReady: (model) => viewModel = model, builder: (_, model, wi){ return - AppScaffold( - appBarTitle: TranslationBase.of(context).offerAndPackages, - isShowAppBar: true, - isPharmacy: false, - showPharmacyCart: false, - showHomeAppBarIcon: false, - isOfferPackages: true, - showOfferPackagesCart: true, - isShowDecPage: false, - body: ListView( - children: [ - - // Top Banner Carousel - AspectRatio( - aspectRatio: 2.2/1, - child: bannerCarousel() - ), - - Center( - child: CarouselIndicator( - activeColor: Theme.of(context).appBarTheme.color, - color: Colors.grey[300], - cornerRadius: 15, - width: 15, height: 15, - count: _bannerCarousel.itemCount, - index: carouselIndicatorIndex, - onClick: (index){ - debugPrint('onClick at ${index}'); - }, - ), - ), - - SizedBox(height: 10,), - - Padding( - padding: const EdgeInsets.all(15), - child: Column( - children: [ - // Search Textfield - searchTextField(), - - SizedBox(height: 10,), - - // Filter Selection - filterOptionSelection(), - - SizedBox(height: 20,), - - // Horizontal Scrollable Cards - Text( - "Latest offers", - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.black87, - fontSize: 20 - ), - ), - - // Latest Offers Horizontal Scrollable List - AspectRatio( - aspectRatio: 1.3/1, - child: LayoutBuilder(builder: (context, constraints){ - double itemContentPadding = 10; - double itemWidth = (constraints.maxWidth/2) - (itemContentPadding*2); - return latestOfferListView(itemWidth: itemWidth, itemContentPadding: itemContentPadding); - }), + appScaffold = + AppScaffold( + appBarTitle: TranslationBase.of(context).offerAndPackages, + isShowAppBar: true, + isPharmacy: false, + showPharmacyCart: false, + showHomeAppBarIcon: false, + isOfferPackages: true, + showOfferPackagesCart: true, + isShowDecPage: false, + body: ListView( + children: [ + + // Top Banner Carousel + AspectRatio( + aspectRatio: 2.2/1, + child: bannerCarousel() + ), + + Center( + child: CarouselIndicator( + activeColor: Theme.of(context).appBarTheme.color, + color: Colors.grey[300], + cornerRadius: 15, + width: 15, height: 15, + count: _bannerCarousel.itemCount, + index: carouselIndicatorIndex, + onClick: (index){ + debugPrint('onClick at ${index}'); + }, ), - - SizedBox(height: 10,), - - Text( - "Best sellers", - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.black87, - fontSize: 20 - ), - ), - - - // Best Seller Horizontal Scrollable List - AspectRatio( - aspectRatio: 1.3/1, - child: LayoutBuilder(builder: (context, constraints){ - double itemContentPadding = 10; // 10 is content padding in each item - double itemWidth = (constraints.maxWidth/2) - (itemContentPadding*2 /* 2 = LeftRight */); - return bestSellerListView(itemWidth: itemWidth, itemContentPadding: itemContentPadding); - }), - ) - - ],), + ), + + SizedBox(height: 10,), + + Padding( + padding: const EdgeInsets.all(15), + child: Column( + children: [ + // Search Textfield + searchTextField(), + + SizedBox(height: 10,), + + // Filter Selection + filterOptionSelection(), + + SizedBox(height: 20,), + + // Horizontal Scrollable Cards + Text( + "Latest offers", + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black87, + fontSize: 20 + ), + ), + + // Latest Offers Horizontal Scrollable List + AspectRatio( + aspectRatio: 1.3/1, + child: LayoutBuilder(builder: (context, constraints){ + double itemContentPadding = 10; + double itemWidth = (constraints.maxWidth/2) - (itemContentPadding*2); + return latestOfferListView(itemWidth: itemWidth, itemContentPadding: itemContentPadding); + }), + ), + + SizedBox(height: 10,), + + Text( + "Best sellers", + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black87, + fontSize: 20 + ), + ), + + + // Best Seller Horizontal Scrollable List + AspectRatio( + aspectRatio: 1.3/1, + child: LayoutBuilder(builder: (context, constraints){ + double itemContentPadding = 10; // 10 is content padding in each item + double itemWidth = (constraints.maxWidth/2) - (itemContentPadding*2 /* 2 = LeftRight */); + return bestSellerListView(itemWidth: itemWidth, itemContentPadding: itemContentPadding); + }), + ) + + ],), + ), + ], ), - ], - ), - ) - .setOnAppBarCartClick(onCartClick); + ) + .setOnAppBarCartClick(onCartClick); } ); } diff --git a/lib/pages/packages_offers/PackageOrderCompletedPage.dart b/lib/pages/packages_offers/PackageOrderCompletedPage.dart new file mode 100644 index 00000000..c0a7db46 --- /dev/null +++ b/lib/pages/packages_offers/PackageOrderCompletedPage.dart @@ -0,0 +1,146 @@ +import 'package:diplomaticquarterapp/core/viewModels/packages_offers/PackagesOffersViewModel.dart'; +import 'package:diplomaticquarterapp/pages/base/base_view.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_svg/svg.dart'; + +dynamic languageID; + +class PackageOrderCompletedPage extends StatelessWidget{ + double buttonHeight; + double buttonWidth; + Widget icon; + String heading; + String title; + String subTitle; + String actionTitle; + + PackageOrderCompletedPage({this.buttonWidth, this.buttonHeight, @required this.heading, @required this.title, @required this.subTitle, this.actionTitle }); + + @override + Widget build(BuildContext context) { + assert((heading != null || title != null || subTitle != null), "Data missing in properties"); + + buttonWidth = buttonWidth ?? MediaQuery.of(context).size.width/2; + buttonHeight = buttonHeight ?? 40; + actionTitle = actionTitle ?? TranslationBase.of(context).done; + + return BaseView( + allowAny: true, + onModelReady: (model){}, + builder: (_, model, wi){ + return Container( + color: Colors.white, + child: Padding( + padding: const EdgeInsets.all(15), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + + AspectRatio( + aspectRatio: 1.2/1, + child: + iconWidget(context), + ), + + headingWidget(context), + + + AspectRatio( + aspectRatio: 1/1, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + titleWidget(context), + SizedBox(height: 20,), + subTitleWidget(context), + SizedBox(height: 50,), + actionWidget(context) + ], + ), + ), + ) + + ], + ), + ), + ); + } + ); + } + + Widget iconWidget(BuildContext context){ + return Padding( + padding: const EdgeInsets.all(50), + child: icon ?? SvgPicture.asset( + "assets/images/svg/success.svg", + semanticsLabel: 'icon' + ), + ); + } + + Widget headingWidget(BuildContext context) => Text( + heading, + textAlign: TextAlign.center, + maxLines: 1, + style: TextStyle( + color: Theme.of(context).primaryColor, + fontSize: 35.0, + fontWeight: FontWeight.bold, + letterSpacing: 0.9 + ) + ); + + Widget titleWidget(BuildContext context) => Text( + title, + textAlign: TextAlign.center, + maxLines: 2, + style: TextStyle( + color: Theme.of(context).primaryColor, + fontSize: 25.0, + fontWeight: FontWeight.w200, + letterSpacing: 0.9 + ) + ); + + Widget subTitleWidget(BuildContext context) => Text( + subTitle, + textAlign: TextAlign.center, + maxLines: 2, + style: TextStyle( + color: Theme.of(context).primaryColor, + fontSize: 15.0, + fontWeight: FontWeight.normal, + letterSpacing: 0.9 + ) + ); + + + Widget actionWidget(BuildContext context) => Container( + height: buttonHeight, + width: buttonWidth, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + + shape:RoundedRectangleBorder( + borderRadius: new BorderRadius.circular(buttonHeight/2), + ) + ), + child: Text( + actionTitle, + style: TextStyle( + color: Colors.white, + fontSize: 18.0, + fontWeight: FontWeight.normal, + ) + ), + onPressed: (){ + Navigator.of(context).pop(); + }, + ), + ); + +} diff --git a/lib/routes.dart b/lib/routes.dart index 57452796..51aaccfe 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -12,6 +12,7 @@ import 'package:diplomaticquarterapp/pages/login/login.dart'; import 'package:diplomaticquarterapp/pages/login/register.dart'; import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackagesCartPage.dart'; import 'package:diplomaticquarterapp/pages/packages_offers/OfferAndPackagesPage.dart'; +import 'package:diplomaticquarterapp/pages/packages_offers/PackageOrderCompletedPage.dart'; import 'package:diplomaticquarterapp/pages/settings/settings.dart'; import 'package:diplomaticquarterapp/pages/symptom-checker/info.dart'; import 'package:diplomaticquarterapp/pages/symptom-checker/select-gender.dart'; @@ -39,6 +40,7 @@ const String SELECT_GENDER = 'select-gender'; const String SETTINGS = 'settings'; const String PACKAGES_OFFERS = 'packages-offers'; const String PACKAGES_OFFERS_CART = 'packages-offers-cart'; +const String PACKAGES_ORDER_COMPLETED = 'packages-offers-cart'; var routes = { SPLASH: (_) => SplashScreen(), @@ -60,4 +62,5 @@ var routes = { SETTINGS: (_) => Settings(), PACKAGES_OFFERS: (_) => PackagesHomePage(), PACKAGES_OFFERS_CART: (_) => PackagesCartPage(), + PACKAGES_ORDER_COMPLETED: (_) => PackageOrderCompletedPage(), }; diff --git a/lib/widgets/AnimatedTextFields.dart b/lib/widgets/AnimatedTextFields.dart new file mode 100644 index 00000000..b4ad5f65 --- /dev/null +++ b/lib/widgets/AnimatedTextFields.dart @@ -0,0 +1,347 @@ +import 'dart:math'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +enum TextFieldInertiaDirection { + left, + right, +} + +Interval _getInternalInterval( + double start, + double end, + double externalStart, + double externalEnd, [ + Curve curve = Curves.linear, + ]) { + return Interval( + start + (end - start) * externalStart, + start + (end - start) * externalEnd, + curve: curve, + ); +} + +class AnimatedTextFormField extends StatefulWidget { + AnimatedTextFormField({ + Key key, + this.interval = const Interval(0.0, 1.0), + @required this.width, + this.loadingController, + this.inertiaController, + this.inertiaDirection, + this.enabled = true, + this.labelText, + this.prefixIcon, + this.suffixIcon, + this.keyboardType, + this.textInputAction, + this.obscureText = false, + this.controller, + this.focusNode, + this.validator, + this.onFieldSubmitted, + this.onSaved, + }) : assert((inertiaController == null && inertiaDirection == null) || + (inertiaController != null && inertiaDirection != null)), + super(key: key); + + final Interval interval; + final AnimationController loadingController; + final AnimationController inertiaController; + final double width; + final bool enabled; + final String labelText; + final Widget prefixIcon; + final Widget suffixIcon; + final TextInputType keyboardType; + final TextInputAction textInputAction; + final bool obscureText; + final TextEditingController controller; + final FocusNode focusNode; + final FormFieldValidator validator; + final ValueChanged onFieldSubmitted; + final FormFieldSetter onSaved; + final TextFieldInertiaDirection inertiaDirection; + + @override + _AnimatedTextFormFieldState createState() => _AnimatedTextFormFieldState(); +} + +class _AnimatedTextFormFieldState extends State { + Animation scaleAnimation; + Animation sizeAnimation; + Animation suffixIconOpacityAnimation; + + Animation fieldTranslateAnimation; + Animation iconRotationAnimation; + Animation iconTranslateAnimation; + + @override + void initState() { + super.initState(); + + widget.inertiaController?.addStatusListener(handleAnimationStatus); + + final interval = widget.interval; + final loadingController = widget.loadingController; + + if (loadingController != null) { + scaleAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: loadingController, + curve: _getInternalInterval( + 0, .2, interval.begin, interval.end, Curves.easeOutBack), + )); + suffixIconOpacityAnimation = + Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( + parent: loadingController, + curve: _getInternalInterval(.65, 1.0, interval.begin, interval.end), + )); + _updateSizeAnimation(); + } + + final inertiaController = widget.inertiaController; + final inertiaDirection = widget.inertiaDirection; + final sign = inertiaDirection == TextFieldInertiaDirection.right ? 1 : -1; + + if (inertiaController != null) { + fieldTranslateAnimation = Tween( + begin: 0.0, + end: sign * 15.0, + ).animate(CurvedAnimation( + parent: inertiaController, + curve: Interval(0, .5, curve: Curves.easeOut), + reverseCurve: Curves.easeIn, + )); + iconRotationAnimation = + Tween(begin: 0.0, end: sign * pi / 12 /* ~15deg */) + .animate(CurvedAnimation( + parent: inertiaController, + curve: Interval(.5, 1.0, curve: Curves.easeOut), + reverseCurve: Curves.easeIn, + )); + iconTranslateAnimation = + Tween(begin: 0.0, end: 8.0).animate(CurvedAnimation( + parent: inertiaController, + curve: Interval(.5, 1.0, curve: Curves.easeOut), + reverseCurve: Curves.easeIn, + )); + } + } + + void _updateSizeAnimation() { + final interval = widget.interval; + final loadingController = widget.loadingController; + + sizeAnimation = Tween( + begin: 48.0, + end: widget.width, + ).animate(CurvedAnimation( + parent: loadingController, + curve: _getInternalInterval( + .2, 1.0, interval.begin, interval.end, Curves.linearToEaseOut), + reverseCurve: Curves.easeInExpo, + )); + } + + @override + void didUpdateWidget(AnimatedTextFormField oldWidget) { + super.didUpdateWidget(oldWidget); + + if (oldWidget.width != widget.width) { + _updateSizeAnimation(); + } + } + + @override + dispose() { + widget.inertiaController?.removeStatusListener(handleAnimationStatus); + super.dispose(); + } + + void handleAnimationStatus(status) { + if (status == AnimationStatus.completed) { + widget.inertiaController?.reverse(); + } + } + + Widget _buildInertiaAnimation(Widget child) { + if (widget.inertiaController == null) { + return child; + } + + return AnimatedBuilder( + animation: iconTranslateAnimation, + builder: (context, child) => Transform( + alignment: Alignment.center, + transform: Matrix4.identity() + ..translate(iconTranslateAnimation.value) + ..rotateZ(iconRotationAnimation.value), + child: child, + ), + child: child, + ); + } + + InputDecoration _getInputDecoration(ThemeData theme) { + return InputDecoration( + contentPadding: EdgeInsets.fromLTRB(0, 0, 0, 0), + border: OutlineInputBorder( + borderRadius: new BorderRadius.circular(10.0), + borderSide: new BorderSide(), + ), + labelText: widget.labelText, + prefixIcon: _buildInertiaAnimation(widget.prefixIcon), + suffixIcon: _buildInertiaAnimation(widget.loadingController != null + ? FadeTransition( + opacity: suffixIconOpacityAnimation, + child: widget.suffixIcon, + ) + : widget.suffixIcon), + ); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + Widget textField = TextFormField( + controller: widget.controller, + focusNode: widget.focusNode, + decoration: _getInputDecoration(theme), + keyboardType: widget.keyboardType, + textInputAction: widget.textInputAction, + obscureText: widget.obscureText, + onFieldSubmitted: widget.onFieldSubmitted, + onSaved: widget.onSaved, + validator: widget.validator, + enabled: widget.enabled, + ); + + if (widget.loadingController != null) { + textField = ScaleTransition( + scale: scaleAnimation, + child: AnimatedBuilder( + animation: sizeAnimation, + builder: (context, child) => ConstrainedBox( + constraints: BoxConstraints.tightFor(width: sizeAnimation.value), + child: child, + ), + child: textField, + ), + ); + } + + if (widget.inertiaController != null) { + textField = AnimatedBuilder( + animation: fieldTranslateAnimation, + builder: (context, child) => Transform.translate( + offset: Offset(fieldTranslateAnimation.value, 0), + child: child, + ), + child: textField, + ); + } + + return textField; + } +} + +class AnimatedPasswordTextFormField extends StatefulWidget { + AnimatedPasswordTextFormField({ + Key key, + this.interval = const Interval(0.0, 1.0), + @required this.animatedWidth, + this.loadingController, + this.inertiaController, + this.inertiaDirection, + this.enabled = true, + this.labelText, + this.keyboardType, + this.textInputAction, + this.controller, + this.focusNode, + this.validator, + this.onFieldSubmitted, + this.onSaved, + }) : assert((inertiaController == null && inertiaDirection == null) || + (inertiaController != null && inertiaDirection != null)), + super(key: key); + + final Interval interval; + final AnimationController loadingController; + final AnimationController inertiaController; + final double animatedWidth; + final bool enabled; + final String labelText; + final TextInputType keyboardType; + final TextInputAction textInputAction; + final TextEditingController controller; + final FocusNode focusNode; + final FormFieldValidator validator; + final ValueChanged onFieldSubmitted; + final FormFieldSetter onSaved; + final TextFieldInertiaDirection inertiaDirection; + + @override + _AnimatedPasswordTextFormFieldState createState() => + _AnimatedPasswordTextFormFieldState(); +} + +class _AnimatedPasswordTextFormFieldState + extends State { + var _obscureText = true; + + @override + Widget build(BuildContext context) { + return AnimatedTextFormField( + interval: widget.interval, + loadingController: widget.loadingController, + inertiaController: widget.inertiaController, + width: widget.animatedWidth, + enabled: widget.enabled, + labelText: widget.labelText, + prefixIcon: Icon(Icons.lock, size: 20), + suffixIcon: GestureDetector( + onTap: () => setState(() => _obscureText = !_obscureText), + dragStartBehavior: DragStartBehavior.down, + child: AnimatedCrossFade( + duration: const Duration(milliseconds: 250), + firstCurve: Curves.easeInOutSine, + secondCurve: Curves.easeInOutSine, + alignment: Alignment.center, + layoutBuilder: (Widget topChild, _, Widget bottomChild, __) { + return Stack( + alignment: Alignment.center, + children: [bottomChild, topChild], + ); + }, + firstChild: Icon( + Icons.visibility, + size: 25.0, + semanticLabel: 'show password', + ), + secondChild: Icon( + Icons.visibility_off, + size: 25.0, + semanticLabel: 'hide password', + ), + crossFadeState: _obscureText + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + ), + ), + obscureText: _obscureText, + keyboardType: widget.keyboardType, + textInputAction: widget.textInputAction, + controller: widget.controller, + focusNode: widget.focusNode, + validator: widget.validator, + onFieldSubmitted: widget.onFieldSubmitted, + onSaved: widget.onSaved, + inertiaDirection: widget.inertiaDirection, + ); + } +} \ No newline at end of file diff --git a/lib/widgets/LoadingButton.dart b/lib/widgets/LoadingButton.dart new file mode 100644 index 00000000..8fef5271 --- /dev/null +++ b/lib/widgets/LoadingButton.dart @@ -0,0 +1,465 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'dart:math'; + +class AnimatedButton extends StatefulWidget { + AnimatedButton({ + Key key, + @required this.text, + @required this.onPressed, + @required this.controller, + this.textColor, + this.loadingColor, + this.color, + }) : super(key: key); + + final String text; + final Color color; + final Color textColor; + final Color loadingColor; + final Function onPressed; + final AnimationController controller; + + @override + _AnimatedButtonState createState() => _AnimatedButtonState(); +} + +class _AnimatedButtonState extends State + with SingleTickerProviderStateMixin { + Animation _sizeAnimation; + Animation _textOpacityAnimation; + Animation _buttonOpacityAnimation; + Animation _ringThicknessAnimation; + Animation _ringOpacityAnimation; + Animation _colorAnimation; + var _isLoading = false; + var _hover = false; + var _width = 120.0; + + Color _color; + Color _loadingColor; + + static const _height = 40.0; + static const _loadingCircleRadius = _height / 2; + static const _loadingCircleThickness = 4.0; + + @override + void initState() { + super.initState(); + + _textOpacityAnimation = Tween(begin: 1.0, end: 0.0).animate( + CurvedAnimation( + parent: widget.controller, + curve: Interval(0.0, .25), + ), + ); + + // _colorAnimation + // _width, _sizeAnimation + + _buttonOpacityAnimation = + Tween(begin: 1.0, end: 0.0).animate(CurvedAnimation( + parent: widget.controller, + curve: Threshold(.65), + )); + + _ringThicknessAnimation = + Tween(begin: _loadingCircleRadius, end: _loadingCircleThickness) + .animate(CurvedAnimation( + parent: widget.controller, + curve: Interval(.65, .85), + )); + _ringOpacityAnimation = + Tween(begin: 1.0, end: 0.0).animate(CurvedAnimation( + parent: widget.controller, + curve: Interval(.85, 1.0), + )); + + widget.controller.addStatusListener(handleStatusChanged); + } + + @override + void didChangeDependencies() { + _updateColorAnimation(); + _updateWidth(); + super.didChangeDependencies(); + } + + void _updateColorAnimation() { + final theme = Theme.of(context); + final buttonTheme = theme.floatingActionButtonTheme; + + _color = widget.color ?? buttonTheme.backgroundColor; + _loadingColor = widget.loadingColor ?? theme.accentColor; + + _colorAnimation = ColorTween( + begin: _color, + end: _loadingColor, + ).animate( + CurvedAnimation( + parent: widget.controller, + curve: const Interval(0.0, .65, curve: Curves.fastOutSlowIn), + ), + ); + } + + @override + void didUpdateWidget(AnimatedButton oldWidget) { + super.didUpdateWidget(oldWidget); + + if (oldWidget.color != widget.color || + oldWidget.loadingColor != widget.loadingColor) { + _updateColorAnimation(); + } + + if (oldWidget.text != widget.text) { + _updateWidth(); + } + } + + @override + void dispose() { + super.dispose(); + widget.controller.removeStatusListener(handleStatusChanged); + } + + void handleStatusChanged(status) { + if (status == AnimationStatus.forward) { + setState(() => _isLoading = true); + } + if (status == AnimationStatus.dismissed) { + setState(() => _isLoading = false); + } + } + + /// sets width and size animation + void _updateWidth() { + final theme = Theme.of(context); + final fontSize = theme.textTheme.button.fontSize; + final renderParagraph = RenderParagraph( + TextSpan( + text: widget.text, + style: TextStyle( + fontSize: fontSize, + fontWeight: theme.textTheme.button.fontWeight, + letterSpacing: theme.textTheme.button.letterSpacing, + ), + ), + textDirection: TextDirection.ltr, + maxLines: 1, + ); + + renderParagraph.layout(BoxConstraints(minWidth: 120.0)); + + // text width based on fontSize, plus 45.0 for padding + var textWidth = + renderParagraph.getMinIntrinsicWidth(fontSize).ceilToDouble() + 45.0; + + // button width is min 120.0 and max 240.0 + _width = textWidth > 120.0 && textWidth < 240.0 + ? textWidth + : textWidth >= 240.0 ? 240.0 : 120.0; + + _sizeAnimation = Tween(begin: 1.0, end: _height / _width) + .animate(CurvedAnimation( + parent: widget.controller, + curve: Interval(0.0, .65, curve: Curves.fastOutSlowIn), + )); + } + + Widget _buildButtonText(ThemeData theme) { + return FadeTransition( + opacity: _textOpacityAnimation, + child: AnimatedText( + text: widget.text, + style: TextStyle(color: widget.textColor ?? Colors.white), + ), + ); + } + + Widget _buildButton(ThemeData theme) { + final buttonTheme = theme.floatingActionButtonTheme; + + return FadeTransition( + opacity: _buttonOpacityAnimation, + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + child: AnimatedBuilder( + animation: _colorAnimation, + builder: (context, child) => Material( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(_height/2) + ), + color: _colorAnimation.value, + child: child, + shadowColor: _color, + elevation: !_isLoading + ? (_hover ? buttonTheme.highlightElevation : buttonTheme.elevation) + : 0, + ), + child: InkWell( + onTap: !_isLoading ? widget.onPressed : null, + splashColor: buttonTheme.splashColor, + customBorder: buttonTheme.shape, + onHighlightChanged: (value) => setState(() => _hover = value), + child: SizeTransition( + sizeFactor: _sizeAnimation, + axis: Axis.horizontal, + child: Container( + width: _width, + height: _height, + alignment: Alignment.center, + child: _buildButtonText(theme), + ), + ), + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Stack( + alignment: Alignment.center, + children: [ + FadeTransition( + opacity: _ringOpacityAnimation, + child: AnimatedBuilder( + animation: _ringThicknessAnimation, + builder: (context, child) => Ring( + color: widget.loadingColor, + size: _height, + thickness: _ringThicknessAnimation.value, + ), + ), + ), + if (_isLoading) + SizedBox( + width: _height - _loadingCircleThickness, + height: _height - _loadingCircleThickness, + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(widget.loadingColor), + // backgroundColor: Colors.red, + strokeWidth: _loadingCircleThickness, + ), + ), + _buildButton(theme), + ], + ); + } +} + +class Ring extends StatelessWidget { + Ring({ + Key key, + this.color, + this.size = 40.0, + this.thickness = 2.0, + this.value = 1.0, + }) : assert(size - thickness > 0), + assert(thickness >= 0), + super(key: key); + + final Color color; + final double size; + final double thickness; + final double value; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: size - thickness, + height: size - thickness, + child: thickness == 0 + ? null + : CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(color), + strokeWidth: thickness, + value: value, + ), + ); + } +} + + +enum AnimatedTextRotation { up, down } + +/// https://medium.com/flutter-community/flutter-challenge-3d-bottom-navigation-bar-48952a5fd996 +class AnimatedText extends StatefulWidget { + AnimatedText({ + Key key, + @required this.text, + this.style, + this.textRotation = AnimatedTextRotation.up, + }) : super(key: key); + + final String text; + final TextStyle style; + final AnimatedTextRotation textRotation; + + @override + _AnimatedTextState createState() => _AnimatedTextState(); +} + +class _AnimatedTextState extends State + with SingleTickerProviderStateMixin { + var _newText = ''; + var _oldText = ''; + var _layoutHeight = 0.0; + final _textKey = GlobalKey(); + + Animation _animation; + AnimationController _controller; + + double get radius => _layoutHeight / 2; + + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 500), + ); + + _animation = Tween(begin: 0.0, end: pi / 2).animate(CurvedAnimation( + parent: _controller, + curve: Curves.easeOutBack, + )); + + _oldText = widget.text; + + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() => _layoutHeight = getWidgetSize(_textKey)?.height); + }); + } + + @override + void didUpdateWidget(AnimatedText oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.text != oldWidget.text) { + _oldText = oldWidget.text; + _newText = widget.text; + _controller.forward().then((_) { + setState(() { + final t = _oldText; + _oldText = _newText; + _newText = t; + }); + _controller.reset(); + }); + } + } + + @override + void dispose() { + super.dispose(); + _controller.dispose(); + } + + Matrix4 get _matrix { + // Fix: The text is not centered after applying perspective effect in the web build. Idk why + if (kIsWeb) { + return Matrix4.identity(); + } + return Matrix4.identity()..setEntry(3, 2, .006); + } + + Matrix4 _getFrontSideUp(double value) { + return _matrix + ..translate( + 0.0, + -radius * sin(_animation.value), + -radius * cos(_animation.value), + ) + ..rotateX(-_animation.value); // 0 -> -pi/2 + } + + Matrix4 _getBackSideUp(double value) { + return _matrix + ..translate( + 0.0, + radius * cos(_animation.value), + -radius * sin(_animation.value), + ) + ..rotateX((pi / 2) - _animation.value); // pi/2 -> 0 + } + + Matrix4 _getFrontSideDown(double value) { + return _matrix + ..translate( + 0.0, + radius * sin(_animation.value), + -radius * cos(_animation.value), + ) + ..rotateX(_animation.value); // 0 -> pi/2 + } + + Matrix4 _getBackSideDown(double value) { + return _matrix + ..translate( + 0.0, + -radius * cos(_animation.value), + -radius * sin(_animation.value), + ) + ..rotateX(_animation.value - pi / 2); // -pi/2 -> 0 + } + + @override + Widget build(BuildContext context) { + final rollUp = widget.textRotation == AnimatedTextRotation.up; + final oldText = Text( + _oldText, + key: _textKey, + style: widget.style, + overflow: TextOverflow.visible, + softWrap: false, + ); + final newText = Text( + _newText, + style: widget.style, + overflow: TextOverflow.visible, + softWrap: false, + ); + + return AnimatedBuilder( + animation: _animation, + builder: (context, child) => Stack( + alignment: Alignment.center, + children: [ + if (_animation.value <= toRadian(85)) + Transform( + alignment: Alignment.center, + transform: rollUp + ? _getFrontSideUp(_animation.value) + : _getFrontSideDown(_animation.value), + child: oldText, + ), + if (_animation.value >= toRadian(5)) + Transform( + alignment: Alignment.center, + transform: rollUp + ? _getBackSideUp(_animation.value) + : _getBackSideDown(_animation.value), + child: newText, + ), + ], + ), + ); + } + + +// Helpers + double toRadian(double degree) => degree * pi / 180; + double lerp(double start, double end, double percent) => (start + percent * (end - start)); + Size getWidgetSize(GlobalKey key) { + final RenderBox renderBox = key.currentContext?.findRenderObject(); + return renderBox?.size; + } +} \ No newline at end of file diff --git a/lib/widgets/TextFieldInertiaDirection.java b/lib/widgets/TextFieldInertiaDirection.java new file mode 100644 index 00000000..627bf406 --- /dev/null +++ b/lib/widgets/TextFieldInertiaDirection.java @@ -0,0 +1,343 @@ +import 'dart:math'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +enum TextFieldInertiaDirection { + left, + right, +} + +Interval _getInternalInterval( + double start, + double end, + double externalStart, + double externalEnd, [ + Curve curve = Curves.linear, +]) { + return Interval( + start + (end - start) * externalStart, + start + (end - start) * externalEnd, + curve: curve, + ); +} + +class AnimatedTextFormField extends StatefulWidget { + AnimatedTextFormField({ + Key key, + this.interval = const Interval(0.0, 1.0), + @required this.width, + this.loadingController, + this.inertiaController, + this.inertiaDirection, + this.enabled = true, + this.labelText, + this.prefixIcon, + this.suffixIcon, + this.keyboardType, + this.textInputAction, + this.obscureText = false, + this.controller, + this.focusNode, + this.validator, + this.onFieldSubmitted, + this.onSaved, + }) : assert((inertiaController == null && inertiaDirection == null) || + (inertiaController != null && inertiaDirection != null)), + super(key: key); + + final Interval interval; + final AnimationController loadingController; + final AnimationController inertiaController; + final double width; + final bool enabled; + final String labelText; + final Widget prefixIcon; + final Widget suffixIcon; + final TextInputType keyboardType; + final TextInputAction textInputAction; + final bool obscureText; + final TextEditingController controller; + final FocusNode focusNode; + final FormFieldValidator validator; + final ValueChanged onFieldSubmitted; + final FormFieldSetter onSaved; + final TextFieldInertiaDirection inertiaDirection; + + @override + _AnimatedTextFormFieldState createState() => _AnimatedTextFormFieldState(); +} + +class _AnimatedTextFormFieldState extends State { + Animation scaleAnimation; + Animation sizeAnimation; + Animation suffixIconOpacityAnimation; + + Animation fieldTranslateAnimation; + Animation iconRotationAnimation; + Animation iconTranslateAnimation; + + @override + void initState() { + super.initState(); + + widget.inertiaController?.addStatusListener(handleAnimationStatus); + + final interval = widget.interval; + final loadingController = widget.loadingController; + + if (loadingController != null) { + scaleAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: loadingController, + curve: _getInternalInterval( + 0, .2, interval.begin, interval.end, Curves.easeOutBack), + )); + suffixIconOpacityAnimation = + Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( + parent: loadingController, + curve: _getInternalInterval(.65, 1.0, interval.begin, interval.end), + )); + _updateSizeAnimation(); + } + + final inertiaController = widget.inertiaController; + final inertiaDirection = widget.inertiaDirection; + final sign = inertiaDirection == TextFieldInertiaDirection.right ? 1 : -1; + + if (inertiaController != null) { + fieldTranslateAnimation = Tween( + begin: 0.0, + end: sign * 15.0, + ).animate(CurvedAnimation( + parent: inertiaController, + curve: Interval(0, .5, curve: Curves.easeOut), + reverseCurve: Curves.easeIn, + )); + iconRotationAnimation = + Tween(begin: 0.0, end: sign * pi / 12 /* ~15deg */) + .animate(CurvedAnimation( + parent: inertiaController, + curve: Interval(.5, 1.0, curve: Curves.easeOut), + reverseCurve: Curves.easeIn, + )); + iconTranslateAnimation = + Tween(begin: 0.0, end: 8.0).animate(CurvedAnimation( + parent: inertiaController, + curve: Interval(.5, 1.0, curve: Curves.easeOut), + reverseCurve: Curves.easeIn, + )); + } + } + + void _updateSizeAnimation() { + final interval = widget.interval; + final loadingController = widget.loadingController; + + sizeAnimation = Tween( + begin: 48.0, + end: widget.width, + ).animate(CurvedAnimation( + parent: loadingController, + curve: _getInternalInterval( + .2, 1.0, interval.begin, interval.end, Curves.linearToEaseOut), + reverseCurve: Curves.easeInExpo, + )); + } + + @override + void didUpdateWidget(AnimatedTextFormField oldWidget) { + super.didUpdateWidget(oldWidget); + + if (oldWidget.width != widget.width) { + _updateSizeAnimation(); + } + } + + @override + dispose() { + widget.inertiaController?.removeStatusListener(handleAnimationStatus); + super.dispose(); + } + + void handleAnimationStatus(status) { + if (status == AnimationStatus.completed) { + widget.inertiaController?.reverse(); + } + } + + Widget _buildInertiaAnimation(Widget child) { + if (widget.inertiaController == null) { + return child; + } + + return AnimatedBuilder( + animation: iconTranslateAnimation, + builder: (context, child) => Transform( + alignment: Alignment.center, + transform: Matrix4.identity() + ..translate(iconTranslateAnimation.value) + ..rotateZ(iconRotationAnimation.value), + child: child, + ), + child: child, + ); + } + + InputDecoration _getInputDecoration(ThemeData theme) { + return InputDecoration( + labelText: widget.labelText, + prefixIcon: _buildInertiaAnimation(widget.prefixIcon), + suffixIcon: _buildInertiaAnimation(widget.loadingController != null + ? FadeTransition( + opacity: suffixIconOpacityAnimation, + child: widget.suffixIcon, + ) + : widget.suffixIcon), + ); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + Widget textField = TextFormField( + controller: widget.controller, + focusNode: widget.focusNode, + decoration: _getInputDecoration(theme), + keyboardType: widget.keyboardType, + textInputAction: widget.textInputAction, + obscureText: widget.obscureText, + onFieldSubmitted: widget.onFieldSubmitted, + onSaved: widget.onSaved, + validator: widget.validator, + enabled: widget.enabled, + ); + + if (widget.loadingController != null) { + textField = ScaleTransition( + scale: scaleAnimation, + child: AnimatedBuilder( + animation: sizeAnimation, + builder: (context, child) => ConstrainedBox( + constraints: BoxConstraints.tightFor(width: sizeAnimation.value), + child: child, + ), + child: textField, + ), + ); + } + + if (widget.inertiaController != null) { + textField = AnimatedBuilder( + animation: fieldTranslateAnimation, + builder: (context, child) => Transform.translate( + offset: Offset(fieldTranslateAnimation.value, 0), + child: child, + ), + child: textField, + ); + } + + return textField; + } +} + +class AnimatedPasswordTextFormField extends StatefulWidget { + AnimatedPasswordTextFormField({ + Key key, + this.interval = const Interval(0.0, 1.0), + @required this.animatedWidth, + this.loadingController, + this.inertiaController, + this.inertiaDirection, + this.enabled = true, + this.labelText, + this.keyboardType, + this.textInputAction, + this.controller, + this.focusNode, + this.validator, + this.onFieldSubmitted, + this.onSaved, + }) : assert((inertiaController == null && inertiaDirection == null) || + (inertiaController != null && inertiaDirection != null)), + super(key: key); + + final Interval interval; + final AnimationController loadingController; + final AnimationController inertiaController; + final double animatedWidth; + final bool enabled; + final String labelText; + final TextInputType keyboardType; + final TextInputAction textInputAction; + final TextEditingController controller; + final FocusNode focusNode; + final FormFieldValidator validator; + final ValueChanged onFieldSubmitted; + final FormFieldSetter onSaved; + final TextFieldInertiaDirection inertiaDirection; + + @override + _AnimatedPasswordTextFormFieldState createState() => + _AnimatedPasswordTextFormFieldState(); +} + +class _AnimatedPasswordTextFormFieldState + extends State { + var _obscureText = true; + + @override + Widget build(BuildContext context) { + return AnimatedTextFormField( + interval: widget.interval, + loadingController: widget.loadingController, + inertiaController: widget.inertiaController, + width: widget.animatedWidth, + enabled: widget.enabled, + labelText: widget.labelText, + prefixIcon: Icon(FontAwesomeIcons.lock, size: 20), + suffixIcon: GestureDetector( + onTap: () => setState(() => _obscureText = !_obscureText), + dragStartBehavior: DragStartBehavior.down, + child: AnimatedCrossFade( + duration: const Duration(milliseconds: 250), + firstCurve: Curves.easeInOutSine, + secondCurve: Curves.easeInOutSine, + alignment: Alignment.center, + layoutBuilder: (Widget topChild, _, Widget bottomChild, __) { + return Stack( + alignment: Alignment.center, + children: [bottomChild, topChild], + ); + }, + firstChild: Icon( + Icons.visibility, + size: 25.0, + semanticLabel: 'show password', + ), + secondChild: Icon( + Icons.visibility_off, + size: 25.0, + semanticLabel: 'hide password', + ), + crossFadeState: _obscureText + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + ), + ), + obscureText: _obscureText, + keyboardType: widget.keyboardType, + textInputAction: widget.textInputAction, + controller: widget.controller, + focusNode: widget.focusNode, + validator: widget.validator, + onFieldSubmitted: widget.onFieldSubmitted, + onSaved: widget.onSaved, + inertiaDirection: widget.inertiaDirection, + ); + } +} \ No newline at end of file diff --git a/lib/widgets/in_app_browser/InAppBrowser.dart b/lib/widgets/in_app_browser/InAppBrowser.dart index 603c5108..5654411c 100644 --- a/lib/widgets/in_app_browser/InAppBrowser.dart +++ b/lib/widgets/in_app_browser/InAppBrowser.dart @@ -1,13 +1,26 @@ import 'dart:convert'; +import 'package:diplomaticquarterapp/config/config.dart'; import 'package:diplomaticquarterapp/config/shared_pref_kay.dart'; import 'package:diplomaticquarterapp/models/Appointments/AppoimentAllHistoryResultList.dart'; import 'package:diplomaticquarterapp/models/Authentication/authenticated_user.dart'; import 'package:diplomaticquarterapp/services/authentication/auth_provider.dart'; import 'package:diplomaticquarterapp/uitl/app_shared_preferences.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +enum _PAYMENT_TYPE{PACKAGES, PHARMACY, PATIENT} +var _InAppBrowserOptions = InAppBrowserClassOptions( + inAppWebViewGroupOptions: InAppWebViewGroupOptions(crossPlatform: InAppWebViewOptions(useShouldOverrideUrlLoading: true)), + crossPlatform: InAppBrowserOptions(hideUrlBar: true), + ios: IOSInAppBrowserOptions(toolbarBottom: false,) +); + class MyInAppBrowser extends InAppBrowser { + _PAYMENT_TYPE paymentType; + static String SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; // Payfort Payment Gateway URL UAT @@ -20,6 +33,11 @@ class MyInAppBrowser extends InAppBrowser { // static String PREAUTH_SERVICE_URL = // 'https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx'; //Payfort PreAuth Payment Gateway URL Live Store + // Packages + static String PACKAGES_REQUEST_PAYMENT_URL = '$EXA_CART_API_BASE_URL/checkout/OpcCompleteRedirectionPayment1'; + static String PACKAGES_PAYMENT_SUCCESS_URL = '$EXA_CART_API_BASE_URL/Checkout/MobilePaymentSuccess'; + static String PACKAGES_PAYMENT_FAIL_URL = '$EXA_CART_API_BASE_URL/Checkout/MobilePaymentFailed'; + static List successURLS = [ 'success', 'PayFortResponse', @@ -30,6 +48,7 @@ class MyInAppBrowser extends InAppBrowser { final Function onExitCallback; final Function onLoadStartCallback; + final BuildContext context; AppSharedPreferences sharedPref = AppSharedPreferences(); AuthProvider authProvider = new AuthProvider(); @@ -45,7 +64,7 @@ class MyInAppBrowser extends InAppBrowser { static bool isPaymentDone = false; - MyInAppBrowser({this.onExitCallback, this.appo, this.onLoadStartCallback}); + MyInAppBrowser({this.onExitCallback, this.appo, this.onLoadStartCallback, this.context}); Future onBrowserCreated() async { print("\n\nBrowser Created!\n\n"); @@ -53,7 +72,8 @@ class MyInAppBrowser extends InAppBrowser { @override Future onLoadStart(String url) async { - onLoadStartCallback(url); + if(onLoadStartCallback != null) + onLoadStartCallback(url); } @override @@ -74,13 +94,20 @@ class MyInAppBrowser extends InAppBrowser { @override void onExit() { print("\n\nBrowser closed!\n\n"); - onExitCallback(appo, isPaymentDone); + if(onExitCallback != null) + onExitCallback(appo, isPaymentDone); } @override - Future shouldOverrideUrlLoading( - ShouldOverrideUrlLoadingRequest shouldOverrideUrlLoadingRequest) async { - print("\n\n override ${shouldOverrideUrlLoadingRequest.url}\n\n"); + Future shouldOverrideUrlLoading(ShouldOverrideUrlLoadingRequest shouldOverrideUrlLoadingRequest) async { + var url = shouldOverrideUrlLoadingRequest.url; + debugPrint("redirecting/overriding to: $url"); + + if(paymentType == _PAYMENT_TYPE.PACKAGES && [PACKAGES_PAYMENT_SUCCESS_URL,PACKAGES_PAYMENT_FAIL_URL].contains(url)){ + isPaymentDone = (url == PACKAGES_PAYMENT_SUCCESS_URL); + close(); + } + return ShouldOverrideUrlLoadingAction.ALLOW; } @@ -106,6 +133,12 @@ class MyInAppBrowser extends InAppBrowser { } } + openPackagesPaymentBrowser({@required int customer_id, @required int order_id}){ + paymentType = _PAYMENT_TYPE.PACKAGES; + var full_url = '$PACKAGES_REQUEST_PAYMENT_URL?customer_id=$customer_id&order_id=$order_id'; + this.openUrl(url: full_url, options: _InAppBrowserOptions); + } + openPaymentBrowser( double amount, String orderDesc, @@ -142,13 +175,15 @@ class MyInAppBrowser extends InAppBrowser { clinicID, doctorID) .then((value) { - this.browser.openUrl(url: value); + + paymentType = _PAYMENT_TYPE.PATIENT; + this.browser.openUrl(url: value, options: _InAppBrowserOptions); }); } openBrowser(String url) { this.browser = browser; - this.browser.openUrl(url: url); + this.browser.openUrl(url: url, options: _InAppBrowserOptions); } Future generateURL( @@ -311,4 +346,4 @@ class MyChromeSafariBrowser extends ChromeSafariBrowser { void onClosed() { print("ChromeSafari browser closed"); } -} +} \ No newline at end of file diff --git a/lib/widgets/offers_packages/PackagesCartItemCard.dart b/lib/widgets/offers_packages/PackagesCartItemCard.dart index 72ce7475..532c132d 100644 --- a/lib/widgets/offers_packages/PackagesCartItemCard.dart +++ b/lib/widgets/offers_packages/PackagesCartItemCard.dart @@ -12,7 +12,7 @@ import 'package:flutter/material.dart'; bool wide = true; class PackagesCartItemCard extends StatefulWidget { - final CartProductResponseModel itemModel; + final PackagesCartItemsResponseModel itemModel; final StepperCallbackFuture shouldStepperChangeApply ; const PackagesCartItemCard( diff --git a/lib/widgets/offers_packages/PackagesOfferCard.dart b/lib/widgets/offers_packages/PackagesOfferCard.dart index af0341c5..c0a1a22a 100644 --- a/lib/widgets/offers_packages/PackagesOfferCard.dart +++ b/lib/widgets/offers_packages/PackagesOfferCard.dart @@ -30,6 +30,7 @@ class PackagesItemCard extends StatefulWidget { } class PackagesItemCardState extends State { + imageUrl() => widget.itemModel.images.isNotEmpty ? widget.itemModel.images.first.src : "https://wallpaperaccess.com/full/30103.jpg"; @override Widget build(BuildContext context) { @@ -57,8 +58,7 @@ class PackagesItemCardState extends State { child: ClipRRect( borderRadius: BorderRadius.circular(10), child: Utils.loadNetworkImage( - url: - "https://wallpaperaccess.com/full/30103.jpg", + url: imageUrl(), )), )), Text( diff --git a/lib/widgets/others/app_scaffold_widget.dart b/lib/widgets/others/app_scaffold_widget.dart index af61052e..9c9cbcf9 100644 --- a/lib/widgets/others/app_scaffold_widget.dart +++ b/lib/widgets/others/app_scaffold_widget.dart @@ -1,3 +1,4 @@ +import 'package:badges/badges.dart'; import 'package:diplomaticquarterapp/config/config.dart'; import 'package:diplomaticquarterapp/config/size_config.dart'; import 'package:diplomaticquarterapp/config/size_config.dart'; @@ -56,6 +57,7 @@ class AppScaffold extends StatelessWidget { AuthenticatedUserObject authenticatedUserObject = locator(); + AppBarWidget appBar; AppScaffold( {@required this.body, this.appBarTitle = '', @@ -92,7 +94,7 @@ class AppScaffold extends StatelessWidget { backgroundColor: backgroundColor ?? Theme.of(context).scaffoldBackgroundColor, appBar: isShowAppBar - ? AppBarWidget( + ? appBar = AppBarWidget( appBarTitle: appBarTitle, appBarIcons: appBarIcons, showHomeAppBarIcon: showHomeAppBarIcon, @@ -104,20 +106,23 @@ class AppScaffold extends StatelessWidget { ) : null, bottomSheet: bottomSheet, - body: (!Provider.of(context, listen: false).isLogin && - isShowDecPage) - ? NotAutPage( - title: title ?? appBarTitle, - description: description, - infoList: infoList, - imagesInfo: imagesInfo, - ) - : baseViewModel != null - ? NetworkBaseView( - child: body, - baseViewModel: baseViewModel, - ) - : body, + body: SafeArea( + top: true, bottom: true, + child: (!Provider.of(context, listen: false).isLogin && + isShowDecPage) + ? NotAutPage( + title: title ?? appBarTitle, + description: description, + infoList: infoList, + imagesInfo: imagesInfo, + ) + : baseViewModel != null + ? NetworkBaseView( + child: body, + baseViewModel: baseViewModel, + ) + : body, + ), floatingActionButton: floatingActionButton, ); } @@ -127,7 +132,7 @@ class AppScaffold extends StatelessWidget { } } -class AppBarWidget extends StatelessWidget with PreferredSizeWidget { +class AppBarWidget extends StatefulWidget with PreferredSizeWidget { final AuthenticatedUserObject authenticatedUserObject = locator(); @@ -140,6 +145,8 @@ class AppBarWidget extends StatelessWidget with PreferredSizeWidget { final bool showOfferPackagesCart; final bool isShowDecPage; + Function(String) badgeUpdater; + AppBarWidget( {this.appBarTitle, this.showHomeAppBarIcon, @@ -150,23 +157,40 @@ class AppBarWidget extends StatelessWidget with PreferredSizeWidget { this.showOfferPackagesCart = false, this.isShowDecPage = true}); + @override + State createState() => AppBarWidgetState(); + + @override + Size get preferredSize => Size(double.maxFinite, 60); +} + +class AppBarWidgetState extends State{ + + String badgeText = "0"; @override Widget build(BuildContext context) { + widget.badgeUpdater = badgeUpdateBlock; return buildAppBar(context); } + badgeUpdateBlock(String value){ + setState(() { + badgeText = value; + }); + } + Widget buildAppBar(BuildContext context) { ProjectViewModel projectViewModel = Provider.of(context); return AppBar( elevation: 0, backgroundColor: - isPharmacy ? Colors.green : Theme.of(context).appBarTheme.color, + widget.isPharmacy ? Colors.green : Theme.of(context).appBarTheme.color, textTheme: TextTheme( headline6: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), ), title: Text( - authenticatedUserObject.isLogin || !isShowDecPage - ? appBarTitle.toUpperCase() + widget.authenticatedUserObject.isLogin || !widget.isShowDecPage + ? widget.appBarTitle.toUpperCase() : TranslationBase.of(context).serviceInformationTitle, style: TextStyle( fontWeight: FontWeight.bold, @@ -179,18 +203,31 @@ class AppBarWidget extends StatelessWidget with PreferredSizeWidget { ), centerTitle: true, actions: [ - (isPharmacy && showPharmacyCart) + (widget.isPharmacy && widget.showPharmacyCart) ? IconButton( - icon: Icon(Icons.shopping_cart), + icon: Badge( + badgeContent: Text( + badgeText + ), + child: Icon(Icons.shopping_cart) + ), color: Colors.white, onPressed: () { Navigator.of(context).popUntil(ModalRoute.withName('/')); }) : Container(), - (isOfferPackages && showOfferPackagesCart) + (widget.isOfferPackages && widget.showOfferPackagesCart) ? IconButton( - icon: Icon(Icons.shopping_cart), + icon: Badge( + + position: BadgePosition.topStart(top: -15,start: -10), + badgeContent: Text( + badgeText, + style: TextStyle(fontSize: 9,color: Colors.white, fontWeight: FontWeight.normal), + ), + child: Icon(Icons.shopping_cart) + ), color: Colors.white, onPressed: () { // Cart Click Event @@ -200,7 +237,7 @@ class AppBarWidget extends StatelessWidget with PreferredSizeWidget { }) : Container(), - if (showHomeAppBarIcon) + if (widget.showHomeAppBarIcon) IconButton( icon: Icon(FontAwesomeIcons.home), color: Colors.white, @@ -208,18 +245,16 @@ class AppBarWidget extends StatelessWidget with PreferredSizeWidget { Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (context) => LandingPage()), - (Route r) => false); + (Route r) => false); // Cart Click Event if(_onCartClick != null) _onCartClick(); }, ), - if (appBarIcons != null) ...appBarIcons + if (widget.appBarIcons != null) ...widget.appBarIcons ], ); } - @override - Size get preferredSize => Size(double.maxFinite, 60); } From f9b8191a5ee480c681e58eeaf576dff683d8bc3e Mon Sep 17 00:00:00 2001 From: hussam al-habibeh Date: Tue, 16 Mar 2021 15:21:21 +0200 Subject: [PATCH 6/7] jira bugs --- lib/config/config.dart | 20 +- lib/core/service/client/base_app_client.dart | 46 ++- lib/pages/insurance/insurance_page.dart | 291 +++++++++--------- .../insurance/insurance_update_screen.dart | 10 +- 4 files changed, 176 insertions(+), 191 deletions(-) diff --git a/lib/config/config.dart b/lib/config/config.dart index 0f8df420..c8ad92ef 100644 --- a/lib/config/config.dart +++ b/lib/config/config.dart @@ -95,9 +95,12 @@ const GET_NEAREST_HOSPITAL = 'Services/Patients.svc/REST/Patient_GetProjectAvgERWaitingTime'; ///ED Online -const ER_GET_VISUAL_TRIAGE_QUESTIONS = "services/Doctors.svc/REST/ER_GetVisualTriageQuestions"; -const ER_SAVE_TRIAGE_INFORMATION = "services/Doctors.svc/REST/ER_SaveTriageInformation"; -const ER_GetPatientPaymentInformationForERClinic = "services/Doctors.svc/REST/ER_GetPatientPaymentInformationForERClinic"; +const ER_GET_VISUAL_TRIAGE_QUESTIONS = + "services/Doctors.svc/REST/ER_GetVisualTriageQuestions"; +const ER_SAVE_TRIAGE_INFORMATION = + "services/Doctors.svc/REST/ER_SaveTriageInformation"; +const ER_GetPatientPaymentInformationForERClinic = + "services/Doctors.svc/REST/ER_GetPatientPaymentInformationForERClinic"; ///Er Nearest const GET_AMBULANCE_REQUEST = @@ -312,7 +315,7 @@ var DEVICE_TOKEN = ""; var IS_VOICE_COMMAND_CLOSED = false; var IS_TEXT_COMPLETED = false; var DeviceTypeID = Platform.isIOS ? 1 : 2; -const LANGUAGE_ID = 1; +const LANGUAGE_ID = 2; const GET_PHARMCY_ITEMS = "Services/Lists.svc/REST/GetPharmcyItems_Region"; const GET_PHARMACY_LIST = "Services/Patients.svc/REST/GetPharmcyList"; const GET_PAtIENTS_INSURANCE = @@ -534,12 +537,9 @@ const GET_SPECIFICATION = "productspecification/"; const GET_BRAND_ITEMS = "products"; // External API -const ADD_ADDRESS_INFO = - "addcustomeraddress"; -const GET_CUSTOMER_ADDRESSES = - "Customers/"; -const GET_CUSTOMER_INFO = - "VerifyCustomer"; +const ADD_ADDRESS_INFO = "addcustomeraddress"; +const GET_CUSTOMER_ADDRESSES = "Customers/"; +const GET_CUSTOMER_INFO = "VerifyCustomer"; //Pharmacy diff --git a/lib/core/service/client/base_app_client.dart b/lib/core/service/client/base_app_client.dart index 1d5b55aa..a647c1c0 100644 --- a/lib/core/service/client/base_app_client.dart +++ b/lib/core/service/client/base_app_client.dart @@ -216,11 +216,10 @@ class BaseAppClient { postPharmacy(String endPoint, {Map body, - Function(dynamic response, int statusCode) onSuccess, - Function(String error, int statusCode) onFailure, - bool isAllowAny = false, - bool isExternal = false}) async { - + Function(dynamic response, int statusCode) onSuccess, + Function(String error, int statusCode) onFailure, + bool isAllowAny = false, + bool isExternal = false}) async { var token = await sharedPref.getString(PHARMACY_AUTORZIE_TOKEN); var user = await sharedPref.getObject(USER_PROFILE); String url; @@ -246,12 +245,12 @@ class BaseAppClient { if (!isExternal) { String token = await sharedPref.getString(TOKEN); var languageID = - await sharedPref.getStringWithDefaultValue(APP_LANGUAGE, 'ar'); + await sharedPref.getStringWithDefaultValue(APP_LANGUAGE, 'ar'); if (body.containsKey('SetupID')) { body['SetupID'] = body.containsKey('SetupID') ? body['SetupID'] != null - ? body['SetupID'] - : SETUP_ID + ? body['SetupID'] + : SETUP_ID : SETUP_ID; } @@ -263,17 +262,17 @@ class BaseAppClient { body['generalid'] = GENERAL_ID; body['PatientOutSA'] = body.containsKey('PatientOutSA') ? body['PatientOutSA'] != null - ? body['PatientOutSA'] - : PATIENT_OUT_SA + ? body['PatientOutSA'] + : PATIENT_OUT_SA : PATIENT_OUT_SA; if (body.containsKey('isDentalAllowedBackend')) { body['isDentalAllowedBackend'] = - body.containsKey('isDentalAllowedBackend') - ? body['isDentalAllowedBackend'] != null - ? body['isDentalAllowedBackend'] - : IS_DENTAL_ALLOWED_BACKEND - : IS_DENTAL_ALLOWED_BACKEND; + body.containsKey('isDentalAllowedBackend') + ? body['isDentalAllowedBackend'] != null + ? body['isDentalAllowedBackend'] + : IS_DENTAL_ALLOWED_BACKEND + : IS_DENTAL_ALLOWED_BACKEND; } body['DeviceTypeID'] = DeviceTypeID; @@ -281,18 +280,18 @@ class BaseAppClient { if (!body.containsKey('IsPublicRequest')) { body['PatientType'] = body.containsKey('PatientType') ? body['PatientType'] != null - ? body['PatientType'] - : user['PatientType'] != null - ? user['PatientType'] - : PATIENT_TYPE + ? body['PatientType'] + : user['PatientType'] != null + ? user['PatientType'] + : PATIENT_TYPE : PATIENT_TYPE; body['PatientTypeID'] = body.containsKey('PatientTypeID') ? body['PatientTypeID'] != null - ? body['PatientTypeID'] - : user['PatientType'] != null - ? user['PatientType'] - : PATIENT_TYPE_ID + ? body['PatientTypeID'] + : user['PatientType'] != null + ? user['PatientType'] + : PATIENT_TYPE_ID : PATIENT_TYPE_ID; if (user != null) { body['TokenID'] = token; @@ -587,7 +586,6 @@ class BaseAppClient { return params; } - pharmacyPost(String endPoint, {Map body, Function(dynamic response, int statusCode) onSuccess, diff --git a/lib/pages/insurance/insurance_page.dart b/lib/pages/insurance/insurance_page.dart index f938d9e5..a391f554 100644 --- a/lib/pages/insurance/insurance_page.dart +++ b/lib/pages/insurance/insurance_page.dart @@ -16,7 +16,7 @@ class InsurancePage extends StatelessWidget { final InsuranceViewModel model; InsuranceCardService _insuranceCardService = locator(); - InsurancePage({Key key, this.model}) : super(key: key); + InsurancePage({Key key, this.model}) : super(key: key); @override Widget build(BuildContext context) { return SingleChildScrollView( @@ -31,24 +31,20 @@ class InsurancePage extends StatelessWidget { width: MediaQuery.of(context).size.width, padding: EdgeInsets.all(10.0), child: Row( - crossAxisAlignment: - CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.max, children: [ if (model.user != null) Expanded( flex: 3, child: Container( - margin: EdgeInsets.only( - top: 2.0, - left: 10.0, - right: 20.0), + margin: + EdgeInsets.only(top: 2.0, left: 10.0, right: 20.0), child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Texts( - model.user.firstName ?? '' + " " + model.user.lastName ?? '', + model.user.firstName + " " + model.user.lastName, fontSize: 14, color: Colors.black, fontWeight: FontWeight.w500, @@ -57,11 +53,9 @@ class InsurancePage extends StatelessWidget { height: 8, ), Texts( - TranslationBase.of(context) - .fileno + + TranslationBase.of(context).fileno + ": " + - model.user.patientID - .toString(), + model.user.patientID.toString(), fontSize: 14, color: Colors.black, fontWeight: FontWeight.w500, @@ -78,9 +72,7 @@ class InsurancePage extends StatelessWidget { children: [ Container( child: SecondaryButton( - label: TranslationBase.of( - context) - .fetchData, + label: TranslationBase.of(context).fetchData, small: true, textColor: Colors.white, onTap: () { @@ -88,15 +80,12 @@ class InsurancePage extends StatelessWidget { setupID: '010266', projectID: 15, patientIdentificationID: - model.user - .patientIdentificationNo, - patientID: model - .user.patientID, - name: model.user - .firstName + + model.user.patientIdentificationNo, + patientID: model.user.patientID, + name: model.user.firstName + " " + - model - .user.lastName,context: context); + model.user.lastName, + context: context); }, ), ), @@ -107,122 +96,115 @@ class InsurancePage extends StatelessWidget { ], ), ), - if(model.getAllSharedRecordsByStatusResponse.getAllSharedRecordsByStatusList != null ?? false) - ...List.generate(model.getAllSharedRecordsByStatusResponse.getAllSharedRecordsByStatusList.length, (index) => - model.getAllSharedRecordsByStatusResponse.getAllSharedRecordsByStatusList[index].status == 3 - ? Container( - margin: EdgeInsets.all(10.0), - child: Card( - margin: EdgeInsets.fromLTRB( - 8.0, 16.0, 8.0, 8.0), - color: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(10), - ), - child: Container( - width: MediaQuery.of(context).size.width, - padding: EdgeInsets.all(10.0), - child: Row( - crossAxisAlignment: - CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - flex: 3, - child: Container( - margin: EdgeInsets.only( - top: 2.0, - left: 10.0, - right: 20.0), - child: Column( - crossAxisAlignment: - CrossAxisAlignment - .start, - children: [ - Texts( - model - .getAllSharedRecordsByStatusResponse - .getAllSharedRecordsByStatusList[ - index] - .patientName, - fontSize: 14, - color: Colors.black, - fontWeight: - FontWeight.w500, - ), - SizedBox( - height: 8, - ), - Texts( - TranslationBase.of( - context) - .fileno + - ": " + - model - .getAllSharedRecordsByStatusResponse - .getAllSharedRecordsByStatusList[ - index] - .patientID - .toString(), - fontSize: 14, - color: Colors.black, - fontWeight: - FontWeight.w500, - ) - ], - ), - ), - ), - Expanded( - flex: 2, - child: Container( - margin: - EdgeInsets.only(top: 2.0), - child: Column( - children: [ - Container( - child: SecondaryButton( - label: TranslationBase - .of(context) - .fetchData, - small: true, - textColor: - Colors.white, - onTap: () { - getDetails( - projectID: 15, - patientIdentificationID: model - .getAllSharedRecordsByStatusResponse - .getAllSharedRecordsByStatusList[ - index] - .patientIdenficationNumber, - setupID: - '010266', - patientID: model - .getAllSharedRecordsByStatusResponse - .getAllSharedRecordsByStatusList[ - index] - .responseID, - name: model - .getAllSharedRecordsByStatusResponse - .getAllSharedRecordsByStatusList[ - index].patientName,context: context); - }, - ), + if (model.getAllSharedRecordsByStatusResponse + .getAllSharedRecordsByStatusList != + null ?? + false) + ...List.generate( + model.getAllSharedRecordsByStatusResponse + .getAllSharedRecordsByStatusList.length, + (index) => model.getAllSharedRecordsByStatusResponse + .getAllSharedRecordsByStatusList[index].status == + 3 + ? Container( + margin: EdgeInsets.all(10.0), + child: Card( + margin: EdgeInsets.fromLTRB(8.0, 16.0, 8.0, 8.0), + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + child: Container( + width: MediaQuery.of(context).size.width, + padding: EdgeInsets.all(10.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + flex: 3, + child: Container( + margin: EdgeInsets.only( + top: 2.0, left: 10.0, right: 20.0), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Texts( + model + .getAllSharedRecordsByStatusResponse + .getAllSharedRecordsByStatusList[ + index] + .patientName, + fontSize: 14, + color: Colors.black, + fontWeight: FontWeight.w500, + ), + SizedBox( + height: 8, + ), + Texts( + TranslationBase.of(context).fileno + + ": " + + model + .getAllSharedRecordsByStatusResponse + .getAllSharedRecordsByStatusList[ + index] + .patientID + .toString(), + fontSize: 14, + color: Colors.black, + fontWeight: FontWeight.w500, + ) + ], + ), + ), + ), + Expanded( + flex: 2, + child: Container( + margin: EdgeInsets.only(top: 2.0), + child: Column( + children: [ + Container( + child: SecondaryButton( + label: TranslationBase.of(context) + .fetchData, + small: true, + textColor: Colors.white, + onTap: () { + getDetails( + projectID: 15, + patientIdentificationID: model + .getAllSharedRecordsByStatusResponse + .getAllSharedRecordsByStatusList[ + index] + .patientIdenficationNumber, + setupID: '010266', + patientID: model + .getAllSharedRecordsByStatusResponse + .getAllSharedRecordsByStatusList[ + index] + .responseID, + name: model + .getAllSharedRecordsByStatusResponse + .getAllSharedRecordsByStatusList[ + index] + .patientName, + context: context); + }, + ), + ), + ], + ), + ), + ) + ], ), - ], + ), ), - ), - ) - ], - ), - ), - ), - ) - : Container() - ), - + ) + : Container()), ], ), ); @@ -230,29 +212,32 @@ class InsurancePage extends StatelessWidget { getDetails( {String setupID, - int projectID, - String patientIdentificationID, - int patientID, - String name,BuildContext context}) { + int projectID, + String patientIdentificationID, + int patientID, + String name, + BuildContext context}) { GifLoaderDialogUtils.showMyDialog(context); _insuranceCardService .getPatientInsuranceDetails( - setupID: setupID, - projectID: projectID, - patientID: patientID, - patientIdentificationID: patientIdentificationID) + setupID: setupID, + projectID: projectID, + patientID: patientID, + patientIdentificationID: patientIdentificationID) .then((value) { GifLoaderDialogUtils.hideDialog(context); - if (!_insuranceCardService.hasError && _insuranceCardService.isHaveInsuranceCard) { + if (!_insuranceCardService.hasError && + _insuranceCardService.isHaveInsuranceCard) { Navigator.push( context, FadePage( page: InsuranceCardUpdateDetails( - insuranceCardDetailsModel: _insuranceCardService.insuranceCardDetailsList, - patientID: patientID, - patientIdentificationID: patientIdentificationID, - name: name, - ))); + insuranceCardDetailsModel: + _insuranceCardService.insuranceCardDetailsList, + patientID: patientID, + patientIdentificationID: patientIdentificationID, + name: name, + ))); } else { AppToast.showErrorToast(message: _insuranceCardService.error); } diff --git a/lib/pages/insurance/insurance_update_screen.dart b/lib/pages/insurance/insurance_update_screen.dart index 36b424a8..6c3ee61c 100644 --- a/lib/pages/insurance/insurance_update_screen.dart +++ b/lib/pages/insurance/insurance_update_screen.dart @@ -32,7 +32,11 @@ class _InsuranceUpdateState extends State super.initState(); _tabController = TabController(length: 2, vsync: this); - imagesInfo.add(ImagesInfo(imageEn: 'https://hmgwebservices.com/Images/MobileApp/imges-info/insurance-card/en/0.png',imageAr: 'https://hmgwebservices.com/Images/MobileApp/imges-info/insurance-card/ar/0.png')); + imagesInfo.add(ImagesInfo( + imageEn: + 'https://hmgwebservices.com/Images/MobileApp/imges-info/insurance-card/en/0.png', + imageAr: + 'https://hmgwebservices.com/Images/MobileApp/imges-info/insurance-card/ar/0.png')); } void dispose() { @@ -111,7 +115,7 @@ class _InsuranceUpdateState extends State physics: BouncingScrollPhysics(), controller: _tabController, children: [ - InsurancePage(model:model), + InsurancePage(model: model), Container( child: ListView.builder( itemCount: model.insuranceUpdate == null @@ -227,6 +231,4 @@ class _InsuranceUpdateState extends State ), ); } - - } From a6ec7326b23698d67abb6f634865181787882c95 Mon Sep 17 00:00:00 2001 From: hussam al-habibeh Date: Tue, 16 Mar 2021 15:37:10 +0200 Subject: [PATCH 7/7] jira bugs --- lib/pages/vaccine/my_vaccines_screen.dart | 37 ++++++++++++----------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/pages/vaccine/my_vaccines_screen.dart b/lib/pages/vaccine/my_vaccines_screen.dart index 169e5cd5..fb723a1f 100644 --- a/lib/pages/vaccine/my_vaccines_screen.dart +++ b/lib/pages/vaccine/my_vaccines_screen.dart @@ -169,29 +169,30 @@ class _MyVaccinesState extends State { width: double.infinity, // height: 80.0, child: Button( + disabled: true, label: TranslationBase.of(context).checkVaccineAvailability, backgroundColor: Color(0xff9EA3A4), - onTap: () => - Navigator.push(context, FadePage(page: MyVaccinesItemPage())), + onTap: () => Navigator.push( + context, FadePage(page: MyVaccinesItemPage())), ), ), - if(projectViewModel.havePrivilege(27)) - Container( - width: double.infinity, - // height: 80.0, - child: SecondaryButton( - label: TranslationBase.of(context).sendEmail, - color: Color(0xffF62426), - textColor: Colors.white, - disabled: model.vaccineList.length == 0, - loading: model.state == ViewState.BusyLocal, - onTap: () async { - model.sendEmail( - message: - TranslationBase.of(context).emailSentSuccessfully); - }, + if (projectViewModel.havePrivilege(27)) + Container( + width: double.infinity, + // height: 80.0, + child: SecondaryButton( + label: TranslationBase.of(context).sendEmail, + color: Color(0xffF62426), + textColor: Colors.white, + disabled: model.vaccineList.length == 0, + loading: model.state == ViewState.BusyLocal, + onTap: () async { + model.sendEmail( + message: TranslationBase.of(context) + .emailSentSuccessfully); + }, + ), ), - ), ], ), ),