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:
@@ -2,21 +2,28 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lunchpick/core/constants/app_colors.dart';
|
||||
import 'package:lunchpick/core/constants/app_typography.dart';
|
||||
import 'package:lunchpick/core/widgets/info_row.dart';
|
||||
import 'package:lunchpick/domain/entities/restaurant.dart';
|
||||
import 'package:lunchpick/presentation/providers/restaurant_provider.dart';
|
||||
import 'package:lunchpick/presentation/providers/visit_provider.dart';
|
||||
import 'edit_restaurant_dialog.dart';
|
||||
|
||||
/// 맛집 카드 위젯
|
||||
/// [lastVisitDate]를 외부에서 주입받아 리스트 렌더링 최적화
|
||||
class RestaurantCard extends ConsumerWidget {
|
||||
final Restaurant restaurant;
|
||||
final double? distanceKm;
|
||||
final DateTime? lastVisitDate;
|
||||
|
||||
const RestaurantCard({super.key, required this.restaurant, this.distanceKm});
|
||||
const RestaurantCard({
|
||||
super.key,
|
||||
required this.restaurant,
|
||||
this.distanceKm,
|
||||
this.lastVisitDate,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final lastVisitAsync = ref.watch(lastVisitDateProvider(restaurant.id));
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
@@ -177,39 +184,26 @@ class RestaurantCard extends ConsumerWidget {
|
||||
),
|
||||
|
||||
// 마지막 방문일
|
||||
lastVisitAsync.when(
|
||||
data: (lastVisit) {
|
||||
if (lastVisit != null) {
|
||||
final daysSinceVisit = DateTime.now()
|
||||
.difference(lastVisit)
|
||||
.inDays;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.schedule,
|
||||
size: 16,
|
||||
color: isDark
|
||||
? AppColors.darkTextSecondary
|
||||
: AppColors.lightTextSecondary,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
daysSinceVisit == 0
|
||||
? '오늘 방문'
|
||||
: '$daysSinceVisit일 전 방문',
|
||||
style: AppTypography.caption(isDark),
|
||||
),
|
||||
],
|
||||
if (lastVisitDate != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.schedule,
|
||||
size: 16,
|
||||
color: isDark
|
||||
? AppColors.darkTextSecondary
|
||||
: AppColors.lightTextSecondary,
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
loading: () => const SizedBox.shrink(),
|
||||
error: (_, __) => const SizedBox.shrink(),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
_formatLastVisit(lastVisitDate!),
|
||||
style: AppTypography.caption(isDark),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -240,6 +234,11 @@ class RestaurantCard extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
String _formatLastVisit(DateTime date) {
|
||||
final daysSinceVisit = DateTime.now().difference(date).inDays;
|
||||
return daysSinceVisit == 0 ? '오늘 방문' : '$daysSinceVisit일 전 방문';
|
||||
}
|
||||
|
||||
void _showRestaurantDetail(BuildContext context, bool isDark) {
|
||||
showDialog(
|
||||
context: context,
|
||||
@@ -252,22 +251,22 @@ class RestaurantCard extends ConsumerWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildDetailRow(
|
||||
'카테고리',
|
||||
'${restaurant.category} > ${restaurant.subCategory}',
|
||||
isDark,
|
||||
InfoRow(
|
||||
label: '카테고리',
|
||||
value: '${restaurant.category} > ${restaurant.subCategory}',
|
||||
isDark: isDark,
|
||||
),
|
||||
if (restaurant.description != null)
|
||||
_buildDetailRow('설명', restaurant.description!, isDark),
|
||||
InfoRow(label: '설명', value: restaurant.description!, isDark: isDark),
|
||||
if (restaurant.phoneNumber != null)
|
||||
_buildDetailRow('전화번호', restaurant.phoneNumber!, isDark),
|
||||
_buildDetailRow('도로명 주소', restaurant.roadAddress, isDark),
|
||||
_buildDetailRow('지번 주소', restaurant.jibunAddress, isDark),
|
||||
InfoRow(label: '전화번호', value: restaurant.phoneNumber!, isDark: isDark),
|
||||
InfoRow(label: '도로명 주소', value: restaurant.roadAddress, isDark: isDark),
|
||||
InfoRow(label: '지번 주소', value: restaurant.jibunAddress, isDark: isDark),
|
||||
if (restaurant.lastVisitDate != null)
|
||||
_buildDetailRow(
|
||||
'마지막 방문',
|
||||
'${restaurant.lastVisitDate!.year}년 ${restaurant.lastVisitDate!.month}월 ${restaurant.lastVisitDate!.day}일',
|
||||
isDark,
|
||||
InfoRow(
|
||||
label: '마지막 방문',
|
||||
value: '${restaurant.lastVisitDate!.year}년 ${restaurant.lastVisitDate!.month}월 ${restaurant.lastVisitDate!.day}일',
|
||||
isDark: isDark,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -281,20 +280,6 @@ class RestaurantCard extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetailRow(String label, String value, bool isDark) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label, style: AppTypography.caption(isDark)),
|
||||
const SizedBox(height: 2),
|
||||
Text(value, style: AppTypography.body2(isDark)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleMenuAction(
|
||||
_RestaurantMenuAction action,
|
||||
BuildContext context,
|
||||
|
||||
Reference in New Issue
Block a user