Files
worker/lib/core/utils/formatters.dart
2025-11-07 11:52:06 +07:00

378 lines
11 KiB
Dart

/// Data Formatters for Vietnamese Locale
///
/// Provides formatting utilities for currency, dates, phone numbers,
/// and other data types commonly used in the app.
library;
import 'package:intl/intl.dart';
/// Currency formatter for Vietnamese Dong (VND)
class CurrencyFormatter {
CurrencyFormatter._();
/// Format amount as Vietnamese currency (e.g., "100,000 ₫")
static String format(double amount, {bool showSymbol = true}) {
final formatter = NumberFormat.currency(
locale: 'vi_VN',
symbol: showSymbol ? '' : '',
decimalDigits: 0,
);
return formatter.format(amount);
}
/// Format amount with custom precision
static String formatWithDecimals(
double amount, {
int decimalDigits = 2,
bool showSymbol = true,
}) {
final formatter = NumberFormat.currency(
locale: 'vi_VN',
symbol: showSymbol ? '' : '',
decimalDigits: decimalDigits,
);
return formatter.format(amount);
}
/// Format as compact currency (e.g., "1.5M ₫")
static String formatCompact(double amount, {bool showSymbol = true}) {
final formatter = NumberFormat.compactCurrency(
locale: 'vi_VN',
symbol: showSymbol ? '' : '',
decimalDigits: 1,
);
return formatter.format(amount);
}
/// Parse currency string to double
static double? parse(String value) {
try {
// Remove currency symbol and spaces
final cleaned = value.replaceAll(RegExp(r'[₫\s,]'), '');
return double.tryParse(cleaned);
} catch (e) {
return null;
}
}
}
/// Date and time formatter
class DateFormatter {
DateFormatter._();
/// Format date as "dd/MM/yyyy" (Vietnamese format)
static String formatDate(DateTime date) {
final formatter = DateFormat('dd/MM/yyyy', 'vi_VN');
return formatter.format(date);
}
/// Format date as "dd-MM-yyyy"
static String formatDateDash(DateTime date) {
final formatter = DateFormat('dd-MM-yyyy', 'vi_VN');
return formatter.format(date);
}
/// Format time as "HH:mm"
static String formatTime(DateTime date) {
final formatter = DateFormat('HH:mm', 'vi_VN');
return formatter.format(date);
}
/// Format date and time as "dd/MM/yyyy HH:mm"
static String formatDateTime(DateTime date) {
final formatter = DateFormat('dd/MM/yyyy HH:mm', 'vi_VN');
return formatter.format(date);
}
/// Format date as "dd/MM/yyyy lúc HH:mm"
static String formatDateTimeVN(DateTime date) {
final formatter = DateFormat('dd/MM/yyyy', 'vi_VN');
final timeFormatter = DateFormat('HH:mm', 'vi_VN');
return '${formatter.format(date)} lúc ${timeFormatter.format(date)}';
}
/// Format as relative time (e.g., "2 giờ trước")
static String formatRelative(DateTime date) {
final now = DateTime.now();
final difference = now.difference(date);
if (difference.inSeconds < 60) {
return 'Vừa xong';
} else if (difference.inMinutes < 60) {
return '${difference.inMinutes} phút trước';
} else if (difference.inHours < 24) {
return '${difference.inHours} giờ trước';
} else if (difference.inDays < 7) {
return '${difference.inDays} ngày trước';
} else if (difference.inDays < 30) {
final weeks = (difference.inDays / 7).floor();
return '$weeks tuần trước';
} else if (difference.inDays < 365) {
final months = (difference.inDays / 30).floor();
return '$months tháng trước';
} else {
final years = (difference.inDays / 365).floor();
return '$years năm trước';
}
}
/// Format as day of week (e.g., "Thứ Hai")
static String formatDayOfWeek(DateTime date) {
final formatter = DateFormat('EEEE', 'vi_VN');
return formatter.format(date);
}
/// Format as month and year (e.g., "Tháng 10 năm 2024")
static String formatMonthYear(DateTime date) {
final formatter = DateFormat('MMMM yyyy', 'vi_VN');
return formatter.format(date);
}
/// Format as full date with day of week (e.g., "Thứ Hai, 17/10/2024")
static String formatFullDate(DateTime date) {
final dayOfWeek = formatDayOfWeek(date);
final dateStr = formatDate(date);
return '$dayOfWeek, $dateStr';
}
/// Parse date string in format "dd/MM/yyyy"
static DateTime? parseDate(String dateStr) {
try {
final formatter = DateFormat('dd/MM/yyyy');
return formatter.parse(dateStr);
} catch (e) {
return null;
}
}
/// Parse datetime string in format "dd/MM/yyyy HH:mm"
static DateTime? parseDateTime(String dateTimeStr) {
try {
final formatter = DateFormat('dd/MM/yyyy HH:mm');
return formatter.parse(dateTimeStr);
} catch (e) {
return null;
}
}
}
/// Phone number formatter for Vietnamese phone numbers
class PhoneFormatter {
PhoneFormatter._();
/// Format phone number as "(0xxx) xxx xxx"
static String format(String phone) {
// Remove all non-digit characters
final cleaned = phone.replaceAll(RegExp(r'\D'), '');
if (cleaned.isEmpty) return '';
// Handle Vietnamese phone number formats
if (cleaned.startsWith('84')) {
// +84 format
final local = cleaned.substring(2);
if (local.length >= 9) {
return '(+84${local.substring(0, 2)}) ${local.substring(2, 5)} ${local.substring(5)}';
}
} else if (cleaned.startsWith('0')) {
// 0xxx format
if (cleaned.length >= 10) {
return '(${cleaned.substring(0, 4)}) ${cleaned.substring(4, 7)} ${cleaned.substring(7)}';
}
}
return phone; // Return original if format doesn't match
}
/// Format as international number (+84xxx xxx xxx)
static String formatInternational(String phone) {
final cleaned = phone.replaceAll(RegExp(r'\D'), '');
if (cleaned.isEmpty) return '';
if (cleaned.startsWith('0')) {
// Convert 0xxx to +84xxx
final local = cleaned.substring(1);
if (local.length >= 9) {
return '+84${local.substring(0, 2)} ${local.substring(2, 5)} ${local.substring(5)}';
}
} else if (cleaned.startsWith('84')) {
final local = cleaned.substring(2);
if (local.length >= 9) {
return '+84${local.substring(0, 2)} ${local.substring(2, 5)} ${local.substring(5)}';
}
}
return phone;
}
/// Remove formatting to get clean phone number
static String clean(String phone) {
return phone.replaceAll(RegExp(r'\D'), '');
}
/// Convert to E.164 format (+84xxxxxxxxx)
static String toE164(String phone) {
final cleaned = clean(phone);
if (cleaned.startsWith('0')) {
return '+84${cleaned.substring(1)}';
} else if (cleaned.startsWith('84')) {
return '+$cleaned';
} else if (cleaned.startsWith('+84')) {
return cleaned;
}
return '+84$cleaned';
}
/// Mask phone number (e.g., "0xxx xxx ***")
static String mask(String phone) {
final cleaned = clean(phone);
if (cleaned.length >= 10) {
return '${cleaned.substring(0, 4)} ${cleaned.substring(4, 7)} ***';
}
return phone;
}
}
/// Number formatter
class NumberFormatter {
NumberFormatter._();
/// Format number with thousand separators
static String format(num number, {int decimalDigits = 0}) {
final formatter = NumberFormat('#,###', 'vi_VN');
if (decimalDigits > 0) {
return formatter.format(number);
}
return formatter.format(number.round());
}
/// Format as percentage
static String formatPercentage(
double value, {
int decimalDigits = 0,
bool showSymbol = true,
}) {
final formatter = NumberFormat.percentPattern('vi_VN');
formatter.maximumFractionDigits = decimalDigits;
formatter.minimumFractionDigits = decimalDigits;
final result = formatter.format(value / 100);
return showSymbol ? result : result.replaceAll('%', '');
}
/// Format as compact number (e.g., "1.5K")
static String formatCompact(num number) {
final formatter = NumberFormat.compact(locale: 'vi_VN');
return formatter.format(number);
}
/// Format file size
static String formatBytes(int bytes, {int decimals = 2}) {
if (bytes <= 0) return '0 B';
const suffixes = ['B', 'KB', 'MB', 'GB', 'TB'];
final i = (bytes.bitLength - 1) ~/ 10;
final value = bytes / (1 << (i * 10));
return '${value.toStringAsFixed(decimals)} ${suffixes[i]}';
}
/// Format duration (e.g., "1:30:45")
static String formatDuration(Duration duration) {
final hours = duration.inHours;
final minutes = duration.inMinutes.remainder(60);
final seconds = duration.inSeconds.remainder(60);
if (hours > 0) {
return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
} else {
return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
}
}
}
/// Text formatter utilities
class TextFormatter {
TextFormatter._();
/// Capitalize first letter
static String capitalize(String text) {
if (text.isEmpty) return text;
return text[0].toUpperCase() + text.substring(1);
}
/// Capitalize each word
static String capitalizeWords(String text) {
if (text.isEmpty) return text;
return text.split(' ').map((word) => capitalize(word)).join(' ');
}
/// Truncate text with ellipsis
static String truncate(
String text,
int maxLength, {
String ellipsis = '...',
}) {
if (text.length <= maxLength) return text;
return text.substring(0, maxLength - ellipsis.length) + ellipsis;
}
/// Remove diacritics from Vietnamese text
static String removeDiacritics(String text) {
const withDiacritics =
'àáạảãâầấậẩẫăằắặẳẵèéẹẻẽêềếệểễìíịỉĩòóọỏõôồốộổỗơờớợởỡùúụủũưừứựửữỳýỵỷỹđ';
const withoutDiacritics =
'aaaaaaaaaaaaaaaaaeeeeeeeeeeeiiiiioooooooooooooooooouuuuuuuuuuuyyyyyd';
var result = text.toLowerCase();
for (var i = 0; i < withDiacritics.length; i++) {
result = result.replaceAll(withDiacritics[i], withoutDiacritics[i]);
}
return result;
}
/// Create URL-friendly slug
static String slugify(String text) {
var slug = removeDiacritics(text);
slug = slug.toLowerCase();
slug = slug.replaceAll(RegExp(r'[^\w\s-]'), '');
slug = slug.replaceAll(RegExp(r'[-\s]+'), '-');
return slug;
}
}
/// Loyalty tier formatter
class LoyaltyFormatter {
LoyaltyFormatter._();
/// Format tier name in Vietnamese
static String formatTier(String tier) {
switch (tier.toLowerCase()) {
case 'diamond':
return 'Kim Cương';
case 'platinum':
return 'Bạch Kim';
case 'gold':
return 'Vàng';
default:
return tier;
}
}
/// Format points with label
static String formatPoints(int points) {
return '${NumberFormatter.format(points)} điểm';
}
/// Format points progress (e.g., "1,200 / 5,000 điểm")
static String formatPointsProgress(int current, int target) {
return '${NumberFormatter.format(current)} / ${NumberFormatter.format(target)} điểm';
}
}