timsquad 3.3.0 โ†’ 3.4.0

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 (135) hide show
  1. package/README.ko.md +288 -0
  2. package/README.md +158 -151
  3. package/dist/commands/compile.d.ts +3 -0
  4. package/dist/commands/compile.d.ts.map +1 -0
  5. package/dist/commands/compile.js +170 -0
  6. package/dist/commands/compile.js.map +1 -0
  7. package/dist/commands/daemon.d.ts.map +1 -1
  8. package/dist/commands/daemon.js +94 -5
  9. package/dist/commands/daemon.js.map +1 -1
  10. package/dist/commands/init.d.ts.map +1 -1
  11. package/dist/commands/init.js +12 -3
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/commands/skills.d.ts +12 -0
  14. package/dist/commands/skills.d.ts.map +1 -0
  15. package/dist/commands/skills.js +231 -0
  16. package/dist/commands/skills.js.map +1 -0
  17. package/dist/commands/upgrade.js +5 -0
  18. package/dist/commands/upgrade.js.map +1 -1
  19. package/dist/daemon/entry.js +3 -3
  20. package/dist/daemon/entry.js.map +1 -1
  21. package/dist/daemon/index.d.ts +3 -2
  22. package/dist/daemon/index.d.ts.map +1 -1
  23. package/dist/daemon/index.js +137 -45
  24. package/dist/daemon/index.js.map +1 -1
  25. package/dist/daemon/meta-cache.d.ts +1 -0
  26. package/dist/daemon/meta-cache.d.ts.map +1 -1
  27. package/dist/daemon/meta-cache.js +9 -0
  28. package/dist/daemon/meta-cache.js.map +1 -1
  29. package/dist/daemon/session-state.d.ts +19 -0
  30. package/dist/daemon/session-state.d.ts.map +1 -0
  31. package/dist/daemon/session-state.js +132 -0
  32. package/dist/daemon/session-state.js.map +1 -0
  33. package/dist/daemon/shutdown.d.ts.map +1 -1
  34. package/dist/daemon/shutdown.js +7 -1
  35. package/dist/daemon/shutdown.js.map +1 -1
  36. package/dist/index.js +4 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/lib/compile-rules.d.ts +66 -0
  39. package/dist/lib/compile-rules.d.ts.map +1 -0
  40. package/dist/lib/compile-rules.js +114 -0
  41. package/dist/lib/compile-rules.js.map +1 -0
  42. package/dist/lib/compiler.d.ts +105 -0
  43. package/dist/lib/compiler.d.ts.map +1 -0
  44. package/dist/lib/compiler.js +368 -0
  45. package/dist/lib/compiler.js.map +1 -0
  46. package/dist/lib/config.d.ts +1 -0
  47. package/dist/lib/config.d.ts.map +1 -1
  48. package/dist/lib/config.js +8 -1
  49. package/dist/lib/config.js.map +1 -1
  50. package/dist/lib/template.d.ts.map +1 -1
  51. package/dist/lib/template.js +6 -0
  52. package/dist/lib/template.js.map +1 -1
  53. package/dist/types/config.d.ts +1 -0
  54. package/dist/types/config.d.ts.map +1 -1
  55. package/dist/types/config.js +12 -1
  56. package/dist/types/config.js.map +1 -1
  57. package/dist/types/project.d.ts +1 -1
  58. package/dist/types/project.d.ts.map +1 -1
  59. package/dist/types/project.js +2 -0
  60. package/dist/types/project.js.map +1 -1
  61. package/package.json +1 -1
  62. package/templates/base/agents/overlays/domain/mobile/_common.md +13 -0
  63. package/templates/base/skills/controller/SKILL.md +111 -0
  64. package/templates/base/skills/controller/references/README.md +35 -0
  65. package/templates/base/skills/controller/rules/README.md +18 -0
  66. package/templates/base/skills/mobile/dart/SKILL.md +69 -0
  67. package/templates/base/skills/mobile/dart/rules/async-patterns.md +112 -0
  68. package/templates/base/skills/mobile/dart/rules/code-style.md +96 -0
  69. package/templates/base/skills/mobile/dart/rules/null-safety.md +84 -0
  70. package/templates/base/skills/mobile/dart/rules/type-system.md +111 -0
  71. package/templates/base/skills/mobile/flutter/SKILL.md +89 -0
  72. package/templates/base/skills/mobile/flutter/ci-cd/SKILL.md +82 -0
  73. package/templates/base/skills/mobile/flutter/ci-cd/references/ci-cd-pipeline.md +314 -0
  74. package/templates/base/skills/mobile/flutter/ci-cd/rules/code-signing.md +106 -0
  75. package/templates/base/skills/mobile/flutter/ci-cd/rules/codemagic-setup.md +116 -0
  76. package/templates/base/skills/mobile/flutter/ci-cd/rules/fastlane-setup.md +105 -0
  77. package/templates/base/skills/mobile/flutter/ci-cd/rules/github-actions.md +112 -0
  78. package/templates/base/skills/mobile/flutter/ci-cd/rules/store-deployment.md +106 -0
  79. package/templates/base/skills/mobile/flutter/ci-cd/rules/versioning.md +107 -0
  80. package/templates/base/skills/mobile/flutter/i18n/SKILL.md +78 -0
  81. package/templates/base/skills/mobile/flutter/i18n/references/i18n-architecture.md +225 -0
  82. package/templates/base/skills/mobile/flutter/i18n/rules/arb-files.md +182 -0
  83. package/templates/base/skills/mobile/flutter/i18n/rules/locale-switching.md +226 -0
  84. package/templates/base/skills/mobile/flutter/i18n/rules/localization-setup.md +137 -0
  85. package/templates/base/skills/mobile/flutter/i18n/rules/plural-gender.md +159 -0
  86. package/templates/base/skills/mobile/flutter/i18n/rules/text-direction.md +199 -0
  87. package/templates/base/skills/mobile/flutter/monitoring/SKILL.md +81 -0
  88. package/templates/base/skills/mobile/flutter/monitoring/references/monitoring-architecture.md +269 -0
  89. package/templates/base/skills/mobile/flutter/monitoring/rules/analytics.md +227 -0
  90. package/templates/base/skills/mobile/flutter/monitoring/rules/crashlytics-setup.md +195 -0
  91. package/templates/base/skills/mobile/flutter/monitoring/rules/logging.md +258 -0
  92. package/templates/base/skills/mobile/flutter/monitoring/rules/performance-monitoring.md +248 -0
  93. package/templates/base/skills/mobile/flutter/monitoring/rules/sentry-integration.md +249 -0
  94. package/templates/base/skills/mobile/flutter/networking/SKILL.md +88 -0
  95. package/templates/base/skills/mobile/flutter/networking/references/api-client-architecture.md +305 -0
  96. package/templates/base/skills/mobile/flutter/networking/rules/caching.md +212 -0
  97. package/templates/base/skills/mobile/flutter/networking/rules/connectivity.md +213 -0
  98. package/templates/base/skills/mobile/flutter/networking/rules/dio-setup.md +159 -0
  99. package/templates/base/skills/mobile/flutter/networking/rules/error-handling.md +209 -0
  100. package/templates/base/skills/mobile/flutter/networking/rules/interceptors.md +205 -0
  101. package/templates/base/skills/mobile/flutter/networking/rules/retrofit-patterns.md +194 -0
  102. package/templates/base/skills/mobile/flutter/push-notifications/SKILL.md +87 -0
  103. package/templates/base/skills/mobile/flutter/push-notifications/references/notification-architecture.md +340 -0
  104. package/templates/base/skills/mobile/flutter/push-notifications/references/platform-setup.md +286 -0
  105. package/templates/base/skills/mobile/flutter/push-notifications/rules/background-processing.md +308 -0
  106. package/templates/base/skills/mobile/flutter/push-notifications/rules/deep-linking.md +217 -0
  107. package/templates/base/skills/mobile/flutter/push-notifications/rules/fcm-setup.md +164 -0
  108. package/templates/base/skills/mobile/flutter/push-notifications/rules/local-notifications.md +262 -0
  109. package/templates/base/skills/mobile/flutter/push-notifications/rules/notification-handling.md +210 -0
  110. package/templates/base/skills/mobile/flutter/push-notifications/rules/notification-permissions.md +246 -0
  111. package/templates/base/skills/mobile/flutter/push-notifications/rules/rich-notifications.md +320 -0
  112. package/templates/base/skills/mobile/flutter/references/freezed-patterns.md +162 -0
  113. package/templates/base/skills/mobile/flutter/references/project-structure.md +170 -0
  114. package/templates/base/skills/mobile/flutter/rules/animations.md +112 -0
  115. package/templates/base/skills/mobile/flutter/rules/architecture.md +121 -0
  116. package/templates/base/skills/mobile/flutter/rules/navigation-routing.md +117 -0
  117. package/templates/base/skills/mobile/flutter/rules/performance.md +112 -0
  118. package/templates/base/skills/mobile/flutter/rules/platform-adaptive.md +126 -0
  119. package/templates/base/skills/mobile/flutter/rules/state-management.md +110 -0
  120. package/templates/base/skills/mobile/flutter/rules/testing.md +131 -0
  121. package/templates/base/skills/mobile/flutter/rules/widget-conventions.md +122 -0
  122. package/templates/base/skills/mobile/flutter/security/SKILL.md +86 -0
  123. package/templates/base/skills/mobile/flutter/security/references/mobile-security-checklist.md +168 -0
  124. package/templates/base/skills/mobile/flutter/security/rules/api-key-protection.md +206 -0
  125. package/templates/base/skills/mobile/flutter/security/rules/authentication.md +248 -0
  126. package/templates/base/skills/mobile/flutter/security/rules/data-protection.md +271 -0
  127. package/templates/base/skills/mobile/flutter/security/rules/obfuscation.md +213 -0
  128. package/templates/base/skills/mobile/flutter/security/rules/secure-storage.md +171 -0
  129. package/templates/base/skills/mobile/flutter/security/rules/ssl-pinning.md +197 -0
  130. package/templates/platforms/claude-code/CLAUDE.md.template +25 -0
  131. package/templates/platforms/claude-code/scripts/completion-guard.sh +57 -0
  132. package/templates/platforms/claude-code/scripts/phase-guard.sh +79 -0
  133. package/templates/platforms/claude-code/settings.json +75 -3
  134. package/templates/project-types/mobile-app/config.yaml +123 -0
  135. package/templates/project-types/mobile-app/process/workflow.xml +191 -0
