Files
submanager/lib/services/currency_util.dart
2026-01-14 00:18:12 +09:00

313 lines
10 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:intl/intl.dart';
import '../models/subscription_model.dart';
import '../utils/billing_cost_util.dart';
import 'exchange_rate_service.dart';
import 'cache_manager.dart';
/// 통화 단위 변환 및 포맷팅을 위한 유틸리티 클래스
class CurrencyUtil {
static final ExchangeRateService _exchangeRateService = ExchangeRateService();
static final SimpleCacheManager<String> _fmtCache =
SimpleCacheManager<String>(
maxEntries: 256,
maxBytes: 256 * 1024,
ttl: const Duration(minutes: 15),
);
/// 언어에 따른 기본 통화 반환
static String getDefaultCurrency(String locale) {
switch (locale) {
case 'ko':
return 'KRW';
case 'ja':
return 'JPY';
case 'zh':
return 'CNY';
case 'en':
default:
return 'USD';
}
}
/// 언어에 따른 서브 통화 반환 (영어 제외 모두 USD)
static String? getSecondaryCurrency(String locale, String? selectedCurrency) {
if (locale == 'en' && selectedCurrency == 'KRW') {
return 'KRW';
}
return locale != 'en' ? 'USD' : null;
}
/// 통화 기호 반환
static String getCurrencySymbol(String currency) {
switch (currency) {
case 'KRW':
return '';
case 'USD':
return '\$';
case 'JPY':
return '¥';
case 'CNY':
return '¥';
default:
return currency;
}
}
/// 통화별 locale 반환
static String _getLocaleForCurrency(String currency) {
switch (currency) {
case 'KRW':
return 'ko_KR';
case 'USD':
return 'en_US';
case 'JPY':
return 'ja_JP';
case 'CNY':
return 'zh_CN';
default:
return 'en_US';
}
}
/// 단일 통화 포맷팅
static String _formatSingleCurrency(double amount, String currency) {
final locale = _getLocaleForCurrency(currency);
final symbol = getCurrencySymbol(currency);
final decimals = (currency == 'KRW' || currency == 'JPY') ? 0 : 2;
return NumberFormat.currency(
locale: locale,
symbol: symbol,
decimalDigits: decimals,
).format(amount);
}
/// 금액 포맷팅 (기본 통화 + 서브 통화)
static Future<String> formatAmountWithLocale(
double amount,
String currency,
String locale,
) async {
// 캐시 조회
final decimals = (currency == 'KRW' || currency == 'JPY') ? 0 : 2;
final key = 'fmt:$locale:$currency:${amount.toStringAsFixed(decimals)}';
final cached = _fmtCache.get(key);
if (cached != null) return cached;
final defaultCurrency = getDefaultCurrency(locale);
// 입력 통화가 기본 통화인 경우
if (currency == defaultCurrency) {
final result = _formatSingleCurrency(amount, currency);
_fmtCache.set(key, result, size: result.length);
return result;
}
// USD 입력인 경우 - 기본 통화로 변환하여 표시
if (currency == 'USD' && defaultCurrency != 'USD') {
final convertedAmount = await _exchangeRateService.convertUsdToTarget(
amount, defaultCurrency);
if (convertedAmount != null) {
final primaryFormatted =
_formatSingleCurrency(convertedAmount, defaultCurrency);
final usdFormatted = _formatSingleCurrency(amount, 'USD');
final result = '$primaryFormatted ($usdFormatted)';
_fmtCache.set(key, result, size: result.length);
return result;
}
}
// 영어 사용자가 KRW 선택한 경우
if (locale == 'en' && currency == 'KRW') {
final result = _formatSingleCurrency(amount, currency);
_fmtCache.set(key, result, size: result.length);
return result;
}
// 기타 통화 입력인 경우
final result = _formatSingleCurrency(amount, currency);
_fmtCache.set(key, result, size: result.length);
return result;
}
/// 구독 목록의 이번 달 총 비용을 계산 (언어별 기본 통화로)
/// 이번 달에 결제가 발생하는 구독만 포함하며, 실제 결제 금액을 사용
static Future<double> calculateTotalMonthlyExpenseInDefaultCurrency(
List<SubscriptionModel> subscriptions,
String locale,
) async {
final defaultCurrency = getDefaultCurrency(locale);
double total = 0.0;
final now = DateTime.now();
final currentYear = now.year;
final currentMonth = now.month;
for (var subscription in subscriptions) {
// 이번 달에 결제가 발생하는지 확인
final hasBilling = BillingCostUtil.hasBillingInMonth(
subscription.nextBillingDate,
subscription.billingCycle,
currentYear,
currentMonth,
);
if (!hasBilling) continue;
// 실제 결제 금액으로 역변환
final actualPrice = BillingCostUtil.convertFromMonthlyCost(
subscription.currentPrice,
subscription.billingCycle,
);
final converted = await _exchangeRateService.convertBetweenCurrencies(
actualPrice,
subscription.currency,
defaultCurrency,
);
total += converted ?? actualPrice;
}
return total;
}
/// 구독 목록의 총 월 비용을 계산 (원화로 환산, 이벤트 가격 반영) - 기존 호환성 유지
static Future<double> calculateTotalMonthlyExpense(
List<SubscriptionModel> subscriptions) async {
return calculateTotalMonthlyExpenseInDefaultCurrency(subscriptions, 'ko');
}
/// 구독 목록의 예상 연간 총 비용을 계산 (언어별 기본 통화로)
/// 모든 구독의 연간 비용을 합산 (월 환산 비용 × 12)
static Future<double> calculateTotalAnnualExpenseInDefaultCurrency(
List<SubscriptionModel> subscriptions,
String locale,
) async {
final defaultCurrency = getDefaultCurrency(locale);
double total = 0.0;
for (var subscription in subscriptions) {
// 월 환산 비용 × 12 = 연간 비용
final annualPrice = subscription.currentPrice * 12;
final converted = await _exchangeRateService.convertBetweenCurrencies(
annualPrice,
subscription.currency,
defaultCurrency,
);
total += converted ?? annualPrice;
}
return total;
}
/// 구독의 월 비용을 표시 형식에 맞게 변환 (언어별 통화)
static Future<String> formatSubscriptionAmountWithLocale(
SubscriptionModel subscription, String locale) async {
// 월 환산 금액을 실제 결제 금액으로 역변환
final price = BillingCostUtil.convertFromMonthlyCost(
subscription.currentPrice,
subscription.billingCycle,
);
// 구독 단위 캐시 키 (통화/가격/locale + id + billingCycle)
final decimals =
(subscription.currency == 'KRW' || subscription.currency == 'JPY')
? 0
: 2;
final key =
'subfmt:$locale:${subscription.currency}:${price.toStringAsFixed(decimals)}:${subscription.id}:${subscription.billingCycle}';
final cached = _fmtCache.get(key);
if (cached != null) return cached;
final result =
await formatAmountWithLocale(price, subscription.currency, locale);
_fmtCache.set(key, result, size: result.length);
return result;
}
/// 구독의 월 비용을 표시 형식에 맞게 변환 (원화 변환 포함, 이벤트 가격 반영) - 기존 호환성 유지
static Future<String> formatSubscriptionAmount(
SubscriptionModel subscription) async {
return formatSubscriptionAmountWithLocale(subscription, 'ko');
}
/// 총액을 언어별 기본 통화로 표시
static String formatTotalAmountWithLocale(double amount, String locale) {
final defaultCurrency = getDefaultCurrency(locale);
return _formatSingleCurrency(amount, defaultCurrency);
}
/// 총액을 원화로 표시 - 기존 호환성 유지
static String formatTotalAmount(double amount) {
return formatTotalAmountWithLocale(amount, 'ko');
}
/// 환율 정보 텍스트 가져오기
static Future<String> getExchangeRateInfo() {
return _exchangeRateService.getFormattedExchangeRateInfo();
}
/// 언어별 환율 정보 텍스트 가져오기
static Future<String> getExchangeRateInfoForLocale(String locale) {
return _exchangeRateService.getFormattedExchangeRateInfoForLocale(locale);
}
/// 이벤트로 인한 총 절약액 계산 (언어별 기본 통화로)
static Future<double> calculateTotalEventSavingsInDefaultCurrency(
List<SubscriptionModel> subscriptions, String locale) async {
final defaultCurrency = getDefaultCurrency(locale);
double total = 0.0;
for (var subscription in subscriptions) {
if (subscription.isCurrentlyInEvent) {
final savings = subscription.eventSavings;
final converted = await _exchangeRateService.convertBetweenCurrencies(
savings,
subscription.currency,
defaultCurrency,
);
total += converted ?? savings;
}
}
return total;
}
/// 이벤트로 인한 총 절약액 계산 (원화로 환산) - 기존 호환성 유지
static Future<double> calculateTotalEventSavings(
List<SubscriptionModel> subscriptions) async {
return calculateTotalEventSavingsInDefaultCurrency(subscriptions, 'ko');
}
/// 이벤트 절약액을 표시 형식에 맞게 변환 (언어별)
static Future<String> formatEventSavingsWithLocale(
SubscriptionModel subscription, String locale) async {
if (!subscription.isCurrentlyInEvent) {
return '';
}
final savings = subscription.eventSavings;
return formatAmountWithLocale(savings, subscription.currency, locale);
}
/// 이벤트 절약액을 표시 형식에 맞게 변환 - 기존 호환성 유지
static Future<String> formatEventSavings(
SubscriptionModel subscription) async {
return formatEventSavingsWithLocale(subscription, 'ko');
}
/// 금액과 통화를 받아 포맷팅하여 반환 (언어별)
static Future<String> formatAmountWithCurrencyAndLocale(
double amount, String currency, String locale) async {
return formatAmountWithLocale(amount, currency, locale);
}
/// 금액과 통화를 받아 포맷팅하여 반환 - 기존 호환성 유지
static Future<String> formatAmount(double amount, String currency) async {
return formatAmountWithCurrencyAndLocale(amount, currency, 'ko');
}
}