kasy-cli 1.12.1 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/bin/kasy.js +21 -0
  2. package/lib/commands/splash.js +220 -0
  3. package/lib/scaffold/CHANGELOG.json +9 -0
  4. package/lib/scaffold/backends/api/patch/lib/main.dart +29 -10
  5. package/lib/scaffold/backends/supabase/patch/lib/main.dart +29 -10
  6. package/lib/scaffold/features/README.md +15 -139
  7. package/lib/scaffold/shared/generator-utils.js +16 -15
  8. package/lib/utils/i18n.js +78 -0
  9. package/package.json +2 -2
  10. package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
  11. package/templates/firebase/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
  12. package/templates/firebase/android/app/src/main/res/drawable-mdpi/android12splash.png +0 -0
  13. package/templates/firebase/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
  14. package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
  15. package/templates/firebase/android/app/src/main/res/drawable-night/launch_background.xml +9 -0
  16. package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/android12splash.png +0 -0
  17. package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/splash.png +0 -0
  18. package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/android12splash.png +0 -0
  19. package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/splash.png +0 -0
  20. package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
  21. package/templates/firebase/android/app/src/main/res/drawable-night-v21/launch_background.xml +9 -0
  22. package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png +0 -0
  23. package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/splash.png +0 -0
  24. package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png +0 -0
  25. package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/splash.png +0 -0
  26. package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png +0 -0
  27. package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/splash.png +0 -0
  28. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/android12splash.png +0 -0
  29. package/templates/firebase/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
  30. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/android12splash.png +0 -0
  31. package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
  32. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/android12splash.png +0 -0
  33. package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
  34. package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +2 -1
  35. package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -0
  36. package/templates/firebase/assets/images/splash_logo_dark.png +0 -0
  37. package/templates/firebase/assets/images/splash_logo_light.png +0 -0
  38. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json +9 -8
  39. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
  40. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +33 -0
  41. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
  42. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
  43. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
  44. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png +0 -0
  45. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png +0 -0
  46. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png +0 -0
  47. package/templates/firebase/ios/Runner/Base.lproj/LaunchScreen.storyboard +1 -1
  48. package/templates/firebase/lib/core/theme/providers/theme_provider.dart +48 -24
  49. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +4 -0
  50. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +1 -0
  51. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +13 -0
  52. package/templates/firebase/lib/features/settings/settings_page.dart +158 -18
  53. package/templates/firebase/lib/i18n/en.i18n.json +6 -2
  54. package/templates/firebase/lib/i18n/es.i18n.json +6 -2
  55. package/templates/firebase/lib/i18n/pt.i18n.json +6 -2
  56. package/templates/firebase/lib/main.dart +29 -10
  57. package/templates/firebase/pubspec.yaml +4 -5
  58. package/templates/firebase/test/core/data/repositories/user_repository_test.dart +1 -1
  59. package/templates/firebase/web/index.html +47 -39
  60. package/templates/firebase/web/splash/img/dark-1x.png +0 -0
  61. package/templates/firebase/web/splash/img/dark-2x.png +0 -0
  62. package/templates/firebase/web/splash/img/dark-3x.png +0 -0
  63. package/templates/firebase/web/splash/img/dark-4x.png +0 -0
  64. package/templates/firebase/web/splash/img/light-1x.png +0 -0
  65. package/templates/firebase/web/splash/img/light-2x.png +0 -0
  66. package/templates/firebase/web/splash/img/light-3x.png +0 -0
  67. package/templates/firebase/web/splash/img/light-4x.png +0 -0
  68. package/lib/scaffold/features/analytics/lib/core/data/api/analytics_api.dart +0 -124
  69. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.es.md +0 -35
  70. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.md +0 -35
  71. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.pt.md +0 -35
  72. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.es.md +0 -12
  73. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.md +0 -12
  74. package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.pt.md +0 -12
  75. package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.es.md +0 -17
  76. package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.md +0 -17
  77. package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.pt.md +0 -17
  78. package/lib/scaffold/features/ci/.github/dependabot.yml +0 -16
  79. package/lib/scaffold/features/ci/.github/workflows/app.yml +0 -20
  80. package/lib/scaffold/features/ci/.gitlab/templates/deploy.yaml +0 -14
  81. package/lib/scaffold/features/ci/.gitlab/templates/dropbox.yaml +0 -19
  82. package/lib/scaffold/features/ci/.gitlab/templates/flutter.yaml +0 -163
  83. package/lib/scaffold/features/ci/.gitlab/templates/mailgun.yaml +0 -28
  84. package/lib/scaffold/features/ci/.gitlab-ci.yml +0 -37
  85. package/lib/scaffold/features/ci/codemagic.yaml +0 -157
  86. package/lib/scaffold/features/facebook/lib/core/data/api/tracking_api.dart +0 -111
  87. package/lib/scaffold/features/feedback/lib/features/feedbacks/api/entities/feature_request_entity.dart +0 -27
  88. package/lib/scaffold/features/feedback/lib/features/feedbacks/api/entities/feature_vote_entity.dart +0 -27
  89. package/lib/scaffold/features/feedback/lib/features/feedbacks/api/feature_request_api.dart +0 -50
  90. package/lib/scaffold/features/feedback/lib/features/feedbacks/api/feature_vote_api.dart +0 -79
  91. package/lib/scaffold/features/feedback/lib/features/feedbacks/models/feature_requests.dart +0 -48
  92. package/lib/scaffold/features/feedback/lib/features/feedbacks/models/feedback_state.dart +0 -42
  93. package/lib/scaffold/features/feedback/lib/features/feedbacks/providers/feedback_page_notifier.dart +0 -147
  94. package/lib/scaffold/features/feedback/lib/features/feedbacks/repositories/feature_request_repository.dart +0 -95
  95. package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/component/add_feature_form.dart +0 -199
  96. package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/feedback_page.dart +0 -175
  97. package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -76
  98. package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/widgets/feature_card.dart +0 -279
  99. package/lib/scaffold/features/ios-release/.kasy/apple.env.example +0 -8
  100. package/lib/scaffold/features/ios-release/.kasy/codemagic.env.example +0 -7
  101. package/lib/scaffold/features/ios-release/docs/codemagic-release.en.md +0 -50
  102. package/lib/scaffold/features/ios-release/docs/codemagic-release.es.md +0 -50
  103. package/lib/scaffold/features/ios-release/docs/codemagic-release.pt.md +0 -50
  104. package/lib/scaffold/features/ios-release/docs/ios-release.en.md +0 -41
  105. package/lib/scaffold/features/ios-release/docs/ios-release.es.md +0 -41
  106. package/lib/scaffold/features/ios-release/docs/ios-release.pt.md +0 -41
  107. package/lib/scaffold/features/ios-release/scripts/bump-ios-version.js +0 -38
  108. package/lib/scaffold/features/ios-release/scripts/release-ios.sh +0 -137
  109. package/lib/scaffold/features/llm_chat/lib/features/llm_chat/llm_chat_page.dart +0 -301
  110. package/lib/scaffold/features/local_notifications/lib/features/local_reminder/providers/reminder_notifier.dart +0 -81
  111. package/lib/scaffold/features/local_notifications/lib/features/local_reminder/repositories/reminder_preferences.dart +0 -76
  112. package/lib/scaffold/features/local_notifications/lib/features/local_reminder/ui/reminder_page.dart +0 -282
  113. package/lib/scaffold/features/onboarding/lib/features/onboarding/api/entities/user_info_entity.dart +0 -24
  114. package/lib/scaffold/features/onboarding/lib/features/onboarding/api/user_infos_api.dart +0 -71
  115. package/lib/scaffold/features/onboarding/lib/features/onboarding/models/user_info.dart +0 -92
  116. package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_model.dart +0 -15
  117. package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_provider.dart +0 -78
  118. package/lib/scaffold/features/onboarding/lib/features/onboarding/repositories/user_infos_repository.dart +0 -29
  119. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/animations/page_transitions.dart +0 -30
  120. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_att_setup.dart +0 -66
  121. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_features.dart +0 -72
  122. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_loader.dart +0 -92
  123. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
  124. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_questions.dart +0 -89
  125. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/onboarding_page.dart +0 -94
  126. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_background.dart +0 -80
  127. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_feature.dart +0 -139
  128. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +0 -110
  129. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_progress.dart +0 -84
  130. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +0 -173
  131. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_reassurance.dart +0 -45
  132. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +0 -77
  133. package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +0 -392
  134. package/lib/scaffold/features/revenuecat/lib/core/data/api/tracking_api.dart +0 -116
  135. package/lib/scaffold/features/revenuecat/lib/core/data/models/subscription.dart +0 -322
  136. package/lib/scaffold/features/revenuecat/lib/core/home_widgets/home_widget_background_task.dart +0 -41
  137. package/lib/scaffold/features/revenuecat/lib/core/states/user_state_notifier.dart +0 -305
  138. package/templates/firebase/assets/images/splashscreen.png +0 -0
