guru_sdk/guru_app/lib/firebase/messaging/remote_messaging_manager.dart

454 lines
16 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'dart:ui';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/services.dart';
import 'package:guru_app/account/account_manager.dart';
import 'package:guru_app/analytics/guru_analytics.dart';
import 'package:guru_app/guru_app.dart';
import 'package:guru_app/lifecycle/lifecycle_model.dart';
import 'package:guru_app/property/app_property.dart';
import 'package:guru_app/property/property_keys.dart';
import 'package:guru_utils/controller/lifecycle_controller.dart';
import 'package:guru_utils/lifecycle/lifecycle_manager.dart';
import 'package:guru_utils/router/router.dart';
import 'package:guru_utils/extensions/extensions.dart';
import 'package:guru_utils/log/log.dart';
import 'package:guru_utils/math/math_utils.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:json_annotation/json_annotation.dart';
/// Created by Haoyi on 5/14/21
//
Future<dynamic> _backgroundMessageHandler(RemoteMessage message) async {
Log.d("_backgroundMessageHandler:${message.data} ${message.data["uri"]}");
return;
}
enum RationaleResult { skip, allow }
enum PromptTrigger {
@JsonValue(0)
rationale, // 依赖Android原生的shouldShowRequestRationale返回值来展示对应的Rationale页面
@JsonValue(1)
request // 依赖请求的次数来展示对应的Rationale页面
}
class RemoteMessagingManager {
static RemoteMessagingManager instance = RemoteMessagingManager._();
late FirebaseMessaging _firebaseMessaging;
final BehaviorSubject<String?> fcmToken = BehaviorSubject.seeded(null);
Stream<String?> get observableFCMToken => fcmToken.stream;
RemoteMessagingManager._();
int _retryFetchTokenCount = 0;
final _statusMap = {
AuthorizationStatus.authorized: "granted",
AuthorizationStatus.denied: "denied",
AuthorizationStatus.provisional: "provisional",
AuthorizationStatus.notDetermined: "not_determined"
};
String? _getUriLastSegment(String? uri) {
try {
return Uri.parse(uri ?? "").pathSegments.last;
} catch (error) {
return null;
}
}
void fetchToken({Completer<String>? completer}) async {
Log.d("Fetch FCMToken!!");
String? token;
try {
token = await _firebaseMessaging.getToken();
if (token != null) {
fcmToken.addEx(token);
// RuntimeProperty.instance.setString("firebase_push_token", token);
Log.d("### FCMToken :$token");
}
} catch (error, stacktrace) {
Log.d("fetchToken error!", error: error, stackTrace: stacktrace);
}
if (token == null || token == '') {
final intervalSeconds =
(MathUtils.fibonacci(_retryFetchTokenCount) * 8).clamp(8, 600);
Future.delayed(Duration(seconds: intervalSeconds), () {
fetchToken();
});
_retryFetchTokenCount++;
} else {
_retryFetchTokenCount = 0;
completer?.complete(Future.value(token));
}
}
Future<String?> getToken() async {
final result = fcmToken.value ?? (await _firebaseMessaging.getToken());
if (result != null && fcmToken.value == null) {
fcmToken.addEx(result);
}
return result;
}
void init() async {
_firebaseMessaging = FirebaseMessaging.instance;
final granted = await checkNotificationPermission();
if (!granted) {
Future.delayed(const Duration(seconds: 8), () async {
if (GuruApp
.instance.appSpec.deployment.autoRequestNotificationPermission) {
Log.d("guru_app auto request notification permissions!");
requestNotificationPermission();
} else {
Log.d("guru_app check notification permissions!");
final shouldShowRequestRationale =
await Permission.notification.shouldShowRequestRationale;
Log.d(
"guru_app post request notification permission event! shouldShowRequestRationale:$shouldShowRequestRationale");
LifecycleManager.instance.postEvent(
RequestNotificationPermissionEvent(
rationale: shouldShowRequestRationale));
}
});
}
FirebaseMessaging.instance.getInitialMessage().then((message) {
if (message != null) {
final uri = message.data["uri"];
if (uri != null && uri is String && uri.isNotEmpty) {
Log.d("getInitialMessage:${message.data} $uri");
RouteCenter.instance.dispatchUri(Uri.parse(uri));
}
}
});
FirebaseMessaging.onMessage.listen((message) {
Log.d("onMessage:${message.data}");
final data = message.data;
// final notification = message.notification;
// if (Platform.isAndroid && notification != null) {
// NotificationChannel.showNotification(
// NotificationChannel.pushType, {"title": notification.title, "body": notification.body, "cmd": data["cmd"], "uri": data["uri"]});
// }
GuruAnalytics.instance.logEventEx("push_receive",
itemCategory: data["cmd"], itemName: _getUriLastSegment(data["uri"]));
});
FirebaseMessaging.onMessageOpenedApp.listen((message) {
final uri = message.data["uri"];
if (uri != null && uri is String && uri.isNotEmpty) {
Log.d("onMessageOpenApp:${message.data} ${message.data["uri"]}");
RouteCenter.instance.dispatchUri(Uri.parse(message.data["uri"]));
}
});
_firebaseMessaging.onTokenRefresh.listen((event) {
Log.d("onTokenRefresh $event");
AccountManager.instance.refreshFcmToken();
// Injector.provide<AccountService>().refreshFcmToken();
});
fetchToken();
}
Future<bool> checkNotificationPermission() async {
final _map = {
AuthorizationStatus.authorized: "granted",
AuthorizationStatus.denied: "denied",
AuthorizationStatus.provisional: "provisional",
AuthorizationStatus.notDetermined: "not_determined"
};
final notificationSettings =
await _firebaseMessaging.getNotificationSettings();
final property = _map[notificationSettings.authorizationStatus];
if (property != null) {
GuruAnalytics.instance.setUserProperty("noti_perm", property);
} else {
GuruAnalytics.instance.setUserProperty("noti_perm", "not_determined");
}
return notificationSettings.authorizationStatus ==
AuthorizationStatus.authorized;
}
Future<AuthorizationStatus> getNotificationAuthorizationStatus() async {
final notificationSettings =
await _firebaseMessaging.getNotificationSettings();
return notificationSettings.authorizationStatus;
}
Future<bool> isShouldShowRequestRationale() async {
if (GuruApp
.instance.appSpec.deployment.notificationPermissionPromptTrigger ==
PromptTrigger.rationale) {
return await Permission.notification.shouldShowRequestRationale;
}
if (await Permission.notification.isGranted) {
return false;
}
final permanentlyDenied = await Permission.notification.isPermanentlyDenied;
if (permanentlyDenied) {
return false;
}
int deniedTimes = await AppProperty.getInstance()
.getInt(PropertyKeys.deniedNotificationPermissionTimes, defValue: 0);
if (deniedTimes >= 2) {
return false;
}
final requestTimes = await AppProperty.getInstance()
.getInt(PropertyKeys.requestNotificationPermissionTimes, defValue: 0);
return requestTimes >= 1;
}
Future<bool> _requestNotificationPermissionForAndroid(
{String style = "default",
String scene = "",
Completer<RationaleResult> Function()? showRationale}) async {
final PromptTrigger promptTrigger =
GuruApp.instance.appSpec.deployment.notificationPermissionPromptTrigger;
if (await Permission.notification.isGranted) {
GuruAnalytics.instance.setUserProperty("noti_perm", "granted");
return true;
} else {
final permanentlyDenied =
await Permission.notification.isPermanentlyDenied;
if (permanentlyDenied) {
GuruAnalytics.instance.setUserProperty("noti_perm", "denied");
return false;
}
int deniedTimes = await AppProperty.getInstance()
.getInt(PropertyKeys.deniedNotificationPermissionTimes, defValue: 0);
if (deniedTimes >= 2) {
GuruAnalytics.instance.setUserProperty("noti_perm", "denied");
return false;
}
final promptTriggerValue =
promptTrigger == PromptTrigger.rationale ? "a" : "b";
final requestTimes = await AppProperty.getInstance().increaseAndGet(
PropertyKeys.requestNotificationPermissionTimes,
defValue: 0);
final trackingNotificationPermissionPass = GuruApp
.instance.appSpec.deployment.trackingNotificationPermissionPass &&
requestTimes <
(GuruApp.instance.appSpec.deployment
.trackingNotificationPermissionPassLimitTimes);
if (trackingNotificationPermissionPass) {
GuruAnalytics.instance.logEventEx("noti_perm_req_$requestTimes",
itemCategory: style,
itemName: scene,
parameters: {
"request_times": requestTimes,
"denied_times": deniedTimes,
"prompt_trigger": promptTriggerValue
});
}
final shouldShowRequestRationale =
await Permission.notification.shouldShowRequestRationale ||
(promptTrigger == PromptTrigger.request && requestTimes > 1);
Log.d(
"_requestNotificationPermission requestTimes:$requestTimes deniedTimes:$deniedTimes trackingNotificationPermissionPass:$trackingNotificationPermissionPass promptTrigger:$promptTrigger shouldShowRequestRationale:$shouldShowRequestRationale ");
if (shouldShowRequestRationale && showRationale != null) {
GuruAnalytics.instance.logEventEx("noti_perm_rationale_imp",
itemCategory: style, itemName: scene);
RationaleResult rationaleResult = RationaleResult.skip;
try {
final completer = showRationale();
rationaleResult = await completer.future;
} catch (error, stacktrace) {
Log.d("showRationale error!", error: error, stackTrace: stacktrace);
}
GuruAnalytics.instance.logEventEx("noti_perm_rationale_result",
itemCategory: style,
itemName: scene,
parameters: {
"result":
rationaleResult == RationaleResult.allow ? "allow" : "skip",
});
if (rationaleResult == RationaleResult.skip) {
return false;
}
}
final showTimes = await AppProperty.getInstance().increaseAndGet(
PropertyKeys.showNotificationPermissionTimes,
defValue: 0);
GuruAnalytics.instance.logEventEx("noti_perm_imp",
itemCategory: style,
itemName: scene,
parameters: {
"show_times": showTimes,
"request_times": requestTimes,
"denied_times": deniedTimes,
"prompt_trigger": promptTriggerValue
});
final requestSettings = await _firebaseMessaging.requestPermission();
final result =
_statusMap[requestSettings.authorizationStatus] ?? "not_determined";
await GuruAnalytics.instance.setUserProperty("noti_perm", result);
if (requestSettings.authorizationStatus !=
AuthorizationStatus.authorized) {
final shouldShowRequestRationale2 =
await Permission.notification.shouldShowRequestRationale;
if (deniedTimes == 0 && shouldShowRequestRationale2) {
deniedTimes = 1;
await AppProperty.getInstance()
.setInt(PropertyKeys.deniedNotificationPermissionTimes, 1);
} else if (deniedTimes == 1 &&
shouldShowRequestRationale != shouldShowRequestRationale2) {
deniedTimes = 2;
await AppProperty.getInstance()
.setInt(PropertyKeys.deniedNotificationPermissionTimes, 2);
}
} else {
if (trackingNotificationPermissionPass) {
GuruAnalytics.instance.logEventEx("noti_perm_pass_$requestTimes",
itemCategory: style,
itemName: scene,
parameters: {
"show_times": showTimes,
"request_times": requestTimes,
"denied_times": deniedTimes,
"prompt_trigger": promptTriggerValue
});
}
}
GuruAnalytics.instance.logEventEx("noti_perm_result",
itemCategory: style,
itemName: scene,
parameters: {
"result": result,
"show_times": showTimes,
"request_times": requestTimes,
"denied_times": deniedTimes,
"prompt_trigger": promptTriggerValue
});
Log.d(
"notificationSettings.authorizationStatus:${requestSettings.authorizationStatus} showTimes:$requestTimes deniedTimes:$deniedTimes promptTrigger: $promptTrigger");
return requestSettings.authorizationStatus ==
AuthorizationStatus.authorized;
}
}
Future<bool> _requestNotificationPermissionForIOS(
{String style = "default", String scene = ""}) async {
final status = await getNotificationAuthorizationStatus();
switch (status) {
case AuthorizationStatus.authorized:
GuruAnalytics.instance.setUserProperty("noti_perm", "granted");
return true;
case AuthorizationStatus.provisional:
GuruAnalytics.instance.setUserProperty("noti_perm", "provisional");
return true;
case AuthorizationStatus.denied:
GuruAnalytics.instance.setUserProperty("noti_perm", "denied");
return false;
default:
break;
}
final trackingNotificationPermissionPass =
GuruApp.instance.appSpec.deployment.trackingNotificationPermissionPass;
int deniedTimes = await AppProperty.getInstance()
.getInt(PropertyKeys.deniedNotificationPermissionTimes, defValue: 0);
final requestTimes = await AppProperty.getInstance().increaseAndGet(
PropertyKeys.requestNotificationPermissionTimes,
defValue: 0);
final showTimes = await AppProperty.getInstance().increaseAndGet(
PropertyKeys.showNotificationPermissionTimes,
defValue: 0);
if (trackingNotificationPermissionPass) {
GuruAnalytics.instance.logEventEx("noti_perm_req_$requestTimes",
itemCategory: style,
itemName: scene,
parameters: {
"request_times": requestTimes,
"denied_times": deniedTimes,
"prompt_trigger": "a"
});
}
final requestSettings = await _firebaseMessaging.requestPermission();
final result =
_statusMap[requestSettings.authorizationStatus] ?? "not_determined";
await GuruAnalytics.instance.setUserProperty("noti_perm", result);
if (requestSettings.authorizationStatus != AuthorizationStatus.authorized) {
deniedTimes += 1;
} else {
if (trackingNotificationPermissionPass) {
GuruAnalytics.instance.logEventEx("noti_perm_pass_$requestTimes",
itemCategory: style,
itemName: scene,
parameters: {
"show_times": showTimes,
"request_times": requestTimes,
"denied_times": deniedTimes,
"prompt_trigger": "a"
});
}
}
GuruAnalytics.instance.logEventEx("noti_perm_result",
itemCategory: style,
itemName: scene,
parameters: {
"result": result,
"show_times": showTimes,
"request_times": requestTimes,
"denied_times": deniedTimes,
"prompt_trigger": "a"
});
Log.d(
"notificationSettings.authorizationStatus:${requestSettings.authorizationStatus} showTimes:$requestTimes deniedTimes:$deniedTimes");
return requestSettings.authorizationStatus ==
AuthorizationStatus.authorized;
}
Future<bool> requestNotificationPermission(
{String style = "default",
String scene = "",
Completer<RationaleResult> Function()? showRationale}) async {
if (Platform.isAndroid) {
return _requestNotificationPermissionForAndroid(
style: style, scene: scene, showRationale: showRationale);
} else if (Platform.isIOS) {
return _requestNotificationPermissionForIOS(style: style, scene: scene);
}
return false;
}
void saveTokenToClipboard() {
final token = fcmToken.value;
if (token != null) {
Clipboard.setData(ClipboardData(text: token));
Log.d("saveTokenToClipboard:$token");
}
}
void dispose() {
fcmToken.close();
}
}