feat: 초기 프로젝트 설정 및 LunchPick 앱 구현
LunchPick(오늘 뭐 먹Z?) Flutter 앱의 초기 구현입니다. 주요 기능: - 네이버 지도 연동 맛집 추가 - 랜덤 메뉴 추천 시스템 - 날씨 기반 거리 조정 - 방문 기록 관리 - Bluetooth 맛집 공유 - 다크모드 지원 기술 스택: - Flutter 3.8.1+ - Riverpod 상태 관리 - Hive 로컬 DB - Clean Architecture 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
174
lib/presentation/providers/notification_handler_provider.dart
Normal file
174
lib/presentation/providers/notification_handler_provider.dart
Normal file
@@ -0,0 +1,174 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:lunchpick/presentation/pages/calendar/widgets/visit_confirmation_dialog.dart';
|
||||
import 'package:lunchpick/presentation/providers/restaurant_provider.dart';
|
||||
|
||||
/// 알림 payload 데이터 모델
|
||||
class NotificationPayload {
|
||||
final String type;
|
||||
final String restaurantId;
|
||||
final String restaurantName;
|
||||
final DateTime recommendationTime;
|
||||
|
||||
NotificationPayload({
|
||||
required this.type,
|
||||
required this.restaurantId,
|
||||
required this.restaurantName,
|
||||
required this.recommendationTime,
|
||||
});
|
||||
|
||||
factory NotificationPayload.fromString(String payload) {
|
||||
try {
|
||||
final parts = payload.split('|');
|
||||
if (parts.length < 4) {
|
||||
throw FormatException('Invalid payload format - expected 4 parts but got ${parts.length}: $payload');
|
||||
}
|
||||
|
||||
// 각 필드 유효성 검증
|
||||
if (parts[0].isEmpty) {
|
||||
throw FormatException('Type cannot be empty');
|
||||
}
|
||||
if (parts[1].isEmpty) {
|
||||
throw FormatException('Restaurant ID cannot be empty');
|
||||
}
|
||||
if (parts[2].isEmpty) {
|
||||
throw FormatException('Restaurant name cannot be empty');
|
||||
}
|
||||
|
||||
// DateTime 파싱 시도
|
||||
DateTime? recommendationTime;
|
||||
try {
|
||||
recommendationTime = DateTime.parse(parts[3]);
|
||||
} catch (e) {
|
||||
throw FormatException('Invalid date format: ${parts[3]}. Error: $e');
|
||||
}
|
||||
|
||||
return NotificationPayload(
|
||||
type: parts[0],
|
||||
restaurantId: parts[1],
|
||||
restaurantName: parts[2],
|
||||
recommendationTime: recommendationTime,
|
||||
);
|
||||
} catch (e) {
|
||||
// 더 상세한 오류 정보 제공
|
||||
print('NotificationPayload parsing error: $e');
|
||||
print('Original payload: $payload');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
String toString() {
|
||||
return '$type|$restaurantId|$restaurantName|${recommendationTime.toIso8601String()}';
|
||||
}
|
||||
}
|
||||
|
||||
/// 알림 핸들러 StateNotifier
|
||||
class NotificationHandlerNotifier extends StateNotifier<AsyncValue<void>> {
|
||||
final Ref _ref;
|
||||
|
||||
NotificationHandlerNotifier(this._ref) : super(const AsyncValue.data(null));
|
||||
|
||||
/// 알림 클릭 처리
|
||||
Future<void> handleNotificationTap(BuildContext context, String? payload) async {
|
||||
if (payload == null || payload.isEmpty) {
|
||||
print('Notification payload is null or empty');
|
||||
return;
|
||||
}
|
||||
|
||||
print('Handling notification with payload: $payload');
|
||||
|
||||
try {
|
||||
// 기존 형식 (visit_reminder:restaurantName) 처리
|
||||
if (payload.startsWith('visit_reminder:')) {
|
||||
final restaurantName = payload.substring(15);
|
||||
print('Legacy format - Restaurant name: $restaurantName');
|
||||
|
||||
// 맛집 이름으로 ID 찾기
|
||||
final restaurantsAsync = await _ref.read(restaurantListProvider.future);
|
||||
final restaurant = restaurantsAsync.firstWhere(
|
||||
(r) => r.name == restaurantName,
|
||||
orElse: () => throw Exception('Restaurant not found: $restaurantName'),
|
||||
);
|
||||
|
||||
// 방문 확인 다이얼로그 표시
|
||||
if (context.mounted) {
|
||||
await VisitConfirmationDialog.show(
|
||||
context: context,
|
||||
restaurantId: restaurant.id,
|
||||
restaurantName: restaurant.name,
|
||||
recommendationTime: DateTime.now().subtract(const Duration(hours: 2)),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 새로운 형식의 payload 처리
|
||||
print('Attempting to parse new format payload');
|
||||
|
||||
try {
|
||||
final notificationPayload = NotificationPayload.fromString(payload);
|
||||
print('Successfully parsed payload - Type: ${notificationPayload.type}, RestaurantId: ${notificationPayload.restaurantId}');
|
||||
|
||||
if (notificationPayload.type == 'visit_reminder') {
|
||||
// 방문 확인 다이얼로그 표시
|
||||
if (context.mounted) {
|
||||
final confirmed = await VisitConfirmationDialog.show(
|
||||
context: context,
|
||||
restaurantId: notificationPayload.restaurantId,
|
||||
restaurantName: notificationPayload.restaurantName,
|
||||
recommendationTime: notificationPayload.recommendationTime,
|
||||
);
|
||||
|
||||
// 확인 또는 취소 후 캘린더 화면으로 이동
|
||||
if (context.mounted && confirmed != null) {
|
||||
context.go('/home?tab=calendar');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (parseError) {
|
||||
print('Failed to parse new format, attempting fallback parsing');
|
||||
print('Parse error: $parseError');
|
||||
|
||||
// Fallback: 간단한 파싱 시도
|
||||
if (payload.contains('|')) {
|
||||
final parts = payload.split('|');
|
||||
if (parts.isNotEmpty && parts[0] == 'visit_reminder') {
|
||||
// 최소한 캘린더로 이동
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('알림을 처리했습니다. 방문 기록을 확인해주세요.'),
|
||||
),
|
||||
);
|
||||
context.go('/home?tab=calendar');
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 파싱 실패 시 원래 에러 다시 발생
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
print('Error handling notification: $e');
|
||||
print('Stack trace: $stackTrace');
|
||||
state = AsyncValue.error(e, stackTrace);
|
||||
|
||||
// 에러 발생 시 기본적으로 캘린더 화면으로 이동
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('알림 처리 중 오류가 발생했습니다: ${e.toString()}'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
context.go('/home?tab=calendar');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// NotificationHandler Provider
|
||||
final notificationHandlerProvider = StateNotifierProvider<NotificationHandlerNotifier, AsyncValue<void>>((ref) {
|
||||
return NotificationHandlerNotifier(ref);
|
||||
});
|
||||
Reference in New Issue
Block a user