Files
lunchpick/lib/main.dart
JiWoong Sul 6f45c7b456 perf(app): 초기화 병렬 처리 및 UI 개선
## 성능 최적화

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

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

## UI 개선

### 각 화면 리팩토링
- AppDimensions 상수 적용
- 스켈레톤 로더 적용
- 코드 정리 및 일관성 개선
2026-01-12 15:16:05 +09:00

187 lines
5.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:go_router/go_router.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:timezone/data/latest_all.dart' as tz;
import 'core/constants/app_colors.dart';
import 'core/constants/app_constants.dart';
import 'core/services/notification_service.dart';
import 'core/utils/ad_helper.dart';
import 'domain/entities/restaurant.dart';
import 'domain/entities/visit_record.dart';
import 'domain/entities/recommendation_record.dart';
import 'domain/entities/user_settings.dart';
import 'presentation/pages/splash/splash_screen.dart';
import 'presentation/pages/main/main_screen.dart';
import 'data/sample/sample_data_initializer.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize timezone (동기, 빠름)
tz.initializeTimeZones();
// 광고 SDK와 Hive 초기화를 병렬 처리
await Future.wait([
if (AdHelper.isMobilePlatform) MobileAds.instance.initialize(),
_initializeHive(),
]);
// Hive 초기화 후 병렬 처리 가능한 작업들
await Future.wait([
SampleDataInitializer.seedInitialData(),
_initializeNotifications(),
AdaptiveTheme.getThemeMode(),
]).then((results) {
final savedThemeMode = results[2] as AdaptiveThemeMode?;
runApp(ProviderScope(child: LunchPickApp(savedThemeMode: savedThemeMode)));
});
}
/// Hive 초기화 및 Box 오픈
Future<void> _initializeHive() async {
await Hive.initFlutter();
// Register Hive Adapters
Hive.registerAdapter(RestaurantAdapter());
Hive.registerAdapter(DataSourceAdapter());
Hive.registerAdapter(VisitRecordAdapter());
Hive.registerAdapter(RecommendationRecordAdapter());
Hive.registerAdapter(UserSettingsAdapter());
// Open Hive Boxes (병렬 오픈)
await Future.wait([
Hive.openBox<Restaurant>(AppConstants.restaurantBox),
Hive.openBox<VisitRecord>(AppConstants.visitRecordBox),
Hive.openBox<RecommendationRecord>(AppConstants.recommendationBox),
Hive.openBox(AppConstants.settingsBox),
Hive.openBox<UserSettings>('user_settings'),
]);
}
/// 알림 서비스 초기화 (비-웹 플랫폼)
Future<void> _initializeNotifications() async {
if (kIsWeb) return;
final notificationService = NotificationService();
await notificationService.ensureInitialized(requestPermission: true);
}
class LunchPickApp extends StatelessWidget {
final AdaptiveThemeMode? savedThemeMode;
const LunchPickApp({super.key, this.savedThemeMode});
@override
Widget build(BuildContext context) {
return AdaptiveTheme(
light: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: AppColors.lightPrimary,
brightness: Brightness.light,
),
primaryColor: AppColors.lightPrimary,
scaffoldBackgroundColor: AppColors.lightBackground,
appBarTheme: const AppBarTheme(
backgroundColor: AppColors.lightPrimary,
foregroundColor: Colors.white,
elevation: 0,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.lightPrimary,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
cardTheme: CardThemeData(
color: AppColors.lightSurface,
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
dark: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: AppColors.darkPrimary,
brightness: Brightness.dark,
),
primaryColor: AppColors.darkPrimary,
scaffoldBackgroundColor: AppColors.darkBackground,
appBarTheme: const AppBarTheme(
backgroundColor: AppColors.darkPrimary,
foregroundColor: Colors.white,
elevation: 0,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.darkPrimary,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
cardTheme: CardThemeData(
color: AppColors.darkSurface,
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
initial: savedThemeMode ?? AdaptiveThemeMode.light,
builder: (theme, darkTheme) => MaterialApp.router(
title: AppConstants.appName,
theme: theme,
darkTheme: darkTheme,
routerConfig: _router,
debugShowCheckedModeBanner: false,
),
);
}
}
// GoRouter configuration
final _router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(path: '/', builder: (context, state) => const SplashScreen()),
GoRoute(
path: '/home',
builder: (context, state) {
final tabParam = state.uri.queryParameters['tab'];
int initialTab = 2; // 기본값: 뽑기 탭
if (tabParam != null) {
switch (tabParam) {
case 'share':
initialTab = 0;
break;
case 'list':
initialTab = 1;
break;
case 'random':
initialTab = 2;
break;
case 'calendar':
initialTab = 3;
break;
case 'settings':
initialTab = 4;
break;
}
}
return MainScreen(initialTab: initialTab);
},
),
],
);