Files
lunchpick/lib/presentation/providers/notification_handler_provider.dart
JiWoong Sul 2a01fa50c6 feat(app): finalize ad gated flows and weather
- add AppLogger and replace scattered print logging\n- implement ad-gated recommendation flow with reminder handling and calendar link\n- complete Bluetooth share pipeline with ad gate and merge\n- integrate KMA weather API with caching and dart-define decoding\n- add NaverUrlProcessor refactor and restore restaurant repository tests
2025-11-22 00:10:51 +09:00

190 lines
6.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:lunchpick/core/utils/app_logger.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) {
// 더 상세한 오류 정보 제공
AppLogger.error('NotificationPayload parsing error', error: e);
AppLogger.debug('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) {
AppLogger.debug('Notification payload is null or empty');
return;
}
AppLogger.debug('Handling notification with payload: $payload');
try {
// 기존 형식 (visit_reminder:restaurantName) 처리
if (payload.startsWith('visit_reminder:')) {
final restaurantName = payload.substring(15);
AppLogger.debug('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 처리
AppLogger.debug('Attempting to parse new format payload');
try {
final notificationPayload = NotificationPayload.fromString(payload);
AppLogger.debug(
'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) {
AppLogger.debug(
'Failed to parse new format, attempting fallback parsing',
);
AppLogger.debug('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) {
AppLogger.error(
'Error handling notification',
error: e,
stackTrace: 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);
});