Commit 33ec1dde authored by DatHV's avatar DatHV
Browse files

update game, notify

parent 8bef97c9
class OrderItemsProductResponse {
final String? itemIds;
final String? itemExpireTime;
final String? productType;
OrderItemsProductResponse({
this.itemIds,
this.itemExpireTime,
this.productType,
});
factory OrderItemsProductResponse.fromJson(Map<String, dynamic> json) {
return OrderItemsProductResponse(
itemIds: json['item_ids'] as String?,
itemExpireTime: json['item_expire_time'] as String?,
productType: json['product_type'] as String?,
);
}
Map<String, dynamic> toJson() => {
'item_ids': itemIds,
'item_expire_time': itemExpireTime,
'product_type': productType,
};
}
import 'package:json_annotation/json_annotation.dart';
import '../../voucher/models/product_type.dart';
import 'order_items_product_payment_response_model.dart';
part 'order_product_payment_response_model.g.dart';
@JsonSerializable()
class OrderProductPaymentResponseModel {
final String? id;
@JsonKey(name: 'payment_method')
final String? paymentMethod;
@JsonKey(name: "payment_type")
final String? paymentType;
@JsonKey(name: "payment_partner")
final String? paymentPartner;
@JsonKey(name: "payment_url")
final String? paymentUrl;
@JsonKey(name: "request_id")
final String? requestId;
final List<OrderItemsProductResponse>? items;
final int? subtotal;
@JsonKey(name: "payment_transaction_id")
final String? paymentTransactionId;
@JsonKey(name: "created_at")
final String? createdAt;
OrderProductPaymentResponseModel({
this.id,
this.paymentMethod,
this.paymentType,
this.paymentPartner,
this.paymentUrl,
this.requestId,
this.items,
this.subtotal,
this.paymentTransactionId,
this.createdAt,
});
factory OrderProductPaymentResponseModel.fromJson(Map<String, dynamic> json) =>
_$OrderProductPaymentResponseModelFromJson(json);
Map<String, dynamic> toJson() => _$OrderProductPaymentResponseModelToJson(this);
/// Custom Getter: redeemId
String? get redeemId {
final firstItemIds = items?.firstOrNull?.itemIds;
return firstItemIds?.split(',').first;
}
/// Custom Getter: expireTime
String? get expireTime {
return items?.firstOrNull?.itemExpireTime;
}
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'order_product_payment_response_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
OrderProductPaymentResponseModel _$OrderProductPaymentResponseModelFromJson(
Map<String, dynamic> json,
) => OrderProductPaymentResponseModel(
id: json['id'] as String?,
paymentMethod: json['payment_method'] as String?,
paymentType: json['payment_type'] as String?,
paymentPartner: json['payment_partner'] as String?,
paymentUrl: json['payment_url'] as String?,
requestId: json['request_id'] as String?,
items:
(json['items'] as List<dynamic>?)
?.map(
(e) =>
OrderItemsProductResponse.fromJson(e as Map<String, dynamic>),
)
.toList(),
subtotal: (json['subtotal'] as num?)?.toInt(),
paymentTransactionId: json['payment_transaction_id'] as String?,
createdAt: json['created_at'] as String?,
);
Map<String, dynamic> _$OrderProductPaymentResponseModelToJson(
OrderProductPaymentResponseModel instance,
) => <String, dynamic>{
'id': instance.id,
'payment_method': instance.paymentMethod,
'payment_type': instance.paymentType,
'payment_partner': instance.paymentPartner,
'payment_url': instance.paymentUrl,
'request_id': instance.requestId,
'items': instance.items,
'subtotal': instance.subtotal,
'payment_transaction_id': instance.paymentTransactionId,
'created_at': instance.createdAt,
};
...@@ -8,9 +8,10 @@ class PaymentMethodModel { ...@@ -8,9 +8,10 @@ class PaymentMethodModel {
final String? code; final String? code;
final String? name; final String? name;
final String? logo; final String? logo;
@JsonKey(name: 'save_token')
final bool? saveToken; final bool? saveToken;
final bool? isSelected; final bool? isSelected;
final bool? needSaveTokenWhenOrder; bool? needSaveTokenWhenOrder;
PaymentMethodType? get type { PaymentMethodType? get type {
if (code == null) return null; if (code == null) return null;
......
...@@ -12,7 +12,7 @@ PaymentMethodModel _$PaymentMethodModelFromJson(Map<String, dynamic> json) => ...@@ -12,7 +12,7 @@ PaymentMethodModel _$PaymentMethodModelFromJson(Map<String, dynamic> json) =>
code: json['code'] as String?, code: json['code'] as String?,
name: json['name'] as String?, name: json['name'] as String?,
logo: json['logo'] as String?, logo: json['logo'] as String?,
saveToken: json['saveToken'] as bool?, saveToken: json['save_token'] as bool?,
isSelected: json['isSelected'] as bool?, isSelected: json['isSelected'] as bool?,
needSaveTokenWhenOrder: json['needSaveTokenWhenOrder'] as bool?, needSaveTokenWhenOrder: json['needSaveTokenWhenOrder'] as bool?,
); );
...@@ -23,7 +23,7 @@ Map<String, dynamic> _$PaymentMethodModelToJson(PaymentMethodModel instance) => ...@@ -23,7 +23,7 @@ Map<String, dynamic> _$PaymentMethodModelToJson(PaymentMethodModel instance) =>
'code': instance.code, 'code': instance.code,
'name': instance.name, 'name': instance.name,
'logo': instance.logo, 'logo': instance.logo,
'saveToken': instance.saveToken, 'save_token': instance.saveToken,
'isSelected': instance.isSelected, 'isSelected': instance.isSelected,
'needSaveTokenWhenOrder': instance.needSaveTokenWhenOrder, 'needSaveTokenWhenOrder': instance.needSaveTokenWhenOrder,
}; };
...@@ -19,6 +19,19 @@ enum PaymentMethodType { ...@@ -19,6 +19,19 @@ enum PaymentMethodType {
} }
} }
String get rawValue {
switch (this) {
case PaymentMethodType.card:
return 'CARD';
case PaymentMethodType.wallet:
return 'WALLET';
case PaymentMethodType.transfer:
return 'VA_QRCODE';
case PaymentMethodType.internalCard:
return 'INTERNATIONAL_CARD';
}
}
String get methodBillEVN { String get methodBillEVN {
switch (this) { switch (this) {
case PaymentMethodType.card: case PaymentMethodType.card:
......
...@@ -14,6 +14,8 @@ class PreviewOrderPaymentPointDataModel { ...@@ -14,6 +14,8 @@ class PreviewOrderPaymentPointDataModel {
this.textDisplay, this.textDisplay,
}); });
bool get isEnableUsePoint => status == 1;
factory PreviewOrderPaymentPointDataModel.fromJson(Map<String, dynamic> json) => factory PreviewOrderPaymentPointDataModel.fromJson(Map<String, dynamic> json) =>
_$PreviewOrderPaymentPointDataModelFromJson(json); _$PreviewOrderPaymentPointDataModelFromJson(json);
Map<String, dynamic> toJson() => _$PreviewOrderPaymentPointDataModelToJson(this); Map<String, dynamic> toJson() => _$PreviewOrderPaymentPointDataModelToJson(this);
......
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_rx/src/rx_types/rx_types.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart'; import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import 'package:mypoint_flutter_app/shared/router_gage.dart';
import 'package:uuid/uuid.dart';
import '../../base/restful_api_viewmodel.dart'; import '../../base/restful_api_viewmodel.dart';
import '../../configs/constants.dart';
import '../../preference/data_preference.dart'; import '../../preference/data_preference.dart';
import '../voucher/models/product_model.dart';
import '../webview/payment_web_view_screen.dart';
import '../webview/web_view_screen.dart';
import 'model/payment_bank_account_info_model.dart'; import 'model/payment_bank_account_info_model.dart';
import 'model/payment_method_model.dart'; import 'model/payment_method_model.dart';
import 'model/preview_order_payment_model.dart'; import 'model/preview_order_payment_model.dart';
class TransactionDetailViewModel extends RestfulApiViewModel { class TransactionDetailViewModel extends RestfulApiViewModel {
final int definedCodeIndexBankAccount = 1000;
var previewData = Rxn<PreviewOrderPaymentModel>(); var previewData = Rxn<PreviewOrderPaymentModel>();
var paymentMethods = RxList<PaymentMethodModel>(); var paymentMethods = RxList<PaymentMethodModel>();
var paymentBankAccounts = RxList<PaymentBankAccountInfoModel>(); var paymentBankAccounts = RxList<PaymentBankAccountInfoModel>();
final RxBool isLoading = false.obs; final RxBool isLoading = false.obs;
final ProductModel product;
final int quantity;
final RxBool usePoints = true.obs;
var selectedPaymentMethodIndex = -1.obs;
void Function(String message)? onShowAlertError;
int get finalTotal {
final totalPrice = previewData.value?.totalPrice ?? 0;
final pointValue = previewData.value?.pointData?.point ?? 0;
final finalTotal = usePoints.value && previewData.value?.pointData?.status == 1
? totalPrice - pointValue
: totalPrice;
return finalTotal;
}
TransactionDetailViewModel({required this.product, required this.quantity});
@override @override
void onInit() { void onInit() {
...@@ -20,23 +45,80 @@ class TransactionDetailViewModel extends RestfulApiViewModel { ...@@ -20,23 +45,80 @@ class TransactionDetailViewModel extends RestfulApiViewModel {
Future<void> refreshData() async { Future<void> refreshData() async {
isLoading.value = true; isLoading.value = true;
await Future.wait([ await Future.wait([_getPreviewOrderPayment(), _getPaymentMethods(), _getPaymentBankAccounts()]);
_getPreviewOrderPayment(),
_getPaymentMethods(),
_getPaymentBankAccounts(),
]);
isLoading.value = false; isLoading.value = false;
} }
requestPaymentProduct() {
showLoading();
final requestId = Uuid().v4();
int? point = usePoints.value ? previewData.value?.pointData?.point : 0;
PaymentBankAccountInfoModel? selectedBankAccount;
PaymentMethodModel? selectedPaymentMethod;
bool? saveToken;
if (finalTotal == 0) {
point = previewData.value?.pointData?.point ?? 0;
} else if (selectedPaymentMethodIndex >= definedCodeIndexBankAccount) {
selectedBankAccount = paymentBankAccounts.value[selectedPaymentMethodIndex - definedCodeIndexBankAccount];
} else if (selectedPaymentMethodIndex >= 0) {
selectedPaymentMethod = paymentMethods.value[selectedPaymentMethodIndex];
saveToken = selectedPaymentMethod?.saveToken == true && selectedPaymentMethod?.needSaveTokenWhenOrder == true;
}
client
.orderSubmitPayment(
products: [product],
quantity: quantity,
requestId: requestId,
point: point,
cash: finalTotal,
method: selectedPaymentMethod,
paymentTokenId: selectedBankAccount?.id,
saveToken: saveToken,
metadata: "",
)
.then((value) {
hideLoading();
if (value.isSuccess) {
final data = value.data;
if ((data?.paymentUrl ?? "").isNotEmpty) {
Get.toNamed(
paymentWebViewScreen,
arguments: PaymentWebViewInput(
url: data?.paymentUrl ?? "",
isContract: false,
orderId: data?.id ?? "",
showAlertBack: true,
callback: (result) {},
)
);
} else if ((data?.redeemId ?? "").isNotEmpty && (data?.id ?? "").isNotEmpty) {
Get.offNamed(transactionHistoryDetailScreen,
arguments: {
"orderId": data?.id ?? "",
"canBack": true,
}
);
} else {
onShowAlertError?.call(value.errorMessage ?? Constants.commonError);
}
} else {
onShowAlertError?.call(value.errorMessage ?? Constants.commonError);
}
});
}
Future<void> _getPreviewOrderPayment() async { Future<void> _getPreviewOrderPayment() async {
String? token = DataPreference.instance.token ?? ""; String? token = DataPreference.instance.token ?? "";
try { try {
final body = { final body = {
"product_id": 13796, "product_id": product.id,
"quantity": 1, "quantity": quantity,
"price": product.amountToBePaid ?? 0,
"access_token": token, "access_token": token,
"price": 100000,
}; };
if (product.previewFlashSale?.isFlashSalePrice == true && product.previewFlashSale?.id != null) {
body["flash_sale_id"] = product.previewFlashSale!.id;
}
final response = await client.getPreviewOrderInfo(body); final response = await client.getPreviewOrderInfo(body);
previewData.value = response.data; previewData.value = response.data;
} catch (error) { } catch (error) {
......
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:mypoint_flutter_app/extensions/num_extension.dart';
import 'package:mypoint_flutter_app/screen/voucher/detail/store_list_section.dart'; import 'package:mypoint_flutter_app/screen/voucher/detail/store_list_section.dart';
import 'package:mypoint_flutter_app/screen/voucher/models/product_type.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../../../base/base_screen.dart'; import '../../../base/base_screen.dart';
import '../../../base/basic_state.dart'; import '../../../base/basic_state.dart';
import '../../../preference/point/point_manager.dart';
import '../../../resouce/base_color.dart'; import '../../../resouce/base_color.dart';
import '../../../shared/router_gage.dart'; import '../../../shared/router_gage.dart';
import '../../../widgets/alert/data_alert_model.dart';
import '../../../widgets/back_button.dart'; import '../../../widgets/back_button.dart';
import '../../../widgets/custom_empty_widget.dart'; import '../../../widgets/custom_empty_widget.dart';
import '../../../widgets/custom_point_text_tag.dart'; import '../../../widgets/custom_point_text_tag.dart';
...@@ -181,12 +185,8 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi ...@@ -181,12 +185,8 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded(child: Text(product.brand?.name ?? '', style: const TextStyle(fontSize: 14))), Expanded(child: Text(product.brand?.name ?? '', style: const TextStyle(fontSize: 14))),
// PriceTagWidget(point: product.amountToBePaid ?? 0), // PriceTagWidget(point: product.amountToBePaid ?? 0),
CustomPointText( CustomPointText(point: product.amountToBePaid ?? 0, type: product.price?.method),
point: product.amountToBePaid ?? 0,
type: product.price?.method,
),
], ],
), ),
], ],
), ),
...@@ -203,8 +203,10 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi ...@@ -203,8 +203,10 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
if (hasExpire) if (hasExpire)
Text('Hạn dùng: ', style: const TextStyle(color: Colors.grey, fontWeight: FontWeight.bold, fontSize: 12)), Text('Hạn dùng: ', style: const TextStyle(color: Colors.grey, fontWeight: FontWeight.bold, fontSize: 12)),
if (hasExpire) if (hasExpire)
Text(product.expired ? "Hết hạn" : product.expire, Text(
style: const TextStyle(color: BaseColor.primary500, fontWeight: FontWeight.bold, fontSize: 12)), product.expired ? "Hết hạn" : product.expire,
style: const TextStyle(color: BaseColor.primary500, fontWeight: FontWeight.bold, fontSize: 12),
),
if (isOutOfStock) if (isOutOfStock)
Container( Container(
margin: const EdgeInsets.only(left: 8), margin: const EdgeInsets.only(left: 8),
...@@ -219,14 +221,6 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi ...@@ -219,14 +221,6 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
); );
} }
Widget _buildDetailBlock(ProductModel product) {
return _buildTextBlock("Chi tiết ưu đãi:", product.content?.detail);
}
Widget _buildConditionBlock(ProductModel product) {
return _buildTextBlock("Điều kiện áp dụng:", product.content?.termAndCondition);
}
Widget _buildTextBlock(String title, String? content) { Widget _buildTextBlock(String title, String? content) {
if (content == null || content.isEmpty) return const SizedBox(); if (content == null || content.isEmpty) return const SizedBox();
return Container( return Container(
...@@ -379,8 +373,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi ...@@ -379,8 +373,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
height: 48, height: 48,
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
Get.toNamed(registerFormInputScreen, arguments: {"id": 13484}); _handleContinueButtonAction();
// TODO: Handle đổi ưu đãi
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: BaseColor.primary500, backgroundColor: BaseColor.primary500,
...@@ -399,6 +392,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi ...@@ -399,6 +392,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
return _buildBottomActionContainer( return _buildBottomActionContainer(
child: Row( child: Row(
children: [ children: [
if (_viewModel.product.value?.productType == ProductType.voucher)
Row( Row(
children: [ children: [
Container( Container(
...@@ -440,7 +434,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi ...@@ -440,7 +434,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
height: 48, height: 48,
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
_viewModel.verifyOrderProduct(); _handleContinueButtonAction();
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: BaseColor.primary500, backgroundColor: BaseColor.primary500,
...@@ -458,6 +452,50 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi ...@@ -458,6 +452,50 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
); );
} }
_handleContinueButtonAction() {
final product = _viewModel.product.value;
if (product?.requireFormRegis == true) {
Get.toNamed(registerFormInputScreen, arguments: {"product": product});
return;
}
_viewModel.verifyOrderProduct(() {
if (product?.price?.method == CashType.point) {
_handleRedeemProduct();
} else {
Get.toNamed(transactionDetailScreen, arguments: {"product": product, "quantity": _viewModel.quantity.value});
}
});
}
_handleRedeemProduct() {
final amountToBePaid = _viewModel.product.value?.amountToBePaid ?? 0;
if (UserPointManager().point < amountToBePaid) {
showAlertError(content: "Bạn không đủ điểm để đổi ưu đãi này");
return;
}
final dataAlert = DataAlertModel(
title: "Xác nhận",
description: "Bạn có muốn sử dụng <b style=\"color:#E71D28\"> ${amountToBePaid.money(CurrencyUnit.point)}</b> MyPoint để đổi Ưu Đãi này không?",
localHeaderImage: "assets/images/ic_pipi_02.png",
buttons: [AlertButton(
text: "Đồng ý",
onPressed: () {
Get.back();
_viewModel.redeemProduct();
},
bgColor: BaseColor.primary500,
textColor: Colors.white,
),
AlertButton(
text: "Huỷ",
onPressed: () => Get.back(),
bgColor: Colors.white,
textColor: BaseColor.second500,
),],
);
showAlert(data: dataAlert);
}
Widget _buildFavoriteButton() { Widget _buildFavoriteButton() {
return Obx(() { return Obx(() {
final isFavorite = _viewModel.liked.value; final isFavorite = _viewModel.liked.value;
......
import 'dart:ui';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart'; import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import 'package:uuid/uuid.dart';
import '../../../base/restful_api_viewmodel.dart'; import '../../../base/restful_api_viewmodel.dart';
import '../../../configs/constants.dart'; import '../../../configs/constants.dart';
import '../../../shared/router_gage.dart'; import '../../../shared/router_gage.dart';
...@@ -38,7 +41,6 @@ class VoucherDetailViewModel extends RestfulApiViewModel { ...@@ -38,7 +41,6 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
} }
} catch (error) { } catch (error) {
onShowAlertError?.call("Error toggling favorite: $error"); onShowAlertError?.call("Error toggling favorite: $error");
print("Error toggling favorite: $error");
} }
} }
...@@ -51,7 +53,6 @@ class VoucherDetailViewModel extends RestfulApiViewModel { ...@@ -51,7 +53,6 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
liked.value = product.value?.liked == true; liked.value = product.value?.liked == true;
} catch (error) { } catch (error) {
onShowAlertError?.call("Error fetching product detail: $error"); onShowAlertError?.call("Error fetching product detail: $error");
print("Error fetching product detail: $error");
} finally { } finally {
isLoading.value = false; isLoading.value = false;
} }
...@@ -67,13 +68,9 @@ class VoucherDetailViewModel extends RestfulApiViewModel { ...@@ -67,13 +68,9 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
} finally {} } finally {}
} }
verifyOrderProduct() async { verifyOrderProduct(Function verified) async {
final value = product.value; final value = product.value;
var body = { var body = {"product_id": productId, "price": value?.amountToBePaid, "quantity": quantity.value};
"product_id": productId,
"price": value?.amountToBePaid,
"quantity": quantity.value,
};
if (value?.previewFlashSale?.isFlashSalePrice == true) { if (value?.previewFlashSale?.isFlashSalePrice == true) {
final flashSaleId = value?.previewFlashSale?.id; final flashSaleId = value?.previewFlashSale?.id;
if (flashSaleId != null) { if (flashSaleId != null) {
...@@ -86,8 +83,28 @@ class VoucherDetailViewModel extends RestfulApiViewModel { ...@@ -86,8 +83,28 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
if (!value.isSuccess) { if (!value.isSuccess) {
onShowAlertError?.call(value.errorMessage ?? Constants.commonError); onShowAlertError?.call(value.errorMessage ?? Constants.commonError);
} else { } else {
Get.toNamed(transactionDetailScreen); verified.call();
// onShowAlertError?.call("Verify Order Product Success -> Go To Payment Detail"); }
});
}
redeemProduct() {
showLoading();
final requestId = Uuid().v4();
client
.orderSubmitPayment(
products: [product.value!],
quantity: 1,
requestId: requestId,
point: product.value?.amountToBePaid ?? 0,
)
.then((value) {
hideLoading();
if (!value.isSuccess) {
onShowAlertError?.call(value.errorMessage ?? Constants.commonError);
} else {
// Success -> go to transaction detail screen
onShowAlertError?.call("Redeem success -> go to transaction detail screen");
} }
}); });
} }
......
...@@ -9,6 +9,7 @@ import 'package:mypoint_flutter_app/screen/voucher/models/product_item_model.dar ...@@ -9,6 +9,7 @@ import 'package:mypoint_flutter_app/screen/voucher/models/product_item_model.dar
import 'package:mypoint_flutter_app/screen/voucher/models/product_media_item.dart'; import 'package:mypoint_flutter_app/screen/voucher/models/product_media_item.dart';
import 'package:mypoint_flutter_app/screen/voucher/models/product_price_model.dart'; import 'package:mypoint_flutter_app/screen/voucher/models/product_price_model.dart';
import 'package:mypoint_flutter_app/screen/voucher/models/product_properties_model.dart'; import 'package:mypoint_flutter_app/screen/voucher/models/product_properties_model.dart';
import 'package:mypoint_flutter_app/screen/voucher/models/product_type.dart';
import '../../flash_sale/preview_flash_sale_model.dart'; import '../../flash_sale/preview_flash_sale_model.dart';
import 'media_type.dart'; import 'media_type.dart';
import 'my_product_status_type.dart'; import 'my_product_status_type.dart';
...@@ -35,6 +36,9 @@ class ProductModel { ...@@ -35,6 +36,9 @@ class ProductModel {
final ProductItemModel? itemModel; final ProductItemModel? itemModel;
@JsonKey(name: 'expire_time') @JsonKey(name: 'expire_time')
final String? expireTime; final String? expireTime;
@JsonKey(name: 'require_form_regis')
final bool? requireFormRegis;
final String? type;
ProductModel({ ProductModel({
this.id, this.id,
...@@ -49,12 +53,18 @@ class ProductModel { ...@@ -49,12 +53,18 @@ class ProductModel {
this.customerInfoModel, this.customerInfoModel,
this.itemModel, this.itemModel,
this.expireTime, this.expireTime,
this.requireFormRegis,
this.type,
}); });
String? get name { String? get name {
return content?.name; return content?.name;
} }
ProductType get productType {
return ProductTypeExt.from(type) ?? ProductType.voucher;
}
String get expire { String get expire {
final ex = (isMyProduct ? itemModel?.expireTime : expireTime) ?? ""; final ex = (isMyProduct ? itemModel?.expireTime : expireTime) ?? "";
return ex.toDate()?.toFormattedString() ?? ""; return ex.toDate()?.toFormattedString() ?? "";
......
...@@ -53,6 +53,8 @@ ProductModel _$ProductModelFromJson(Map<String, dynamic> json) => ProductModel( ...@@ -53,6 +53,8 @@ ProductModel _$ProductModelFromJson(Map<String, dynamic> json) => ProductModel(
json['product_item'] as Map<String, dynamic>, json['product_item'] as Map<String, dynamic>,
), ),
expireTime: json['expire_time'] as String?, expireTime: json['expire_time'] as String?,
requireFormRegis: json['require_form_regis'] as bool?,
type: json['type'] as String?,
); );
Map<String, dynamic> _$ProductModelToJson(ProductModel instance) => Map<String, dynamic> _$ProductModelToJson(ProductModel instance) =>
...@@ -69,4 +71,6 @@ Map<String, dynamic> _$ProductModelToJson(ProductModel instance) => ...@@ -69,4 +71,6 @@ Map<String, dynamic> _$ProductModelToJson(ProductModel instance) =>
'customer_product_info': instance.customerInfoModel, 'customer_product_info': instance.customerInfoModel,
'product_item': instance.itemModel, 'product_item': instance.itemModel,
'expire_time': instance.expireTime, 'expire_time': instance.expireTime,
'require_form_regis': instance.requireFormRegis,
'type': instance.type,
}; };
...@@ -4,6 +4,7 @@ import '../../../shared/router_gage.dart'; ...@@ -4,6 +4,7 @@ import '../../../shared/router_gage.dart';
import '../../../widgets/custom_empty_widget.dart'; import '../../../widgets/custom_empty_widget.dart';
import '../../../widgets/custom_navigation_bar.dart'; import '../../../widgets/custom_navigation_bar.dart';
import '../../../widgets/custom_search_navigation_bar.dart'; import '../../../widgets/custom_search_navigation_bar.dart';
import '../../transaction/history/transaction_history_detail_screen.dart';
import '../sub_widget/voucher_item_list.dart'; import '../sub_widget/voucher_item_list.dart';
import 'voucher_list_viewmodel.dart'; import 'voucher_list_viewmodel.dart';
...@@ -82,7 +83,12 @@ class _VoucherListScreenState extends State<VoucherListScreen> { ...@@ -82,7 +83,12 @@ class _VoucherListScreenState extends State<VoucherListScreen> {
final product = _viewModel.products[index]; final product = _viewModel.products[index];
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
Get.toNamed(voucherDetailScreen, arguments: product.id); // // TODO
Get.toNamed(transactionHistoryDetailScreen, arguments: {
'orderId': "02744757-a5ef-420d-a737-c0bc93d767b7",
'canBack': true,
});
// Get.toNamed(voucherDetailScreen, arguments: product.id);
}, },
child: VoucherListItem(product: product), child: VoucherListItem(product: product),
); );
......
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/widgets/custom_app_bar.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../base/base_screen.dart';
import '../../base/basic_state.dart';
import '../../resouce/base_color.dart';
import '../../shared/router_gage.dart';
import '../../widgets/alert/data_alert_model.dart';
import '../../widgets/back_button.dart';
enum PaymentProcess {
begin,
processing,
success,
failure;
String get title {
switch (this) {
case PaymentProcess.begin:
return 'Bắt đầu thanh toán';
case PaymentProcess.processing:
return 'Đang xử lý thanh toán';
case PaymentProcess.success:
return 'Thanh toán thành công';
case PaymentProcess.failure:
return 'Thanh toán thất bại';
}
}
String get content {
switch (this) {
case PaymentProcess.begin:
return 'Vui lòng tiến hành thanh toán.';
case PaymentProcess.processing:
return 'Hệ thống đang xử lý giao dịch của bạn.';
case PaymentProcess.success:
return 'Giao dịch của bạn đã hoàn tất.';
case PaymentProcess.failure:
return 'Giao dịch thất bại. Vui lòng thử lại.';
}
}
}
class PaymentWebViewInput {
final String url;
final String orderId;
final bool isContract;
final bool showAlertBack;
final Function(PaymentProcess result)? callback;
PaymentWebViewInput({
required this.url,
required this.orderId,
this.isContract = false,
this.showAlertBack = true,
this.callback,
});
}
class PaymentWebViewScreen extends BaseScreen {
const PaymentWebViewScreen({super.key});
@override
State<PaymentWebViewScreen> createState() => _PaymentWebViewScreenState();
}
class _PaymentWebViewScreenState extends BaseState<PaymentWebViewScreen> with BasicState {
late final PaymentWebViewInput input;
late final WebViewController _controller;
bool _isLoading = true;
final List<String> paymentSuccessUrls = [
"https://localhost/paymentGatewayRequestSuccessful",
"mypointapp://open?click_action_type=PAYMENT_SUCCESS",
"https://localhost/paymentGatewayAutoDebitRequestSuccessful",
];
final List<String> paymentFailedUrls = [
"https://localhost/paymentGatewayRequestFailed",
"mypointapp://open?click_action_type=PAYMENT_FAIL",
"https://localhost/paymentGatewayAutoDebitRequestFailed",
];
@override
void initState() {
super.initState();
final args = Get.arguments;
if (args is! PaymentWebViewInput) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Get.back();
});
return;
}
input = args;
_controller =
WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageStarted: (_) {
setState(() {
_isLoading = true;
});
},
onPageFinished: (_) {
setState(() {
_isLoading = false;
});
},
onNavigationRequest: _handleNavigation,
),
)
..loadRequest(Uri.parse(input.url));
}
@override
Widget createBody() {
return Scaffold(
appBar: CustomAppBar(
title: "Thanh toán",
leftButtons: [
CustomBackButton(
onPressed: () async {
if (input.showAlertBack) {
_onBackPressed();
return;
}
Get.back();
},
),
],
),
body: Stack(
children: [
WebViewWidget(controller: _controller),
if (_isLoading) const Center(child: CircularProgressIndicator()),
],
),
);
}
NavigationDecision _handleNavigation(NavigationRequest request) {
final url = request.url;
debugPrint("➡️ Navigating: $url");
if (paymentSuccessUrls.any((success) => url.startsWith(success))) {
_onPaymentResult(PaymentProcess.success);
return NavigationDecision.prevent;
}
if (paymentFailedUrls.any((fail) => url.startsWith(fail))) {
_onPaymentResult(PaymentProcess.failure);
return NavigationDecision.prevent;
}
// Mở app Zalo nếu redirect đến scheme của nó
final uri = Uri.tryParse(url);
final zaloSchemes = ["zalo", "zalopay", "zalopay.api.v2"];
if (uri != null && zaloSchemes.contains(uri.scheme)) {
launchUrl(uri, mode: LaunchMode.externalApplication);
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
}
void _onPaymentResult(PaymentProcess result) {
if (input.isContract) {
_navigateToContractScreen();
} else if (input.callback != null) {
input.callback!(result);
Get.back(); // hoặc điều hướng phù hợp
} else {
_backToRoot();
}
}
void _backToRoot() {
Get.until((route) => route.isFirst);
}
void _navigateToContractScreen() {
Get.snackbar('Thông báo', 'Đi tới danh sách hợp đồng điện'); // placeholder
}
_onBackPressed() {
final dataAlert = DataAlertModel(
title: "Huỷ đơn hàng?",
description: "Bạn có chắc muốn huỷ thanh toán đơn hàng này?",
localHeaderImage: "assets/images/ic_pipi_03.png",
buttons: [
AlertButton(
text: "Đồng ý",
onPressed: () {
Get.offNamed(transactionHistoryDetailScreen, arguments: {"orderId": input.orderId ?? "", "canBack": false});
},
bgColor: BaseColor.primary500,
textColor: Colors.white,
),
AlertButton(
text: "Huỷ",
onPressed: () => Navigator.pop(context, false),
bgColor: Colors.white,
textColor: BaseColor.second500,
),
],
);
showAlert(data: dataAlert);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:webview_flutter/webview_flutter.dart';
class BaseWebViewInput {
final String? title;
final String url;
final bool isFullScreen;
const BaseWebViewInput({
this.title,
required this.url,
this.isFullScreen = false,
});
}
class BaseWebViewScreen extends StatefulWidget {
const BaseWebViewScreen({super.key});
@override
State<BaseWebViewScreen> createState() => _BaseWebViewScreenState();
}
class _BaseWebViewScreenState extends State<BaseWebViewScreen> {
late final BaseWebViewInput input;
late final WebViewController _controller;
String? _dynamicTitle;
@override
void initState() {
super.initState();
final args = Get.arguments;
if (args is BaseWebViewInput) {
input = args;
} else {
throw Exception('BaseWebViewInput is required in arguments');
}
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageFinished: (_) async {
final title = await _controller.getTitle();
setState(() {
_dynamicTitle = title;
});
},
onWebResourceError: (_) {
_showWebErrorDialog();
},
),
)
..loadRequest(Uri.parse(input.url));
_clearCookies();
}
Future<void> _clearCookies() async {
final cookieManager = WebViewCookieManager();
await cookieManager.clearCookies();
}
void _showWebErrorDialog() {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text("Lỗi"),
content: const Text("Không thể tải nội dung. Vui lòng thử lại sau."),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("Đóng"),
)
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: input.isFullScreen
? null
: AppBar(
title: Text(
input.title ?? _dynamicTitle ?? Uri.parse(input.url).host,
style: const TextStyle(fontSize: 16),
),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: _handleBack,
),
),
body: SafeArea(
child: WebViewWidget(controller: _controller),
),
);
}
void _handleBack() async {
if (await _controller.canGoBack()) {
_controller.goBack();
} else {
if (context.mounted) Navigator.of(context).pop();
}
}
}
\ No newline at end of file
...@@ -2,13 +2,18 @@ import 'package:get/get_navigation/src/routes/get_route.dart'; ...@@ -2,13 +2,18 @@ import 'package:get/get_navigation/src/routes/get_route.dart';
import '../screen/game/game_cards/game_card_screen.dart'; import '../screen/game/game_cards/game_card_screen.dart';
import '../screen/login/login_screen.dart'; import '../screen/login/login_screen.dart';
import '../screen/main_tab_screen/main_tab_screen.dart'; import '../screen/main_tab_screen/main_tab_screen.dart';
import '../screen/notification/notification_screen.dart';
import '../screen/onboarding/onboarding_screen.dart'; import '../screen/onboarding/onboarding_screen.dart';
import '../screen/register_campaign/register_form_input_screen.dart'; import '../screen/register_campaign/register_form_input_screen.dart';
import '../screen/setting/setting_screen.dart'; import '../screen/setting/setting_screen.dart';
import '../screen/splash/splash_screen.dart'; import '../screen/splash/splash_screen.dart';
import '../screen/support/support_screen.dart';
import '../screen/transaction/history/transaction_history_detail_screen.dart';
import '../screen/transaction/transaction_detail_screen.dart'; import '../screen/transaction/transaction_detail_screen.dart';
import '../screen/voucher/detail/voucher_detail_screen.dart'; import '../screen/voucher/detail/voucher_detail_screen.dart';
import '../screen/voucher/voucher_list/voucher_list_screen.dart'; import '../screen/voucher/voucher_list/voucher_list_screen.dart';
import '../screen/webview/payment_web_view_screen.dart';
import '../screen/webview/web_view_screen.dart';
const splashScreen = '/splash'; const splashScreen = '/splash';
const onboardingScreen = '/onboarding'; const onboardingScreen = '/onboarding';
...@@ -20,6 +25,11 @@ const voucherDetailScreen = '/voucherDetail'; ...@@ -20,6 +25,11 @@ const voucherDetailScreen = '/voucherDetail';
const gameCardScreen = '/gameCardScreen'; const gameCardScreen = '/gameCardScreen';
const registerFormInputScreen = '/registerFormInputScreen'; const registerFormInputScreen = '/registerFormInputScreen';
const transactionDetailScreen = '/transactionDetailScreen'; const transactionDetailScreen = '/transactionDetailScreen';
const baseWebViewScreen = '/baseWebViewScreen';
const paymentWebViewScreen = '/paymentWebViewScreen';
const transactionHistoryDetailScreen = '/transactionHistoryDetailScreen';
const supportScreen = '/supportScreen';
const notificationScreen = '/notificationScreen';
class RouterPage { class RouterPage {
static List<GetPage> pages() { static List<GetPage> pages() {
...@@ -40,6 +50,11 @@ class RouterPage { ...@@ -40,6 +50,11 @@ class RouterPage {
GetPage(name: gameCardScreen, page: () => GameCardScreen(),), GetPage(name: gameCardScreen, page: () => GameCardScreen(),),
GetPage(name: registerFormInputScreen, page: () => RegisterFormInputScreen(),), GetPage(name: registerFormInputScreen, page: () => RegisterFormInputScreen(),),
GetPage(name: transactionDetailScreen, page: () => TransactionDetailScreen(),), GetPage(name: transactionDetailScreen, page: () => TransactionDetailScreen(),),
GetPage(name: baseWebViewScreen, page: () => BaseWebViewScreen(),),
GetPage(name: paymentWebViewScreen, page: () => PaymentWebViewScreen(),),
GetPage(name: transactionHistoryDetailScreen, page: () => TransactionHistoryDetailScreen(),),
GetPage(name: supportScreen, page: () => SupportScreen(),),
GetPage(name: notificationScreen, page: () => NotificationScreen(),),
]; ];
} }
} }
\ No newline at end of file
...@@ -34,7 +34,7 @@ class CustomAlertDialog extends StatelessWidget { ...@@ -34,7 +34,7 @@ class CustomAlertDialog extends StatelessWidget {
_buildHeaderImage(), _buildHeaderImage(),
const SizedBox(height: 2), const SizedBox(height: 2),
// Title // Title
if (alertData.title != null) if ((alertData.title ?? "").isNotEmpty)
Text( Text(
alertData.title!, alertData.title!,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
...@@ -48,7 +48,7 @@ class CustomAlertDialog extends StatelessWidget { ...@@ -48,7 +48,7 @@ class CustomAlertDialog extends StatelessWidget {
</div> </div>
'''), '''),
const SizedBox(height: 4), const SizedBox(height: 4),
if (alertData.content != null) if ((alertData.content ?? "").isNotEmpty)
Text( Text(
alertData.content!, alertData.content!,
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: BaseColor.primary500), style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: BaseColor.primary500),
......
...@@ -48,6 +48,7 @@ dependencies: ...@@ -48,6 +48,7 @@ dependencies:
local_auth: local_auth:
pin_code_fields: pin_code_fields:
intl: ^0.18.1 intl: ^0.18.1
webview_flutter: ^4.2.2
game_miniapp: game_miniapp:
path: ../mini_app/game_miniapp path: ../mini_app/game_miniapp
dev_dependencies: dev_dependencies:
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment