kasy-cli 1.12.1 → 1.14.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 (249) hide show
  1. package/bin/kasy.js +143 -7
  2. package/lib/commands/add.js +2 -2
  3. package/lib/commands/codemagic.js +11 -4
  4. package/lib/commands/deploy.js +3 -3
  5. package/lib/commands/favicon.js +115 -0
  6. package/lib/commands/icon.js +143 -0
  7. package/lib/commands/ios.js +20 -5
  8. package/lib/commands/new.js +8 -20
  9. package/lib/commands/remove.js +1 -1
  10. package/lib/commands/reset.js +287 -0
  11. package/lib/commands/run.js +24 -17
  12. package/lib/commands/splash.js +219 -0
  13. package/lib/commands/update.js +1 -1
  14. package/lib/scaffold/CHANGELOG.json +9 -0
  15. package/lib/scaffold/backends/api/patch/README.md +1 -1
  16. package/lib/scaffold/backends/api/patch/android/app/src/main/AndroidManifest.xml +1 -1
  17. package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +53 -0
  18. package/lib/scaffold/backends/api/patch/lib/main.dart +29 -10
  19. package/lib/scaffold/backends/api/pubspec.yaml.tpl +11 -1
  20. package/lib/scaffold/backends/firebase/tokens.js +2 -2
  21. package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/index.ts +8 -2
  22. package/lib/scaffold/backends/supabase/migrations/20240101000011_dedupe_device_tokens.sql +34 -0
  23. package/lib/scaffold/backends/supabase/patch/README.md +1 -1
  24. package/lib/scaffold/backends/supabase/patch/android/app/src/main/AndroidManifest.xml +1 -1
  25. package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +43 -0
  26. package/lib/scaffold/backends/supabase/patch/lib/main.dart +29 -10
  27. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +11 -1
  28. package/lib/scaffold/features/README.md +15 -139
  29. package/lib/scaffold/shared/generator-utils.js +16 -15
  30. package/lib/utils/apple-release.js +85 -16
  31. package/lib/utils/checks.js +4 -105
  32. package/lib/utils/flutter-run.js +173 -0
  33. package/lib/utils/i18n.js +413 -0
  34. package/lib/utils/mobile-identity.js +35 -0
  35. package/lib/utils/ui.js +114 -0
  36. package/package.json +2 -3
  37. package/templates/firebase/README.en.md +1 -1
  38. package/templates/firebase/README.es.md +1 -1
  39. package/templates/firebase/README.md +1 -1
  40. package/templates/firebase/android/app/build.gradle.kts +10 -1
  41. package/templates/firebase/android/app/src/main/AndroidManifest.xml +1 -1
  42. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +25 -1
  43. package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +160 -11
  44. package/templates/firebase/android/app/src/main/res/drawable/widget_add_button.xml +15 -0
  45. package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_bg.xml +9 -0
  46. package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_inner.xml +12 -0
  47. package/templates/firebase/android/app/src/main/res/drawable/widget_plan_pill_bg.xml +5 -0
  48. package/templates/firebase/android/app/src/main/res/drawable/widget_preview_image.xml +17 -0
  49. package/templates/firebase/android/app/src/main/res/drawable/widget_pro_pill_bg.xml +5 -0
  50. package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
  51. package/templates/firebase/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
  52. package/templates/firebase/android/app/src/main/res/drawable-mdpi/android12splash.png +0 -0
  53. package/templates/firebase/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
  54. package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
  55. package/templates/firebase/android/app/src/main/res/drawable-night/launch_background.xml +9 -0
  56. package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/android12splash.png +0 -0
  57. package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/splash.png +0 -0
  58. package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/android12splash.png +0 -0
  59. package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/splash.png +0 -0
  60. package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
  61. package/templates/firebase/android/app/src/main/res/drawable-night-v21/launch_background.xml +9 -0
  62. package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png +0 -0
  63. package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/splash.png +0 -0
  64. package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png +0 -0
  65. package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/splash.png +0 -0
  66. package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png +0 -0
  67. package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/splash.png +0 -0
  68. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/android12splash.png +0 -0
  69. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
  70. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/android12splash.png +0 -0
  71. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
  72. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/android12splash.png +0 -0
  73. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
  74. package/templates/firebase/android/app/src/main/res/layout/widget_loading.xml +8 -0
  75. package/templates/firebase/android/app/src/main/res/layout/widget_preview.xml +46 -0
  76. package/templates/firebase/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  77. package/templates/firebase/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  78. package/templates/firebase/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  79. package/templates/firebase/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  80. package/templates/firebase/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  81. package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +2 -1
  82. package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -0
  83. package/templates/firebase/android/app/src/main/res/xml/mywidget_info.xml +9 -3
  84. package/templates/firebase/assets/images/favicon.png +0 -0
  85. package/templates/firebase/assets/images/icon.png +0 -0
  86. package/templates/firebase/assets/images/splash_logo_dark.png +0 -0
  87. package/templates/firebase/assets/images/splash_logo_light.png +0 -0
  88. package/templates/firebase/firestore.indexes.json +10 -0
  89. package/templates/firebase/functions/src/core/data/entities/user_device_entity.ts +3 -0
  90. package/templates/firebase/functions/src/core/data/repositories/user_device_repository.ts +17 -1
  91. package/templates/firebase/functions/src/index.ts +1 -0
  92. package/templates/firebase/functions/src/notifications/device_triggers.ts +58 -0
  93. package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +116 -33
  94. package/templates/firebase/ios/Runner/AppDelegate.swift +17 -1
  95. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
  96. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
  97. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
  98. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
  99. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
  100. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
  101. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
  102. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
  103. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
  104. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
  105. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png +0 -0
  106. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png +0 -0
  107. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png +0 -0
  108. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png +0 -0
  109. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
  110. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
  111. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png +0 -0
  112. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png +0 -0
  113. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
  114. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
  115. package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png +0 -0
  116. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json +9 -8
  117. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
  118. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +33 -0
  119. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
  120. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
  121. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
  122. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png +0 -0
  123. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png +0 -0
  124. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png +0 -0
  125. package/templates/firebase/ios/Runner/Base.lproj/LaunchScreen.storyboard +1 -1
  126. package/templates/firebase/ios/Runner/Info.plist +2 -2
  127. package/templates/firebase/ios/Runner/es.lproj/InfoPlist.strings +1 -1
  128. package/templates/firebase/ios/Runner/pt-BR.lproj/InfoPlist.strings +1 -1
  129. package/templates/firebase/ios/Runner/pt.lproj/InfoPlist.strings +1 -1
  130. package/templates/firebase/lib/components/kasy_button.dart +8 -0
  131. package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +18 -0
  132. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +67 -19
  133. package/templates/firebase/lib/core/home_widgets/home_widget_service.dart +22 -8
  134. package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +16 -4
  135. package/templates/firebase/lib/core/states/components/maybeshow_component.dart +4 -8
  136. package/templates/firebase/lib/core/states/user_state_notifier.dart +13 -1
  137. package/templates/firebase/lib/core/theme/providers/theme_provider.dart +48 -24
  138. package/templates/firebase/lib/features/home/home_page.dart +0 -6
  139. package/templates/firebase/lib/features/notifications/api/device_api.dart +57 -0
  140. package/templates/firebase/lib/features/notifications/providers/models/notification.dart +11 -1
  141. package/templates/firebase/lib/features/notifications/repositories/device_repository.dart +9 -0
  142. package/templates/firebase/lib/features/notifications/repositories/notifications_repository.dart +1 -4
  143. package/templates/firebase/lib/features/notifications/shared/att_permission.dart +28 -8
  144. package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +16 -1
  145. package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +44 -11
  146. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +4 -0
  147. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +1 -0
  148. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +13 -0
  149. package/templates/firebase/lib/features/settings/settings_page.dart +158 -18
  150. package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +31 -29
  151. package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +21 -5
  152. package/templates/firebase/lib/i18n/en.i18n.json +10 -3
  153. package/templates/firebase/lib/i18n/es.i18n.json +10 -3
  154. package/templates/firebase/lib/i18n/pt.i18n.json +10 -3
  155. package/templates/firebase/lib/main.dart +29 -10
  156. package/templates/firebase/pubspec.yaml +10 -6
  157. package/templates/firebase/test/core/data/repositories/user_repository_test.dart +1 -1
  158. package/templates/firebase/test/features/notifications/data/device_api_fake.dart +9 -0
  159. package/templates/firebase/web/favicon.png +0 -0
  160. package/templates/firebase/web/icons/Icon-192.png +0 -0
  161. package/templates/firebase/web/icons/Icon-512.png +0 -0
  162. package/templates/firebase/web/icons/Icon-maskable-192.png +0 -0
  163. package/templates/firebase/web/icons/Icon-maskable-512.png +0 -0
  164. package/templates/firebase/web/index.html +50 -39
  165. package/templates/firebase/web/manifest.json +3 -3
  166. package/templates/firebase/web/splash/img/dark-1x.png +0 -0
  167. package/templates/firebase/web/splash/img/dark-2x.png +0 -0
  168. package/templates/firebase/web/splash/img/dark-3x.png +0 -0
  169. package/templates/firebase/web/splash/img/dark-4x.png +0 -0
  170. package/templates/firebase/web/splash/img/light-1x.png +0 -0
  171. package/templates/firebase/web/splash/img/light-2x.png +0 -0
  172. package/templates/firebase/web/splash/img/light-3x.png +0 -0
  173. package/templates/firebase/web/splash/img/light-4x.png +0 -0
  174. package/lib/scaffold/features/analytics/lib/core/data/api/analytics_api.dart +0 -124
  175. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.es.md +0 -35
  176. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.md +0 -35
  177. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.pt.md +0 -35
  178. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.es.md +0 -12
  179. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.md +0 -12
  180. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.pt.md +0 -12
  181. package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.es.md +0 -17
  182. package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.md +0 -17
  183. package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.pt.md +0 -17
  184. package/lib/scaffold/features/ci/.github/dependabot.yml +0 -16
  185. package/lib/scaffold/features/ci/.github/workflows/app.yml +0 -20
  186. package/lib/scaffold/features/ci/.gitlab/templates/deploy.yaml +0 -14
  187. package/lib/scaffold/features/ci/.gitlab/templates/dropbox.yaml +0 -19
  188. package/lib/scaffold/features/ci/.gitlab/templates/flutter.yaml +0 -163
  189. package/lib/scaffold/features/ci/.gitlab/templates/mailgun.yaml +0 -28
  190. package/lib/scaffold/features/ci/.gitlab-ci.yml +0 -37
  191. package/lib/scaffold/features/ci/codemagic.yaml +0 -157
  192. package/lib/scaffold/features/facebook/lib/core/data/api/tracking_api.dart +0 -111
  193. package/lib/scaffold/features/feedback/lib/features/feedbacks/api/entities/feature_request_entity.dart +0 -27
  194. package/lib/scaffold/features/feedback/lib/features/feedbacks/api/entities/feature_vote_entity.dart +0 -27
  195. package/lib/scaffold/features/feedback/lib/features/feedbacks/api/feature_request_api.dart +0 -50
  196. package/lib/scaffold/features/feedback/lib/features/feedbacks/api/feature_vote_api.dart +0 -79
  197. package/lib/scaffold/features/feedback/lib/features/feedbacks/models/feature_requests.dart +0 -48
  198. package/lib/scaffold/features/feedback/lib/features/feedbacks/models/feedback_state.dart +0 -42
  199. package/lib/scaffold/features/feedback/lib/features/feedbacks/providers/feedback_page_notifier.dart +0 -147
  200. package/lib/scaffold/features/feedback/lib/features/feedbacks/repositories/feature_request_repository.dart +0 -95
  201. package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/component/add_feature_form.dart +0 -199
  202. package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/feedback_page.dart +0 -175
  203. package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -76
  204. package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/widgets/feature_card.dart +0 -279
  205. package/lib/scaffold/features/ios-release/.kasy/apple.env.example +0 -8
  206. package/lib/scaffold/features/ios-release/.kasy/codemagic.env.example +0 -7
  207. package/lib/scaffold/features/ios-release/docs/codemagic-release.en.md +0 -50
  208. package/lib/scaffold/features/ios-release/docs/codemagic-release.es.md +0 -50
  209. package/lib/scaffold/features/ios-release/docs/codemagic-release.pt.md +0 -50
  210. package/lib/scaffold/features/ios-release/docs/ios-release.en.md +0 -41
  211. package/lib/scaffold/features/ios-release/docs/ios-release.es.md +0 -41
  212. package/lib/scaffold/features/ios-release/docs/ios-release.pt.md +0 -41
  213. package/lib/scaffold/features/ios-release/scripts/bump-ios-version.js +0 -38
  214. package/lib/scaffold/features/ios-release/scripts/release-ios.sh +0 -137
  215. package/lib/scaffold/features/llm_chat/lib/features/llm_chat/llm_chat_page.dart +0 -301
  216. package/lib/scaffold/features/local_notifications/lib/features/local_reminder/providers/reminder_notifier.dart +0 -81
  217. package/lib/scaffold/features/local_notifications/lib/features/local_reminder/repositories/reminder_preferences.dart +0 -76
  218. package/lib/scaffold/features/local_notifications/lib/features/local_reminder/ui/reminder_page.dart +0 -282
  219. package/lib/scaffold/features/onboarding/lib/features/onboarding/api/entities/user_info_entity.dart +0 -24
  220. package/lib/scaffold/features/onboarding/lib/features/onboarding/api/user_infos_api.dart +0 -71
  221. package/lib/scaffold/features/onboarding/lib/features/onboarding/models/user_info.dart +0 -92
  222. package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_model.dart +0 -15
  223. package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_provider.dart +0 -78
  224. package/lib/scaffold/features/onboarding/lib/features/onboarding/repositories/user_infos_repository.dart +0 -29
  225. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/animations/page_transitions.dart +0 -30
  226. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_att_setup.dart +0 -66
  227. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_features.dart +0 -72
  228. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_loader.dart +0 -92
  229. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
  230. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_questions.dart +0 -89
  231. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/onboarding_page.dart +0 -94
  232. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_background.dart +0 -80
  233. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_feature.dart +0 -139
  234. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +0 -110
  235. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_progress.dart +0 -84
  236. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +0 -173
  237. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_reassurance.dart +0 -45
  238. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +0 -77
  239. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +0 -392
  240. package/lib/scaffold/features/revenuecat/lib/core/data/api/tracking_api.dart +0 -116
  241. package/lib/scaffold/features/revenuecat/lib/core/data/models/subscription.dart +0 -322
  242. package/lib/scaffold/features/revenuecat/lib/core/home_widgets/home_widget_background_task.dart +0 -41
  243. package/lib/scaffold/features/revenuecat/lib/core/states/user_state_notifier.dart +0 -305
  244. package/templates/firebase/assets/images/app_icon.png +0 -0
  245. package/templates/firebase/assets/images/onboarding/img1.jpg +0 -0
  246. package/templates/firebase/assets/images/onboarding/onboarding.png +0 -0
  247. package/templates/firebase/assets/images/splashscreen.png +0 -0
  248. package/templates/firebase/lib/core/states/components/maybe_ask_biometric_setup.dart +0 -88
  249. package/templates/firebase/lib/features/notifications/shared/notification_permission_bottom_sheet.dart +0 -144
@@ -1,4 +1,4 @@
1
- # AppFirebase
1
+ # Kasy App
2
2
 
3
3
  Flutter app com backend API REST — gerado pelo kasy.
4
4
 
@@ -14,7 +14,7 @@
14
14
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
15
15
 
16
16
  <application
17
- android:label="AppFirebase"
17
+ android:label="Kasy App"
18
18
  android:name="${applicationName}"
19
19
  android:icon="@mipmap/ic_launcher">
20
20
  <activity
@@ -29,6 +29,14 @@ abstract class DeviceApi {
29
29
  /// Register the device in the backend
30
30
  /// Of course your backend should check if the device is already registered
31
31
  /// throws an [ApiError] if something goes wrong
32
+ ///
33
+ /// IMPORTANT — Cross-user token uniqueness:
34
+ /// The backend `POST /users/{userId}/devices` endpoint MUST guarantee that
35
+ /// the FCM token is unique across users. When the same token is registered
36
+ /// for a new user, delete any existing record holding that same token under
37
+ /// other users. Without this, a failed logout (offline) leaves the phone
38
+ /// registered to both accounts, and push for account A delivers to a phone
39
+ /// now signed in as account B.
32
40
  Future<DeviceEntity> register(String userId, DeviceEntity device);
33
41
 
34
42
  /// Update the device in the backend
@@ -38,6 +46,25 @@ abstract class DeviceApi {
38
46
  /// Unregister the device in the backend
39
47
  Future<void> unregister(String userId, String deviceId);
40
48
 
49
+ /// Heartbeat — tell the backend this install is still active.
50
+ /// Backend should update a `lastUpdateDate` (or equivalent) timestamp and use
51
+ /// it to skip stale devices when sending push notifications. Without this,
52
+ /// re-installing the app (Xcode -> TestFlight, build updates) leaves
53
+ /// orphan device records that still receive push, causing duplicates.
54
+ ///
55
+ /// Suggested endpoint: `PATCH /users/{userId}/devices/{installationId}/touch`
56
+ /// Implement on your API to update the device row's last-seen timestamp.
57
+ Future<void> touch(String userId, String installationId);
58
+
59
+ /// Ask the backend to drop device records of the same user that haven't
60
+ /// been heartbeated in a while (typically 30 days). Called after registering
61
+ /// a fresh installation to clean up orphans from previous installs on the
62
+ /// same physical device.
63
+ ///
64
+ /// Suggested endpoint: `POST /users/{userId}/devices/cleanup-stale`
65
+ /// with body `{ "currentInstallationId": "...", "olderThanDays": 30 }`.
66
+ Future<void> cleanupStaleDevices(String userId, String currentInstallationId);
67
+
41
68
  /// Listen to token refresh
42
69
  void onTokenRefresh(OnTokenRefresh onTokenRefresh);
43
70
 
@@ -153,6 +180,32 @@ class FirebaseDeviceApi implements DeviceApi {
153
180
  }
154
181
  }
155
182
 
183
+ @override
184
+ Future<void> touch(String userId, String installationId) async {
185
+ // Fire-and-forget: silently no-ops if the backend doesn't implement
186
+ // the touch endpoint yet. The duplicated-push protection becomes active
187
+ // once the API exposes the endpoint described in the abstract above.
188
+ try {
189
+ await _client.patch('/users/$userId/devices/$installationId/touch');
190
+ } catch (_) {}
191
+ }
192
+
193
+ @override
194
+ Future<void> cleanupStaleDevices(
195
+ String userId,
196
+ String currentInstallationId,
197
+ ) async {
198
+ try {
199
+ await _client.post(
200
+ '/users/$userId/devices/cleanup-stale',
201
+ data: {
202
+ 'currentInstallationId': currentInstallationId,
203
+ 'olderThanDays': 30,
204
+ },
205
+ );
206
+ } catch (_) {}
207
+ }
208
+
156
209
  @override
157
210
  void onTokenRefresh(OnTokenRefresh onTokenRefresh) {
158
211
  _onTokenRefreshSubscription =
@@ -108,7 +108,7 @@ void run(SharedPreferences prefs) => runApp(
108
108
  // mode: ThemeMode.dark,
109
109
  // ),
110
110
  // See ./docs/theme.md for more details
111
- class MyApp extends ConsumerWidget {
111
+ class MyApp extends ConsumerStatefulWidget {
112
112
  final SharedPreferences sharedPreferences;
113
113
 
114
114
  const MyApp({
@@ -116,23 +116,42 @@ class MyApp extends ConsumerWidget {
116
116
  required this.sharedPreferences,
117
117
  });
118
118
 
119
+ @override
120
+ ConsumerState<MyApp> createState() => _MyAppState();
121
+ }
122
+
123
+ class _MyAppState extends ConsumerState<MyApp> {
124
+ late final AppTheme _appTheme;
125
+
126
+ @override
127
+ void initState() {
128
+ super.initState();
129
+ _appTheme = AppTheme.uniform(
130
+ sharedPreferences: widget.sharedPreferences,
131
+ themeFactory: const UniversalThemeFactory(),
132
+ lightColors: KasyColors.light(),
133
+ darkColors: KasyColors.dark(),
134
+ textTheme: KasyTextTheme.build(),
135
+ defaultMode: ThemeMode.system,
136
+ );
137
+ }
138
+
139
+ @override
140
+ void dispose() {
141
+ _appTheme.dispose();
142
+ super.dispose();
143
+ }
144
+
119
145
  // This widget is the root of your application.
120
146
  @override
121
- Widget build(BuildContext context, WidgetRef ref) {
147
+ Widget build(BuildContext context) {
122
148
  ErrorWidget.builder = (FlutterErrorDetails details) {
123
149
  return AppErrorWidget(error: details);
124
150
  };
125
151
  final goRouter = ref.watch(goRouterProvider);
126
152
 
127
153
  return ThemeProvider(
128
- notifier: AppTheme.uniform(
129
- sharedPreferences: sharedPreferences,
130
- themeFactory: const UniversalThemeFactory(),
131
- lightColors: KasyColors.light(),
132
- darkColors: KasyColors.dark(),
133
- textTheme: KasyTextTheme.build(),
134
- defaultMode: ThemeMode.light,
135
- ),
154
+ notifier: _appTheme,
136
155
  child: Builder(builder: (context) {
137
156
  return WebDevicePreview.wrap(
138
157
  child: DevInspector.wrap(
@@ -98,11 +98,21 @@ flutter_launcher_icons:
98
98
  android: ic_launcher
99
99
  ios: true
100
100
  remove_alpha_ios: true
101
+ web:
102
+ generate: true
103
+ image_path: assets/images/favicon.png
104
+ background_color: "#01171f"
105
+ theme_color: "#01171f"
101
106
  flutter_native_splash:
102
107
  color: "#FFFFFF"
108
+ color_dark: "#000000"
103
109
  fullscreen: true
104
110
  ios: true
105
111
  android: true
106
- image: assets/images/splashscreen.png
112
+ image: assets/images/splash_logo_light.png
113
+ image_dark: assets/images/splash_logo_dark.png
107
114
  android_12:
108
115
  color: "#FFFFFF"
116
+ color_dark: "#000000"
117
+ image: assets/images/splash_logo_light.png
118
+ image_dark: assets/images/splash_logo_dark.png
@@ -10,13 +10,13 @@
10
10
  * Original hardcoded values in Firebase/:
11
11
  * - package name : kasy_kit (Dart import path prefix)
12
12
  * - bundle ID : com.aicrus.firebase.kit (Android namespace, iOS bundle ID)
13
- * - app display : AppFirebase (AndroidManifest, Info.plist)
13
+ * - app display : Kasy App (AndroidManifest, Info.plist) — unique string with space, won't collide with KasyButton/KasyTheme/etc
14
14
  * - short name : appfirebase (kAppName Dart constant)
15
15
  */
16
16
 
17
17
  const ORIGINAL_PACKAGE = 'kasy_kit';
18
18
  const ORIGINAL_BUNDLE_ID = 'com.aicrus.firebase.kit';
19
- const ORIGINAL_APP_NAME = 'AppFirebase';
19
+ const ORIGINAL_APP_NAME = 'Kasy App';
20
20
  const ORIGINAL_SHORT_NAME = 'appfirebase';
21
21
 
22
22
  /**
@@ -254,11 +254,17 @@ Deno.serve(async (req: Request) => {
254
254
  const serviceRoleKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
255
255
  const supabase = createClient(supabaseUrl, serviceRoleKey);
256
256
 
257
- // Fetch device tokens for the user
257
+ // Fetch device tokens for the user, skipping orphan installs.
258
+ // Devices not touched in the last 60 days are treated as leftovers from
259
+ // previous installations on the same physical device (each install gets a
260
+ // fresh installation_id). Sending to them causes duplicated push delivery.
261
+ const STALE_DEVICE_TTL_MS = 60 * 24 * 60 * 60 * 1000;
262
+ const cutoffIso = new Date(Date.now() - STALE_DEVICE_TTL_MS).toISOString();
258
263
  const { data: devices, error: devErr } = await supabase
259
264
  .from("devices")
260
265
  .select("id, token")
261
- .eq("user_id", notification.user_id);
266
+ .eq("user_id", notification.user_id)
267
+ .or(`last_update_date.is.null,last_update_date.gte.${cutoffIso}`);
262
268
 
263
269
  if (devErr || !devices?.length) {
264
270
  console.log(`[send-push] no devices for user ${notification.user_id}`);
@@ -0,0 +1,34 @@
1
+ -- Cross-user device token deduplication.
2
+ --
3
+ -- Guarantees that a single FCM token belongs to at most one user at any time.
4
+ -- When the same install registers under a new account (typical scenario:
5
+ -- logout failed offline), the old row is automatically deleted so the iPhone
6
+ -- only receives push for the account currently signed in.
7
+ --
8
+ -- Winner = the row that was just inserted/updated (most recent intent).
9
+
10
+ CREATE OR REPLACE FUNCTION public.cleanup_duplicate_device_tokens()
11
+ RETURNS TRIGGER
12
+ LANGUAGE plpgsql
13
+ SECURITY DEFINER
14
+ SET search_path = public
15
+ AS $$
16
+ BEGIN
17
+ IF NEW.token IS NULL OR NEW.token = '' THEN
18
+ RETURN NEW;
19
+ END IF;
20
+
21
+ DELETE FROM public.devices
22
+ WHERE token = NEW.token
23
+ AND id <> NEW.id;
24
+
25
+ RETURN NEW;
26
+ END;
27
+ $$;
28
+
29
+ DROP TRIGGER IF EXISTS trg_cleanup_duplicate_device_tokens ON public.devices;
30
+
31
+ CREATE TRIGGER trg_cleanup_duplicate_device_tokens
32
+ AFTER INSERT OR UPDATE OF token ON public.devices
33
+ FOR EACH ROW
34
+ EXECUTE FUNCTION public.cleanup_duplicate_device_tokens();
@@ -1,4 +1,4 @@
1
- # AppFirebase
1
+ # Kasy App
2
2
 
3
3
  Flutter app com backend Supabase — gerado pelo kasy.
4
4
 
@@ -14,7 +14,7 @@
14
14
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
15
15
 
16
16
  <application
17
- android:label="AppFirebase"
17
+ android:label="Kasy App"
18
18
  android:name="${applicationName}"
19
19
  android:icon="@mipmap/ic_launcher">
20
20
  <activity
@@ -36,6 +36,15 @@ abstract class DeviceApi {
36
36
  /// Unregister the device in the backend
37
37
  Future<void> unregister(String userId, String deviceId);
38
38
 
39
+ /// Heartbeat — update `last_update_date` on the current device row.
40
+ /// Used so the backend can detect orphan rows from previous installs.
41
+ Future<void> touch(String userId, String installationId);
42
+
43
+ /// Delete device rows of the same user that haven't been touched in a while.
44
+ /// Called after registering a fresh installation to remove orphans left by
45
+ /// previous installs (whose installation_id no longer matches).
46
+ Future<void> cleanupStaleDevices(String userId, String currentInstallationId);
47
+
39
48
  /// Listen to token refresh
40
49
  void onTokenRefresh(OnTokenRefresh onTokenRefresh);
41
50
 
@@ -160,6 +169,40 @@ class FirebaseDeviceApi implements DeviceApi {
160
169
  }
161
170
  }
162
171
 
172
+ @override
173
+ Future<void> touch(String userId, String installationId) async {
174
+ try {
175
+ await _client
176
+ .from('devices')
177
+ .update({'last_update_date': DateTime.now().toIso8601String()})
178
+ .eq('user_id', userId)
179
+ .eq('installation_id', installationId);
180
+ } catch (_) {
181
+ // Missing row — caller will re-register on next session.
182
+ }
183
+ }
184
+
185
+ @override
186
+ Future<void> cleanupStaleDevices(
187
+ String userId,
188
+ String currentInstallationId,
189
+ ) async {
190
+ // Devices not touched in the last 30 days are treated as orphans from
191
+ // previous installations on the same physical device. Active second
192
+ // devices stay above this threshold via heartbeat.
193
+ final cutoff = DateTime.now().subtract(const Duration(days: 30));
194
+ try {
195
+ await _client
196
+ .from('devices')
197
+ .delete()
198
+ .eq('user_id', userId)
199
+ .neq('installation_id', currentInstallationId)
200
+ .lt('last_update_date', cutoff.toIso8601String());
201
+ } catch (e) {
202
+ Logger().w('cleanupStaleDevices failed: $e');
203
+ }
204
+ }
205
+
163
206
  @override
164
207
  Future<void> clear(String userId) async {
165
208
  try {
@@ -138,7 +138,7 @@ void run(SharedPreferences prefs) => runApp(
138
138
  // mode: ThemeMode.dark,
139
139
  // ),
140
140
  // See ./docs/theme.md for more details
141
- class MyApp extends ConsumerWidget {
141
+ class MyApp extends ConsumerStatefulWidget {
142
142
  final SharedPreferences sharedPreferences;
143
143
 
144
144
  const MyApp({
@@ -146,23 +146,42 @@ class MyApp extends ConsumerWidget {
146
146
  required this.sharedPreferences,
147
147
  });
148
148
 
149
+ @override
150
+ ConsumerState<MyApp> createState() => _MyAppState();
151
+ }
152
+
153
+ class _MyAppState extends ConsumerState<MyApp> {
154
+ late final AppTheme _appTheme;
155
+
156
+ @override
157
+ void initState() {
158
+ super.initState();
159
+ _appTheme = AppTheme.uniform(
160
+ sharedPreferences: widget.sharedPreferences,
161
+ themeFactory: const UniversalThemeFactory(),
162
+ lightColors: KasyColors.light(),
163
+ darkColors: KasyColors.dark(),
164
+ textTheme: KasyTextTheme.build(),
165
+ defaultMode: ThemeMode.system,
166
+ );
167
+ }
168
+
169
+ @override
170
+ void dispose() {
171
+ _appTheme.dispose();
172
+ super.dispose();
173
+ }
174
+
149
175
  // This widget is the root of your application.
150
176
  @override
151
- Widget build(BuildContext context, WidgetRef ref) {
177
+ Widget build(BuildContext context) {
152
178
  ErrorWidget.builder = (FlutterErrorDetails details) {
153
179
  return AppErrorWidget(error: details);
154
180
  };
155
181
  final goRouter = ref.watch(goRouterProvider);
156
182
 
157
183
  return ThemeProvider(
158
- notifier: AppTheme.uniform(
159
- sharedPreferences: sharedPreferences,
160
- themeFactory: const UniversalThemeFactory(),
161
- lightColors: KasyColors.light(),
162
- darkColors: KasyColors.dark(),
163
- textTheme: KasyTextTheme.build(),
164
- defaultMode: ThemeMode.light,
165
- ),
184
+ notifier: _appTheme,
166
185
  child: Builder(builder: (context) {
167
186
  return WebDevicePreview.wrap(
168
187
  child: DevInspector.wrap(
@@ -100,11 +100,21 @@ flutter_launcher_icons:
100
100
  android: ic_launcher
101
101
  ios: true
102
102
  remove_alpha_ios: true
103
+ web:
104
+ generate: true
105
+ image_path: assets/images/favicon.png
106
+ background_color: "#01171f"
107
+ theme_color: "#01171f"
103
108
  flutter_native_splash:
104
109
  color: "#FFFFFF"
110
+ color_dark: "#000000"
105
111
  fullscreen: true
106
112
  ios: true
107
113
  android: true
108
- image: assets/images/splashscreen.png
114
+ image: assets/images/splash_logo_light.png
115
+ image_dark: assets/images/splash_logo_dark.png
109
116
  android_12:
110
117
  color: "#FFFFFF"
118
+ color_dark: "#000000"
119
+ image: assets/images/splash_logo_light.png
120
+ image_dark: assets/images/splash_logo_dark.png
@@ -1,151 +1,27 @@
1
1
  # Feature Patches
2
2
 
3
- Este diretório contém os **patches por feature**: arquivos extras que são copiados para o projeto gerado quando o usuário seleciona um módulo específico no wizard do CLI.
3
+ Este diretório guarda **patches por feature**: arquivos extras que são copiados para o projeto gerado quando o usuário seleciona um módulo específico no wizard do CLI.
4
4
 
5
- ## Estrutura esperada
5
+ ## Estado atual
6
6
 
7
- ```
8
- features/
9
- ci/ ← patch aplicado quando o usuário seleciona o módulo "ci"
10
- .github/
11
- workflows/
12
- app.yml
13
- .gitlab-ci.yml
14
- ...
15
- web/ ← patch aplicado quando "web" é selecionado
16
- web/
17
- index.html
18
- ...
19
- widget/ ← patch aplicado quando "widget" é selecionado
20
- android/
21
- ...
22
- ```
7
+ Este diretório está intencionalmente vazio (só este README). Todas as features já vivem dentro de `Firebase/` e são incluídas em `cli/templates/firebase/` pelo `cli/scripts/bundle-template.js` no momento do publish. Features opcionais são controladas por `removeModuleDirs()` em `cli/lib/scaffold/shared/generator-utils.js` — quando a feature não é selecionada, sua pasta é removida do projeto gerado.
23
8
 
24
- Cada subdiretório tem o **mesmo nome** que o valor do módulo em `new.js` (`sentry`, `analytics`, `facebook`, `revenuecat`, `onboarding`, `web`, `widget`, `llm_chat`, `local_notifications`, `feedback`, `ci`).
9
+ ## Quando criar um patch aqui
25
10
 
26
- Ao gerar um projeto, o engine copia recursivamente o conteúdo de `features/{modulo}/` para a raiz do projeto, sobrescrevendo ou acrescentando arquivos.
11
+ Apenas quando a feature precisar de arquivos que **não existem** em `Firebase/`. Se o mesmo caminho já existe em `Firebase/`, **não crie patch** — o patch vai sobrescrever silenciosamente o template atualizado pela versão (provavelmente antiga) que estiver no patch, causando regressão no projeto do cliente (perda de integrações, validações, UI nova, etc.).
27
12
 
28
- ## Módulos que precisam de patch físico
13
+ ## Como adicionar um patch
29
14
 
30
- | Módulo | Patch necessário? | Motivo |
31
- |-------------|------------------|-----------------------------------------------------|
32
- | `ci` | Sim | Adiciona `.github/`, `.gitlab-ci.yml`, etc. |
33
- | `web` | Sim | Adiciona pasta `web/` e configurações de plataforma |
34
- | `widget` | Não | Os arquivos do widget (iOS + Android + Dart) já vão no template base. Quando o módulo não é selecionado, `removeAndroidWidgetArtifacts` + `writeNoOpAdminHomeWidgets` em `generate.js` limpam o que sobra. |
35
- | `llm_chat` | Não | Apenas `LLM_CHAT_ENDPOINT` via dart-define. A chave da API LLM fica no servidor (Firebase Secret / Supabase Secret) — nunca no app. |
36
- | `sentry` | Não | Apenas dart-define (`SENTRY_DSN`) |
37
- | `analytics` | Não | Apenas dart-define |
38
- | `facebook` | Não | Token já substituído via `buildTokens` |
39
- | `revenuecat`| Não | Apenas dart-define (`REVENUECAT_KEY_*`) |
40
- | `onboarding`| Não | Habilitado via dart-define / `features_config.json` |
41
- | `feedback` | Não | Habilitado via dart-define / `features_config.json` |
42
- | `local_notifications` | ✅ Sim | Copia `lib/features/local_reminder/` com UI, provider e repositório |
15
+ 1. Crie `features/{modulo}/`.
16
+ 2. Coloque os arquivos exatamente como devem aparecer no projeto gerado (caminhos relativos à raiz do projeto).
17
+ 3. Adicione o nome do módulo a `ALLOWED_FEATURE_PATCHES` em `cli/scripts/check-feature-patches.js`, com um comentário de uma linha explicando por que o patch é necessário.
18
+ 4. O engine aplica os patches depois de copiar o template base e o patch do backend.
43
19
 
44
- ## Como criar um patch
20
+ ## Drift guard
45
21
 
46
- 1. Crie a pasta `features/{modulo}/`.
47
- 2. Coloque dentro dela os arquivos **exatamente como devem aparecer** no projeto gerado (caminhos relativos à raiz do projeto).
48
- 3. O engine aplica os patches após copiar o template base e após aplicar o patch do backend.
22
+ `cli/scripts/check-feature-patches.js` roda no `npm prepack` (antes de qualquer `npm publish` ou `npm pack`). O build falha se:
49
23
 
50
- > Os patches de feature são aplicados pelo `generate.js` via `applyFeaturePatches()`.
51
- > Os geradores específicos por backend (`firebase/generator.js`, `supabase/generator.js`, `api/generator.js`)
52
- > precisam ser atualizados para chamar essa etapa quando a unificação for concluída.
24
+ - alguma pasta dentro de `features/` não estiver listada em `ALLOWED_FEATURE_PATCHES`, ou
25
+ - algum arquivo dentro de um patch permitido tiver caminho equivalente em `Firebase/` (significa que o patch é duplicata desatualizada).
53
26
 
54
- ---
55
-
56
- ## Feature: `local_notifications` — Notificações Locais Agendadas
57
-
58
- > **Plataformas:** iOS e Android apenas (sem suporte Web/Desktop).
59
-
60
- ### O que esta feature faz
61
-
62
- Permite ao usuário configurar um lembrete recorrente diretamente no app, sem servidor. A notificação é agendada localmente via `flutter_local_notifications` e persiste entre sessões via `SharedPreferences`.
63
-
64
- ### Tipos de lembrete suportados
65
-
66
- | Tipo | Comportamento |
67
- |-----------------|------------------------------------------------------|
68
- | `daily` | Dispara todo dia no horário escolhido |
69
- | `weekly` | Dispara uma vez por semana no dia + horário escolhido |
70
- | `specificDate` | Dispara uma única vez na data + hora escolhida |
71
-
72
- ### Entrada do usuário
73
-
74
- Acesso via **Settings → Lembretes** (`/reminder`):
75
-
76
- 1. **Toggle "Ativar lembrete"** — liga/desliga (cancela notificação pendente ao desligar)
77
- 2. **Segmented button "Repetir"** — escolhe o tipo: `Todo dia / Toda semana / Data específica`
78
- 3. **Horário** — `TimePicker` nativo do sistema
79
- 4. **Dia da semana** — `ChoiceChip` (seg–dom), aparece somente no modo semanal
80
- 5. **Data e hora** — `DatePicker` + `TimePicker` em sequência, aparece somente no modo data específica
81
-
82
- ### Estrutura de arquivos gerados no projeto
83
-
84
- ```
85
- lib/features/local_reminder/
86
- repositories/
87
- reminder_preferences.dart ← SharedPreferences (load/save ReminderState)
88
- providers/
89
- reminder_notifier.dart ← AsyncNotifier (keepAlive) + agenda/cancela
90
- reminder_notifier.g.dart ← gerado pelo build_runner (provider: reminderProvider)
91
- ui/
92
- reminder_page.dart ← ReminderPage + _ReminderForm + subwidgets
93
- ```
94
-
95
- ### Integrações automáticas pelo CLI
96
-
97
- | Arquivo | O que é adicionado |
98
- |--------------------------------|---------------------------------------------------------|
99
- | `lib/core/config/features.dart` | `const bool withLocalNotifications = true/false;` |
100
- | `lib/router.dart` | Import + `GoRoute(path: '/reminder', ...)` |
101
- | `lib/features/settings/settings_page.dart` | Tile "Lembretes" → `context.push('/reminder')` |
102
-
103
- ### Model — `ReminderState`
104
-
105
- ```dart
106
- class ReminderState {
107
- final bool enabled;
108
- final ReminderType type; // daily | weekly | specificDate
109
- final int hour;
110
- final int minute;
111
- final int dayOfWeek; // 1=Segunda … 7=Domingo
112
- final DateTime? date; // usado somente para specificDate
113
- }
114
- ```
115
-
116
- ### Notificação agendada
117
-
118
- ID fixo `42`. Texto configurado via i18n (`dailyReminder.title` / `dailyReminder.body`).
119
- Ao salvar qualquer mudança, o provider cancela o ID 42 e re-agenda com os novos parâmetros.
120
-
121
- ### Dependências (já no core — não adicionadas pelo módulo)
122
-
123
- - `flutter_local_notifications`
124
- - `flutter_timezone` + `timezone`
125
- - `shared_preferences`
126
- - `riverpod_annotation`
127
-
128
- ### Traduções adicionadas
129
-
130
- Chaves em `lib/i18n/{pt,en,es}.i18n.json`:
131
-
132
- ```json
133
- "reminderPage": {
134
- "title": "Lembretes",
135
- "toggleLabel": "Ativar lembrete",
136
- "typeLabel": "Repetir",
137
- "daily": "Todo dia",
138
- "weekly": "Toda semana",
139
- "specificDate": "Data específica",
140
- "timeLabel": "Horário",
141
- "dayLabel": "Dia da semana",
142
- "dateLabel": "Data e hora",
143
- "selectDate": "Selecionar data e hora"
144
- },
145
- "dailyReminder": { "title": "Lembrete", "body": "Está na hora de beber água." },
146
- "settings": { "reminders": "Lembretes" }
147
- ```
148
-
149
- ### Bug corrigido durante a implementação
150
-
151
- `local_notifier.dart` tinha um método `scheduleFromNow()` com lógica invertida (agendava apenas quando **não** havia notificações pendentes) e usava `Future.delayed` que não sobrevive ao restart do app. O método foi **removido** completamente.
27
+ Esse é o mecanismo que impede o cenário em que patches divergem de `Firebase/` ao longo do tempo e o CLI passa a entregar código velho para os clientes.
@@ -273,7 +273,6 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
273
273
  lines.push(`import 'package:${pkg}/features/authentication/ui/signin_page.dart';`);
274
274
  lines.push(`import 'package:${pkg}/features/authentication/ui/signup_page.dart';`);
275
275
  if (withFeedback) {
276
- lines.push(`import 'package:${pkg}/features/feedbacks/ui/component/add_feature_form.dart';`);
277
276
  lines.push(`import 'package:${pkg}/features/feedbacks/ui/feedback_page.dart';`);
278
277
  }
279
278
  if (withLlmChat) {
@@ -374,11 +373,6 @@ async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'b
374
373
  lines.push(` path: '/feedback',`);
375
374
  lines.push(` builder: (context, state) => const FeedbackPage(),`);
376
375
  lines.push(` ),`);
377
- lines.push(` GoRoute(`);
378
- lines.push(` name: 'feedback_new',`);
379
- lines.push(` path: '/feedback/new',`);
380
- lines.push(` builder: (context, state) => const AddFeatureComponent(),`);
381
- lines.push(` ),`);
382
376
  }
383
377
 
384
378
  // LLM Chat
@@ -596,6 +590,13 @@ class NoOpAnalyticsApi implements AnalyticsApi {
596
590
  @override Future<void> identify(User user) async {}
597
591
  }
598
592
 
593
+ // Stub kept for source-compat with code that still calls MixpanelAnalyticsApi.instance().
594
+ // Run: kasy add analytics to swap this file for the real Mixpanel-backed implementation.
595
+ class MixpanelAnalyticsApi extends NoOpAnalyticsApi {
596
+ const MixpanelAnalyticsApi._() : super();
597
+ factory MixpanelAnalyticsApi.instance() => const MixpanelAnalyticsApi._();
598
+ }
599
+
599
600
  class AnalyticsObserver extends RouteObserver<ModalRoute<dynamic>> {
600
601
  final AnalyticsApi analyticsApi;
601
602
  final String? prefix;
@@ -1186,11 +1187,8 @@ async function stripPubspecDeps(projectDir, modules) {
1186
1187
  }
1187
1188
 
1188
1189
  /**
1189
- * Patches core files that always import sentry_flutter, removing the dependency
1190
+ * Patches files that always import sentry_flutter, removing the dependency
1190
1191
  * when neither sentry, revenuecat, nor facebook modules are selected.
1191
- * Affected files:
1192
- * - lib/core/initializer/onstart_widget.dart
1193
- * - lib/core/data/api/remote_config_api.dart
1194
1192
  *
1195
1193
  * @param {string} projectDir
1196
1194
  */
@@ -1200,15 +1198,16 @@ async function writeNoOpSentryUsages(projectDir) {
1200
1198
  const files = [
1201
1199
  path.join(projectDir, 'lib', 'core', 'initializer', 'onstart_widget.dart'),
1202
1200
  path.join(projectDir, 'lib', 'core', 'data', 'api', 'remote_config_api.dart'),
1201
+ path.join(projectDir, 'lib', 'features', 'notifications', 'api', 'local_notifier.dart'),
1202
+ path.join(projectDir, 'lib', 'features', 'notifications', 'providers', 'models', 'notification.dart'),
1203
+ path.join(projectDir, 'lib', 'features', 'notifications', 'shared', 'notification_permission_bottom_sheet.dart'),
1203
1204
  ];
1204
1205
 
1205
1206
  for (const filePath of files) {
1206
1207
  if (!(await fs.pathExists(filePath))) continue;
1207
1208
  let content = await fs.readFile(filePath, 'utf8');
1208
- // Remove sentry import line
1209
1209
  content = content.replace(sentryImport, '');
1210
- // Remove single-line Sentry.captureException(...); calls (with leading whitespace)
1211
- content = content.replace(/^[ \t]*Sentry\.captureException\([^)]+\);\n/gm, '');
1210
+ content = content.replace(/^[ \t]*Sentry\.captureException\([^)]*\);\n/gm, '');
1212
1211
  await fs.outputFile(filePath, content, 'utf8');
1213
1212
  }
1214
1213
  }
@@ -1469,11 +1468,13 @@ class PremiumPageArgs {
1469
1468
  'utf8',
1470
1469
  );
1471
1470
 
1472
- // 4. No-op premium_page_factory.dart (used by admin_paywalls)
1471
+ // 4. No-op premium_page_factory.dart (used by admin_paywalls).
1472
+ // Mirror the constants declared in Firebase/.../premium_page_factory.dart so that
1473
+ // admin_routes.dart compiles even without revenuecat selected.
1473
1474
  await fs.outputFile(
1474
1475
  path.join(projectDir, 'lib', 'features', 'subscription', 'ui', 'component', 'premium_page_factory.dart'),
1475
1476
  `// No-op paywall factory. Run: kasy add revenuecat to activate.
1476
- enum PaywallFactory { basic }
1477
+ enum PaywallFactory { basic, basicRow, minimal, withSwitch }
1477
1478
  `,
1478
1479
  'utf8',
1479
1480
  );