feat: 다국어 지원 및 다중 통화 환율 변환 기능 확대

- ExchangeRateService에 JPY, CNY 환율 지원 추가
- 구독 서비스별 다국어 표시 이름 지원
- 분석 화면 차트 및 UI/UX 개선
- 설정 화면 전면 리팩토링
- SMS 스캔 기능 사용성 개선
- 전체 앱 다국어 번역 확대

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-07-16 17:34:32 +09:00
parent 4d1c0f5dab
commit 0f0b02bf08
55 changed files with 4100 additions and 1197 deletions

View File

@@ -1,12 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:provider/provider.dart';
import '../../models/subscription_model.dart';
import '../../services/currency_util.dart';
import '../../providers/locale_provider.dart';
import '../../utils/haptic_feedback_helper.dart';
import '../../theme/app_colors.dart';
import '../glassmorphism_card.dart';
import '../themed_text.dart';
import '../../l10n/app_localizations.dart';
/// 총 지출 요약을 보여주는 카드 위젯
class TotalExpenseSummaryCard extends StatelessWidget {
@@ -23,6 +26,7 @@ class TotalExpenseSummaryCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final locale = context.watch<LocaleProvider>().locale.languageCode;
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
@@ -52,7 +56,7 @@ class TotalExpenseSummaryCard extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ThemedText.headline(
text: '총 지출 요약',
text: AppLocalizations.of(context).totalExpenseSummary,
style: const TextStyle(
fontSize: 18,
),
@@ -63,14 +67,14 @@ class TotalExpenseSummaryCard extends StatelessWidget {
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
onPressed: () async {
final totalExpenseText = CurrencyUtil.formatTotalAmount(totalExpense);
final totalExpenseText = CurrencyUtil.formatTotalAmountWithLocale(totalExpense, locale);
await Clipboard.setData(
ClipboardData(text: totalExpenseText));
HapticFeedbackHelper.lightImpact();
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('총 지출액이 복사되었습니다: $totalExpenseText'),
content: Text(AppLocalizations.of(context).totalExpenseCopied(totalExpenseText)),
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
@@ -89,7 +93,7 @@ class TotalExpenseSummaryCard extends StatelessWidget {
),
const SizedBox(height: 8),
ThemedText.subtitle(
text: '월 단위 총액',
text: AppLocalizations.of(context).monthlyTotalAmount,
style: const TextStyle(
fontSize: 14,
),
@@ -103,7 +107,7 @@ class TotalExpenseSummaryCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ThemedText.caption(
text: '총 지출',
text: AppLocalizations.of(context).totalExpense,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
@@ -111,7 +115,7 @@ class TotalExpenseSummaryCard extends StatelessWidget {
),
const SizedBox(height: 4),
ThemedText(
CurrencyUtil.formatTotalAmount(totalExpense),
CurrencyUtil.formatTotalAmountWithLocale(totalExpense, locale),
style: const TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
@@ -148,7 +152,7 @@ class TotalExpenseSummaryCard extends StatelessWidget {
CrossAxisAlignment.start,
children: [
ThemedText.caption(
text: '총 서비스',
text: AppLocalizations.of(context).totalServices,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
@@ -156,7 +160,7 @@ class TotalExpenseSummaryCard extends StatelessWidget {
),
const SizedBox(height: 2),
ThemedText(
'${subscriptions.length}',
AppLocalizations.of(context).subscriptionCount(subscriptions.length),
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
@@ -190,7 +194,7 @@ class TotalExpenseSummaryCard extends StatelessWidget {
CrossAxisAlignment.start,
children: [
ThemedText.caption(
text: '평균 요금',
text: AppLocalizations.of(context).averageCost,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
@@ -198,10 +202,11 @@ class TotalExpenseSummaryCard extends StatelessWidget {
),
const SizedBox(height: 2),
ThemedText(
CurrencyUtil.formatTotalAmount(
CurrencyUtil.formatTotalAmountWithLocale(
subscriptions.isEmpty
? 0
: totalExpense / subscriptions.length),
: totalExpense / subscriptions.length,
locale),
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,