kasy-cli 1.17.0 → 1.19.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 +16 -2
- package/lib/commands/add.js +7 -7
- package/lib/commands/configure.js +548 -0
- package/lib/commands/deploy.js +4 -4
- package/lib/commands/doctor.js +17 -0
- package/lib/commands/favicon.js +4 -4
- package/lib/commands/icon.js +5 -5
- package/lib/commands/new.js +483 -324
- package/lib/commands/run.js +17 -4
- package/lib/commands/splash.js +5 -5
- package/lib/commands/update.js +9 -9
- package/lib/scaffold/CHANGELOG.json +14 -0
- package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +108 -0
- package/lib/scaffold/backends/firebase/setup-from-scratch.js +123 -5
- package/lib/scaffold/generate.js +24 -8
- package/lib/scaffold/shared/post-build.js +8 -0
- package/lib/utils/brand.js +16 -12
- package/lib/utils/flutter-run.js +139 -11
- package/lib/utils/i18n/messages-en.js +62 -5
- package/lib/utils/i18n/messages-es.js +62 -5
- package/lib/utils/i18n/messages-pt.js +63 -6
- package/lib/utils/ui.js +79 -4
- package/package.json +1 -2
- 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/src/main/kotlin/com/aicrus/firebase/kit/MainActivity.kt +0 -15
- package/templates/firebase/android/app/src/main/kotlin/com/aicrus/firebase/kit/MyWidget.kt +65 -26
- package/templates/firebase/android/app/src/main/res/drawable/widget_add_button.xml +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_bg.xml +7 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_gradient_inner.xml +6 -6
- package/templates/firebase/android/app/src/main/res/drawable/widget_plan_pill_bg.xml +1 -1
- package/templates/firebase/android/app/src/main/res/drawable/widget_preview_image.xml +2 -2
- package/templates/firebase/android/app/src/main/res/drawable/widget_pro_pill_bg.xml +1 -1
- 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-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-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_preview.xml +3 -3
- package/templates/firebase/android/app/src/main/res/values/colors.xml +32 -0
- package/templates/firebase/assets/images/splash_logo_dark.png +0 -0
- package/templates/firebase/assets/images/splash_logo_dark_android12.png +0 -0
- package/templates/firebase/assets/images/splash_logo_light.png +0 -0
- package/templates/firebase/assets/images/splash_logo_light_android12.png +0 -0
- package/templates/firebase/ios/HomeWidgetExtension/MyWidget.swift +75 -29
- 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/components/components.dart +1 -0
- package/templates/firebase/lib/components/kasy_avatar.dart +65 -17
- package/templates/firebase/lib/components/kasy_avatar_presets.dart +121 -97
- package/templates/firebase/lib/components/kasy_button.dart +8 -8
- package/templates/firebase/lib/components/kasy_date_picker.dart +2173 -0
- package/templates/firebase/lib/components/kasy_tabs.dart +214 -91
- package/templates/firebase/lib/components/kasy_text_area.dart +9 -4
- package/templates/firebase/lib/components/kasy_text_field.dart +96 -36
- package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -2
- package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +88 -35
- package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +7 -43
- package/templates/firebase/lib/core/dev_inspector/dev_inspector.dart +118 -16
- package/templates/firebase/lib/core/dev_inspector/dev_inspector_service.dart +14 -20
- package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +12 -18
- package/templates/firebase/lib/core/security/secured_storage.dart +56 -15
- package/templates/firebase/lib/core/theme/providers/theme_provider.dart +3 -0
- package/templates/firebase/lib/core/theme/web_background_sync.dart +3 -0
- package/templates/firebase/lib/core/theme/web_background_sync_web.dart +18 -0
- package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +3 -6
- package/templates/firebase/lib/features/authentication/api/authentication_api.dart +6 -0
- package/templates/firebase/lib/features/home/home_components_page.dart +3 -2
- package/templates/firebase/lib/features/home/home_components_preview_registry.dart +949 -77
- package/templates/firebase/lib/features/home/home_page.dart +17 -40
- package/templates/firebase/lib/features/notifications/ui/notifications_page.dart +1 -16
- package/templates/firebase/lib/features/notifications/ui/widgets/empty_notifications.dart +0 -4
- package/templates/firebase/lib/features/settings/ui/components/language_switcher.dart +10 -5
- package/templates/firebase/lib/i18n/en.i18n.json +2 -1
- package/templates/firebase/lib/i18n/es.i18n.json +2 -1
- package/templates/firebase/lib/i18n/pt.i18n.json +2 -1
- package/templates/firebase/lib/main.dart +34 -34
- package/templates/firebase/pubspec.yaml +2 -1
- package/templates/firebase/storage.cors.json +8 -0
- package/templates/firebase/web/index.html +24 -2
- 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/templates/firebase/lib/core/bottom_menu/kasy_bart_navigation.dart +0 -22
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import 'package:bart/bart.dart' as bart;
|
|
2
|
+
import 'package:bart/bart/bart_bottombar_actions.dart';
|
|
2
3
|
import 'package:flutter/foundation.dart';
|
|
3
4
|
import 'package:flutter/material.dart';
|
|
4
5
|
import 'package:flutter/services.dart';
|
|
@@ -8,22 +9,92 @@ import 'package:kasy_kit/core/sidebar/kasy_sidebar.dart';
|
|
|
8
9
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
9
10
|
import 'package:kasy_kit/core/widgets/responsive_layout.dart';
|
|
10
11
|
|
|
11
|
-
///
|
|
12
|
-
///
|
|
13
|
-
///
|
|
14
|
-
///
|
|
15
|
-
///
|
|
16
|
-
|
|
12
|
+
/// Bottom navigation host powered by Bart (https://pub.dev/packages/bart).
|
|
13
|
+
///
|
|
14
|
+
/// Bart stores its bottom-bar visibility inside a [ValueNotifier] created in
|
|
15
|
+
/// the [bart.BartScaffold] constructor. Because [ResponsiveLayout] recreates
|
|
16
|
+
/// one of three [bart.BartScaffold]s on every rebuild (breakpoint change,
|
|
17
|
+
/// theme toggle, device-preview resize), that notifier resets to its initial
|
|
18
|
+
/// value and a previously-hidden bar can come back wrong — or, when returning
|
|
19
|
+
/// from a native overlay (FaceID, photo picker, permission dialog), Bart's
|
|
20
|
+
/// internal state ends up out of sync with the visible route.
|
|
21
|
+
///
|
|
22
|
+
/// This widget owns visibility end-to-end so feature pages never need to
|
|
23
|
+
/// touch it. The rules are simple:
|
|
24
|
+
/// * Bottom-bar tab (1 path segment) → bar visible.
|
|
25
|
+
/// * Inner route (multi-segment, or [bart.BartMenuRoute.showBottomBar] =
|
|
26
|
+
/// false) → bar hidden.
|
|
27
|
+
///
|
|
28
|
+
/// Sync points: [onRouteChanged], post-frame after every [build] (catches
|
|
29
|
+
/// scaffold rebuilds), and [didChangeAppLifecycleState] on resume.
|
|
30
|
+
class BottomMenu extends StatefulWidget {
|
|
17
31
|
final String? initialRoute;
|
|
18
32
|
|
|
19
33
|
const BottomMenu({super.key, this.initialRoute});
|
|
20
34
|
|
|
35
|
+
@override
|
|
36
|
+
State<BottomMenu> createState() => _BottomMenuState();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class _BottomMenuState extends State<BottomMenu>
|
|
40
|
+
with WidgetsBindingObserver, BartNotifier {
|
|
41
|
+
String? _currentRoutePath;
|
|
42
|
+
|
|
43
|
+
@override
|
|
44
|
+
void initState() {
|
|
45
|
+
super.initState();
|
|
46
|
+
WidgetsBinding.instance.addObserver(this);
|
|
47
|
+
_currentRoutePath = _resolveInitialRoute(widget.initialRoute);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@override
|
|
51
|
+
void dispose() {
|
|
52
|
+
WidgetsBinding.instance.removeObserver(this);
|
|
53
|
+
super.dispose();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@override
|
|
57
|
+
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
58
|
+
super.didChangeAppLifecycleState(state);
|
|
59
|
+
if (state == AppLifecycleState.resumed) {
|
|
60
|
+
_scheduleSync();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void _onRouteChanged(bart.BartMenuRoute route) {
|
|
65
|
+
_currentRoutePath = route.path;
|
|
66
|
+
_scheduleSync();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
void _scheduleSync() {
|
|
70
|
+
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
71
|
+
if (!mounted) return;
|
|
72
|
+
_applyVisibility();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
void _applyVisibility() {
|
|
77
|
+
if (_shouldShowBottomBar(_currentRoutePath)) {
|
|
78
|
+
showBottomBar(context);
|
|
79
|
+
} else {
|
|
80
|
+
hideBottomBar(context);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
21
84
|
@override
|
|
22
85
|
Widget build(BuildContext context) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
86
|
+
// Re-assert visibility after every rebuild: the Bart scaffold underneath
|
|
87
|
+
// may have been freshly instantiated (see class doc).
|
|
88
|
+
_scheduleSync();
|
|
89
|
+
|
|
90
|
+
final String? resolvedInitialRoute = _resolveInitialRoute(
|
|
91
|
+
widget.initialRoute,
|
|
92
|
+
);
|
|
93
|
+
final bool showBottomBarOnStart = _shouldShowBottomBar(resolvedInitialRoute);
|
|
94
|
+
final scaffoldOptions = bart.ScaffoldOptions(
|
|
95
|
+
backgroundColor: context.colors.background,
|
|
26
96
|
);
|
|
97
|
+
|
|
27
98
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
|
28
99
|
value: switch (Theme.brightnessOf(context)) {
|
|
29
100
|
Brightness.dark => SystemUiOverlayStyle.light,
|
|
@@ -35,13 +106,8 @@ class BottomMenu extends StatelessWidget {
|
|
|
35
106
|
bottomBar: kasyPaddedSurfaceBottomBar(),
|
|
36
107
|
initialRoute: resolvedInitialRoute,
|
|
37
108
|
showBottomBarOnStart: showBottomBarOnStart,
|
|
38
|
-
scaffoldOptions:
|
|
39
|
-
|
|
40
|
-
),
|
|
41
|
-
onRouteChanged: (route) {
|
|
42
|
-
// If you want to log tab events to analytics
|
|
43
|
-
// MixpanelAnalyticsApi.instance().logEvent('home/$route', {});
|
|
44
|
-
},
|
|
109
|
+
scaffoldOptions: scaffoldOptions,
|
|
110
|
+
onRouteChanged: _onRouteChanged,
|
|
45
111
|
),
|
|
46
112
|
// medium (768–1024 px): icon-only collapsed rail
|
|
47
113
|
medium: bart.BartScaffold(
|
|
@@ -49,16 +115,11 @@ class BottomMenu extends StatelessWidget {
|
|
|
49
115
|
bottomBar: kasyPaddedSurfaceBottomBar(),
|
|
50
116
|
initialRoute: resolvedInitialRoute,
|
|
51
117
|
showBottomBarOnStart: showBottomBarOnStart,
|
|
52
|
-
scaffoldOptions:
|
|
53
|
-
backgroundColor: context.colors.background,
|
|
54
|
-
),
|
|
118
|
+
scaffoldOptions: scaffoldOptions,
|
|
55
119
|
sideBarOptions: bart.CustomSideBarOptions(
|
|
56
120
|
sideBarBuilder: kasySidebarCollapsedBuilder,
|
|
57
121
|
),
|
|
58
|
-
onRouteChanged:
|
|
59
|
-
// If you want to log tab events to analytics
|
|
60
|
-
// MixpanelAnalyticsApi.instance().logEvent('home/$route', {});
|
|
61
|
-
},
|
|
122
|
+
onRouteChanged: _onRouteChanged,
|
|
62
123
|
),
|
|
63
124
|
// large (1024 px+): full expanded sidebar
|
|
64
125
|
large: bart.BartScaffold(
|
|
@@ -66,16 +127,11 @@ class BottomMenu extends StatelessWidget {
|
|
|
66
127
|
bottomBar: kasyPaddedSurfaceBottomBar(),
|
|
67
128
|
initialRoute: resolvedInitialRoute,
|
|
68
129
|
showBottomBarOnStart: showBottomBarOnStart,
|
|
69
|
-
scaffoldOptions:
|
|
70
|
-
backgroundColor: context.colors.background,
|
|
71
|
-
),
|
|
130
|
+
scaffoldOptions: scaffoldOptions,
|
|
72
131
|
sideBarOptions: bart.CustomSideBarOptions(
|
|
73
132
|
sideBarBuilder: kasySidebarBuilder,
|
|
74
133
|
),
|
|
75
|
-
onRouteChanged:
|
|
76
|
-
// If you want to log tab events to analytics
|
|
77
|
-
// MixpanelAnalyticsApi.instance().logEvent('home/$route', {});
|
|
78
|
-
},
|
|
134
|
+
onRouteChanged: _onRouteChanged,
|
|
79
135
|
),
|
|
80
136
|
),
|
|
81
137
|
);
|
|
@@ -114,14 +170,11 @@ class BottomMenu extends StatelessWidget {
|
|
|
114
170
|
return path;
|
|
115
171
|
}
|
|
116
172
|
|
|
117
|
-
bool
|
|
173
|
+
bool _shouldShowBottomBar(String? route) {
|
|
118
174
|
if (route == null) {
|
|
119
175
|
return true;
|
|
120
176
|
}
|
|
121
177
|
final segments = Uri.parse(route).pathSegments;
|
|
122
|
-
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
return false;
|
|
178
|
+
return segments.length < 2;
|
|
126
179
|
}
|
|
127
180
|
}
|
|
@@ -2,7 +2,6 @@ import 'package:bart/bart.dart';
|
|
|
2
2
|
import 'package:flutter/material.dart';
|
|
3
3
|
import 'package:kasy_kit/components/kasy_app_bar.dart';
|
|
4
4
|
import 'package:kasy_kit/core/bottom_menu/bart_inner_paths.dart';
|
|
5
|
-
import 'package:kasy_kit/core/bottom_menu/kasy_bart_navigation.dart';
|
|
6
5
|
import 'package:kasy_kit/core/bottom_menu/notification_bottom_item.dart';
|
|
7
6
|
import 'package:kasy_kit/core/navigation/kasy_navigation_config.dart';
|
|
8
7
|
import 'package:kasy_kit/core/navigation/kasy_route_transition.dart';
|
|
@@ -110,53 +109,18 @@ List<BartMenuRoute> subRoutes() {
|
|
|
110
109
|
];
|
|
111
110
|
}
|
|
112
111
|
|
|
113
|
-
|
|
112
|
+
/// Marker wrapper for inner home routes. Visibility is owned by
|
|
113
|
+
/// [BottomMenu] (see [BottomMenu._onRouteChanged] + [BartMenuRoute.innerRoute]
|
|
114
|
+
/// `showBottomBar: false`); this widget exists for symmetry with the dashboard
|
|
115
|
+
/// cards and to keep `HomeFeaturesPage` / `HomeComponentsPage` / etc. agnostic
|
|
116
|
+
/// of how they were pushed.
|
|
117
|
+
class _HomeInnerRoute extends StatelessWidget {
|
|
114
118
|
final Widget child;
|
|
115
119
|
|
|
116
120
|
const _HomeInnerRoute({required this.child});
|
|
117
121
|
|
|
118
122
|
@override
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
class _HomeInnerRouteState extends State<_HomeInnerRoute> {
|
|
123
|
-
ModalRoute<Object?>? _route;
|
|
124
|
-
|
|
125
|
-
@override
|
|
126
|
-
void didChangeDependencies() {
|
|
127
|
-
super.didChangeDependencies();
|
|
128
|
-
final ModalRoute<Object?>? route = ModalRoute.of(context);
|
|
129
|
-
if (_route != route) {
|
|
130
|
-
_route?.animation?.removeStatusListener(_onRouteAnimStatus);
|
|
131
|
-
_route = route;
|
|
132
|
-
final Animation<double>? anim = route?.animation;
|
|
133
|
-
if (anim != null && !anim.isCompleted) {
|
|
134
|
-
anim.addStatusListener(_onRouteAnimStatus);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
kasyHideBottomBar(context);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
void _onRouteAnimStatus(AnimationStatus status) {
|
|
141
|
-
if (mounted && status == AnimationStatus.completed) {
|
|
142
|
-
kasyHideBottomBar(context);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
@override
|
|
147
|
-
void dispose() {
|
|
148
|
-
_route?.animation?.removeStatusListener(_onRouteAnimStatus);
|
|
149
|
-
super.dispose();
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
@override
|
|
153
|
-
void reassemble() {
|
|
154
|
-
super.reassemble();
|
|
155
|
-
kasyHideBottomBar(context);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
@override
|
|
159
|
-
Widget build(BuildContext context) => widget.child;
|
|
123
|
+
Widget build(BuildContext context) => child;
|
|
160
124
|
}
|
|
161
125
|
|
|
162
126
|
/// Placeholder page for the wishlist tab.
|
|
@@ -2,6 +2,7 @@ import 'dart:async';
|
|
|
2
2
|
|
|
3
3
|
import 'package:flutter/foundation.dart';
|
|
4
4
|
import 'package:flutter/material.dart';
|
|
5
|
+
import 'package:flutter/scheduler.dart';
|
|
5
6
|
import 'package:flutter/services.dart';
|
|
6
7
|
import 'package:kasy_kit/core/dev_inspector/dev_inspector_info.dart';
|
|
7
8
|
import 'package:kasy_kit/core/dev_inspector/dev_inspector_service.dart';
|
|
@@ -12,6 +13,10 @@ import 'package:kasy_kit/core/theme/providers/theme_provider.dart';
|
|
|
12
13
|
import 'package:kasy_kit/i18n/translations.g.dart';
|
|
13
14
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
14
15
|
|
|
16
|
+
const Color _highlightColor = Color(0xFFD2F51E);
|
|
17
|
+
const double _highlightStrokeWidth = 2.0;
|
|
18
|
+
const double _highlightCornerRadius = 4.0;
|
|
19
|
+
|
|
15
20
|
final GlobalKey<ScaffoldMessengerState> devInspectorRootScaffoldMessengerKey =
|
|
16
21
|
GlobalKey<ScaffoldMessengerState>(debugLabel: 'devInspectorRoot');
|
|
17
22
|
|
|
@@ -31,6 +36,12 @@ final ValueNotifier<bool> devInspectorRevealNowNotifier =
|
|
|
31
36
|
final ValueNotifier<bool> devInspectorCopyTriggerNotifier =
|
|
32
37
|
ValueNotifier<bool>(false);
|
|
33
38
|
|
|
39
|
+
/// Active state of the custom inspector. Toggled by the FAB (native) and by
|
|
40
|
+
/// the Web Device Preview pill; observed by [DevInspector] to mount the
|
|
41
|
+
/// tap-absorbing overlay and the highlight.
|
|
42
|
+
final ValueNotifier<bool> devInspectorActiveNotifier =
|
|
43
|
+
ValueNotifier<bool>(false);
|
|
44
|
+
|
|
34
45
|
class DevInspector extends StatefulWidget {
|
|
35
46
|
const DevInspector({super.key, required this.child});
|
|
36
47
|
|
|
@@ -45,7 +56,8 @@ class DevInspector extends StatefulWidget {
|
|
|
45
56
|
State<DevInspector> createState() => _DevInspectorState();
|
|
46
57
|
}
|
|
47
58
|
|
|
48
|
-
class _DevInspectorState extends State<DevInspector>
|
|
59
|
+
class _DevInspectorState extends State<DevInspector>
|
|
60
|
+
with SingleTickerProviderStateMixin {
|
|
49
61
|
static const Duration _fabRevealDelay = Duration(seconds: 2);
|
|
50
62
|
static const Duration _fabDeemphasizeDelay = Duration(seconds: 2);
|
|
51
63
|
static const Duration _fabOpacityAnimation = Duration(milliseconds: 700);
|
|
@@ -71,11 +83,15 @@ class _DevInspectorState extends State<DevInspector> {
|
|
|
71
83
|
bool _fabFadeIn = false;
|
|
72
84
|
bool _fabEmphasized = false;
|
|
73
85
|
|
|
86
|
+
DevInspectorInfo? _selectedInfo;
|
|
87
|
+
RenderObject? _selectedRender;
|
|
88
|
+
Rect? _highlightRect;
|
|
89
|
+
Ticker? _highlightTicker;
|
|
90
|
+
|
|
74
91
|
@override
|
|
75
92
|
void initState() {
|
|
76
93
|
super.initState();
|
|
77
|
-
|
|
78
|
-
.addListener(_handleInspectorOverrideChanged);
|
|
94
|
+
devInspectorActiveNotifier.addListener(_handleActiveChanged);
|
|
79
95
|
devInspectorFabEnabledNotifier.addListener(_onFabEnabledNotifierChanged);
|
|
80
96
|
devInspectorRevealNowNotifier.addListener(_onRevealNow);
|
|
81
97
|
devInspectorCopyTriggerNotifier.addListener(_onCopyTriggered);
|
|
@@ -87,13 +103,13 @@ class _DevInspectorState extends State<DevInspector> {
|
|
|
87
103
|
_revealTimer?.cancel();
|
|
88
104
|
_fabDeemphasizeTimer?.cancel();
|
|
89
105
|
_copyFeedbackTimer?.cancel();
|
|
106
|
+
_highlightTicker?.dispose();
|
|
90
107
|
devInspectorFabEnabledNotifier.removeListener(_onFabEnabledNotifierChanged);
|
|
91
108
|
devInspectorRevealNowNotifier.removeListener(_onRevealNow);
|
|
92
109
|
devInspectorCopyTriggerNotifier.removeListener(_onCopyTriggered);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
WidgetsBinding.instance.debugShowWidgetInspectorOverride = false;
|
|
110
|
+
devInspectorActiveNotifier.removeListener(_handleActiveChanged);
|
|
111
|
+
if (devInspectorActiveNotifier.value) {
|
|
112
|
+
devInspectorActiveNotifier.value = false;
|
|
97
113
|
}
|
|
98
114
|
super.dispose();
|
|
99
115
|
}
|
|
@@ -141,7 +157,7 @@ class _DevInspectorState extends State<DevInspector> {
|
|
|
141
157
|
if (!mounted) return;
|
|
142
158
|
if (!enabled) {
|
|
143
159
|
if (_active) {
|
|
144
|
-
|
|
160
|
+
devInspectorActiveNotifier.value = false;
|
|
145
161
|
}
|
|
146
162
|
_copyFeedbackTimer?.cancel();
|
|
147
163
|
setState(() {
|
|
@@ -152,6 +168,7 @@ class _DevInspectorState extends State<DevInspector> {
|
|
|
152
168
|
_dragging = false;
|
|
153
169
|
_copyFeedbackText = null;
|
|
154
170
|
_copyFeedbackIsError = false;
|
|
171
|
+
_clearSelection();
|
|
155
172
|
});
|
|
156
173
|
_copyFeedbackTimer?.cancel();
|
|
157
174
|
return;
|
|
@@ -192,17 +209,62 @@ class _DevInspectorState extends State<DevInspector> {
|
|
|
192
209
|
}
|
|
193
210
|
|
|
194
211
|
void _setInspectorActive(bool value) {
|
|
195
|
-
|
|
196
|
-
WidgetsBinding.instance.debugShowWidgetInspectorOverride = value;
|
|
212
|
+
devInspectorActiveNotifier.value = value;
|
|
197
213
|
HapticFeedback.lightImpact();
|
|
198
214
|
}
|
|
199
215
|
|
|
200
|
-
void
|
|
201
|
-
final bool
|
|
202
|
-
|
|
203
|
-
if (_active == override) return;
|
|
216
|
+
void _handleActiveChanged() {
|
|
217
|
+
final bool active = devInspectorActiveNotifier.value;
|
|
218
|
+
if (_active == active) return;
|
|
204
219
|
if (!mounted) return;
|
|
205
|
-
setState(()
|
|
220
|
+
setState(() {
|
|
221
|
+
_active = active;
|
|
222
|
+
if (!active) _clearSelection();
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
void _clearSelection() {
|
|
227
|
+
_selectedInfo = null;
|
|
228
|
+
_selectedRender = null;
|
|
229
|
+
_highlightRect = null;
|
|
230
|
+
_stopHighlightTicker();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
void _onInspectorTap(Offset globalPosition) {
|
|
234
|
+
final picked = DevInspectorService.pickAt(globalPosition);
|
|
235
|
+
if (picked == null) {
|
|
236
|
+
HapticFeedback.heavyImpact();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
setState(() {
|
|
240
|
+
_selectedInfo = picked.info;
|
|
241
|
+
_selectedRender = picked.renderObject;
|
|
242
|
+
_highlightRect = picked.info.boundingBox;
|
|
243
|
+
});
|
|
244
|
+
_startHighlightTicker();
|
|
245
|
+
HapticFeedback.selectionClick();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
void _startHighlightTicker() {
|
|
249
|
+
_highlightTicker ??= createTicker(_onHighlightTick);
|
|
250
|
+
if (!_highlightTicker!.isActive) _highlightTicker!.start();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
void _stopHighlightTicker() {
|
|
254
|
+
if (_highlightTicker?.isActive ?? false) _highlightTicker!.stop();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
void _onHighlightTick(Duration _) {
|
|
258
|
+
final RenderObject? target = _selectedRender;
|
|
259
|
+
if (target == null) return;
|
|
260
|
+
final Rect? rect = DevInspectorService.remeasure(target);
|
|
261
|
+
if (rect == null) {
|
|
262
|
+
setState(_clearSelection);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (_highlightRect != rect) {
|
|
266
|
+
setState(() => _highlightRect = rect);
|
|
267
|
+
}
|
|
206
268
|
}
|
|
207
269
|
|
|
208
270
|
String? _extractRouteHint(DevInspectorInfo info) {
|
|
@@ -233,7 +295,7 @@ class _DevInspectorState extends State<DevInspector> {
|
|
|
233
295
|
if (!_active || _copyBusy) return;
|
|
234
296
|
_copyBusy = true;
|
|
235
297
|
try {
|
|
236
|
-
final DevInspectorInfo? info =
|
|
298
|
+
final DevInspectorInfo? info = _selectedInfo;
|
|
237
299
|
if (!mounted) return;
|
|
238
300
|
if (info == null) {
|
|
239
301
|
_showCopyFeedback(t.devInspector.selectWidgetFirst, isError: true);
|
|
@@ -308,6 +370,20 @@ class _DevInspectorState extends State<DevInspector> {
|
|
|
308
370
|
clipBehavior: Clip.none,
|
|
309
371
|
children: <Widget>[
|
|
310
372
|
widget.child,
|
|
373
|
+
if (_active)
|
|
374
|
+
Positioned.fill(
|
|
375
|
+
child: Listener(
|
|
376
|
+
behavior: HitTestBehavior.opaque,
|
|
377
|
+
onPointerDown: (PointerDownEvent ev) =>
|
|
378
|
+
_onInspectorTap(ev.position),
|
|
379
|
+
child: IgnorePointer(
|
|
380
|
+
child: CustomPaint(
|
|
381
|
+
painter: _HighlightPainter(rect: _highlightRect),
|
|
382
|
+
size: Size.infinite,
|
|
383
|
+
),
|
|
384
|
+
),
|
|
385
|
+
),
|
|
386
|
+
),
|
|
311
387
|
if (_fabLayerMounted &&
|
|
312
388
|
devInspectorFabEnabledNotifier.value)
|
|
313
389
|
Positioned(
|
|
@@ -462,3 +538,29 @@ class _EntryFab extends StatelessWidget {
|
|
|
462
538
|
);
|
|
463
539
|
}
|
|
464
540
|
}
|
|
541
|
+
|
|
542
|
+
class _HighlightPainter extends CustomPainter {
|
|
543
|
+
_HighlightPainter({required this.rect});
|
|
544
|
+
|
|
545
|
+
final Rect? rect;
|
|
546
|
+
|
|
547
|
+
@override
|
|
548
|
+
void paint(Canvas canvas, Size size) {
|
|
549
|
+
final Rect? r = rect;
|
|
550
|
+
if (r == null || r.isEmpty) return;
|
|
551
|
+
final Paint paint = Paint()
|
|
552
|
+
..color = _highlightColor
|
|
553
|
+
..style = PaintingStyle.stroke
|
|
554
|
+
..strokeWidth = _highlightStrokeWidth;
|
|
555
|
+
final Rect outer = r.inflate(_highlightStrokeWidth / 2);
|
|
556
|
+
final RRect rrect = RRect.fromRectAndRadius(
|
|
557
|
+
outer,
|
|
558
|
+
const Radius.circular(_highlightCornerRadius),
|
|
559
|
+
);
|
|
560
|
+
canvas.drawRRect(rrect, paint);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
@override
|
|
564
|
+
bool shouldRepaint(_HighlightPainter oldDelegate) =>
|
|
565
|
+
oldDelegate.rect != rect;
|
|
566
|
+
}
|
|
@@ -280,23 +280,12 @@ class DevInspectorService {
|
|
|
280
280
|
);
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
///
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
WidgetInspectorService.instance.selection.current;
|
|
290
|
-
if (renderObject == null) return null;
|
|
291
|
-
return _extractFromRenderObject(renderObject);
|
|
292
|
-
} catch (_) {
|
|
293
|
-
return null;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/// Inspects the widget at a global position, syncs the native inspector
|
|
298
|
-
/// selection (tooltip + highlight), and return the info.
|
|
299
|
-
static DevInspectorInfo? inspectAndSelect(Offset globalPosition) {
|
|
283
|
+
/// Hit-tests at [globalPosition] and returns the inspector info plus the
|
|
284
|
+
/// underlying render object so the caller can re-measure the bounds during
|
|
285
|
+
/// animations.
|
|
286
|
+
static ({DevInspectorInfo info, RenderObject renderObject})? pickAt(
|
|
287
|
+
Offset globalPosition,
|
|
288
|
+
) {
|
|
300
289
|
if (!kDebugMode) return null;
|
|
301
290
|
try {
|
|
302
291
|
final result = BoxHitTestResult();
|
|
@@ -308,9 +297,7 @@ class DevInspectorService {
|
|
|
308
297
|
final RenderObject candidate = entry.target as RenderObject;
|
|
309
298
|
final DevInspectorInfo? info = _extractFromRenderObject(candidate);
|
|
310
299
|
if (info != null) {
|
|
311
|
-
|
|
312
|
-
WidgetInspectorService.instance.setSelection(candidate, 'kit_inspector');
|
|
313
|
-
return info;
|
|
300
|
+
return (info: info, renderObject: candidate);
|
|
314
301
|
}
|
|
315
302
|
}
|
|
316
303
|
return null;
|
|
@@ -318,4 +305,11 @@ class DevInspectorService {
|
|
|
318
305
|
return null;
|
|
319
306
|
}
|
|
320
307
|
}
|
|
308
|
+
|
|
309
|
+
/// Re-measures the global bounding rect of a previously picked render
|
|
310
|
+
/// object. Returns null if it became detached from the tree.
|
|
311
|
+
static Rect? remeasure(RenderObject target) {
|
|
312
|
+
if (!target.attached) return null;
|
|
313
|
+
return _globalBoundingRect(target);
|
|
314
|
+
}
|
|
321
315
|
}
|
|
@@ -71,12 +71,12 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
|
|
|
71
71
|
final user = ref.read(userStateNotifierProvider).user;
|
|
72
72
|
|
|
73
73
|
// Slang lazy-loads non-base locales: AppLocale.pt.translations falls back
|
|
74
|
-
// silently to the base locale (en)
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
|
|
74
|
+
// silently to the base locale (en) if the bundle isn't in the translation
|
|
75
|
+
// map yet. The async loadLocale() can't be used here as a safety net
|
|
76
|
+
// because it short-circuits ("already loading") when setLocale() is in
|
|
77
|
+
// flight in parallel — leaving us reading translations that aren't loaded
|
|
78
|
+
// yet. loadLocaleSync forces the bundle into the map right now, no race.
|
|
79
|
+
LocaleSettings.instance.loadLocaleSync(locale);
|
|
80
80
|
final t = locale.translations;
|
|
81
81
|
|
|
82
82
|
// "Logged out" = no user id at all (post-logout in authRequired mode, or
|
|
@@ -111,6 +111,7 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
|
|
|
111
111
|
? ''
|
|
112
112
|
: (isPro ? t.home_widget.plan_pro : t.home_widget.plan_free);
|
|
113
113
|
final quote = t.home_widget.quote;
|
|
114
|
+
final quoteAuthor = t.home_widget.quote_author;
|
|
114
115
|
|
|
115
116
|
logger.d(
|
|
116
117
|
'Widget payload → greeting: "$greeting", title: "$title", '
|
|
@@ -123,6 +124,7 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
|
|
|
123
124
|
'planText': planText,
|
|
124
125
|
'isPro': isPro.toString(),
|
|
125
126
|
'quote': quote,
|
|
127
|
+
'quoteAuthor': quoteAuthor,
|
|
126
128
|
});
|
|
127
129
|
}
|
|
128
130
|
|
|
@@ -164,6 +166,10 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
|
|
|
164
166
|
await HomeWidget.saveWidgetData<String>('planText', data['planText'] ?? '');
|
|
165
167
|
await HomeWidget.saveWidgetData<String>('isPro', data['isPro'] ?? 'false');
|
|
166
168
|
await HomeWidget.saveWidgetData<String>('quote', data['quote'] ?? '');
|
|
169
|
+
await HomeWidget.saveWidgetData<String>(
|
|
170
|
+
'quoteAuthor',
|
|
171
|
+
data['quoteAuthor'] ?? '',
|
|
172
|
+
);
|
|
167
173
|
|
|
168
174
|
await HomeWidget.updateWidget(
|
|
169
175
|
name: _androidWidgetName,
|
|
@@ -171,18 +177,6 @@ class MyWidgetHomeWidget extends _$MyWidgetHomeWidget
|
|
|
171
177
|
);
|
|
172
178
|
}
|
|
173
179
|
|
|
174
|
-
Future<Map<String, dynamic>> getWidgetData() async {
|
|
175
|
-
return {
|
|
176
|
-
'greeting': await HomeWidget.getWidgetData<String>('greeting'),
|
|
177
|
-
'title': await HomeWidget.getWidgetData<String>('title'),
|
|
178
|
-
'planText': await HomeWidget.getWidgetData<String>('planText'),
|
|
179
|
-
'isPro': await HomeWidget.getWidgetData<String>(
|
|
180
|
-
'isPro',
|
|
181
|
-
defaultValue: 'false',
|
|
182
|
-
),
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
180
|
/// Time-of-day greeting in the app language (matches what the user sees
|
|
187
181
|
/// inside the app, not the device language).
|
|
188
182
|
static String _greeting(Translations t) {
|