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.
- package/bin/kasy.js +21 -0
- package/lib/commands/splash.js +220 -0
- package/lib/scaffold/CHANGELOG.json +9 -0
- package/lib/scaffold/backends/api/patch/lib/main.dart +29 -10
- package/lib/scaffold/backends/supabase/patch/lib/main.dart +29 -10
- package/lib/scaffold/features/README.md +15 -139
- package/lib/scaffold/shared/generator-utils.js +16 -15
- package/lib/utils/i18n.js +78 -0
- package/package.json +2 -2
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-hdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-mdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night/launch_background.xml +9 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-hdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-mdpi/splash.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-night-v21/launch_background.xml +9 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-night-xxxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/android12splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/drawable-xxxhdpi/splash.png +0 -0
- package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +2 -1
- package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -0
- package/templates/firebase/assets/images/splash_logo_dark.png +0 -0
- package/templates/firebase/assets/images/splash_logo_light.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json +9 -8
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +33 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png +0 -0
- package/templates/firebase/ios/Runner/Base.lproj/LaunchScreen.storyboard +1 -1
- package/templates/firebase/lib/core/theme/providers/theme_provider.dart +48 -24
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +4 -0
- package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +1 -0
- package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +13 -0
- package/templates/firebase/lib/features/settings/settings_page.dart +158 -18
- package/templates/firebase/lib/i18n/en.i18n.json +6 -2
- package/templates/firebase/lib/i18n/es.i18n.json +6 -2
- package/templates/firebase/lib/i18n/pt.i18n.json +6 -2
- package/templates/firebase/lib/main.dart +29 -10
- package/templates/firebase/pubspec.yaml +4 -5
- package/templates/firebase/test/core/data/repositories/user_repository_test.dart +1 -1
- package/templates/firebase/web/index.html +47 -39
- package/templates/firebase/web/splash/img/dark-1x.png +0 -0
- package/templates/firebase/web/splash/img/dark-2x.png +0 -0
- package/templates/firebase/web/splash/img/dark-3x.png +0 -0
- package/templates/firebase/web/splash/img/dark-4x.png +0 -0
- package/templates/firebase/web/splash/img/light-1x.png +0 -0
- package/templates/firebase/web/splash/img/light-2x.png +0 -0
- package/templates/firebase/web/splash/img/light-3x.png +0 -0
- package/templates/firebase/web/splash/img/light-4x.png +0 -0
- package/lib/scaffold/features/analytics/lib/core/data/api/analytics_api.dart +0 -124
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.es.md +0 -35
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.md +0 -35
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/BUG.pt.md +0 -35
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.es.md +0 -12
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.md +0 -12
- package/lib/scaffold/features/ci/.github/ISSUE_TEMPLATE/feature_request.pt.md +0 -12
- package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.es.md +0 -17
- package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.md +0 -17
- package/lib/scaffold/features/ci/.github/PULL_REQUEST_TEMPLATE.pt.md +0 -17
- package/lib/scaffold/features/ci/.github/dependabot.yml +0 -16
- package/lib/scaffold/features/ci/.github/workflows/app.yml +0 -20
- package/lib/scaffold/features/ci/.gitlab/templates/deploy.yaml +0 -14
- package/lib/scaffold/features/ci/.gitlab/templates/dropbox.yaml +0 -19
- package/lib/scaffold/features/ci/.gitlab/templates/flutter.yaml +0 -163
- package/lib/scaffold/features/ci/.gitlab/templates/mailgun.yaml +0 -28
- package/lib/scaffold/features/ci/.gitlab-ci.yml +0 -37
- package/lib/scaffold/features/ci/codemagic.yaml +0 -157
- package/lib/scaffold/features/facebook/lib/core/data/api/tracking_api.dart +0 -111
- package/lib/scaffold/features/feedback/lib/features/feedbacks/api/entities/feature_request_entity.dart +0 -27
- package/lib/scaffold/features/feedback/lib/features/feedbacks/api/entities/feature_vote_entity.dart +0 -27
- package/lib/scaffold/features/feedback/lib/features/feedbacks/api/feature_request_api.dart +0 -50
- package/lib/scaffold/features/feedback/lib/features/feedbacks/api/feature_vote_api.dart +0 -79
- package/lib/scaffold/features/feedback/lib/features/feedbacks/models/feature_requests.dart +0 -48
- package/lib/scaffold/features/feedback/lib/features/feedbacks/models/feedback_state.dart +0 -42
- package/lib/scaffold/features/feedback/lib/features/feedbacks/providers/feedback_page_notifier.dart +0 -147
- package/lib/scaffold/features/feedback/lib/features/feedbacks/repositories/feature_request_repository.dart +0 -95
- package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/component/add_feature_form.dart +0 -199
- package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/feedback_page.dart +0 -175
- package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/widgets/add_feature_button.dart +0 -76
- package/lib/scaffold/features/feedback/lib/features/feedbacks/ui/widgets/feature_card.dart +0 -279
- package/lib/scaffold/features/ios-release/.kasy/apple.env.example +0 -8
- package/lib/scaffold/features/ios-release/.kasy/codemagic.env.example +0 -7
- package/lib/scaffold/features/ios-release/docs/codemagic-release.en.md +0 -50
- package/lib/scaffold/features/ios-release/docs/codemagic-release.es.md +0 -50
- package/lib/scaffold/features/ios-release/docs/codemagic-release.pt.md +0 -50
- package/lib/scaffold/features/ios-release/docs/ios-release.en.md +0 -41
- package/lib/scaffold/features/ios-release/docs/ios-release.es.md +0 -41
- package/lib/scaffold/features/ios-release/docs/ios-release.pt.md +0 -41
- package/lib/scaffold/features/ios-release/scripts/bump-ios-version.js +0 -38
- package/lib/scaffold/features/ios-release/scripts/release-ios.sh +0 -137
- package/lib/scaffold/features/llm_chat/lib/features/llm_chat/llm_chat_page.dart +0 -301
- package/lib/scaffold/features/local_notifications/lib/features/local_reminder/providers/reminder_notifier.dart +0 -81
- package/lib/scaffold/features/local_notifications/lib/features/local_reminder/repositories/reminder_preferences.dart +0 -76
- package/lib/scaffold/features/local_notifications/lib/features/local_reminder/ui/reminder_page.dart +0 -282
- package/lib/scaffold/features/onboarding/lib/features/onboarding/api/entities/user_info_entity.dart +0 -24
- package/lib/scaffold/features/onboarding/lib/features/onboarding/api/user_infos_api.dart +0 -71
- package/lib/scaffold/features/onboarding/lib/features/onboarding/models/user_info.dart +0 -92
- package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_model.dart +0 -15
- package/lib/scaffold/features/onboarding/lib/features/onboarding/providers/onboarding_provider.dart +0 -78
- package/lib/scaffold/features/onboarding/lib/features/onboarding/repositories/user_infos_repository.dart +0 -29
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/animations/page_transitions.dart +0 -30
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_att_setup.dart +0 -66
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_features.dart +0 -72
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_loader.dart +0 -92
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +0 -73
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/components/onboarding_questions.dart +0 -89
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/onboarding_page.dart +0 -94
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_background.dart +0 -80
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_feature.dart +0 -139
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +0 -110
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_progress.dart +0 -84
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +0 -173
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_reassurance.dart +0 -45
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +0 -77
- package/lib/scaffold/features/onboarding/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +0 -392
- package/lib/scaffold/features/revenuecat/lib/core/data/api/tracking_api.dart +0 -116
- package/lib/scaffold/features/revenuecat/lib/core/data/models/subscription.dart +0 -322
- package/lib/scaffold/features/revenuecat/lib/core/home_widgets/home_widget_background_task.dart +0 -41
- package/lib/scaffold/features/revenuecat/lib/core/states/user_state_notifier.dart +0 -305
- 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
|
-
}
|