prod 웹앱(spin.special-partners.com)을 감싸는 네이티브 셸. 화면 개발 없음. - InAppWebView: 쿠키/캐시 영속·UA(spinApp) 태그·풀투리프레시·외부링크 분기·오프라인 화면 - Android 하드웨어 뒤로가기(웹 히스토리→더블탭 종료), navy 스플래시/상태바 - 파일/카메라 업로드 권한(Android/iOS), 생체인증 잠금(local_auth) - FCM 푸시(firebase_messaging) — 설정 전 자동 비활성, 토큰은 웹 세션으로 /api/devices 등록 - prod URL 고정(app_config.dart) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
52 lines
1.9 KiB
Dart
52 lines
1.9 KiB
Dart
import "package:flutter/foundation.dart";
|
|
import "package:firebase_core/firebase_core.dart";
|
|
import "package:firebase_messaging/firebase_messaging.dart";
|
|
|
|
/// FCM 푸시 서비스.
|
|
///
|
|
/// Firebase 설정(google-services.json / GoogleService-Info.plist + firebase_options)
|
|
/// 이 아직 없으면 `Firebase.initializeApp()` 이 실패하므로 try/catch로 감싸고 비활성
|
|
/// 상태로 동작한다 (앱은 정상 실행). 설정이 추가되면 자동으로 토큰을 얻는다.
|
|
class PushService {
|
|
PushService._();
|
|
static final PushService instance = PushService._();
|
|
|
|
bool enabled = false;
|
|
|
|
/// 현재 FCM 토큰 (없으면 null). 웹뷰 로드 후 web으로 전달해 /api/devices 에 등록.
|
|
final ValueNotifier<String?> token = ValueNotifier<String?>(null);
|
|
|
|
/// 푸시 탭으로 진입했을 때 이동할 in-app 경로(예: "/inbox"). 웹뷰가 소비.
|
|
final ValueNotifier<String?> pendingLink = ValueNotifier<String?>(null);
|
|
|
|
Future<void> init() async {
|
|
try {
|
|
await Firebase.initializeApp();
|
|
} catch (e) {
|
|
debugPrint("push: Firebase 미설정 — 푸시 비활성 ($e)");
|
|
enabled = false;
|
|
return;
|
|
}
|
|
enabled = true;
|
|
final messaging = FirebaseMessaging.instance;
|
|
try {
|
|
await messaging.requestPermission(alert: true, badge: true, sound: true);
|
|
token.value = await messaging.getToken();
|
|
messaging.onTokenRefresh.listen((t) => token.value = t);
|
|
|
|
// 앱이 종료 상태에서 푸시 탭으로 실행된 경우
|
|
final initial = await messaging.getInitialMessage();
|
|
if (initial != null) _consume(initial);
|
|
// 백그라운드에서 푸시 탭으로 복귀
|
|
FirebaseMessaging.onMessageOpenedApp.listen(_consume);
|
|
} catch (e) {
|
|
debugPrint("push: init 오류 $e");
|
|
}
|
|
}
|
|
|
|
void _consume(RemoteMessage m) {
|
|
final link = m.data["link"];
|
|
if (link is String && link.isNotEmpty) pendingLink.value = link;
|
|
}
|
|
}
|