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:
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/subscription_provider.dart';
|
||||
import '../providers/locale_provider.dart';
|
||||
import '../widgets/native_ad_widget.dart';
|
||||
import '../widgets/analysis/analysis_screen_spacer.dart';
|
||||
import '../widgets/analysis/subscription_pie_chart_card.dart';
|
||||
@@ -22,8 +23,8 @@ class _AnalysisScreenState extends State<AnalysisScreen>
|
||||
|
||||
double _totalExpense = 0;
|
||||
List<Map<String, dynamic>> _monthlyData = [];
|
||||
int _touchedIndex = -1;
|
||||
bool _isLoading = true;
|
||||
String _lastDataHash = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -36,6 +37,23 @@ class _AnalysisScreenState extends State<AnalysisScreen>
|
||||
_loadData();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
// Provider 변경 감지
|
||||
final provider = Provider.of<SubscriptionProvider>(context);
|
||||
final currentHash = _calculateDataHash(provider);
|
||||
|
||||
debugPrint('[AnalysisScreen] didChangeDependencies: '
|
||||
'현재 해시=$currentHash, 이전 해시=$_lastDataHash, 로딩중=$_isLoading');
|
||||
|
||||
// 데이터가 변경되었고 현재 로딩 중이 아닌 경우에만 리로드
|
||||
if (currentHash != _lastDataHash && !_isLoading && _lastDataHash.isNotEmpty) {
|
||||
debugPrint('[AnalysisScreen] 데이터 변경 감지됨, 리로드 시작');
|
||||
_loadData();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
@@ -43,24 +61,50 @@ class _AnalysisScreenState extends State<AnalysisScreen>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// 구독 데이터의 해시값을 계산하여 변경 감지
|
||||
String _calculateDataHash(SubscriptionProvider provider) {
|
||||
final subscriptions = provider.subscriptions;
|
||||
final buffer = StringBuffer();
|
||||
|
||||
buffer.write(subscriptions.length);
|
||||
buffer.write('_');
|
||||
buffer.write(provider.totalMonthlyExpense.toStringAsFixed(2));
|
||||
|
||||
for (final sub in subscriptions) {
|
||||
buffer.write('_${sub.id}_${sub.currentPrice.toStringAsFixed(2)}_${sub.currency}');
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
Future<void> _loadData() async {
|
||||
debugPrint('[AnalysisScreen] _loadData 호출됨');
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
final provider = Provider.of<SubscriptionProvider>(context, listen: false);
|
||||
final localeProvider = Provider.of<LocaleProvider>(context, listen: false);
|
||||
final locale = localeProvider.locale.languageCode;
|
||||
|
||||
// 총 지출 계산
|
||||
_totalExpense = await provider.calculateTotalExpense();
|
||||
// 총 지출 계산 (로케일별 기본 통화로 환산)
|
||||
_totalExpense = await provider.calculateTotalExpense(locale: locale);
|
||||
debugPrint('[AnalysisScreen] 총 지출 계산 완료: $_totalExpense');
|
||||
|
||||
// 월별 데이터 계산
|
||||
_monthlyData = await provider.getMonthlyExpenseData();
|
||||
// 월별 데이터 계산 (로케일별 기본 통화로 환산)
|
||||
_monthlyData = await provider.getMonthlyExpenseData(locale: locale);
|
||||
debugPrint('[AnalysisScreen] 월별 데이터 계산 완료: ${_monthlyData.length}개월');
|
||||
|
||||
// 현재 데이터 해시값 저장
|
||||
_lastDataHash = _calculateDataHash(provider);
|
||||
debugPrint('[AnalysisScreen] 데이터 해시값 저장: $_lastDataHash');
|
||||
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
|
||||
// 데이터 로드 완료 후 애니메이션 시작
|
||||
_animationController.reset();
|
||||
_animationController.forward();
|
||||
}
|
||||
|
||||
@@ -85,74 +129,72 @@ class _AnalysisScreenState extends State<AnalysisScreen>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<SubscriptionProvider>(
|
||||
builder: (context, provider, child) {
|
||||
final subscriptions = provider.subscriptions;
|
||||
// Provider를 직접 사용하여 변경 감지
|
||||
final provider = Provider.of<SubscriptionProvider>(context);
|
||||
final subscriptions = provider.subscriptions;
|
||||
|
||||
if (_isLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
if (_isLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
return CustomScrollView(
|
||||
controller: _scrollController,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
slivers: <Widget>[
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: kToolbarHeight + MediaQuery.of(context).padding.top,
|
||||
),
|
||||
),
|
||||
|
||||
// 네이티브 광고 위젯
|
||||
SliverToBoxAdapter(
|
||||
child: _buildAnimatedAd(),
|
||||
),
|
||||
return CustomScrollView(
|
||||
controller: _scrollController,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
slivers: <Widget>[
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: kToolbarHeight + MediaQuery.of(context).padding.top,
|
||||
),
|
||||
),
|
||||
|
||||
// 네이티브 광고 위젯
|
||||
SliverToBoxAdapter(
|
||||
child: _buildAnimatedAd(),
|
||||
),
|
||||
|
||||
const AnalysisScreenSpacer(),
|
||||
const AnalysisScreenSpacer(),
|
||||
|
||||
// 1. 구독 비율 파이 차트
|
||||
SubscriptionPieChartCard(
|
||||
subscriptions: subscriptions,
|
||||
animationController: _animationController,
|
||||
touchedIndex: _touchedIndex,
|
||||
onPieTouch: (index) => setState(() => _touchedIndex = index),
|
||||
),
|
||||
// 1. 구독 비율 파이 차트
|
||||
SubscriptionPieChartCard(
|
||||
subscriptions: subscriptions,
|
||||
animationController: _animationController,
|
||||
),
|
||||
|
||||
const AnalysisScreenSpacer(),
|
||||
const AnalysisScreenSpacer(),
|
||||
|
||||
// 2. 총 지출 요약 카드
|
||||
TotalExpenseSummaryCard(
|
||||
subscriptions: subscriptions,
|
||||
totalExpense: _totalExpense,
|
||||
animationController: _animationController,
|
||||
),
|
||||
// 2. 총 지출 요약 카드
|
||||
TotalExpenseSummaryCard(
|
||||
key: ValueKey('total_expense_${_lastDataHash}'),
|
||||
subscriptions: subscriptions,
|
||||
totalExpense: _totalExpense,
|
||||
animationController: _animationController,
|
||||
),
|
||||
|
||||
const AnalysisScreenSpacer(),
|
||||
const AnalysisScreenSpacer(),
|
||||
|
||||
// 3. 월별 지출 차트
|
||||
MonthlyExpenseChartCard(
|
||||
monthlyData: _monthlyData,
|
||||
animationController: _animationController,
|
||||
),
|
||||
// 3. 월별 지출 차트
|
||||
MonthlyExpenseChartCard(
|
||||
key: ValueKey('monthly_expense_${_lastDataHash}'),
|
||||
monthlyData: _monthlyData,
|
||||
animationController: _animationController,
|
||||
),
|
||||
|
||||
const AnalysisScreenSpacer(),
|
||||
const AnalysisScreenSpacer(),
|
||||
|
||||
// 4. 이벤트 분석
|
||||
EventAnalysisCard(
|
||||
animationController: _animationController,
|
||||
),
|
||||
// 4. 이벤트 분석
|
||||
EventAnalysisCard(
|
||||
animationController: _animationController,
|
||||
),
|
||||
|
||||
// FloatingNavigationBar를 위한 충분한 하단 여백
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 120 + MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
// FloatingNavigationBar를 위한 충분한 하단 여백
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 120 + MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user