payment page
This commit is contained in:
@@ -5,7 +5,9 @@ library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/router/app_router.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
|
||||
/// Checkout Submit Button
|
||||
@@ -103,22 +105,38 @@ class CheckoutSubmitButton extends StatelessWidget {
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
|
||||
// Navigate back after a short delay
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Generate order ID (mock - replace with actual from backend)
|
||||
final orderId = 'DH${DateTime.now().millisecondsSinceEpoch.toString().substring(7)}';
|
||||
|
||||
// Show order success message
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Đặt hàng thành công!'),
|
||||
content: Text('Đặt hàng thành công! Chuyển đến thanh toán...'),
|
||||
backgroundColor: AppColors.success,
|
||||
duration: Duration(seconds: 2),
|
||||
duration: Duration(seconds: 1),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Navigate back after a short delay
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
});
|
||||
// Navigate to payment QR page after a short delay
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (context.mounted) {
|
||||
context.pushNamed(
|
||||
RouteNames.paymentQr,
|
||||
queryParameters: {
|
||||
'orderId': orderId,
|
||||
'amount': total.toString(),
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
605
lib/features/orders/presentation/pages/payment_qr_page.dart
Normal file
605
lib/features/orders/presentation/pages/payment_qr_page.dart
Normal file
@@ -0,0 +1,605 @@
|
||||
/// Payment QR Page
|
||||
///
|
||||
/// QR code payment screen with bank transfer information.
|
||||
/// Features:
|
||||
/// - Payment amount display with minimum payment warning
|
||||
/// - QR code for quick payment
|
||||
/// - Bank transfer information with copy buttons
|
||||
/// - Payment confirmation and proof upload buttons
|
||||
/// - Countdown timer for payment
|
||||
/// - Payment instructions modal
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
|
||||
/// Payment QR Page
|
||||
///
|
||||
/// Displays QR code and bank transfer information for payment.
|
||||
class PaymentQrPage extends HookConsumerWidget {
|
||||
final String orderId;
|
||||
final double amount;
|
||||
|
||||
const PaymentQrPage({
|
||||
super.key,
|
||||
required this.orderId,
|
||||
required this.amount,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
// Countdown timer (15 minutes = 900 seconds)
|
||||
final remainingSeconds = useState<int>(900);
|
||||
final timer = useRef<Timer?>(null);
|
||||
|
||||
// Start countdown timer
|
||||
useEffect(() {
|
||||
timer.value = Timer.periodic(const Duration(seconds: 1), (t) {
|
||||
if (remainingSeconds.value > 0) {
|
||||
remainingSeconds.value--;
|
||||
} else {
|
||||
t.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
return () {
|
||||
timer.value?.cancel();
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Format timer display
|
||||
final minutes = remainingSeconds.value ~/ 60;
|
||||
final seconds = remainingSeconds.value % 60;
|
||||
final timerDisplay =
|
||||
'${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF4F6F8),
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.black),
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
title: const Text(
|
||||
'Thanh toán',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.info_outline, color: Colors.black),
|
||||
onPressed: () => _showInfoDialog(context),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Payment Amount Card
|
||||
_buildAmountCard(amount),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// QR Code Card
|
||||
_buildQrCodeCard(amount, orderId),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Bank Transfer Info Card
|
||||
_buildBankInfoCard(context, orderId),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Action Buttons
|
||||
_buildActionButtons(context),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Timer
|
||||
_buildTimer(timerDisplay, remainingSeconds.value),
|
||||
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build payment amount card
|
||||
Widget _buildAmountCard(double amount) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
||||
padding: const EdgeInsets.all(AppSpacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(AppRadius.card),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
_formatCurrency(amount),
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.primaryBlue,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'Số tiền cần thanh toán',
|
||||
style: TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFFF8E1),
|
||||
borderRadius: BorderRadius.circular(AppRadius.card),
|
||||
border: Border.all(color: const Color(0xFFFFD54F)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.info, color: AppColors.warning, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Thanh toán không dưới 20%',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.orange[900],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build QR code card
|
||||
Widget _buildQrCodeCard(double amount, String orderId) {
|
||||
// Generate QR code data URL
|
||||
final qrData = Uri.encodeComponent(
|
||||
'https://eurotile.com/payment/$orderId?amount=$amount');
|
||||
final qrUrl =
|
||||
'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=$qrData';
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
||||
padding: const EdgeInsets.all(AppSpacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(AppRadius.card),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text(
|
||||
'Quét mã QR để thanh toán',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF212121),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Container(
|
||||
width: 220,
|
||||
height: 220,
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(AppRadius.card),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
),
|
||||
child: Image.network(
|
||||
qrUrl,
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return const Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.qr_code, size: 80, color: AppColors.grey500),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'Không thể tải mã QR',
|
||||
style: TextStyle(fontSize: 12, color: AppColors.grey500),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
const Text(
|
||||
'Quét mã QR bằng ứng dụng ngân hàng để thanh toán nhanh chóng',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build bank transfer info card
|
||||
Widget _buildBankInfoCard(BuildContext context, String orderId) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
||||
padding: const EdgeInsets.all(AppSpacing.md),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(AppRadius.card),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Thông tin chuyển khoản',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF212121),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Bank Name
|
||||
_buildInfoRow(
|
||||
context: context,
|
||||
label: 'Ngân hàng:',
|
||||
value: 'BIDV',
|
||||
),
|
||||
|
||||
const Divider(height: 24),
|
||||
|
||||
// Account Number
|
||||
_buildInfoRow(
|
||||
context: context,
|
||||
label: 'Số tài khoản:',
|
||||
value: '19036810704016',
|
||||
),
|
||||
|
||||
const Divider(height: 24),
|
||||
|
||||
// Account Holder
|
||||
_buildInfoRow(
|
||||
context: context,
|
||||
label: 'Chủ tài khoản:',
|
||||
value: 'CÔNG TY EUROTILE',
|
||||
),
|
||||
|
||||
const Divider(height: 24),
|
||||
|
||||
// Transfer Content
|
||||
_buildInfoRow(
|
||||
context: context,
|
||||
label: 'Nội dung:',
|
||||
value: '$orderId La Nguyen Quynh',
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
|
||||
// Note
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFE3F2FD),
|
||||
borderRadius: BorderRadius.circular(AppRadius.card),
|
||||
border: Border.all(color: const Color(0xFF90CAF9)),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(Icons.lightbulb_outline,
|
||||
color: AppColors.primaryBlue, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: Color(0xFF1565C0),
|
||||
height: 1.4,
|
||||
),
|
||||
children: [
|
||||
const TextSpan(
|
||||
text: 'Lưu ý: ',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const TextSpan(
|
||||
text:
|
||||
'Vui lòng ghi đúng nội dung chuyển khoản để đơn hàng được xử lý nhanh chóng.',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build info row with copy button
|
||||
Widget _buildInfoRow({
|
||||
required BuildContext context,
|
||||
required String label,
|
||||
required String value,
|
||||
}) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF212121),
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.copy, size: 20, color: AppColors.primaryBlue),
|
||||
onPressed: () => _copyToClipboard(context, value),
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Build action buttons
|
||||
Widget _buildActionButtons(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
||||
child: Row(
|
||||
children: [
|
||||
// Confirmed Payment Button
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => _confirmPayment(context),
|
||||
icon: const Icon(Icons.check, size: 20),
|
||||
label: const Text(
|
||||
'Đã thanh toán',
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
|
||||
),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: AppColors.primaryBlue,
|
||||
side: const BorderSide(color: AppColors.primaryBlue, width: 1.5),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.button),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
|
||||
// Upload Proof Button
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () => _uploadProof(context),
|
||||
icon: const Icon(Icons.camera_alt, size: 20),
|
||||
label: const Text(
|
||||
'Upload bill',
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryBlue,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.button),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build countdown timer
|
||||
Widget _buildTimer(String timerDisplay, int remainingSeconds) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(AppRadius.card),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.schedule, size: 18, color: AppColors.grey500),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'Thời gian thanh toán: ',
|
||||
style: TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
),
|
||||
Text(
|
||||
timerDisplay,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: remainingSeconds < 300
|
||||
? AppColors.danger
|
||||
: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Copy text to clipboard
|
||||
void _copyToClipboard(BuildContext context, String text) {
|
||||
Clipboard.setData(ClipboardData(text: text));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Đã sao chép: $text'),
|
||||
duration: const Duration(seconds: 1),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Show payment info dialog
|
||||
void _showInfoDialog(BuildContext context) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Hướng dẫn thanh toán'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Đây là nội dung hướng dẫn sử dụng cho tính năng Thanh toán:',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildInfoItem('Quét mã QR bằng app ngân hàng hoặc ví điện tử'),
|
||||
_buildInfoItem('Chuyển khoản theo thông tin được cung cấp'),
|
||||
_buildInfoItem('Ghi đúng nội dung chuyển khoản'),
|
||||
_buildInfoItem('Upload hóa đơn sau khi chuyển khoản'),
|
||||
_buildInfoItem('Thanh toán tối thiểu 20% giá trị đơn hàng'),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Đóng'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build info item for dialog
|
||||
Widget _buildInfoItem(String text) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('• ', style: TextStyle(fontSize: 14)),
|
||||
Expanded(
|
||||
child: Text(text, style: const TextStyle(fontSize: 14)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Confirm payment
|
||||
void _confirmPayment(BuildContext context) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Xác nhận thanh toán'),
|
||||
content: const Text(
|
||||
'Bạn đã hoàn tất thanh toán cho đơn hàng này?',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Chưa'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Đã xác nhận thanh toán!'),
|
||||
backgroundColor: AppColors.success,
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
// Navigate back after delay
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
});
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryBlue,
|
||||
),
|
||||
child: const Text('Đã thanh toán'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Upload payment proof
|
||||
void _uploadProof(BuildContext context) {
|
||||
// TODO: Implement image picker and upload
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Tính năng upload bill đang được phát triển'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Format currency
|
||||
String _formatCurrency(double amount) {
|
||||
return '${amount.toStringAsFixed(0).replaceAllMapped(
|
||||
RegExp(r'(\d)(?=(\d{3})+(?!\d))'),
|
||||
(Match m) => '${m[1]}.',
|
||||
)}₫';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user