kasy-cli 1.2.1
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.
- package/README.md +56 -0
- package/bin/kasy.js +529 -0
- package/docs/cli-reference.md +251 -0
- package/lib/commands/add.js +777 -0
- package/lib/commands/check.js +240 -0
- package/lib/commands/codemagic.js +161 -0
- package/lib/commands/deploy.js +257 -0
- package/lib/commands/docs.js +47 -0
- package/lib/commands/doctor.js +227 -0
- package/lib/commands/features.js +36 -0
- package/lib/commands/ios.js +206 -0
- package/lib/commands/new.js +1870 -0
- package/lib/commands/notifications.js +207 -0
- package/lib/commands/remove.js +466 -0
- package/lib/commands/run.js +75 -0
- package/lib/commands/update.js +382 -0
- package/lib/commands/validate.js +131 -0
- package/lib/scaffold/CHANGELOG.json +6 -0
- package/lib/scaffold/backends/api/.flutter-plugins-dependencies +1 -0
- package/lib/scaffold/backends/api/analysis_options.yaml +5 -0
- package/lib/scaffold/backends/api/generator.js +76 -0
- package/lib/scaffold/backends/api/patch/README.md +85 -0
- package/lib/scaffold/backends/api/patch/android/app/src/main/AndroidManifest.xml +87 -0
- package/lib/scaffold/backends/api/patch/ios/Runner/AppDelegate.swift +61 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/api/meta_ads_api.dart +80 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +117 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/api/user_api.dart +148 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/entities/json_converters.dart +35 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +48 -0
- package/lib/scaffold/backends/api/patch/lib/core/rating/widgets/review_popup.dart +211 -0
- package/lib/scaffold/backends/api/patch/lib/environnements.dart +151 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +303 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api_interface.dart +80 -0
- package/lib/scaffold/backends/api/patch/lib/features/authentication/repositories/authentication_repository.dart +406 -0
- package/lib/scaffold/backends/api/patch/lib/features/camera/xfile_extension.dart +6 -0
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/entities/feature_request_entity.dart +24 -0
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/entities/feature_vote_entity.dart +21 -0
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +52 -0
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_vote_api.dart +60 -0
- package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +58 -0
- package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_message_entity.dart +29 -0
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +398 -0
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/entities/device_entity.dart +39 -0
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/entities/notifications_entity.dart +23 -0
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/notifications_api.dart +333 -0
- package/lib/scaffold/backends/api/patch/lib/features/notifications/providers/models/notification.dart +185 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/api/entities/user_info_entity.dart +26 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/api/user_infos_api.dart +107 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/models/user_info.dart +94 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/repositories/user_infos_repository.dart +29 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/animations/movefade_anim.dart +34 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +66 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +73 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +173 -0
- package/lib/scaffold/backends/api/patch/lib/features/settings/ui/widgets/avatar_utils.dart +33 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscription/api/entities/subscription_entity.dart +34 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscription/api/subscription_api.dart +49 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +88 -0
- package/lib/scaffold/backends/api/patch/lib/main.dart +256 -0
- package/lib/scaffold/backends/api/patch/lib/router.dart +133 -0
- package/lib/scaffold/backends/api/pubspec.yaml.tpl +108 -0
- package/lib/scaffold/backends/firebase/deploy.js +1006 -0
- package/lib/scaffold/backends/firebase/generator.js +24 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +1008 -0
- package/lib/scaffold/backends/firebase/tokens.js +113 -0
- package/lib/scaffold/backends/supabase/.flutter-plugins-dependencies +1 -0
- package/lib/scaffold/backends/supabase/analysis_options.yaml +5 -0
- package/lib/scaffold/backends/supabase/config.toml +34 -0
- package/lib/scaffold/backends/supabase/deploy.js +485 -0
- package/lib/scaffold/backends/supabase/edge-functions/delete-user-account/README.md +15 -0
- package/lib/scaffold/backends/supabase/edge-functions/delete-user-account/index.ts +97 -0
- package/lib/scaffold/backends/supabase/edge-functions/llm-chat/index.ts +156 -0
- package/lib/scaffold/backends/supabase/edge-functions/meta-track-event/index.ts +292 -0
- package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/README.md +30 -0
- package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/index.ts +488 -0
- package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/README.md +35 -0
- package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/index.ts +314 -0
- package/lib/scaffold/backends/supabase/generator.js +108 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000001_initial_schema.sql +149 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000002_storage_bucket.sql +40 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000005_vote_triggers.sql +34 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000006_notification_webhook.sql +36 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000007_welcome_notification.sql +64 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000008_user_feature_requests.sql +2 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +22 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000010_notification_image.sql +2 -0
- package/lib/scaffold/backends/supabase/patch/README.md +121 -0
- package/lib/scaffold/backends/supabase/patch/android/app/src/main/AndroidManifest.xml +87 -0
- package/lib/scaffold/backends/supabase/patch/ios/Runner/AppDelegate.swift +61 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/data/api/meta_ads_api.dart +75 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/data/api/storage_api.dart +83 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/data/api/user_api.dart +143 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/json_converters.dart +35 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +48 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/rating/widgets/review_popup.dart +211 -0
- package/lib/scaffold/backends/supabase/patch/lib/environnements.dart +149 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +546 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/repositories/authentication_repository.dart +410 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/camera/xfile_extension.dart +6 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/entities/feature_request_entity.dart +25 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/entities/feature_vote_entity.dart +21 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart +59 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_vote_api.dart +85 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/ui/component/add_feature_form.dart +199 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +47 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_message_entity.dart +30 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/providers/llm_chat_notifier.dart +266 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +410 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/entities/device_entity.dart +51 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/entities/notifications_entity.dart +26 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/notifications_api.dart +317 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/providers/models/notification.dart +174 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/api/entities/user_info_entity.dart +26 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/api/user_infos_api.dart +90 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/models/user_info.dart +94 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/repositories/user_infos_repository.dart +29 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/animations/movefade_anim.dart +34 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +66 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +73 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +173 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/widgets/avatar_utils.dart +35 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscription/api/entities/subscription_entity.dart +35 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscription/api/subscription_api.dart +44 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscription/shared/maybeshow_premium.dart +85 -0
- package/lib/scaffold/backends/supabase/patch/lib/google_auth_options.dart +23 -0
- package/lib/scaffold/backends/supabase/patch/lib/main.dart +288 -0
- package/lib/scaffold/backends/supabase/patch/lib/router.dart +133 -0
- package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +110 -0
- package/lib/scaffold/backends/supabase/tokens.js +9 -0
- package/lib/scaffold/catalog.js +177 -0
- package/lib/scaffold/engine.js +230 -0
- package/lib/scaffold/features/README.md +152 -0
- package/lib/scaffold/features/analytics/lib/core/data/api/analytics_api.dart +124 -0
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.es.md +35 -0
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.md +35 -0
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.pt.md +35 -0
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.es.md +12 -0
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.md +12 -0
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.pt.md +12 -0
- package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.es.md +17 -0
- package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.md +17 -0
- package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.pt.md +17 -0
- package/lib/scaffold/features/ci/.github/dependabot.yml +16 -0
- package/lib/scaffold/features/ci/.github/workflows/app.yml +20 -0
- package/lib/scaffold/features/ci/.gitlab/templates/deploy.yaml +14 -0
- package/lib/scaffold/features/ci/.gitlab/templates/dropbox.yaml +19 -0
- package/lib/scaffold/features/ci/.gitlab/templates/flutter.yaml +163 -0
- package/lib/scaffold/features/ci/.gitlab/templates/mailgun.yaml +28 -0
- package/lib/scaffold/features/ci/.gitlab-ci.yml +37 -0
- package/lib/scaffold/features/ci/codemagic.yaml +157 -0
- package/lib/scaffold/features/facebook/lib/core/data/api/tracking_api.dart +111 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/api/entities/feature_request_entity.dart +27 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/api/entities/feature_vote_entity.dart +27 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/api/feature_request_api.dart +50 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/api/feature_vote_api.dart +79 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/models/feature_requests.dart +48 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/models/feedback_state.dart +42 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/providers/feedback_page_notifier.dart +147 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/repositories/feature_request_repository.dart +95 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/component/add_feature_form.dart +199 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/feedback_page.dart +175 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/widgets/add_feature_button.dart +76 -0
- package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/widgets/feature_card.dart +279 -0
- package/lib/scaffold/features/ios-release/.kasy/apple.env.example +8 -0
- package/lib/scaffold/features/ios-release/.kasy/codemagic.env.example +7 -0
- package/lib/scaffold/features/ios-release/docs/codemagic-release.en.md +50 -0
- package/lib/scaffold/features/ios-release/docs/codemagic-release.es.md +50 -0
- package/lib/scaffold/features/ios-release/docs/codemagic-release.pt.md +50 -0
- package/lib/scaffold/features/ios-release/docs/ios-release.en.md +41 -0
- package/lib/scaffold/features/ios-release/docs/ios-release.es.md +41 -0
- package/lib/scaffold/features/ios-release/docs/ios-release.pt.md +41 -0
- package/lib/scaffold/features/ios-release/scripts/bump-ios-version.js +38 -0
- package/lib/scaffold/features/ios-release/scripts/release-ios.sh +137 -0
- package/lib/scaffold/features/llm_chat/lib/features/llm_chat/llm_chat_page.dart +301 -0
- package/lib/scaffold/features/local_notifications/lib/features/local_reminder/providers/reminder_notifier.dart +81 -0
- package/lib/scaffold/features/local_notifications/lib/features/local_reminder/repositories/reminder_preferences.dart +76 -0
- package/lib/scaffold/features/local_notifications/lib/features/local_reminder/ui/reminder_page.dart +282 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/api/entities/user_info_entity.dart +24 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/api/user_infos_api.dart +71 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/models/user_info.dart +92 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_model.dart +15 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_provider.dart +78 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/repositories/user_infos_repository.dart +29 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/animations/page_transitions.dart +30 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_att_setup.dart +66 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_features.dart +72 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_loader.dart +92 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +73 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_questions.dart +89 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/onboarding_page.dart +94 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_background.dart +80 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_feature.dart +139 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +110 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_progress.dart +84 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +173 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_reassurance.dart +45 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +77 -0
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +392 -0
- package/lib/scaffold/features/revenuecat/lib/core/data/api/tracking_api.dart +116 -0
- package/lib/scaffold/features/revenuecat/lib/core/data/models/subscription.dart +322 -0
- package/lib/scaffold/features/revenuecat/lib/core/home_widgets/home_widget_background_task.dart +41 -0
- package/lib/scaffold/features/revenuecat/lib/core/states/user_state_notifier.dart +305 -0
- package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_background_task.dart +41 -0
- package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_mywidget_service.dart +57 -0
- package/lib/scaffold/features/widget/lib/core/home_widgets/home_widget_service.dart +54 -0
- package/lib/scaffold/features/widget/lib/features/settings/ui/components/admin/admin_home_widgets.dart +32 -0
- package/lib/scaffold/generate.js +370 -0
- package/lib/scaffold/patch.js +395 -0
- package/lib/scaffold/shared/backend-config.js +74 -0
- package/lib/scaffold/shared/fcm-service-account.js +126 -0
- package/lib/scaffold/shared/generator-utils.js +1664 -0
- package/lib/scaffold/shared/post-build.js +809 -0
- package/lib/scaffold/shared/template-strings.js +112 -0
- package/lib/utils/apple-release.js +273 -0
- package/lib/utils/checks.js +319 -0
- package/lib/utils/codemagic-release.js +127 -0
- package/lib/utils/fs.js +122 -0
- package/lib/utils/i18n.js +2123 -0
- package/lib/utils/license-gate.js +72 -0
- package/lib/utils/license.js +64 -0
- package/lib/utils/prompts.js +213 -0
- package/lib/utils/updates.js +97 -0
- package/package.json +55 -0
- package/templates/firebase/.env.example +24 -0
- package/templates/firebase/.firebaserc +5 -0
- package/templates/firebase/.kasy/apple.env.example +8 -0
- package/templates/firebase/.kasy/codemagic.env.example +7 -0
- package/templates/firebase/.metadata +30 -0
- package/templates/firebase/.windsurfrules +95 -0
- package/templates/firebase/Makefile +30 -0
- package/templates/firebase/README.en.md +180 -0
- package/templates/firebase/README.es.md +180 -0
- package/templates/firebase/README.md +180 -0
- package/templates/firebase/analysis_options.yaml +37 -0
- package/templates/firebase/android/app/build.gradle.kts +80 -0
- package/templates/firebase/android/app/src/debug/AndroidManifest.xml +7 -0
- package/templates/firebase/android/app/src/main/AndroidManifest.xml +93 -0
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +36 -0
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +50 -0
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidgetReceiver.kt +7 -0
- package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable/launch_background.xml +9 -0
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-v21/launch_background.xml +9 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/values/strings.xml +6 -0
- package/templates/firebase/android/app/src/main/res/values/styles.xml +22 -0
- package/templates/firebase/android/app/src/main/res/values-night/styles.xml +22 -0
- package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +20 -0
- package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +20 -0
- package/templates/firebase/android/app/src/main/res/xml/locales_config.xml +7 -0
- package/templates/firebase/android/app/src/main/res/xml/mywidget_info.xml +11 -0
- package/templates/firebase/android/app/src/profile/AndroidManifest.xml +7 -0
- package/templates/firebase/android/build.gradle.kts +28 -0
- package/templates/firebase/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/templates/firebase/android/gradle.properties +2 -0
- package/templates/firebase/android/settings.gradle.kts +29 -0
- package/templates/firebase/assets/icons/apple.png +0 -0
- package/templates/firebase/assets/icons/facebook.png +0 -0
- package/templates/firebase/assets/icons/google.png +0 -0
- package/templates/firebase/assets/icons/google_play_games.png +0 -0
- package/templates/firebase/assets/images/app_icon.png +0 -0
- package/templates/firebase/assets/images/empty_notifications.jpg +0 -0
- package/templates/firebase/assets/images/icon.png +0 -0
- package/templates/firebase/assets/images/onboarding/authentication-login-template.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/img1.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/img2.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/img3.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/notifications.png +0 -0
- package/templates/firebase/assets/images/onboarding/onboarding.png +0 -0
- package/templates/firebase/assets/images/onboarding/purchase.png +0 -0
- package/templates/firebase/assets/images/premium-bg.jpg +0 -0
- package/templates/firebase/assets/images/premium-switch-header.png +0 -0
- package/templates/firebase/assets/images/review.png +0 -0
- package/templates/firebase/assets/images/splashscreen.png +0 -0
- package/templates/firebase/assets/images/update.png +0 -0
- package/templates/firebase/build.yaml +13 -0
- package/templates/firebase/devtools_options.yaml +3 -0
- package/templates/firebase/docs/auth-setup.en.md +145 -0
- package/templates/firebase/docs/auth-setup.es.md +145 -0
- package/templates/firebase/docs/auth-setup.pt.md +145 -0
- package/templates/firebase/docs/codemagic-release.en.md +50 -0
- package/templates/firebase/docs/codemagic-release.es.md +50 -0
- package/templates/firebase/docs/codemagic-release.pt.md +50 -0
- package/templates/firebase/docs/ios-release.en.md +41 -0
- package/templates/firebase/docs/ios-release.es.md +41 -0
- package/templates/firebase/docs/ios-release.pt.md +54 -0
- package/templates/firebase/docs/navigation-transitions.md +80 -0
- package/templates/firebase/docs/revenuecat-setup.es.md +341 -0
- package/templates/firebase/docs/revenuecat-setup.pt.md +341 -0
- package/templates/firebase/firebase.json +1 -0
- package/templates/firebase/firestore.indexes.json +25 -0
- package/templates/firebase/firestore.rules +51 -0
- package/templates/firebase/functions/.eslintrc.js +45 -0
- package/templates/firebase/functions/jest.config.js +16 -0
- package/templates/firebase/functions/package-lock.json +6876 -0
- package/templates/firebase/functions/package.json +33 -0
- package/templates/firebase/functions/src/authentication/functions.ts +30 -0
- package/templates/firebase/functions/src/authentication/triggers.ts +103 -0
- package/templates/firebase/functions/src/core/api/meta_ads_api.ts +390 -0
- package/templates/firebase/functions/src/core/data/entities/entity_utils.ts +87 -0
- package/templates/firebase/functions/src/core/data/entities/notification_entity.ts +74 -0
- package/templates/firebase/functions/src/core/data/entities/page.ts +4 -0
- package/templates/firebase/functions/src/core/data/entities/subscription_entity.ts +76 -0
- package/templates/firebase/functions/src/core/data/entities/user_device_entity.ts +64 -0
- package/templates/firebase/functions/src/core/data/entities/user_entity.ts +43 -0
- package/templates/firebase/functions/src/core/data/repositories/repositories.ts +16 -0
- package/templates/firebase/functions/src/core/data/repositories/subscription_repository.ts +42 -0
- package/templates/firebase/functions/src/core/data/repositories/user_device_repository.ts +31 -0
- package/templates/firebase/functions/src/core/data/repositories/user_notifications_repository.ts +58 -0
- package/templates/firebase/functions/src/core/data/repositories/user_repository.ts +84 -0
- package/templates/firebase/functions/src/core/logger/logger.ts +13 -0
- package/templates/firebase/functions/src/core/storage/storage_manager.ts +52 -0
- package/templates/firebase/functions/src/core/utils/async_utils.ts +5 -0
- package/templates/firebase/functions/src/feature_requests/triggers.ts +3 -0
- package/templates/firebase/functions/src/index.ts +24 -0
- package/templates/firebase/functions/src/llm_chat/index.ts +181 -0
- package/templates/firebase/functions/src/notifications/admin_functions.ts +91 -0
- package/templates/firebase/functions/src/notifications/models/notification.ts +61 -0
- package/templates/firebase/functions/src/notifications/notifications_api.ts +147 -0
- package/templates/firebase/functions/src/notifications/triggers.ts +102 -0
- package/templates/firebase/functions/src/subscriptions/models/revenuecat_events.ts +162 -0
- package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +18 -0
- package/templates/firebase/functions/src/subscriptions/models/subscriptions.ts +173 -0
- package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +135 -0
- package/templates/firebase/functions/src/subscriptions/triggers.ts +35 -0
- package/templates/firebase/functions/src/test/core/data/entities/entity_utils.spec.ts +44 -0
- package/templates/firebase/functions/tsconfig.dev.json +5 -0
- package/templates/firebase/functions/tsconfig.json +18 -0
- package/templates/firebase/home_widget_config.json +30 -0
- package/templates/firebase/ios/Flutter/AppFrameworkInfo.plist +24 -0
- package/templates/firebase/ios/Flutter/Debug.xcconfig +2 -0
- package/templates/firebase/ios/Flutter/Profile.xcconfig +2 -0
- package/templates/firebase/ios/Flutter/Release.xcconfig +2 -0
- package/templates/firebase/ios/HomeWidgetExtension/HomeWidgetBundle.swift +13 -0
- package/templates/firebase/ios/HomeWidgetExtension/Info.plist +29 -0
- package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +86 -0
- package/templates/firebase/ios/HomeWidgetExtension.entitlements +10 -0
- package/templates/firebase/ios/NotificationService/Info.plist +31 -0
- package/templates/firebase/ios/NotificationService/NotificationService.swift +76 -0
- package/templates/firebase/ios/Podfile +111 -0
- package/templates/firebase/ios/Runner/AppDelegate.swift +75 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +1 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json +21 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +23 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +5 -0
- package/templates/firebase/ios/Runner/Base.lproj/LaunchScreen.storyboard +44 -0
- package/templates/firebase/ios/Runner/Base.lproj/Main.storyboard +26 -0
- package/templates/firebase/ios/Runner/Info.plist +132 -0
- package/templates/firebase/ios/Runner/Runner-Bridging-Header.h +1 -0
- package/templates/firebase/ios/Runner/Runner.entitlements +14 -0
- package/templates/firebase/ios/Runner/es.lproj/InfoPlist.strings +10 -0
- package/templates/firebase/ios/Runner/pt-BR.lproj/InfoPlist.strings +10 -0
- package/templates/firebase/ios/Runner/pt.lproj/InfoPlist.strings +10 -0
- package/templates/firebase/ios/Runner.xcodeproj/project.pbxproj +1108 -0
- package/templates/firebase/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/templates/firebase/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/templates/firebase/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- package/templates/firebase/ios/Runner.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme +58 -0
- package/templates/firebase/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +101 -0
- package/templates/firebase/ios/Runner.xcworkspace/contents.xcworkspacedata +10 -0
- package/templates/firebase/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/templates/firebase/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- package/templates/firebase/ios/RunnerTests/RunnerTests.swift +12 -0
- package/templates/firebase/lib/components/components.dart +27 -0
- package/templates/firebase/lib/components/kasy_accordion.dart +384 -0
- package/templates/firebase/lib/components/kasy_alert.dart +266 -0
- package/templates/firebase/lib/components/kasy_app_bar.dart +536 -0
- package/templates/firebase/lib/components/kasy_avatar.dart +549 -0
- package/templates/firebase/lib/components/kasy_avatar_presets.dart +104 -0
- package/templates/firebase/lib/components/kasy_badge.dart +278 -0
- package/templates/firebase/lib/components/kasy_bottom_sheet.dart +338 -0
- package/templates/firebase/lib/components/kasy_button.dart +926 -0
- package/templates/firebase/lib/components/kasy_card.dart +128 -0
- package/templates/firebase/lib/components/kasy_checkbox.dart +429 -0
- package/templates/firebase/lib/components/kasy_chip.dart +83 -0
- package/templates/firebase/lib/components/kasy_dialog.dart +789 -0
- package/templates/firebase/lib/components/kasy_otp_verification_bottom_sheet.dart +233 -0
- package/templates/firebase/lib/components/kasy_skeleton.dart +225 -0
- package/templates/firebase/lib/components/kasy_text_area.dart +392 -0
- package/templates/firebase/lib/components/kasy_text_field.dart +591 -0
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +532 -0
- package/templates/firebase/lib/components/kasy_toast.dart +803 -0
- package/templates/firebase/lib/core/ads/ads_provider.dart +136 -0
- package/templates/firebase/lib/core/ads/ads_state.dart +15 -0
- package/templates/firebase/lib/core/animations/bottomfade_anim.dart +37 -0
- package/templates/firebase/lib/core/animations/movefade_anim.dart +34 -0
- package/templates/firebase/lib/core/animations/slideright_anim.dart +38 -0
- package/templates/firebase/lib/core/bottom_menu/bart_inner_paths.dart +5 -0
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +127 -0
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +281 -0
- package/templates/firebase/lib/core/bottom_menu/kasy_bart_navigation.dart +22 -0
- package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +127 -0
- package/templates/firebase/lib/core/bottom_menu/notification_bottom_item.dart +48 -0
- package/templates/firebase/lib/core/config/app_env.dart +100 -0
- package/templates/firebase/lib/core/config/features.dart +15 -0
- package/templates/firebase/lib/core/data/api/analytics_api.dart +124 -0
- package/templates/firebase/lib/core/data/api/base_api_exceptions.dart +28 -0
- package/templates/firebase/lib/core/data/api/firestore_retry.dart +29 -0
- package/templates/firebase/lib/core/data/api/http_client.dart +38 -0
- package/templates/firebase/lib/core/data/api/image.dart +52 -0
- package/templates/firebase/lib/core/data/api/remote_config_api.dart +170 -0
- package/templates/firebase/lib/core/data/api/storage_api.dart +115 -0
- package/templates/firebase/lib/core/data/api/tracking_api.dart +119 -0
- package/templates/firebase/lib/core/data/api/user_api.dart +115 -0
- package/templates/firebase/lib/core/data/entities/json_converters.dart +39 -0
- package/templates/firebase/lib/core/data/entities/upload_result.dart +48 -0
- package/templates/firebase/lib/core/data/entities/user_entity.dart +54 -0
- package/templates/firebase/lib/core/data/models/pageable.dart +12 -0
- package/templates/firebase/lib/core/data/models/subscription.dart +322 -0
- package/templates/firebase/lib/core/data/models/user.dart +107 -0
- package/templates/firebase/lib/core/data/repositories/user_repository.dart +130 -0
- package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +464 -0
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_highlight.dart +88 -0
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_info.dart +70 -0
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_panel.dart +204 -0
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +321 -0
- package/templates/firebase/lib/core/extensions/date.ext.dart +32 -0
- package/templates/firebase/lib/core/guards/authenticated_guard.dart +37 -0
- package/templates/firebase/lib/core/guards/guard.dart +68 -0
- package/templates/firebase/lib/core/guards/user_info_guard.dart +55 -0
- package/templates/firebase/lib/core/haptics/haptic_feedback_notifier.dart +19 -0
- package/templates/firebase/lib/core/haptics/kasy_haptics.dart +27 -0
- package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +41 -0
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +57 -0
- package/templates/firebase/lib/core/home_widgets/home_widget_service.dart +54 -0
- package/templates/firebase/lib/core/icons/kasy_icons.dart +86 -0
- package/templates/firebase/lib/core/initializer/models/run_state.dart +14 -0
- package/templates/firebase/lib/core/initializer/onstart_service.dart +38 -0
- package/templates/firebase/lib/core/initializer/onstart_widget.dart +83 -0
- package/templates/firebase/lib/core/initializer/pending_notification_handler.dart +48 -0
- package/templates/firebase/lib/core/keyboard_fix/keyboard_flicker_fix.dart +164 -0
- package/templates/firebase/lib/core/navigation/kasy_fade_page_transitions_builder.dart +34 -0
- package/templates/firebase/lib/core/navigation/kasy_material_page_route.dart +41 -0
- package/templates/firebase/lib/core/navigation/kasy_navigation_config.dart +29 -0
- package/templates/firebase/lib/core/navigation/kasy_page_transition.dart +40 -0
- package/templates/firebase/lib/core/navigation/kasy_route_transition.dart +68 -0
- package/templates/firebase/lib/core/navigation/kasy_transition_kind.dart +17 -0
- package/templates/firebase/lib/core/navigation/navigation.dart +6 -0
- package/templates/firebase/lib/core/rating/api/rating_api.dart +99 -0
- package/templates/firebase/lib/core/rating/models/rating.dart +52 -0
- package/templates/firebase/lib/core/rating/models/review.dart +70 -0
- package/templates/firebase/lib/core/rating/providers/rating_repository.dart +75 -0
- package/templates/firebase/lib/core/rating/widgets/rate_banner.dart +190 -0
- package/templates/firebase/lib/core/rating/widgets/rate_popup.dart +54 -0
- package/templates/firebase/lib/core/rating/widgets/review_popup.dart +175 -0
- package/templates/firebase/lib/core/security/biometric_copy_slot.dart +20 -0
- package/templates/firebase/lib/core/security/biometric_guard.dart +114 -0
- package/templates/firebase/lib/core/security/biometric_preference_notifier.dart +17 -0
- package/templates/firebase/lib/core/security/biometric_service.dart +79 -0
- package/templates/firebase/lib/core/security/biometric_ui_bundle.dart +136 -0
- package/templates/firebase/lib/core/security/secured_storage.dart +46 -0
- package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +70 -0
- package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +2021 -0
- package/templates/firebase/lib/core/states/components/maybe_ask_biometric_setup.dart +88 -0
- package/templates/firebase/lib/core/states/components/maybe_ask_rating.dart +49 -0
- package/templates/firebase/lib/core/states/components/maybe_show_update_bottom_sheet.dart +86 -0
- package/templates/firebase/lib/core/states/components/maybeshow_component.dart +110 -0
- package/templates/firebase/lib/core/states/events_dispatcher.dart +103 -0
- package/templates/firebase/lib/core/states/models/event_model.dart +118 -0
- package/templates/firebase/lib/core/states/models/user_state.dart +22 -0
- package/templates/firebase/lib/core/states/notifications_dispatcher.dart +81 -0
- package/templates/firebase/lib/core/states/translations.dart +11 -0
- package/templates/firebase/lib/core/states/user_state_notifier.dart +321 -0
- package/templates/firebase/lib/core/theme/colors.dart +262 -0
- package/templates/firebase/lib/core/theme/extensions/theme_extension.dart +48 -0
- package/templates/firebase/lib/core/theme/providers/kasy_theme.dart +76 -0
- package/templates/firebase/lib/core/theme/providers/theme_provider.dart +212 -0
- package/templates/firebase/lib/core/theme/radius.dart +33 -0
- package/templates/firebase/lib/core/theme/shadows.dart +64 -0
- package/templates/firebase/lib/core/theme/spacing.dart +44 -0
- package/templates/firebase/lib/core/theme/texts.dart +176 -0
- package/templates/firebase/lib/core/theme/theme.dart +24 -0
- package/templates/firebase/lib/core/theme/theme_data/theme_data.dart +32 -0
- package/templates/firebase/lib/core/theme/theme_data/theme_data_factory.dart +15 -0
- package/templates/firebase/lib/core/theme/universal_theme.dart +210 -0
- package/templates/firebase/lib/core/toast/toast_service.dart +55 -0
- package/templates/firebase/lib/core/ui/app_dialog.dart +26 -0
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +1126 -0
- package/templates/firebase/lib/core/widgets/debouncer.dart +31 -0
- package/templates/firebase/lib/core/widgets/kasy_hover.dart +166 -0
- package/templates/firebase/lib/core/widgets/kasy_pressable.dart +2 -0
- package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +161 -0
- package/templates/firebase/lib/core/widgets/kasy_scroll_behavior.dart +17 -0
- package/templates/firebase/lib/core/widgets/keyboard_visibility.dart +75 -0
- package/templates/firebase/lib/core/widgets/page_background.dart +71 -0
- package/templates/firebase/lib/core/widgets/page_not_found.dart +15 -0
- package/templates/firebase/lib/core/widgets/responsive_layout.dart +215 -0
- package/templates/firebase/lib/core/widgets/update_bottom_sheet.dart +192 -0
- package/templates/firebase/lib/environnements.dart +160 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +528 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api_interface.dart +84 -0
- package/templates/firebase/lib/features/authentication/providers/models/email.dart +36 -0
- package/templates/firebase/lib/features/authentication/providers/models/password.dart +37 -0
- package/templates/firebase/lib/features/authentication/providers/models/phone_signin_state.dart +21 -0
- package/templates/firebase/lib/features/authentication/providers/models/recover_state.dart +21 -0
- package/templates/firebase/lib/features/authentication/providers/models/signin_state.dart +20 -0
- package/templates/firebase/lib/features/authentication/providers/models/signup_state.dart +20 -0
- package/templates/firebase/lib/features/authentication/providers/phone_auth_notifier.dart +132 -0
- package/templates/firebase/lib/features/authentication/providers/recover_provider.dart +51 -0
- package/templates/firebase/lib/features/authentication/providers/signin_state_provider.dart +166 -0
- package/templates/firebase/lib/features/authentication/providers/signup_state_provider.dart +67 -0
- package/templates/firebase/lib/features/authentication/repositories/authentication_repository.dart +399 -0
- package/templates/firebase/lib/features/authentication/repositories/exceptions/authentication_exceptions.dart +85 -0
- package/templates/firebase/lib/features/authentication/ui/components/apple_signin.dart +19 -0
- package/templates/firebase/lib/features/authentication/ui/components/facebook_signin.dart +19 -0
- package/templates/firebase/lib/features/authentication/ui/components/google_signin.dart +32 -0
- package/templates/firebase/lib/features/authentication/ui/components/otp_verification.dart +100 -0
- package/templates/firebase/lib/features/authentication/ui/components/phone_input.dart +109 -0
- package/templates/firebase/lib/features/authentication/ui/phone_auth_page.dart +60 -0
- package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +101 -0
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +321 -0
- package/templates/firebase/lib/features/authentication/ui/signup_page.dart +310 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +35 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_page_back_button.dart +149 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/otp_input.dart +194 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +62 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/round_signin.dart +73 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/social_separator.dart +26 -0
- package/templates/firebase/lib/features/dev/keyboard_test_page.dart +93 -0
- package/templates/firebase/lib/features/feedbacks/api/entities/feature_request_entity.dart +27 -0
- package/templates/firebase/lib/features/feedbacks/api/entities/feature_vote_entity.dart +27 -0
- package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +51 -0
- package/templates/firebase/lib/features/feedbacks/api/feature_vote_api.dart +79 -0
- package/templates/firebase/lib/features/feedbacks/models/feature_requests.dart +48 -0
- package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +43 -0
- package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +147 -0
- package/templates/firebase/lib/features/feedbacks/repositories/feature_request_repository.dart +92 -0
- package/templates/firebase/lib/features/feedbacks/ui/component/add_feature_form.dart +112 -0
- package/templates/firebase/lib/features/feedbacks/ui/feedback_page.dart +184 -0
- package/templates/firebase/lib/features/feedbacks/ui/widgets/add_feature_button.dart +66 -0
- package/templates/firebase/lib/features/feedbacks/ui/widgets/feature_card.dart +266 -0
- package/templates/firebase/lib/features/home/design_system_page.dart +552 -0
- package/templates/firebase/lib/features/home/home_components_page.dart +314 -0
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +791 -0
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +7712 -0
- package/templates/firebase/lib/features/home/home_features_page.dart +207 -0
- package/templates/firebase/lib/features/home/home_page.dart +348 -0
- package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +50 -0
- package/templates/firebase/lib/features/llm_chat/api/llm_chat_message_entity.dart +35 -0
- package/templates/firebase/lib/features/llm_chat/llm_chat_page.dart +352 -0
- package/templates/firebase/lib/features/llm_chat/providers/llm_chat_notifier.dart +315 -0
- package/templates/firebase/lib/features/llm_chat/ui/widgets/llm_chat_avatars.dart +148 -0
- package/templates/firebase/lib/features/llm_chat/ui/widgets/llm_chat_composer.dart +124 -0
- package/templates/firebase/lib/features/local_reminder/providers/reminder_notifier.dart +81 -0
- package/templates/firebase/lib/features/local_reminder/repositories/reminder_preferences.dart +76 -0
- package/templates/firebase/lib/features/local_reminder/ui/reminder_page.dart +288 -0
- package/templates/firebase/lib/features/notifications/api/device_api.dart +433 -0
- package/templates/firebase/lib/features/notifications/api/entities/device_entity.dart +62 -0
- package/templates/firebase/lib/features/notifications/api/entities/notifications_entity.dart +35 -0
- package/templates/firebase/lib/features/notifications/api/local_notifier.dart +409 -0
- package/templates/firebase/lib/features/notifications/api/notifications_api.dart +319 -0
- package/templates/firebase/lib/features/notifications/providers/models/device.dart +27 -0
- package/templates/firebase/lib/features/notifications/providers/models/notification.dart +196 -0
- package/templates/firebase/lib/features/notifications/providers/models/notification_list.dart +38 -0
- package/templates/firebase/lib/features/notifications/providers/notifications_provider.dart +120 -0
- package/templates/firebase/lib/features/notifications/repositories/background_notification_handler.dart +13 -0
- package/templates/firebase/lib/features/notifications/repositories/device_repository.dart +137 -0
- package/templates/firebase/lib/features/notifications/repositories/notifications_repository.dart +214 -0
- package/templates/firebase/lib/features/notifications/shared/att_permission.dart +75 -0
- package/templates/firebase/lib/features/notifications/shared/notification_permission_bottom_sheet.dart +144 -0
- package/templates/firebase/lib/features/notifications/ui/components/notification_settings_sheet.dart +169 -0
- package/templates/firebase/lib/features/notifications/ui/components/notification_tile.dart +46 -0
- package/templates/firebase/lib/features/notifications/ui/components/notifications_header.dart +32 -0
- package/templates/firebase/lib/features/notifications/ui/components/push_notification_switcher.dart +106 -0
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +202 -0
- package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +84 -0
- package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +73 -0
- package/templates/firebase/lib/features/notifications/ui/widgets/notification_tile.dart +362 -0
- package/templates/firebase/lib/features/onboarding/api/entities/user_info_entity.dart +24 -0
- package/templates/firebase/lib/features/onboarding/api/user_infos_api.dart +71 -0
- package/templates/firebase/lib/features/onboarding/models/user_info.dart +92 -0
- package/templates/firebase/lib/features/onboarding/providers/onboarding_model.dart +15 -0
- package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +78 -0
- package/templates/firebase/lib/features/onboarding/repositories/user_infos_repository.dart +29 -0
- package/templates/firebase/lib/features/onboarding/ui/animations/page_transitions.dart +33 -0
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +66 -0
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +76 -0
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +92 -0
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +76 -0
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +89 -0
- package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +108 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +80 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +145 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +125 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +84 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +173 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_reassurance.dart +44 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +77 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +392 -0
- package/templates/firebase/lib/features/settings/settings_page.dart +460 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +301 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_home_widgets.dart +38 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +53 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +22 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +366 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notifier.dart +40 -0
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +430 -0
- package/templates/firebase/lib/features/settings/ui/components/delete_user_component.dart +28 -0
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +163 -0
- package/templates/firebase/lib/features/settings/ui/widgets/admin_card.dart +59 -0
- package/templates/firebase/lib/features/settings/ui/widgets/avatar_utils.dart +51 -0
- package/templates/firebase/lib/features/settings/ui/widgets/round_progress.dart +151 -0
- package/templates/firebase/lib/features/settings/ui/widgets/settings_bottom_sheet_option_tile.dart +52 -0
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +163 -0
- package/templates/firebase/lib/features/subscription/api/entities/subscription_entity.dart +40 -0
- package/templates/firebase/lib/features/subscription/api/inapp_subscription_api.dart +389 -0
- package/templates/firebase/lib/features/subscription/api/subscription_api.dart +39 -0
- package/templates/firebase/lib/features/subscription/providers/models/premium_state.dart +35 -0
- package/templates/firebase/lib/features/subscription/providers/premium_page_provider.dart +259 -0
- package/templates/firebase/lib/features/subscription/repositories/subscription_repository.dart +177 -0
- package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +85 -0
- package/templates/firebase/lib/features/subscription/ui/component/active_premium_content.dart +171 -0
- package/templates/firebase/lib/features/subscription/ui/component/paywall_minimal.dart +146 -0
- package/templates/firebase/lib/features/subscription/ui/component/paywall_row.dart +332 -0
- package/templates/firebase/lib/features/subscription/ui/component/paywall_with_switch.dart +207 -0
- package/templates/firebase/lib/features/subscription/ui/component/premium_content.dart +157 -0
- package/templates/firebase/lib/features/subscription/ui/component/premium_page_factory.dart +161 -0
- package/templates/firebase/lib/features/subscription/ui/premium_page.dart +139 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/comparison_table.dart +346 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/feature_line.dart +131 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/paywall_empty_state.dart +68 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/premium_background.dart +63 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/premium_background_gradient.dart +53 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/premium_banner.dart +80 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/premium_bottom_menu.dart +102 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/premium_card.dart +26 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/premium_close_button.dart +61 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/premium_feature.dart +39 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/selectable_col.dart +377 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/selectable_row.dart +436 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/trial_switcher.dart +85 -0
- package/templates/firebase/lib/features/subscription/ui/widgets/unsubscribe_feedback_popup.dart +123 -0
- package/templates/firebase/lib/firebase_options.dart +75 -0
- package/templates/firebase/lib/google_auth_options.dart +10 -0
- package/templates/firebase/lib/i18n/app_locale_display.dart +17 -0
- package/templates/firebase/lib/i18n/en.i18n.json +514 -0
- package/templates/firebase/lib/i18n/es.i18n.json +514 -0
- package/templates/firebase/lib/i18n/pt.i18n.json +514 -0
- package/templates/firebase/lib/i18n/widgets/locale_code_badge.dart +47 -0
- package/templates/firebase/lib/main.dart +263 -0
- package/templates/firebase/lib/router.dart +226 -0
- package/templates/firebase/login-redesign-preview.png +0 -0
- package/templates/firebase/pubspec.yaml +177 -0
- package/templates/firebase/scripts/bump-ios-version.js +38 -0
- package/templates/firebase/scripts/release-ios.sh +137 -0
- package/templates/firebase/slang.yaml +7 -0
- package/templates/firebase/storage.rules +12 -0
- package/templates/firebase/test/core/data/api/analytics_api_fake.dart +25 -0
- package/templates/firebase/test/core/data/api/fake_remote_config_api.dart +45 -0
- package/templates/firebase/test/core/data/api/storage_api_fake.dart +28 -0
- package/templates/firebase/test/core/data/models/subscription_test.dart +42 -0
- package/templates/firebase/test/core/data/repositories/user_repository_test.dart +56 -0
- package/templates/firebase/test/core/guards/guard_test.dart +78 -0
- package/templates/firebase/test/core/navigation/kasy_page_transition_test.dart +30 -0
- package/templates/firebase/test/core/onstart_service_test.dart +18 -0
- package/templates/firebase/test/core/rating/rating_api_fake.dart +58 -0
- package/templates/firebase/test/core/rating/rating_banner_test.dart +117 -0
- package/templates/firebase/test/core/rating/rating_test.dart +60 -0
- package/templates/firebase/test/core/security/secured_storage_fake.dart +33 -0
- package/templates/firebase/test/core/states/event_dispatcher_test.dart +269 -0
- package/templates/firebase/test/core/states/user_state_notifier_test.dart +244 -0
- package/templates/firebase/test/core/widgets/responsive_layout_test.dart +50 -0
- package/templates/firebase/test/device_test_utils.dart +98 -0
- package/templates/firebase/test/features/authentication/data/api/auth_api_fake.dart +169 -0
- package/templates/firebase/test/features/authentication/data/api/user_api_fake.dart +86 -0
- package/templates/firebase/test/features/authentication/lost_password_page_test.dart +59 -0
- package/templates/firebase/test/features/authentication/repositories/authentication_repository_test.dart +88 -0
- package/templates/firebase/test/features/authentication/signin_page_test.dart +133 -0
- package/templates/firebase/test/features/authentication/signup_page_test.dart +136 -0
- package/templates/firebase/test/features/notifications/data/device_api_fake.dart +66 -0
- package/templates/firebase/test/features/notifications/data/fake_facebook_api.dart +40 -0
- package/templates/firebase/test/features/notifications/data/local_notifier_fake.dart +59 -0
- package/templates/firebase/test/features/notifications/data/notifications_api_fake.dart +120 -0
- package/templates/firebase/test/features/notifications/data/notifications_settings_fake.dart +34 -0
- package/templates/firebase/test/features/notifications/providers/device_repository_test.dart +68 -0
- package/templates/firebase/test/features/notifications/providers/notifications_repository_test.dart +74 -0
- package/templates/firebase/test/features/notifications/ui/notifications_page_test.dart +69 -0
- package/templates/firebase/test/features/subscription/api/fake_inapp_subscription_api.dart +85 -0
- package/templates/firebase/test/features/subscription/api/fake_revenuecat_product.dart +186 -0
- package/templates/firebase/test/features/subscription/api/fake_subscription_api.dart +11 -0
- package/templates/firebase/test/features/subscription/subscription_page_test.dart +119 -0
- package/templates/firebase/test/firebase_test_utils.dart +25 -0
- package/templates/firebase/test/test_utils.dart +272 -0
- package/templates/firebase/web/favicon.png +0 -0
- package/templates/firebase/web/firebase-messaging-sw.js +27 -0
- package/templates/firebase/web/icons/Icon-192.png +0 -0
- package/templates/firebase/web/icons/Icon-512.png +0 -0
- package/templates/firebase/web/icons/Icon-maskable-192.png +0 -0
- package/templates/firebase/web/icons/Icon-maskable-512.png +0 -0
- package/templates/firebase/web/index.html +110 -0
- package/templates/firebase/web/local_notifications.js +31 -0
- package/templates/firebase/web/manifest.json +35 -0
- package/templates/firebase/web/splash/img/dark-1x.png +0 -0
- package/templates/firebase/web/splash/img/dark-2x.png +0 -0
- package/templates/firebase/web/splash/img/dark-3x.png +0 -0
- package/templates/firebase/web/splash/img/dark-4x.png +0 -0
- package/templates/firebase/web/splash/img/light-1x.png +0 -0
- package/templates/firebase/web/splash/img/light-2x.png +0 -0
- package/templates/firebase/web/splash/img/light-3x.png +0 -0
- package/templates/firebase/web/splash/img/light-4x.png +0 -0
|
@@ -0,0 +1,1664 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for all backend generators.
|
|
3
|
+
* Single source of truth for dart-defines, launch.json, .env.example, environnements.
|
|
4
|
+
*
|
|
5
|
+
* @module scaffold/shared/generator-utils
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const fs = require('fs-extra');
|
|
10
|
+
const { getStrings } = require('./template-strings');
|
|
11
|
+
const pkg = require('../../../package.json');
|
|
12
|
+
|
|
13
|
+
/** Backend IDs: firebase | supabase | api */
|
|
14
|
+
const BACKENDS = Object.freeze(['firebase', 'supabase', 'api']);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build dart-define args for dev and prod environments.
|
|
18
|
+
* Backend-specific vars: Firebase (none extra), Supabase (BACKEND_URL, SUPABASE_TOKEN), API (BACKEND_URL).
|
|
19
|
+
*
|
|
20
|
+
* @param {'firebase'|'supabase'|'api'} backend
|
|
21
|
+
* @param {string[]} modules - e.g. ['revenuecat','sentry','analytics']
|
|
22
|
+
* @param {object} answers - moduleAnswers + backend-specific (supabaseUrl, supabaseAnonKey, apiBaseUrl)
|
|
23
|
+
* @returns {{ dev: string[], prod: string[] }}
|
|
24
|
+
*/
|
|
25
|
+
function buildDartDefines(backend, modules, answers) {
|
|
26
|
+
const dev = ['--dart-define=ENV=dev'];
|
|
27
|
+
const prod = ['--dart-define=ENV=prod'];
|
|
28
|
+
|
|
29
|
+
if (backend === 'supabase') {
|
|
30
|
+
const url = (answers.supabaseUrl || '').replace(/"/g, '\\"');
|
|
31
|
+
const token = (answers.supabaseAnonKey || '').replace(/"/g, '\\"');
|
|
32
|
+
dev.push(`--dart-define=BACKEND_URL=${url}`);
|
|
33
|
+
dev.push(`--dart-define=SUPABASE_TOKEN=${token}`);
|
|
34
|
+
prod.push(`--dart-define=BACKEND_URL=${url}`);
|
|
35
|
+
prod.push(`--dart-define=SUPABASE_TOKEN=${token}`);
|
|
36
|
+
} else if (backend === 'api') {
|
|
37
|
+
const url = (answers.apiBaseUrl || 'https://YOUR_API_URL.com').replace(/"/g, '\\"');
|
|
38
|
+
dev.push(`--dart-define=BACKEND_URL=${url}`);
|
|
39
|
+
prod.push(`--dart-define=BACKEND_URL=${url}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (modules.includes('revenuecat')) {
|
|
43
|
+
const androidKey = answers.rcAndroidKey || 'YOUR_REVENUECAT_ANDROID_KEY';
|
|
44
|
+
const iosKey = answers.rcIosKey || 'YOUR_REVENUECAT_IOS_KEY';
|
|
45
|
+
dev.push(`--dart-define=RC_ANDROID_API_KEY=${androidKey}`);
|
|
46
|
+
dev.push(`--dart-define=RC_IOS_API_KEY=${iosKey}`);
|
|
47
|
+
prod.push(`--dart-define=RC_ANDROID_API_KEY=${androidKey}`);
|
|
48
|
+
prod.push(`--dart-define=RC_IOS_API_KEY=${iosKey}`);
|
|
49
|
+
if (answers.revenuecatWeb) {
|
|
50
|
+
const webKey = answers.rcWebKey || 'YOUR_REVENUECAT_WEB_KEY';
|
|
51
|
+
dev.push(`--dart-define=RC_WEB_API_KEY=${webKey}`);
|
|
52
|
+
prod.push(`--dart-define=RC_WEB_API_KEY=${webKey}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (modules.includes('sentry')) {
|
|
56
|
+
const dsn = answers.sentryDsn || 'YOUR_SENTRY_DSN';
|
|
57
|
+
prod.push(`--dart-define=SENTRY_DSN=${dsn}`);
|
|
58
|
+
}
|
|
59
|
+
if (modules.includes('analytics')) {
|
|
60
|
+
const token = answers.mixpanelToken || 'YOUR_MIXPANEL_TOKEN';
|
|
61
|
+
dev.push(`--dart-define=MIXPANEL_TOKEN=${token}`);
|
|
62
|
+
prod.push(`--dart-define=MIXPANEL_TOKEN=${token}`);
|
|
63
|
+
}
|
|
64
|
+
if (modules.includes('llm_chat')) {
|
|
65
|
+
let endpoint = answers.llmChatEndpoint || '';
|
|
66
|
+
if (!endpoint) {
|
|
67
|
+
if (backend === 'supabase' && answers.supabaseUrl) {
|
|
68
|
+
endpoint = `${answers.supabaseUrl}/functions/v1/llm-chat`;
|
|
69
|
+
} else if (backend === 'api') {
|
|
70
|
+
endpoint = answers.apiBaseUrl ? `${answers.apiBaseUrl}/llm-chat` : 'YOUR_LLM_ENDPOINT';
|
|
71
|
+
} else {
|
|
72
|
+
// firebase — use the region configured for this project
|
|
73
|
+
const projectId = answers.firebaseProjectId || 'YOUR_PROJECT_ID';
|
|
74
|
+
const region = answers.functionsRegion || 'us-central1';
|
|
75
|
+
endpoint = `https://${region}-${projectId}.cloudfunctions.net/llmChat`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
dev.push(`--dart-define=LLM_CHAT_ENDPOINT=${endpoint}`);
|
|
79
|
+
prod.push(`--dart-define=LLM_CHAT_ENDPOINT=${endpoint}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { dev, prod };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Write .vscode/launch.json with pre-configured dart-define entries.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} projectDir - Project root
|
|
89
|
+
* @param {string} appName - Display name
|
|
90
|
+
* @param {'firebase'|'supabase'|'api'} backend
|
|
91
|
+
* @param {string[]} modules
|
|
92
|
+
* @param {object} answers
|
|
93
|
+
* @param {string} [language='en'] - User's CLI language (en, pt, es)
|
|
94
|
+
*/
|
|
95
|
+
async function writeVsCodeLaunch(projectDir, appName, backend, modules, answers, language = 'en') {
|
|
96
|
+
const { dev, prod } = buildDartDefines(backend, modules, answers);
|
|
97
|
+
const l = getStrings(language).launch;
|
|
98
|
+
const launchJson = {
|
|
99
|
+
version: '0.2.0',
|
|
100
|
+
configurations: [
|
|
101
|
+
{ name: `${appName} (${l.dev})`, request: 'launch', type: 'dart', args: dev },
|
|
102
|
+
{ name: `${appName} (${l.prod})`, request: 'launch', type: 'dart', args: prod },
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
await fs.ensureDir(path.join(projectDir, '.vscode'));
|
|
106
|
+
await fs.outputFile(
|
|
107
|
+
path.join(projectDir, '.vscode', 'launch.json'),
|
|
108
|
+
JSON.stringify(launchJson, null, 2) + '\n',
|
|
109
|
+
'utf8'
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Write .env.example as reference for developers.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} projectDir
|
|
117
|
+
* @param {string[]} modules
|
|
118
|
+
* @param {object} answers
|
|
119
|
+
* @param {string} [language='en'] - User's CLI language (en, pt, es)
|
|
120
|
+
*/
|
|
121
|
+
async function writeEnvExample(projectDir, modules, answers, language = 'en') {
|
|
122
|
+
const t = getStrings(language).envExample;
|
|
123
|
+
const lines = [
|
|
124
|
+
t.generated,
|
|
125
|
+
t.dartDefine,
|
|
126
|
+
t.envSource,
|
|
127
|
+
'',
|
|
128
|
+
t.envDev,
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
// Backend-specific variables
|
|
132
|
+
if (answers.supabaseUrl) {
|
|
133
|
+
lines.push('');
|
|
134
|
+
lines.push(t.supabase);
|
|
135
|
+
lines.push(`BACKEND_URL=${answers.supabaseUrl || 'https://YOUR_PROJECT.supabase.co'}`);
|
|
136
|
+
lines.push(`SUPABASE_TOKEN=${answers.supabaseAnonKey || 'YOUR_SUPABASE_ANON_KEY'}`);
|
|
137
|
+
} else if (answers.apiBaseUrl) {
|
|
138
|
+
lines.push('');
|
|
139
|
+
lines.push(t.apiRest);
|
|
140
|
+
lines.push(`BACKEND_URL=${answers.apiBaseUrl || 'https://YOUR_API_URL.com'}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Optional module variables
|
|
144
|
+
if (modules.includes('revenuecat')) {
|
|
145
|
+
lines.push('');
|
|
146
|
+
lines.push(t.revenuecat);
|
|
147
|
+
lines.push(`RC_ANDROID_API_KEY=${answers.rcAndroidKey || 'YOUR_REVENUECAT_ANDROID_KEY'}`);
|
|
148
|
+
lines.push(`RC_IOS_API_KEY=${answers.rcIosKey || 'YOUR_REVENUECAT_IOS_KEY'}`);
|
|
149
|
+
if (answers.revenuecatWeb) {
|
|
150
|
+
lines.push(`RC_WEB_API_KEY=${answers.rcWebKey || 'YOUR_REVENUECAT_WEB_KEY'}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (modules.includes('sentry')) {
|
|
154
|
+
lines.push('');
|
|
155
|
+
lines.push(t.sentry);
|
|
156
|
+
lines.push(`SENTRY_DSN=${answers.sentryDsn || 'YOUR_SENTRY_DSN'}`);
|
|
157
|
+
}
|
|
158
|
+
if (modules.includes('analytics')) {
|
|
159
|
+
lines.push('');
|
|
160
|
+
lines.push(t.mixpanel);
|
|
161
|
+
lines.push(`MIXPANEL_TOKEN=${answers.mixpanelToken || 'YOUR_MIXPANEL_TOKEN'}`);
|
|
162
|
+
}
|
|
163
|
+
if (modules.includes('llm_chat')) {
|
|
164
|
+
lines.push('');
|
|
165
|
+
lines.push(t.llmChat);
|
|
166
|
+
lines.push(`LLM_CHAT_ENDPOINT=${answers.llmChatEndpoint || 'YOUR_FUNCTION_URL'}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
await fs.outputFile(path.join(projectDir, '.env.example'), lines.join('\n') + '\n', 'utf8');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Create .env from .env.example on first scaffold.
|
|
174
|
+
*
|
|
175
|
+
* @param {string} projectDir
|
|
176
|
+
*/
|
|
177
|
+
async function writeEnvFileIfMissing(projectDir) {
|
|
178
|
+
const envPath = path.join(projectDir, '.env');
|
|
179
|
+
if (await fs.pathExists(envPath)) return;
|
|
180
|
+
const envExamplePath = path.join(projectDir, '.env.example');
|
|
181
|
+
if (!(await fs.pathExists(envExamplePath))) return;
|
|
182
|
+
const content = await fs.readFile(envExamplePath, 'utf8');
|
|
183
|
+
await fs.outputFile(envPath, content, 'utf8');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Write .firebaserc with project ID.
|
|
188
|
+
*
|
|
189
|
+
* @param {string} projectDir
|
|
190
|
+
* @param {string} firebaseProjectId
|
|
191
|
+
*/
|
|
192
|
+
async function writeFirebaserc(projectDir, firebaseProjectId) {
|
|
193
|
+
await fs.outputFile(
|
|
194
|
+
path.join(projectDir, '.firebaserc'),
|
|
195
|
+
JSON.stringify({ projects: { default: firebaseProjectId } }, null, 2) + '\n',
|
|
196
|
+
'utf8'
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Write environnements.dart overrides for backend-specific config.
|
|
202
|
+
*
|
|
203
|
+
* @param {string} projectDir
|
|
204
|
+
* @param {'firebase'|'supabase'|'api'} backend
|
|
205
|
+
* @param {Record<string,string>} tokens
|
|
206
|
+
* @param {object} answers - supabaseUrl, supabaseAnonKey, apiBaseUrl
|
|
207
|
+
*/
|
|
208
|
+
async function writeEnvironnementsOverrides(projectDir, backend, tokens, answers) {
|
|
209
|
+
const envPath = path.join(projectDir, 'lib', 'environnements.dart');
|
|
210
|
+
if (!(await fs.pathExists(envPath))) return;
|
|
211
|
+
|
|
212
|
+
let content = await fs.readFile(envPath, 'utf8');
|
|
213
|
+
|
|
214
|
+
if (backend === 'supabase') {
|
|
215
|
+
const url = (answers.supabaseUrl || 'https://YOUR_PROJECT.supabase.co').replace(/'/g, "\\'");
|
|
216
|
+
const token = (answers.supabaseAnonKey || 'YOUR_SUPABASE_ANON_KEY').replace(/'/g, "\\'");
|
|
217
|
+
content = content.replace(
|
|
218
|
+
/defaultValue:\s*'https:\/\/supabaseplaceholder\.supabase\.co'/g,
|
|
219
|
+
`defaultValue: '${url}'`
|
|
220
|
+
);
|
|
221
|
+
content = content.replace(
|
|
222
|
+
/defaultValue:\s*'sb_publishable_placeholder'/g,
|
|
223
|
+
`defaultValue: '${token}'`
|
|
224
|
+
);
|
|
225
|
+
} else if (backend === 'api') {
|
|
226
|
+
const url = (answers.apiBaseUrl || 'https://YOUR_API_URL.com').replace(/'/g, "\\'");
|
|
227
|
+
content = content.replace(
|
|
228
|
+
/(\/\/ please replace it with your own backend URL\s*\n\s*defaultValue: )''/,
|
|
229
|
+
`$1'${url}'`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const [from, to] of Object.entries(tokens)) {
|
|
234
|
+
content = content.replaceAll(from, to);
|
|
235
|
+
}
|
|
236
|
+
await fs.outputFile(envPath, content, 'utf8');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Generate lib/router.dart based on selected modules.
|
|
241
|
+
* Routes and imports are only included for selected modules,
|
|
242
|
+
* eliminating unused code and import errors at compile time.
|
|
243
|
+
*
|
|
244
|
+
* @param {string} projectDir
|
|
245
|
+
* @param {string[]} modules - Selected module IDs
|
|
246
|
+
* @param {string} packageName - Dart package name (e.g. my_app)
|
|
247
|
+
*/
|
|
248
|
+
async function writeRouter(projectDir, modules, packageName, defaultPaywall = 'basic') {
|
|
249
|
+
const pkg = packageName;
|
|
250
|
+
const withFeedback = modules.includes('feedback');
|
|
251
|
+
const withLlmChat = modules.includes('llm_chat');
|
|
252
|
+
const withLocalNotifications = modules.includes('local_notifications');
|
|
253
|
+
const withOnboarding = modules.includes('onboarding');
|
|
254
|
+
const withRevenuecat = modules.includes('revenuecat');
|
|
255
|
+
const withAnalytics = modules.includes('analytics');
|
|
256
|
+
|
|
257
|
+
const fallback = withOnboarding ? '/onboarding' : '/signin';
|
|
258
|
+
|
|
259
|
+
const lines = [];
|
|
260
|
+
|
|
261
|
+
// ── Imports ────────────────────────────────────────────────────────────────
|
|
262
|
+
lines.push(`import 'package:flutter/material.dart';`);
|
|
263
|
+
lines.push(`import 'package:flutter_riverpod/flutter_riverpod.dart';`);
|
|
264
|
+
lines.push(`import 'package:go_router/go_router.dart';`);
|
|
265
|
+
lines.push(`import 'package:${pkg}/core/bottom_menu/bottom_menu.dart';`);
|
|
266
|
+
if (withAnalytics) {
|
|
267
|
+
lines.push(`import 'package:${pkg}/core/data/api/analytics_api.dart';`);
|
|
268
|
+
}
|
|
269
|
+
lines.push(`import 'package:${pkg}/core/guards/user_info_guard.dart';`);
|
|
270
|
+
lines.push(`import 'package:${pkg}/core/widgets/page_not_found.dart';`);
|
|
271
|
+
lines.push(`import 'package:${pkg}/features/authentication/ui/phone_auth_page.dart';`);
|
|
272
|
+
lines.push(`import 'package:${pkg}/features/authentication/ui/recover_password_page.dart';`);
|
|
273
|
+
lines.push(`import 'package:${pkg}/features/authentication/ui/signin_page.dart';`);
|
|
274
|
+
lines.push(`import 'package:${pkg}/features/authentication/ui/signup_page.dart';`);
|
|
275
|
+
if (withFeedback) {
|
|
276
|
+
lines.push(`import 'package:${pkg}/features/feedbacks/ui/component/add_feature_form.dart';`);
|
|
277
|
+
lines.push(`import 'package:${pkg}/features/feedbacks/ui/feedback_page.dart';`);
|
|
278
|
+
}
|
|
279
|
+
if (withLlmChat) {
|
|
280
|
+
lines.push(`import 'package:${pkg}/features/llm_chat/llm_chat_page.dart';`);
|
|
281
|
+
}
|
|
282
|
+
if (withLocalNotifications) {
|
|
283
|
+
lines.push(`import 'package:${pkg}/features/local_reminder/ui/reminder_page.dart';`);
|
|
284
|
+
}
|
|
285
|
+
if (withOnboarding) {
|
|
286
|
+
lines.push(`import 'package:${pkg}/features/onboarding/ui/onboarding_page.dart';`);
|
|
287
|
+
}
|
|
288
|
+
if (withRevenuecat) {
|
|
289
|
+
lines.push(`import 'package:${pkg}/features/subscription/ui/component/premium_page_factory.dart';`);
|
|
290
|
+
lines.push(`import 'package:${pkg}/features/subscription/ui/premium_page.dart';`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ── Provider ──────────────────────────────────────────────────────────────
|
|
294
|
+
lines.push('');
|
|
295
|
+
lines.push(`final goRouterProvider = Provider<GoRouter>((ref) => generateRouter());`);
|
|
296
|
+
lines.push('');
|
|
297
|
+
lines.push(`extension GoRouterRiverpod on Ref {`);
|
|
298
|
+
lines.push(` GoRouter get goRouter => read(goRouterProvider);`);
|
|
299
|
+
lines.push(`}`);
|
|
300
|
+
lines.push('');
|
|
301
|
+
lines.push(`final navigatorKey = GlobalKey<NavigatorState>();`);
|
|
302
|
+
lines.push('');
|
|
303
|
+
|
|
304
|
+
// ── generateRouter ─────────────────────────────────────────────────────────
|
|
305
|
+
lines.push(`GoRouter generateRouter({`);
|
|
306
|
+
lines.push(` String? initialLocation,`);
|
|
307
|
+
lines.push(` List<GoRoute>? additionalRoutes,`);
|
|
308
|
+
lines.push(` List<NavigatorObserver>? observers,`);
|
|
309
|
+
lines.push(`}) {`);
|
|
310
|
+
lines.push(` return GoRouter(`);
|
|
311
|
+
lines.push(` initialLocation: '/',`);
|
|
312
|
+
lines.push(` navigatorKey: navigatorKey,`);
|
|
313
|
+
lines.push(` errorBuilder: (context, state) => const PageNotFound(),`);
|
|
314
|
+
lines.push(` observers: [`);
|
|
315
|
+
if (withAnalytics) {
|
|
316
|
+
lines.push(` AnalyticsObserver(analyticsApi: MixpanelAnalyticsApi.instance()),`);
|
|
317
|
+
}
|
|
318
|
+
lines.push(` ...?observers,`);
|
|
319
|
+
lines.push(` ],`);
|
|
320
|
+
lines.push(` routes: [`);
|
|
321
|
+
|
|
322
|
+
// Home
|
|
323
|
+
lines.push(` GoRoute(`);
|
|
324
|
+
lines.push(` name: 'home',`);
|
|
325
|
+
lines.push(` path: '/',`);
|
|
326
|
+
lines.push(` builder: (context, state) => const UserInfosGuard(`);
|
|
327
|
+
lines.push(` fallbackRoute: '${fallback}',`);
|
|
328
|
+
lines.push(` child: BottomMenu(),`);
|
|
329
|
+
lines.push(` ),`);
|
|
330
|
+
lines.push(` ),`);
|
|
331
|
+
|
|
332
|
+
// Onboarding
|
|
333
|
+
if (withOnboarding) {
|
|
334
|
+
lines.push(` GoRoute(`);
|
|
335
|
+
lines.push(` name: 'onboarding',`);
|
|
336
|
+
lines.push(` path: '/onboarding',`);
|
|
337
|
+
lines.push(` builder: (context, state) => const OnboardingPage(),`);
|
|
338
|
+
lines.push(` ),`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Auth (always)
|
|
342
|
+
lines.push(` GoRoute(`);
|
|
343
|
+
lines.push(` name: 'signup',`);
|
|
344
|
+
lines.push(` path: '/signup',`);
|
|
345
|
+
lines.push(` builder: (context, state) => const SignupPage(),`);
|
|
346
|
+
lines.push(` ),`);
|
|
347
|
+
lines.push(` GoRoute(`);
|
|
348
|
+
lines.push(` name: 'signin',`);
|
|
349
|
+
lines.push(` path: '/signin',`);
|
|
350
|
+
lines.push(` builder: (context, state) => const SigninPage(),`);
|
|
351
|
+
lines.push(` ),`);
|
|
352
|
+
lines.push(` GoRoute(`);
|
|
353
|
+
lines.push(` name: 'signinWithPhone',`);
|
|
354
|
+
lines.push(` path: '/signinWithPhone',`);
|
|
355
|
+
lines.push(` builder: (context, state) => const PhoneAuthPage(),`);
|
|
356
|
+
lines.push(` ),`);
|
|
357
|
+
|
|
358
|
+
// Premium (revenuecat)
|
|
359
|
+
if (withRevenuecat) {
|
|
360
|
+
const paywallFactory = ['basic', 'minimal', 'withSwitch', 'basicRow'].includes(defaultPaywall)
|
|
361
|
+
? `PaywallFactory.${defaultPaywall}`
|
|
362
|
+
: 'PaywallFactory.basic';
|
|
363
|
+
lines.push(` GoRoute(`);
|
|
364
|
+
lines.push(` name: 'premium',`);
|
|
365
|
+
lines.push(` path: '/premium',`);
|
|
366
|
+
lines.push(` builder: (context, state) => const PremiumPage(paywall: ${paywallFactory}),`);
|
|
367
|
+
lines.push(` ),`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Feedback
|
|
371
|
+
if (withFeedback) {
|
|
372
|
+
lines.push(` GoRoute(`);
|
|
373
|
+
lines.push(` name: 'feedback',`);
|
|
374
|
+
lines.push(` path: '/feedback',`);
|
|
375
|
+
lines.push(` builder: (context, state) => const FeedbackPage(),`);
|
|
376
|
+
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
|
+
}
|
|
383
|
+
|
|
384
|
+
// LLM Chat
|
|
385
|
+
if (withLlmChat) {
|
|
386
|
+
lines.push(` GoRoute(`);
|
|
387
|
+
lines.push(` name: 'assistant',`);
|
|
388
|
+
lines.push(` path: '/assistant',`);
|
|
389
|
+
lines.push(` builder: (context, state) => const LlmChatPage(),`);
|
|
390
|
+
lines.push(` ),`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Local notifications reminder
|
|
394
|
+
if (withLocalNotifications) {
|
|
395
|
+
lines.push(` GoRoute(`);
|
|
396
|
+
lines.push(` name: 'reminder',`);
|
|
397
|
+
lines.push(` path: '/reminder',`);
|
|
398
|
+
lines.push(` builder: (context, state) => const ReminderPage(),`);
|
|
399
|
+
lines.push(` ),`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Recover password + 404 (always)
|
|
403
|
+
lines.push(` GoRoute(`);
|
|
404
|
+
lines.push(` name: 'recover_password',`);
|
|
405
|
+
lines.push(` path: '/recover_password',`);
|
|
406
|
+
lines.push(` builder: (context, state) => const RecoverPasswordPage(),`);
|
|
407
|
+
lines.push(` ),`);
|
|
408
|
+
lines.push(` GoRoute(`);
|
|
409
|
+
lines.push(` name: '404',`);
|
|
410
|
+
lines.push(` path: '/404',`);
|
|
411
|
+
lines.push(` builder: (context, state) => const PageNotFound(),`);
|
|
412
|
+
lines.push(` ),`);
|
|
413
|
+
|
|
414
|
+
lines.push(` ],`);
|
|
415
|
+
lines.push(` );`);
|
|
416
|
+
lines.push(`}`);
|
|
417
|
+
|
|
418
|
+
await fs.ensureDir(path.join(projectDir, 'lib'));
|
|
419
|
+
await fs.outputFile(
|
|
420
|
+
path.join(projectDir, 'lib', 'router.dart'),
|
|
421
|
+
lines.join('\n') + '\n',
|
|
422
|
+
'utf8'
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Write lib/core/config/features.dart with feature flags from user choices.
|
|
428
|
+
*
|
|
429
|
+
* @param {string} projectDir
|
|
430
|
+
* @param {string[]} modules
|
|
431
|
+
* @param {object} [answers={}] - moduleAnswers (revenuecatWeb, etc.)
|
|
432
|
+
* @param {string} [language='en'] - User's CLI language (en, pt, es)
|
|
433
|
+
*/
|
|
434
|
+
async function writeFeaturesConfig(projectDir, modules, answers = {}, language = 'en') {
|
|
435
|
+
const withOnboarding = modules.includes('onboarding');
|
|
436
|
+
const withLlmChat = modules.includes('llm_chat');
|
|
437
|
+
const withFeedback = modules.includes('feedback');
|
|
438
|
+
const withRevenuecat = modules.includes('revenuecat');
|
|
439
|
+
const withLocalNotifications = modules.includes('local_notifications');
|
|
440
|
+
const withWeb = modules.includes('web');
|
|
441
|
+
const revenuecatWeb = !!(modules.includes('revenuecat') && modules.includes('web') && answers.revenuecatWeb);
|
|
442
|
+
|
|
443
|
+
const f = getStrings(language).features;
|
|
444
|
+
const content = `${f.comment1}
|
|
445
|
+
${f.comment2}
|
|
446
|
+
// ignore_for_file: avoid_redundant_argument_values
|
|
447
|
+
const bool withOnboarding = ${withOnboarding};
|
|
448
|
+
const bool withLlmChat = ${withLlmChat};
|
|
449
|
+
const bool withFeedback = ${withFeedback};
|
|
450
|
+
const bool withRevenuecat = ${withRevenuecat};
|
|
451
|
+
const bool withLocalNotifications = ${withLocalNotifications};
|
|
452
|
+
${f.comment3}
|
|
453
|
+
${f.comment4}
|
|
454
|
+
${f.comment5}
|
|
455
|
+
${f.comment6}
|
|
456
|
+
const bool withWeb = ${withWeb};
|
|
457
|
+
${f.comment7}
|
|
458
|
+
const bool revenuecatWeb = ${revenuecatWeb};
|
|
459
|
+
`;
|
|
460
|
+
await fs.ensureDir(path.join(projectDir, 'lib', 'core', 'config'));
|
|
461
|
+
await fs.outputFile(
|
|
462
|
+
path.join(projectDir, 'lib', 'core', 'config', 'features.dart'),
|
|
463
|
+
content,
|
|
464
|
+
'utf8'
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Write kit_setup.json reflecting user choices (backend, modules).
|
|
470
|
+
*
|
|
471
|
+
* @param {string} projectDir
|
|
472
|
+
* @param {object} options - appName, bundleId, backend, modules, firebaseProjectId, supabaseUrl, supabaseAnonKey
|
|
473
|
+
*/
|
|
474
|
+
async function writeKitSetup(projectDir, options) {
|
|
475
|
+
const { appName, bundleId, backend, modules = [], firebaseProjectId, supabaseUrl, supabaseAnonKey, moduleAnswers = {} } = options;
|
|
476
|
+
const revenuecatWeb = !!(modules.includes('revenuecat') && modules.includes('web') && moduleAnswers.revenuecatWeb);
|
|
477
|
+
const config = {
|
|
478
|
+
appName: appName || 'App',
|
|
479
|
+
bundleId: bundleId || 'com.example.app',
|
|
480
|
+
backendProvider: backend || 'firebase',
|
|
481
|
+
functionsRegion: moduleAnswers.functionsRegion || 'us-central1',
|
|
482
|
+
subscriptionModule: modules.includes('revenuecat'),
|
|
483
|
+
feedbackModule: modules.includes('feedback'),
|
|
484
|
+
useFirebaseAuth: true,
|
|
485
|
+
useFirebaseFirestore: backend === 'firebase',
|
|
486
|
+
storageProvider: backend === 'firebase' ? 'firebase' : backend === 'supabase' ? 'supabase' : 'api',
|
|
487
|
+
webCompat: modules.includes('web'),
|
|
488
|
+
revenuecatWeb,
|
|
489
|
+
internationalization: true,
|
|
490
|
+
useSentry: modules.includes('sentry'),
|
|
491
|
+
withOnboarding: modules.includes('onboarding'),
|
|
492
|
+
remoteConfigProvider: backend === 'firebase' ? 'firebase' : null,
|
|
493
|
+
analyticsProvider: modules.includes('analytics') ? 'mixpanel' : null,
|
|
494
|
+
anonymousSignup: true,
|
|
495
|
+
withFacebookPixel: modules.includes('facebook'),
|
|
496
|
+
socialAuthList: ['google', 'googlePlay', 'facebook', 'apple'],
|
|
497
|
+
firebaseProjectId: firebaseProjectId || null,
|
|
498
|
+
supabaseProjectId: supabaseUrl ? supabaseUrl.replace(/^https:\/\/([^.]+).*/, '$1') : null,
|
|
499
|
+
withDashboard: false,
|
|
500
|
+
supabasePublishableKey: supabaseAnonKey || null,
|
|
501
|
+
templateVersion: '1.0.0',
|
|
502
|
+
cliVersion: pkg.version,
|
|
503
|
+
};
|
|
504
|
+
await fs.outputFile(
|
|
505
|
+
path.join(projectDir, 'kit_setup.json'),
|
|
506
|
+
JSON.stringify(config, null, 2) + '\n',
|
|
507
|
+
'utf8'
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Write Makefile with run targets for iOS, Android, and default device.
|
|
513
|
+
* Inlines dart-define flags so `make run` works without VS Code.
|
|
514
|
+
*
|
|
515
|
+
* @param {string} projectDir - Project root
|
|
516
|
+
* @param {string} [language='en'] - User's CLI language (en, pt, es)
|
|
517
|
+
* @param {string} [backend] - backend id (firebase|supabase|api)
|
|
518
|
+
* @param {string[]} [modules] - selected modules
|
|
519
|
+
* @param {object} [answers] - user answers (credentials)
|
|
520
|
+
*/
|
|
521
|
+
async function writeMakefile(projectDir, language = 'en', backend, modules, answers) {
|
|
522
|
+
const m = getStrings(language).makefile;
|
|
523
|
+
|
|
524
|
+
let definesList = '';
|
|
525
|
+
if (backend && modules && answers) {
|
|
526
|
+
const { dev } = buildDartDefines(backend, modules, answers);
|
|
527
|
+
if (dev.length > 0) {
|
|
528
|
+
definesList = dev.map((d, i) =>
|
|
529
|
+
i === 0 ? ` ${d} \\` : ` ${d} \\`
|
|
530
|
+
).join('\n');
|
|
531
|
+
// Remove trailing backslash from last line
|
|
532
|
+
definesList = definesList.replace(/ \\$/, '');
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const definesBlock = definesList
|
|
537
|
+
? `DEFINES := \\\n${definesList}\n\n`
|
|
538
|
+
: '';
|
|
539
|
+
const runCmd = definesList ? 'flutter run $(DEFINES)' : 'flutter run';
|
|
540
|
+
const runWebCmd = definesList ? 'flutter run -d chrome $(DEFINES)' : 'flutter run -d chrome';
|
|
541
|
+
|
|
542
|
+
const content = `${m.comment1}
|
|
543
|
+
${m.comment2}
|
|
544
|
+
${m.comment3}
|
|
545
|
+
${m.comment4}
|
|
546
|
+
|
|
547
|
+
${definesBlock}.PHONY: run run-ios run-android run-web ${m.releaseIosTarget}
|
|
548
|
+
|
|
549
|
+
run:
|
|
550
|
+
\t${runCmd}
|
|
551
|
+
|
|
552
|
+
run-ios:
|
|
553
|
+
\t${runCmd}
|
|
554
|
+
|
|
555
|
+
run-android:
|
|
556
|
+
\t${runCmd}
|
|
557
|
+
|
|
558
|
+
run-web:
|
|
559
|
+
\t${runWebCmd}
|
|
560
|
+
|
|
561
|
+
${m.releaseIos}
|
|
562
|
+
${m.releaseIosTarget}:
|
|
563
|
+
\tbash scripts/release-ios.sh
|
|
564
|
+
`;
|
|
565
|
+
await fs.outputFile(path.join(projectDir, 'Makefile'), content, 'utf8');
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Write lib/core/data/api/analytics_api.dart as a no-op stub (no Mixpanel dependency).
|
|
570
|
+
* Called when the user does NOT select the analytics module.
|
|
571
|
+
*
|
|
572
|
+
* @param {string} projectDir
|
|
573
|
+
* @param {string} packageName
|
|
574
|
+
*/
|
|
575
|
+
async function writeNoOpAnalyticsApi(projectDir, packageName) {
|
|
576
|
+
const pkg = packageName;
|
|
577
|
+
const content = `import 'package:flutter/material.dart';
|
|
578
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
579
|
+
import 'package:${pkg}/core/data/models/user.dart';
|
|
580
|
+
import 'package:${pkg}/core/initializer/onstart_service.dart';
|
|
581
|
+
|
|
582
|
+
// No-op analytics. Run: kasy add analytics to activate Mixpanel.
|
|
583
|
+
final analyticsApiProvider = Provider<AnalyticsApi>((ref) => const NoOpAnalyticsApi());
|
|
584
|
+
|
|
585
|
+
abstract class AnalyticsApi implements OnStartService {
|
|
586
|
+
Future<void> logEvent(String name, Map<String, dynamic> params);
|
|
587
|
+
Future<void> logSignout();
|
|
588
|
+
Future<void> identify(User user);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
class NoOpAnalyticsApi implements AnalyticsApi {
|
|
592
|
+
const NoOpAnalyticsApi();
|
|
593
|
+
@override Future<void> init() async {}
|
|
594
|
+
@override Future<void> logEvent(String name, Map<String, dynamic> params) async {}
|
|
595
|
+
@override Future<void> logSignout() async {}
|
|
596
|
+
@override Future<void> identify(User user) async {}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
class AnalyticsObserver extends RouteObserver<ModalRoute<dynamic>> {
|
|
600
|
+
final AnalyticsApi analyticsApi;
|
|
601
|
+
final String? prefix;
|
|
602
|
+
AnalyticsObserver({required this.analyticsApi, this.prefix});
|
|
603
|
+
@override void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {}
|
|
604
|
+
@override void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {}
|
|
605
|
+
@override void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {}
|
|
606
|
+
@override void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) {}
|
|
607
|
+
@override void didStartUserGesture(Route<dynamic> route, Route<dynamic>? previousRoute) {}
|
|
608
|
+
@override void didStopUserGesture() {}
|
|
609
|
+
}
|
|
610
|
+
`;
|
|
611
|
+
await fs.ensureDir(path.join(projectDir, 'lib', 'core', 'data', 'api'));
|
|
612
|
+
await fs.outputFile(
|
|
613
|
+
path.join(projectDir, 'lib', 'core', 'data', 'api', 'analytics_api.dart'),
|
|
614
|
+
content,
|
|
615
|
+
'utf8'
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Write lib/core/data/api/tracking_api.dart as a no-op stub (no facebook/purchases/sentry deps).
|
|
621
|
+
* Called when the user does NOT select the facebook module.
|
|
622
|
+
*
|
|
623
|
+
* @param {string} projectDir
|
|
624
|
+
* @param {string} packageName
|
|
625
|
+
*/
|
|
626
|
+
async function writeNoOpTrackingApi(projectDir, packageName) {
|
|
627
|
+
const pkg = packageName;
|
|
628
|
+
const content = `import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
629
|
+
import 'package:${pkg}/core/initializer/onstart_service.dart';
|
|
630
|
+
import 'package:${pkg}/core/states/models/user_state.dart';
|
|
631
|
+
import 'package:${pkg}/core/states/user_state_notifier.dart';
|
|
632
|
+
import 'package:${pkg}/features/notifications/repositories/device_repository.dart';
|
|
633
|
+
|
|
634
|
+
// No-op tracking. Run: kasy add facebook to activate Facebook Events.
|
|
635
|
+
final facebookEventApiProvider = Provider(
|
|
636
|
+
(ref) => FacebookEventApi(
|
|
637
|
+
deviceRepository: ref.watch(deviceRepositoryProvider),
|
|
638
|
+
userState: ref.watch(userStateNotifierProvider),
|
|
639
|
+
),
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
class FacebookEventApi implements OnStartService {
|
|
643
|
+
final DeviceRepository deviceRepository;
|
|
644
|
+
final UserState userState;
|
|
645
|
+
FacebookEventApi({required this.deviceRepository, required this.userState});
|
|
646
|
+
@override Future<void> init() async {}
|
|
647
|
+
Future<void> initUser(String userId) async {}
|
|
648
|
+
Future<void> requestIDFA() async {}
|
|
649
|
+
Future<void> logMetaStartTrial(String orderId, double price, String currency) async {}
|
|
650
|
+
Future<void> logMetaSubscribe(String orderId, double price, String currency) async {}
|
|
651
|
+
}
|
|
652
|
+
`;
|
|
653
|
+
await fs.ensureDir(path.join(projectDir, 'lib', 'core', 'data', 'api'));
|
|
654
|
+
await fs.outputFile(
|
|
655
|
+
path.join(projectDir, 'lib', 'core', 'data', 'api', 'tracking_api.dart'),
|
|
656
|
+
content,
|
|
657
|
+
'utf8'
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Generate lib/main.dart based on backend + selected modules.
|
|
663
|
+
*
|
|
664
|
+
* @param {string} projectDir
|
|
665
|
+
* @param {'firebase'|'supabase'|'api'} backend
|
|
666
|
+
* @param {string[]} modules
|
|
667
|
+
* @param {string} packageName
|
|
668
|
+
* @param {object} [options={}] - supabaseUrl, supabaseAnonKey
|
|
669
|
+
*/
|
|
670
|
+
async function writeMainDart(projectDir, backend, modules, packageName, options = {}) {
|
|
671
|
+
const pkg = packageName;
|
|
672
|
+
const withSentry = modules.includes('sentry');
|
|
673
|
+
const withWidget = modules.includes('widget');
|
|
674
|
+
const withRevenuecat = modules.includes('revenuecat');
|
|
675
|
+
const withSupabase = backend === 'supabase';
|
|
676
|
+
const withFirebase = backend === 'firebase' || backend === 'supabase'; // Firebase used for auth/notifications in all backends currently
|
|
677
|
+
|
|
678
|
+
const lines = [];
|
|
679
|
+
|
|
680
|
+
// ── Imports ────────────────────────────────────────────────────────────────
|
|
681
|
+
if (withFirebase) {
|
|
682
|
+
lines.push(`import 'package:firebase_core/firebase_core.dart';`);
|
|
683
|
+
}
|
|
684
|
+
lines.push(`import 'package:flutter/foundation.dart';`);
|
|
685
|
+
lines.push(`import 'package:flutter/material.dart';`);
|
|
686
|
+
lines.push(`import 'package:flutter/services.dart';`);
|
|
687
|
+
lines.push(`import 'package:flutter_localizations/flutter_localizations.dart';`);
|
|
688
|
+
lines.push(`import 'package:flutter_riverpod/flutter_riverpod.dart';`);
|
|
689
|
+
lines.push(`import 'package:logger/logger.dart';`);
|
|
690
|
+
lines.push(`import 'package:${pkg}/core/config/app_env.dart';`);
|
|
691
|
+
lines.push(`import 'package:${pkg}/core/data/api/analytics_api.dart';`);
|
|
692
|
+
lines.push(`import 'package:${pkg}/core/data/api/remote_config_api.dart';`);
|
|
693
|
+
lines.push(`import 'package:${pkg}/core/data/api/tracking_api.dart';`);
|
|
694
|
+
if (withWidget) {
|
|
695
|
+
lines.push(`import 'package:${pkg}/core/home_widgets/home_widget_service.dart';`);
|
|
696
|
+
}
|
|
697
|
+
lines.push(`import 'package:${pkg}/core/initializer/onstart_widget.dart';`);
|
|
698
|
+
lines.push(`import 'package:${pkg}/core/shared_preferences/shared_preferences.dart';`);
|
|
699
|
+
lines.push(`import 'package:${pkg}/core/states/user_state_notifier.dart';`);
|
|
700
|
+
lines.push(`import 'package:${pkg}/core/theme/theme.dart';`);
|
|
701
|
+
lines.push(`import 'package:${pkg}/environnements.dart';`);
|
|
702
|
+
if (withFirebase) {
|
|
703
|
+
lines.push(`import 'package:${pkg}/firebase_options_dev.dart' as firebase_dev;`);
|
|
704
|
+
}
|
|
705
|
+
lines.push(`import 'package:${pkg}/i18n/translations.g.dart';`);
|
|
706
|
+
lines.push(`import 'package:${pkg}/features/authentication/api/authentication_api.dart';`);
|
|
707
|
+
lines.push(`import 'package:${pkg}/features/notifications/api/local_notifier.dart';`);
|
|
708
|
+
lines.push(`import 'package:${pkg}/features/notifications/repositories/notifications_repository.dart';`);
|
|
709
|
+
if (withRevenuecat) {
|
|
710
|
+
lines.push(`import 'package:${pkg}/features/subscription/repositories/subscription_repository.dart';`);
|
|
711
|
+
}
|
|
712
|
+
lines.push(`import 'package:${pkg}/router.dart';`);
|
|
713
|
+
if (withSentry) {
|
|
714
|
+
lines.push(`import 'package:sentry_flutter/sentry_flutter.dart';`);
|
|
715
|
+
}
|
|
716
|
+
lines.push(`import 'package:shared_preferences/shared_preferences.dart';`);
|
|
717
|
+
if (withSupabase) {
|
|
718
|
+
lines.push(`import 'package:supabase_flutter/supabase_flutter.dart';`);
|
|
719
|
+
}
|
|
720
|
+
lines.push('');
|
|
721
|
+
|
|
722
|
+
// ── main() ────────────────────────────────────────────────────────────────
|
|
723
|
+
lines.push(`void main() async {`);
|
|
724
|
+
lines.push(` WidgetsFlutterBinding.ensureInitialized();`);
|
|
725
|
+
lines.push(` await AppEnv.load();`);
|
|
726
|
+
lines.push(` final env = Environment.fromEnv();`);
|
|
727
|
+
lines.push(` final logger = Logger();`);
|
|
728
|
+
lines.push(` logger.i('Starting app in \${env.name} mode');`);
|
|
729
|
+
lines.push(` // I like to force portrait mode on mobile devices`);
|
|
730
|
+
lines.push(` // What is the last time you used an app in landscape mode?`);
|
|
731
|
+
lines.push(` SystemChrome.setPreferredOrientations([`);
|
|
732
|
+
lines.push(` DeviceOrientation.portraitUp,`);
|
|
733
|
+
lines.push(` DeviceOrientation.portraitDown,`);
|
|
734
|
+
lines.push(` ]);`);
|
|
735
|
+
lines.push('');
|
|
736
|
+
lines.push(` // initialize shared preferences for theme and locale`);
|
|
737
|
+
lines.push(` final sharedPrefs = await SharedPreferences.getInstance();`);
|
|
738
|
+
lines.push('');
|
|
739
|
+
lines.push(` // Restore saved locale; fall back to device locale if none saved.`);
|
|
740
|
+
lines.push(` // Supported locales: en, pt, es. If the device locale is not one of these,`);
|
|
741
|
+
lines.push(` // the base locale (en) is used as fallback (configured in slang.yaml).`);
|
|
742
|
+
lines.push(` // To change the default locale, update \`base_locale\` in slang.yaml.`);
|
|
743
|
+
lines.push(` final savedLocale = sharedPrefs.getString('app_locale');`);
|
|
744
|
+
lines.push(` if (savedLocale != null) {`);
|
|
745
|
+
lines.push(` final appLocale = AppLocale.values.firstWhere(`);
|
|
746
|
+
lines.push(` (l) => l.languageCode == savedLocale,`);
|
|
747
|
+
lines.push(` orElse: () => AppLocale.en,`);
|
|
748
|
+
lines.push(` );`);
|
|
749
|
+
lines.push(` LocaleSettings.setLocale(appLocale);`);
|
|
750
|
+
lines.push(` } else {`);
|
|
751
|
+
lines.push(` LocaleSettings.useDeviceLocale();`);
|
|
752
|
+
lines.push(` }`);
|
|
753
|
+
lines.push('');
|
|
754
|
+
|
|
755
|
+
if (withSupabase) {
|
|
756
|
+
lines.push(` // initialize Supabase`);
|
|
757
|
+
lines.push(` await Supabase.initialize(`);
|
|
758
|
+
lines.push(` url: AppEnv.backendUrl,`);
|
|
759
|
+
lines.push(` anonKey: AppEnv.supabaseToken,`);
|
|
760
|
+
lines.push(` );`);
|
|
761
|
+
lines.push('');
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (withFirebase) {
|
|
765
|
+
lines.push(` // initialize firebase app for notifications`);
|
|
766
|
+
lines.push(` await switch (env) {`);
|
|
767
|
+
lines.push(` DevEnvironment() => Firebase.initializeApp(`);
|
|
768
|
+
lines.push(` options: firebase_dev.DefaultFirebaseOptions.currentPlatform,`);
|
|
769
|
+
lines.push(` ),`);
|
|
770
|
+
lines.push(` ProdEnvironment() => Firebase.initializeApp(`);
|
|
771
|
+
lines.push(` // TODO replace with your own firebase options for production environment (if needed)`);
|
|
772
|
+
lines.push(` options: firebase_dev.DefaultFirebaseOptions.currentPlatform,`);
|
|
773
|
+
lines.push(` ),`);
|
|
774
|
+
lines.push(` };`);
|
|
775
|
+
lines.push('');
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if (withSentry) {
|
|
779
|
+
lines.push(` // initialize sentry for error reporting in production only`);
|
|
780
|
+
lines.push(` // run the app with Sentry for production environment`);
|
|
781
|
+
lines.push(` if (env is DevEnvironment) {`);
|
|
782
|
+
lines.push(` run(sharedPrefs);`);
|
|
783
|
+
lines.push(` } else if (env is ProdEnvironment) {`);
|
|
784
|
+
lines.push(` SentryFlutter.init((options) {`);
|
|
785
|
+
lines.push(` options.dsn = env.sentryDsn;`);
|
|
786
|
+
lines.push(` // 20% of traces will be sent to Sentry server. You should start with 1 and decrease it once you have more users.`);
|
|
787
|
+
lines.push(` options.tracesSampleRate = 0.2;`);
|
|
788
|
+
lines.push(` options.environment = env.name;`);
|
|
789
|
+
lines.push(` }, appRunner: () => run(sharedPrefs));`);
|
|
790
|
+
lines.push(` }`);
|
|
791
|
+
} else {
|
|
792
|
+
lines.push(` run(sharedPrefs);`);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
lines.push(`}`);
|
|
796
|
+
lines.push('');
|
|
797
|
+
|
|
798
|
+
// ── run() ────────────────────────────────────────────────────────────────
|
|
799
|
+
lines.push(`void run(SharedPreferences prefs) => runApp(`);
|
|
800
|
+
lines.push(` TranslationProvider(`);
|
|
801
|
+
lines.push(` child: ProviderScope(child: MyApp(sharedPreferences: prefs)),`);
|
|
802
|
+
lines.push(` ),`);
|
|
803
|
+
lines.push(`);`);
|
|
804
|
+
lines.push('');
|
|
805
|
+
|
|
806
|
+
// ── MyApp widget ─────────────────────────────────────────────────────────
|
|
807
|
+
lines.push(`// use this if you want to define different themes for different platforms`);
|
|
808
|
+
lines.push(`// notifier: AppTheme.adaptive(`);
|
|
809
|
+
lines.push(`// defaultTextTheme: KasyTextTheme.build(),`);
|
|
810
|
+
lines.push(`// ios: const IosThemeFactory(),`);
|
|
811
|
+
lines.push(`// android: const AndroidThemeFactory(),`);
|
|
812
|
+
lines.push(`// web: const WebThemeFactory(),`);
|
|
813
|
+
lines.push(`// lightColors: KasyColors.light(),`);
|
|
814
|
+
lines.push(`// darkColors: KasyColors.dark(),`);
|
|
815
|
+
lines.push(`// mode: ThemeMode.dark,`);
|
|
816
|
+
lines.push(`// ),`);
|
|
817
|
+
lines.push(`// See ./docs/theme.md for more details`);
|
|
818
|
+
lines.push(`class MyApp extends ConsumerWidget {`);
|
|
819
|
+
lines.push(` final SharedPreferences sharedPreferences;`);
|
|
820
|
+
lines.push('');
|
|
821
|
+
lines.push(` const MyApp({super.key, required this.sharedPreferences});`);
|
|
822
|
+
lines.push('');
|
|
823
|
+
lines.push(` // This widget is the root of your application.`);
|
|
824
|
+
lines.push(` @override`);
|
|
825
|
+
lines.push(` Widget build(BuildContext context, WidgetRef ref) {`);
|
|
826
|
+
lines.push(` ErrorWidget.builder = (FlutterErrorDetails details) {`);
|
|
827
|
+
lines.push(` return AppErrorWidget(error: details);`);
|
|
828
|
+
lines.push(` };`);
|
|
829
|
+
lines.push(` final goRouter = ref.watch(goRouterProvider);`);
|
|
830
|
+
lines.push('');
|
|
831
|
+
lines.push(` return ThemeProvider(`);
|
|
832
|
+
lines.push(` notifier: AppTheme.uniform(`);
|
|
833
|
+
lines.push(` sharedPreferences: sharedPreferences,`);
|
|
834
|
+
lines.push(` themeFactory: const UniversalThemeFactory(),`);
|
|
835
|
+
lines.push(` lightColors: KasyColors.light(),`);
|
|
836
|
+
lines.push(` darkColors: KasyColors.dark(),`);
|
|
837
|
+
lines.push(` textTheme: KasyTextTheme.build(),`);
|
|
838
|
+
lines.push(` defaultMode: ThemeMode.light,`);
|
|
839
|
+
lines.push(` ),`);
|
|
840
|
+
lines.push(` child: Builder(`);
|
|
841
|
+
lines.push(` builder: (context) {`);
|
|
842
|
+
lines.push(` return MaterialApp.router(`);
|
|
843
|
+
lines.push(` title: 'Kasy',`);
|
|
844
|
+
lines.push(` theme: ThemeProvider.of(context).light,`);
|
|
845
|
+
lines.push(` darkTheme: ThemeProvider.of(context).dark,`);
|
|
846
|
+
lines.push(` themeMode: ThemeProvider.of(context).mode,`);
|
|
847
|
+
lines.push(` routerConfig: goRouter,`);
|
|
848
|
+
lines.push(` localizationsDelegates: const [`);
|
|
849
|
+
lines.push(` GlobalMaterialLocalizations.delegate,`);
|
|
850
|
+
lines.push(` GlobalWidgetsLocalizations.delegate,`);
|
|
851
|
+
lines.push(` GlobalCupertinoLocalizations.delegate,`);
|
|
852
|
+
lines.push(` ],`);
|
|
853
|
+
lines.push(` locale: TranslationProvider.of(context).flutterLocale,`);
|
|
854
|
+
lines.push(` supportedLocales: AppLocaleUtils.supportedLocales,`);
|
|
855
|
+
lines.push(` // Initializer is a widget that allows us to run some code before the app is ready`);
|
|
856
|
+
lines.push(` builder: (context, child) => Initializer(`);
|
|
857
|
+
lines.push(` services: [`);
|
|
858
|
+
lines.push(` authenticationApiProvider,`);
|
|
859
|
+
lines.push(` // shared preferences must be loaded`);
|
|
860
|
+
lines.push(` sharedPreferencesProvider,`);
|
|
861
|
+
lines.push(` // remote config api`);
|
|
862
|
+
lines.push(` remoteConfigApiProvider,`);
|
|
863
|
+
lines.push(` // notifications`);
|
|
864
|
+
lines.push(` notificationsSettingsProvider,`);
|
|
865
|
+
lines.push(` notificationRepositoryProvider,`);
|
|
866
|
+
lines.push(` // user state`);
|
|
867
|
+
if (withRevenuecat) {
|
|
868
|
+
lines.push(` subscriptionRepositoryProvider,`);
|
|
869
|
+
}
|
|
870
|
+
lines.push(` userStateNotifierProvider.notifier,`);
|
|
871
|
+
if (withWidget) {
|
|
872
|
+
lines.push(` homeWidgetsManagerProvider,`);
|
|
873
|
+
}
|
|
874
|
+
lines.push(` // analytics`);
|
|
875
|
+
lines.push(` analyticsApiProvider,`);
|
|
876
|
+
lines.push(` facebookEventApiProvider,`);
|
|
877
|
+
lines.push(` ],`);
|
|
878
|
+
lines.push(` onReady: child!,`);
|
|
879
|
+
lines.push(` onError: (_, error) => InitializationErrorPage(error: error),`);
|
|
880
|
+
lines.push(` onLoading: Scaffold(`);
|
|
881
|
+
lines.push(` body: Center(`);
|
|
882
|
+
lines.push(` child: CircularProgressIndicator.adaptive(`);
|
|
883
|
+
lines.push(` valueColor: AlwaysStoppedAnimation<Color>(`);
|
|
884
|
+
lines.push(` context.colors.primary,`);
|
|
885
|
+
lines.push(` ),`);
|
|
886
|
+
lines.push(` ),`);
|
|
887
|
+
lines.push(` ),`);
|
|
888
|
+
lines.push(` ),`);
|
|
889
|
+
lines.push(` ),`);
|
|
890
|
+
lines.push(` );`);
|
|
891
|
+
lines.push(` },`);
|
|
892
|
+
lines.push(` ),`);
|
|
893
|
+
lines.push(` );`);
|
|
894
|
+
lines.push(` }`);
|
|
895
|
+
lines.push(`}`);
|
|
896
|
+
lines.push('');
|
|
897
|
+
|
|
898
|
+
// ── AppErrorWidget ────────────────────────────────────────────────────────
|
|
899
|
+
lines.push(`/// This is an example of a more user friendly error widget`);
|
|
900
|
+
lines.push(`/// By default Flutter will show a red screen with the error in debug mode`);
|
|
901
|
+
lines.push(`/// and a grey screen in release mode`);
|
|
902
|
+
lines.push(`class AppErrorWidget extends StatelessWidget {`);
|
|
903
|
+
lines.push(` final FlutterErrorDetails? error;`);
|
|
904
|
+
lines.push('');
|
|
905
|
+
lines.push(` const AppErrorWidget({super.key, this.error});`);
|
|
906
|
+
lines.push('');
|
|
907
|
+
lines.push(` @override`);
|
|
908
|
+
lines.push(` Widget build(BuildContext context) {`);
|
|
909
|
+
lines.push(` return Container(`);
|
|
910
|
+
lines.push(` padding: const EdgeInsets.all(16),`);
|
|
911
|
+
lines.push(` color: Colors.orangeAccent,`);
|
|
912
|
+
lines.push(` child: Column(`);
|
|
913
|
+
lines.push(` mainAxisAlignment: MainAxisAlignment.center,`);
|
|
914
|
+
lines.push(` children: [`);
|
|
915
|
+
lines.push(` const Text(`);
|
|
916
|
+
lines.push(` 'Oups!',`);
|
|
917
|
+
lines.push(` style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),`);
|
|
918
|
+
lines.push(` ),`);
|
|
919
|
+
lines.push(` const SizedBox(height: 8),`);
|
|
920
|
+
lines.push(` const Text(`);
|
|
921
|
+
lines.push(` 'Sorry, Something went wrong',`);
|
|
922
|
+
lines.push(` style: TextStyle(color: Colors.white),`);
|
|
923
|
+
lines.push(` ),`);
|
|
924
|
+
lines.push(` const SizedBox(height: 8),`);
|
|
925
|
+
lines.push(` Text(`);
|
|
926
|
+
lines.push(` '\${error?.exception}\\n',`);
|
|
927
|
+
lines.push(` style: const TextStyle(color: Colors.white, fontSize: 10),`);
|
|
928
|
+
lines.push(` ),`);
|
|
929
|
+
lines.push(` ],`);
|
|
930
|
+
lines.push(` ),`);
|
|
931
|
+
lines.push(` );`);
|
|
932
|
+
lines.push(` }`);
|
|
933
|
+
lines.push(`}`);
|
|
934
|
+
lines.push('');
|
|
935
|
+
|
|
936
|
+
// ── InitializationErrorPage ───────────────────────────────────────────────
|
|
937
|
+
lines.push(`class InitializationErrorPage extends StatelessWidget {`);
|
|
938
|
+
lines.push(` final String error;`);
|
|
939
|
+
lines.push('');
|
|
940
|
+
lines.push(` const InitializationErrorPage({super.key, required this.error});`);
|
|
941
|
+
lines.push('');
|
|
942
|
+
lines.push(` @override`);
|
|
943
|
+
lines.push(` Widget build(BuildContext context) {`);
|
|
944
|
+
lines.push(` return Scaffold(`);
|
|
945
|
+
lines.push(` body: Padding(`);
|
|
946
|
+
lines.push(` padding: const EdgeInsets.all(24.0),`);
|
|
947
|
+
lines.push(` child: Column(`);
|
|
948
|
+
lines.push(` mainAxisAlignment: MainAxisAlignment.center,`);
|
|
949
|
+
lines.push(` crossAxisAlignment: CrossAxisAlignment.stretch,`);
|
|
950
|
+
lines.push(` spacing: 8,`);
|
|
951
|
+
lines.push(` children: [`);
|
|
952
|
+
lines.push(` Text('Cannot start app', style: context.textTheme.titleLarge),`);
|
|
953
|
+
lines.push(` Text(`);
|
|
954
|
+
lines.push(` 'Check your internet connection and start again',`);
|
|
955
|
+
lines.push(` style: context.textTheme.bodyLarge?.copyWith(`);
|
|
956
|
+
lines.push(` color: context.colors.muted,`);
|
|
957
|
+
lines.push(` ),`);
|
|
958
|
+
lines.push(` ),`);
|
|
959
|
+
lines.push(` if (kDebugMode)`);
|
|
960
|
+
lines.push(` Text(`);
|
|
961
|
+
lines.push(` "developper mode error: \$error",`);
|
|
962
|
+
lines.push(` style: context.textTheme.bodyLarge?.copyWith(`);
|
|
963
|
+
lines.push(` color: context.colors.error,`);
|
|
964
|
+
lines.push(` ),`);
|
|
965
|
+
lines.push(` ),`);
|
|
966
|
+
lines.push(` ],`);
|
|
967
|
+
lines.push(` ),`);
|
|
968
|
+
lines.push(` ),`);
|
|
969
|
+
lines.push(` );`);
|
|
970
|
+
lines.push(` }`);
|
|
971
|
+
lines.push(`}`);
|
|
972
|
+
lines.push('');
|
|
973
|
+
|
|
974
|
+
await fs.ensureDir(path.join(projectDir, 'lib'));
|
|
975
|
+
await fs.outputFile(
|
|
976
|
+
path.join(projectDir, 'lib', 'main.dart'),
|
|
977
|
+
lines.join('\n'),
|
|
978
|
+
'utf8'
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Remove optional module directories that were NOT selected by the user.
|
|
984
|
+
* The base template (Firebase/) has all modules; this function removes the
|
|
985
|
+
* directories for modules the user did not select to avoid dead code.
|
|
986
|
+
*
|
|
987
|
+
* @param {string} projectDir
|
|
988
|
+
* @param {string[]} modules - Selected modules (e.g. ['camera', 'analytics'])
|
|
989
|
+
*/
|
|
990
|
+
async function removeModuleDirs(projectDir, modules) {
|
|
991
|
+
const removable = [
|
|
992
|
+
{ module: 'feedback', dir: path.join('lib', 'features', 'feedbacks') },
|
|
993
|
+
{ module: 'llm_chat', dir: path.join('lib', 'features', 'llm_chat') },
|
|
994
|
+
{ module: 'onboarding',dir: path.join('lib', 'features', 'onboarding') },
|
|
995
|
+
// home_widgets/ imports home_widget package — remove when widget not selected
|
|
996
|
+
{ module: 'widget', dir: path.join('lib', 'core', 'home_widgets') },
|
|
997
|
+
// subscription/ imports purchases_flutter — remove when revenuecat not selected
|
|
998
|
+
{ module: 'revenuecat', dir: path.join('lib', 'features', 'subscription') },
|
|
999
|
+
// local_reminder/ uses flutter_timezone/timezone — remove when local_notifications not selected
|
|
1000
|
+
{ module: 'local_notifications', dir: path.join('lib', 'features', 'local_reminder') },
|
|
1001
|
+
];
|
|
1002
|
+
|
|
1003
|
+
for (const { module, dir } of removable) {
|
|
1004
|
+
if (!modules.includes(module)) {
|
|
1005
|
+
const fullPath = path.join(projectDir, dir);
|
|
1006
|
+
if (await fs.pathExists(fullPath)) {
|
|
1007
|
+
await fs.remove(fullPath);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
/**
|
|
1014
|
+
* Remove Android native widget artifacts when the widget module is not selected.
|
|
1015
|
+
* Deletes MyWidget.kt, MyWidgetReceiver.kt, mywidget_info.xml, and removes
|
|
1016
|
+
* the <receiver> block from AndroidManifest.xml so the widget does not appear
|
|
1017
|
+
* in the Android widget picker.
|
|
1018
|
+
*
|
|
1019
|
+
* @param {string} projectDir
|
|
1020
|
+
* @param {string} bundleId - e.g. "com.example.myapp"
|
|
1021
|
+
*/
|
|
1022
|
+
/**
|
|
1023
|
+
* Removes Facebook SDK meta-data entries from AndroidManifest.xml when the
|
|
1024
|
+
* Facebook module is not selected. Without this, the Facebook SDK initializes
|
|
1025
|
+
* on app start and logs OAuthException error 190 (invalid App ID) for every
|
|
1026
|
+
* network request.
|
|
1027
|
+
*
|
|
1028
|
+
* @param {string} projectDir
|
|
1029
|
+
*/
|
|
1030
|
+
/**
|
|
1031
|
+
* Remove FacebookSigninComponent from signin_page.dart and signup_page.dart
|
|
1032
|
+
* when the facebook module is not selected.
|
|
1033
|
+
* Removes both the import line and the widget line (package-name-agnostic regex).
|
|
1034
|
+
*
|
|
1035
|
+
* @param {string} projectDir
|
|
1036
|
+
*/
|
|
1037
|
+
async function removeFacebookSigninFromAuthPages(projectDir) {
|
|
1038
|
+
const pages = [
|
|
1039
|
+
path.join(projectDir, 'lib', 'features', 'authentication', 'ui', 'signin_page.dart'),
|
|
1040
|
+
path.join(projectDir, 'lib', 'features', 'authentication', 'ui', 'signup_page.dart'),
|
|
1041
|
+
];
|
|
1042
|
+
for (const p of pages) {
|
|
1043
|
+
if (!(await fs.pathExists(p))) continue;
|
|
1044
|
+
let content = await fs.readFile(p, 'utf8');
|
|
1045
|
+
// Remove import line
|
|
1046
|
+
content = content.replace(/^import 'package:[^']+\/features\/authentication\/ui\/components\/facebook_signin\.dart';\n/m, '');
|
|
1047
|
+
// Remove component line (any leading whitespace)
|
|
1048
|
+
content = content.replace(/[ \t]*const FacebookSigninComponent\(\),\n/g, '');
|
|
1049
|
+
await fs.writeFile(p, content, 'utf8');
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
async function removeAndroidFacebookMetadata(projectDir) {
|
|
1054
|
+
const manifestPath = path.join(projectDir, 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
|
|
1055
|
+
if (!(await fs.pathExists(manifestPath))) return;
|
|
1056
|
+
|
|
1057
|
+
let content = await fs.readFile(manifestPath, 'utf8');
|
|
1058
|
+
// Remove both facebook meta-data lines (ApplicationId and ClientToken)
|
|
1059
|
+
content = content.replace(/[ \t]*<meta-data android:name="com\.facebook\.sdk\.ApplicationId"[^\n]*\n?/g, '');
|
|
1060
|
+
content = content.replace(/[ \t]*<meta-data android:name="com\.facebook\.sdk\.ClientToken"[^\n]*\n?/g, '');
|
|
1061
|
+
await fs.writeFile(manifestPath, content, 'utf8');
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
async function removeAndroidWidgetArtifacts(projectDir, bundleId) {
|
|
1065
|
+
const bundlePath = (bundleId || '').replace(/\./g, '/');
|
|
1066
|
+
const kotlinDir = path.join(projectDir, 'android', 'app', 'src', 'main', 'kotlin', bundlePath);
|
|
1067
|
+
|
|
1068
|
+
for (const file of ['MyWidget.kt', 'MyWidgetReceiver.kt']) {
|
|
1069
|
+
const p = path.join(kotlinDir, file);
|
|
1070
|
+
if (await fs.pathExists(p)) await fs.remove(p);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
const widgetXml = path.join(projectDir, 'android', 'app', 'src', 'main', 'res', 'xml', 'mywidget_info.xml');
|
|
1074
|
+
if (await fs.pathExists(widgetXml)) await fs.remove(widgetXml);
|
|
1075
|
+
|
|
1076
|
+
const manifestPath = path.join(projectDir, 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
|
|
1077
|
+
if (await fs.pathExists(manifestPath)) {
|
|
1078
|
+
let content = await fs.readFile(manifestPath, 'utf8');
|
|
1079
|
+
// Remove the MyWidgetReceiver <receiver> block (multiline)
|
|
1080
|
+
content = content.replace(/[ \t]*<receiver[\s\S]*?MyWidgetReceiver[\s\S]*?<\/receiver>\n?/m, '');
|
|
1081
|
+
await fs.writeFile(manifestPath, content, 'utf8');
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* Writes a NoOp admin_home_widgets.dart when the widget module is not selected.
|
|
1087
|
+
* The real version imports home_widget_mywidget_service.dart which won't exist.
|
|
1088
|
+
*
|
|
1089
|
+
* @param {string} projectDir
|
|
1090
|
+
*/
|
|
1091
|
+
async function writeNoOpAdminHomeWidgets(projectDir) {
|
|
1092
|
+
const content = `import 'package:flutter/material.dart';
|
|
1093
|
+
// No-op home widgets admin panel. Run: kasy add widget to activate.
|
|
1094
|
+
class AdminHomeWidgets extends StatelessWidget {
|
|
1095
|
+
const AdminHomeWidgets({super.key});
|
|
1096
|
+
|
|
1097
|
+
@override
|
|
1098
|
+
Widget build(BuildContext context) => const SizedBox.shrink();
|
|
1099
|
+
}
|
|
1100
|
+
`;
|
|
1101
|
+
await fs.outputFile(
|
|
1102
|
+
path.join(projectDir, 'lib', 'features', 'settings', 'ui', 'components', 'admin', 'admin_home_widgets.dart'),
|
|
1103
|
+
content,
|
|
1104
|
+
'utf8',
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* Writes a NoOp feature_request_repository.dart when the feedback module is not selected.
|
|
1110
|
+
* premium_page_provider.dart imports this provider — without it the project won't compile.
|
|
1111
|
+
*
|
|
1112
|
+
* @param {string} projectDir
|
|
1113
|
+
* @param {string} packageName
|
|
1114
|
+
*/
|
|
1115
|
+
async function writeNoOpFeatureRequestRepository(projectDir, packageName) {
|
|
1116
|
+
const content = `import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
1117
|
+
// No-op feedback repository. Run: kasy add feedback to activate.
|
|
1118
|
+
final featureRequestRepositoryProvider = Provider<FeatureRequestRepository>(
|
|
1119
|
+
(ref) => const FeatureRequestRepository(),
|
|
1120
|
+
);
|
|
1121
|
+
|
|
1122
|
+
class FeatureRequestRepository {
|
|
1123
|
+
const FeatureRequestRepository();
|
|
1124
|
+
|
|
1125
|
+
Future<List<dynamic>> getActiveFeatureRequests() async => [];
|
|
1126
|
+
Future<List<dynamic>> getUserVotes(String userId) async => [];
|
|
1127
|
+
Future<void> createNewFeatureSuggestion({
|
|
1128
|
+
required String userId,
|
|
1129
|
+
required String title,
|
|
1130
|
+
required String description,
|
|
1131
|
+
}) async {}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
class UserAlreadyVotedException implements Exception {
|
|
1135
|
+
final String? message;
|
|
1136
|
+
UserAlreadyVotedException({this.message});
|
|
1137
|
+
}
|
|
1138
|
+
`;
|
|
1139
|
+
await fs.outputFile(
|
|
1140
|
+
path.join(projectDir, 'lib', 'features', 'feedbacks', 'repositories', 'feature_request_repository.dart'),
|
|
1141
|
+
content,
|
|
1142
|
+
'utf8',
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* Strip optional pubspec.yaml dependencies that are not needed based on selected modules.
|
|
1148
|
+
* Reads pubspec.yaml as text and removes lines matching " <dep>: ..." for each dep.
|
|
1149
|
+
*
|
|
1150
|
+
* @param {string} projectDir
|
|
1151
|
+
* @param {string[]} modules - Selected modules
|
|
1152
|
+
*/
|
|
1153
|
+
async function stripPubspecDeps(projectDir, modules) {
|
|
1154
|
+
const pubspecPath = path.join(projectDir, 'pubspec.yaml');
|
|
1155
|
+
if (!(await fs.pathExists(pubspecPath))) return;
|
|
1156
|
+
|
|
1157
|
+
// Map: module → list of dep package names to remove when module NOT selected.
|
|
1158
|
+
const depMap = [
|
|
1159
|
+
{ module: 'analytics', deps: ['mixpanel_flutter'] },
|
|
1160
|
+
{ module: 'revenuecat', deps: ['purchases_flutter'] },
|
|
1161
|
+
{ module: 'widget', deps: ['home_widget', 'background_fetch'] },
|
|
1162
|
+
];
|
|
1163
|
+
|
|
1164
|
+
let content = await fs.readFile(pubspecPath, 'utf8');
|
|
1165
|
+
|
|
1166
|
+
for (const { module, deps } of depMap) {
|
|
1167
|
+
if (!modules.includes(module)) {
|
|
1168
|
+
for (const dep of deps) {
|
|
1169
|
+
// Remove lines like " mixpanel_flutter: ^2.5.0" (with any amount of leading spaces)
|
|
1170
|
+
content = content.replace(new RegExp(`^[ \\t]*${dep}:.*\\n`, 'gm'), '');
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// sentry_flutter can be stripped only when none of the modules that use it are selected:
|
|
1176
|
+
// - revenuecat: revenuecat tracking_api.dart uses Sentry
|
|
1177
|
+
// - facebook: facebook tracking_api.dart uses Sentry
|
|
1178
|
+
// - sentry: explicitly selected
|
|
1179
|
+
// When none of these are selected, writeNoOpSentryUsages patches core files so they
|
|
1180
|
+
// no longer import sentry_flutter, allowing the dep to be removed.
|
|
1181
|
+
if (!modules.includes('sentry') && !modules.includes('revenuecat') && !modules.includes('facebook')) {
|
|
1182
|
+
content = content.replace(new RegExp(`^[ \\t]*sentry_flutter:.*\\n`, 'gm'), '');
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
await fs.outputFile(pubspecPath, content, 'utf8');
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* Patches core files that always import sentry_flutter, removing the dependency
|
|
1190
|
+
* 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
|
+
*
|
|
1195
|
+
* @param {string} projectDir
|
|
1196
|
+
*/
|
|
1197
|
+
async function writeNoOpSentryUsages(projectDir) {
|
|
1198
|
+
const sentryImport = `import 'package:sentry_flutter/sentry_flutter.dart';\n`;
|
|
1199
|
+
|
|
1200
|
+
const files = [
|
|
1201
|
+
path.join(projectDir, 'lib', 'core', 'initializer', 'onstart_widget.dart'),
|
|
1202
|
+
path.join(projectDir, 'lib', 'core', 'data', 'api', 'remote_config_api.dart'),
|
|
1203
|
+
];
|
|
1204
|
+
|
|
1205
|
+
for (const filePath of files) {
|
|
1206
|
+
if (!(await fs.pathExists(filePath))) continue;
|
|
1207
|
+
let content = await fs.readFile(filePath, 'utf8');
|
|
1208
|
+
// Remove sentry import line
|
|
1209
|
+
content = content.replace(sentryImport, '');
|
|
1210
|
+
// Remove single-line Sentry.captureException(...); calls (with leading whitespace)
|
|
1211
|
+
content = content.replace(/^[ \t]*Sentry\.captureException\([^)]+\);\n/gm, '');
|
|
1212
|
+
await fs.outputFile(filePath, content, 'utf8');
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
/**
|
|
1217
|
+
* Adds a dependency to pubspec.yaml if not already present.
|
|
1218
|
+
* Inserts alphabetically within the dependencies block.
|
|
1219
|
+
*
|
|
1220
|
+
* @param {string} projectDir
|
|
1221
|
+
* @param {string} depName - e.g. 'mixpanel_flutter'
|
|
1222
|
+
* @param {string} depVersion - e.g. '^2.5.0'
|
|
1223
|
+
*/
|
|
1224
|
+
async function addPubspecDep(projectDir, depName, depVersion) {
|
|
1225
|
+
const pubspecPath = path.join(projectDir, 'pubspec.yaml');
|
|
1226
|
+
if (!(await fs.pathExists(pubspecPath))) return;
|
|
1227
|
+
|
|
1228
|
+
let content = await fs.readFile(pubspecPath, 'utf8');
|
|
1229
|
+
|
|
1230
|
+
// Skip if already present
|
|
1231
|
+
if (new RegExp(`^[ \\t]*${depName}:`, 'm').test(content)) return;
|
|
1232
|
+
|
|
1233
|
+
const newLine = ` ${depName}: ${depVersion}`;
|
|
1234
|
+
|
|
1235
|
+
// Find the dependencies block and insert alphabetically
|
|
1236
|
+
const lines = content.split('\n');
|
|
1237
|
+
let inDeps = false;
|
|
1238
|
+
let insertAt = -1;
|
|
1239
|
+
|
|
1240
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1241
|
+
const line = lines[i];
|
|
1242
|
+
if (/^dependencies:/.test(line)) {
|
|
1243
|
+
inDeps = true;
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
if (inDeps) {
|
|
1247
|
+
// End of dependencies block
|
|
1248
|
+
if (/^[a-zA-Z]/.test(line) || /^dev_dependencies:/.test(line)) {
|
|
1249
|
+
if (insertAt === -1) insertAt = i; // fallback: insert just before this
|
|
1250
|
+
break;
|
|
1251
|
+
}
|
|
1252
|
+
// Only consider simple dep lines (2 spaces + name:)
|
|
1253
|
+
const match = line.match(/^ ([a-z_][a-z_0-9]*)\s*:/);
|
|
1254
|
+
if (match) {
|
|
1255
|
+
if (match[1] > depName) {
|
|
1256
|
+
insertAt = i;
|
|
1257
|
+
break;
|
|
1258
|
+
}
|
|
1259
|
+
insertAt = i + 1; // advance past this dep
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
if (insertAt === -1) {
|
|
1265
|
+
// Fallback: append before dev_dependencies
|
|
1266
|
+
const devIdx = lines.findIndex(l => /^dev_dependencies:/.test(l));
|
|
1267
|
+
insertAt = devIdx !== -1 ? devIdx : lines.length;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
lines.splice(insertAt, 0, newLine);
|
|
1271
|
+
await fs.outputFile(pubspecPath, lines.join('\n'), 'utf8');
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
/**
|
|
1275
|
+
* Writes no-op stubs for the subscription module when revenuecat is not selected.
|
|
1276
|
+
* Removes purchases_flutter-dependent test files and creates minimal stubs so that
|
|
1277
|
+
* core files that reference subscription types still compile.
|
|
1278
|
+
*
|
|
1279
|
+
* @param {string} projectDir
|
|
1280
|
+
* @param {string} packageName
|
|
1281
|
+
*/
|
|
1282
|
+
async function writeNoOpSubscriptionStubs(projectDir, packageName) {
|
|
1283
|
+
const pkg = packageName;
|
|
1284
|
+
|
|
1285
|
+
// 0. Replace lib/core/data/models/subscription.dart with stripped version (no purchases_flutter)
|
|
1286
|
+
await fs.outputFile(
|
|
1287
|
+
path.join(projectDir, 'lib', 'core', 'data', 'models', 'subscription.dart'),
|
|
1288
|
+
`import 'package:flutter/material.dart';
|
|
1289
|
+
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
1290
|
+
|
|
1291
|
+
part 'subscription.freezed.dart';
|
|
1292
|
+
|
|
1293
|
+
abstract class SubscriptionProduct {
|
|
1294
|
+
String get skuId;
|
|
1295
|
+
String get id;
|
|
1296
|
+
String get description;
|
|
1297
|
+
String get label;
|
|
1298
|
+
double get price;
|
|
1299
|
+
Duration get duration;
|
|
1300
|
+
String? get promotion;
|
|
1301
|
+
String formattedPrice(BuildContext context);
|
|
1302
|
+
DurationType get durationType;
|
|
1303
|
+
String? get title;
|
|
1304
|
+
int? get trialDays;
|
|
1305
|
+
List<String>? get features;
|
|
1306
|
+
String get priceString;
|
|
1307
|
+
String get currency;
|
|
1308
|
+
String pricePerMonth(BuildContext context);
|
|
1309
|
+
String? pricePerYear(BuildContext context);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// RevenueCatProduct is provided by the revenuecat feature module.
|
|
1313
|
+
// Run: kasy add revenuecat to activate in-app purchases.
|
|
1314
|
+
|
|
1315
|
+
enum DurationType { week, month, threeMonth, sixMonth, year, lifetime }
|
|
1316
|
+
|
|
1317
|
+
@freezed
|
|
1318
|
+
sealed class Subscription with _$Subscription {
|
|
1319
|
+
const factory Subscription.active({
|
|
1320
|
+
SubscriptionProduct? activeOffer,
|
|
1321
|
+
}) = SubscriptionStateData;
|
|
1322
|
+
|
|
1323
|
+
const factory Subscription.inactive({
|
|
1324
|
+
required int hoursBetweenTwoRequests,
|
|
1325
|
+
DateTime? lastAskingDate,
|
|
1326
|
+
}) = SubscriptionInactiveStateData;
|
|
1327
|
+
|
|
1328
|
+
const factory Subscription.loading() = SubscriptionStateLoading;
|
|
1329
|
+
|
|
1330
|
+
const Subscription._();
|
|
1331
|
+
|
|
1332
|
+
bool get canPurchase => switch (this) {
|
|
1333
|
+
SubscriptionStateData() => false,
|
|
1334
|
+
SubscriptionInactiveStateData() => true,
|
|
1335
|
+
SubscriptionStateLoading() => false,
|
|
1336
|
+
};
|
|
1337
|
+
|
|
1338
|
+
bool get isActive => switch (this) {
|
|
1339
|
+
SubscriptionStateData() => true,
|
|
1340
|
+
SubscriptionInactiveStateData() => false,
|
|
1341
|
+
SubscriptionStateLoading() => false,
|
|
1342
|
+
};
|
|
1343
|
+
|
|
1344
|
+
// Returns false without RevenueCat — run: kasy add revenuecat to activate
|
|
1345
|
+
bool get isInTrial => false;
|
|
1346
|
+
|
|
1347
|
+
// Returns false without RevenueCat — run: kasy add revenuecat to activate
|
|
1348
|
+
bool get hasRenewal => false;
|
|
1349
|
+
|
|
1350
|
+
bool get isLifetime => switch (this) {
|
|
1351
|
+
SubscriptionStateData(:final activeOffer) =>
|
|
1352
|
+
activeOffer?.durationType == DurationType.lifetime,
|
|
1353
|
+
SubscriptionInactiveStateData() => false,
|
|
1354
|
+
SubscriptionStateLoading() => false,
|
|
1355
|
+
};
|
|
1356
|
+
|
|
1357
|
+
bool get shouldAskForSubscription {
|
|
1358
|
+
final now = DateTime.now();
|
|
1359
|
+
final (lastAskingDate, hoursBetweenTwoRequests) = switch (this) {
|
|
1360
|
+
SubscriptionStateData() => (null, null),
|
|
1361
|
+
SubscriptionInactiveStateData(
|
|
1362
|
+
:final lastAskingDate,
|
|
1363
|
+
:final hoursBetweenTwoRequests,
|
|
1364
|
+
) =>
|
|
1365
|
+
(lastAskingDate, hoursBetweenTwoRequests),
|
|
1366
|
+
SubscriptionStateLoading() => (null, null),
|
|
1367
|
+
};
|
|
1368
|
+
if (lastAskingDate == null) {
|
|
1369
|
+
return true;
|
|
1370
|
+
}
|
|
1371
|
+
final diff = now.difference(lastAskingDate);
|
|
1372
|
+
return diff.inHours >= hoursBetweenTwoRequests!;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
`,
|
|
1376
|
+
'utf8',
|
|
1377
|
+
);
|
|
1378
|
+
|
|
1379
|
+
// 0b. Strip EntitlementInfo from user_state_notifier.dart (purchases_flutter must be removed)
|
|
1380
|
+
const userStateFile = path.join(projectDir, 'lib', 'core', 'states', 'user_state_notifier.dart');
|
|
1381
|
+
if (await fs.pathExists(userStateFile)) {
|
|
1382
|
+
let userStateContent = await fs.readFile(userStateFile, 'utf8');
|
|
1383
|
+
userStateContent = userStateContent
|
|
1384
|
+
.replaceAll("import 'package:purchases_flutter/models/entitlement_info_wrapper.dart';\n", '')
|
|
1385
|
+
.replaceAll('\n List<EntitlementInfo>? entitlements,', '')
|
|
1386
|
+
.replace(/,\n\s+entitlements: entitlements,/g, ',');
|
|
1387
|
+
await fs.writeFile(userStateFile, userStateContent, 'utf8');
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// 1. No-op subscription_repository.dart (used by user_repository, onboarding, home_widget_background_task)
|
|
1391
|
+
await fs.outputFile(
|
|
1392
|
+
path.join(projectDir, 'lib', 'features', 'subscription', 'repositories', 'subscription_repository.dart'),
|
|
1393
|
+
`import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
1394
|
+
import 'package:${pkg}/core/data/models/subscription.dart';
|
|
1395
|
+
import 'package:${pkg}/core/initializer/onstart_service.dart';
|
|
1396
|
+
// No-op subscription repository. Run: kasy add revenuecat to activate.
|
|
1397
|
+
final subscriptionRepositoryProvider = Provider<SubscriptionRepository>(
|
|
1398
|
+
(ref) => SubscriptionRepository(),
|
|
1399
|
+
);
|
|
1400
|
+
|
|
1401
|
+
class SubscriptionRepository implements OnStartService {
|
|
1402
|
+
SubscriptionRepository({
|
|
1403
|
+
dynamic subscriptionApi,
|
|
1404
|
+
dynamic inAppSubscriptionApi,
|
|
1405
|
+
dynamic prefs,
|
|
1406
|
+
});
|
|
1407
|
+
|
|
1408
|
+
@override
|
|
1409
|
+
Future<void> init() async {}
|
|
1410
|
+
|
|
1411
|
+
Future<void> initUser(String userId) async {}
|
|
1412
|
+
|
|
1413
|
+
Future<Subscription> get(String userId) async =>
|
|
1414
|
+
const Subscription.inactive(hoursBetweenTwoRequests: 24);
|
|
1415
|
+
|
|
1416
|
+
Future<List<SubscriptionProduct>> getOffers({String? offerId}) async => [];
|
|
1417
|
+
|
|
1418
|
+
Future<bool> checkPermission(String permissionToCheck) async => false;
|
|
1419
|
+
|
|
1420
|
+
Future<List<dynamic>?> purchase(SubscriptionProduct product) async => null;
|
|
1421
|
+
|
|
1422
|
+
Future<void> unsubscribe() async {}
|
|
1423
|
+
|
|
1424
|
+
Future<void> restorePurchase() async {}
|
|
1425
|
+
|
|
1426
|
+
Future<void> saveLastAskingDate() async {}
|
|
1427
|
+
|
|
1428
|
+
DateTime? get lastAskingDate => null;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
class UserCancelledPurchaseException implements Exception {}
|
|
1432
|
+
`,
|
|
1433
|
+
'utf8',
|
|
1434
|
+
);
|
|
1435
|
+
|
|
1436
|
+
// 2. No-op maybeshow_premium.dart (used by home_page — must implement MaybeShowWithRef)
|
|
1437
|
+
await fs.outputFile(
|
|
1438
|
+
path.join(projectDir, 'lib', 'features', 'subscription', 'shared', 'maybeshow_premium.dart'),
|
|
1439
|
+
`import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
1440
|
+
import 'package:${pkg}/core/states/components/maybeshow_component.dart';
|
|
1441
|
+
import 'package:${pkg}/core/states/models/event_model.dart';
|
|
1442
|
+
// No-op premium paywall trigger. Run: kasy add revenuecat to activate.
|
|
1443
|
+
class MaybeShowPremiumPage implements MaybeShowWithRef {
|
|
1444
|
+
@override
|
|
1445
|
+
Future<bool> handle(WidgetRef ref, AppEvent event) async => false;
|
|
1446
|
+
}
|
|
1447
|
+
`,
|
|
1448
|
+
'utf8',
|
|
1449
|
+
);
|
|
1450
|
+
|
|
1451
|
+
// 3. No-op premium_page.dart (used by onboarding_page with args: PremiumPageArgs(redirect:))
|
|
1452
|
+
await fs.outputFile(
|
|
1453
|
+
path.join(projectDir, 'lib', 'features', 'subscription', 'ui', 'premium_page.dart'),
|
|
1454
|
+
`import 'package:flutter/material.dart';
|
|
1455
|
+
// No-op premium page. Run: kasy add revenuecat to activate.
|
|
1456
|
+
class PremiumPage extends StatelessWidget {
|
|
1457
|
+
const PremiumPage({super.key, this.paywall, this.args});
|
|
1458
|
+
final dynamic paywall;
|
|
1459
|
+
final PremiumPageArgs? args;
|
|
1460
|
+
@override
|
|
1461
|
+
Widget build(BuildContext context) => const SizedBox.shrink();
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
class PremiumPageArgs {
|
|
1465
|
+
const PremiumPageArgs({this.redirect});
|
|
1466
|
+
final String? redirect;
|
|
1467
|
+
}
|
|
1468
|
+
`,
|
|
1469
|
+
'utf8',
|
|
1470
|
+
);
|
|
1471
|
+
|
|
1472
|
+
// 4. No-op premium_page_factory.dart (used by admin_paywalls)
|
|
1473
|
+
await fs.outputFile(
|
|
1474
|
+
path.join(projectDir, 'lib', 'features', 'subscription', 'ui', 'component', 'premium_page_factory.dart'),
|
|
1475
|
+
`// No-op paywall factory. Run: kasy add revenuecat to activate.
|
|
1476
|
+
enum PaywallFactory { basic }
|
|
1477
|
+
`,
|
|
1478
|
+
'utf8',
|
|
1479
|
+
);
|
|
1480
|
+
|
|
1481
|
+
// 5. No-op subscription_entity.dart (used by test_utils, fake_subscription_api)
|
|
1482
|
+
await fs.outputFile(
|
|
1483
|
+
path.join(projectDir, 'lib', 'features', 'subscription', 'api', 'entities', 'subscription_entity.dart'),
|
|
1484
|
+
`// No-op subscription entity. Run: kasy add revenuecat to activate.
|
|
1485
|
+
enum SubscriptionStatus { ACTIVE, PAUSED, EXPIRED, LIFETIME, CANCELLED }
|
|
1486
|
+
|
|
1487
|
+
class SubscriptionEntity {
|
|
1488
|
+
final String skuId;
|
|
1489
|
+
final String? offerId;
|
|
1490
|
+
final DateTime? creationDate;
|
|
1491
|
+
final DateTime? periodEndDate;
|
|
1492
|
+
final SubscriptionStatus status;
|
|
1493
|
+
const SubscriptionEntity({
|
|
1494
|
+
required this.skuId,
|
|
1495
|
+
required this.status,
|
|
1496
|
+
this.offerId,
|
|
1497
|
+
this.creationDate,
|
|
1498
|
+
this.periodEndDate,
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
`,
|
|
1502
|
+
'utf8',
|
|
1503
|
+
);
|
|
1504
|
+
|
|
1505
|
+
// 6. No-op subscription_api.dart (provider + abstract class used by test_utils and fake)
|
|
1506
|
+
await fs.outputFile(
|
|
1507
|
+
path.join(projectDir, 'lib', 'features', 'subscription', 'api', 'subscription_api.dart'),
|
|
1508
|
+
`import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
1509
|
+
import 'package:${pkg}/features/subscription/api/entities/subscription_entity.dart';
|
|
1510
|
+
// No-op subscription API. Run: kasy add revenuecat to activate.
|
|
1511
|
+
final subscriptionApiProvider = Provider<SubscriptionApi>(
|
|
1512
|
+
(ref) => _NoOpSubscriptionApi(),
|
|
1513
|
+
);
|
|
1514
|
+
|
|
1515
|
+
abstract class SubscriptionApi {
|
|
1516
|
+
Future<SubscriptionEntity?> get(String userId);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
class _NoOpSubscriptionApi implements SubscriptionApi {
|
|
1520
|
+
@override
|
|
1521
|
+
Future<SubscriptionEntity?> get(String userId) async => null;
|
|
1522
|
+
}
|
|
1523
|
+
`,
|
|
1524
|
+
'utf8',
|
|
1525
|
+
);
|
|
1526
|
+
|
|
1527
|
+
// 7. No-op inapp_subscription_api.dart (provider + class used by test_utils and fake)
|
|
1528
|
+
await fs.outputFile(
|
|
1529
|
+
path.join(projectDir, 'lib', 'features', 'subscription', 'api', 'inapp_subscription_api.dart'),
|
|
1530
|
+
`import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
1531
|
+
import 'package:${pkg}/core/data/models/subscription.dart';
|
|
1532
|
+
// No-op in-app subscription API. Run: kasy add revenuecat to activate.
|
|
1533
|
+
final inAppSubscriptionApiProvider = Provider<RevenueCatPaymentApi>(
|
|
1534
|
+
(ref) => RevenueCatPaymentApi(),
|
|
1535
|
+
);
|
|
1536
|
+
|
|
1537
|
+
class RevenueCatPaymentApi {
|
|
1538
|
+
Future<void> init() async {}
|
|
1539
|
+
Future<void> initUser(String userId) async {}
|
|
1540
|
+
Future<void> disconnectUser() async {}
|
|
1541
|
+
Future<List<SubscriptionProduct>> getOffers(String? offerId) async => [];
|
|
1542
|
+
Future<void> unsubscribe() async {}
|
|
1543
|
+
Future<void> restorePurchase() async {}
|
|
1544
|
+
Future<List<SubscriptionProduct>> getActiveSubscription() async => [];
|
|
1545
|
+
Future<SubscriptionProduct?> getFromProductId(String productId) async => null;
|
|
1546
|
+
Future<List<dynamic>> getLastPurchase() async => [];
|
|
1547
|
+
Future<void> presentCodeRedemptionSheet() async {}
|
|
1548
|
+
Future<List<dynamic>> getPermissions() async => [];
|
|
1549
|
+
Future<List<dynamic>?> getEntitlements() async => [];
|
|
1550
|
+
}
|
|
1551
|
+
`,
|
|
1552
|
+
'utf8',
|
|
1553
|
+
);
|
|
1554
|
+
|
|
1555
|
+
// 8. Remove test/features/subscription/ entirely (imports purchases_flutter and RevenueCat types)
|
|
1556
|
+
const testSubscriptionDir = path.join(projectDir, 'test', 'features', 'subscription');
|
|
1557
|
+
if (await fs.pathExists(testSubscriptionDir)) {
|
|
1558
|
+
await fs.remove(testSubscriptionDir);
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
// 9. Restore fake_subscription_api.dart with currentFake setter (required by test_utils.dart)
|
|
1562
|
+
await fs.outputFile(
|
|
1563
|
+
path.join(projectDir, 'test', 'features', 'subscription', 'api', 'fake_subscription_api.dart'),
|
|
1564
|
+
`import 'package:${pkg}/features/subscription/api/entities/subscription_entity.dart';
|
|
1565
|
+
import 'package:${pkg}/features/subscription/api/subscription_api.dart';
|
|
1566
|
+
// No-op subscription API fake. Run: kasy add revenuecat to activate.
|
|
1567
|
+
class SubscriptionApiFake implements SubscriptionApi {
|
|
1568
|
+
SubscriptionEntity? currentFake;
|
|
1569
|
+
|
|
1570
|
+
@override
|
|
1571
|
+
Future<SubscriptionEntity?> get(String userId) async => currentFake;
|
|
1572
|
+
}
|
|
1573
|
+
`,
|
|
1574
|
+
'utf8',
|
|
1575
|
+
);
|
|
1576
|
+
|
|
1577
|
+
// 10. Restore fake_inapp_subscription_api.dart without purchases_flutter dependency
|
|
1578
|
+
await fs.outputFile(
|
|
1579
|
+
path.join(projectDir, 'test', 'features', 'subscription', 'api', 'fake_inapp_subscription_api.dart'),
|
|
1580
|
+
`import 'package:${pkg}/features/subscription/api/inapp_subscription_api.dart';
|
|
1581
|
+
// No-op in-app subscription API fake. Run: kasy add revenuecat to activate.
|
|
1582
|
+
class InAppSubscriptionApiFake extends RevenueCatPaymentApi {}
|
|
1583
|
+
`,
|
|
1584
|
+
'utf8',
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
/**
|
|
1589
|
+
* Remove DEVELOPMENT_TEAM entries from the generated iOS project.pbxproj.
|
|
1590
|
+
* Clients use their own Apple team — leaving the internal team ID would cause
|
|
1591
|
+
* signing failures for any account that doesn't belong to AICRUS.
|
|
1592
|
+
*
|
|
1593
|
+
* @param {string} projectDir
|
|
1594
|
+
*/
|
|
1595
|
+
async function removeDevelopmentTeam(projectDir) {
|
|
1596
|
+
const pbxprojPath = path.join(projectDir, 'ios', 'Runner.xcodeproj', 'project.pbxproj');
|
|
1597
|
+
if (!(await fs.pathExists(pbxprojPath))) return;
|
|
1598
|
+
let content = await fs.readFile(pbxprojPath, 'utf8');
|
|
1599
|
+
content = content.replace(/^\s*DEVELOPMENT_TEAM = [A-Z0-9]+;\n/gm, '');
|
|
1600
|
+
await fs.outputFile(pbxprojPath, content, 'utf8');
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
const RELEASE_DOC_BASES = ['ios-release', 'codemagic-release', 'auth-setup'];
|
|
1604
|
+
const RELEASE_DOC_LANGS = ['pt', 'en', 'es'];
|
|
1605
|
+
|
|
1606
|
+
/**
|
|
1607
|
+
* Deliver a single localized release doc per topic (no pt/en/es duplicates).
|
|
1608
|
+
* Source templates in the kit repo use `{base}.{lang}.md`; projects receive `{base}.md`.
|
|
1609
|
+
*
|
|
1610
|
+
* @param {string} projectDir
|
|
1611
|
+
* @param {string} [language='en'] - CLI language (en, pt, es)
|
|
1612
|
+
*/
|
|
1613
|
+
async function localizeReleaseDocs(projectDir, language = 'en') {
|
|
1614
|
+
const lang = RELEASE_DOC_LANGS.includes(language) ? language : 'en';
|
|
1615
|
+
const docsDir = path.join(projectDir, 'docs');
|
|
1616
|
+
if (!(await fs.pathExists(docsDir))) return;
|
|
1617
|
+
|
|
1618
|
+
for (const base of RELEASE_DOC_BASES) {
|
|
1619
|
+
const sourcePath = path.join(docsDir, `${base}.${lang}.md`);
|
|
1620
|
+
const targetPath = path.join(docsDir, `${base}.md`);
|
|
1621
|
+
if (!(await fs.pathExists(sourcePath))) continue;
|
|
1622
|
+
|
|
1623
|
+
let content = await fs.readFile(sourcePath, 'utf8');
|
|
1624
|
+
content = content
|
|
1625
|
+
.replace(/ios-release\.(?:pt|en|es)\.md/g, 'ios-release.md')
|
|
1626
|
+
.replace(/codemagic-release\.(?:pt|en|es)\.md/g, 'codemagic-release.md')
|
|
1627
|
+
.replace(/auth-setup\.(?:pt|en|es)\.md/g, 'auth-setup.md');
|
|
1628
|
+
await fs.outputFile(targetPath, content, 'utf8');
|
|
1629
|
+
|
|
1630
|
+
for (const l of RELEASE_DOC_LANGS) {
|
|
1631
|
+
const localized = path.join(docsDir, `${base}.${l}.md`);
|
|
1632
|
+
if (await fs.pathExists(localized)) await fs.remove(localized);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
module.exports = {
|
|
1638
|
+
BACKENDS,
|
|
1639
|
+
buildDartDefines,
|
|
1640
|
+
writeRouter,
|
|
1641
|
+
writeVsCodeLaunch,
|
|
1642
|
+
writeEnvExample,
|
|
1643
|
+
writeEnvFileIfMissing,
|
|
1644
|
+
writeFirebaserc,
|
|
1645
|
+
writeEnvironnementsOverrides,
|
|
1646
|
+
writeFeaturesConfig,
|
|
1647
|
+
writeKitSetup,
|
|
1648
|
+
writeMakefile,
|
|
1649
|
+
writeNoOpAnalyticsApi,
|
|
1650
|
+
writeNoOpTrackingApi,
|
|
1651
|
+
writeMainDart,
|
|
1652
|
+
removeModuleDirs,
|
|
1653
|
+
removeAndroidWidgetArtifacts,
|
|
1654
|
+
removeFacebookSigninFromAuthPages,
|
|
1655
|
+
removeAndroidFacebookMetadata,
|
|
1656
|
+
writeNoOpAdminHomeWidgets,
|
|
1657
|
+
writeNoOpFeatureRequestRepository,
|
|
1658
|
+
writeNoOpSubscriptionStubs,
|
|
1659
|
+
writeNoOpSentryUsages,
|
|
1660
|
+
stripPubspecDeps,
|
|
1661
|
+
addPubspecDep,
|
|
1662
|
+
removeDevelopmentTeam,
|
|
1663
|
+
localizeReleaseDocs,
|
|
1664
|
+
};
|