/// 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'; } }