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 {
final String? code;
final String? name;
final String? logo;
@JsonKey(name: 'save_token')
final bool? saveToken;
final bool? isSelected;
final bool? needSaveTokenWhenOrder;
bool? needSaveTokenWhenOrder;
PaymentMethodType? get type {
if (code == null) return null;
......
......@@ -12,7 +12,7 @@ PaymentMethodModel _$PaymentMethodModelFromJson(Map<String, dynamic> json) =>
code: json['code'] as String?,
name: json['name'] as String?,
logo: json['logo'] as String?,
saveToken: json['saveToken'] as bool?,
saveToken: json['save_token'] as bool?,
isSelected: json['isSelected'] as bool?,
needSaveTokenWhenOrder: json['needSaveTokenWhenOrder'] as bool?,
);
......@@ -23,7 +23,7 @@ Map<String, dynamic> _$PaymentMethodModelToJson(PaymentMethodModel instance) =>
'code': instance.code,
'name': instance.name,
'logo': instance.logo,
'saveToken': instance.saveToken,
'save_token': instance.saveToken,
'isSelected': instance.isSelected,
'needSaveTokenWhenOrder': instance.needSaveTokenWhenOrder,
};
......@@ -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 {
switch (this) {
case PaymentMethodType.card:
......
......@@ -14,6 +14,8 @@ class PreviewOrderPaymentPointDataModel {
this.textDisplay,
});
bool get isEnableUsePoint => status == 1;
factory PreviewOrderPaymentPointDataModel.fromJson(Map<String, dynamic> json) =>
_$PreviewOrderPaymentPointDataModelFromJson(json);
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: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 '../../configs/constants.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_method_model.dart';
import 'model/preview_order_payment_model.dart';
class TransactionDetailViewModel extends RestfulApiViewModel {
final int definedCodeIndexBankAccount = 1000;
var previewData = Rxn<PreviewOrderPaymentModel>();
var paymentMethods = RxList<PaymentMethodModel>();
var paymentBankAccounts = RxList<PaymentBankAccountInfoModel>();
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
void onInit() {
......@@ -20,23 +45,80 @@ class TransactionDetailViewModel extends RestfulApiViewModel {
Future<void> refreshData() async {
isLoading.value = true;
await Future.wait([
_getPreviewOrderPayment(),
_getPaymentMethods(),
_getPaymentBankAccounts(),
]);
await Future.wait([_getPreviewOrderPayment(), _getPaymentMethods(), _getPaymentBankAccounts()]);
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 {
String? token = DataPreference.instance.token ?? "";
try {
final body = {
"product_id": 13796,
"quantity": 1,
"product_id": product.id,
"quantity": quantity,
"price": product.amountToBePaid ?? 0,
"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);
previewData.value = response.data;
} catch (error) {
......@@ -61,4 +143,4 @@ class TransactionDetailViewModel extends RestfulApiViewModel {
print("Error fetching payment bank accounts: $error");
}
}
}
\ No newline at end of file
}
import 'dart:math';
import 'package:flutter/material.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/models/product_type.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../base/base_screen.dart';
import '../../../base/basic_state.dart';
import '../../../preference/point/point_manager.dart';
import '../../../resouce/base_color.dart';
import '../../../shared/router_gage.dart';
import '../../../widgets/alert/data_alert_model.dart';
import '../../../widgets/back_button.dart';
import '../../../widgets/custom_empty_widget.dart';
import '../../../widgets/custom_point_text_tag.dart';
......@@ -181,12 +185,8 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
const SizedBox(width: 8),
Expanded(child: Text(product.brand?.name ?? '', style: const TextStyle(fontSize: 14))),
// PriceTagWidget(point: product.amountToBePaid ?? 0),
CustomPointText(
point: product.amountToBePaid ?? 0,
type: product.price?.method,
),
CustomPointText(point: product.amountToBePaid ?? 0, type: product.price?.method),
],
),
],
),
......@@ -203,8 +203,10 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
if (hasExpire)
Text('Hạn dùng: ', style: const TextStyle(color: Colors.grey, fontWeight: FontWeight.bold, fontSize: 12)),
if (hasExpire)
Text(product.expired ? "Hết hạn" : product.expire,
style: const TextStyle(color: BaseColor.primary500, fontWeight: FontWeight.bold, fontSize: 12)),
Text(
product.expired ? "Hết hạn" : product.expire,
style: const TextStyle(color: BaseColor.primary500, fontWeight: FontWeight.bold, fontSize: 12),
),
if (isOutOfStock)
Container(
margin: const EdgeInsets.only(left: 8),
......@@ -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) {
if (content == null || content.isEmpty) return const SizedBox();
return Container(
......@@ -379,8 +373,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
height: 48,
child: ElevatedButton(
onPressed: () {
Get.toNamed(registerFormInputScreen, arguments: {"id": 13484});
// TODO: Handle đổi ưu đãi
_handleContinueButtonAction();
},
style: ElevatedButton.styleFrom(
backgroundColor: BaseColor.primary500,
......@@ -399,48 +392,49 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
return _buildBottomActionContainer(
child: Row(
children: [
Row(
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(50),
),
child: IconButton(
icon: const Icon(Icons.remove, color: Colors.black),
onPressed: () {
if (_viewModel.quantity.value > 1) {
_viewModel.quantity.value--;
}
},
),
),
const SizedBox(width: 12),
Obx(() => Text('${_viewModel.quantity.value}', style: const TextStyle(fontSize: 16))),
const SizedBox(width: 12),
Container(
decoration: BoxDecoration(
color: BaseColor.primary500,
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(50),
if (_viewModel.product.value?.productType == ProductType.voucher)
Row(
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(50),
),
child: IconButton(
icon: const Icon(Icons.remove, color: Colors.black),
onPressed: () {
if (_viewModel.quantity.value > 1) {
_viewModel.quantity.value--;
}
},
),
),
child: IconButton(
icon: const Icon(Icons.add, color: Colors.white),
onPressed: () {
_viewModel.quantity.value++;
},
const SizedBox(width: 12),
Obx(() => Text('${_viewModel.quantity.value}', style: const TextStyle(fontSize: 16))),
const SizedBox(width: 12),
Container(
decoration: BoxDecoration(
color: BaseColor.primary500,
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(50),
),
child: IconButton(
icon: const Icon(Icons.add, color: Colors.white),
onPressed: () {
_viewModel.quantity.value++;
},
),
),
),
],
),
],
),
const SizedBox(width: 36),
Expanded(
child: SizedBox(
height: 48,
child: ElevatedButton(
onPressed: () {
_viewModel.verifyOrderProduct();
_handleContinueButtonAction();
},
style: ElevatedButton.styleFrom(
backgroundColor: BaseColor.primary500,
......@@ -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() {
return Obx(() {
final isFavorite = _viewModel.liked.value;
......
import 'dart:ui';
import 'package:get/get.dart';
import 'package:mypoint_flutter_app/networking/restful_api_request.dart';
import 'package:uuid/uuid.dart';
import '../../../base/restful_api_viewmodel.dart';
import '../../../configs/constants.dart';
import '../../../shared/router_gage.dart';
......@@ -38,7 +41,6 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
}
} catch (error) {
onShowAlertError?.call("Error toggling favorite: $error");
print("Error toggling favorite: $error");
}
}
......@@ -51,7 +53,6 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
liked.value = product.value?.liked == true;
} catch (error) {
onShowAlertError?.call("Error fetching product detail: $error");
print("Error fetching product detail: $error");
} finally {
isLoading.value = false;
}
......@@ -67,13 +68,9 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
} finally {}
}
verifyOrderProduct() async {
verifyOrderProduct(Function verified) async {
final value = product.value;
var body = {
"product_id": productId,
"price": value?.amountToBePaid,
"quantity": quantity.value,
};
var body = {"product_id": productId, "price": value?.amountToBePaid, "quantity": quantity.value};
if (value?.previewFlashSale?.isFlashSalePrice == true) {
final flashSaleId = value?.previewFlashSale?.id;
if (flashSaleId != null) {
......@@ -86,9 +83,29 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
if (!value.isSuccess) {
onShowAlertError?.call(value.errorMessage ?? Constants.commonError);
} else {
Get.toNamed(transactionDetailScreen);
// onShowAlertError?.call("Verify Order Product Success -> Go To Payment Detail");
verified.call();
}
});
}
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
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_properties_model.dart';
import 'package:mypoint_flutter_app/screen/voucher/models/product_type.dart';
import '../../flash_sale/preview_flash_sale_model.dart';
import 'media_type.dart';
import 'my_product_status_type.dart';
......@@ -35,6 +36,9 @@ class ProductModel {
final ProductItemModel? itemModel;
@JsonKey(name: 'expire_time')
final String? expireTime;
@JsonKey(name: 'require_form_regis')
final bool? requireFormRegis;
final String? type;
ProductModel({
this.id,
......@@ -49,12 +53,18 @@ class ProductModel {
this.customerInfoModel,
this.itemModel,
this.expireTime,
this.requireFormRegis,
this.type,
});
String? get name {
return content?.name;
}
ProductType get productType {
return ProductTypeExt.from(type) ?? ProductType.voucher;
}
String get expire {
final ex = (isMyProduct ? itemModel?.expireTime : expireTime) ?? "";
return ex.toDate()?.toFormattedString() ?? "";
......
......@@ -53,6 +53,8 @@ ProductModel _$ProductModelFromJson(Map<String, dynamic> json) => ProductModel(
json['product_item'] as Map<String, dynamic>,
),
expireTime: json['expire_time'] as String?,
requireFormRegis: json['require_form_regis'] as bool?,
type: json['type'] as String?,
);
Map<String, dynamic> _$ProductModelToJson(ProductModel instance) =>
......@@ -69,4 +71,6 @@ Map<String, dynamic> _$ProductModelToJson(ProductModel instance) =>
'customer_product_info': instance.customerInfoModel,
'product_item': instance.itemModel,
'expire_time': instance.expireTime,
'require_form_regis': instance.requireFormRegis,
'type': instance.type,
};
......@@ -4,6 +4,7 @@ import '../../../shared/router_gage.dart';
import '../../../widgets/custom_empty_widget.dart';
import '../../../widgets/custom_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 'voucher_list_viewmodel.dart';
......@@ -82,7 +83,12 @@ class _VoucherListScreenState extends State<VoucherListScreen> {
final product = _viewModel.products[index];
return GestureDetector(
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),
);
......
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';
import '../screen/game/game_cards/game_card_screen.dart';
import '../screen/login/login_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/register_campaign/register_form_input_screen.dart';
import '../screen/setting/setting_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/voucher/detail/voucher_detail_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 onboardingScreen = '/onboarding';
......@@ -20,6 +25,11 @@ const voucherDetailScreen = '/voucherDetail';
const gameCardScreen = '/gameCardScreen';
const registerFormInputScreen = '/registerFormInputScreen';
const transactionDetailScreen = '/transactionDetailScreen';
const baseWebViewScreen = '/baseWebViewScreen';
const paymentWebViewScreen = '/paymentWebViewScreen';
const transactionHistoryDetailScreen = '/transactionHistoryDetailScreen';
const supportScreen = '/supportScreen';
const notificationScreen = '/notificationScreen';
class RouterPage {
static List<GetPage> pages() {
......@@ -40,6 +50,11 @@ class RouterPage {
GetPage(name: gameCardScreen, page: () => GameCardScreen(),),
GetPage(name: registerFormInputScreen, page: () => RegisterFormInputScreen(),),
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 {
_buildHeaderImage(),
const SizedBox(height: 2),
// Title
if (alertData.title != null)
if ((alertData.title ?? "").isNotEmpty)
Text(
alertData.title!,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
......@@ -48,7 +48,7 @@ class CustomAlertDialog extends StatelessWidget {
</div>
'''),
const SizedBox(height: 4),
if (alertData.content != null)
if ((alertData.content ?? "").isNotEmpty)
Text(
alertData.content!,
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: BaseColor.primary500),
......
......@@ -48,6 +48,7 @@ dependencies:
local_auth:
pin_code_fields:
intl: ^0.18.1
webview_flutter: ^4.2.2
game_miniapp:
path: ../mini_app/game_miniapp
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