@rashidee/co2 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/dist/index.js +25449 -0
  2. package/drizzle/0000_remarkable_charles_xavier.sql +210 -0
  3. package/drizzle/0001_wise_wolf_cub.sql +13 -0
  4. package/drizzle/0002_secret_spyke.sql +16 -0
  5. package/drizzle/0003_workspace_config_singleton.sql +16 -0
  6. package/drizzle/0004_terminal_claim_v120.sql +11 -0
  7. package/drizzle/0005_brainstorm_v124.sql +10 -0
  8. package/drizzle/0006_project_v140.sql +6 -0
  9. package/drizzle/0007_prd_wizard_v124.sql +14 -0
  10. package/drizzle/0008_workflow_artifact_v126.sql +24 -0
  11. package/drizzle/0009_project_import_v150.sql +7 -0
  12. package/drizzle/0010_quality_v151.sql +3 -0
  13. package/drizzle/meta/0000_snapshot.json +1335 -0
  14. package/drizzle/meta/0001_snapshot.json +1416 -0
  15. package/drizzle/meta/0002_snapshot.json +1510 -0
  16. package/drizzle/meta/_journal.json +83 -0
  17. package/package.json +41 -0
  18. package/plugin/.claude-plugin/marketplace.json +36 -0
  19. package/plugin/.claude-plugin/plugin.json +22 -0
  20. package/plugin/README.md +625 -0
  21. package/plugin/SKILLS.md +350 -0
  22. package/plugin/co2.svg +5 -0
  23. package/plugin/commands/brainstorm-loop.md +129 -0
  24. package/plugin/skills/conductor-defect/SKILL.md +898 -0
  25. package/plugin/skills/conductor-feature-develop/SKILL.md +1383 -0
  26. package/plugin/skills/conductor-feature-develop/references/implementation-domain-template.md +131 -0
  27. package/plugin/skills/conductor-feature-develop/references/implementation-master-template.md +89 -0
  28. package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +224 -0
  29. package/plugin/skills/conductor-feature-prepare/SKILL.md +604 -0
  30. package/plugin/skills/depgen-k8s/SKILL.md +468 -0
  31. package/plugin/skills/depgen-k8s/references/dockerfile-laravel.md +197 -0
  32. package/plugin/skills/depgen-k8s/references/dockerfile-nodejs.md +163 -0
  33. package/plugin/skills/depgen-k8s/references/dockerfile-spring-boot.md +174 -0
  34. package/plugin/skills/depgen-k8s/references/k8s-patterns.md +869 -0
  35. package/plugin/skills/mockgen-shadcn/SKILL.md +1067 -0
  36. package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +1467 -0
  37. package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +498 -0
  38. package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +216 -0
  39. package/plugin/skills/mockgen-tailwind/SKILL.md +904 -0
  40. package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +720 -0
  41. package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +498 -0
  42. package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -0
  43. package/plugin/skills/modelgen-nosql/SKILL.md +617 -0
  44. package/plugin/skills/modelgen-nosql/references/model-extraction-methodology.md +497 -0
  45. package/plugin/skills/modelgen-nosql/references/nosql-design-guide.md +258 -0
  46. package/plugin/skills/modelgen-relational/SKILL.md +707 -0
  47. package/plugin/skills/modelgen-relational/references/model-extraction-methodology.md +499 -0
  48. package/plugin/skills/specgen-flutter-riverpod/SKILL.md +898 -0
  49. package/plugin/skills/specgen-flutter-riverpod/references/component-patterns.md +971 -0
  50. package/plugin/skills/specgen-flutter-riverpod/references/notification-patterns.md +457 -0
  51. package/plugin/skills/specgen-flutter-riverpod/references/routing-patterns.md +362 -0
  52. package/plugin/skills/specgen-flutter-riverpod/references/security-patterns.md +709 -0
  53. package/plugin/skills/specgen-flutter-riverpod/references/spec-template.md +1566 -0
  54. package/plugin/skills/specgen-flutter-riverpod/references/state-patterns.md +518 -0
  55. package/plugin/skills/specgen-flutter-riverpod/references/storage-patterns.md +370 -0
  56. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/SKILL.md +984 -0
  57. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/batch-patterns.md +359 -0
  58. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/messaging-patterns.md +468 -0
  59. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/modulith-patterns.md +461 -0
  60. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/reporting-patterns.md +803 -0
  61. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/security-patterns.md +501 -0
  62. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/spec-template.md +1268 -0
  63. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/web-patterns.md +301 -0
  64. package/plugin/skills/specgen-node-cli-web/SKILL.md +561 -0
  65. package/plugin/skills/specgen-node-cli-web/references/auth-patterns.md +293 -0
  66. package/plugin/skills/specgen-node-cli-web/references/cli-packaging-patterns.md +250 -0
  67. package/plugin/skills/specgen-node-cli-web/references/server-patterns.md +247 -0
  68. package/plugin/skills/specgen-node-cli-web/references/spec-template.md +401 -0
  69. package/plugin/skills/specgen-node-cli-web/references/testing-patterns.md +174 -0
  70. package/plugin/skills/specgen-react-mui/SKILL.md +789 -0
  71. package/plugin/skills/specgen-react-mui/references/component-patterns.md +1077 -0
  72. package/plugin/skills/specgen-react-mui/references/reporting-patterns.md +1998 -0
  73. package/plugin/skills/specgen-react-mui/references/routing-patterns.md +461 -0
  74. package/plugin/skills/specgen-react-mui/references/security-patterns.md +599 -0
  75. package/plugin/skills/specgen-react-mui/references/spec-template.md +1030 -0
  76. package/plugin/skills/specgen-react-mui/references/state-patterns.md +327 -0
  77. package/plugin/skills/specgen-react-tailwind/SKILL.md +859 -0
  78. package/plugin/skills/specgen-react-tailwind/references/component-patterns.md +1212 -0
  79. package/plugin/skills/specgen-react-tailwind/references/reporting-patterns.md +1529 -0
  80. package/plugin/skills/specgen-react-tailwind/references/routing-patterns.md +669 -0
  81. package/plugin/skills/specgen-react-tailwind/references/security-patterns.md +656 -0
  82. package/plugin/skills/specgen-react-tailwind/references/spec-template.md +1237 -0
  83. package/plugin/skills/specgen-react-tailwind/references/state-patterns.md +327 -0
  84. package/plugin/skills/specgen-sdk-java/SKILL.md +933 -0
  85. package/plugin/skills/specgen-sdk-java/references/api-extraction-patterns.md +247 -0
  86. package/plugin/skills/specgen-sdk-java/references/http-client-patterns.md +495 -0
  87. package/plugin/skills/specgen-sdk-java/references/packaging-patterns.md +366 -0
  88. package/plugin/skills/specgen-sdk-java/references/spec-template.md +956 -0
  89. package/plugin/skills/specgen-spring-jpa-jtehtmx/SKILL.md +1284 -0
  90. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/batch-patterns.md +1224 -0
  91. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/jasper-patterns.md +1638 -0
  92. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/messaging-patterns.md +512 -0
  93. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/modulith-patterns.md +354 -0
  94. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/security-patterns.md +551 -0
  95. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/spec-template.md +2502 -0
  96. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/web-patterns.md +503 -0
  97. package/plugin/skills/specgen-spring-jpa-restapi/SKILL.md +990 -0
  98. package/plugin/skills/specgen-spring-jpa-restapi/references/batch-patterns.md +1224 -0
  99. package/plugin/skills/specgen-spring-jpa-restapi/references/messaging-patterns.md +512 -0
  100. package/plugin/skills/specgen-spring-jpa-restapi/references/modulith-patterns.md +279 -0
  101. package/plugin/skills/specgen-spring-jpa-restapi/references/reporting-patterns.md +1536 -0
  102. package/plugin/skills/specgen-spring-jpa-restapi/references/restapi-patterns.md +722 -0
  103. package/plugin/skills/specgen-spring-jpa-restapi/references/security-patterns.md +351 -0
  104. package/plugin/skills/specgen-spring-jpa-restapi/references/spec-template.md +553 -0
  105. package/plugin/skills/specgen-ts-cli/SKILL.md +861 -0
  106. package/plugin/skills/specgen-ts-cli/references/async-patterns.md +1101 -0
  107. package/plugin/skills/specgen-ts-cli/references/command-patterns.md +383 -0
  108. package/plugin/skills/specgen-ts-cli/references/config-patterns.md +430 -0
  109. package/plugin/skills/specgen-ts-cli/references/database-patterns.md +750 -0
  110. package/plugin/skills/specgen-ts-cli/references/packaging-patterns.md +426 -0
  111. package/plugin/skills/specgen-ts-cli/references/spec-template.md +1391 -0
  112. package/plugin/skills/specgen-ts-cli/references/testing-patterns.md +909 -0
  113. package/plugin/skills/testgen-functional/SKILL.md +1381 -0
  114. package/plugin/skills/tracegen-matrix/SKILL.md +243 -0
  115. package/plugin/skills/util-preparek8senv/SKILL.md +422 -0
  116. package/plugin/skills/util-projectinit/SKILL.md +307 -0
  117. package/plugin/skills/util-projectinit/reference/brainstorm-loop.md +129 -0
  118. package/plugin/skills/util-projectinit/reference/brainstorm-protocol.md +144 -0
  119. package/plugin/skills/util-projectinit/reference/brainstorm-setup-guide.md +153 -0
  120. package/plugin/skills/util-projectinit/reference/brainstorm-template-appdev.md +87 -0
  121. package/plugin/skills/util-projectinit/reference/co2-context-generation-guide.md +761 -0
  122. package/plugin/skills/util-projectsync/SKILL.md +501 -0
  123. package/plugin/skills/util-updprd/SKILL.md +324 -0
  124. package/plugin/skills/util-usanalyzer/SKILL.md +307 -0
  125. package/plugin/skills/util-ustagger/SKILL.md +154 -0
  126. package/static/assets/abnfDiagram-VRR7QNED-DvPuwyCV.js +1 -0
  127. package/static/assets/arc-DRKw7sjs.js +1 -0
  128. package/static/assets/architectureDiagram-ZJ3FMSHR-CaNcxUbC.js +36 -0
  129. package/static/assets/blockDiagram-677ZJIJ3-Demd4AOK.js +132 -0
  130. package/static/assets/c4Diagram-LMCZKHZV-PVKUfc2x.js +10 -0
  131. package/static/assets/channel-C2nVbuwv.js +1 -0
  132. package/static/assets/chunk-2Q5K7J3B-CExf8ww_.js +1 -0
  133. package/static/assets/chunk-32BRIVSS-CFPaoONF.js +1 -0
  134. package/static/assets/chunk-5VM5RSS4-DE2esM67.js +15 -0
  135. package/static/assets/chunk-EX3LRPZG-SON_YdDG.js +231 -0
  136. package/static/assets/chunk-JWPE2WC7-Qxah8LNr.js +1 -0
  137. package/static/assets/chunk-MOJQB5TN-BcWGQV3j.js +88 -0
  138. package/static/assets/chunk-RYQCIY6F-DkDj-OR5.js +1 -0
  139. package/static/assets/chunk-V7JOEXUC-BlI1Mrah.js +206 -0
  140. package/static/assets/chunk-VR4S4FIN-Agf__duj.js +1 -0
  141. package/static/assets/chunk-XXDRQBXY-B5CfAh85.js +1 -0
  142. package/static/assets/classDiagram-OUVF2IWQ-DXJIljsR.js +1 -0
  143. package/static/assets/classDiagram-v2-EOCWNBFH-DXJIljsR.js +1 -0
  144. package/static/assets/cose-bilkent-JH36ORCC-DO0cQkcy.js +1 -0
  145. package/static/assets/cynefin-VYW2F7L2-CfOnsnXc.js +178 -0
  146. package/static/assets/cynefinDiagram-TSTJHNR4-BJ8tULAv.js +62 -0
  147. package/static/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
  148. package/static/assets/dagre-VKFMJZFB-DfjwzHqP.js +4 -0
  149. package/static/assets/defaultLocale-DX6XiGOO.js +1 -0
  150. package/static/assets/diagram-FQU43EPY-CndSOIIM.js +3 -0
  151. package/static/assets/diagram-G47NLZAW-Czf5hN0Z.js +24 -0
  152. package/static/assets/diagram-NH7WQ7WH-COAyojGh.js +24 -0
  153. package/static/assets/diagram-OA4YK3LP-H8PoiN2m.js +30 -0
  154. package/static/assets/diagram-WEI45ONY-DzcaVzjp.js +41 -0
  155. package/static/assets/ebnfDiagram-CCIWWBDH-DPazGYLJ.js +1 -0
  156. package/static/assets/erDiagram-Q63AITRT-Bo-fubgF.js +85 -0
  157. package/static/assets/flowDiagram-23GEKE2U-CSvxUDwh.js +156 -0
  158. package/static/assets/ganttDiagram-NO4QXBWP-DnBveWX6.js +292 -0
  159. package/static/assets/gitGraphDiagram-IHSO6WYX-B3rVL8c-.js +106 -0
  160. package/static/assets/graph-C9eacEi8.js +1 -0
  161. package/static/assets/index-LZSQkLE2.css +41 -0
  162. package/static/assets/index-y7XDSgMG.js +495 -0
  163. package/static/assets/infoDiagram-FWYZ7A6U-CvWV9yy2.js +2 -0
  164. package/static/assets/init-Gi6I4Gst.js +1 -0
  165. package/static/assets/ishikawaDiagram-FXEZZL3T-DF28a1N6.js +70 -0
  166. package/static/assets/journeyDiagram-5HDEW3XC-CB6CugYk.js +139 -0
  167. package/static/assets/kanban-definition-HUTT4EX6-ResSLF6d.js +89 -0
  168. package/static/assets/katex-C5jXJg4s.js +257 -0
  169. package/static/assets/layout-DEXfKzaS.js +1 -0
  170. package/static/assets/linear-CLO--ryk.js +1 -0
  171. package/static/assets/map-Czzmt4hB.js +1 -0
  172. package/static/assets/mindmap-definition-LN4V7U3C-BvW3EbxX.js +96 -0
  173. package/static/assets/ordinal-Cboi1Yqb.js +1 -0
  174. package/static/assets/pegDiagram-2B236MQR-BHCgqrrM.js +1 -0
  175. package/static/assets/pieDiagram-ENE6RG2P-DVmCQ5FA.js +39 -0
  176. package/static/assets/quadrantDiagram-ABIIQ3AL-B3e5KR52.js +7 -0
  177. package/static/assets/railroadDiagram-RFXS5EU6-Csv5ysLc.js +1 -0
  178. package/static/assets/requirementDiagram-TGXJPOKE-DOMVzvnB.js +84 -0
  179. package/static/assets/sankeyDiagram-HTMAVEWB-B1dcTOvy.js +40 -0
  180. package/static/assets/sequenceDiagram-DBY2YBRQ-BY414yeO.js +162 -0
  181. package/static/assets/sizeCapture-X5ZJPWSS-CuJfrfBN.js +1 -0
  182. package/static/assets/stateDiagram-2N3HPSRC-FX4rJkzV.js +1 -0
  183. package/static/assets/stateDiagram-v2-6OUMAXLB-BVufbO_H.js +1 -0
  184. package/static/assets/swimlanes-5IMT3BWC-Sfy58mmv.js +2 -0
  185. package/static/assets/swimlanesDiagram-G3AALYLV-wDCDw08H.js +8 -0
  186. package/static/assets/timeline-definition-FHXFAJF6-CmV7by8p.js +120 -0
  187. package/static/assets/vennDiagram-L72KCM5P-FqIyFZFt.js +34 -0
  188. package/static/assets/wardleyDiagram-EHGQE667-BZ06O45i.js +78 -0
  189. package/static/assets/xychartDiagram-FW5EYKEG-BOkffR06.js +7 -0
  190. package/static/index.html +24 -0
