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,50 +0,0 @@
1
- import 'package:cloud_firestore/cloud_firestore.dart';
2
- import 'package:flutter_riverpod/flutter_riverpod.dart';
3
- import 'package:kasy_kit/features/feedbacks/api/entities/feature_request_entity.dart';
4
-
5
- final featureRequestApiProvider = Provider<FeatureRequestApi>(
6
- (ref) => FeatureRequestApi(
7
- client: FirebaseFirestore.instance,
8
- ),
9
- );
10
-
11
- class FeatureRequestApi {
12
- final FirebaseFirestore _client;
13
-
14
- FeatureRequestApi({
15
- required FirebaseFirestore client,
16
- }) : _client = client;
17
-
18
- CollectionReference<FeatureRequestEntity?> get _collection => _client
19
- .collection('feature_requests')
20
- .withConverter(
21
- fromFirestore: (snapshot, _) {
22
- if (snapshot.exists) {
23
- return FeatureRequestEntity.fromJson(snapshot.id, snapshot.data()!);
24
- }
25
- return null;
26
- },
27
- toFirestore: (data, _) => data!.toJson(),
28
- );
29
-
30
- Future<List<FeatureRequestEntity>> getAllActive() async {
31
- final snapshot = await _collection.where('active', isEqualTo: true).get();
32
- return snapshot.docs.map((e) => e.data()!).toList();
33
- }
34
-
35
- Future<String> create({
36
- required String title,
37
- required String description,
38
- }) async {
39
- final now = DateTime.now().toIso8601String();
40
- final docRef = await _client.collection('feature_requests').add({
41
- 'title': {'en': title, 'pt': title, 'es': title},
42
- 'description': {'en': description, 'pt': description, 'es': description},
43
- 'votes': 0,
44
- 'active': false,
45
- 'creation_date': now,
46
- 'last_update_date': now,
47
- });
48
- return docRef.id;
49
- }
50
- }
@@ -1,79 +0,0 @@
1
- import 'package:cloud_firestore/cloud_firestore.dart';
2
- import 'package:flutter_riverpod/flutter_riverpod.dart';
3
- import 'package:kasy_kit/features/feedbacks/api/entities/feature_vote_entity.dart';
4
-
5
- final featureVoteApiProvider = Provider<FeatureVoteApi>(
6
- (ref) => FeatureVoteApi(
7
- client: FirebaseFirestore.instance,
8
- ),
9
- );
10
-
11
- class FeatureVoteApi {
12
- final FirebaseFirestore _client;
13
-
14
- FeatureVoteApi({
15
- required FirebaseFirestore client,
16
- }) : _client = client;
17
-
18
- CollectionReference<UserFeatureVoteEntity?> _featureVoteCollection(
19
- String featureId) =>
20
- _client
21
- .collection('feature_requests')
22
- .doc(featureId)
23
- .collection('votes')
24
- .withConverter(
25
- fromFirestore: (snapshot, _) {
26
- if (snapshot.exists) {
27
- return UserFeatureVoteEntity.fromJson(
28
- snapshot.id, snapshot.data()!);
29
- }
30
- return null;
31
- },
32
- toFirestore: (data, _) => data!.toJson(),
33
- );
34
-
35
- Query<UserFeatureVoteEntity?> get collectionGroup =>
36
- _client.collectionGroup('votes').withConverter(
37
- fromFirestore: (snapshot, _) {
38
- if (snapshot.exists) {
39
- return UserFeatureVoteEntity.fromJson(
40
- snapshot.id, snapshot.data()!);
41
- }
42
- return null;
43
- },
44
- toFirestore: (data, _) => data!.toJson(),
45
- );
46
-
47
- Future<List<UserFeatureVoteEntity>> getUserVotes(String userId) async {
48
- final snapshot =
49
- await collectionGroup.where('userId', isEqualTo: userId).get();
50
- return snapshot.docs.map((e) => e.data()!).toList();
51
- }
52
-
53
- Future<UserFeatureVoteEntity> create(String userId, String featureId) async {
54
- final result = UserFeatureVoteEntity(
55
- id: userId,
56
- userId: userId,
57
- creationDate: DateTime.now(),
58
- featureId: featureId,
59
- );
60
- final batch = _client.batch();
61
- batch.set(_featureVoteCollection(featureId).doc(userId), result);
62
- batch.update(
63
- _client.collection('feature_requests').doc(featureId),
64
- {'votes': FieldValue.increment(1), 'last_update_date': FieldValue.serverTimestamp()},
65
- );
66
- await batch.commit();
67
- return result;
68
- }
69
-
70
- Future<void> delete(String featureId, String voteId) async {
71
- final batch = _client.batch();
72
- batch.delete(_featureVoteCollection(featureId).doc(voteId));
73
- batch.update(
74
- _client.collection('feature_requests').doc(featureId),
75
- {'votes': FieldValue.increment(-1), 'last_update_date': FieldValue.serverTimestamp()},
76
- );
77
- await batch.commit();
78
- }
79
- }
@@ -1,48 +0,0 @@
1
- import 'package:freezed_annotation/freezed_annotation.dart';
2
- import 'package:kasy_kit/features/feedbacks/api/entities/feature_request_entity.dart';
3
- import 'package:kasy_kit/features/feedbacks/api/entities/feature_vote_entity.dart';
4
-
5
- part 'feature_requests.freezed.dart';
6
-
7
- @freezed
8
- abstract class FeatureRequest with _$FeatureRequest {
9
- const factory FeatureRequest({
10
- required String id,
11
- required DateTime creationDate,
12
- required String title,
13
- required String description,
14
- required int votes,
15
- }) = FeatureRequestData;
16
-
17
- const FeatureRequest._();
18
-
19
- factory FeatureRequest.from(FeatureRequestEntity entity, String language) {
20
- return FeatureRequest(
21
- id: entity.id!,
22
- creationDate: entity.creationDate,
23
- title: entity.title.containsKey(language)
24
- ? entity.title[language]!
25
- : entity.title['en']!,
26
- description: entity.description.containsKey(language)
27
- ? entity.description[language]!
28
- : entity.description['en']!,
29
- votes: entity.votes,
30
- );
31
- }
32
- }
33
-
34
- @freezed
35
- abstract class FeatureRequestVote with _$FeatureRequestVote {
36
- const factory FeatureRequestVote({
37
- required String id,
38
- required String featureId,
39
- }) = FeatureRequestVoteData;
40
-
41
- const FeatureRequestVote._();
42
-
43
- factory FeatureRequestVote.from(UserFeatureVoteEntity entity) =>
44
- FeatureRequestVote(
45
- id: entity.id!,
46
- featureId: entity.featureId,
47
- );
48
- }
@@ -1,42 +0,0 @@
1
- import 'package:kasy_kit/features/feedbacks/models/feature_requests.dart';
2
-
3
- class FeebackPageState {
4
- List<FeatureRequestVote> userVotes;
5
- List<FeatureRequest> featureRequests;
6
-
7
- FeebackPageState({
8
- required this.featureRequests,
9
- required this.userVotes,
10
- });
11
-
12
- FeebackPageState copyWith({
13
- List<FeatureRequest>? featureRequests,
14
- List<FeatureRequestVote>? userVotes,
15
- }) {
16
- return FeebackPageState(
17
- featureRequests: featureRequests ?? this.featureRequests,
18
- userVotes: userVotes ?? this.userVotes,
19
- );
20
- }
21
-
22
- FeebackPageState addVote(FeatureRequest feature, int vote) {
23
- final index =
24
- featureRequests.indexWhere((element) => element.id == feature.id);
25
- if (index == -1) {
26
- return this;
27
- }
28
- final newFeature =
29
- featureRequests[index].copyWith(votes: feature.votes + vote);
30
- final newFeatureRequests = List<FeatureRequest>.from(featureRequests);
31
- newFeatureRequests[index] = newFeature;
32
- return copyWith(featureRequests: newFeatureRequests);
33
- }
34
-
35
- bool hasVoted(FeatureRequest feature) {
36
- return userVotes.any((element) => element.featureId == feature.id);
37
- }
38
-
39
- bool canVote() {
40
- return userVotes.isEmpty;
41
- }
42
- }
@@ -1,147 +0,0 @@
1
- import 'package:flutter/foundation.dart';
2
- import 'package:kasy_kit/components/kasy_toast.dart';
3
- import 'package:kasy_kit/core/states/translations.dart';
4
- import 'package:kasy_kit/core/states/user_state_notifier.dart';
5
- import 'package:kasy_kit/features/feedbacks/models/feature_requests.dart';
6
- import 'package:kasy_kit/features/feedbacks/models/feedback_state.dart';
7
- import 'package:kasy_kit/features/feedbacks/repositories/feature_request_repository.dart';
8
- import 'package:riverpod_annotation/riverpod_annotation.dart';
9
-
10
- part 'feedback_page_notifier.g.dart';
11
-
12
- @Riverpod()
13
- class FeedbackPageNotifier extends _$FeedbackPageNotifier {
14
- bool _isVoting = false;
15
-
16
- @override
17
- Future<FeebackPageState> build() async {
18
- final featureRequestRepository = ref.read(featureRequestRepositoryProvider);
19
- final userId = ref.read(userStateNotifierProvider).user.idOrNull;
20
-
21
- final userVotes = userId != null
22
- ? await featureRequestRepository.getUserVotes(userId)
23
- : <FeatureRequestVote>[];
24
- final features = await featureRequestRepository.getActiveFeatureRequests();
25
- features.sort((a, b) => b.votes.compareTo(a.votes));
26
-
27
- return FeebackPageState(
28
- featureRequests: features,
29
- userVotes: userVotes,
30
- );
31
- }
32
-
33
- Future<void> vote(FeatureRequest featureRequest) async {
34
- if (_isVoting) return;
35
- _isVoting = true;
36
- final translations = ref.read(translationsProvider).feature_requests;
37
-
38
- if (state.value!.hasVoted(featureRequest)) {
39
- await _unVote(featureRequest);
40
- _isVoting = false;
41
- return;
42
- }
43
-
44
- final oldState = state.value!;
45
- final featureRequestRepository = ref.read(featureRequestRepositoryProvider);
46
- final userState = ref.read(userStateNotifierProvider);
47
-
48
- try {
49
- final userId = userState.user.idOrNull;
50
- if (userId == null) return;
51
-
52
- // Optimistic update: count +1 AND mark as voted immediately so
53
- // hasVoted returns true right away (prevents inverted toggle).
54
- final optimisticVote = FeatureRequestVote(
55
- id: '_optimistic',
56
- featureId: featureRequest.id,
57
- );
58
- var newState = state.value!
59
- .addVote(featureRequest, 1)
60
- .copyWith(
61
- userVotes: [...state.value!.userVotes, optimisticVote],
62
- );
63
- state = AsyncData(newState);
64
-
65
- final userVote = await featureRequestRepository.vote(
66
- userId,
67
- featureRequest,
68
- );
69
- // Replace optimistic entry with real vote from server.
70
- newState = newState.copyWith(userVotes: [
71
- ...newState.userVotes.where((v) => v.id != '_optimistic'),
72
- userVote,
73
- ]);
74
- state = AsyncData(newState);
75
- ref.read(toastProvider).success(
76
- title: translations.vote_success.title,
77
- text: translations.vote_success.description,
78
- );
79
- } catch (e) {
80
- state = AsyncData(oldState);
81
- ref.read(toastProvider).error(
82
- title: translations.vote_error.title,
83
- text: translations.vote_error.description,
84
- );
85
- } finally {
86
- _isVoting = false;
87
- }
88
- }
89
-
90
- Future<void> _unVote(FeatureRequest featureRequest) async {
91
- final oldState = state.value!;
92
- final featureRequestRepository = ref.read(featureRequestRepositoryProvider);
93
- final userState = ref.read(userStateNotifierProvider);
94
-
95
- try {
96
- final userId = userState.user.idOrNull;
97
- if (userId == null) return;
98
- final voteToDelete = oldState.userVotes
99
- .where((e) => e.featureId == featureRequest.id)
100
- .firstOrNull;
101
- if (voteToDelete == null) return;
102
-
103
- var newState = oldState.addVote(featureRequest, -1);
104
- newState = newState.copyWith(userVotes: [
105
- ...newState.userVotes.where((e) => e.featureId != featureRequest.id),
106
- ]);
107
- state = AsyncData(newState);
108
-
109
- // If the vote was only optimistic (never reached the server), skip delete.
110
- if (voteToDelete.id == '_optimistic') return;
111
- await featureRequestRepository.removeVote(userId, voteToDelete);
112
- } catch (e) {
113
- debugPrint('Error while unvoting: $e');
114
- state = AsyncData(oldState);
115
- }
116
- }
117
-
118
- Future<void> createFeatureSuggestion({
119
- required String title,
120
- required String description,
121
- }) async {
122
- final userId = ref.read(userStateNotifierProvider).user.idOrNull;
123
- if (userId == null) return;
124
- if (title.isEmpty || description.isEmpty) {
125
- final tr = ref.read(translationsProvider).feature_requests.add_feature;
126
- ref.read(toastProvider).error(
127
- title: tr.error_title,
128
- text: tr.error_required,
129
- );
130
- return;
131
- }
132
- if (description.length < 40) {
133
- final tr = ref.read(translationsProvider).feature_requests.add_feature;
134
- ref.read(toastProvider).error(
135
- title: tr.error_title,
136
- text: tr.error_too_short,
137
- );
138
- return;
139
- }
140
- final featureRequestRepository = ref.read(featureRequestRepositoryProvider);
141
- await featureRequestRepository.createNewFeatureSuggestion(
142
- userId: userId,
143
- title: title,
144
- description: description,
145
- );
146
- }
147
- }
@@ -1,95 +0,0 @@
1
- import 'package:flutter_riverpod/flutter_riverpod.dart';
2
- import 'package:logger/logger.dart';
3
- import 'package:kasy_kit/i18n/translations.g.dart';
4
- import 'package:kasy_kit/features/feedbacks/api/feature_request_api.dart';
5
- import 'package:kasy_kit/features/feedbacks/api/feature_vote_api.dart';
6
- import 'package:kasy_kit/features/feedbacks/models/feature_requests.dart';
7
-
8
- final featureRequestRepositoryProvider = Provider<FeatureRequestRepository>(
9
- (ref) => FeatureRequestRepository(
10
- featureVoteApi: ref.read(featureVoteApiProvider),
11
- featureRequestApi: ref.read(featureRequestApiProvider),
12
- logger: Logger(),
13
- ),
14
- );
15
-
16
- class FeatureRequestRepository {
17
- final FeatureVoteApi _featureVoteApi;
18
- final FeatureRequestApi _featureRequestApi;
19
- final Logger _logger;
20
-
21
- FeatureRequestRepository({
22
- required FeatureVoteApi featureVoteApi,
23
- required FeatureRequestApi featureRequestApi,
24
- required Logger logger,
25
- }) : _featureVoteApi = featureVoteApi,
26
- _logger = logger,
27
- _featureRequestApi = featureRequestApi;
28
-
29
- Future<List<FeatureRequest>> getActiveFeatureRequests() async {
30
- try {
31
- final featureRequestsEntities = await _featureRequestApi.getAllActive();
32
- final currentLocale = LocaleSettings.currentLocale;
33
- return featureRequestsEntities
34
- .map(
35
- (entity) => FeatureRequest.from(entity, currentLocale.languageCode),
36
- )
37
- .toList();
38
- } catch (e, s) {
39
- _logger.e('Error while fetching active feature requests: $e : $s');
40
- rethrow;
41
- }
42
- }
43
-
44
- Future<List<FeatureRequestVote>> getUserVotes(String userId) async {
45
- try {
46
- final uservotes = await _featureVoteApi.getUserVotes(userId);
47
- return uservotes.map((e) => FeatureRequestVote.from(e)).toList();
48
- } catch (e, s) {
49
- _logger.e('Error while fetching user votes: $e : $s');
50
- rethrow;
51
- }
52
- }
53
-
54
- Future<FeatureRequestVote> vote(
55
- String userId,
56
- FeatureRequest featureRequest,
57
- ) async {
58
- try {
59
- final result = await _featureVoteApi.create(
60
- userId,
61
- featureRequest.id,
62
- );
63
- return FeatureRequestVote.from(result);
64
- } catch (e, s) {
65
- _logger.e('Error while voting for feature request: $e : $s');
66
- rethrow;
67
- }
68
- }
69
-
70
- Future<void> removeVote(String userId, FeatureRequestVote vote) async {
71
- try {
72
- await _featureVoteApi.delete(vote.featureId, vote.id);
73
- } catch (e, s) {
74
- _logger.e('Error while deleting vote for feature request: $e : $s');
75
- rethrow;
76
- }
77
- }
78
-
79
- Future<void> createNewFeatureSuggestion({
80
- required String userId,
81
- required String title,
82
- required String description,
83
- }) async {
84
- try {
85
- final featureId = await _featureRequestApi.create(
86
- title: title,
87
- description: description,
88
- );
89
- await _featureVoteApi.create(userId, featureId);
90
- } catch (e, s) {
91
- _logger.e('Error while creating feature suggestion: $e : $s');
92
- rethrow;
93
- }
94
- }
95
- }
@@ -1,199 +0,0 @@
1
- import 'package:flutter/material.dart';
2
- import 'package:flutter_riverpod/flutter_riverpod.dart';
3
- import 'package:go_router/go_router.dart';
4
- import 'package:kasy_kit/components/components.dart';
5
- import 'package:kasy_kit/core/theme/theme.dart';
6
- import 'package:kasy_kit/core/widgets/kasy_scroll_behavior.dart';
7
- import 'package:kasy_kit/features/feedbacks/providers/feedback_page_notifier.dart';
8
- import 'package:kasy_kit/i18n/translations.g.dart';
9
-
10
- Future<void> showAddFeatureBottomSheet(BuildContext context) {
11
- return showModalBottomSheet<bool?>(
12
- context: context,
13
- isScrollControlled: true,
14
- builder: (context) => const FractionallySizedBox(
15
- heightFactor: .8,
16
- child: AddFeatureComponent(),
17
- ),
18
- ).then((res) {
19
- if (!context.mounted) return;
20
- final translations = Translations.of(context).feature_requests.add_feature;
21
- if (res == true) {
22
- showSuccessToast(
23
- context: context,
24
- title: translations.toast_success.title,
25
- text: translations.toast_success.description,
26
- );
27
- }
28
- });
29
- }
30
-
31
- class AddFeatureComponent extends ConsumerStatefulWidget {
32
- const AddFeatureComponent({super.key});
33
-
34
- @override
35
- ConsumerState<ConsumerStatefulWidget> createState() =>
36
- _AddFeatureComponentState();
37
- }
38
-
39
- class _AddFeatureComponentState extends ConsumerState<AddFeatureComponent> {
40
- String title = '';
41
- String description = '';
42
- bool locked = false;
43
-
44
- final titleFocusNode = FocusNode();
45
- final textFocusNode = FocusNode();
46
-
47
- bool get _titleFilled => title.trim().isNotEmpty;
48
- bool get _descriptionValid => description.length >= 40;
49
- bool get canSubmit => _titleFilled && _descriptionValid && !locked;
50
-
51
- void _onSubmitAttempt() {
52
- final tr = Translations.of(context).feature_requests.add_feature;
53
- if (!_titleFilled || description.trim().isEmpty) {
54
- ref.read(toastProvider).error(
55
- title: tr.error_title,
56
- text: tr.error_required,
57
- );
58
- return;
59
- }
60
- if (!_descriptionValid) {
61
- ref.read(toastProvider).error(
62
- title: tr.error_title,
63
- text: tr.error_too_short,
64
- );
65
- return;
66
- }
67
- }
68
-
69
- void _submit() {
70
- if (locked) return;
71
- FocusScope.of(context).unfocus();
72
- locked = true;
73
- final tr = Translations.of(context).feature_requests.add_feature;
74
- ref
75
- .read(feedbackPageProvider.notifier)
76
- .createFeatureSuggestion(
77
- title: title,
78
- description: description,
79
- )
80
- .then(
81
- (_) {
82
- locked = false;
83
- if (!mounted) return;
84
- context.pop(true);
85
- },
86
- onError: (_) {
87
- locked = false;
88
- ref.read(toastProvider).error(
89
- title: tr.error_title,
90
- text: tr.error_sending,
91
- );
92
- },
93
- );
94
- }
95
-
96
- @override
97
- Widget build(BuildContext context) {
98
- final translations = Translations.of(context).feature_requests.add_feature;
99
-
100
- final Widget submitButton = KasyButton(
101
- label: translations.save_button,
102
- expand: true,
103
- onPressed: canSubmit ? _submit : null,
104
- );
105
-
106
- return Scaffold(
107
- body: Stack(
108
- fit: StackFit.expand,
109
- children: [
110
- Positioned.fill(
111
- child: ScrollConfiguration(
112
- behavior: const KasyKitScrollBehavior(),
113
- child: CustomScrollView(
114
- slivers: kasyOverlayPaddedSlivers(
115
- context,
116
- contentPadding: EdgeInsets.fromLTRB(
117
- KasySpacing.pageHorizontalGutter,
118
- KasySpacing.belowChromeContentGap,
119
- KasySpacing.pageHorizontalGutter,
120
- MediaQuery.paddingOf(context).bottom + KasySpacing.xxxl,
121
- ),
122
- slivers: [
123
- SliverToBoxAdapter(
124
- child: Column(
125
- crossAxisAlignment: CrossAxisAlignment.stretch,
126
- children: [
127
- const SizedBox(height: KasySpacing.md),
128
- Text(
129
- translations.description,
130
- style: context.textTheme.bodyMedium?.copyWith(
131
- color: context.colors.muted,
132
- height: 1.5,
133
- ),
134
- ),
135
- const SizedBox(height: KasySpacing.xl),
136
- KasyTextField(
137
- label: translations.title_label,
138
- hint: translations.title_hint,
139
- showRequiredIndicator: true,
140
- variant: KasyTextFieldVariant.secondary,
141
- onChanged: (value) =>
142
- setState(() => title = value),
143
- focusNode: titleFocusNode,
144
- textInputAction: TextInputAction.next,
145
- onSubmitted: (_) {
146
- titleFocusNode.unfocus();
147
- textFocusNode.requestFocus();
148
- },
149
- maxLength: 40,
150
- ),
151
- const SizedBox(height: KasySpacing.md),
152
- KasyTextField(
153
- label: translations.description_label,
154
- hint: translations.description_hint,
155
- showRequiredIndicator: true,
156
- variant: KasyTextFieldVariant.secondary,
157
- minLines: 6,
158
- maxLines: 6,
159
- onChanged: (value) =>
160
- setState(() => description = value),
161
- textInputAction: TextInputAction.done,
162
- onSubmitted: (_) => textFocusNode.unfocus(),
163
- maxLength: 1000,
164
- focusNode: textFocusNode,
165
- ),
166
- const SizedBox(height: KasySpacing.xl),
167
- ],
168
- ),
169
- ),
170
- ],
171
- ),
172
- ),
173
- ),
174
- ),
175
- Positioned(
176
- top: 0,
177
- left: 0,
178
- right: 0,
179
- child: KasyAppBar(
180
- title: translations.title,
181
- onBack: () => context.pop(),
182
- ),
183
- ),
184
- Positioned(
185
- left: KasySpacing.pageHorizontalGutter,
186
- right: KasySpacing.pageHorizontalGutter,
187
- bottom: MediaQuery.paddingOf(context).bottom + KasySpacing.md,
188
- child: canSubmit
189
- ? submitButton
190
- : GestureDetector(
191
- onTap: _onSubmitAttempt,
192
- child: submitButton,
193
- ),
194
- ),
195
- ],
196
- ),
197
- );
198
- }
199
- }