kasy-cli 1.12.1 → 1.14.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 +143 -7
- package/lib/commands/add.js +2 -2
- package/lib/commands/codemagic.js +11 -4
- package/lib/commands/deploy.js +3 -3
- package/lib/commands/favicon.js +115 -0
- package/lib/commands/icon.js +143 -0
- package/lib/commands/ios.js +20 -5
- package/lib/commands/new.js +8 -20
- package/lib/commands/remove.js +1 -1
- package/lib/commands/reset.js +287 -0
- package/lib/commands/run.js +24 -17
- package/lib/commands/splash.js +219 -0
- package/lib/commands/update.js +1 -1
- package/lib/scaffold/CHANGELOG.json +9 -0
- package/lib/scaffold/backends/api/patch/README.md +1 -1
- package/lib/scaffold/backends/api/patch/android/app/src/main/AndroidManifest.xml +1 -1
- package/lib/scaffold/backends/api/patch/lib/features/notifications/api/device_api.dart +53 -0
- package/lib/scaffold/backends/api/patch/lib/main.dart +29 -10
- package/lib/scaffold/backends/api/pubspec.yaml.tpl +11 -1
- package/lib/scaffold/backends/firebase/tokens.js +2 -2
- package/lib/scaffold/backends/supabase/edge-functions/send-push-notification/index.ts +8 -2
- package/lib/scaffold/backends/supabase/migrations/20240101000011_dedupe_device_tokens.sql +34 -0
- package/lib/scaffold/backends/supabase/patch/README.md +1 -1
- package/lib/scaffold/backends/supabase/patch/android/app/src/main/AndroidManifest.xml +1 -1
- package/lib/scaffold/backends/supabase/patch/lib/features/notifications/api/device_api.dart +43 -0
- package/lib/scaffold/backends/supabase/patch/lib/main.dart +29 -10
- package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +11 -1
- package/lib/scaffold/features/README.md +15 -139
- package/lib/scaffold/shared/generator-utils.js +16 -15
- package/lib/utils/apple-release.js +85 -16
- package/lib/utils/checks.js +4 -105
- package/lib/utils/flutter-run.js +173 -0
- package/lib/utils/i18n.js +413 -0
- package/lib/utils/mobile-identity.js +35 -0
- package/lib/utils/ui.js +114 -0
- package/package.json +2 -3
- package/templates/firebase/README.en.md +1 -1
- package/templates/firebase/README.es.md +1 -1
- package/templates/firebase/README.md +1 -1
- package/templates/firebase/android/app/build.gradle.kts +10 -1
- package/templates/firebase/android/app/src/main/AndroidManifest.xml +1 -1
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +25 -1
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +160 -11
- package/templates/firebase/android/app/src/main/res/drawable/widget_add_button.xml +15 -0
- package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_bg.xml +9 -0
- package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_inner.xml +12 -0
- package/templates/firebase/android/app/src/main/res/drawable/widget_plan_pill_bg.xml +5 -0
- package/templates/firebase/android/app/src/main/res/drawable/widget_preview_image.xml +17 -0
- package/templates/firebase/android/app/src/main/res/drawable/widget_pro_pill_bg.xml +5 -0
- 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/layout/widget_loading.xml +8 -0
- package/templates/firebase/android/app/src/main/res/layout/widget_preview.xml +46 -0
- package/templates/firebase/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/templates/firebase/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.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/android/app/src/main/res/xml/mywidget_info.xml +9 -3
- package/templates/firebase/assets/images/favicon.png +0 -0
- package/templates/firebase/assets/images/icon.png +0 -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/firestore.indexes.json +10 -0
- package/templates/firebase/functions/src/core/data/entities/user_device_entity.ts +3 -0
- package/templates/firebase/functions/src/core/data/repositories/user_device_repository.ts +17 -1
- package/templates/firebase/functions/src/index.ts +1 -0
- package/templates/firebase/functions/src/notifications/device_triggers.ts +58 -0
- package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +116 -33
- package/templates/firebase/ios/Runner/AppDelegate.swift +17 -1
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
- package/templates/firebase/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.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/ios/Runner/Info.plist +2 -2
- package/templates/firebase/ios/Runner/es.lproj/InfoPlist.strings +1 -1
- package/templates/firebase/ios/Runner/pt-BR.lproj/InfoPlist.strings +1 -1
- package/templates/firebase/ios/Runner/pt.lproj/InfoPlist.strings +1 -1
- package/templates/firebase/lib/components/kasy_button.dart +8 -0
- package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +18 -0
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +67 -19
- package/templates/firebase/lib/core/home_widgets/home_widget_service.dart +22 -8
- package/templates/firebase/lib/core/shared_preferences/shared_preferences.dart +16 -4
- package/templates/firebase/lib/core/states/components/maybeshow_component.dart +4 -8
- package/templates/firebase/lib/core/states/user_state_notifier.dart +13 -1
- package/templates/firebase/lib/core/theme/providers/theme_provider.dart +48 -24
- package/templates/firebase/lib/features/home/home_page.dart +0 -6
- package/templates/firebase/lib/features/notifications/api/device_api.dart +57 -0
- package/templates/firebase/lib/features/notifications/providers/models/notification.dart +11 -1
- package/templates/firebase/lib/features/notifications/repositories/device_repository.dart +9 -0
- package/templates/firebase/lib/features/notifications/repositories/notifications_repository.dart +1 -4
- package/templates/firebase/lib/features/notifications/shared/att_permission.dart +28 -8
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +16 -1
- package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +44 -11
- 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/features/settings/ui/components/admin/admin_bottom_sheet.dart +31 -29
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +21 -5
- package/templates/firebase/lib/i18n/en.i18n.json +10 -3
- package/templates/firebase/lib/i18n/es.i18n.json +10 -3
- package/templates/firebase/lib/i18n/pt.i18n.json +10 -3
- package/templates/firebase/lib/main.dart +29 -10
- package/templates/firebase/pubspec.yaml +10 -6
- package/templates/firebase/test/core/data/repositories/user_repository_test.dart +1 -1
- package/templates/firebase/test/features/notifications/data/device_api_fake.dart +9 -0
- package/templates/firebase/web/favicon.png +0 -0
- package/templates/firebase/web/icons/Icon-192.png +0 -0
- package/templates/firebase/web/icons/Icon-512.png +0 -0
- package/templates/firebase/web/icons/Icon-maskable-192.png +0 -0
- package/templates/firebase/web/icons/Icon-maskable-512.png +0 -0
- package/templates/firebase/web/index.html +50 -39
- package/templates/firebase/web/manifest.json +3 -3
- 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/app_icon.png +0 -0
- package/templates/firebase/assets/images/onboarding/img1.jpg +0 -0
- package/templates/firebase/assets/images/onboarding/onboarding.png +0 -0
- package/templates/firebase/assets/images/splashscreen.png +0 -0
- package/templates/firebase/lib/core/states/components/maybe_ask_biometric_setup.dart +0 -88
- package/templates/firebase/lib/features/notifications/shared/notification_permission_bottom_sheet.dart +0 -144
|
@@ -54,6 +54,9 @@ class DeviceRepository {
|
|
|
54
54
|
// Without the userId check, a new anonymous user (same device token) would
|
|
55
55
|
// reuse the cached prefs and never register in the database.
|
|
56
56
|
if (device != null && device.token == newDevice.token && cachedUserId == userId) {
|
|
57
|
+
// Heartbeat — keeps the backend's stale-device filter (60d TTL) from
|
|
58
|
+
// dropping this active install. Cheap: one Firestore update per launch.
|
|
59
|
+
await _deviceApi.touch(userId, newDevice.installationId);
|
|
57
60
|
return;
|
|
58
61
|
}
|
|
59
62
|
// Include device properties so the welcome notification fires in the correct locale.
|
|
@@ -62,6 +65,12 @@ class DeviceRepository {
|
|
|
62
65
|
final response = await _deviceApi.register(userId, deviceWithLocale);
|
|
63
66
|
await _saveInPrefs(response);
|
|
64
67
|
await _prefs.setString(_deviceUserIdPrefsKey, userId);
|
|
68
|
+
// Remove orphan devices left by previous installs on the same physical
|
|
69
|
+
// device (different installationId, never touched again). Without this,
|
|
70
|
+
// a re-install (Xcode -> TestFlight, or TestFlight build update with an
|
|
71
|
+
// uninstall in between) leaves stale tokens that still receive push,
|
|
72
|
+
// causing duplicated notifications.
|
|
73
|
+
await _deviceApi.cleanupStaleDevices(userId, newDevice.installationId);
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
Future<void> unregister(String userId) async {
|
package/templates/firebase/lib/features/notifications/repositories/notifications_repository.dart
CHANGED
|
@@ -158,10 +158,7 @@ class AppNotificationsRepository implements NotificationsRepository {
|
|
|
158
158
|
repository: this,
|
|
159
159
|
);
|
|
160
160
|
case PermissionStatus.permanentlyDenied:
|
|
161
|
-
return
|
|
162
|
-
notificationSettings: _notificationSettings,
|
|
163
|
-
repository: this,
|
|
164
|
-
);
|
|
161
|
+
return NotificationPermissionPermanentlyDenied();
|
|
165
162
|
default:
|
|
166
163
|
return NotificationPermissionWaiting(
|
|
167
164
|
notificationSettings: _notificationSettings,
|
|
@@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
4
4
|
import 'package:kasy_kit/components/components.dart';
|
|
5
5
|
import 'package:kasy_kit/core/data/api/tracking_api.dart';
|
|
6
6
|
import 'package:kasy_kit/core/icons/kasy_icons.dart';
|
|
7
|
+
import 'package:kasy_kit/core/shared_preferences/shared_preferences.dart';
|
|
7
8
|
import 'package:kasy_kit/core/states/components/maybeshow_component.dart';
|
|
8
9
|
import 'package:kasy_kit/core/states/models/event_model.dart';
|
|
9
10
|
import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
@@ -13,15 +14,19 @@ import 'package:permission_handler/permission_handler.dart';
|
|
|
13
14
|
|
|
14
15
|
/// Requests App Tracking Transparency (ATT) on iOS if not yet determined.
|
|
15
16
|
///
|
|
16
|
-
///
|
|
17
|
-
///
|
|
18
|
-
///
|
|
19
|
-
///
|
|
20
|
-
///
|
|
17
|
+
/// Apple only allows the native ATT prompt to be shown once per install, so we
|
|
18
|
+
/// gate the system call behind a soft explanation dialog (KasyDialog) first.
|
|
19
|
+
/// If the user dismisses the soft dialog, we back off and ask again later (up
|
|
20
|
+
/// to [_maxSoftAttempts] times, with [_cooldown] between attempts). After the
|
|
21
|
+
/// last attempt we stop asking forever.
|
|
21
22
|
///
|
|
22
23
|
/// To remove: delete this file and remove [MaybeShowAttPermission] from
|
|
23
24
|
/// [HomePage]'s event list.
|
|
24
25
|
class MaybeShowAttPermission implements MaybeShowWithRef {
|
|
26
|
+
static const Duration _settleDelay = Duration(milliseconds: 1500);
|
|
27
|
+
static const int _maxSoftAttempts = 3;
|
|
28
|
+
static const Duration _cooldown = Duration(days: 7);
|
|
29
|
+
|
|
25
30
|
@override
|
|
26
31
|
Future<bool> handle(WidgetRef ref, AppEvent event) async {
|
|
27
32
|
if (kIsWeb) return false;
|
|
@@ -32,12 +37,28 @@ class MaybeShowAttPermission implements MaybeShowWithRef {
|
|
|
32
37
|
// `denied` in permission_handler maps to iOS "notDetermined" — never asked.
|
|
33
38
|
if (!status.isDenied) return false;
|
|
34
39
|
|
|
40
|
+
final prefs = ref.read(sharedPreferencesProvider);
|
|
41
|
+
final attempts = prefs.getAttSoftDismissCount();
|
|
42
|
+
if (attempts >= _maxSoftAttempts) return false;
|
|
43
|
+
|
|
44
|
+
final lastAsked = prefs.getAttSoftLastAskedAt();
|
|
45
|
+
if (lastAsked != null &&
|
|
46
|
+
DateTime.now().difference(lastAsked) < _cooldown) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await Future<void>.delayed(_settleDelay);
|
|
51
|
+
await prefs.setAttSoftLastAskedAt(DateTime.now());
|
|
35
52
|
if (!ref.context.mounted) return false;
|
|
36
53
|
|
|
37
54
|
final confirmed = await _showExplanationDialog(ref.context);
|
|
38
|
-
if (!confirmed)
|
|
55
|
+
if (!confirmed) {
|
|
56
|
+
await prefs.setAttSoftDismissCount(attempts + 1);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
39
59
|
|
|
40
60
|
await Permission.appTrackingTransparency.request();
|
|
61
|
+
await prefs.setAttSoftDismissCount(_maxSoftAttempts);
|
|
41
62
|
|
|
42
63
|
final userId = ref.read(userStateNotifierProvider).user.idOrNull;
|
|
43
64
|
if (userId != null) {
|
|
@@ -45,8 +66,7 @@ class MaybeShowAttPermission implements MaybeShowWithRef {
|
|
|
45
66
|
ref.read(facebookEventApiProvider).initUser(userId).ignore();
|
|
46
67
|
}
|
|
47
68
|
|
|
48
|
-
|
|
49
|
-
return false;
|
|
69
|
+
return true;
|
|
50
70
|
}
|
|
51
71
|
|
|
52
72
|
Future<bool> _showExplanationDialog(BuildContext context) async {
|
|
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|
|
4
4
|
import 'package:flutter/rendering.dart';
|
|
5
5
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
6
6
|
import 'package:kasy_kit/components/components.dart';
|
|
7
|
+
import 'package:kasy_kit/core/bottom_menu/kasy_bart_navigation.dart';
|
|
7
8
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
8
9
|
import 'package:kasy_kit/features/notifications/providers/models/notification.dart'
|
|
9
10
|
as app;
|
|
@@ -20,25 +21,39 @@ class NotificationsPage extends ConsumerStatefulWidget {
|
|
|
20
21
|
ConsumerState<NotificationsPage> createState() => _NotificationsPageState();
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
class _NotificationsPageState extends ConsumerState<NotificationsPage>
|
|
24
|
+
class _NotificationsPageState extends ConsumerState<NotificationsPage>
|
|
25
|
+
with WidgetsBindingObserver {
|
|
24
26
|
final ScrollController _scrollController = ScrollController();
|
|
25
27
|
Timer? _autoReadTimer;
|
|
26
28
|
|
|
27
29
|
@override
|
|
28
30
|
void initState() {
|
|
29
31
|
super.initState();
|
|
32
|
+
WidgetsBinding.instance.addObserver(this);
|
|
30
33
|
_scrollController.addListener(_onScrollChange);
|
|
31
34
|
requestReadAll();
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
@override
|
|
35
38
|
void dispose() {
|
|
39
|
+
WidgetsBinding.instance.removeObserver(this);
|
|
36
40
|
_autoReadTimer?.cancel();
|
|
37
41
|
_scrollController.removeListener(_onScrollChange);
|
|
38
42
|
_scrollController.dispose();
|
|
39
43
|
super.dispose();
|
|
40
44
|
}
|
|
41
45
|
|
|
46
|
+
@override
|
|
47
|
+
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
48
|
+
super.didChangeAppLifecycleState(state);
|
|
49
|
+
if (state == AppLifecycleState.resumed && mounted) {
|
|
50
|
+
// Native permission dialogs push the app to the background briefly. When
|
|
51
|
+
// we come back, the Bart bottom bar can stay hidden because its internal
|
|
52
|
+
// visibility state is reset. Force it back so the user is never stranded.
|
|
53
|
+
kasyShowBottomBar(context);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
42
57
|
void _onScrollChange() {
|
|
43
58
|
final direction = _scrollController.position.userScrollDirection;
|
|
44
59
|
final isScrollingDown = direction == ScrollDirection.reverse;
|
|
@@ -1,16 +1,45 @@
|
|
|
1
1
|
import 'package:flutter/material.dart';
|
|
2
2
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
3
3
|
import 'package:kasy_kit/components/components.dart';
|
|
4
|
+
import 'package:kasy_kit/core/bottom_menu/kasy_bart_navigation.dart';
|
|
4
5
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
5
6
|
import 'package:kasy_kit/features/notifications/providers/models/notification.dart';
|
|
6
7
|
import 'package:kasy_kit/features/notifications/repositories/notifications_repository.dart';
|
|
7
8
|
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
8
9
|
|
|
9
|
-
class EmptyNotifications extends
|
|
10
|
+
class EmptyNotifications extends ConsumerStatefulWidget {
|
|
10
11
|
const EmptyNotifications({super.key});
|
|
11
12
|
|
|
12
13
|
@override
|
|
13
|
-
|
|
14
|
+
ConsumerState<EmptyNotifications> createState() => _EmptyNotificationsState();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class _EmptyNotificationsState extends ConsumerState<EmptyNotifications> {
|
|
18
|
+
late Future<NotificationPermission> _permissionFuture;
|
|
19
|
+
|
|
20
|
+
@override
|
|
21
|
+
void initState() {
|
|
22
|
+
super.initState();
|
|
23
|
+
_permissionFuture = _loadPermission();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Future<NotificationPermission> _loadPermission() {
|
|
27
|
+
return ref.read(notificationRepositoryProvider).getPermissionStatus();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Future<void> _onAskPressed(NotificationPermission permission) async {
|
|
31
|
+
await permission.maybeAsk();
|
|
32
|
+
if (!mounted) return;
|
|
33
|
+
// The native permission dialog pulls the app out of focus; restore the
|
|
34
|
+
// bottom bar defensively before refreshing the local state.
|
|
35
|
+
kasyShowBottomBar(context);
|
|
36
|
+
setState(() {
|
|
37
|
+
_permissionFuture = _loadPermission();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@override
|
|
42
|
+
Widget build(BuildContext context) {
|
|
14
43
|
final tr = context.t.notifications;
|
|
15
44
|
return Padding(
|
|
16
45
|
padding: const EdgeInsets.symmetric(horizontal: KasySpacing.md),
|
|
@@ -50,18 +79,22 @@ class EmptyNotifications extends ConsumerWidget {
|
|
|
50
79
|
),
|
|
51
80
|
const SizedBox(height: KasySpacing.xl),
|
|
52
81
|
FutureBuilder<NotificationPermission>(
|
|
53
|
-
future:
|
|
54
|
-
.read(notificationRepositoryProvider)
|
|
55
|
-
.getPermissionStatus(),
|
|
82
|
+
future: _permissionFuture,
|
|
56
83
|
builder: (context, snapshot) {
|
|
57
|
-
final
|
|
58
|
-
if (
|
|
84
|
+
final permission = snapshot.data;
|
|
85
|
+
if (permission == null) return const SizedBox.shrink();
|
|
86
|
+
if (permission is NotificationPermissionGranted) {
|
|
87
|
+
return const SizedBox.shrink();
|
|
88
|
+
}
|
|
89
|
+
final isLocked =
|
|
90
|
+
permission is NotificationPermissionPermanentlyDenied;
|
|
59
91
|
return KasyButton(
|
|
60
|
-
label: tr.empty_cta,
|
|
92
|
+
label: isLocked ? tr.empty_cta_open_settings : tr.empty_cta,
|
|
61
93
|
variant: KasyButtonVariant.soft,
|
|
62
|
-
icon:
|
|
63
|
-
|
|
64
|
-
|
|
94
|
+
icon: isLocked
|
|
95
|
+
? KasyIcons.settings
|
|
96
|
+
: KasyIcons.notificationAdd,
|
|
97
|
+
onPressed: () => _onAskPressed(permission),
|
|
65
98
|
);
|
|
66
99
|
},
|
|
67
100
|
),
|
|
@@ -5,11 +5,13 @@ import 'package:kasy_kit/i18n/translations.g.dart';
|
|
|
5
5
|
class OnboardingFeatureOne extends StatelessWidget {
|
|
6
6
|
final String nextRoute;
|
|
7
7
|
final VoidCallback? onSkip;
|
|
8
|
+
final VoidCallback? onLogin;
|
|
8
9
|
|
|
9
10
|
const OnboardingFeatureOne({
|
|
10
11
|
super.key,
|
|
11
12
|
required this.nextRoute,
|
|
12
13
|
this.onSkip,
|
|
14
|
+
this.onLogin,
|
|
13
15
|
});
|
|
14
16
|
|
|
15
17
|
@override
|
|
@@ -25,6 +27,8 @@ class OnboardingFeatureOne extends StatelessWidget {
|
|
|
25
27
|
progress: 0.1,
|
|
26
28
|
onSkip: onSkip,
|
|
27
29
|
skipLabel: translations.skip,
|
|
30
|
+
onSecondary: onLogin,
|
|
31
|
+
secondaryLabel: onLogin != null ? translations.login : null,
|
|
28
32
|
);
|
|
29
33
|
}
|
|
30
34
|
}
|
|
@@ -20,6 +20,8 @@ class OnboardingStep extends StatelessWidget {
|
|
|
20
20
|
final bool withBg;
|
|
21
21
|
final VoidCallback? onSkip;
|
|
22
22
|
final String? skipLabel;
|
|
23
|
+
final VoidCallback? onSecondary;
|
|
24
|
+
final String? secondaryLabel;
|
|
23
25
|
|
|
24
26
|
const OnboardingStep({
|
|
25
27
|
super.key,
|
|
@@ -35,6 +37,8 @@ class OnboardingStep extends StatelessWidget {
|
|
|
35
37
|
this.image,
|
|
36
38
|
this.onSkip,
|
|
37
39
|
this.skipLabel,
|
|
40
|
+
this.onSecondary,
|
|
41
|
+
this.secondaryLabel,
|
|
38
42
|
});
|
|
39
43
|
|
|
40
44
|
@override
|
|
@@ -128,6 +132,15 @@ class OnboardingStep extends StatelessWidget {
|
|
|
128
132
|
}
|
|
129
133
|
},
|
|
130
134
|
),
|
|
135
|
+
if (onSecondary != null && secondaryLabel != null) ...[
|
|
136
|
+
const SizedBox(height: KasySpacing.xs),
|
|
137
|
+
KasyButton(
|
|
138
|
+
label: secondaryLabel!,
|
|
139
|
+
variant: KasyButtonVariant.link,
|
|
140
|
+
expand: true,
|
|
141
|
+
onPressed: onSecondary,
|
|
142
|
+
),
|
|
143
|
+
],
|
|
131
144
|
],
|
|
132
145
|
);
|
|
133
146
|
|
|
@@ -426,35 +426,175 @@ class HapticFeedbackSwitcher extends ConsumerWidget {
|
|
|
426
426
|
}
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
-
class ThemeSwitcher extends
|
|
429
|
+
class ThemeSwitcher extends StatelessWidget {
|
|
430
430
|
const ThemeSwitcher({super.key});
|
|
431
431
|
|
|
432
|
+
IconData _iconFor(ThemeMode mode) {
|
|
433
|
+
switch (mode) {
|
|
434
|
+
case ThemeMode.system:
|
|
435
|
+
return KasyIcons.phoneAndroid;
|
|
436
|
+
case ThemeMode.dark:
|
|
437
|
+
return KasyIcons.darkMode;
|
|
438
|
+
case ThemeMode.light:
|
|
439
|
+
return KasyIcons.lightMode;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
432
443
|
@override
|
|
433
|
-
|
|
444
|
+
Widget build(BuildContext context) {
|
|
445
|
+
final tr = context.t.settings;
|
|
446
|
+
final theme = ThemeProvider.of(context);
|
|
447
|
+
final options = <({ThemeMode mode, IconData icon, String label})>[
|
|
448
|
+
(
|
|
449
|
+
mode: ThemeMode.system,
|
|
450
|
+
icon: KasyIcons.phoneAndroid,
|
|
451
|
+
label: tr.theme_option_system,
|
|
452
|
+
),
|
|
453
|
+
(
|
|
454
|
+
mode: ThemeMode.light,
|
|
455
|
+
icon: KasyIcons.lightMode,
|
|
456
|
+
label: tr.theme_option_light,
|
|
457
|
+
),
|
|
458
|
+
(
|
|
459
|
+
mode: ThemeMode.dark,
|
|
460
|
+
icon: KasyIcons.darkMode,
|
|
461
|
+
label: tr.theme_option_dark,
|
|
462
|
+
),
|
|
463
|
+
];
|
|
464
|
+
return Padding(
|
|
465
|
+
padding: const EdgeInsets.symmetric(vertical: KasySpacing.sm),
|
|
466
|
+
child: Column(
|
|
467
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
468
|
+
children: [
|
|
469
|
+
Row(
|
|
470
|
+
children: [
|
|
471
|
+
Icon(
|
|
472
|
+
_iconFor(theme.mode),
|
|
473
|
+
size: 21,
|
|
474
|
+
color: context.colors.onSurface,
|
|
475
|
+
),
|
|
476
|
+
const SizedBox(width: KasySpacing.sm),
|
|
477
|
+
Expanded(
|
|
478
|
+
child: Text(
|
|
479
|
+
tr.theme_title,
|
|
480
|
+
style: context.textTheme.titleMedium?.copyWith(
|
|
481
|
+
color: context.colors.onSurface,
|
|
482
|
+
),
|
|
483
|
+
),
|
|
484
|
+
),
|
|
485
|
+
],
|
|
486
|
+
),
|
|
487
|
+
const SizedBox(height: KasySpacing.sm),
|
|
488
|
+
_ThemeModeSegmented(
|
|
489
|
+
current: theme.mode,
|
|
490
|
+
options: options,
|
|
491
|
+
onChanged: theme.setMode,
|
|
492
|
+
),
|
|
493
|
+
],
|
|
494
|
+
),
|
|
495
|
+
);
|
|
496
|
+
}
|
|
434
497
|
}
|
|
435
498
|
|
|
436
|
-
class
|
|
437
|
-
|
|
499
|
+
class _ThemeModeSegmented extends StatelessWidget {
|
|
500
|
+
const _ThemeModeSegmented({
|
|
501
|
+
required this.current,
|
|
502
|
+
required this.options,
|
|
503
|
+
required this.onChanged,
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
final ThemeMode current;
|
|
507
|
+
final List<({ThemeMode mode, IconData icon, String label})> options;
|
|
508
|
+
final ValueChanged<ThemeMode> onChanged;
|
|
438
509
|
|
|
439
510
|
@override
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
511
|
+
Widget build(BuildContext context) {
|
|
512
|
+
final trackColor = context.colors.onSurface.withValues(alpha: 0.06);
|
|
513
|
+
return Container(
|
|
514
|
+
padding: const EdgeInsets.all(3),
|
|
515
|
+
decoration: BoxDecoration(
|
|
516
|
+
color: trackColor,
|
|
517
|
+
borderRadius: BorderRadius.circular(10),
|
|
518
|
+
),
|
|
519
|
+
child: Row(
|
|
520
|
+
children: [
|
|
521
|
+
for (final opt in options)
|
|
522
|
+
Expanded(
|
|
523
|
+
child: _ThemeModeSegment(
|
|
524
|
+
icon: opt.icon,
|
|
525
|
+
label: opt.label,
|
|
526
|
+
selected: current == opt.mode,
|
|
527
|
+
onTap: () => onChanged(opt.mode),
|
|
528
|
+
),
|
|
529
|
+
),
|
|
530
|
+
],
|
|
531
|
+
),
|
|
532
|
+
);
|
|
446
533
|
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
class _ThemeModeSegment extends StatelessWidget {
|
|
537
|
+
const _ThemeModeSegment({
|
|
538
|
+
required this.icon,
|
|
539
|
+
required this.label,
|
|
540
|
+
required this.selected,
|
|
541
|
+
required this.onTap,
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
final IconData icon;
|
|
545
|
+
final String label;
|
|
546
|
+
final bool selected;
|
|
547
|
+
final VoidCallback onTap;
|
|
447
548
|
|
|
448
549
|
@override
|
|
449
550
|
Widget build(BuildContext context) {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
551
|
+
final selectedBg = context.colors.surface;
|
|
552
|
+
final selectedFg = context.colors.onSurface;
|
|
553
|
+
final unselectedFg = context.colors.muted;
|
|
554
|
+
return Semantics(
|
|
555
|
+
button: true,
|
|
556
|
+
selected: selected,
|
|
557
|
+
label: label,
|
|
558
|
+
child: GestureDetector(
|
|
559
|
+
onTap: onTap,
|
|
560
|
+
behavior: HitTestBehavior.opaque,
|
|
561
|
+
child: AnimatedContainer(
|
|
562
|
+
duration: const Duration(milliseconds: 160),
|
|
563
|
+
curve: Curves.easeOut,
|
|
564
|
+
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
565
|
+
decoration: BoxDecoration(
|
|
566
|
+
color: selected ? selectedBg : Colors.transparent,
|
|
567
|
+
borderRadius: BorderRadius.circular(8),
|
|
568
|
+
boxShadow: selected
|
|
569
|
+
? [
|
|
570
|
+
BoxShadow(
|
|
571
|
+
color: Colors.black.withValues(alpha: 0.06),
|
|
572
|
+
blurRadius: 4,
|
|
573
|
+
offset: const Offset(0, 1),
|
|
574
|
+
),
|
|
575
|
+
]
|
|
576
|
+
: const [],
|
|
577
|
+
),
|
|
578
|
+
child: Column(
|
|
579
|
+
mainAxisSize: MainAxisSize.min,
|
|
580
|
+
children: [
|
|
581
|
+
Icon(
|
|
582
|
+
icon,
|
|
583
|
+
size: 18,
|
|
584
|
+
color: selected ? selectedFg : unselectedFg,
|
|
585
|
+
),
|
|
586
|
+
const SizedBox(height: 2),
|
|
587
|
+
Text(
|
|
588
|
+
label,
|
|
589
|
+
style: context.textTheme.labelSmall?.copyWith(
|
|
590
|
+
color: selected ? selectedFg : unselectedFg,
|
|
591
|
+
fontWeight: selected ? FontWeight.w600 : FontWeight.w500,
|
|
592
|
+
),
|
|
593
|
+
),
|
|
594
|
+
],
|
|
595
|
+
),
|
|
596
|
+
),
|
|
597
|
+
),
|
|
458
598
|
);
|
|
459
599
|
}
|
|
460
600
|
}
|
package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart
CHANGED
|
@@ -11,7 +11,7 @@ import 'package:kasy_kit/core/states/user_state_notifier.dart';
|
|
|
11
11
|
import 'package:kasy_kit/core/toast/toast_service.dart';
|
|
12
12
|
import 'package:kasy_kit/core/web_device_preview/web_device_preview.dart';
|
|
13
13
|
import 'package:kasy_kit/core/widgets/update_bottom_sheet.dart';
|
|
14
|
-
import 'package:kasy_kit/features/notifications/
|
|
14
|
+
import 'package:kasy_kit/features/notifications/api/local_notifier.dart';
|
|
15
15
|
import 'package:kasy_kit/features/settings/settings_page.dart';
|
|
16
16
|
import 'package:kasy_kit/features/settings/ui/components/admin/admin_routes.dart';
|
|
17
17
|
import 'package:kasy_kit/features/settings/ui/widgets/settings_tile.dart';
|
|
@@ -89,30 +89,33 @@ class _AdminSheet extends ConsumerWidget {
|
|
|
89
89
|
);
|
|
90
90
|
},
|
|
91
91
|
),
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
92
|
+
if (kIsWeb) ...[
|
|
93
|
+
const SettingsDivider(),
|
|
94
|
+
ValueListenableBuilder<bool>(
|
|
95
|
+
valueListenable: webDevicePreviewEnabledNotifier,
|
|
96
|
+
builder: (context, enabled, _) {
|
|
97
|
+
return SettingsSwitchTile(
|
|
98
|
+
icon: KasyIcons.phoneAndroid,
|
|
99
|
+
title: t.settings.admin.device_preview_title,
|
|
100
|
+
value: enabled,
|
|
101
|
+
onChanged: (v) async {
|
|
102
|
+
final navigator = Navigator.of(
|
|
103
|
+
context,
|
|
104
|
+
rootNavigator: true,
|
|
105
|
+
);
|
|
106
|
+
final p =
|
|
107
|
+
await SharedPreferences.getInstance();
|
|
108
|
+
await p.setBool(
|
|
109
|
+
webDevicePreviewEnabledPrefKey,
|
|
110
|
+
v,
|
|
111
|
+
);
|
|
112
|
+
webDevicePreviewEnabledNotifier.value = v;
|
|
113
|
+
if (v) navigator.pop();
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
},
|
|
117
|
+
),
|
|
118
|
+
],
|
|
116
119
|
const SettingsDivider(),
|
|
117
120
|
SettingsTile(
|
|
118
121
|
icon: KasyIcons.note,
|
|
@@ -186,10 +189,9 @@ class _AdminSheet extends ConsumerWidget {
|
|
|
186
189
|
title: t.settings.admin.ask_notification,
|
|
187
190
|
onTap: () {
|
|
188
191
|
Navigator.pop(context);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
);
|
|
192
|
+
ref
|
|
193
|
+
.read(notificationsSettingsProvider)
|
|
194
|
+
.askPermission();
|
|
193
195
|
},
|
|
194
196
|
),
|
|
195
197
|
const SettingsDivider(),
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
|
|
1
3
|
import 'package:flutter/material.dart';
|
|
2
4
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
5
|
+
import 'package:kasy_kit/core/home_widgets/home_widget_mywidget_service.dart';
|
|
3
6
|
import 'package:kasy_kit/core/shared_preferences/shared_preferences.dart';
|
|
4
7
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
5
8
|
import 'package:kasy_kit/features/settings/ui/widgets/settings_tile.dart';
|
|
@@ -93,12 +96,25 @@ class LanguageSwitcher extends ConsumerWidget {
|
|
|
93
96
|
return _LocaleOptionTile(
|
|
94
97
|
locale: locale,
|
|
95
98
|
isSelected: isSelected,
|
|
96
|
-
onTap: ()
|
|
99
|
+
onTap: () {
|
|
100
|
+
// Close the sheet FIRST. Awaiting work before pop
|
|
101
|
+
// races with the rebuild triggered by setLocale and
|
|
102
|
+
// crashed Navigator.pop with !_debugLocked.
|
|
103
|
+
Navigator.pop(sheetContext);
|
|
97
104
|
LocaleSettings.setLocale(locale);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
// Re-render the home widget in the new language —
|
|
106
|
+
// fire-and-forget so the UI doesn't wait on a
|
|
107
|
+
// network call (RevenueCat) inside update().
|
|
108
|
+
unawaited(
|
|
109
|
+
ref
|
|
110
|
+
.read(sharedPreferencesProvider)
|
|
111
|
+
.setAppLocale(locale.languageCode),
|
|
112
|
+
);
|
|
113
|
+
unawaited(
|
|
114
|
+
ref
|
|
115
|
+
.read(myWidgetHomeWidgetProvider.notifier)
|
|
116
|
+
.updateForLocale(locale),
|
|
117
|
+
);
|
|
102
118
|
},
|
|
103
119
|
);
|
|
104
120
|
}),
|
|
@@ -175,7 +175,8 @@
|
|
|
175
175
|
"title": "Subscriptions module",
|
|
176
176
|
"description": "Manage subscriptions with premade paywalls",
|
|
177
177
|
"action": "Continue",
|
|
178
|
-
"skip": "Skip"
|
|
178
|
+
"skip": "Skip",
|
|
179
|
+
"login": "Already have an account? Log in"
|
|
179
180
|
},
|
|
180
181
|
"feature_2": {
|
|
181
182
|
"title": "Authentication module",
|
|
@@ -321,7 +322,10 @@
|
|
|
321
322
|
"cancel": "Cancel"
|
|
322
323
|
},
|
|
323
324
|
"language_title": "Languages",
|
|
324
|
-
"theme_title": "
|
|
325
|
+
"theme_title": "Theme",
|
|
326
|
+
"theme_option_system": "System",
|
|
327
|
+
"theme_option_light": "Light",
|
|
328
|
+
"theme_option_dark": "Dark",
|
|
325
329
|
"haptic_feedback_title": "Haptic feedback",
|
|
326
330
|
"section_preferences_label": "PREFERENCES",
|
|
327
331
|
"section_security_label": "SECURITY",
|
|
@@ -439,6 +443,7 @@
|
|
|
439
443
|
"group_yesterday": "Yesterday",
|
|
440
444
|
"group_older": "Older",
|
|
441
445
|
"empty_cta": "Enable notifications",
|
|
446
|
+
"empty_cta_open_settings": "Open settings",
|
|
442
447
|
"delete_all": "Delete all",
|
|
443
448
|
"delete_all_confirm_title": "Delete all notifications?",
|
|
444
449
|
"delete_all_confirm_message": "This will permanently remove every notification from your account. This cannot be undone.",
|
|
@@ -523,8 +528,10 @@
|
|
|
523
528
|
"greeting_evening": "Good evening",
|
|
524
529
|
"title_with_name": "Hi, $name!",
|
|
525
530
|
"title_default": "Hi there!",
|
|
531
|
+
"title_logged_out": "We'll be here when you're back",
|
|
526
532
|
"plan_free": "Free plan",
|
|
527
|
-
"plan_pro": "PRO"
|
|
533
|
+
"plan_pro": "PRO",
|
|
534
|
+
"quote": "It always seems impossible until it's done."
|
|
528
535
|
}
|
|
529
536
|
}
|
|
530
537
|
|