LunchPick(오늘 뭐 먹Z?) Flutter 앱의 초기 구현입니다. 주요 기능: - 네이버 지도 연동 맛집 추가 - 랜덤 메뉴 추천 시스템 - 날씨 기반 거리 조정 - 방문 기록 관리 - Bluetooth 맛집 공유 - 다크모드 지원 기술 스택: - Flutter 3.8.1+ - Riverpod 상태 관리 - Hive 로컬 DB - Clean Architecture 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
189 lines
5.5 KiB
Dart
189 lines
5.5 KiB
Dart
import 'dart:math' as math;
|
|
import 'package:flutter/material.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import '../../../core/constants/app_colors.dart';
|
|
import '../../../core/constants/app_typography.dart';
|
|
import '../../../core/constants/app_constants.dart';
|
|
|
|
class SplashScreen extends StatefulWidget {
|
|
const SplashScreen({super.key});
|
|
|
|
@override
|
|
State<SplashScreen> createState() => _SplashScreenState();
|
|
}
|
|
|
|
class _SplashScreenState extends State<SplashScreen> with TickerProviderStateMixin {
|
|
late List<AnimationController> _foodControllers;
|
|
late AnimationController _questionMarkController;
|
|
late AnimationController _centerIconController;
|
|
|
|
final List<IconData> foodIcons = [
|
|
Icons.rice_bowl,
|
|
Icons.ramen_dining,
|
|
Icons.lunch_dining,
|
|
Icons.fastfood,
|
|
Icons.local_pizza,
|
|
Icons.cake,
|
|
Icons.coffee,
|
|
Icons.icecream,
|
|
Icons.bakery_dining,
|
|
];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_initializeAnimations();
|
|
_navigateToHome();
|
|
}
|
|
|
|
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),
|
|
vsync: this,
|
|
)..repeat(reverse: true);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
|
|
return Scaffold(
|
|
backgroundColor: isDark ? AppColors.darkBackground : AppColors.lightBackground,
|
|
body: Stack(
|
|
children: [
|
|
// 랜덤 위치 음식 아이콘들
|
|
..._buildFoodIcons(),
|
|
|
|
// 중앙 컨텐츠
|
|
Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
// 선택 아이콘
|
|
ScaleTransition(
|
|
scale: Tween(begin: 0.8, end: 1.2).animate(
|
|
CurvedAnimation(
|
|
parent: _centerIconController,
|
|
curve: Curves.easeInOut,
|
|
),
|
|
),
|
|
child: Icon(
|
|
Icons.restaurant_menu,
|
|
size: 80,
|
|
color: isDark ? AppColors.darkPrimary : AppColors.lightPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
|
|
// 앱 타이틀
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text(
|
|
'오늘 뭐 먹Z',
|
|
style: AppTypography.heading1(isDark),
|
|
),
|
|
AnimatedBuilder(
|
|
animation: _questionMarkController,
|
|
builder: (context, child) {
|
|
final questionMarks = '?' * (((_questionMarkController.value * 3).floor() % 3) + 1);
|
|
return Text(
|
|
questionMarks,
|
|
style: AppTypography.heading1(isDark),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// 하단 카피라이트
|
|
Positioned(
|
|
bottom: 30,
|
|
left: 0,
|
|
right: 0,
|
|
child: Text(
|
|
AppConstants.appCopyright,
|
|
style: AppTypography.caption(isDark).copyWith(
|
|
color: (isDark ? AppColors.darkTextSecondary : AppColors.lightTextSecondary)
|
|
.withOpacity(0.5),
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
List<Widget> _buildFoodIcons() {
|
|
final random = math.Random();
|
|
|
|
return List.generate(foodIcons.length, (index) {
|
|
final left = random.nextDouble() * 0.8 + 0.1;
|
|
final top = random.nextDouble() * 0.7 + 0.1;
|
|
|
|
return Positioned(
|
|
left: MediaQuery.of(context).size.width * left,
|
|
top: MediaQuery.of(context).size.height * top,
|
|
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: Icon(
|
|
foodIcons[index],
|
|
size: 40,
|
|
color: AppColors.lightPrimary.withOpacity(0.3),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
void _navigateToHome() {
|
|
Future.delayed(AppConstants.splashAnimationDuration, () {
|
|
if (mounted) {
|
|
context.go('/home');
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
for (final controller in _foodControllers) {
|
|
controller.dispose();
|
|
}
|
|
_questionMarkController.dispose();
|
|
_centerIconController.dispose();
|
|
super.dispose();
|
|
}
|
|
} |