Commit fc2caf86 authored by DatHV's avatar DatHV
Browse files

update ui, logic.

parent 0b973e61
import 'package:json_annotation/json_annotation.dart';
import 'package:mypoint_flutter_app/screen/register_campaign/model/verify_register_model.dart';
import 'form_input_description_model.dart';
import 'input_cell_model.dart';
......@@ -17,6 +18,7 @@ class InputFormRegistrationModel {
@JsonKey(name: 'click_action_param')
final String? clickActionParam;
final bool? checked;
final VerifyFormRegisterModel? verify;
InputFormRegistrationModel({
this.title,
......@@ -26,6 +28,7 @@ class InputFormRegistrationModel {
this.clickActionType,
this.clickActionParam,
this.checked,
this.verify,
});
factory InputFormRegistrationModel.fromJson(Map<String, dynamic> json) =>
......
......@@ -29,6 +29,12 @@ InputFormRegistrationModel _$InputFormRegistrationModelFromJson(
clickActionType: json['click_action_type'] as String?,
clickActionParam: json['click_action_param'] as String?,
checked: json['checked'] as bool?,
verify:
json['verify'] == null
? null
: VerifyFormRegisterModel.fromJson(
json['verify'] as Map<String, dynamic>,
),
);
Map<String, dynamic> _$InputFormRegistrationModelToJson(
......@@ -41,4 +47,5 @@ Map<String, dynamic> _$InputFormRegistrationModelToJson(
'click_action_type': instance.clickActionType,
'click_action_param': instance.clickActionParam,
'checked': instance.checked,
'verify': instance.verify,
};
import 'dart:convert';
import 'package:json_annotation/json_annotation.dart';
import 'package:mypoint_flutter_app/extensions/string_extension.dart';
import 'package:mypoint_flutter_app/screen/register_campaign/model/registration_form_package_verify_model.dart';
import 'input_form_registration_model.dart';
......@@ -38,4 +41,32 @@ class RegistrationFormPackageModel {
_$RegistrationFormPackageModelFromJson(json);
Map<String, dynamic> toJson() => _$RegistrationFormPackageModelToJson(this);
Map<String, dynamic> get submitParams {
final inputs = formRegistration?.inputRequired;
if (inputs == null || inputs.isEmpty) return {};
final out = <String, dynamic>{};
for (final item in inputs) {
// Ưu tiên code_default, nếu rỗng fallback content
final value = [item.codeDefault, item.content, item.defaultValue]
.firstWhere((s) => s?.trim().isNotEmpty ?? false, orElse: () => item.defaultValue);
out[item.key ?? ''] = value;
}
return {'form_card': out};
}
}
extension JsonStringX on Map<String, dynamic> {
/// Convert Map -> JSON String
/// - [prettify]: thêm indent 2 spaces cho dễ đọc
String? toJsonString({bool prettify = false}) {
try {
final encoder = prettify
? const JsonEncoder.withIndent(' ')
: const JsonEncoder();
return encoder.convert(this);
} catch (_) {
return null; // Không encode được (có thể do giá trị không JSON-encodable)
}
}
}
\ No newline at end of file
import 'package:json_annotation/json_annotation.dart';
part 'verify_register_model.g.dart';
@JsonSerializable()
class VerifyRegisteredPackageModel {
final bool? registed;
@JsonKey(name: 'create_time')
final String? createTime;
@JsonKey(name: 'expire_time')
final String? expireTime;
final String? description;
const VerifyRegisteredPackageModel({
this.registed,
this.createTime,
this.expireTime,
this.description,
});
factory VerifyRegisteredPackageModel.fromJson(Map<String, dynamic> json)
=> _$VerifyRegisteredPackageModelFromJson(json);
Map<String, dynamic> toJson() => _$VerifyRegisteredPackageModelToJson(this);
}
@JsonSerializable(explicitToJson: true)
class VerifyRegisterVnTraAlertModel {
final bool? alert;
final String? description;
const VerifyRegisterVnTraAlertModel({this.alert, this.description});
factory VerifyRegisterVnTraAlertModel.fromJson(Map<String, dynamic> json)
=> _$VerifyRegisterVnTraAlertModelFromJson(json);
Map<String, dynamic> toJson() => _$VerifyRegisterVnTraAlertModelToJson(this);
}
@JsonSerializable(explicitToJson: true)
class VerifyRegisterCampaignModel {
final bool? valid;
@JsonKey(name: 'full_name')
final String? fullName;
@JsonKey(name: 'phone_number')
final String? phoneNumber;
@JsonKey(name: 'license_plate')
final String? licensePlate;
@JsonKey(name: 'registed_package')
final VerifyRegisteredPackageModel? registeredPackage;
final VerifyRegisterVnTraAlertModel? alert;
const VerifyRegisterCampaignModel({
this.valid,
this.fullName,
this.phoneNumber,
this.licensePlate,
this.registeredPackage,
this.alert,
});
factory VerifyRegisterCampaignModel.fromJson(Map<String, dynamic> json)
=> _$VerifyRegisterVnTraModelFromJson(json);
Map<String, dynamic> toJson() => _$VerifyRegisterVnTraModelToJson(this);
}
class VerifyFormRegisterModel {
final String? url;
const VerifyFormRegisterModel({this.url});
factory VerifyFormRegisterModel.fromJson(Map<String, dynamic> json) {
return VerifyFormRegisterModel(
url: json['url'] as String?,
);
}
Map<String, dynamic> toJson() => {
'url': url,
};
}
\ No newline at end of file
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'verify_register_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
VerifyRegisteredPackageModel _$VerifyRegisteredPackageModelFromJson(
Map<String, dynamic> json,
) => VerifyRegisteredPackageModel(
registed: json['registed'] as bool?,
createTime: json['create_time'] as String?,
expireTime: json['expire_time'] as String?,
description: json['description'] as String?,
);
Map<String, dynamic> _$VerifyRegisteredPackageModelToJson(
VerifyRegisteredPackageModel instance,
) => <String, dynamic>{
'registed': instance.registed,
'create_time': instance.createTime,
'expire_time': instance.expireTime,
'description': instance.description,
};
VerifyRegisterVnTraAlertModel _$VerifyRegisterVnTraAlertModelFromJson(
Map<String, dynamic> json,
) => VerifyRegisterVnTraAlertModel(
alert: json['alert'] as bool?,
description: json['description'] as String?,
);
Map<String, dynamic> _$VerifyRegisterVnTraAlertModelToJson(
VerifyRegisterVnTraAlertModel instance,
) => <String, dynamic>{
'alert': instance.alert,
'description': instance.description,
};
VerifyRegisterCampaignModel _$VerifyRegisterCampaignModelFromJson(
Map<String, dynamic> json,
) => VerifyRegisterCampaignModel(
valid: json['valid'] as bool?,
fullName: json['full_name'] as String?,
phoneNumber: json['phone_number'] as String?,
licensePlate: json['license_plate'] as String?,
registeredPackage:
json['registed_package'] == null
? null
: VerifyRegisteredPackageModel.fromJson(
json['registed_package'] as Map<String, dynamic>,
),
alert:
json['alert'] == null
? null
: VerifyRegisterVnTraAlertModel.fromJson(
json['alert'] as Map<String, dynamic>,
),
);
Map<String, dynamic> _$VerifyRegisterCampaignModelToJson(
VerifyRegisterCampaignModel instance,
) => <String, dynamic>{
'valid': instance.valid,
'full_name': instance.fullName,
'phone_number': instance.phoneNumber,
'license_plate': instance.licensePlate,
'registed_package': instance.registeredPackage?.toJson(),
'alert': instance.alert?.toJson(),
};
......@@ -21,6 +21,7 @@ class _RegisterFormInputScreenState extends State<RegisterFormInputScreen> {
String _title = '';
final _isFormValid = false.obs;
late var _isConfirmScreen = false;
ProductModel? _product;
@override
void initState() {
......@@ -28,23 +29,24 @@ class _RegisterFormInputScreenState extends State<RegisterFormInputScreen> {
_viewModel = Get.put(RegisterFormInputViewModel());
final args = Get.arguments as Map<String, dynamic>?;
_product = args?['product'] as ProductModel?;
if (args?['formConfirm'] != null) {
_isConfirmScreen = true;
final data = args!['formConfirm'] as RegistrationFormPackageModel;
_title = data?.formConfirm?.title ?? '';
_viewModel.form.value = data;
} else {
final id = (args?['product'] as ProductModel?)?.id;
if (id != null) {
_isConfirmScreen = false;
_viewModel.fetchRegisterFormInput(id.toString()).then((_) {
final id = _product?.id.toString() ?? '';
if (id.isEmpty) return;
_viewModel.fetchRegisterFormInput(id).then((_) {
setState(() {
_title = _viewModel.form.value?.title ?? '';
});
});
}
}
}
@override
Widget build(BuildContext context) {
......@@ -176,12 +178,14 @@ class _RegisterFormInputScreenState extends State<RegisterFormInputScreen> {
}
void _gotoPaymentScreen() {
print("_gotoPaymentScreen");
final metaData = (_viewModel.form.value?.submitParams ?? {}).toJsonString();
print("_gotoPaymentScreen metaData $metaData");
Get.toNamed(transactionDetailScreen, arguments: {"product": _product, "quantity": 1, "metaData": metaData});
}
void _onSubmit() {
if (_viewModel.form.value?.formConfirm?.checked == true) {
Get.toNamed(registerFormInputScreen, arguments: {"formConfirm": _viewModel.form.value}, preventDuplicates: false);
Get.toNamed(registerFormInputScreen, arguments: {"formConfirm": _viewModel.form.value, "product": _product}, preventDuplicates: false);
} else {
_gotoPaymentScreen();
}
......@@ -190,9 +194,9 @@ class _RegisterFormInputScreenState extends State<RegisterFormInputScreen> {
void _validateForm() {
final form = _viewModel.form.value?.formRegistration;
final inputs = form?.inputRequired ?? [];
inputs.forEach((input) {
for (var input in inputs) {
print("Input: ${input.title}, Value: ${input.value}");
});
}
final isValid = inputs.every((input) {
if (input.require == true) {
return input.value.trim().isNotEmpty == true;
......
......@@ -2,9 +2,11 @@ import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:mypoint_flutter_app/networking/restful_api_client_all_request.dart';
import '../../networking/restful_api_viewmodel.dart';
import 'model/registration_form_package_model.dart';
import 'model/verify_register_model.dart';
class RegisterFormInputViewModel extends RestfulApiViewModel {
var form = Rxn<RegistrationFormPackageModel>();
var verifyData = Rxn<VerifyRegisterCampaignModel>();
var isLoading = false.obs;
var isChecked = true.obs;
......@@ -20,4 +22,17 @@ class RegisterFormInputViewModel extends RestfulApiViewModel {
isLoading.value = false;
}
}
Future<void> verifyRegisterForm() async {
final path = form.value?.formRegistration?.verify?.url ?? '';
try {
isLoading.value = true;
final response = await client.verifyRegisterForm(path);
verifyData.value = response.data;
} catch (error) {
print("Error fetching product detail: $error");
} finally {
isLoading.value = false;
}
}
}
\ No newline at end of file
......@@ -48,7 +48,7 @@ class _TransactionHistoryDetailScreenState extends BaseState<TransactionHistoryD
leftButtons: canBack ? [CustomBackButton()] : [],
rightButtons: [
IconButton(
icon: const Icon(Icons.headset_mic, size: 24, color: Colors.black54),
icon: const Icon(Icons.headset_rounded, size: 24),
onPressed: () {
Get.toNamed(supportScreen);
},
......@@ -100,7 +100,7 @@ class _TransactionHistoryDetailScreenState extends BaseState<TransactionHistoryD
Widget _buildMainInfoSection(TransactionHistoryModel data) {
return Center(
child: Container(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(0),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12)),
child: Column(
children: [
......@@ -114,7 +114,7 @@ class _TransactionHistoryDetailScreenState extends BaseState<TransactionHistoryD
),
),
const SizedBox(height: 8),
const Text("Thanh toán mua ưu đãi", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
Text(data.name ?? "Thanh toán mua ưu đãi", style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.center,
......
......@@ -24,6 +24,7 @@ class _TransactionDetailScreenState extends BaseState<TransactionDetailScreen> w
late final TransactionDetailViewModel _viewModel;
final currencyFormatter = NumberFormat.currency(locale: 'vi_VN', symbol: 'đ', decimalDigits: 0);
ProductModel? _product;
String? _metaData;
int _quantity = 1;
String? _targetPhoneNumber;
bool _isPaymentMethodsExpanded = true;
......@@ -35,6 +36,7 @@ class _TransactionDetailScreenState extends BaseState<TransactionDetailScreen> w
final args = Get.arguments;
if (args is Map) {
_product = args['product'];
_metaData = args['metaData'];
_quantity = args['quantity'] ?? 1;
_targetPhoneNumber = args['targetPhoneNumber'];
}
......@@ -45,7 +47,7 @@ class _TransactionDetailScreenState extends BaseState<TransactionDetailScreen> w
return;
}
_viewModel = Get.put(
TransactionDetailViewModel(product: _product!, quantity: _quantity, targetPhoneNumber: _targetPhoneNumber),
TransactionDetailViewModel(product: _product!, quantity: _quantity, targetPhoneNumber: _targetPhoneNumber, metaData: _metaData),
);
_viewModel.refreshData();
......@@ -202,7 +204,7 @@ class _TransactionDetailScreenState extends BaseState<TransactionDetailScreen> w
children: [
_buildTotalRow('Tổng số tiền', totalPrice, false),
_buildTotalRow('Sử dụng điểm', -pointsUsed, false),
_buildTotalRow('Tổng tạm tính', finalTotal, false),
_buildTotalRow('Tổng tạm tính', finalTotal, true),
],
),
);
......@@ -217,13 +219,13 @@ class _TransactionDetailScreenState extends BaseState<TransactionDetailScreen> w
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: TextStyle(fontSize: 16, color: isHighlighted ? Colors.black87 : Colors.grey.shade700)),
Text(label, style: TextStyle(fontSize: 16, color: Colors.grey.shade700)),
Text(
displayAmount,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: isHighlighted && amount == 0 ? Colors.red : Colors.black87,
color: isHighlighted ? Colors.red : Colors.black87,
),
),
],
......
......@@ -22,6 +22,7 @@ class TransactionDetailViewModel extends RestfulApiViewModel {
var paymentBankAccounts = RxList<PaymentBankAccountInfoModel>();
final RxBool isLoading = false.obs;
final ProductModel product;
String? metaData;
final int quantity;
final String? targetPhoneNumber;
final RxBool usePoints = true.obs;
......@@ -36,7 +37,7 @@ class TransactionDetailViewModel extends RestfulApiViewModel {
return finalTotal;
}
TransactionDetailViewModel({required this.product, required this.quantity, this.targetPhoneNumber});
TransactionDetailViewModel({required this.product, required this.quantity, this.targetPhoneNumber, this.metaData});
@override
void onInit() {
......@@ -50,7 +51,7 @@ class TransactionDetailViewModel extends RestfulApiViewModel {
isLoading.value = false;
}
requestPaymentProduct() {
void requestPaymentProduct() {
showLoading();
final requestId = Uuid().v4();
int? point = usePoints.value ? previewData.value?.pointData?.point : 0;
......@@ -75,7 +76,7 @@ class TransactionDetailViewModel extends RestfulApiViewModel {
method: selectedPaymentMethod,
paymentTokenId: selectedBankAccount?.id,
saveToken: saveToken,
metadata: "",
metadata: metaData,
targetPhoneNumber: targetPhoneNumber,
)
.then((value) {
......@@ -127,6 +128,9 @@ class TransactionDetailViewModel extends RestfulApiViewModel {
if (product.previewFlashSale?.isFlashSalePrice == true && product.previewFlashSale?.id != null) {
body["flash_sale_id"] = product.previewFlashSale!.id;
}
if ((metaData ?? '').isNotEmpty) {
body["metadata"] = metaData;
}
final response = await client.getPreviewOrderInfo(body);
previewData.value = response.data;
} catch (error) {
......
......@@ -464,7 +464,7 @@ class _VoucherDetailScreenState extends BaseState<VoucherDetailScreen> with Basi
);
}
_handleContinueButtonAction() {
void _handleContinueButtonAction() {
final product = _viewModel.product.value;
if (product?.requireFormRegis == true) {
Get.toNamed(registerFormInputScreen, arguments: {"product": product});
......
......@@ -79,7 +79,7 @@ class VoucherDetailViewModel extends RestfulApiViewModel {
} finally {}
}
verifyOrderProduct(Function verified) async {
Future<void> verifyOrderProduct(Function verified) async {
final value = product.value;
var body = {"product_id": productId, "price": value?.amountToBePaid, "quantity": quantity.value};
if (value?.previewFlashSale?.isFlashSalePrice == true) {
......
......@@ -249,23 +249,23 @@ class _PaymentWebViewScreenState extends BaseState<PaymentWebViewScreen> with Ba
}
}
_onBackPressed() {
void _onBackPressed() {
final dataAlert = DataAlertModel(
title: "Huỷ đơn hàng?",
title: "Xác nhận huỷ thanh toán",
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});
},
text: "Tiếp tục thanh toán",
onPressed: () => Navigator.pop(context, false),
bgColor: BaseColor.primary500,
textColor: Colors.white,
),
AlertButton(
text: "Huỷ",
onPressed: () => Navigator.pop(context, false),
text: "Dừng thanh toán",
onPressed: () {
Get.offNamed(transactionHistoryDetailScreen, arguments: {"orderId": input.orderId ?? "", "canBack": false});
},
bgColor: Colors.white,
textColor: BaseColor.second500,
),
......
......@@ -16,6 +16,7 @@ import '../screen/electric_payment/electric_payment_history_screen.dart';
import '../screen/electric_payment/electric_payment_screen.dart';
import '../screen/game/game_cards/game_card_screen.dart';
import '../screen/game/game_tab_screen.dart';
import '../screen/health_book/health_book_screen.dart';
import '../screen/history_point/history_point_screen.dart';
import '../screen/history_point_cashback/history_point_cashback_screen.dart';
import '../screen/interested_categories/interestied_categories_screen.dart';
......@@ -90,6 +91,7 @@ const electricPaymentScreen = '/electricPaymentScreen';
const electricPaymentHistoryScreen = '/electricPaymentHistoryScreen';
const trafficServiceScreen = '/trafficServiceScreen';
const trafficServiceDetailScreen = '/trafficServiceDetailScreen';
const healthBookScreen = '/healthBookScreen';
const campaignSevenDayScreen = '/campaignSevenDayScreen';
const surveyQuestionScreen = '/surveyQuestionScreen';
const deviceManagerScreen = '/deviceManagerScreen';
......@@ -165,6 +167,7 @@ class RouterPage {
GetPage(name: historyPointScreen, page: () => HistoryPointScreen()),
GetPage(name: qrCodeScreen, page: () => QRCodeScreen()),
GetPage(name: myMobileCardDetailScreen, page: () => MyMobileCardDetailScreen()),
GetPage(name: healthBookScreen, page: () => HealthBookScreen()),
];
}
}
\ No newline at end of file
......@@ -24,9 +24,8 @@ class SupportButton extends StatelessWidget {
Get.to(() => const SupportScreen());
},
icon: const Icon(
Icons.headset_mic,
Icons.headset_rounded,
size: 18,
color: BaseColor.second600,
),
label: const Text("Hỗ trợ"),
style: TextButton.styleFrom(
......
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