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
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import 'package:flutter/foundation.dart' show kIsWeb;
|
|
2
|
+
import 'package:flutter/material.dart';
|
|
3
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
4
|
+
import 'package:intl/intl.dart';
|
|
5
|
+
import 'package:kasy_kit/core/data/models/user.dart';
|
|
6
|
+
import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
7
|
+
import 'package:kasy_kit/core/theme/theme.dart';
|
|
8
|
+
import 'package:kasy_kit/features/ai_chat/api/ai_chat_conversation_entity.dart';
|
|
9
|
+
import 'package:kasy_kit/features/ai_chat/ui/widgets/ai_chat_avatars.dart';
|
|
10
|
+
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
11
|
+
|
|
12
|
+
/// A single row in the conversation list (Figma `mail` list item): avatar +
|
|
13
|
+
/// title + last-message preview + timestamp. Avatar/title reflect the author
|
|
14
|
+
/// of the most recent message — assistant (app identity) or the user.
|
|
15
|
+
class AiConversationTile extends ConsumerStatefulWidget {
|
|
16
|
+
const AiConversationTile({
|
|
17
|
+
super.key,
|
|
18
|
+
required this.conversation,
|
|
19
|
+
required this.selected,
|
|
20
|
+
required this.onTap,
|
|
21
|
+
required this.onDelete,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
final AiChatConversationEntity conversation;
|
|
25
|
+
final bool selected;
|
|
26
|
+
final VoidCallback onTap;
|
|
27
|
+
final VoidCallback onDelete;
|
|
28
|
+
|
|
29
|
+
@override
|
|
30
|
+
ConsumerState<AiConversationTile> createState() => _AiConversationTileState();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class _AiConversationTileState extends ConsumerState<AiConversationTile> {
|
|
34
|
+
bool _hovered = false;
|
|
35
|
+
|
|
36
|
+
@override
|
|
37
|
+
Widget build(BuildContext context) {
|
|
38
|
+
final conversation = widget.conversation;
|
|
39
|
+
final bool fromUser = conversation.lastMessageRole == 'user';
|
|
40
|
+
|
|
41
|
+
final String title = fromUser
|
|
42
|
+
? _userDisplayName(ref) ?? t.ai_chat.title
|
|
43
|
+
: t.ai_chat.title;
|
|
44
|
+
final Widget avatar = fromUser
|
|
45
|
+
? const AiChatUserAvatar(diameter: 36)
|
|
46
|
+
: const AiChatAssistantAvatar(diameter: 36);
|
|
47
|
+
final String preview =
|
|
48
|
+
conversation.lastMessageContent ?? t.ai_chat.conversations_empty;
|
|
49
|
+
|
|
50
|
+
final Color titleColor = context.colors.onBackground;
|
|
51
|
+
final Color subtitleColor = context.colors.muted;
|
|
52
|
+
|
|
53
|
+
final Widget tile = Container(
|
|
54
|
+
padding: const EdgeInsets.all(KasySpacing.smd),
|
|
55
|
+
decoration: BoxDecoration(
|
|
56
|
+
color: widget.selected
|
|
57
|
+
? context.colors.accentSoft
|
|
58
|
+
: (_hovered ? context.colors.surfaceNeutralSoft : null),
|
|
59
|
+
borderRadius: BorderRadius.circular(16),
|
|
60
|
+
),
|
|
61
|
+
child: Row(
|
|
62
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
63
|
+
children: [
|
|
64
|
+
avatar,
|
|
65
|
+
const SizedBox(width: KasySpacing.smd),
|
|
66
|
+
Expanded(
|
|
67
|
+
child: Column(
|
|
68
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
69
|
+
children: [
|
|
70
|
+
Row(
|
|
71
|
+
children: [
|
|
72
|
+
Expanded(
|
|
73
|
+
child: Text(
|
|
74
|
+
title,
|
|
75
|
+
maxLines: 1,
|
|
76
|
+
overflow: TextOverflow.ellipsis,
|
|
77
|
+
style: context.textTheme.bodyMedium?.copyWith(
|
|
78
|
+
fontWeight: FontWeight.w600,
|
|
79
|
+
color: titleColor,
|
|
80
|
+
),
|
|
81
|
+
),
|
|
82
|
+
),
|
|
83
|
+
const SizedBox(width: KasySpacing.xs),
|
|
84
|
+
_trailing(context, subtitleColor),
|
|
85
|
+
],
|
|
86
|
+
),
|
|
87
|
+
const SizedBox(height: 2),
|
|
88
|
+
Text(
|
|
89
|
+
preview,
|
|
90
|
+
maxLines: 1,
|
|
91
|
+
overflow: TextOverflow.ellipsis,
|
|
92
|
+
style: context.textTheme.bodySmall?.copyWith(
|
|
93
|
+
color: subtitleColor,
|
|
94
|
+
),
|
|
95
|
+
),
|
|
96
|
+
],
|
|
97
|
+
),
|
|
98
|
+
),
|
|
99
|
+
],
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
final Widget tappable = Semantics(
|
|
104
|
+
button: true,
|
|
105
|
+
selected: widget.selected,
|
|
106
|
+
label: title,
|
|
107
|
+
child: GestureDetector(
|
|
108
|
+
behavior: HitTestBehavior.opaque,
|
|
109
|
+
onTap: widget.onTap,
|
|
110
|
+
child: tile,
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Hover reveals the delete affordance on pointer devices (web / desktop).
|
|
115
|
+
if (!kIsWeb) return tappable;
|
|
116
|
+
return MouseRegion(
|
|
117
|
+
onEnter: (_) => setState(() => _hovered = true),
|
|
118
|
+
onExit: (_) => setState(() => _hovered = false),
|
|
119
|
+
child: tappable,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// On hover (desktop), shows a delete button; otherwise the timestamp.
|
|
124
|
+
Widget _trailing(BuildContext context, Color color) {
|
|
125
|
+
if (kIsWeb && _hovered) {
|
|
126
|
+
return SizedBox(
|
|
127
|
+
height: 16,
|
|
128
|
+
child: InkResponse(
|
|
129
|
+
onTap: widget.onDelete,
|
|
130
|
+
radius: 18,
|
|
131
|
+
child: Icon(KasyIcons.trash, size: 16, color: context.colors.error),
|
|
132
|
+
),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return Text(
|
|
136
|
+
_formatTimestamp(context, widget.conversation.updatedAt),
|
|
137
|
+
style: context.textTheme.bodySmall?.copyWith(color: color),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
String? _userDisplayName(WidgetRef ref) {
|
|
142
|
+
final user = ref.watch(userStateNotifierProvider).user;
|
|
143
|
+
if (user is! AuthenticatedUserData) return null;
|
|
144
|
+
final String? name = user.name?.trim();
|
|
145
|
+
if (name != null && name.isNotEmpty) return name;
|
|
146
|
+
final String local = user.email.split('@').first.trim();
|
|
147
|
+
return local.isNotEmpty ? local : null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
String _formatTimestamp(BuildContext context, DateTime time) {
|
|
151
|
+
final String locale = Localizations.localeOf(context).toString();
|
|
152
|
+
final DateTime now = DateTime.now();
|
|
153
|
+
final bool sameDay =
|
|
154
|
+
now.year == time.year && now.month == time.month && now.day == time.day;
|
|
155
|
+
if (sameDay) {
|
|
156
|
+
return DateFormat.jm(locale).format(time);
|
|
157
|
+
}
|
|
158
|
+
if (now.year == time.year) {
|
|
159
|
+
return DateFormat.MMMd(locale).format(time);
|
|
160
|
+
}
|
|
161
|
+
return DateFormat.yMd(locale).format(time);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -9,6 +9,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
9
9
|
import 'package:google_sign_in/google_sign_in.dart';
|
|
10
10
|
import 'package:kasy_kit/core/data/entities/user_entity.dart';
|
|
11
11
|
import 'package:kasy_kit/features/authentication/api/authentication_api_interface.dart';
|
|
12
|
+
import 'package:kasy_kit/features/authentication/api/popup_dismiss_watcher.dart'
|
|
13
|
+
if (dart.library.js_interop) 'package:kasy_kit/features/authentication/api/popup_dismiss_watcher_web.dart';
|
|
12
14
|
import 'package:kasy_kit/features/authentication/repositories/exceptions/authentication_exceptions.dart';
|
|
13
15
|
import 'package:kasy_kit/google_auth_options.dart';
|
|
14
16
|
import 'package:logger/logger.dart';
|
|
@@ -144,6 +146,37 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
|
|
|
144
146
|
}
|
|
145
147
|
}
|
|
146
148
|
|
|
149
|
+
/// Firebase Auth web error codes that mean the user dismissed the provider
|
|
150
|
+
/// popup (closed the window / cancelled the account chooser) — a user cancel,
|
|
151
|
+
/// not a real failure.
|
|
152
|
+
bool _isUserCancelledPopup(String code) =>
|
|
153
|
+
code == 'popup-closed-by-user' ||
|
|
154
|
+
code == 'cancelled-popup-request' ||
|
|
155
|
+
code == 'web-context-cancelled' ||
|
|
156
|
+
code == 'user-cancelled' ||
|
|
157
|
+
code == 'canceled';
|
|
158
|
+
|
|
159
|
+
/// Runs a Firebase web popup sign-in ([signInWithPopup] / [linkWithPopup])
|
|
160
|
+
/// alongside a window-refocus watcher. On web those futures never reject when
|
|
161
|
+
/// the user closes the popup manually, so without this the caller would hang
|
|
162
|
+
/// in a loading state forever. If the window refocuses (popup dismissed)
|
|
163
|
+
/// before the popup resolves, we surface a [UserCancelledSignInException].
|
|
164
|
+
Future<UserCredential> _popupOrCancel(
|
|
165
|
+
Future<UserCredential> Function() run,
|
|
166
|
+
) async {
|
|
167
|
+
final watch = watchAuthPopupDismiss();
|
|
168
|
+
try {
|
|
169
|
+
final result = await Future.any<UserCredential?>([
|
|
170
|
+
run(),
|
|
171
|
+
watch.dismissed.then<UserCredential?>((_) => null),
|
|
172
|
+
]);
|
|
173
|
+
if (result == null) throw const UserCancelledSignInException();
|
|
174
|
+
return result;
|
|
175
|
+
} finally {
|
|
176
|
+
watch.cancel();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
147
180
|
@override
|
|
148
181
|
Future<Credentials> signinWithGoogle() async {
|
|
149
182
|
if (kIsWeb) {
|
|
@@ -156,13 +189,15 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
|
|
|
156
189
|
// 'login_hint': 'user@example.com'
|
|
157
190
|
// });
|
|
158
191
|
try {
|
|
159
|
-
final credentials = await
|
|
192
|
+
final credentials = await _popupOrCancel(
|
|
193
|
+
() => _auth.signInWithPopup(googleProvider),
|
|
194
|
+
);
|
|
160
195
|
return Credentials(
|
|
161
196
|
id: credentials.user!.uid,
|
|
162
197
|
token: credentials.credential?.token.toString() ?? '',
|
|
163
198
|
);
|
|
164
199
|
} on FirebaseAuthException catch (e) {
|
|
165
|
-
if (e.code
|
|
200
|
+
if (_isUserCancelledPopup(e.code)) {
|
|
166
201
|
throw const UserCancelledSignInException();
|
|
167
202
|
}
|
|
168
203
|
rethrow;
|
|
@@ -198,15 +233,17 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
|
|
|
198
233
|
if (kIsWeb) {
|
|
199
234
|
final googleProvider = GoogleAuthProvider();
|
|
200
235
|
try {
|
|
201
|
-
final userCredential =
|
|
202
|
-
|
|
203
|
-
|
|
236
|
+
final userCredential = await _popupOrCancel(
|
|
237
|
+
() => _auth.currentUser != null
|
|
238
|
+
? _auth.currentUser!.linkWithPopup(googleProvider)
|
|
239
|
+
: _auth.signInWithPopup(googleProvider),
|
|
240
|
+
);
|
|
204
241
|
return Credentials(
|
|
205
242
|
id: userCredential.user!.uid,
|
|
206
243
|
token: userCredential.credential?.token.toString() ?? '',
|
|
207
244
|
);
|
|
208
245
|
} on FirebaseAuthException catch (e) {
|
|
209
|
-
if (e.code
|
|
246
|
+
if (_isUserCancelledPopup(e.code)) {
|
|
210
247
|
throw const UserCancelledSignInException();
|
|
211
248
|
}
|
|
212
249
|
if (e.code == 'credential-already-in-use') {
|
|
@@ -215,7 +252,9 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
|
|
|
215
252
|
if (anonymousUser != null && anonymousUser.isAnonymous) {
|
|
216
253
|
await anonymousUser.delete();
|
|
217
254
|
}
|
|
218
|
-
final userCredential = await
|
|
255
|
+
final userCredential = await _popupOrCancel(
|
|
256
|
+
() => _auth.signInWithPopup(googleProvider),
|
|
257
|
+
);
|
|
219
258
|
return Credentials(id: userCredential.user!.uid, token: userCredential.credential?.token.toString() ?? '');
|
|
220
259
|
}
|
|
221
260
|
rethrow;
|
|
@@ -243,7 +282,7 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
|
|
|
243
282
|
final result = await _auth.signInWithCredential(credential);
|
|
244
283
|
return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
|
|
245
284
|
} on FirebaseAuthException catch (e) {
|
|
246
|
-
if (e.code
|
|
285
|
+
if (_isUserCancelledPopup(e.code)) {
|
|
247
286
|
throw const UserCancelledSignInException();
|
|
248
287
|
}
|
|
249
288
|
rethrow;
|
|
@@ -286,7 +325,7 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
|
|
|
286
325
|
appleProvider.addScope('email');
|
|
287
326
|
try {
|
|
288
327
|
final value = kIsWeb
|
|
289
|
-
? await _auth.signInWithPopup(appleProvider)
|
|
328
|
+
? await _popupOrCancel(() => _auth.signInWithPopup(appleProvider))
|
|
290
329
|
: await _auth.signInWithProvider(appleProvider);
|
|
291
330
|
return Credentials(
|
|
292
331
|
id: value.user!.uid,
|
|
@@ -311,11 +350,11 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
|
|
|
311
350
|
// No anonymous session — fall back to regular sign-in.
|
|
312
351
|
try {
|
|
313
352
|
final result = kIsWeb
|
|
314
|
-
? await _auth.signInWithPopup(appleProvider)
|
|
353
|
+
? await _popupOrCancel(() => _auth.signInWithPopup(appleProvider))
|
|
315
354
|
: await _auth.signInWithProvider(appleProvider);
|
|
316
355
|
return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
|
|
317
356
|
} on FirebaseAuthException catch (e) {
|
|
318
|
-
if (e.code
|
|
357
|
+
if (_isUserCancelledPopup(e.code)) {
|
|
319
358
|
throw const UserCancelledSignInException();
|
|
320
359
|
}
|
|
321
360
|
rethrow;
|
|
@@ -341,7 +380,7 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
|
|
|
341
380
|
return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
|
|
342
381
|
}
|
|
343
382
|
final result = kIsWeb
|
|
344
|
-
? await _auth.signInWithPopup(appleProvider)
|
|
383
|
+
? await _popupOrCancel(() => _auth.signInWithPopup(appleProvider))
|
|
345
384
|
: await _auth.signInWithProvider(appleProvider);
|
|
346
385
|
return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
|
|
347
386
|
}
|
|
@@ -365,7 +404,7 @@ class FirebaseAuthenticationApi implements AuthenticationApi {
|
|
|
365
404
|
final result = await _auth.signInWithCredential(credential);
|
|
366
405
|
return Credentials(id: result.user!.uid, token: result.credential?.token.toString() ?? '');
|
|
367
406
|
} on FirebaseAuthException catch (e) {
|
|
368
|
-
if (e.code
|
|
407
|
+
if (_isUserCancelledPopup(e.code)) {
|
|
369
408
|
throw const UserCancelledSignInException();
|
|
370
409
|
}
|
|
371
410
|
rethrow;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
|
|
3
|
+
/// Watches for the user dismissing a provider auth popup.
|
|
4
|
+
///
|
|
5
|
+
/// Native/non-web stub: there is no browser popup window to watch, so
|
|
6
|
+
/// [PopupDismissWatch.dismissed] never completes and [PopupDismissWatch.cancel]
|
|
7
|
+
/// is a no-op. The real implementation lives in `popup_dismiss_watcher_web.dart`
|
|
8
|
+
/// and is selected via conditional import on web.
|
|
9
|
+
typedef PopupDismissWatch = ({Future<void> dismissed, void Function() cancel});
|
|
10
|
+
|
|
11
|
+
PopupDismissWatch watchAuthPopupDismiss() =>
|
|
12
|
+
(dismissed: Completer<void>().future, cancel: () {});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
import 'dart:js_interop';
|
|
3
|
+
|
|
4
|
+
import 'package:web/web.dart' as web;
|
|
5
|
+
|
|
6
|
+
typedef PopupDismissWatch = ({Future<void> dismissed, void Function() cancel});
|
|
7
|
+
|
|
8
|
+
/// Web implementation: Firebase's `signInWithPopup` / `linkWithPopup` never
|
|
9
|
+
/// reject when the user closes the popup window manually, leaving the sign-in
|
|
10
|
+
/// future pending forever. We detect that dismissal indirectly: when the popup
|
|
11
|
+
/// closes, the main window regains focus.
|
|
12
|
+
///
|
|
13
|
+
/// [dismissed] completes ~1.2s after the main window refocuses (a debounce so a
|
|
14
|
+
/// successful sign-in — which resolves its own future first — wins the race).
|
|
15
|
+
/// Always call [cancel] once the sign-in settles to remove the listener.
|
|
16
|
+
PopupDismissWatch watchAuthPopupDismiss() {
|
|
17
|
+
final completer = Completer<void>();
|
|
18
|
+
Timer? settle;
|
|
19
|
+
|
|
20
|
+
final handler = ((web.Event _) {
|
|
21
|
+
settle?.cancel();
|
|
22
|
+
settle = Timer(const Duration(milliseconds: 1200), () {
|
|
23
|
+
if (!completer.isCompleted) completer.complete();
|
|
24
|
+
});
|
|
25
|
+
}).toJS;
|
|
26
|
+
|
|
27
|
+
web.window.addEventListener('focus', handler);
|
|
28
|
+
|
|
29
|
+
void cancel() {
|
|
30
|
+
settle?.cancel();
|
|
31
|
+
web.window.removeEventListener('focus', handler);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (dismissed: completer.future, cancel: cancel);
|
|
35
|
+
}
|
|
@@ -1,101 +1,141 @@
|
|
|
1
1
|
import 'package:flutter/material.dart';
|
|
2
|
-
import 'package:flutter/services.dart';
|
|
3
2
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
4
3
|
import 'package:go_router/go_router.dart';
|
|
5
4
|
import 'package:kasy_kit/components/components.dart';
|
|
6
5
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
7
|
-
import 'package:kasy_kit/core/widgets/
|
|
6
|
+
import 'package:kasy_kit/core/widgets/kasy_pressable_depth.dart';
|
|
7
|
+
import 'package:kasy_kit/core/widgets/page_background.dart';
|
|
8
8
|
import 'package:kasy_kit/features/authentication/providers/models/email.dart';
|
|
9
9
|
import 'package:kasy_kit/features/authentication/providers/models/recover_state.dart';
|
|
10
10
|
import 'package:kasy_kit/features/authentication/providers/recover_provider.dart';
|
|
11
|
+
import 'package:kasy_kit/features/authentication/ui/widgets/auth_card_scaffold.dart';
|
|
11
12
|
import 'package:kasy_kit/features/authentication/ui/widgets/recover_password_result.dart';
|
|
12
13
|
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
13
14
|
|
|
14
15
|
final _formKey = GlobalKey<FormState>();
|
|
15
16
|
|
|
17
|
+
/// Reset-password screen. Mirrors the sign-in / sign-up layout: a centered
|
|
18
|
+
/// card (logo, title, single email field, button) on a plain background, so it
|
|
19
|
+
/// looks identical to those pages on web/desktop instead of a stretched mobile
|
|
20
|
+
/// sub-page.
|
|
16
21
|
class RecoverPasswordPage extends ConsumerWidget {
|
|
17
22
|
const RecoverPasswordPage({super.key});
|
|
18
23
|
|
|
24
|
+
void _submit(BuildContext context, WidgetRef ref) {
|
|
25
|
+
if (!_formKey.currentState!.validate()) return;
|
|
26
|
+
FocusScope.of(context).unfocus();
|
|
27
|
+
ref.read(recoverStateProvider.notifier).send();
|
|
28
|
+
}
|
|
29
|
+
|
|
19
30
|
@override
|
|
20
31
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
21
32
|
final state = ref.watch(recoverStateProvider);
|
|
33
|
+
final bool isSending = state is RecoverStateSending;
|
|
34
|
+
final bool isSent = state is RecoverStateSent;
|
|
22
35
|
|
|
23
36
|
return PopScope(
|
|
24
37
|
canPop: context.canPop(),
|
|
25
38
|
onPopInvokedWithResult: (didPop, _) {
|
|
26
|
-
if (!didPop)
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
39
|
+
if (!didPop) return;
|
|
29
40
|
ref.invalidate(recoverStateProvider);
|
|
30
41
|
},
|
|
31
|
-
child:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return null;
|
|
63
|
-
},
|
|
64
|
-
onChanged: (value) => ref
|
|
65
|
-
.read(recoverStateProvider.notifier)
|
|
66
|
-
.setEmail(value),
|
|
67
|
-
),
|
|
68
|
-
const SizedBox(height: KasySpacing.lg),
|
|
69
|
-
KasyButton(
|
|
70
|
-
key: const Key('recover_button'),
|
|
71
|
-
label: t.auth.recover.submit,
|
|
72
|
-
onPressed: () {
|
|
73
|
-
if (!_formKey.currentState!.validate()) return;
|
|
74
|
-
ref.read(recoverStateProvider.notifier).send();
|
|
75
|
-
},
|
|
76
|
-
expand: true,
|
|
77
|
-
),
|
|
78
|
-
],
|
|
42
|
+
child: Scaffold(
|
|
43
|
+
resizeToAvoidBottomInset: false,
|
|
44
|
+
body: Stack(
|
|
45
|
+
children: [
|
|
46
|
+
if (isSent)
|
|
47
|
+
const Background(child: RecoverPasswordSent())
|
|
48
|
+
else
|
|
49
|
+
Form(
|
|
50
|
+
key: _formKey,
|
|
51
|
+
child: AuthCardScaffold(
|
|
52
|
+
title: t.auth.recover.title,
|
|
53
|
+
subtitle: t.auth.recover.subtitle,
|
|
54
|
+
children: [
|
|
55
|
+
KasyTextField(
|
|
56
|
+
key: const Key('email_input'),
|
|
57
|
+
label: t.auth.recover.email_label,
|
|
58
|
+
contentType: KasyTextFieldContentType.email,
|
|
59
|
+
textInputAction: TextInputAction.done,
|
|
60
|
+
validator: (value) {
|
|
61
|
+
try {
|
|
62
|
+
state.email.validate();
|
|
63
|
+
} on EmailException catch (_) {
|
|
64
|
+
return t.auth.signin.email_invalid;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
},
|
|
68
|
+
onChanged: (value) => ref
|
|
69
|
+
.read(recoverStateProvider.notifier)
|
|
70
|
+
.setEmail(value),
|
|
71
|
+
onSubmitted:
|
|
72
|
+
isSending ? null : (_) => _submit(context, ref),
|
|
79
73
|
),
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
),
|
|
74
|
+
const SizedBox(height: KasySpacing.lg),
|
|
75
|
+
KasyButton(
|
|
76
|
+
key: const Key('recover_button'),
|
|
77
|
+
label: t.auth.recover.submit,
|
|
78
|
+
isLoading: isSending,
|
|
79
|
+
onPressed:
|
|
80
|
+
isSending ? null : () => _submit(context, ref),
|
|
81
|
+
expand: true,
|
|
89
82
|
),
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
child: RecoverPasswordSent(),
|
|
83
|
+
const SizedBox(height: KasySpacing.md),
|
|
84
|
+
const _BackToSigninPrompt(),
|
|
85
|
+
],
|
|
94
86
|
),
|
|
87
|
+
),
|
|
88
|
+
],
|
|
89
|
+
),
|
|
90
|
+
),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Text link back to sign‑in, styled like the "Don't have an account? Sign up"
|
|
96
|
+
/// prompts. Replaces the old floating back orb so reaching login is one obvious
|
|
97
|
+
/// tap right under the action.
|
|
98
|
+
class _BackToSigninPrompt extends StatelessWidget {
|
|
99
|
+
const _BackToSigninPrompt();
|
|
100
|
+
|
|
101
|
+
@override
|
|
102
|
+
Widget build(BuildContext context) {
|
|
103
|
+
return Center(
|
|
104
|
+
child: FittedBox(
|
|
105
|
+
fit: BoxFit.scaleDown,
|
|
106
|
+
child: Row(
|
|
107
|
+
mainAxisAlignment: MainAxisAlignment.center,
|
|
108
|
+
mainAxisSize: MainAxisSize.min,
|
|
109
|
+
children: [
|
|
110
|
+
Text(
|
|
111
|
+
t.auth.recover.remember,
|
|
112
|
+
style: context.textTheme.bodyMedium?.copyWith(
|
|
113
|
+
color: context.colors.onSurface.withValues(alpha: 0.62),
|
|
114
|
+
fontWeight: FontWeight.w500,
|
|
115
|
+
),
|
|
116
|
+
),
|
|
117
|
+
const SizedBox(width: 4),
|
|
118
|
+
KasyPressableDepth(
|
|
119
|
+
semanticLabel: t.auth.recover.signin_link,
|
|
120
|
+
onPressed: () {
|
|
121
|
+
if (context.canPop()) {
|
|
122
|
+
context.pop();
|
|
123
|
+
} else {
|
|
124
|
+
context.go('/signin');
|
|
125
|
+
}
|
|
95
126
|
},
|
|
96
|
-
|
|
97
|
-
|
|
127
|
+
child: Text(
|
|
128
|
+
t.auth.recover.signin_link,
|
|
129
|
+
style: context.textTheme.bodyMedium?.copyWith(
|
|
130
|
+
color: context.colors.onSurface,
|
|
131
|
+
fontWeight: FontWeight.w800,
|
|
132
|
+
),
|
|
133
|
+
),
|
|
134
|
+
),
|
|
135
|
+
],
|
|
98
136
|
),
|
|
137
|
+
),
|
|
99
138
|
);
|
|
100
139
|
}
|
|
101
140
|
}
|
|
141
|
+
|