@@ -1,137 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Build and upload iOS IPA to App Store Connect.
3
- # Prefer: kasy ios release (configure first with kasy ios configure)
4
-
5
- set -euo pipefail
6
-
7
- ROOT="$(cd "$(dirname "$0")/.." && pwd)"
8
- cd "$ROOT"
9
-
10
- APPLE_ENV="${ROOT}/.kasy/apple.env"
11
- UPLOAD=1
12
- BUMP=1
13
- VERSION_NAME=""
14
-
15
- while [[ $# -gt 0 ]]; do
16
- case "$1" in
17
- --no-upload) UPLOAD=0; shift ;;
18
- --no-bump) BUMP=0; shift ;;
19
- --version-name)
20
- VERSION_NAME="${2:-}"
21
- shift 2
22
- ;;
23
- *) echo "Unknown option: $1" >&2; exit 1 ;;
24
- esac
25
- done
26
-
27
- if [[ ! -f pubspec.yaml ]]; then
28
- echo "pubspec.yaml not found. Run from the Flutter project root." >&2
29
- exit 1
30
- fi
31
-
32
- check_google_ios_url_scheme() {
33
- local google_plist="${ROOT}/ios/Runner/GoogleService-Info.plist"
34
- local info_plist="${ROOT}/ios/Runner/Info.plist"
35
-
36
- [[ -f "$google_plist" ]] || return 0
37
-
38
- if [[ ! -f "$info_plist" ]]; then
39
- echo "ios/Runner/Info.plist not found." >&2
40
- exit 1
41
- fi
42
-
43
- local reversed_client_id
44
- reversed_client_id="$(/usr/libexec/PlistBuddy -c "Print :REVERSED_CLIENT_ID" "$google_plist" 2>/dev/null || true)"
45
- if [[ -z "$reversed_client_id" ]]; then
46
- echo "REVERSED_CLIENT_ID not found in ios/Runner/GoogleService-Info.plist." >&2
47
- echo "Run flutterfire configure before building iOS." >&2
48
- exit 1
49
- fi
50
-
51
- if ! /usr/libexec/PlistBuddy -c "Print :CFBundleURLTypes" "$info_plist" 2>/dev/null | grep -Fq "$reversed_client_id"; then
52
- echo "Google Sign-In iOS URL scheme mismatch." >&2
53
- echo "Expected CFBundleURLSchemes to include: $reversed_client_id" >&2
54
- echo "Run: kasy ios clean, then rebuild. If it still fails, re-run flutterfire configure/project setup." >&2
55
- exit 1
56
- fi
57
- }
58
-
59
- check_google_ios_url_scheme
60
-
61
- if [[ "$BUMP" -eq 1 ]]; then
62
- BUMP_SCRIPT="${ROOT}/scripts/bump-ios-version.js"
63
- if [[ -f "$BUMP_SCRIPT" ]]; then
64
- if [[ -n "$VERSION_NAME" ]]; then
65
- node "$BUMP_SCRIPT" --version-name "$VERSION_NAME"
66
- else
67
- node "$BUMP_SCRIPT"
68
- fi
69
- else
70
- echo "bump-ios-version.js not found" >&2
71
- exit 1
72
- fi
73
- fi
74
-
75
- load_dart_defines() {
76
- DEFINES_ARGS=()
77
- if [[ -f .dart_defines ]]; then
78
- while IFS= read -r line || [[ -n "$line" ]]; do
79
- [[ -z "$line" || "$line" =~ ^# ]] && continue
80
- DEFINES_ARGS+=("$line")
81
- done < .dart_defines
82
- fi
83
- # Projects generated by Kasy may inline DEFINES in the Makefile instead of .dart_defines
84
- if [[ ${#DEFINES_ARGS[@]} -eq 0 && -f Makefile ]]; then
85
- local block
86
- block="$(awk '/^DEFINES :=/{flag=1;next} flag && /^[^[:space:]#]/{exit} flag{print}' Makefile)"
87
- while IFS= read -r line || [[ -n "$block" ]]; do
88
- line="${line%%\\}" # trim trailing backslash from Makefile continuation
89
- line="$(echo "$line" | xargs)"
90
- [[ -z "$line" || "$line" =~ ^# ]] && continue
91
- DEFINES_ARGS+=("$line")
92
- done <<< "$block"
93
- fi
94
- }
95
-
96
- load_dart_defines
97
-
98
- echo "Building IPA..."
99
- if [[ ${#DEFINES_ARGS[@]} -gt 0 ]]; then
100
- flutter build ipa --release "${DEFINES_ARGS[@]}"
101
- else
102
- flutter build ipa --release
103
- fi
104
-
105
- if [[ "$UPLOAD" -eq 0 ]]; then
106
- echo "IPA built at build/ios/ipa/"
107
- exit 0
108
- fi
109
-
110
- if [[ ! -f "$APPLE_ENV" ]]; then
111
- echo "Missing $APPLE_ENV — run: kasy ios configure" >&2
112
- exit 1
113
- fi
114
-
115
- # shellcheck disable=SC1090
116
- source "$APPLE_ENV"
117
-
118
- if [[ -z "${APP_STORE_API_KEY:-}" || -z "${APP_STORE_ISSUER_ID:-}" ]]; then
119
- echo "APP_STORE_API_KEY and APP_STORE_ISSUER_ID required in .kasy/apple.env" >&2
120
- exit 1
121
- fi
122
-
123
- shopt -s nullglob
124
- IPA=(build/ios/ipa/*.ipa)
125
- shopt -u nullglob
126
-
127
- if [[ ${#IPA[@]} -eq 0 || ! -f "${IPA[0]}" ]]; then
128
- echo "IPA not found in build/ios/ipa/" >&2
129
- exit 1
130
- fi
131
-
132
- echo "Uploading to App Store Connect..."
133
- xcrun altool --upload-app --type ios -f "${IPA[0]}" \
134
- --apiKey "$APP_STORE_API_KEY" \
135
- --apiIssuer "$APP_STORE_ISSUER_ID"
136
-
137
- echo "Upload complete."
@@ -1,301 +0,0 @@
1
- import 'package:flutter/material.dart';
2
- import 'package:flutter_riverpod/flutter_riverpod.dart';
3
- import 'package:kasy_kit/components/components.dart';
4
- import 'package:kasy_kit/core/theme/theme.dart';
5
- import 'package:kasy_kit/core/widgets/kasy_app_bar.dart';
6
- import 'package:kasy_kit/core/widgets/kasy_scroll_behavior.dart';
7
- import 'package:kasy_kit/features/llm_chat/providers/llm_chat_notifier.dart';
8
- import 'package:kasy_kit/i18n/translations.g.dart';
9
-
10
- class LlmChatPage extends ConsumerStatefulWidget {
11
- const LlmChatPage({super.key});
12
-
13
- @override
14
- ConsumerState<LlmChatPage> createState() => _LlmChatPageState();
15
- }
16
-
17
- class _LlmChatPageState extends ConsumerState<LlmChatPage> {
18
- final TextEditingController _controller = TextEditingController();
19
- final ScrollController _scrollController = ScrollController();
20
-
21
- @override
22
- void initState() {
23
- super.initState();
24
- // Scroll to bottom once the history finishes loading.
25
- WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToBottom());
26
- }
27
-
28
- void _sendMessage() {
29
- final prompt = _controller.text.trim();
30
- if (prompt.isEmpty) return;
31
- _controller.clear();
32
- ref.read(llmChatNotifierProvider.notifier).sendMessage(prompt);
33
- }
34
-
35
- void _scrollToBottom() {
36
- WidgetsBinding.instance.addPostFrameCallback((_) {
37
- if (!_scrollController.hasClients) return;
38
- _scrollController.animateTo(
39
- _scrollController.position.maxScrollExtent,
40
- duration: const Duration(milliseconds: 250),
41
- curve: Curves.easeOut,
42
- );
43
- });
44
- }
45
-
46
- @override
47
- void dispose() {
48
- _controller.dispose();
49
- _scrollController.dispose();
50
- super.dispose();
51
- }
52
-
53
- @override
54
- Widget build(BuildContext context) {
55
- // Scroll to bottom whenever the message list changes.
56
- ref.listen(llmChatNotifierProvider, (_, _) => _scrollToBottom());
57
-
58
- final chatAsync = ref.watch(llmChatNotifierProvider);
59
-
60
- return Scaffold(
61
- backgroundColor: context.colors.background,
62
- body: Column(
63
- crossAxisAlignment: CrossAxisAlignment.stretch,
64
- children: [
65
- KasyAppBar(
66
- title: t.llm_chat.title,
67
- ),
68
- Expanded(
69
- child: chatAsync.when(
70
- loading: () => const Center(child: CircularProgressIndicator()),
71
- error: (_, _) =>
72
- Center(child: Text(t.llm_chat.error_network)),
73
- data: (chatState) => _buildBody(context, chatState),
74
- ),
75
- ),
76
- ],
77
- ),
78
- );
79
- }
80
-
81
- Widget _buildBody(BuildContext context, LlmChatState chatState) {
82
- final messages = chatState.messages;
83
- final isReplying = chatState.isReplying;
84
-
85
- // Show the typing indicator only while waiting for the first SSE chunk.
86
- // Once streaming starts the partial assistant bubble is already in [messages].
87
- final showTypingIndicator = isReplying && !chatState.streamingStarted;
88
-
89
- return Column(
90
- children: [
91
- Expanded(
92
- child: messages.isEmpty && !isReplying
93
- ? Center(
94
- child: Text(
95
- t.llm_chat.empty_state,
96
- style: context.textTheme.bodyLarge?.copyWith(
97
- color: context.colors.muted,
98
- ),
99
- ),
100
- )
101
- : ScrollConfiguration(
102
- behavior: const KasyKitScrollBehavior(),
103
- child: ListView.builder(
104
- controller: _scrollController,
105
- padding: const EdgeInsets.fromLTRB(
106
- KasySpacing.pageHorizontalGutter,
107
- KasySpacing.md,
108
- KasySpacing.pageHorizontalGutter,
109
- KasySpacing.md,
110
- ),
111
- // +1 for the typing indicator shown before the first SSE chunk
112
- itemCount: messages.length + (showTypingIndicator ? 1 : 0),
113
- itemBuilder: (context, index) {
114
- if (showTypingIndicator && index == messages.length) {
115
- return const Align(
116
- alignment: Alignment.centerLeft,
117
- child: _TypingIndicator(),
118
- );
119
- }
120
- final message = messages[index];
121
- return _ChatBubble(
122
- message: message,
123
- isUser: message.role == 'user',
124
- );
125
- },
126
- ),
127
- ),
128
- ),
129
- SafeArea(
130
- top: false,
131
- child: Padding(
132
- padding: const EdgeInsets.fromLTRB(
133
- KasySpacing.pageHorizontalGutter,
134
- KasySpacing.sm,
135
- KasySpacing.pageHorizontalGutter,
136
- KasySpacing.md,
137
- ),
138
- child: Row(
139
- crossAxisAlignment: CrossAxisAlignment.end,
140
- children: [
141
- Expanded(
142
- child: KasyTextField(
143
- controller: _controller,
144
- maxLines: 4,
145
- textInputAction: TextInputAction.send,
146
- onSubmitted: (_) => _sendMessage(),
147
- hint: t.llm_chat.hint,
148
- ),
149
- ),
150
- const SizedBox(width: KasySpacing.smd),
151
- SizedBox(
152
- height: 48,
153
- width: 48,
154
- child: ElevatedButton(
155
- style: ElevatedButton.styleFrom(
156
- padding: EdgeInsets.zero,
157
- shape: RoundedRectangleBorder(
158
- borderRadius: KasyRadius.smBorderRadius,
159
- ),
160
- ),
161
- onPressed: isReplying ? null : _sendMessage,
162
- child: isReplying
163
- ? SizedBox(
164
- width: 18,
165
- height: 18,
166
- child: CircularProgressIndicator(
167
- strokeWidth: 2,
168
- color: context.colors.muted,
169
- ),
170
- )
171
- : const Icon(KasyIcons.send, size: 20),
172
- ),
173
- ),
174
- ],
175
- ),
176
- ),
177
- ),
178
- ],
179
- );
180
- }
181
- }
182
-
183
- class _ChatBubble extends StatelessWidget {
184
- const _ChatBubble({required this.message, required this.isUser});
185
-
186
- final ChatMessage message;
187
- final bool isUser;
188
-
189
- @override
190
- Widget build(BuildContext context) {
191
- return Align(
192
- alignment: isUser ? Alignment.centerRight : Alignment.centerLeft,
193
- child: Container(
194
- margin: EdgeInsets.only(
195
- bottom: KasySpacing.sm,
196
- left: isUser ? 48 : 0,
197
- right: isUser ? 0 : 48,
198
- ),
199
- padding: const EdgeInsets.symmetric(
200
- horizontal: KasySpacing.smd,
201
- vertical: KasySpacing.sm,
202
- ),
203
- decoration: BoxDecoration(
204
- color: isUser
205
- ? context.colors.primary
206
- : context.colors.onBackground.withValues(alpha: 0.06),
207
- borderRadius: BorderRadius.only(
208
- topLeft: const Radius.circular(16),
209
- topRight: const Radius.circular(16),
210
- bottomLeft:
211
- isUser ? const Radius.circular(16) : const Radius.circular(4),
212
- bottomRight:
213
- isUser ? const Radius.circular(4) : const Radius.circular(16),
214
- ),
215
- ),
216
- child: Text(
217
- message.content,
218
- style: context.textTheme.bodyMedium?.copyWith(
219
- color:
220
- isUser ? context.colors.onPrimary : context.colors.onBackground,
221
- ),
222
- ),
223
- ),
224
- );
225
- }
226
- }
227
-
228
- /// Animated three-dot indicator shown while the assistant is generating a reply.
229
- class _TypingIndicator extends StatefulWidget {
230
- const _TypingIndicator();
231
-
232
- @override
233
- State<_TypingIndicator> createState() => _TypingIndicatorState();
234
- }
235
-
236
- class _TypingIndicatorState extends State<_TypingIndicator>
237
- with SingleTickerProviderStateMixin {
238
- late final AnimationController _controller;
239
-
240
- @override
241
- void initState() {
242
- super.initState();
243
- _controller = AnimationController(
244
- vsync: this,
245
- duration: const Duration(milliseconds: 900),
246
- )..repeat();
247
- }
248
-
249
- @override
250
- void dispose() {
251
- _controller.dispose();
252
- super.dispose();
253
- }
254
-
255
- @override
256
- Widget build(BuildContext context) {
257
- return Container(
258
- margin: const EdgeInsets.only(bottom: KasySpacing.sm),
259
- padding: const EdgeInsets.symmetric(
260
- horizontal: KasySpacing.smd,
261
- vertical: KasySpacing.sm,
262
- ),
263
- decoration: BoxDecoration(
264
- color: context.colors.onBackground.withValues(alpha: 0.06),
265
- borderRadius: const BorderRadius.only(
266
- topLeft: Radius.circular(16),
267
- topRight: Radius.circular(16),
268
- bottomLeft: Radius.circular(4),
269
- bottomRight: Radius.circular(16),
270
- ),
271
- ),
272
- child: Row(
273
- mainAxisSize: MainAxisSize.min,
274
- spacing: 4,
275
- children: List.generate(3, (i) {
276
- return AnimatedBuilder(
277
- animation: _controller,
278
- builder: (context, _) {
279
- // Each dot bounces at a slightly different phase.
280
- final double phase =
281
- (_controller.value - i * 0.2).clamp(0.0, 1.0);
282
- final double scale = 1.0 +
283
- 0.4 * (phase < 0.5 ? phase * 2 : (1.0 - phase) * 2);
284
- return Transform.scale(
285
- scale: scale,
286
- child: Container(
287
- width: 6,
288
- height: 6,
289
- decoration: BoxDecoration(
290
- shape: BoxShape.circle,
291
- color: context.colors.muted,
292
- ),
293
- ),
294
- );
295
- },
296
- );
297
- }),
298
- ),
299
- );
300
- }
301
- }
@@ -1,81 +0,0 @@
1
- import 'package:kasy_kit/core/states/translations.dart';
2
- import 'package:kasy_kit/features/local_reminder/repositories/reminder_preferences.dart';
3
- import 'package:kasy_kit/features/notifications/api/local_notifier.dart';
4
- import 'package:riverpod_annotation/riverpod_annotation.dart';
5
-
6
- part 'reminder_notifier.g.dart';
7
-
8
- const _kReminderId = 42;
9
-
10
- @Riverpod(keepAlive: true)
11
- class ReminderNotifier extends _$ReminderNotifier {
12
- @override
13
- Future<ReminderState> build() {
14
- return ReminderPreferences.load();
15
- }
16
-
17
- Future<void> setEnabled(bool value) async {
18
- final current = state.requireValue;
19
- await _applyAndSave(current.copyWith(enabled: value));
20
- }
21
-
22
- Future<void> setType(ReminderType type) async {
23
- final current = state.requireValue;
24
- await _applyAndSave(current.copyWith(type: type));
25
- }
26
-
27
- Future<void> setTime(int hour, int minute) async {
28
- final current = state.requireValue;
29
- await _applyAndSave(current.copyWith(hour: hour, minute: minute));
30
- }
31
-
32
- Future<void> setDayOfWeek(int day) async {
33
- final current = state.requireValue;
34
- await _applyAndSave(current.copyWith(dayOfWeek: day));
35
- }
36
-
37
- Future<void> setDate(DateTime date) async {
38
- final current = state.requireValue;
39
- await _applyAndSave(current.copyWith(date: date));
40
- }
41
-
42
- Future<void> _applyAndSave(ReminderState next) async {
43
- state = AsyncData(next);
44
- final notifier = ref.read(localNotifierProvider);
45
- await notifier.cancel(_kReminderId);
46
-
47
- if (next.enabled) {
48
- final tr = ref.read(translationsProvider).dailyReminder;
49
- switch (next.type) {
50
- case ReminderType.daily:
51
- await notifier.scheduleDailyAt(
52
- notificationId: _kReminderId,
53
- title: tr.title,
54
- body: tr.body,
55
- hour: next.hour,
56
- minute: next.minute,
57
- );
58
- case ReminderType.weekly:
59
- await notifier.scheduleWeekly(
60
- notificationId: _kReminderId,
61
- title: tr.title,
62
- body: tr.body,
63
- dayOfWeekIndex: next.dayOfWeek,
64
- hour: next.hour,
65
- minute: next.minute,
66
- );
67
- case ReminderType.specificDate:
68
- if (next.date != null) {
69
- await notifier.scheduleAt(
70
- notificationId: _kReminderId,
71
- title: tr.title,
72
- body: tr.body,
73
- date: next.date!,
74
- );
75
- }
76
- }
77
- }
78
-
79
- await ReminderPreferences.save(next);
80
- }
81
- }
@@ -1,76 +0,0 @@
1
- import 'package:shared_preferences/shared_preferences.dart';
2
-
3
- enum ReminderType { daily, weekly, specificDate }
4
-
5
- class ReminderState {
6
- final bool enabled;
7
- final ReminderType type;
8
- final int hour;
9
- final int minute;
10
- final int dayOfWeek;
11
- final DateTime? date;
12
-
13
- const ReminderState({
14
- this.enabled = false,
15
- this.type = ReminderType.daily,
16
- this.hour = 9,
17
- this.minute = 0,
18
- this.dayOfWeek = 1,
19
- this.date,
20
- });
21
-
22
- ReminderState copyWith({
23
- bool? enabled,
24
- ReminderType? type,
25
- int? hour,
26
- int? minute,
27
- int? dayOfWeek,
28
- DateTime? date,
29
- bool clearDate = false,
30
- }) =>
31
- ReminderState(
32
- enabled: enabled ?? this.enabled,
33
- type: type ?? this.type,
34
- hour: hour ?? this.hour,
35
- minute: minute ?? this.minute,
36
- dayOfWeek: dayOfWeek ?? this.dayOfWeek,
37
- date: clearDate ? null : (date ?? this.date),
38
- );
39
- }
40
-
41
- // ignore: avoid_classes_with_only_static_members
42
- class ReminderPreferences {
43
- static const _enabledKey = 'reminder_enabled';
44
- static const _typeKey = 'reminder_type';
45
- static const _hourKey = 'reminder_hour';
46
- static const _minuteKey = 'reminder_minute';
47
- static const _dayOfWeekKey = 'reminder_day_of_week';
48
- static const _dateKey = 'reminder_date_ms';
49
-
50
- static Future<ReminderState> load() async {
51
- final prefs = await SharedPreferences.getInstance();
52
- final dateMs = prefs.getInt(_dateKey);
53
- return ReminderState(
54
- enabled: prefs.getBool(_enabledKey) ?? false,
55
- type: ReminderType.values[prefs.getInt(_typeKey) ?? 0],
56
- hour: prefs.getInt(_hourKey) ?? 9,
57
- minute: prefs.getInt(_minuteKey) ?? 0,
58
- dayOfWeek: prefs.getInt(_dayOfWeekKey) ?? 1,
59
- date: dateMs != null ? DateTime.fromMillisecondsSinceEpoch(dateMs) : null,
60
- );
61
- }
62
-
63
- static Future<void> save(ReminderState state) async {
64
- final prefs = await SharedPreferences.getInstance();
65
- await prefs.setBool(_enabledKey, state.enabled);
66
- await prefs.setInt(_typeKey, state.type.index);
67
- await prefs.setInt(_hourKey, state.hour);
68
- await prefs.setInt(_minuteKey, state.minute);
69
- await prefs.setInt(_dayOfWeekKey, state.dayOfWeek);
70
- if (state.date != null) {
71
- await prefs.setInt(_dateKey, state.date!.millisecondsSinceEpoch);
72
- } else {
73
- await prefs.remove(_dateKey);
74
- }
75
- }
76
- }