@@ -0,0 +1,340 @@
1
+ ---
2
+ title: Notification Service Architecture
3
+ category: guide
4
+ source: internal
5
+ tags: architecture, service, directory, testing, analytics
6
+ ---
7
+
8
+ # Notification Service Architecture
9
+
10
+ ์•Œ๋ฆผ ์„œ๋น„์Šค ์ „์ฒด ์•„ํ‚คํ…์ฒ˜. ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ, ์„œ๋น„์Šค ๋ ˆ์ด์–ด, ํ…Œ์ŠคํŠธ ์ „๋žต, ๋ถ„์„.
11
+
12
+ ## Key Concepts
13
+
14
+ - **์ค‘์•™ํ™”**: ์•Œ๋ฆผ ๊ด€๋ จ ์ฝ”๋“œ๋ฅผ `core/notifications/` ์— ์ง‘์ค‘ (feature ํšก๋‹จ ๊ด€์‹ฌ์‚ฌ)
15
+ - **์ถ”์ƒํ™”**: `NotificationService` ์ธํ„ฐํŽ˜์ด์Šค โ†’ FCM/Local/Mock ๊ตฌํ˜„ ๋ถ„๋ฆฌ
16
+ - **ํŽ˜์ด๋กœ๋“œ ํ‘œ์ค€ํ™”**: ์„œ๋ฒ„-ํด๋ผ์ด์–ธํŠธ ๊ฐ„ ์•Œ๋ฆผ ๋ฐ์ดํ„ฐ ๊ณ„์•ฝ
17
+ - **ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ์„ฑ**: Mock ๊ตฌํ˜„์œผ๋กœ ์•Œ๋ฆผ ๋กœ์ง ๋‹จ์œ„ ํ…Œ์ŠคํŠธ
18
+
19
+ ## Directory Structure
20
+
21
+ ```
22
+ lib/
23
+ โ”œโ”€โ”€ core/
24
+ โ”‚ โ””โ”€โ”€ notifications/
25
+ โ”‚ โ”œโ”€โ”€ notification_service.dart # ์ถ”์ƒ ์ธํ„ฐํŽ˜์ด์Šค
26
+ โ”‚ โ”œโ”€โ”€ notification_service_impl.dart # ํ†ตํ•ฉ ๊ตฌํ˜„์ฒด
27
+ โ”‚ โ”œโ”€โ”€ fcm/
28
+ โ”‚ โ”‚ โ”œโ”€โ”€ fcm_service.dart # FCM ์ดˆ๊ธฐํ™” + ํ† ํฐ
29
+ โ”‚ โ”‚ โ”œโ”€โ”€ fcm_token_manager.dart # ํ† ํฐ ์„œ๋ฒ„ ๋™๊ธฐํ™”
30
+ โ”‚ โ”‚ โ””โ”€โ”€ fcm_message_handler.dart # ๋ฉ”์‹œ์ง€ ๋ผ์šฐํŒ…
31
+ โ”‚ โ”œโ”€โ”€ local/
32
+ โ”‚ โ”‚ โ”œโ”€โ”€ local_notification_service.dart # flutter_local_notifications ๋ž˜ํผ
33
+ โ”‚ โ”‚ โ”œโ”€โ”€ notification_channels.dart # Android ์ฑ„๋„ ์ •์˜
34
+ โ”‚ โ”‚ โ””โ”€โ”€ scheduled_notification.dart # ์Šค์ผ€์ค„ ์•Œ๋ฆผ ๊ด€๋ฆฌ
35
+ โ”‚ โ”œโ”€โ”€ models/
36
+ โ”‚ โ”‚ โ”œโ”€โ”€ notification_payload.dart # ํŽ˜์ด๋กœ๋“œ ๋ชจ๋ธ
37
+ โ”‚ โ”‚ โ”œโ”€โ”€ notification_channel.dart # ์ฑ„๋„ enum + ์„ค์ •
38
+ โ”‚ โ”‚ โ””โ”€โ”€ notification_action.dart # ์•ก์…˜ ๋ฒ„ํŠผ ๋ชจ๋ธ
39
+ โ”‚ โ”œโ”€โ”€ navigation/
40
+ โ”‚ โ”‚ โ””โ”€โ”€ notification_navigator.dart # ๋”ฅ๋งํฌ ๋„ค๋น„๊ฒŒ์ด์…˜
41
+ โ”‚ โ”œโ”€โ”€ permission/
42
+ โ”‚ โ”‚ โ”œโ”€โ”€ permission_service.dart # ๊ถŒํ•œ ๊ด€๋ฆฌ
43
+ โ”‚ โ”‚ โ””โ”€โ”€ permission_prompt_controller.dart # ํ”„๋ฆฌํ”„๋กฌํ”„ํŠธ ๋กœ์ง
44
+ โ”‚ โ”œโ”€โ”€ background/
45
+ โ”‚ โ”‚ โ”œโ”€โ”€ background_tasks.dart # top-level ์ฝœ๋ฐฑ
46
+ โ”‚ โ”‚ โ””โ”€โ”€ background_task_manager.dart # ํƒœ์Šคํฌ ๋“ฑ๋ก/์ทจ์†Œ
47
+ โ”‚ โ””โ”€โ”€ providers/
48
+ โ”‚ โ””โ”€โ”€ notification_providers.dart # Riverpod providers
49
+ โ”‚
50
+ โ”œโ”€โ”€ features/
51
+ โ”‚ โ””โ”€โ”€ notifications/ # ์•Œ๋ฆผ ๋ชฉ๋ก UI (feature)
52
+ โ”‚ โ”œโ”€โ”€ data/
53
+ โ”‚ โ”‚ โ”œโ”€โ”€ datasources/
54
+ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ notification_local_datasource.dart
55
+ โ”‚ โ”‚ โ””โ”€โ”€ repositories/
56
+ โ”‚ โ”‚ โ””โ”€โ”€ notification_repository_impl.dart
57
+ โ”‚ โ”œโ”€โ”€ domain/
58
+ โ”‚ โ”‚ โ”œโ”€โ”€ entities/
59
+ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ app_notification.dart # ์•ฑ ๋‚ด ์•Œ๋ฆผ ๋ชจ๋ธ
60
+ โ”‚ โ”‚ โ””โ”€โ”€ repositories/
61
+ โ”‚ โ”‚ โ””โ”€โ”€ notification_repository.dart # abstract
62
+ โ”‚ โ””โ”€โ”€ presentation/
63
+ โ”‚ โ”œโ”€โ”€ screens/
64
+ โ”‚ โ”‚ โ””โ”€โ”€ notification_list_screen.dart
65
+ โ”‚ โ”œโ”€โ”€ widgets/
66
+ โ”‚ โ”‚ โ””โ”€โ”€ notification_card.dart
67
+ โ”‚ โ””โ”€โ”€ providers/
68
+ โ”‚ โ””โ”€โ”€ notification_list_provider.dart
69
+ ```
70
+
71
+ ## Service Layer Design
72
+
73
+ ```dart
74
+ /// ์•Œ๋ฆผ ์„œ๋น„์Šค ์ธํ„ฐํŽ˜์ด์Šค (ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ)
75
+ abstract class NotificationService {
76
+ Future<void> initialize();
77
+ Future<void> showNotification({
78
+ required int id,
79
+ required String title,
80
+ required String body,
81
+ String? payload,
82
+ String? imageUrl,
83
+ String channelId,
84
+ });
85
+ Future<void> scheduleNotification({
86
+ required int id,
87
+ required String title,
88
+ required String body,
89
+ required DateTime scheduledTime,
90
+ String? payload,
91
+ });
92
+ Future<void> cancelNotification(int id);
93
+ Future<void> cancelAll();
94
+ Future<String?> getFcmToken();
95
+ Stream<String> get onTokenRefresh;
96
+ Future<AuthorizationStatus> requestPermission();
97
+ Future<AuthorizationStatus> getPermissionStatus();
98
+ }
99
+
100
+ /// ํ†ตํ•ฉ ๊ตฌํ˜„์ฒด
101
+ class NotificationServiceImpl implements NotificationService {
102
+ final FcmService _fcmService;
103
+ final LocalNotificationService _localService;
104
+ final PermissionService _permissionService;
105
+
106
+ NotificationServiceImpl({
107
+ required FcmService fcmService,
108
+ required LocalNotificationService localService,
109
+ required PermissionService permissionService,
110
+ }) : _fcmService = fcmService,
111
+ _localService = localService,
112
+ _permissionService = permissionService;
113
+
114
+ @override
115
+ Future<void> initialize() async {
116
+ await _fcmService.initialize();
117
+ await _localService.initialize();
118
+ }
119
+
120
+ @override
121
+ Future<void> showNotification({
122
+ required int id,
123
+ required String title,
124
+ required String body,
125
+ String? payload,
126
+ String? imageUrl,
127
+ String channelId = 'default_channel',
128
+ }) async {
129
+ await _localService.show(
130
+ id: id,
131
+ title: title,
132
+ body: body,
133
+ payload: payload,
134
+ imageUrl: imageUrl,
135
+ channelId: channelId,
136
+ );
137
+ }
138
+
139
+ // ... ๋‚˜๋จธ์ง€ ๊ตฌํ˜„
140
+ }
141
+
142
+ /// Riverpod Provider ๊ตฌ์„ฑ
143
+ final notificationServiceProvider = Provider<NotificationService>((ref) {
144
+ return NotificationServiceImpl(
145
+ fcmService: ref.watch(fcmServiceProvider),
146
+ localService: ref.watch(localNotificationServiceProvider),
147
+ permissionService: ref.watch(permissionServiceProvider),
148
+ );
149
+ });
150
+ ```
151
+
152
+ ## Initialization Flow
153
+
154
+ ```
155
+ ์•ฑ ์‹œ์ž‘ (main.dart)
156
+ โ”‚
157
+ โ”œโ”€ 1. WidgetsFlutterBinding.ensureInitialized()
158
+ โ”œโ”€ 2. Firebase.initializeApp()
159
+ โ”œโ”€ 3. FirebaseMessaging.onBackgroundMessage(handler) โ† top-level ๋“ฑ๋ก
160
+ โ”œโ”€ 4. NotificationService.initialize()
161
+ โ”‚ โ”œโ”€ FcmService.initialize()
162
+ โ”‚ โ”‚ โ”œโ”€ FCM ํ† ํฐ ํš๋“ + ์„œ๋ฒ„ ๋™๊ธฐํ™”
163
+ โ”‚ โ”‚ โ””โ”€ onTokenRefresh ๊ตฌ๋…
164
+ โ”‚ โ””โ”€ LocalNotificationService.initialize()
165
+ โ”‚ โ”œโ”€ Android ์ฑ„๋„ ์ƒ์„ฑ
166
+ โ”‚ โ””โ”€ ํƒญ ์ฝœ๋ฐฑ ๋“ฑ๋ก
167
+ โ”œโ”€ 5. Workmanager.initialize(callbackDispatcher)
168
+ โ””โ”€ 6. runApp(ProviderScope(child: MyApp()))
169
+ โ”‚
170
+ โ””โ”€ MyApp.initState()
171
+ โ””โ”€ NotificationDeepLinkHandler.initialize()
172
+ โ”œโ”€ getInitialMessage() โ†’ ๋”ฅ๋งํฌ (cold start)
173
+ โ”œโ”€ onMessageOpenedApp.listen โ†’ ๋”ฅ๋งํฌ (background)
174
+ โ””โ”€ onMessage.listen โ†’ ํฌ๊ทธ๋ผ์šด๋“œ ์•Œ๋ฆผ ํ‘œ์‹œ
175
+ ```
176
+
177
+ ## Testing Strategy
178
+
179
+ ```dart
180
+ /// Mock ์•Œ๋ฆผ ์„œ๋น„์Šค (๋‹จ์œ„ ํ…Œ์ŠคํŠธ์šฉ)
181
+ class MockNotificationService implements NotificationService {
182
+ final List<Map<String, dynamic>> shownNotifications = [];
183
+ final List<Map<String, dynamic>> scheduledNotifications = [];
184
+ AuthorizationStatus _permissionStatus = AuthorizationStatus.authorized;
185
+
186
+ @override
187
+ Future<void> showNotification({
188
+ required int id,
189
+ required String title,
190
+ required String body,
191
+ String? payload,
192
+ String? imageUrl,
193
+ String channelId = 'default_channel',
194
+ }) async {
195
+ shownNotifications.add({
196
+ 'id': id,
197
+ 'title': title,
198
+ 'body': body,
199
+ 'payload': payload,
200
+ 'channelId': channelId,
201
+ });
202
+ }
203
+
204
+ @override
205
+ Future<AuthorizationStatus> requestPermission() async {
206
+ return _permissionStatus;
207
+ }
208
+
209
+ void setPermissionStatus(AuthorizationStatus status) {
210
+ _permissionStatus = status;
211
+ }
212
+
213
+ // ... ๋‚˜๋จธ์ง€ Mock ๊ตฌํ˜„
214
+ }
215
+
216
+ /// ์•Œ๋ฆผ ๋„ค๋น„๊ฒŒ์ด์…˜ ํ…Œ์ŠคํŠธ
217
+ void main() {
218
+ group('NotificationNavigator', () {
219
+ late MockGoRouter mockRouter;
220
+ late NotificationNavigator navigator;
221
+
222
+ setUp(() {
223
+ mockRouter = MockGoRouter();
224
+ navigator = NotificationNavigator(router: mockRouter);
225
+ });
226
+
227
+ test('match payload navigates to match detail', () {
228
+ navigator.navigateFromMessage(RemoteMessage(
229
+ data: {'type': 'match', 'id': 'match_123'},
230
+ ));
231
+
232
+ verify(() => mockRouter.push('/match/match_123')).called(1);
233
+ });
234
+
235
+ test('unknown type navigates to notifications', () {
236
+ navigator.navigateFromMessage(RemoteMessage(
237
+ data: {'type': 'unknown'},
238
+ ));
239
+
240
+ verify(() => mockRouter.push('/notifications')).called(1);
241
+ });
242
+ });
243
+
244
+ group('NotificationPayload', () {
245
+ test('toRoute generates correct path', () {
246
+ final payload = NotificationPayload(type: 'chat', id: 'chat_456');
247
+ expect(payload.toRoute(), '/chat/chat_456');
248
+ });
249
+
250
+ test('toRoute with explicit route overrides type', () {
251
+ final payload = NotificationPayload(
252
+ type: 'match',
253
+ id: 'match_123',
254
+ route: '/custom/path',
255
+ );
256
+ expect(payload.toRoute(), '/custom/path');
257
+ });
258
+ });
259
+ }
260
+ ```
261
+
262
+ ## Notification Analytics
263
+
264
+ ```dart
265
+ /// ์•Œ๋ฆผ ๋ถ„์„ ์ด๋ฒคํŠธ
266
+ class NotificationAnalytics {
267
+ final AnalyticsService _analytics;
268
+
269
+ NotificationAnalytics(this._analytics);
270
+
271
+ /// ์•Œ๋ฆผ ์ˆ˜์‹  (ํฌ๊ทธ๋ผ์šด๋“œ)
272
+ void trackReceived(RemoteMessage message) {
273
+ _analytics.logEvent('notification_received', parameters: {
274
+ 'type': message.data['type'] ?? 'unknown',
275
+ 'source': 'fcm',
276
+ 'app_state': 'foreground',
277
+ });
278
+ }
279
+
280
+ /// ์•Œ๋ฆผ ํƒญ (์—ด๋ฆผ)
281
+ void trackOpened(NotificationPayload payload, String appState) {
282
+ _analytics.logEvent('notification_opened', parameters: {
283
+ 'type': payload.type,
284
+ 'action': payload.action ?? 'tap',
285
+ 'app_state': appState, // foreground, background, terminated
286
+ });
287
+ }
288
+
289
+ /// ์•ก์…˜ ๋ฒ„ํŠผ ํƒญ
290
+ void trackAction(String actionId, NotificationPayload payload) {
291
+ _analytics.logEvent('notification_action', parameters: {
292
+ 'action_id': actionId,
293
+ 'type': payload.type,
294
+ });
295
+ }
296
+
297
+ /// ๊ถŒํ•œ ์š”์ฒญ ๊ฒฐ๊ณผ
298
+ void trackPermission(AuthorizationStatus status) {
299
+ _analytics.logEvent('notification_permission', parameters: {
300
+ 'status': status.name,
301
+ });
302
+ }
303
+
304
+ /// ํ”„๋ฆฌํ”„๋กฌํ”„ํŠธ ๊ฒฐ๊ณผ
305
+ void trackPrePrompt(bool accepted) {
306
+ _analytics.logEvent('notification_pre_prompt', parameters: {
307
+ 'accepted': accepted.toString(),
308
+ });
309
+ }
310
+ }
311
+ ```
312
+
313
+ ## Common Pitfalls
314
+
315
+ 1. **Provider ์ ‘๊ทผ in Background**: ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํ•ธ๋“ค๋Ÿฌ์—์„œ Riverpod Provider ์‚ฌ์šฉ ๋ถˆ๊ฐ€ โ†’ ์ง์ ‘ ์˜์กด์„ฑ ์ƒ์„ฑ
316
+ 2. **์ดˆ๊ธฐํ™” ์ˆœ์„œ**: Firebase โ†’ FCM ํ•ธ๋“ค๋Ÿฌ ๋“ฑ๋ก โ†’ ๋กœ์ปฌ ์•Œ๋ฆผ โ†’ Workmanager (์ˆœ์„œ ์ค‘์š”)
317
+ 3. **๋”ฅ๋งํฌ ๋ ˆ์ด์Šค ์ปจ๋””์…˜**: `getInitialMessage()` ํ˜ธ์ถœ ์‹œ ๋ผ์šฐํ„ฐ ๋ฏธ์ค€๋น„ โ†’ ์ง€์—ฐ ํ›„ ๋„ค๋น„๊ฒŒ์ด์…˜
318
+ 4. **ํ† ํฐ ๊ฐฑ์‹  ๋ˆ„๋ฝ**: `onTokenRefresh` ๋ฏธ๊ตฌ๋… โ†’ ์„œ๋ฒ„์— ์ž˜๋ชป๋œ ํ† ํฐ โ†’ ์ „๋‹ฌ ์‹คํŒจ ์ฆ๊ฐ€
319
+ 5. **์ฑ„๋„ ์ค‘์š”๋„ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€**: Android ์ฑ„๋„ ์ƒ์„ฑ ํ›„ ์ค‘์š”๋„ ์ฝ”๋“œ ๋ณ€๊ฒฝ ๋ฌดํšจ โ†’ ์ƒˆ ์ฑ„๋„ ID ํ•„์š”
320
+ 6. **iOS ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ**: APNs ๋ฏธ์ง€์› โ†’ FCM ํ† ํฐ ๋ฐœ๊ธ‰ ๋ถˆ๊ฐ€ โ†’ ์‹ค๊ธฐ๊ธฐ ํ•„์ˆ˜
321
+ 7. **Release ๋นŒ๋“œ ์ฐจ์ด**: Debug์—์„œ๋งŒ ๋™์ž‘ํ•˜๋Š” ๋กœ๊น…์ด Release์—์„œ ํฌ๋ž˜์‹œ ์œ ๋ฐœ ๊ฐ€๋Šฅ
322
+ 8. **๋™์‹œ ์•Œ๋ฆผ**: ๊ฐ™์€ ID๋กœ show() ํ˜ธ์ถœ ์‹œ ๋ฎ์–ด์“ฐ๊ธฐ โ†’ ๊ณ ์œ  ID ์ „๋žต ํ•„์ˆ˜
323
+
324
+ ## Examples
325
+
326
+ ### ์ตœ์†Œ ๊ตฌํ˜„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ
327
+
328
+ ```
329
+ [ ] pubspec.yaml: firebase_core, firebase_messaging, flutter_local_notifications
330
+ [ ] flutterfire configure (google-services.json, GoogleService-Info.plist ์ž๋™ ์ƒ์„ฑ)
331
+ [ ] iOS: Push Notifications + Background Modes capability
332
+ [ ] Android: AndroidManifest.xml ๊ถŒํ•œ + ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ
333
+ [ ] Android: ์•Œ๋ฆผ ์•„์ด์ฝ˜ (ํฐ์ƒ‰+ํˆฌ๋ช…, PNG, 5์ข… ํฌ๊ธฐ)
334
+ [ ] main.dart: ์ดˆ๊ธฐํ™” ์ˆœ์„œ (Firebase โ†’ FCM โ†’ Local โ†’ Workmanager)
335
+ [ ] ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํ•ธ๋“ค๋Ÿฌ: top-level, @pragma
336
+ [ ] ์•Œ๋ฆผ ์ฑ„๋„: ์•ฑ ์‹œ์ž‘ ์‹œ ์ƒ์„ฑ (Android)
337
+ [ ] FCM ํ† ํฐ: ์„œ๋ฒ„ ๋™๊ธฐํ™” + ๊ฐฑ์‹  ๊ตฌ๋…
338
+ [ ] ๋”ฅ๋งํฌ: 3๊ฐ€์ง€ ์•ฑ ์ƒํƒœ (foreground/background/terminated) ์ฒ˜๋ฆฌ
339
+ [ ] ๊ถŒํ•œ: ๋งฅ๋ฝ์  ์š”์ฒญ + ํ”„๋ฆฌํ”„๋กฌํ”„ํŠธ + ์„ค์ • ์œ ๋„
340
+ ```
@@ -0,0 +1,286 @@
1
+ ---
2
+ title: Platform-Specific Setup (iOS APNs + Android)
3
+ category: guide
4
+ source: internal
5
+ tags: apns, android-channel, xcode, manifest, entitlements
6
+ ---
7
+
8
+ # Platform-Specific Setup (iOS APNs + Android)
9
+
10
+ iOS APNs ์ธ์ฆ ํ‚ค ์„ค์ •, Xcode ์„ค์ •, Android ๋งค๋‹ˆํŽ˜์ŠคํŠธ, ์•Œ๋ฆผ ์ฑ„๋„ ์ƒ์„ธ.
11
+ ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐ ์„ค์ • ์‹œ ์ฐธ์กฐ.
12
+
13
+ ## Key Concepts
14
+
15
+ - **iOS**: APNs (Apple Push Notification service) โ†’ FCM์ด APNs๋ฅผ ํ†ตํ•ด iOS ๊ธฐ๊ธฐ์— ์ „๋‹ฌ
16
+ - **Android**: FCM์ด ์ง์ ‘ ๊ธฐ๊ธฐ์— ์ „๋‹ฌ, Android 8+ (API 26) ์ฑ„๋„ ํ•„์ˆ˜
17
+ - **์ธ์ฆ ๋ฐฉ์‹**: APNs Auth Key (.p8) ๊ถŒ์žฅ (์ธ์ฆ์„œ๋ณด๋‹ค ๊ด€๋ฆฌ ์šฉ์ด, ๋งŒ๋ฃŒ ์—†์Œ)
18
+
19
+ ## iOS Setup
20
+
21
+ ### 1. Apple Developer Console
22
+
23
+ ```
24
+ 1. Apple Developer > Certificates, Identifiers & Profiles
25
+ 2. Keys > Create a Key
26
+ - Name: "FCM APNs Auth Key"
27
+ - Enable: Apple Push Notifications service (APNs)
28
+ - Download .p8 ํŒŒ์ผ (1ํšŒ๋งŒ ๋‹ค์šด๋กœ๋“œ ๊ฐ€๋Šฅ!)
29
+ - Key ID ๊ธฐ๋ก
30
+
31
+ 3. App ID ์„ค์ •
32
+ - Identifiers > ์•ฑ ์„ ํƒ > Capabilities
33
+ - Push Notifications ํ™œ์„ฑํ™”
34
+
35
+ 4. Team ID ํ™•์ธ
36
+ - Membership ํŽ˜์ด์ง€์—์„œ Team ID ํ™•์ธ
37
+ ```
38
+
39
+ ### 2. Firebase Console์— APNs ํ‚ค ๋“ฑ๋ก
40
+
41
+ ```
42
+ 1. Firebase Console > Project Settings > Cloud Messaging
43
+ 2. iOS app ์„ ํƒ
44
+ 3. APNs authentication key ์—…๋กœ๋“œ:
45
+ - .p8 ํŒŒ์ผ ์—…๋กœ๋“œ
46
+ - Key ID ์ž…๋ ฅ
47
+ - Team ID ์ž…๋ ฅ
48
+ ```
49
+
50
+ ### 3. Xcode ์„ค์ •
51
+
52
+ ```
53
+ 1. Runner.xcworkspace ์—ด๊ธฐ
54
+
55
+ 2. Signing & Capabilities:
56
+ + Push Notifications (์•Œ๋ฆผ ์ˆ˜์‹ )
57
+ + Background Modes:
58
+ โœ“ Background fetch (๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ)
59
+ โœ“ Remote notifications (์‚ฌ์ผ๋ŸฐํŠธ ํ‘ธ์‹œ)
60
+ โœ“ Background processing (workmanager์šฉ, iOS 13+)
61
+
62
+ 3. Info.plist (์ž๋™ ์ƒ์„ฑ๋˜์ง€๋งŒ ํ™•์ธ):
63
+ - FirebaseAppDelegateProxyEnabled: YES (๊ธฐ๋ณธ๊ฐ’)
64
+
65
+ 4. (์„ ํƒ) Notification Service Extension:
66
+ - File > New > Target > Notification Service Extension
67
+ - ๋ฆฌ์น˜ ์•Œ๋ฆผ (์ด๋ฏธ์ง€ ์ˆ˜์ •, ์•”ํ˜ธํ™” ํ•ด์ œ ๋“ฑ)์— ํ•„์š”
68
+ ```
69
+
70
+ ### 4. AppDelegate ์„ค์ •
71
+
72
+ ```swift
73
+ // ios/Runner/AppDelegate.swift
74
+ import UIKit
75
+ import Flutter
76
+ import Firebase
77
+
78
+ @main
79
+ @objc class AppDelegate: FlutterAppDelegate {
80
+ override func application(
81
+ _ application: UIApplication,
82
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
83
+ ) -> Bool {
84
+ FirebaseApp.configure()
85
+
86
+ // APNs ๋“ฑ๋ก (firebase_messaging์ด ์ž๋™ ์ฒ˜๋ฆฌํ•˜์ง€๋งŒ ๋ช…์‹œ์ ์œผ๋กœ ํ•ด๋„ ๋ฌด๋ฐฉ)
87
+ if #available(iOS 10.0, *) {
88
+ UNUserNotificationCenter.current().delegate = self
89
+ }
90
+
91
+ GeneratedPluginRegistrant.register(with: self)
92
+ return super.application(application, didFinishLaunchingWithOptions: launchOptions)
93
+ }
94
+
95
+ // ์‚ฌ์ผ๋ŸฐํŠธ ํ‘ธ์‹œ ์ˆ˜์‹ 
96
+ override func application(
97
+ _ application: UIApplication,
98
+ didReceiveRemoteNotification userInfo: [AnyHashable : Any],
99
+ fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
100
+ ) {
101
+ completionHandler(.newData)
102
+ }
103
+ }
104
+ ```
105
+
106
+ ### 5. Podfile ์„ค์ •
107
+
108
+ ```ruby
109
+ # ios/Podfile
110
+ platform :ios, '14.0' # Firebase ์ตœ์†Œ ์š”๊ตฌ์‚ฌํ•ญ
111
+
112
+ target 'Runner' do
113
+ use_frameworks!
114
+ use_modular_headers!
115
+
116
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
117
+ end
118
+
119
+ # Notification Service Extension (๋ฆฌ์น˜ ์•Œ๋ฆผ ์‚ฌ์šฉ ์‹œ)
120
+ # target 'NotificationService' do
121
+ # use_frameworks!
122
+ # pod 'Firebase/Messaging'
123
+ # end
124
+
125
+ post_install do |installer|
126
+ installer.pods_project.targets.each do |target|
127
+ flutter_additional_ios_build_settings(target)
128
+ end
129
+ end
130
+ ```
131
+
132
+ ## Android Setup
133
+
134
+ ### 1. Firebase Console
135
+
136
+ ```
137
+ 1. Firebase Console > Project Settings > ์•ฑ ์ถ”๊ฐ€ > Android
138
+ 2. Android ํŒจํ‚ค์ง€๋ช… ์ž…๋ ฅ (build.gradle์˜ applicationId)
139
+ 3. google-services.json ๋‹ค์šด๋กœ๋“œ
140
+ 4. android/app/google-services.json ์— ๋ฐฐ์น˜
141
+ ```
142
+
143
+ ### 2. build.gradle ์„ค์ •
144
+
145
+ ```groovy
146
+ // android/build.gradle (ํ”„๋กœ์ ํŠธ ๋ ˆ๋ฒจ)
147
+ buildscript {
148
+ dependencies {
149
+ classpath 'com.google.gms:google-services:4.4.2'
150
+ }
151
+ }
152
+
153
+ // android/app/build.gradle (์•ฑ ๋ ˆ๋ฒจ)
154
+ plugins {
155
+ id 'com.google.gms.google-services'
156
+ }
157
+
158
+ android {
159
+ compileSdk 35
160
+
161
+ defaultConfig {
162
+ minSdk 23 // FCM ์ตœ์†Œ ์š”๊ตฌ
163
+ targetSdk 35
164
+ }
165
+ }
166
+ ```
167
+
168
+ ### 3. AndroidManifest.xml
169
+
170
+ ```xml
171
+ <!-- android/app/src/main/AndroidManifest.xml -->
172
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
173
+
174
+ <!-- ์•Œ๋ฆผ ๊ถŒํ•œ (Android 13+) -->
175
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
176
+
177
+ <!-- ์ •ํ™•ํ•œ ์•Œ๋ฆผ ์Šค์ผ€์ค„๋ง (์„ ํƒ) -->
178
+ <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
179
+
180
+ <!-- ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… (workmanager) -->
181
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
182
+
183
+ <!-- ์ง„๋™ (์•Œ๋ฆผ์šฉ) -->
184
+ <uses-permission android:name="android.permission.VIBRATE"/>
185
+
186
+ <application
187
+ android:name="${applicationName}"
188
+ android:icon="@mipmap/ic_launcher">
189
+
190
+ <!-- FCM ๊ธฐ๋ณธ ์•Œ๋ฆผ ์ฑ„๋„ (์•ฑ์ด ์ž์ฒด ์ฑ„๋„ ์ƒ์„ฑ ์ „ ํด๋ฐฑ) -->
191
+ <meta-data
192
+ android:name="com.google.firebase.messaging.default_notification_channel_id"
193
+ android:value="default_channel" />
194
+
195
+ <!-- FCM ๊ธฐ๋ณธ ์•Œ๋ฆผ ์•„์ด์ฝ˜ -->
196
+ <meta-data
197
+ android:name="com.google.firebase.messaging.default_notification_icon"
198
+ android:resource="@mipmap/ic_notification" />
199
+
200
+ <!-- FCM ๊ธฐ๋ณธ ์•Œ๋ฆผ ์ƒ‰์ƒ -->
201
+ <meta-data
202
+ android:name="com.google.firebase.messaging.default_notification_color"
203
+ android:resource="@color/notification_color" />
204
+
205
+ <activity
206
+ android:name=".MainActivity"
207
+ android:launchMode="singleTop">
208
+ <!-- ๋”ฅ๋งํฌ ์ธํ…ํŠธ ํ•„ํ„ฐ -->
209
+ <intent-filter>
210
+ <action android:name="FLUTTER_NOTIFICATION_CLICK"/>
211
+ <category android:name="android.intent.category.DEFAULT"/>
212
+ </intent-filter>
213
+ </activity>
214
+
215
+ </application>
216
+ </manifest>
217
+ ```
218
+
219
+ ### 4. ์•Œ๋ฆผ ์•„์ด์ฝ˜ ์ค€๋น„
220
+
221
+ ```
222
+ Android ์•Œ๋ฆผ ์•„์ด์ฝ˜ ์š”๊ตฌ์‚ฌํ•ญ:
223
+ - ํฐ์ƒ‰ + ํˆฌ๋ช… ๋ฐฐ๊ฒฝ (์‹œ์Šคํ…œ์ด ์ƒ‰์ƒ ์ ์šฉ)
224
+ - PNG ํ˜•์‹ (๋ฒกํ„ฐ SVG ๋ถˆ๊ฐ€)
225
+ - ํฌ๊ธฐ๋ณ„ ๋ฐฐ์น˜:
226
+ android/app/src/main/res/
227
+ โ”œโ”€โ”€ mipmap-mdpi/ic_notification.png (24x24)
228
+ โ”œโ”€โ”€ mipmap-hdpi/ic_notification.png (36x36)
229
+ โ”œโ”€โ”€ mipmap-xhdpi/ic_notification.png (48x48)
230
+ โ”œโ”€โ”€ mipmap-xxhdpi/ic_notification.png (72x72)
231
+ โ””โ”€โ”€ mipmap-xxxhdpi/ic_notification.png (96x96)
232
+
233
+ ์•Œ๋ฆผ ์ƒ‰์ƒ:
234
+ android/app/src/main/res/values/colors.xml
235
+ <resources>
236
+ <color name="notification_color">#FF6B35</color>
237
+ </resources>
238
+ ```
239
+
240
+ ### 5. Android ์•Œ๋ฆผ ์ฑ„๋„ ๊ฐ€์ด๋“œ
241
+
242
+ ```
243
+ ์ฑ„๋„ ์„ค๊ณ„ ์›์น™:
244
+ - ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐœ๋ณ„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” ๋‹จ์œ„๋กœ ๋ถ„๋ฆฌ
245
+ - ์ค‘์š”๋„(Importance)์— ๋”ฐ๋ผ ๋ถ„๋ฅ˜
246
+ - ํ•œ๋ฒˆ ์ƒ์„ฑ๋œ ์ฑ„๋„์˜ ์ค‘์š”๋„๋Š” ์ฝ”๋“œ๋กœ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€ (์‚ฌ์šฉ์ž๋งŒ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ)
247
+
248
+ ๊ถŒ์žฅ ์ฑ„๋„ ๊ตฌ์„ฑ:
249
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
250
+ โ”‚ Channel ID โ”‚ Importance โ”‚ ์šฉ๋„ โ”‚
251
+ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
252
+ โ”‚ matches โ”‚ HIGH โ”‚ ๋งค์น˜ ์ดˆ๋Œ€, ๋ณ€๊ฒฝ, ์‹œ์ž‘ ์•Œ๋ฆผ โ”‚
253
+ โ”‚ chat โ”‚ DEFAULT โ”‚ ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€ โ”‚
254
+ โ”‚ reminders โ”‚ HIGH โ”‚ ๋งค์น˜ ์‹œ์ž‘ ์ „ ๋ฆฌ๋งˆ์ธ๋” โ”‚
255
+ โ”‚ system โ”‚ LOW โ”‚ ์•ฑ ์—…๋ฐ์ดํŠธ, ๊ณต์ง€ โ”‚
256
+ โ”‚ silent โ”‚ MIN โ”‚ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋™๊ธฐํ™” (์‚ฌ์šฉ์ž ๋ฏธํ‘œ์‹œ) โ”‚
257
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
258
+
259
+ Importance ๋ ˆ๋ฒจ:
260
+ - MAX: ํ™”๋ฉด ์ƒ๋‹จ ํ”ผํฌ + ์†Œ๋ฆฌ + ์ง„๋™
261
+ - HIGH: ํ—ค๋“œ์—… ์•Œ๋ฆผ + ์†Œ๋ฆฌ + ์ง„๋™
262
+ - DEFAULT: ์†Œ๋ฆฌ + ์ง„๋™ (ํ—ค๋“œ์—… X)
263
+ - LOW: ์†Œ๋ฆฌ X, ์ง„๋™ X (์ƒํƒœ๋ฐ”์—๋งŒ)
264
+ - MIN: ์ƒํƒœ๋ฐ”์—๋„ ์ตœ์†Œ ํ‘œ์‹œ (์ ‘์œผ๋ฉด ๋ณด์ž„)
265
+ ```
266
+
267
+ ## Common Pitfalls
268
+
269
+ ### iOS
270
+ 1. **APNs ํ‚ค vs ์ธ์ฆ์„œ**: .p8 Auth Key ์‚ฌ์šฉ ๊ถŒ์žฅ (๋งŒ๋ฃŒ ์—†์Œ, ๋ชจ๋“  ์•ฑ์— ๊ณต์œ  ๊ฐ€๋Šฅ)
271
+ 2. **์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ**: iOS ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ๋Š” APNs ๋ฏธ์ง€์› โ†’ ์‹ค์ œ ๊ธฐ๊ธฐ์—์„œ๋งŒ ํ‘ธ์‹œ ํ…Œ์ŠคํŠธ
272
+ 3. **Provisional ์•Œ๋ฆผ**: iOS 12+ ์ž„์‹œ ๊ถŒํ•œ โ†’ ์•Œ๋ฆผ ์„ผํ„ฐ์— ์กฐ์šฉํžˆ ์ „๋‹ฌ
273
+ 4. **Background Modes ๋ˆ„๋ฝ**: Xcode์—์„œ Remote notifications ์ฒดํฌ ์•ˆ ํ•˜๋ฉด ์‚ฌ์ผ๋ŸฐํŠธ ํ‘ธ์‹œ ๋ฏธ์ˆ˜์‹ 
274
+ 5. **Production vs Sandbox**: APNs ํ™˜๊ฒฝ ์ž๋™ ์ „ํ™˜ (Debug=Sandbox, Release=Production)
275
+
276
+ ### Android
277
+ 1. **์ฑ„๋„ ์ƒ์„ฑ ํƒ€์ด๋ฐ**: ์•ฑ ์‹œ์ž‘ ์‹œ ์ฑ„๋„ ์ƒ์„ฑ ํ•„์ˆ˜ (์•Œ๋ฆผ ํ‘œ์‹œ ์ „)
278
+ 2. **์•Œ๋ฆผ ์•„์ด์ฝ˜**: ํฐ์ƒ‰+ํˆฌ๋ช…์ด ์•„๋‹ˆ๋ฉด ํšŒ์ƒ‰ ์‚ฌ๊ฐํ˜•์œผ๋กœ ํ‘œ์‹œ
279
+ 3. **targetSdk 34+**: `SCHEDULE_EXACT_ALARM` ๊ถŒํ•œ์ด ๊ธฐ๋ณธ ๊ฑฐ๋ถ€ โ†’ `canScheduleExactAlarms()` ์ฒดํฌ
280
+ 4. **Doze ๋ชจ๋“œ**: ๊ณ ์šฐ์„ ์ˆœ์œ„ FCM์€ Doze ํ†ต๊ณผ, ์ผ๋ฐ˜ ๋ฉ”์‹œ์ง€๋Š” ์ง€์—ฐ ๊ฐ€๋Šฅ
281
+ 5. **์ฑ„๋„ ์ค‘์š”๋„ ๋ณ€๊ฒฝ**: ์ฝ”๋“œ๋กœ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€ โ†’ ์ƒˆ ์ฑ„๋„ ID ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ๋ณ€๊ฒฝ
282
+
283
+ ### ๊ณตํ†ต
284
+ 1. **google-services.json / GoogleService-Info.plist**: ๋ฐ˜๋“œ์‹œ .gitignore์— ์ถ”๊ฐ€ (๋ณด์•ˆ)
285
+ 2. **FlutterFire CLI**: `flutterfire configure` ๋กœ ์ž๋™ ์„ค์ • ๊ถŒ์žฅ
286
+ 3. **์—๋ฎฌ๋ ˆ์ดํ„ฐ**: Android ์—๋ฎฌ๋ ˆ์ดํ„ฐ๋Š” Google Play Services ํฌํ•จ ์ด๋ฏธ์ง€ ํ•„์š”