kasy-cli 1.21.8 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commands/add.js +93 -80
- package/lib/commands/configure.js +100 -32
- package/lib/commands/doctor.js +28 -2
- package/lib/commands/new.js +86 -38
- package/lib/commands/notifications.js +1 -1
- package/lib/commands/remove.js +43 -15
- package/lib/commands/run.js +2 -2
- package/lib/commands/update.js +2 -2
- package/lib/scaffold/CHANGELOG.json +14 -0
- package/lib/scaffold/backends/api/generator.js +14 -14
- package/lib/scaffold/backends/api/patch/README.md +83 -0
- package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
- package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +5 -0
- package/lib/scaffold/backends/api/patch/lib/{environnements.dart → environments.dart} +3 -11
- package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_api.dart +108 -0
- package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +51 -0
- package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +5 -5
- package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +71 -38
- package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +2 -2
- package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +54 -0
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/models/user_info.dart +16 -16
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
- package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
- package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +100 -0
- package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
- package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/stripe_backend_api.dart +60 -0
- package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
- package/lib/scaffold/backends/api/pubspec.yaml.tpl +4 -5
- package/lib/scaffold/backends/firebase/deploy.js +87 -13
- package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +14 -6
- package/lib/scaffold/backends/firebase/generator.js +5 -5
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +69 -45
- package/lib/scaffold/backends/firebase/tokens.js +4 -4
- package/lib/scaffold/backends/supabase/deploy.js +63 -11
- package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +149 -0
- package/lib/scaffold/backends/supabase/edge-functions/{llm-chat → ai-chat}/index.ts +19 -19
- package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/index.ts +2 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-create-checkout-session/index.ts +123 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-create-portal-session/index.ts +97 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-list-prices/index.ts +83 -0
- package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +138 -0
- package/lib/scaffold/backends/supabase/generator.js +17 -17
- package/lib/scaffold/backends/supabase/migrations/20240101000009_ai_messages.sql +50 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000012_stripe_customers.sql +36 -0
- package/lib/scaffold/backends/supabase/migrations/20240101000013_admin_role.sql +62 -0
- package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +4 -0
- package/lib/scaffold/backends/supabase/patch/lib/{environnements.dart → environments.dart} +3 -13
- package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_api.dart +95 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +52 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
- package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +63 -35
- package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +1 -1
- package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart +46 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/models/user_info.dart +16 -16
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
- package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
- package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +93 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/stripe_backend_api.dart +52 -0
- package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
- package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +4 -5
- package/lib/scaffold/backends/supabase/tokens.js +3 -3
- package/lib/scaffold/catalog.js +9 -11
- package/lib/scaffold/generate.js +45 -31
- package/lib/scaffold/shared/generator-utils.js +188 -81
- package/lib/scaffold/shared/sort-imports.js +191 -0
- package/lib/scaffold/shared/template-strings.js +3 -3
- package/lib/utils/checks.js +2 -2
- package/lib/utils/i18n/messages-en.js +50 -35
- package/lib/utils/i18n/messages-es.js +50 -35
- package/lib/utils/i18n/messages-pt.js +52 -37
- package/lib/utils/updates.js +15 -15
- package/package.json +1 -1
- package/templates/firebase/.env.example +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.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/values-night-v31/styles.xml +1 -1
- package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
- package/templates/firebase/assets/images/logo_wordmark_dark.png +0 -0
- package/templates/firebase/assets/images/logo_wordmark_light.png +0 -0
- package/templates/firebase/docs/revenuecat-setup.es.md +1 -1
- package/templates/firebase/docs/revenuecat-setup.pt.md +1 -1
- package/templates/firebase/firestore.rules +24 -5
- package/templates/firebase/functions/package-lock.json +22 -1
- package/templates/firebase/functions/package.json +2 -1
- package/templates/firebase/functions/src/admin/functions.ts +113 -0
- package/templates/firebase/functions/src/{llm_chat → ai_chat}/index.ts +16 -16
- package/templates/firebase/functions/src/index.ts +8 -2
- package/templates/firebase/functions/src/notifications/device_triggers.ts +2 -2
- package/templates/firebase/functions/src/notifications/triggers.ts +3 -3
- package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +2 -0
- package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +222 -0
- package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +2 -2
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
- package/templates/firebase/lib/components/components.dart +4 -1
- package/templates/firebase/lib/components/kasy_app_bar.dart +22 -7
- package/templates/firebase/lib/components/kasy_avatar.dart +7 -6
- package/templates/firebase/lib/components/kasy_button.dart +23 -99
- package/templates/firebase/lib/components/kasy_dialog.dart +11 -11
- package/templates/firebase/lib/components/kasy_image_viewer.dart +84 -0
- package/templates/firebase/lib/components/{kasy_sidebar_pro.dart → kasy_sidebar.dart} +692 -425
- package/templates/firebase/lib/components/kasy_status_tag.dart +91 -0
- package/templates/firebase/lib/components/kasy_text_area.dart +1 -3
- package/templates/firebase/lib/components/kasy_text_field.dart +5 -6
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -3
- package/templates/firebase/lib/components/kasy_toast.dart +2 -2
- package/templates/firebase/lib/components/kasy_web_header.dart +209 -0
- package/templates/firebase/lib/core/ads/ads_provider.dart +1 -1
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +57 -4
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +19 -91
- package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +196 -96
- package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +41 -0
- package/templates/firebase/lib/core/config/app_env.dart +5 -11
- package/templates/firebase/lib/core/config/features.dart +5 -4
- package/templates/firebase/lib/core/data/api/analytics_api.dart +1 -1
- package/templates/firebase/lib/core/data/api/http_client.dart +1 -1
- package/templates/firebase/lib/core/data/entities/user_entity.dart +3 -0
- package/templates/firebase/lib/core/data/models/entitlement.dart +35 -0
- package/templates/firebase/lib/core/data/models/subscription.dart +13 -186
- package/templates/firebase/lib/core/data/models/user.dart +11 -0
- package/templates/firebase/lib/core/data/repositories/user_repository.dart +1 -1
- package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +1 -1
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +1 -1
- package/templates/firebase/lib/core/icons/kasy_icons.dart +31 -1
- package/templates/firebase/lib/core/rating/api/rating_api.dart +2 -2
- package/templates/firebase/lib/core/states/logout_action.dart +25 -0
- package/templates/firebase/lib/core/states/user_state_notifier.dart +3 -3
- package/templates/firebase/lib/core/theme/colors.dart +488 -188
- package/templates/firebase/lib/core/theme/radius.dart +22 -11
- package/templates/firebase/lib/core/theme/shadows.dart +66 -0
- package/templates/firebase/lib/core/theme/texts.dart +75 -41
- package/templates/firebase/lib/core/theme/universal_theme.dart +9 -4
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +5 -4
- package/templates/firebase/lib/core/web_viewport_scale.dart +52 -0
- package/templates/firebase/lib/core/widgets/kasy_brand_badge.dart +118 -0
- package/templates/firebase/lib/core/widgets/kasy_brand_logo.dart +40 -0
- package/templates/firebase/lib/core/widgets/kasy_focus_ring.dart +125 -0
- package/templates/firebase/lib/core/widgets/kasy_hover.dart +33 -13
- package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +18 -13
- package/templates/firebase/lib/{environnements.dart → environments.dart} +3 -14
- package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +159 -0
- package/templates/firebase/lib/features/ai_chat/api/ai_chat_api.dart +107 -0
- package/templates/firebase/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +64 -0
- package/templates/firebase/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
- package/templates/firebase/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +73 -38
- package/templates/firebase/lib/features/ai_chat/providers/ai_conversations_notifier.dart +103 -0
- package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_avatars.dart → ai_chat/ui/widgets/ai_chat_avatars.dart} +13 -13
- package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_composer.dart → ai_chat/ui/widgets/ai_chat_composer.dart} +10 -10
- package/templates/firebase/lib/features/{llm_chat/llm_chat_page.dart → ai_chat/ui/widgets/ai_chat_conversation_view.dart} +80 -67
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +285 -0
- package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +163 -0
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +52 -13
- package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher.dart +12 -0
- package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher_web.dart +35 -0
- package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +108 -68
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +38 -51
- package/templates/firebase/lib/features/authentication/ui/signup_page.dart +38 -51
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +118 -0
- package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +61 -44
- package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +32 -0
- package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +5 -5
- package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +2 -2
- package/templates/firebase/lib/features/home/design_system_page.dart +808 -170
- package/templates/firebase/lib/features/home/home_components_page.dart +6 -3
- package/templates/firebase/lib/features/home/home_components_preview_page.dart +6 -6
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +325 -186
- package/templates/firebase/lib/features/home/home_feed.dart +289 -0
- package/templates/firebase/lib/features/home/home_image_grid.dart +355 -0
- package/templates/firebase/lib/features/home/home_page.dart +11 -250
- package/templates/firebase/lib/features/{local_reminder → local_reminders}/providers/reminder_notifier.dart +1 -1
- package/templates/firebase/lib/features/{local_reminder → local_reminders}/ui/reminder_page.dart +2 -2
- package/templates/firebase/lib/features/notifications/shared/att_permission.dart +1 -1
- package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +25 -61
- package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +117 -0
- package/templates/firebase/lib/features/onboarding/models/user_info.dart +16 -16
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +7 -9
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +71 -48
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +3 -2
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +5 -5
- package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +4 -4
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +4 -2
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +39 -121
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +105 -70
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +639 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +62 -50
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_step_header.dart +75 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +16 -5
- package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +26 -22
- package/templates/firebase/lib/features/settings/settings_page.dart +601 -90
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +1193 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +1 -1
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +2 -3
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +86 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +1215 -0
- package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +236 -0
- package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +3 -3
- package/templates/firebase/lib/features/settings/ui/widgets/kasy_user_avatar.dart +77 -0
- package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +1 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +17 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/api/inapp_subscription_api.dart +67 -46
- package/templates/firebase/lib/features/subscriptions/api/revenuecat_product.dart +189 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +55 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +82 -0
- package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +178 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
- package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api.dart +53 -0
- package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api_provider.dart +21 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/providers/premium_page_provider.dart +9 -6
- package/templates/firebase/lib/features/{subscription → subscriptions}/repositories/subscription_repository.dart +26 -30
- package/{lib/scaffold/backends/supabase/patch/lib/features/subscription → templates/firebase/lib/features/subscriptions}/shared/maybeshow_premium.dart +0 -2
- package/templates/firebase/lib/features/subscriptions/shared/subscription_management.dart +45 -0
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/active_premium_content.dart +28 -4
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_minimal.dart +7 -7
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_row.dart +8 -8
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_with_switch.dart +7 -7
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_content.dart +7 -7
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_page_factory.dart +5 -5
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/premium_page.dart +5 -5
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/comparison_table.dart +1 -1
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/paywall_empty_state.dart +4 -4
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_bottom_menu.dart +3 -4
- package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_col.dart +1 -1
- package/templates/firebase/lib/i18n/en.i18n.json +171 -46
- package/templates/firebase/lib/i18n/es.i18n.json +175 -50
- package/templates/firebase/lib/i18n/pt.i18n.json +166 -41
- package/templates/firebase/lib/main.dart +6 -3
- package/templates/firebase/lib/router.dart +15 -23
- package/templates/firebase/pubspec.yaml +4 -5
- package/templates/firebase/test/core/data/repositories/user_repository_test.dart +3 -3
- package/templates/firebase/test/core/states/user_state_notifier_test.dart +4 -4
- package/templates/firebase/test/core/widgets/focus_ring_shape_test.dart +55 -0
- package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_inapp_subscription_api.dart +11 -4
- package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_revenuecat_product.dart +1 -0
- package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_subscription_api.dart +2 -2
- package/templates/firebase/test/features/{subscription → subscriptions}/subscription_page_test.dart +4 -4
- package/templates/firebase/test/test_utils.dart +6 -6
- package/templates/firebase/web/index.html +5 -2
- package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -58
- package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -86
- package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +0 -22
- package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -47
- package/templates/firebase/assets/images/onboarding/authentication-login-template.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/purchase.png +0 -0
- package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +0 -2021
- package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +0 -35
- package/templates/firebase/lib/features/home/home_features_page.dart +0 -207
- package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +0 -50
- package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +0 -316
- package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +0 -85
- /package/templates/firebase/lib/features/{local_reminder → local_reminders}/repositories/reminder_preferences.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/providers/models/premium_state.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/feature_line.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background_gradient.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_banner.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_card.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_close_button.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_feature.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_row.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/trial_switcher.dart +0 -0
- /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/unsubscribe_feedback_popup.dart +0 -0
|
@@ -16,6 +16,10 @@ sealed class UserEntity with _$UserEntity {
|
|
|
16
16
|
@JsonKey(name: 'avatar_url') String? avatarPath,
|
|
17
17
|
bool? onboarded,
|
|
18
18
|
String? locale,
|
|
19
|
+
// Access-control role. null/absent = normal user; "admin" unlocks the admin
|
|
20
|
+
// console's server data. Server-only: the `users` guard trigger blocks the
|
|
21
|
+
// client from writing it (set it from the Supabase dashboard / SQL editor).
|
|
22
|
+
String? role,
|
|
19
23
|
}) = UserEntityData;
|
|
20
24
|
|
|
21
25
|
const UserEntity._();
|
|
@@ -6,7 +6,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|
|
6
6
|
import 'package:kasy_kit/core/config/app_env.dart';
|
|
7
7
|
import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
8
8
|
|
|
9
|
-
part '
|
|
9
|
+
part 'environments.freezed.dart';
|
|
10
10
|
|
|
11
11
|
// URLs for terms of service and privacy policy
|
|
12
12
|
const kTermsUrl = '';
|
|
@@ -38,10 +38,6 @@ sealed class Environment with _$Environment {
|
|
|
38
38
|
/// (only if you want to use in-app purchases with RevenueCat)
|
|
39
39
|
String? revenueCatIOSApiKey,
|
|
40
40
|
|
|
41
|
-
/// RevenueCat Web Billing API key (rcb_xxx or rcb_sb_xxx)
|
|
42
|
-
/// (only if you want to use subscriptions on web)
|
|
43
|
-
String? revenueCatWebApiKey,
|
|
44
|
-
|
|
45
41
|
/// this is used to open the app store page of your app for reviews
|
|
46
42
|
String? appStoreId,
|
|
47
43
|
|
|
@@ -74,10 +70,6 @@ sealed class Environment with _$Environment {
|
|
|
74
70
|
/// (only if you want to use in-app purchases with RevenueCat)
|
|
75
71
|
String? revenueCatIOSApiKey,
|
|
76
72
|
|
|
77
|
-
/// RevenueCat Web Billing API key (rcb_xxx or rcb_sb_xxx)
|
|
78
|
-
/// (only if you want to use subscriptions on web)
|
|
79
|
-
String? revenueCatWebApiKey,
|
|
80
|
-
|
|
81
73
|
/// only if you want to use ads
|
|
82
74
|
String? androidInterstitialAdUnitId,
|
|
83
75
|
|
|
@@ -114,7 +106,6 @@ sealed class Environment with _$Environment {
|
|
|
114
106
|
appStoreId: '',
|
|
115
107
|
revenueCatAndroidApiKey: AppEnv.rcAndroidApiKey,
|
|
116
108
|
revenueCatIOSApiKey: AppEnv.rcIosApiKey,
|
|
117
|
-
revenueCatWebApiKey: AppEnv.rcWebApiKey,
|
|
118
109
|
mixpanelToken: AppEnv.mixpanelToken,
|
|
119
110
|
authenticationMode: AuthenticationMode.anonymous,
|
|
120
111
|
);
|
|
@@ -125,7 +116,6 @@ sealed class Environment with _$Environment {
|
|
|
125
116
|
appStoreId: AppEnv.appStoreId,
|
|
126
117
|
revenueCatAndroidApiKey: AppEnv.rcAndroidApiKey,
|
|
127
118
|
revenueCatIOSApiKey: AppEnv.rcIosApiKey,
|
|
128
|
-
revenueCatWebApiKey: AppEnv.rcWebApiKey,
|
|
129
119
|
sentryDsn: AppEnv.sentryDsn,
|
|
130
120
|
mixpanelToken: AppEnv.mixpanelToken,
|
|
131
121
|
authenticationMode: AuthenticationMode.anonymous,
|
|
@@ -140,8 +130,8 @@ sealed class Environment with _$Environment {
|
|
|
140
130
|
revenueCatIOSApiKey != null && revenueCatIOSApiKey!.isNotEmpty,
|
|
141
131
|
TargetPlatform.android =>
|
|
142
132
|
revenueCatAndroidApiKey != null && revenueCatAndroidApiKey!.isNotEmpty,
|
|
143
|
-
|
|
144
|
-
|
|
133
|
+
// RevenueCat is mobile-only; web/desktop are never RevenueCat-configured.
|
|
134
|
+
_ => false,
|
|
145
135
|
};
|
|
146
136
|
}
|
|
147
137
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
2
|
+
import 'package:logger/logger.dart';
|
|
3
|
+
import 'package:kasy_kit/features/ai_chat/api/ai_chat_conversation_entity.dart';
|
|
4
|
+
import 'package:kasy_kit/features/ai_chat/api/ai_chat_message_entity.dart';
|
|
5
|
+
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
6
|
+
|
|
7
|
+
final aiChatApiProvider = Provider<AiChatApi>(
|
|
8
|
+
(ref) => AiChatApi(client: Supabase.instance.client),
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const _kConversationsTable = 'ai_conversations';
|
|
12
|
+
const _kMessagesTable = 'ai_messages';
|
|
13
|
+
|
|
14
|
+
/// One user has many conversations (public.ai_conversations), each with many
|
|
15
|
+
/// messages (public.ai_messages, linked by conversation_id).
|
|
16
|
+
class AiChatApi {
|
|
17
|
+
final SupabaseClient _client;
|
|
18
|
+
final Logger _logger = Logger();
|
|
19
|
+
|
|
20
|
+
AiChatApi({required SupabaseClient client}) : _client = client;
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Conversations
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/// Returns the user's conversations, most recently updated first.
|
|
27
|
+
Future<List<AiChatConversationEntity>> loadConversations(
|
|
28
|
+
String userId,
|
|
29
|
+
) async {
|
|
30
|
+
final rows = await _client
|
|
31
|
+
.from(_kConversationsTable)
|
|
32
|
+
.select()
|
|
33
|
+
.eq('user_id', userId)
|
|
34
|
+
.order('updated_at', ascending: false);
|
|
35
|
+
return rows.map(AiChatConversationEntity.fromJson).toList();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Creates an empty conversation and returns it (with its generated id).
|
|
39
|
+
Future<AiChatConversationEntity> createConversation(String userId) async {
|
|
40
|
+
final row = await _client
|
|
41
|
+
.from(_kConversationsTable)
|
|
42
|
+
.insert({'user_id': userId})
|
|
43
|
+
.select()
|
|
44
|
+
.single();
|
|
45
|
+
return AiChatConversationEntity.fromJson(row);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Deletes a conversation; its messages cascade via the foreign key.
|
|
49
|
+
Future<void> deleteConversation(String userId, String conversationId) async {
|
|
50
|
+
await _client.from(_kConversationsTable).delete().eq('id', conversationId);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Messages
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
/// Returns all messages in a conversation, oldest first.
|
|
58
|
+
Future<List<AiChatMessageEntity>> loadMessages(
|
|
59
|
+
String userId,
|
|
60
|
+
String conversationId,
|
|
61
|
+
) async {
|
|
62
|
+
final rows = await _client
|
|
63
|
+
.from(_kMessagesTable)
|
|
64
|
+
.select()
|
|
65
|
+
.eq('conversation_id', conversationId)
|
|
66
|
+
.order('created_at', ascending: true);
|
|
67
|
+
return rows.map(AiChatMessageEntity.fromJson).toList();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Persists a message and denormalizes it onto the parent conversation
|
|
71
|
+
/// (last message preview + updated_at) so the list stays cheap to render.
|
|
72
|
+
Future<void> saveMessage(
|
|
73
|
+
String userId,
|
|
74
|
+
String conversationId,
|
|
75
|
+
AiChatMessageEntity message,
|
|
76
|
+
) async {
|
|
77
|
+
try {
|
|
78
|
+
await _client.from(_kMessagesTable).insert({
|
|
79
|
+
'user_id': userId,
|
|
80
|
+
'conversation_id': conversationId,
|
|
81
|
+
...message.toJson(),
|
|
82
|
+
});
|
|
83
|
+
await _client
|
|
84
|
+
.from(_kConversationsTable)
|
|
85
|
+
.update({
|
|
86
|
+
'updated_at': message.createdAt.toUtc().toIso8601String(),
|
|
87
|
+
'last_message_role': message.role,
|
|
88
|
+
'last_message_content': message.content,
|
|
89
|
+
})
|
|
90
|
+
.eq('id', conversationId);
|
|
91
|
+
} catch (e) {
|
|
92
|
+
_logger.e('Failed to persist AI message: $e');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/// Supabase row mapping for a single AI chat conversation.
|
|
2
|
+
/// Table: public.ai_conversations
|
|
3
|
+
///
|
|
4
|
+
/// The last message is denormalized onto the conversation so the list can be
|
|
5
|
+
/// rendered with a single query (no need to read each conversation's messages
|
|
6
|
+
/// just to show a preview + timestamp).
|
|
7
|
+
class AiChatConversationEntity {
|
|
8
|
+
final String id;
|
|
9
|
+
final DateTime createdAt;
|
|
10
|
+
final DateTime updatedAt;
|
|
11
|
+
|
|
12
|
+
/// Role of the most recent message ('user' or 'assistant'), or null when the
|
|
13
|
+
/// conversation has no messages yet.
|
|
14
|
+
final String? lastMessageRole;
|
|
15
|
+
|
|
16
|
+
/// Content of the most recent message, or null when empty.
|
|
17
|
+
final String? lastMessageContent;
|
|
18
|
+
|
|
19
|
+
const AiChatConversationEntity({
|
|
20
|
+
required this.id,
|
|
21
|
+
required this.createdAt,
|
|
22
|
+
required this.updatedAt,
|
|
23
|
+
this.lastMessageRole,
|
|
24
|
+
this.lastMessageContent,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
bool get isEmpty => lastMessageContent == null;
|
|
28
|
+
|
|
29
|
+
AiChatConversationEntity copyWith({
|
|
30
|
+
DateTime? updatedAt,
|
|
31
|
+
String? lastMessageRole,
|
|
32
|
+
String? lastMessageContent,
|
|
33
|
+
}) {
|
|
34
|
+
return AiChatConversationEntity(
|
|
35
|
+
id: id,
|
|
36
|
+
createdAt: createdAt,
|
|
37
|
+
updatedAt: updatedAt ?? this.updatedAt,
|
|
38
|
+
lastMessageRole: lastMessageRole ?? this.lastMessageRole,
|
|
39
|
+
lastMessageContent: lastMessageContent ?? this.lastMessageContent,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
factory AiChatConversationEntity.fromJson(Map<String, dynamic> json) {
|
|
44
|
+
return AiChatConversationEntity(
|
|
45
|
+
id: json['id'] as String,
|
|
46
|
+
createdAt: DateTime.parse(json['created_at'] as String),
|
|
47
|
+
updatedAt: DateTime.parse(json['updated_at'] as String),
|
|
48
|
+
lastMessageRole: json['last_message_role'] as String?,
|
|
49
|
+
lastMessageContent: json['last_message_content'] as String?,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
/// Supabase row mapping for a single
|
|
2
|
-
/// Table: public.
|
|
3
|
-
class
|
|
1
|
+
/// Supabase row mapping for a single AI chat message.
|
|
2
|
+
/// Table: public.ai_messages
|
|
3
|
+
class AiChatMessageEntity {
|
|
4
4
|
final String? id;
|
|
5
5
|
final String role; // 'user' or 'assistant'
|
|
6
6
|
final String content;
|
|
7
7
|
final DateTime createdAt;
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
const AiChatMessageEntity({
|
|
10
10
|
this.id,
|
|
11
11
|
required this.role,
|
|
12
12
|
required this.content,
|
|
13
13
|
required this.createdAt,
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
factory
|
|
17
|
-
return
|
|
16
|
+
factory AiChatMessageEntity.fromJson(Map<String, dynamic> json) {
|
|
17
|
+
return AiChatMessageEntity(
|
|
18
18
|
id: json['id'] as String?,
|
|
19
19
|
role: json['role'] as String,
|
|
20
20
|
content: json['content'] as String,
|
|
@@ -5,8 +5,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
5
5
|
import 'package:kasy_kit/core/config/app_env.dart';
|
|
6
6
|
import 'package:kasy_kit/core/states/translations.dart';
|
|
7
7
|
import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
8
|
-
import 'package:kasy_kit/features/
|
|
9
|
-
import 'package:kasy_kit/features/
|
|
8
|
+
import 'package:kasy_kit/features/ai_chat/api/ai_chat_api.dart';
|
|
9
|
+
import 'package:kasy_kit/features/ai_chat/api/ai_chat_message_entity.dart';
|
|
10
|
+
import 'package:kasy_kit/features/ai_chat/providers/ai_conversations_notifier.dart';
|
|
10
11
|
import 'package:logger/logger.dart';
|
|
11
12
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
12
13
|
|
|
@@ -24,33 +25,33 @@ class ChatMessage {
|
|
|
24
25
|
factory ChatMessage.assistant(String content) =>
|
|
25
26
|
ChatMessage(role: 'assistant', content: content);
|
|
26
27
|
|
|
27
|
-
factory ChatMessage.fromEntity(
|
|
28
|
+
factory ChatMessage.fromEntity(AiChatMessageEntity entity) =>
|
|
28
29
|
ChatMessage(role: entity.role, content: entity.content);
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
AiChatMessageEntity toEntity() => AiChatMessageEntity(
|
|
31
32
|
role: role,
|
|
32
33
|
content: content,
|
|
33
34
|
createdAt: DateTime.now(),
|
|
34
35
|
);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
class
|
|
38
|
+
class AiChatState {
|
|
38
39
|
final List<ChatMessage> messages;
|
|
39
40
|
final bool isReplying;
|
|
40
41
|
final bool streamingStarted;
|
|
41
42
|
|
|
42
|
-
const
|
|
43
|
+
const AiChatState({
|
|
43
44
|
required this.messages,
|
|
44
45
|
this.isReplying = false,
|
|
45
46
|
this.streamingStarted = false,
|
|
46
47
|
});
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
AiChatState copyWith({
|
|
49
50
|
List<ChatMessage>? messages,
|
|
50
51
|
bool? isReplying,
|
|
51
52
|
bool? streamingStarted,
|
|
52
53
|
}) {
|
|
53
|
-
return
|
|
54
|
+
return AiChatState(
|
|
54
55
|
messages: messages ?? this.messages,
|
|
55
56
|
isReplying: isReplying ?? this.isReplying,
|
|
56
57
|
streamingStarted: streamingStarted ?? this.streamingStarted,
|
|
@@ -58,34 +59,46 @@ class LlmChatState {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
final
|
|
62
|
-
AsyncNotifierProvider<
|
|
62
|
+
final aiChatNotifierProvider =
|
|
63
|
+
AsyncNotifierProvider<AiChatNotifier, AiChatState>(AiChatNotifier.new);
|
|
63
64
|
|
|
64
|
-
class
|
|
65
|
+
class AiChatNotifier extends AsyncNotifier<AiChatState> {
|
|
65
66
|
final Logger _logger = Logger();
|
|
66
67
|
|
|
68
|
+
String? _streamingConversationId;
|
|
69
|
+
|
|
70
|
+
bool get _streamStillActive =>
|
|
71
|
+
ref.read(selectedConversationIdProvider) == _streamingConversationId;
|
|
72
|
+
|
|
67
73
|
@override
|
|
68
|
-
Future<
|
|
74
|
+
Future<AiChatState> build() async {
|
|
75
|
+
final conversationId = ref.watch(selectedConversationIdProvider);
|
|
69
76
|
final userId = ref.read(userStateNotifierProvider).user.idOrNull;
|
|
70
|
-
if (
|
|
77
|
+
if (conversationId == null || userId == null) {
|
|
78
|
+
return const AiChatState(messages: []);
|
|
79
|
+
}
|
|
71
80
|
|
|
72
81
|
try {
|
|
73
|
-
final entities = await ref
|
|
74
|
-
|
|
82
|
+
final entities = await ref
|
|
83
|
+
.read(aiChatApiProvider)
|
|
84
|
+
.loadMessages(userId, conversationId);
|
|
85
|
+
return AiChatState(
|
|
75
86
|
messages: entities.map(ChatMessage.fromEntity).toList(),
|
|
76
87
|
);
|
|
77
88
|
} catch (e) {
|
|
78
|
-
_logger.e('Failed to load
|
|
79
|
-
return const
|
|
89
|
+
_logger.e('Failed to load AI chat history: $e');
|
|
90
|
+
return const AiChatState(messages: []);
|
|
80
91
|
}
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
Future<void> sendMessage(String prompt) async {
|
|
95
|
+
final conversationId = ref.read(selectedConversationIdProvider);
|
|
84
96
|
final current = switch (state) {
|
|
85
97
|
AsyncData(:final value) => value,
|
|
86
98
|
_ => null,
|
|
87
99
|
};
|
|
88
|
-
if (current == null || current.isReplying) return;
|
|
100
|
+
if (conversationId == null || current == null || current.isReplying) return;
|
|
101
|
+
_streamingConversationId = conversationId;
|
|
89
102
|
|
|
90
103
|
final userMsg = ChatMessage.user(prompt);
|
|
91
104
|
state = AsyncData(
|
|
@@ -96,20 +109,23 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
|
|
|
96
109
|
),
|
|
97
110
|
);
|
|
98
111
|
|
|
99
|
-
_persistMessage(userMsg);
|
|
112
|
+
_persistMessage(userMsg, conversationId);
|
|
100
113
|
|
|
101
|
-
final allMessages = (state as AsyncData<
|
|
114
|
+
final allMessages = (state as AsyncData<AiChatState>).value.messages;
|
|
102
115
|
final history = allMessages.length > _kMaxContextMessages
|
|
103
116
|
? allMessages.sublist(allMessages.length - _kMaxContextMessages)
|
|
104
117
|
: allMessages;
|
|
105
|
-
await _requestReplyStream(history);
|
|
118
|
+
await _requestReplyStream(history, conversationId);
|
|
106
119
|
}
|
|
107
120
|
|
|
108
|
-
Future<void> _requestReplyStream(
|
|
109
|
-
|
|
110
|
-
|
|
121
|
+
Future<void> _requestReplyStream(
|
|
122
|
+
List<ChatMessage> history,
|
|
123
|
+
String conversationId,
|
|
124
|
+
) async {
|
|
125
|
+
final t = ref.read(translationsProvider).ai_chat;
|
|
126
|
+
final String aiChatEndpoint = AppEnv.aiChatEndpoint;
|
|
111
127
|
|
|
112
|
-
if (
|
|
128
|
+
if (aiChatEndpoint.isEmpty) {
|
|
113
129
|
_finalizeAssistantMessage(t.error_not_configured);
|
|
114
130
|
return;
|
|
115
131
|
}
|
|
@@ -132,7 +148,7 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
|
|
|
132
148
|
|
|
133
149
|
try {
|
|
134
150
|
final response = await dio.post<ResponseBody>(
|
|
135
|
-
|
|
151
|
+
aiChatEndpoint,
|
|
136
152
|
data: {
|
|
137
153
|
'message': history.last.content,
|
|
138
154
|
'history': history
|
|
@@ -170,7 +186,7 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
|
|
|
170
186
|
|
|
171
187
|
final fullContent = contentBuffer.toString();
|
|
172
188
|
if (fullContent.isNotEmpty) {
|
|
173
|
-
_persistMessage(ChatMessage.assistant(fullContent));
|
|
189
|
+
_persistMessage(ChatMessage.assistant(fullContent), conversationId);
|
|
174
190
|
} else {
|
|
175
191
|
_finalizeAssistantMessage(t.error_no_reply);
|
|
176
192
|
return;
|
|
@@ -180,18 +196,19 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
|
|
|
180
196
|
AsyncData(:final value) => value,
|
|
181
197
|
_ => null,
|
|
182
198
|
};
|
|
183
|
-
if (latest != null) {
|
|
199
|
+
if (latest != null && _streamStillActive) {
|
|
184
200
|
state = AsyncData(
|
|
185
201
|
latest.copyWith(isReplying: false, streamingStarted: false),
|
|
186
202
|
);
|
|
187
203
|
}
|
|
188
204
|
} catch (error) {
|
|
189
|
-
_logger.e('
|
|
205
|
+
_logger.e('AI chat stream failed: $error');
|
|
190
206
|
_finalizeAssistantMessage(t.error_network);
|
|
191
207
|
}
|
|
192
208
|
}
|
|
193
209
|
|
|
194
210
|
void _appendStreamingChunk(String partialContent) {
|
|
211
|
+
if (!_streamStillActive) return;
|
|
195
212
|
final current = switch (state) {
|
|
196
213
|
AsyncData(:final value) => value,
|
|
197
214
|
_ => null,
|
|
@@ -207,20 +224,21 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
|
|
|
207
224
|
: [...msgs, ChatMessage.assistant(partialContent)];
|
|
208
225
|
|
|
209
226
|
state = AsyncData(
|
|
210
|
-
|
|
227
|
+
AiChatState(messages: newMsgs, isReplying: true, streamingStarted: true),
|
|
211
228
|
);
|
|
212
229
|
}
|
|
213
230
|
|
|
214
231
|
void _finalizeAssistantMessage(String content) {
|
|
232
|
+
if (!_streamStillActive) return;
|
|
215
233
|
final current = switch (state) {
|
|
216
234
|
AsyncData(:final value) => value,
|
|
217
|
-
_ => const
|
|
235
|
+
_ => const AiChatState(messages: []),
|
|
218
236
|
};
|
|
219
237
|
final msgs = current.messages;
|
|
220
238
|
final newMsgs = current.streamingStarted
|
|
221
239
|
? [...msgs.sublist(0, msgs.length - 1), ChatMessage.assistant(content)]
|
|
222
240
|
: [...msgs, ChatMessage.assistant(content)];
|
|
223
|
-
state = AsyncData(
|
|
241
|
+
state = AsyncData(AiChatState(messages: newMsgs));
|
|
224
242
|
}
|
|
225
243
|
|
|
226
244
|
String _extractSSEDelta(String line) {
|
|
@@ -253,14 +271,24 @@ class LlmChatNotifier extends AsyncNotifier<LlmChatState> {
|
|
|
253
271
|
return '';
|
|
254
272
|
}
|
|
255
273
|
|
|
256
|
-
void _persistMessage(ChatMessage message) {
|
|
274
|
+
void _persistMessage(ChatMessage message, String conversationId) {
|
|
257
275
|
final userId = ref.read(userStateNotifierProvider).user.idOrNull;
|
|
258
276
|
if (userId == null) return;
|
|
277
|
+
final entity = message.toEntity();
|
|
259
278
|
ref
|
|
260
|
-
.read(
|
|
261
|
-
.saveMessage(userId,
|
|
279
|
+
.read(aiChatApiProvider)
|
|
280
|
+
.saveMessage(userId, conversationId, entity)
|
|
262
281
|
.catchError((e) {
|
|
263
282
|
_logger.e('Failed to persist message: $e');
|
|
264
283
|
});
|
|
284
|
+
// Keep the conversation list preview + ordering in sync.
|
|
285
|
+
ref
|
|
286
|
+
.read(aiConversationsNotifierProvider.notifier)
|
|
287
|
+
.touch(
|
|
288
|
+
conversationId,
|
|
289
|
+
role: message.role,
|
|
290
|
+
content: message.content,
|
|
291
|
+
at: entity.createdAt,
|
|
292
|
+
);
|
|
265
293
|
}
|
|
266
294
|
}
|
package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart
CHANGED
|
@@ -8,7 +8,7 @@ import 'package:google_sign_in/google_sign_in.dart';
|
|
|
8
8
|
import 'package:logger/logger.dart';
|
|
9
9
|
import 'package:kasy_kit/core/data/api/base_api_exceptions.dart';
|
|
10
10
|
import 'package:kasy_kit/core/data/entities/user_entity.dart';
|
|
11
|
-
import 'package:kasy_kit/
|
|
11
|
+
import 'package:kasy_kit/environments.dart';
|
|
12
12
|
import 'package:kasy_kit/google_auth_options.dart';
|
|
13
13
|
import 'package:kasy_kit/features/authentication/api/authentication_api_interface.dart';
|
|
14
14
|
import 'package:kasy_kit/features/authentication/repositories/exceptions/authentication_exceptions.dart';
|
package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart
CHANGED
|
@@ -56,4 +56,50 @@ class FeatureRequestApi {
|
|
|
56
56
|
throw ApiError(code: 0, message: '$e: $stacktrace');
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
|
|
60
|
+
/// Admin: every request (active + hidden), highest-voted first.
|
|
61
|
+
Future<List<FeatureRequestEntity>> getAll() async {
|
|
62
|
+
try {
|
|
63
|
+
final res = await _client
|
|
64
|
+
.from(_kFeatureRequestTable) //
|
|
65
|
+
.select()
|
|
66
|
+
.order('votes', ascending: false);
|
|
67
|
+
return res.map((e) => FeatureRequestEntity.fromJson(e)).toList();
|
|
68
|
+
} catch (e, stacktrace) {
|
|
69
|
+
Logger().e('$e: $stacktrace');
|
|
70
|
+
throw ApiError(code: 0, message: '$e: $stacktrace');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Admin: toggle whether a request is visible (and votable) to users.
|
|
75
|
+
/// Gated server-side by the "Feature requests admin update" RLS policy.
|
|
76
|
+
Future<void> setActive(String id, bool active) async {
|
|
77
|
+
try {
|
|
78
|
+
await _client
|
|
79
|
+
.from(_kFeatureRequestTable) //
|
|
80
|
+
.update({'active': active})
|
|
81
|
+
.eq('id', id);
|
|
82
|
+
} catch (e, stacktrace) {
|
|
83
|
+
Logger().e('$e: $stacktrace');
|
|
84
|
+
throw ApiError(code: 0, message: '$e: $stacktrace');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Admin: update the localized title/description maps (en/pt/es).
|
|
89
|
+
Future<void> updateTexts({
|
|
90
|
+
required String id,
|
|
91
|
+
required Map<String, String> title,
|
|
92
|
+
required Map<String, String> description,
|
|
93
|
+
}) async {
|
|
94
|
+
try {
|
|
95
|
+
await _client.from(_kFeatureRequestTable).update({
|
|
96
|
+
'title': title,
|
|
97
|
+
'description': description,
|
|
98
|
+
'last_update_date': DateTime.now().toUtc().toIso8601String(),
|
|
99
|
+
}).eq('id', id);
|
|
100
|
+
} catch (e, stacktrace) {
|
|
101
|
+
Logger().e('$e: $stacktrace');
|
|
102
|
+
throw ApiError(code: 0, message: '$e: $stacktrace');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
59
105
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import 'package:kasy_kit/features/onboarding/api/entities/user_info_entity.dart';
|
|
2
2
|
|
|
3
3
|
enum UserInfoKeys {
|
|
4
|
-
|
|
4
|
+
gender,
|
|
5
5
|
age,
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
enum
|
|
8
|
+
enum Gender {
|
|
9
9
|
male,
|
|
10
10
|
female,
|
|
11
11
|
none,
|
|
@@ -44,11 +44,11 @@ class UserAgeInfo extends UserInfoDetail<AgeRange> {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
factory UserAgeInfo.fromString(String value) {
|
|
47
|
-
final
|
|
47
|
+
final range = AgeRange.values.firstWhere(
|
|
48
48
|
(element) => element.name == value,
|
|
49
49
|
orElse: () => AgeRange.none,
|
|
50
50
|
);
|
|
51
|
-
return UserAgeInfo(
|
|
51
|
+
return UserAgeInfo(range);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
|
|
@@ -62,32 +62,32 @@ class UserAgeInfo extends UserInfoDetail<AgeRange> {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/// ======================================
|
|
65
|
-
/// user
|
|
65
|
+
/// user gender info
|
|
66
66
|
/// ======================================
|
|
67
|
-
class
|
|
68
|
-
|
|
67
|
+
class UserGenderInfo extends UserInfoDetail<Gender> {
|
|
68
|
+
UserGenderInfo(super.value);
|
|
69
69
|
|
|
70
|
-
factory
|
|
71
|
-
final value =
|
|
70
|
+
factory UserGenderInfo.fromEntity(UserInfoEntity entity) {
|
|
71
|
+
final value = Gender.values.firstWhere(
|
|
72
72
|
(element) => element.name == entity.value,
|
|
73
|
-
orElse: () =>
|
|
73
|
+
orElse: () => Gender.none,
|
|
74
74
|
);
|
|
75
|
-
return
|
|
75
|
+
return UserGenderInfo(value);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
factory
|
|
79
|
-
final
|
|
78
|
+
factory UserGenderInfo.fromString(String value) {
|
|
79
|
+
final gender = Gender.values.firstWhere(
|
|
80
80
|
(element) => element.name == value,
|
|
81
|
-
orElse: () =>
|
|
81
|
+
orElse: () => Gender.none,
|
|
82
82
|
);
|
|
83
|
-
return
|
|
83
|
+
return UserGenderInfo(gender);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
@override
|
|
89
89
|
UserInfoEntity toEntity(String userId) => UserInfoEntity(
|
|
90
|
-
key: UserInfoKeys.
|
|
90
|
+
key: UserInfoKeys.gender.name,
|
|
91
91
|
value: value.name,
|
|
92
92
|
userId: userId,
|
|
93
93
|
);
|
|
@@ -6,7 +6,8 @@ import 'package:kasy_kit/core/data/api/tracking_api.dart';
|
|
|
6
6
|
import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
7
7
|
import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_background.dart';
|
|
8
8
|
import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart';
|
|
9
|
-
import 'package:kasy_kit/features/
|
|
9
|
+
import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_module_mockups.dart';
|
|
10
|
+
import 'package:kasy_kit/features/subscriptions/repositories/subscription_repository.dart';
|
|
10
11
|
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
11
12
|
import 'package:permission_handler/permission_handler.dart';
|
|
12
13
|
|
|
@@ -29,10 +30,10 @@ class AttPermissionStep extends ConsumerWidget {
|
|
|
29
30
|
|
|
30
31
|
return OnboardingBackground(
|
|
31
32
|
child: OnboardingIllustrationScaffold(
|
|
32
|
-
|
|
33
|
+
step: 6,
|
|
33
34
|
title: translations.title,
|
|
34
35
|
description: translations.description,
|
|
35
|
-
|
|
36
|
+
image: const TrackingPermissionMockup(),
|
|
36
37
|
footerActions: [
|
|
37
38
|
KasyButton(
|
|
38
39
|
label: translations.continue_button,
|