perf(app): 초기화 병렬 처리 및 UI 개선

## 성능 최적화

### main.dart
- 앱 초기화 병렬 처리 (Future.wait 활용)
- 광고 SDK, Hive 초기화 동시 실행
- Hive Box 오픈 병렬 처리
- 코드 구조화 (_initializeHive, _initializeNotifications)

### visit_provider.dart
- allLastVisitDatesProvider 추가
- 리스트 화면에서 N+1 쿼리 방지
- 모든 맛집의 마지막 방문일 일괄 조회

## UI 개선

### 각 화면 리팩토링
- AppDimensions 상수 적용
- 스켈레톤 로더 적용
- 코드 정리 및 일관성 개선
This commit is contained in:
JiWoong Sul
2026-01-12 15:16:05 +09:00
parent 21941443ee
commit 6f45c7b456
8 changed files with 252 additions and 205 deletions

View File

@@ -15,10 +15,8 @@ class SplashScreen extends StatefulWidget {
}
class _SplashScreenState extends State<SplashScreen>
with TickerProviderStateMixin {
late List<AnimationController> _foodControllers;
late AnimationController _questionMarkController;
late AnimationController _centerIconController;
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
List<Offset>? _iconPositions;
Size? _lastScreenSize;
@@ -42,24 +40,9 @@ class _SplashScreenState extends State<SplashScreen>
}
void _initializeAnimations() {
// 음식 아이콘 애니메이션 (여러 개)
_foodControllers = List.generate(
foodIcons.length,
(index) => AnimationController(
duration: Duration(seconds: 2 + index % 3),
vsync: this,
)..repeat(reverse: true),
);
// 물음표 애니메이션
_questionMarkController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
)..repeat();
// 중앙 아이콘 애니메이션
_centerIconController = AnimationController(
duration: const Duration(seconds: 1),
// 단일 컨트롤러로 모든 애니메이션 제어 (메모리 최적화)
_animationController = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
}
@@ -142,9 +125,9 @@ class _SplashScreenState extends State<SplashScreen>
children: [
// 선택 아이콘
ScaleTransition(
scale: Tween(begin: 0.8, end: 1.2).animate(
scale: Tween(begin: 0.9, end: 1.1).animate(
CurvedAnimation(
parent: _centerIconController,
parent: _animationController,
curve: Curves.easeInOut,
),
),
@@ -164,11 +147,11 @@ class _SplashScreenState extends State<SplashScreen>
children: [
Text('오늘 뭐 먹Z', style: AppTypography.heading1(isDark)),
AnimatedBuilder(
animation: _questionMarkController,
animation: _animationController,
builder: (context, child) {
final questionMarks =
'?' *
(((_questionMarkController.value * 3).floor() % 3) +
(((_animationController.value * 3).floor() % 3) +
1);
return Text(
questionMarks,
@@ -217,29 +200,30 @@ class _SplashScreenState extends State<SplashScreen>
return List.generate(foodIcons.length, (index) {
final position = _iconPositions![index];
// 각 아이콘마다 위상(phase)을 다르게 적용
final phase = index / foodIcons.length;
return Positioned(
left: position.dx,
top: position.dy,
child: FadeTransition(
opacity: Tween(begin: 0.2, end: 0.8).animate(
CurvedAnimation(
parent: _foodControllers[index],
curve: Curves.easeInOut,
),
),
child: ScaleTransition(
scale: Tween(begin: 0.5, end: 1.5).animate(
CurvedAnimation(
parent: _foodControllers[index],
curve: Curves.easeInOut,
child: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
// 위상 차이로 각 아이콘이 다른 타이밍에 애니메이션
final value =
((_animationController.value + phase) % 1.0 - 0.5).abs() * 2;
return Opacity(
opacity: 0.2 + value * 0.4,
child: Transform.scale(
scale: 0.7 + value * 0.5,
child: child,
),
),
child: Icon(
foodIcons[index],
size: 40,
color: AppColors.lightPrimary.withOpacity(0.3),
),
);
},
child: Icon(
foodIcons[index],
size: 40,
color: AppColors.lightPrimary.withValues(alpha: 0.3),
),
),
);
@@ -274,11 +258,7 @@ class _SplashScreenState extends State<SplashScreen>
@override
void dispose() {
for (final controller in _foodControllers) {
controller.dispose();
}
_questionMarkController.dispose();
_centerIconController.dispose();
_animationController.dispose();
super.dispose();
}
}