- billing_cycle_selector, category_selector, currency_selector 컴포넌트 분리 - 구독 카드 클릭 이슈 해결을 위한 리팩토링 - SMS 스캔 화면 UI/UX 개선 및 기능 강화 - 상세 화면 컨트롤러 로직 개선 - 알림 서비스 및 구독 URL 매칭 기능 추가 - CLAUDE.md 프로젝트 가이드라인 대폭 확장 - 전반적인 코드 구조 개선 및 타입 안정성 강화 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
161 lines
4.5 KiB
Dart
161 lines
4.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import '../providers/subscription_provider.dart';
|
|
import '../widgets/native_ad_widget.dart';
|
|
import '../widgets/analysis/analysis_screen_spacer.dart';
|
|
import '../widgets/analysis/subscription_pie_chart_card.dart';
|
|
import '../widgets/analysis/total_expense_summary_card.dart';
|
|
import '../widgets/analysis/monthly_expense_chart_card.dart';
|
|
import '../widgets/analysis/event_analysis_card.dart';
|
|
|
|
class AnalysisScreen extends StatefulWidget {
|
|
const AnalysisScreen({super.key});
|
|
|
|
@override
|
|
State<AnalysisScreen> createState() => _AnalysisScreenState();
|
|
}
|
|
|
|
class _AnalysisScreenState extends State<AnalysisScreen>
|
|
with TickerProviderStateMixin {
|
|
late AnimationController _animationController;
|
|
late ScrollController _scrollController;
|
|
|
|
double _totalExpense = 0;
|
|
List<Map<String, dynamic>> _monthlyData = [];
|
|
int _touchedIndex = -1;
|
|
bool _isLoading = true;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_animationController = AnimationController(
|
|
duration: const Duration(milliseconds: 1500),
|
|
vsync: this,
|
|
);
|
|
_scrollController = ScrollController();
|
|
_loadData();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_animationController.dispose();
|
|
_scrollController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _loadData() async {
|
|
setState(() {
|
|
_isLoading = true;
|
|
});
|
|
|
|
final provider = Provider.of<SubscriptionProvider>(context, listen: false);
|
|
|
|
// 총 지출 계산
|
|
_totalExpense = await provider.calculateTotalExpense();
|
|
|
|
// 월별 데이터 계산
|
|
_monthlyData = await provider.getMonthlyExpenseData();
|
|
|
|
setState(() {
|
|
_isLoading = false;
|
|
});
|
|
|
|
// 데이터 로드 완료 후 애니메이션 시작
|
|
_animationController.forward();
|
|
}
|
|
|
|
Widget _buildAnimatedAd() {
|
|
return FadeTransition(
|
|
opacity: CurvedAnimation(
|
|
parent: _animationController,
|
|
curve: const Interval(0.0, 0.5, curve: Curves.easeOut),
|
|
),
|
|
child: SlideTransition(
|
|
position: Tween<Offset>(
|
|
begin: const Offset(0, 0.2),
|
|
end: Offset.zero,
|
|
).animate(CurvedAnimation(
|
|
parent: _animationController,
|
|
curve: const Interval(0.0, 0.5, curve: Curves.easeOut),
|
|
)),
|
|
child: const NativeAdWidget(key: ValueKey('analysis_ad')),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Consumer<SubscriptionProvider>(
|
|
builder: (context, provider, child) {
|
|
final subscriptions = provider.subscriptions;
|
|
|
|
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: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: _buildAnimatedAd(),
|
|
),
|
|
),
|
|
|
|
const AnalysisScreenSpacer(),
|
|
|
|
// 1. 구독 비율 파이 차트
|
|
SubscriptionPieChartCard(
|
|
subscriptions: subscriptions,
|
|
animationController: _animationController,
|
|
touchedIndex: _touchedIndex,
|
|
onPieTouch: (index) => setState(() => _touchedIndex = index),
|
|
),
|
|
|
|
const AnalysisScreenSpacer(),
|
|
|
|
// 2. 총 지출 요약 카드
|
|
TotalExpenseSummaryCard(
|
|
subscriptions: subscriptions,
|
|
totalExpense: _totalExpense,
|
|
animationController: _animationController,
|
|
),
|
|
|
|
const AnalysisScreenSpacer(),
|
|
|
|
// 3. 월별 지출 차트
|
|
MonthlyExpenseChartCard(
|
|
monthlyData: _monthlyData,
|
|
animationController: _animationController,
|
|
),
|
|
|
|
const AnalysisScreenSpacer(),
|
|
|
|
// 4. 이벤트 분석
|
|
EventAnalysisCard(
|
|
animationController: _animationController,
|
|
),
|
|
|
|
// FloatingNavigationBar를 위한 충분한 하단 여백
|
|
SliverToBoxAdapter(
|
|
child: SizedBox(
|
|
height: 120 + MediaQuery.of(context).padding.bottom,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
} |