kasy-cli 1.21.9 → 1.23.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 +80 -37
- 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/generator.js +5 -5
- 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 +22 -6
- package/lib/utils/env-tools.js +7 -0
- package/lib/utils/flutter-install.js +114 -0
- package/lib/utils/i18n/messages-en.js +52 -35
- package/lib/utils/i18n/messages-es.js +52 -35
- package/lib/utils/i18n/messages-pt.js +54 -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} +702 -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 +136 -23
- 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 +54 -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 +53 -14
- 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 +128 -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 +5 -6
- 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
|
@@ -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,
|
|
@@ -7,6 +7,7 @@ import 'package:kasy_kit/core/theme/theme.dart';
|
|
|
7
7
|
import 'package:kasy_kit/core/widgets/responsive_layout.dart';
|
|
8
8
|
import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_background.dart';
|
|
9
9
|
import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_progress.dart';
|
|
10
|
+
import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_step_header.dart';
|
|
10
11
|
import 'package:kasy_kit/features/onboarding/ui/widgets/onboarding_sticky_footer.dart';
|
|
11
12
|
import 'package:kasy_kit/features/onboarding/ui/widgets/selectable_row_tile.dart';
|
|
12
13
|
|
|
@@ -18,9 +19,11 @@ typedef OnOptionIdSelected = void Function(String id);
|
|
|
18
19
|
|
|
19
20
|
typedef OnValidate = void Function(String? key);
|
|
20
21
|
|
|
21
|
-
/// Single choice question with
|
|
22
|
+
/// Single choice question with selectable tiles, matching the clean‑premium
|
|
23
|
+
/// onboarding layout (shared header, left‑aligned title, sticky footer).
|
|
22
24
|
class OnboardingRadioQuestion extends ConsumerStatefulWidget {
|
|
23
|
-
final
|
|
25
|
+
final int step;
|
|
26
|
+
final int totalSteps;
|
|
24
27
|
final String title;
|
|
25
28
|
final String description;
|
|
26
29
|
final String btnText;
|
|
@@ -37,7 +40,8 @@ class OnboardingRadioQuestion extends ConsumerStatefulWidget {
|
|
|
37
40
|
required this.btnText,
|
|
38
41
|
required this.optionIds,
|
|
39
42
|
required this.optionBuilder,
|
|
40
|
-
this.
|
|
43
|
+
required this.step,
|
|
44
|
+
this.totalSteps = kOnboardingSteps,
|
|
41
45
|
this.onOptionIdSelected,
|
|
42
46
|
this.onValidate,
|
|
43
47
|
this.reassuranceBuilder,
|
|
@@ -54,61 +58,68 @@ class _OnboardingRadioQuestionState
|
|
|
54
58
|
|
|
55
59
|
@override
|
|
56
60
|
Widget build(BuildContext context) {
|
|
61
|
+
const gutter = KasySpacing.lg;
|
|
62
|
+
|
|
57
63
|
final scrollBody = Column(
|
|
58
|
-
crossAxisAlignment: CrossAxisAlignment.
|
|
64
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
59
65
|
children: [
|
|
60
|
-
|
|
61
|
-
OnboardingProgress(value: widget.progress!),
|
|
66
|
+
OnboardingStepHeader(step: widget.step, totalSteps: widget.totalSteps),
|
|
62
67
|
Padding(
|
|
63
|
-
padding: const EdgeInsets.fromLTRB(
|
|
64
|
-
KasySpacing.md,
|
|
65
|
-
KasySpacing.xl,
|
|
66
|
-
KasySpacing.md,
|
|
67
|
-
0,
|
|
68
|
-
),
|
|
68
|
+
padding: const EdgeInsets.fromLTRB(gutter, KasySpacing.lg, gutter, 0),
|
|
69
69
|
child: MoveFadeAnim(
|
|
70
|
-
delayInMs:
|
|
70
|
+
delayInMs: 120,
|
|
71
71
|
child: Text(
|
|
72
72
|
widget.title,
|
|
73
|
-
textAlign: TextAlign.
|
|
74
|
-
style: context.textTheme.
|
|
73
|
+
textAlign: TextAlign.start,
|
|
74
|
+
style: context.textTheme.headlineMedium?.copyWith(
|
|
75
75
|
color: context.colors.onBackground,
|
|
76
|
+
fontSize: 26,
|
|
77
|
+
fontWeight: FontWeight.w700,
|
|
78
|
+
letterSpacing: -0.2,
|
|
79
|
+
height: 1.2,
|
|
76
80
|
),
|
|
77
81
|
),
|
|
78
82
|
),
|
|
79
83
|
),
|
|
80
84
|
Padding(
|
|
81
85
|
padding: const EdgeInsets.fromLTRB(
|
|
86
|
+
gutter,
|
|
87
|
+
KasySpacing.smd,
|
|
88
|
+
gutter,
|
|
82
89
|
KasySpacing.lg,
|
|
83
|
-
KasySpacing.md,
|
|
84
|
-
KasySpacing.lg,
|
|
85
|
-
KasySpacing.xl,
|
|
86
90
|
),
|
|
87
91
|
child: MoveFadeAnim(
|
|
88
92
|
delayInMs: 200,
|
|
89
93
|
child: Text(
|
|
90
94
|
widget.description,
|
|
91
|
-
textAlign: TextAlign.
|
|
95
|
+
textAlign: TextAlign.start,
|
|
92
96
|
style: context.textTheme.bodyLarge?.copyWith(
|
|
93
|
-
color: context.colors.
|
|
97
|
+
color: context.colors.muted,
|
|
98
|
+
height: 1.45,
|
|
94
99
|
),
|
|
95
100
|
),
|
|
96
101
|
),
|
|
97
102
|
),
|
|
98
103
|
Padding(
|
|
99
|
-
padding: const EdgeInsets.symmetric(horizontal:
|
|
104
|
+
padding: const EdgeInsets.symmetric(horizontal: gutter),
|
|
100
105
|
child: OnboardingSelectableRowGroup(
|
|
101
106
|
physics: const NeverScrollableScrollPhysics(),
|
|
102
|
-
options: widget.optionIds
|
|
103
|
-
.map(
|
|
107
|
+
options: widget.optionIds.map(
|
|
104
108
|
(e) {
|
|
105
109
|
final index = widget.optionIds.indexOf(e);
|
|
106
110
|
return Animate(
|
|
107
111
|
effects: [
|
|
108
112
|
FadeEffect(
|
|
109
|
-
delay: Duration(milliseconds:
|
|
110
|
-
duration: const Duration(milliseconds:
|
|
111
|
-
curve: Curves.
|
|
113
|
+
delay: Duration(milliseconds: 280 + index * 80),
|
|
114
|
+
duration: const Duration(milliseconds: 450),
|
|
115
|
+
curve: Curves.easeOut,
|
|
116
|
+
),
|
|
117
|
+
MoveEffect(
|
|
118
|
+
delay: Duration(milliseconds: 280 + index * 80),
|
|
119
|
+
duration: const Duration(milliseconds: 450),
|
|
120
|
+
curve: Curves.easeOut,
|
|
121
|
+
begin: const Offset(0, 24),
|
|
122
|
+
end: Offset.zero,
|
|
112
123
|
),
|
|
113
124
|
],
|
|
114
125
|
child: widget.optionBuilder(e, e == selectedChoiceId),
|
|
@@ -153,8 +164,7 @@ class _OnboardingRadioQuestionState
|
|
|
153
164
|
),
|
|
154
165
|
DeviceSizeBuilder(
|
|
155
166
|
builder: (device) => OnboardingStickyFooter(
|
|
156
|
-
maxContentWidth:
|
|
157
|
-
device == DeviceType.small ? null : 600,
|
|
167
|
+
maxContentWidth: device == DeviceType.small ? null : 600,
|
|
158
168
|
children: [
|
|
159
169
|
KasyButton(
|
|
160
170
|
label: widget.btnText,
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
2
|
+
import 'package:kasy_kit/core/data/api/base_api_exceptions.dart';
|
|
3
|
+
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
4
|
+
|
|
5
|
+
/// One row of the admin Users table, as returned by the `admin-list-users` Edge
|
|
6
|
+
/// Function. The function verifies the caller is `role == "admin"` and only then
|
|
7
|
+
/// reads across all users with the service role, so this data is never readable
|
|
8
|
+
/// by a non-admin client.
|
|
9
|
+
///
|
|
10
|
+
/// This file mirrors the shared admin data-layer contract (see the Firebase
|
|
11
|
+
/// version): the public surface — [AdminUser], [AdminUsersResult] and
|
|
12
|
+
/// [adminUsersApiProvider] — is identical across backends so the Users tab UI
|
|
13
|
+
/// stays the same; only the body of [AdminUsersApi.fetch] differs.
|
|
14
|
+
class AdminUser {
|
|
15
|
+
final String id;
|
|
16
|
+
final String? email;
|
|
17
|
+
final String? name;
|
|
18
|
+
final DateTime? createdAt;
|
|
19
|
+
final bool subscriber;
|
|
20
|
+
|
|
21
|
+
const AdminUser({
|
|
22
|
+
required this.id,
|
|
23
|
+
required this.subscriber,
|
|
24
|
+
this.email,
|
|
25
|
+
this.name,
|
|
26
|
+
this.createdAt,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
factory AdminUser.fromMap(Map<String, dynamic> m) {
|
|
30
|
+
final createdMillis = m['createdAt'];
|
|
31
|
+
return AdminUser(
|
|
32
|
+
id: (m['id'] as String?) ?? '',
|
|
33
|
+
email: m['email'] as String?,
|
|
34
|
+
name: m['name'] as String?,
|
|
35
|
+
createdAt: createdMillis is num
|
|
36
|
+
? DateTime.fromMillisecondsSinceEpoch(createdMillis.toInt())
|
|
37
|
+
: null,
|
|
38
|
+
subscriber: m['subscriber'] == true,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// The full (bounded) set of users plus collection metadata. The UI searches,
|
|
44
|
+
/// sorts and paginates this list locally, so those interactions are instant.
|
|
45
|
+
class AdminUsersResult {
|
|
46
|
+
final List<AdminUser> users;
|
|
47
|
+
final int totalUsers;
|
|
48
|
+
final bool truncated;
|
|
49
|
+
|
|
50
|
+
const AdminUsersResult({
|
|
51
|
+
required this.users,
|
|
52
|
+
required this.totalUsers,
|
|
53
|
+
required this.truncated,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
final adminUsersApiProvider = Provider<AdminUsersApi>(
|
|
58
|
+
(ref) => AdminUsersApi(client: Supabase.instance.client),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
class AdminUsersApi {
|
|
62
|
+
final SupabaseClient _client;
|
|
63
|
+
|
|
64
|
+
AdminUsersApi({required SupabaseClient client}) : _client = client;
|
|
65
|
+
|
|
66
|
+
/// Loads the users for the admin console via the `admin-list-users` Edge
|
|
67
|
+
/// Function. Returns the whole bounded set in one call; the UI handles
|
|
68
|
+
/// search/sort/pagination on the client.
|
|
69
|
+
Future<AdminUsersResult> fetch() async {
|
|
70
|
+
try {
|
|
71
|
+
final res = await _client.functions.invoke('admin-list-users');
|
|
72
|
+
if (res.status != 200) {
|
|
73
|
+
final msg = res.data is Map && res.data != null
|
|
74
|
+
? (res.data as Map)['error'] as String? ?? 'Error listing users'
|
|
75
|
+
: 'Error listing users';
|
|
76
|
+
throw ApiError(code: res.status, message: msg);
|
|
77
|
+
}
|
|
78
|
+
final data = Map<String, dynamic>.from(res.data as Map);
|
|
79
|
+
final rawUsers = (data['users'] as List?) ?? const [];
|
|
80
|
+
return AdminUsersResult(
|
|
81
|
+
users: rawUsers
|
|
82
|
+
.map((e) => AdminUser.fromMap(Map<String, dynamic>.from(e as Map)))
|
|
83
|
+
.toList(),
|
|
84
|
+
totalUsers: (data['totalUsers'] as num?)?.toInt() ?? 0,
|
|
85
|
+
truncated: data['truncated'] == true,
|
|
86
|
+
);
|
|
87
|
+
} on ApiError {
|
|
88
|
+
rethrow;
|
|
89
|
+
} catch (e, stacktrace) {
|
|
90
|
+
throw ApiError(code: 0, message: '$e: $stacktrace');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -15,6 +15,17 @@ enum SubscriptionStatus {
|
|
|
15
15
|
CANCELLED,
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/// Where the subscription was purchased (its origin). `STRIPE` means it was
|
|
19
|
+
/// bought on the web via Stripe. Used to route the user to the correct
|
|
20
|
+
/// management flow regardless of the device they are currently on.
|
|
21
|
+
enum SubscriptionStore {
|
|
22
|
+
PLAY_STORE,
|
|
23
|
+
APPLE_STORE,
|
|
24
|
+
EARLY_BIRD,
|
|
25
|
+
STRIPE,
|
|
26
|
+
unknown,
|
|
27
|
+
}
|
|
28
|
+
|
|
18
29
|
|
|
19
30
|
@freezed
|
|
20
31
|
sealed class SubscriptionEntity with _$SubscriptionEntity {
|
|
@@ -27,6 +38,8 @@ sealed class SubscriptionEntity with _$SubscriptionEntity {
|
|
|
27
38
|
@JsonKey(name: 'last_update_date') DateTime? lastUpdateDate,
|
|
28
39
|
@JsonKey(name: 'period_end_date') DateTime? periodEndDate,
|
|
29
40
|
@JsonKey(name: 'status') required SubscriptionStatus status,
|
|
41
|
+
@JsonKey(name: 'store', unknownEnumValue: SubscriptionStore.unknown)
|
|
42
|
+
SubscriptionStore? store,
|
|
30
43
|
}) = SubscriptionEntityData;
|
|
31
44
|
|
|
32
45
|
factory SubscriptionEntity.fromJson(Map<String, Object?> json) =>
|