@@ -0,0 +1,457 @@
1
+ # Notification Patterns — FCM + flutter_local_notifications
2
+
3
+ This reference describes how Firebase Cloud Messaging (push) and
4
+ `flutter_local_notifications` (local/foreground/scheduled) are wired together. Include
5
+ this content in Section 16 of the generated specification.
6
+
7
+ ---
8
+
9
+ ## Why Both Libraries?
10
+
11
+ - **Firebase Messaging (FCM)** delivers messages from the server. It shows system
12
+ notifications automatically when the app is **backgrounded** or **terminated** (data
13
+ + notification payload).
14
+ - **Foreground delivery does NOT auto-display.** On Android and iOS, FCM messages
15
+ received while the app is in the foreground only fire the `onMessage` callback —
16
+ the developer must render a notification (or in-app UI) themselves.
17
+ - **`flutter_local_notifications`** is the rendering library for those foreground FCM
18
+ payloads and for any in-app scheduled reminders (alarms, follow-ups).
19
+
20
+ ---
21
+
22
+ ## Initialization
23
+
24
+ ```dart
25
+ // lib/core/notification/notification_service.dart
26
+ import 'package:firebase_messaging/firebase_messaging.dart';
27
+ import 'package:flutter/foundation.dart';
28
+ import 'package:flutter/material.dart';
29
+ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
30
+
31
+ import 'notification_handler.dart';
32
+
33
+ class NotificationService {
34
+ NotificationService._();
35
+ static final NotificationService instance = NotificationService._();
36
+
37
+ final FlutterLocalNotificationsPlugin _local = FlutterLocalNotificationsPlugin();
38
+ final FirebaseMessaging _fcm = FirebaseMessaging.instance;
39
+
40
+ /// Android notification channel for default notifications. Must match the channel
41
+ /// id referenced in AndroidManifest.xml (`default_notification_channel_id`).
42
+ static const AndroidNotificationChannel _defaultChannel = AndroidNotificationChannel(
43
+ 'default',
44
+ 'General Notifications',
45
+ description: 'Default channel for app notifications',
46
+ importance: Importance.high,
47
+ );
48
+
49
+ Future<void> init() async {
50
+ await _initLocal();
51
+ await _initFcm();
52
+ }
53
+
54
+ Future<void> _initLocal() async {
55
+ const androidInit = AndroidInitializationSettings('@mipmap/ic_launcher');
56
+ const iosInit = DarwinInitializationSettings(
57
+ requestAlertPermission: false, // request explicitly via permission flow
58
+ requestBadgePermission: false,
59
+ requestSoundPermission: false,
60
+ );
61
+ const initSettings = InitializationSettings(android: androidInit, iOS: iosInit);
62
+
63
+ await _local.initialize(
64
+ initSettings,
65
+ onDidReceiveNotificationResponse: NotificationHandler.onLocalTap,
66
+ );
67
+
68
+ final androidPlugin =
69
+ _local.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
70
+ await androidPlugin?.createNotificationChannel(_defaultChannel);
71
+ }
72
+
73
+ Future<void> _initFcm() async {
74
+ // Request permission (iOS + Android 13+)
75
+ await _fcm.requestPermission(
76
+ alert: true,
77
+ announcement: false,
78
+ badge: true,
79
+ carPlay: false,
80
+ criticalAlert: false,
81
+ provisional: false,
82
+ sound: true,
83
+ );
84
+
85
+ // Foreground presentation options for iOS (banner + sound while app is open)
86
+ await _fcm.setForegroundNotificationPresentationOptions(
87
+ alert: true,
88
+ badge: true,
89
+ sound: true,
90
+ );
91
+
92
+ // Foreground message handler — show as local notification
93
+ FirebaseMessaging.onMessage.listen(NotificationHandler.onForegroundMessage);
94
+
95
+ // Notification tap (from background → foreground)
96
+ FirebaseMessaging.onMessageOpenedApp.listen(NotificationHandler.onMessageOpenedApp);
97
+
98
+ // Cold start (terminated → foreground via notification tap)
99
+ final initialMessage = await _fcm.getInitialMessage();
100
+ if (initialMessage != null) {
101
+ // Defer until router is mounted
102
+ WidgetsBinding.instance.addPostFrameCallback((_) {
103
+ NotificationHandler.onMessageOpenedApp(initialMessage);
104
+ });
105
+ }
106
+ }
107
+
108
+ Future<String?> fcmToken() => _fcm.getToken();
109
+
110
+ Stream<String> onTokenRefresh() => _fcm.onTokenRefresh;
111
+
112
+ /// Subscribe to a topic (e.g., for role-based broadcasts)
113
+ Future<void> subscribeToTopic(String topic) => _fcm.subscribeToTopic(topic);
114
+
115
+ Future<void> unsubscribeFromTopic(String topic) => _fcm.unsubscribeFromTopic(topic);
116
+
117
+ /// Show a local notification immediately. Used to display foreground FCM payloads.
118
+ Future<void> showLocal({
119
+ required String title,
120
+ required String body,
121
+ String? payload,
122
+ }) async {
123
+ const details = NotificationDetails(
124
+ android: AndroidNotificationDetails(
125
+ 'default',
126
+ 'General Notifications',
127
+ importance: Importance.high,
128
+ priority: Priority.high,
129
+ ),
130
+ iOS: DarwinNotificationDetails(
131
+ presentAlert: true,
132
+ presentBadge: true,
133
+ presentSound: true,
134
+ ),
135
+ );
136
+ await _local.show(
137
+ DateTime.now().millisecondsSinceEpoch.remainder(1 << 31),
138
+ title,
139
+ body,
140
+ details,
141
+ payload: payload,
142
+ );
143
+ }
144
+
145
+ /// Schedule a local notification at a specific time (e.g., reminder).
146
+ Future<void> schedule({
147
+ required int id,
148
+ required String title,
149
+ required String body,
150
+ required DateTime scheduledAt,
151
+ String? payload,
152
+ }) async {
153
+ const details = NotificationDetails(
154
+ android: AndroidNotificationDetails(
155
+ 'default',
156
+ 'General Notifications',
157
+ importance: Importance.high,
158
+ priority: Priority.high,
159
+ ),
160
+ iOS: DarwinNotificationDetails(),
161
+ );
162
+ // Note: `tz.TZDateTime` from package:timezone is preferred; for brevity use
163
+ // local DateTime here. In a production app, initialise timezones in main().
164
+ await _local.zonedSchedule(
165
+ id,
166
+ title,
167
+ body,
168
+ // Convert to tz.TZDateTime.from(scheduledAt, tz.local) in production
169
+ scheduledAt as dynamic,
170
+ details,
171
+ androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
172
+ payload: payload,
173
+ uiLocalNotificationDateInterpretation:
174
+ UILocalNotificationDateInterpretation.absoluteTime,
175
+ );
176
+ }
177
+
178
+ Future<void> cancel(int id) => _local.cancel(id);
179
+ Future<void> cancelAll() => _local.cancelAll();
180
+ }
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Handlers
186
+
187
+ ```dart
188
+ // lib/core/notification/notification_handler.dart
189
+ import 'package:firebase_messaging/firebase_messaging.dart';
190
+ import 'package:flutter/foundation.dart';
191
+ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
192
+
193
+ import 'notification_navigation.dart';
194
+ import 'notification_service.dart';
195
+
196
+ class NotificationHandler {
197
+ NotificationHandler._();
198
+
199
+ /// Called when an FCM message is received while the app is in the foreground.
200
+ /// FCM does NOT auto-display these — render a local notification instead.
201
+ static Future<void> onForegroundMessage(RemoteMessage message) async {
202
+ final notification = message.notification;
203
+ if (notification == null) return;
204
+
205
+ await NotificationService.instance.showLocal(
206
+ title: notification.title ?? '',
207
+ body: notification.body ?? '',
208
+ payload: message.data['deeplink'] as String?,
209
+ );
210
+ }
211
+
212
+ /// Called when the user taps an FCM notification (background or terminated).
213
+ static Future<void> onMessageOpenedApp(RemoteMessage message) async {
214
+ final deeplink = message.data['deeplink'] as String?;
215
+ if (deeplink != null) {
216
+ NotificationNavigator.go(deeplink);
217
+ }
218
+ }
219
+
220
+ /// Called when the user taps a local notification (in-app reminders or
221
+ /// foreground-rendered FCM messages).
222
+ static void onLocalTap(NotificationResponse response) {
223
+ final deeplink = response.payload;
224
+ if (deeplink != null && deeplink.isNotEmpty) {
225
+ NotificationNavigator.go(deeplink);
226
+ }
227
+ }
228
+ }
229
+ ```
230
+
231
+ ---
232
+
233
+ ## Background Message Handler (Top-level)
234
+
235
+ The background handler MUST be a top-level (not class member) function annotated with
236
+ `@pragma('vm:entry-point')` so it survives release-mode tree shaking. It is registered
237
+ in `main.dart` (already shown):
238
+
239
+ ```dart
240
+ @pragma('vm:entry-point')
241
+ Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
242
+ await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
243
+ // No UI work here — the system displays the notification automatically when the
244
+ // payload includes a `notification:` block. Only do background data processing.
245
+ }
246
+ ```
247
+
248
+ ---
249
+
250
+ ## Deep-Link Navigation From Notifications
251
+
252
+ The router lives inside a Riverpod provider, so we expose the root `ProviderContainer`
253
+ in `app.dart` and reach it from outside the widget tree:
254
+
255
+ ```dart
256
+ // lib/core/notification/notification_navigation.dart
257
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
258
+
259
+ import '../../router/app_router.dart';
260
+
261
+ class NotificationNavigator {
262
+ NotificationNavigator._();
263
+
264
+ /// Root container set by the app on startup.
265
+ static ProviderContainer? container;
266
+
267
+ static void go(String location) {
268
+ final c = container;
269
+ if (c == null) return;
270
+ final router = c.read(appRouterProvider);
271
+ router.go(location);
272
+ }
273
+ }
274
+ ```
275
+
276
+ In `main.dart`, expose the container:
277
+
278
+ ```dart
279
+ void main() async {
280
+ // ...existing init...
281
+ final container = ProviderContainer();
282
+ NotificationNavigator.container = container;
283
+ runApp(
284
+ UncontrolledProviderScope(container: container, child: const MyApp()),
285
+ );
286
+ }
287
+ ```
288
+
289
+ ---
290
+
291
+ ## Backend Token Registration
292
+
293
+ Register the FCM token with the backend on first sign-in and on token refresh:
294
+
295
+ ```dart
296
+ // inside auth flow, after successful login:
297
+ final token = await NotificationService.instance.fcmToken();
298
+ if (token != null) {
299
+ await dio.post('/users/me/fcm-token', data: {'token': token});
300
+ }
301
+
302
+ NotificationService.instance.onTokenRefresh().listen((newToken) async {
303
+ await dio.post('/users/me/fcm-token', data: {'token': newToken});
304
+ });
305
+ ```
306
+
307
+ Unregister the token on logout:
308
+
309
+ ```dart
310
+ await dio.delete('/users/me/fcm-token');
311
+ await FirebaseMessaging.instance.deleteToken();
312
+ ```
313
+
314
+ ---
315
+
316
+ ## Permission Handling
317
+
318
+ | Platform | Behaviour |
319
+ |---|---|
320
+ | iOS | `FirebaseMessaging.requestPermission()` triggers the system prompt |
321
+ | Android 13+ | `POST_NOTIFICATIONS` runtime permission required |
322
+ | Android <13 | Notifications enabled by default |
323
+
324
+ ```dart
325
+ // Example Android 13+ permission flow using permission_handler (if added):
326
+ import 'package:permission_handler/permission_handler.dart';
327
+
328
+ final status = await Permission.notification.request();
329
+ if (status.isDenied) {
330
+ // Surface a settings prompt
331
+ }
332
+ ```
333
+
334
+ ---
335
+
336
+ ## Notification Payload Contract
337
+
338
+ The backend should send messages in this shape so the client can route consistently:
339
+
340
+ ```json
341
+ {
342
+ "notification": {
343
+ "title": "New task assigned",
344
+ "body": "Inspect site #42"
345
+ },
346
+ "data": {
347
+ "deeplink": "/task/abc-123",
348
+ "category": "task"
349
+ }
350
+ }
351
+ ```
352
+
353
+ `deeplink` is read by `NotificationHandler.onMessageOpenedApp` and passed to
354
+ `router.go(...)`.
355
+
356
+ ---
357
+
358
+ ## Topic Subscription Strategy
359
+
360
+ Topics are useful for role/segment broadcasts (e.g., all workers, all admins):
361
+
362
+ ```dart
363
+ // On login, subscribe to role-based topics
364
+ for (final role in user.roles) {
365
+ await NotificationService.instance.subscribeToTopic('role-${role.toLowerCase()}');
366
+ }
367
+
368
+ // On logout, unsubscribe
369
+ for (final role in user.roles) {
370
+ await NotificationService.instance.unsubscribeFromTopic('role-${role.toLowerCase()}');
371
+ }
372
+ ```
373
+
374
+ ---
375
+
376
+ ## Scheduled Local Reminders
377
+
378
+ Use `zonedSchedule` for time-based reminders (e.g., "Inspect site at 09:00 tomorrow"):
379
+
380
+ ```dart
381
+ await NotificationService.instance.schedule(
382
+ id: task.hashCode,
383
+ title: 'Task reminder',
384
+ body: task.title,
385
+ scheduledAt: task.dueDate.subtract(const Duration(hours: 1)),
386
+ payload: '/task/${task.id}',
387
+ );
388
+ ```
389
+
390
+ For production, initialise the `timezone` package in `main()`:
391
+
392
+ ```dart
393
+ // pubspec.yaml → dependencies: timezone: ^0.9.4
394
+ import 'package:timezone/data/latest_all.dart' as tz;
395
+ import 'package:timezone/timezone.dart' as tz;
396
+
397
+ tz.initializeTimeZones();
398
+ tz.setLocalLocation(tz.getLocation('Asia/Kuala_Lumpur')); // or detect from device
399
+ ```
400
+
401
+ ---
402
+
403
+ ## iOS-Specific Notes
404
+
405
+ - Add the **Push Notifications** capability in Xcode (Runner target → Signing &
406
+ Capabilities)
407
+ - Add the **Background Modes → Remote notifications** capability
408
+ - Upload an **APNs Authentication Key** to Firebase Console → Project Settings → Cloud
409
+ Messaging
410
+ - `Info.plist` must include `NSUserNotificationUsageDescription` if you display
411
+ notifications while the device is locked
412
+
413
+ ---
414
+
415
+ ## Android-Specific Notes
416
+
417
+ - The Android `default` channel MUST be created on app startup (done in
418
+ `_initLocal`). Otherwise high-importance notifications fall back to silent on
419
+ Android 8.0+.
420
+ - `AndroidManifest.xml` includes the channel meta-data so FCM uses the correct channel
421
+ for background-delivered notifications:
422
+
423
+ ```xml
424
+ <meta-data
425
+ android:name="com.google.firebase.messaging.default_notification_channel_id"
426
+ android:value="default" />
427
+ ```
428
+
429
+ - Add the Android 13+ `POST_NOTIFICATIONS` permission to `AndroidManifest.xml`:
430
+
431
+ ```xml
432
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
433
+ ```
434
+
435
+ ---
436
+
437
+ ## Testing Notifications Locally
438
+
439
+ - **FCM from console**: Firebase Console → Cloud Messaging → New campaign → Test on
440
+ device using FCM token printed at startup
441
+ - **Local foreground**: trigger `NotificationService.instance.showLocal(...)` from a
442
+ debug button
443
+ - **Scheduled local**: short-delay schedule (`DateTime.now().add(Duration(seconds: 5))`)
444
+ to verify channel and tap routing
445
+
446
+ ---
447
+
448
+ ## Common Pitfalls
449
+
450
+ | Pitfall | Mitigation |
451
+ |---|---|
452
+ | Foreground FCM never shown | You MUST manually render a local notification — FCM does not auto-display |
453
+ | Android 13+ silent | Request `POST_NOTIFICATIONS` runtime permission |
454
+ | iOS background not delivered | Ensure APNs key uploaded to Firebase, Background Modes enabled |
455
+ | Deep-link doesn't navigate on cold start | Read `getInitialMessage()` AFTER router mounts (use `addPostFrameCallback`) |
456
+ | FCM token churns after reinstall | Re-register on every app launch, deduplicate server-side |
457
+ | Scheduled notifications not firing | Confirm `androidScheduleMode: exactAllowWhileIdle` and request `SCHEDULE_EXACT_ALARM` on Android 12+ |