awesome_notifications_helper.dart 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. import 'package:awesome_notifications/awesome_notifications.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:get/get.dart';
  4. import '../app/constants/sp_keys.dart';
  5. import '../app/dialog/custom_dialog.dart';
  6. import '../app/data/sp/ix_sp.dart';
  7. import '../app/routes/app_pages.dart';
  8. import '../config/translations/strings_enum.dart';
  9. import 'log/logger.dart';
  10. import 'ntp_time_service.dart';
  11. class AwesomeNotificationsHelper {
  12. // prevent making instance
  13. AwesomeNotificationsHelper._();
  14. static const String TAG = 'AwesomeNotificationsHelper';
  15. // Notification lib
  16. static AwesomeNotifications awesomeNotifications = AwesomeNotifications();
  17. // 标记是否已经请求过权限
  18. static bool _hasRequestedPermission = false;
  19. /// initialize local notifications service, create channels and groups
  20. /// setup notifications button actions handlers
  21. static init() async {
  22. try {
  23. // 初始化通知渠道和组
  24. await _initNotification();
  25. // 设置监听
  26. listenToActionButtons();
  27. // 检查权限但不立即请求
  28. await checkNotificationPermission();
  29. } catch (e) {
  30. log(TAG, 'Notification initialization failed: $e');
  31. }
  32. }
  33. /// 检查通知权限状态
  34. static Future<bool> checkNotificationPermission() async {
  35. return await awesomeNotifications.isNotificationAllowed();
  36. }
  37. /// 打开通知权限设置
  38. static Future<void> showNotificationConfigPage() async {
  39. return await awesomeNotifications.showNotificationConfigPage();
  40. }
  41. /// 请求通知权限(可以被Firebase或其他组件调用)
  42. static Future<bool> requestNotificationPermission() async {
  43. // 如果已经请求过权限,先检查当前状态
  44. if (_hasRequestedPermission) {
  45. final isAllowed = await checkNotificationPermission();
  46. if (isAllowed) return true;
  47. }
  48. // 请求权限
  49. final isAllowed = await awesomeNotifications
  50. .requestPermissionToSendNotifications();
  51. // 标记已请求过权限
  52. _hasRequestedPermission = true;
  53. return isAllowed;
  54. }
  55. /// 检查是否应该显示通知权限提醒弹窗
  56. /// 返回 true 表示应该显示
  57. static Future<bool> shouldShowPushNoticeDialog() async {
  58. try {
  59. // 如果已经开启通知权限,不显示
  60. final isAllowed = await checkNotificationPermission();
  61. if (isAllowed) {
  62. log(TAG, 'Notification already allowed, skip dialog');
  63. return false;
  64. }
  65. // 检查是否超过 pushNoticeTime 分钟
  66. final appConfig = IXSP.getAppConfig();
  67. final pushNoticeMinutes = appConfig?.pushNoticeTime ?? 10080; // 默认7天
  68. final lastNoticeTimeStr =
  69. IXSP.getString(SPKeys.lastPushNoticeTime) ?? '0';
  70. final lastNoticeTime = int.tryParse(lastNoticeTimeStr) ?? 0;
  71. final now = NtpTimeService().getCurrentTimestamp();
  72. // 第一次(lastNoticeTime == 0)或超过间隔时间
  73. if (lastNoticeTime == 0) {
  74. log(TAG, 'First time, should show push notice dialog');
  75. return true;
  76. }
  77. final elapsedMinutes = (now - lastNoticeTime) / (1000 * 60);
  78. if (elapsedMinutes >= pushNoticeMinutes) {
  79. log(
  80. TAG,
  81. 'Push notice time elapsed: ${elapsedMinutes.toStringAsFixed(1)}min >= ${pushNoticeMinutes}min',
  82. );
  83. return true;
  84. }
  85. log(
  86. TAG,
  87. 'Push notice skipped: ${elapsedMinutes.toStringAsFixed(1)}min elapsed, '
  88. 'need ${pushNoticeMinutes}min',
  89. );
  90. return false;
  91. } catch (e) {
  92. log(TAG, 'Error checking push notice: $e');
  93. return false;
  94. }
  95. }
  96. /// 显示通知权限提醒弹窗
  97. static Future<void> showPushNoticeDialog() async {
  98. final shouldShow = await shouldShowPushNoticeDialog();
  99. if (!shouldShow) return;
  100. // 记录本次提醒时间
  101. _saveLastPushNoticeTime();
  102. CustomDialog.showConfirm(
  103. title: Strings.enableNotifications.tr,
  104. message: Strings.enableNotificationsDesc.tr,
  105. confirmText: Strings.enable.tr,
  106. cancelText: Strings.notNow.tr,
  107. icon: Icons.notifications_active_outlined,
  108. iconColor: Get.theme.primaryColor,
  109. confirmButtonColor: Get.theme.primaryColor,
  110. onConfirm: () async {
  111. Navigator.of(Get.context!).pop();
  112. await requestNotificationPermission();
  113. },
  114. onCancel: () {
  115. Navigator.of(Get.context!).pop();
  116. },
  117. );
  118. }
  119. /// 记录上次提醒时间
  120. static void _saveLastPushNoticeTime() {
  121. IXSP.setString(
  122. SPKeys.lastPushNoticeTime,
  123. NtpTimeService().getCurrentTimestamp().toString(),
  124. );
  125. }
  126. /// when user click on notification or click on button on the notification
  127. static listenToActionButtons() {
  128. // Only after at least the action method is set, the notification events are delivered
  129. awesomeNotifications.setListeners(
  130. onActionReceivedMethod: NotificationController.onActionReceivedMethod,
  131. onNotificationCreatedMethod:
  132. NotificationController.onNotificationCreatedMethod,
  133. onNotificationDisplayedMethod:
  134. NotificationController.onNotificationDisplayedMethod,
  135. onDismissActionReceivedMethod:
  136. NotificationController.onDismissActionReceivedMethod,
  137. );
  138. }
  139. ///init notifications channels
  140. static _initNotification() async {
  141. await awesomeNotifications.initialize(
  142. 'resource://mipmap/launcher_icon', // 添加小图标资源路径,
  143. [
  144. NotificationChannel(
  145. channelGroupKey: NotificationChannels.generalChannelGroupKey,
  146. channelKey: NotificationChannels.generalChannelKey,
  147. channelName: NotificationChannels.generalChannelName,
  148. groupKey: NotificationChannels.generalGroupKey,
  149. channelDescription: NotificationChannels.generalChannelDescription,
  150. defaultColor: Colors.green,
  151. ledColor: Colors.white,
  152. channelShowBadge: true,
  153. playSound: true,
  154. importance: NotificationImportance.Max,
  155. ),
  156. NotificationChannel(
  157. channelGroupKey: NotificationChannels.chatChannelGroupKey,
  158. channelKey: NotificationChannels.chatChannelKey,
  159. channelName: NotificationChannels.chatChannelName,
  160. groupKey: NotificationChannels.chatGroupKey,
  161. channelDescription: NotificationChannels.chatChannelDescription,
  162. defaultColor: Colors.green,
  163. ledColor: Colors.white,
  164. channelShowBadge: true,
  165. playSound: true,
  166. importance: NotificationImportance.Max,
  167. ),
  168. ],
  169. channelGroups: [
  170. NotificationChannelGroup(
  171. channelGroupKey: NotificationChannels.generalChannelGroupKey,
  172. channelGroupName: NotificationChannels.generalChannelGroupName,
  173. ),
  174. NotificationChannelGroup(
  175. channelGroupKey: NotificationChannels.chatChannelGroupKey,
  176. channelGroupName: NotificationChannels.chatChannelGroupName,
  177. ),
  178. ],
  179. );
  180. }
  181. //display notification for user with sound
  182. static Future<bool> showNotification({
  183. required String title,
  184. required String body,
  185. required int id,
  186. String? channelKey,
  187. String? groupKey,
  188. NotificationLayout? notificationLayout,
  189. String? summary,
  190. List<NotificationActionButton>? actionButtons,
  191. Map<String, String>? payload,
  192. String? largeIcon,
  193. }) async {
  194. try {
  195. final isAllowed = await checkNotificationPermission();
  196. if (!isAllowed) {
  197. final requested = await requestNotificationPermission();
  198. if (!requested) return false;
  199. }
  200. await awesomeNotifications.createNotification(
  201. content: NotificationContent(
  202. id: id,
  203. title: title,
  204. body: body,
  205. groupKey: groupKey ?? NotificationChannels.generalGroupKey,
  206. channelKey: channelKey ?? NotificationChannels.generalChannelKey,
  207. showWhen: true,
  208. payload: payload,
  209. notificationLayout: notificationLayout ?? NotificationLayout.Default,
  210. autoDismissible: true,
  211. summary: summary,
  212. largeIcon: largeIcon,
  213. ),
  214. actionButtons: actionButtons,
  215. );
  216. return true;
  217. } catch (e) {
  218. log(TAG, 'Sending notification failed: $e');
  219. return false;
  220. }
  221. }
  222. }
  223. class NotificationController {
  224. /// Use this method to detect when a new notification or a schedule is created
  225. @pragma("vm:entry-point")
  226. static Future<void> onNotificationCreatedMethod(
  227. ReceivedNotification receivedNotification,
  228. ) async {
  229. // Your code goes here
  230. }
  231. /// Use this method to detect every time that a new notification is displayed
  232. @pragma("vm:entry-point")
  233. static Future<void> onNotificationDisplayedMethod(
  234. ReceivedNotification receivedNotification,
  235. ) async {
  236. // Your code goes here
  237. }
  238. /// Use this method to detect if the user dismissed a notification
  239. @pragma("vm:entry-point")
  240. static Future<void> onDismissActionReceivedMethod(
  241. ReceivedAction receivedAction,
  242. ) async {
  243. // Your code goes here
  244. }
  245. /// Use this method to detect when the user taps on a notification or action button
  246. @pragma("vm:entry-point")
  247. static Future<void> onActionReceivedMethod(
  248. ReceivedAction receivedAction,
  249. ) async {
  250. try {
  251. final payload = receivedAction.payload;
  252. if (payload != null) {
  253. final route = payload['route'];
  254. if (route != null) {
  255. Get.key.currentState?.pushNamed(route);
  256. } else {
  257. Get.key.currentState?.pushNamed(Routes.HOME);
  258. }
  259. }
  260. } catch (e) {
  261. log(
  262. "onActionReceivedMethod",
  263. 'Handling notification click event failed: $e',
  264. );
  265. }
  266. }
  267. }
  268. class NotificationChannels {
  269. // chat channel (for messages only)
  270. static String get chatChannelKey => "chat_channel";
  271. static String get chatChannelName => "Chat Notifications";
  272. static String get chatGroupKey => "chat_group_key";
  273. static String get chatChannelGroupKey => "chat_channel_group";
  274. static String get chatChannelGroupName => "Chat Notifications";
  275. static String get chatChannelDescription =>
  276. "Receive chat and message notifications";
  277. // general channel (for all other notifications)
  278. static String get generalChannelKey => "general_channel";
  279. static String get generalGroupKey => "general_group_key";
  280. static String get generalChannelGroupKey => "general_channel_group";
  281. static String get generalChannelGroupName => "General Notifications";
  282. static String get generalChannelName => "General Notifications";
  283. static String get generalChannelDescription =>
  284. "Receive general app notifications";
  285. }