Major UI/UX and architecture improvements

- Implemented new navigation system with NavigationProvider and route management
- Added adaptive theme system with ThemeProvider for better theme handling
- Introduced glassmorphism design elements (app bars, scaffolds, cards)
- Added advanced animations (spring animations, page transitions, staggered lists)
- Implemented performance optimizations (memory manager, lazy loading)
- Refactored Analysis screen into modular components
- Added floating navigation bar with haptic feedback
- Improved subscription cards with swipe actions
- Enhanced skeleton loading with better animations
- Added cached network image support
- Improved overall app architecture and code organization

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-07-10 18:36:57 +09:00
parent 8619e96739
commit 4731288622
55 changed files with 8219 additions and 2149 deletions

View File

@@ -0,0 +1,214 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'dart:math' as math;
import '../../services/currency_util.dart';
import '../glassmorphism_card.dart';
import '../themed_text.dart';
/// 월별 지출 현황을 차트로 보여주는 카드 위젯
class MonthlyExpenseChartCard extends StatelessWidget {
final List<Map<String, dynamic>> monthlyData;
final AnimationController animationController;
const MonthlyExpenseChartCard({
super.key,
required this.monthlyData,
required this.animationController,
});
// 월간 지출 차트 데이터
List<BarChartGroupData> _getMonthlyBarGroups() {
final List<BarChartGroupData> barGroups = [];
final calculatedMax = monthlyData.fold<double>(
0, (max, data) => math.max(max, data['totalExpense'] as double));
final maxAmount = calculatedMax > 0 ? calculatedMax : 100000.0; // 기본값 10만원
for (int i = 0; i < monthlyData.length; i++) {
final data = monthlyData[i];
barGroups.add(
BarChartGroupData(
x: i,
barRods: [
BarChartRodData(
toY: data['totalExpense'],
gradient: LinearGradient(
colors: [
const Color(0xFF3B82F6).withValues(alpha: 0.7),
const Color(0xFF60A5FA),
],
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
),
width: 18,
borderRadius: BorderRadius.circular(4),
backDrawRodData: BackgroundBarChartRodData(
show: true,
toY: maxAmount + (maxAmount * 0.1),
color: Colors.grey.withValues(alpha: 0.1),
),
),
],
),
);
}
return barGroups;
}
@override
Widget build(BuildContext context) {
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: FadeTransition(
opacity: CurvedAnimation(
parent: animationController,
curve: const Interval(0.4, 0.9, 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.4, 0.9, curve: Curves.easeOut),
)),
child: GlassmorphismCard(
blur: 10,
opacity: 0.1,
borderRadius: 16,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ThemedText.headline(
text: '월별 지출 현황',
style: const TextStyle(
fontSize: 18,
),
),
const SizedBox(height: 8),
ThemedText.subtitle(
text: '최근 6개월간 추이',
style: const TextStyle(
fontSize: 14,
),
),
const SizedBox(height: 20),
// 바 차트
AspectRatio(
aspectRatio: 1.6,
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
maxY: math.max(
monthlyData.fold<double>(
0,
(max, data) => math.max(
max, data['totalExpense'] as double)) *
1.2,
100000.0 // 최소값 10만원
),
barGroups: _getMonthlyBarGroups(),
gridData: FlGridData(
show: true,
drawVerticalLine: false,
horizontalInterval: math.max(
monthlyData.fold<double>(
0,
(max, data) => math.max(max,
data['totalExpense'] as double)) /
4,
25000.0 // 최소 간격 2.5만원
),
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey.withValues(alpha: 0.1),
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
return Padding(
padding: const EdgeInsets.only(top: 8),
child: ThemedText.caption(
text: monthlyData[value.toInt()]
['monthName'],
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
);
},
),
),
leftTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
borderData: FlBorderData(show: false),
barTouchData: BarTouchData(
enabled: true,
touchTooltipData: BarTouchTooltipData(
tooltipBgColor: Colors.blueGrey.shade800,
tooltipRoundedRadius: 8,
getTooltipItem:
(group, groupIndex, rod, rodIndex) {
return BarTooltipItem(
'${monthlyData[group.x]['monthName']}\n',
const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
children: [
TextSpan(
text: CurrencyUtil.formatTotalAmount(
monthlyData[group.x]['totalExpense']
as double),
style: const TextStyle(
color: Colors.yellow,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
);
},
),
),
),
),
),
const SizedBox(height: 16),
Center(
child: ThemedText.caption(
text: '월 구독 지출',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
),
),
),
),
);
}
}