kasy-cli 1.36.0 → 1.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/scaffold/CHANGELOG.json +14 -0
- package/package.json +1 -1
- package/templates/firebase/.firebase/hosting.YnVpbGQvd2Vi.cache +13 -13
- package/templates/firebase/lib/components/kasy_sidebar.dart +11 -13
- package/templates/firebase/lib/core/states/user_state_notifier.dart +18 -2
- package/templates/firebase/lib/features/authentication/ui/signin_page.dart +3 -1
- package/templates/firebase/lib/features/onboarding/providers/onboarding_provider.dart +8 -2
- package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +15 -4
- package/templates/firebase/test/features/notifications/ui/notifications_page_test.dart +2 -2
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
+
"1.36.1": {
|
|
3
|
+
"modules": {
|
|
4
|
+
"onboarding": {
|
|
5
|
+
"pt": "Onboarding mais resistente a falhas: se a escrita no backend falhar (por exemplo, o documento do usuário ainda não foi criado logo após o cadastro), o loader não trava mais. O onboarding sempre conclui e o estado fica salvo localmente, e uma resposta que não salvou é registrada no log sem interromper a experiência.",
|
|
6
|
+
"en": "More resilient onboarding: if a backend write fails (e.g. the user document isn't created yet right after sign-up), the loader no longer gets stuck. Onboarding always completes with the state saved locally, and an answer that failed to save is logged without interrupting the flow.",
|
|
7
|
+
"es": "Onboarding más resistente a fallos: si una escritura en el backend falla (por ejemplo, el documento del usuario aún no se creó justo tras el registro), el loader ya no se queda atascado. El onboarding siempre concluye con el estado guardado localmente, y una respuesta que no se guardó se registra en el log sin interrumpir la experiencia."
|
|
8
|
+
},
|
|
9
|
+
"components": {
|
|
10
|
+
"pt": "Sidebar: a variante \"collapsed rail\" agora fica fina de verdade em tela estreita (mobile) no showcase, em vez de abrir larga. Só a gaveta explícita (isDrawer) força a abertura larga. O app real não muda (a sidebar só aparece de tablet pra cima).",
|
|
11
|
+
"en": "Sidebar: the \"collapsed rail\" variant now actually stays thin on narrow (mobile) widths in the showcase, instead of opening wide. Only the explicit drawer (isDrawer) forces the wide open. The real app is unchanged (the sidebar only shows from tablet up).",
|
|
12
|
+
"es": "Sidebar: la variante \"collapsed rail\" ahora se mantiene fina de verdad en anchos estrechos (móvil) en el showcase, en vez de abrirse ancha. Solo el cajón explícito (isDrawer) fuerza la apertura ancha. La app real no cambia (la sidebar solo aparece de tablet en adelante)."
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
2
16
|
"1.36.0": {
|
|
3
17
|
"modules": {
|
|
4
18
|
"components": {
|
package/package.json
CHANGED
|
@@ -58,16 +58,16 @@ assets/assets/icons/google_play_games.png,1772398653083,533c1d87d7be8690ab173ef4
|
|
|
58
58
|
assets/assets/icons/google.png,1772398653083,f423e7e7be1e06008d45617d07f095f04da7fdcab9a56523f9e0633828e464e0
|
|
59
59
|
assets/assets/icons/facebook.png,1772398653083,79ac67e449c1db63319d43329ca91f682b4e0bc6a0883b0dfb2a849e13ae6eb6
|
|
60
60
|
assets/assets/icons/apple.png,1772398653083,2a737d8801ca81452b2978bb2e1ac72136acf1a4c338f2de2f77b65e9f6de1fa
|
|
61
|
-
version.json,
|
|
62
|
-
flutter_service_worker.js,
|
|
63
|
-
assets/FontManifest.json,
|
|
64
|
-
assets/AssetManifest.bin
|
|
65
|
-
assets/AssetManifest.bin,
|
|
66
|
-
assets/shaders/
|
|
67
|
-
assets/shaders/
|
|
68
|
-
index.html,
|
|
69
|
-
flutter_bootstrap.js,
|
|
70
|
-
main.dart.js_1.part.js,
|
|
71
|
-
main.dart.js_3.part.js,
|
|
72
|
-
assets/NOTICES,
|
|
73
|
-
main.dart.js,
|
|
61
|
+
version.json,1781384419657,1c93a56a819f36903677817066dbda0bae5785563a39d3a0a486246cf67107ac
|
|
62
|
+
flutter_service_worker.js,1781384420789,baeeaf9f4b8e6f40d3b0549429ceb70ca01f120db6a7ef2f60d5809233dc2205
|
|
63
|
+
assets/FontManifest.json,1781384419796,4d84ab517c27984d36f9a3c8be6f2a72788c0c3985c1d5874297fef0a53407ca
|
|
64
|
+
assets/AssetManifest.bin,1781384419795,78bccb08a36307a400711a1d7e6868bd5f89a24e63439fb5b6747660323e4475
|
|
65
|
+
assets/AssetManifest.bin.json,1781384419795,50a11b51c3fc4cbed1b5ec3a4f466c6a6b75c481d08d4782c8191f99bdbdba9c
|
|
66
|
+
assets/shaders/stretch_effect.frag,1781384419911,1a7d4ac2be40cf0a459dfb390ef08bcd740f37913ffdee8de3c2ea836a18410e
|
|
67
|
+
assets/shaders/ink_sparkle.frag,1781384419911,1c8e222328206d1e06754f76fb53947aad38d62180aafad5298a3c6f510b173d
|
|
68
|
+
index.html,1781384363218,bb8142eb84e9e44049957c3edf86006e1ae5f194c397a8326ce505b68fdf58f4
|
|
69
|
+
flutter_bootstrap.js,1781384363207,c9fd7e9f06eccf37ccbe2a39e88047779c1a67c67fe18febf6971479b7d6b64c
|
|
70
|
+
main.dart.js_1.part.js,1781384401644,7be2ce26bfb76e0142df09977ff2bbb058e5bee8c33d1ebf6c49aff2158d94d0
|
|
71
|
+
main.dart.js_3.part.js,1781384401652,6f1d83d415ac91cc4ca02b7ccefeae40d609713c7609b636502b68501dd44a38
|
|
72
|
+
assets/NOTICES,1781384419796,a18efc42c5ed99b56481537d0f229ddd3add671c548a893aaf8766f30c854158
|
|
73
|
+
main.dart.js,1781384402163,46f09db6091efb87263aad9c85327da8097b61c8c528564b1f7359829aef790b
|
|
@@ -302,19 +302,15 @@ class _KasySidebarState extends State<KasySidebar> {
|
|
|
302
302
|
/// Viewport width below which the sidebar auto-collapses (tablet breakpoint).
|
|
303
303
|
static const double _kBreakpoint = 1024.0;
|
|
304
304
|
|
|
305
|
-
/// Below this (mobile), the rail
|
|
306
|
-
///
|
|
307
|
-
///
|
|
305
|
+
/// Below this (mobile), the rail hides its collapse toggle: on a phone the
|
|
306
|
+
/// thin-vs-wide choice is a fixed pre-configuration (set via [isDrawer] for a
|
|
307
|
+
/// wide drawer, or the collapsed default for a thin rail), not a live toggle.
|
|
308
|
+
/// Width does NOT force wide here — a collapsed config stays thin on mobile.
|
|
308
309
|
static const double _kMobileBreakpoint = 768.0;
|
|
309
310
|
|
|
310
311
|
bool _isMobile(BuildContext context) =>
|
|
311
312
|
MediaQuery.sizeOf(context).width < _kMobileBreakpoint;
|
|
312
313
|
|
|
313
|
-
/// Drawer presentation — always wide, no collapse toggle. True on a phone-
|
|
314
|
-
/// width viewport or when explicitly opened as a drawer ([isDrawer]).
|
|
315
|
-
bool _wideDrawer(BuildContext context) =>
|
|
316
|
-
widget.isDrawer || _isMobile(context);
|
|
317
|
-
|
|
318
314
|
/// True when wired to Bart's navigation (real, tappable screens).
|
|
319
315
|
bool get _connected =>
|
|
320
316
|
widget.routes != null &&
|
|
@@ -382,9 +378,10 @@ class _KasySidebarState extends State<KasySidebar> {
|
|
|
382
378
|
|
|
383
379
|
@override
|
|
384
380
|
Widget build(BuildContext context) {
|
|
385
|
-
//
|
|
386
|
-
// explicit choice, falling back to the
|
|
387
|
-
|
|
381
|
+
// Only an explicit drawer ([isDrawer]) forces wide; everything else honours
|
|
382
|
+
// the user's explicit choice, falling back to the auto-collapse on narrow
|
|
383
|
+
// viewports. Mobile is NOT forced wide — a collapsed config stays thin there.
|
|
384
|
+
_collapsed = !widget.isDrawer &&
|
|
388
385
|
(_collapsePreference ?? _isViewportNarrow(context));
|
|
389
386
|
|
|
390
387
|
final c = _colors;
|
|
@@ -622,8 +619,9 @@ class _KasySidebarState extends State<KasySidebar> {
|
|
|
622
619
|
MediaQuery.sizeOf(context).width >= _kBreakpoint
|
|
623
620
|
? _kTopBandHeight
|
|
624
621
|
: kasyAppBarBodyTopOverlap(context);
|
|
625
|
-
// No collapse toggle on
|
|
626
|
-
|
|
622
|
+
// No collapse toggle on a drawer (always wide) or on mobile (the thin/wide
|
|
623
|
+
// choice is a fixed pre-configuration there, not a live toggle).
|
|
624
|
+
final bool showToggle = !widget.isDrawer && !_isMobile(context);
|
|
627
625
|
final bool anchoredLeft = widget.side == KasySidebarSide.left;
|
|
628
626
|
// Brand wordmark — same artwork as the splash screen.
|
|
629
627
|
final Widget logo = Image.asset(
|
|
@@ -108,8 +108,24 @@ class UserStateNotifier extends _$UserStateNotifier implements OnStartService {
|
|
|
108
108
|
state = state.copyWith(user: const User.anonymous(onboarded: true));
|
|
109
109
|
return;
|
|
110
110
|
}
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
try {
|
|
112
|
+
final newUser = await _userRepository.setOnboarded(state.user);
|
|
113
|
+
state = state.copyWith(user: newUser);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
// The user document may not exist yet: the backend's onUserRegistration
|
|
116
|
+
// trigger runs asynchronously and can lag a just-created anonymous account
|
|
117
|
+
// (Firestore update() throws on a missing doc). Onboarding is already
|
|
118
|
+
// remembered locally (flag above), so reflect it in state and move on
|
|
119
|
+
// instead of letting this bubble up and trap the onboarding loader.
|
|
120
|
+
_logger.w('setOnboarded failed, keeping local onboarded state: $e');
|
|
121
|
+
state = state.copyWith(
|
|
122
|
+
user: switch (state.user) {
|
|
123
|
+
final AuthenticatedUserData u => u.copyWith(onboarded: true),
|
|
124
|
+
final AnonymousUserData u => u.copyWith(onboarded: true),
|
|
125
|
+
final LoadingUserData _ => state.user,
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
}
|
|
113
129
|
}
|
|
114
130
|
|
|
115
131
|
/// Finish onboarding (or "continue as guest" from the sign-in screen) by
|
|
@@ -273,7 +273,9 @@ class _GuestContinueButtonState extends ConsumerState<_GuestContinueButton> {
|
|
|
273
273
|
Widget build(BuildContext context) {
|
|
274
274
|
return KasyButton(
|
|
275
275
|
label: t.auth.signin.continue_without,
|
|
276
|
-
variant:
|
|
276
|
+
// Most subtle variant in the button preview: it's a tertiary action that
|
|
277
|
+
// should sit quietly below sign-in / social, not compete with them.
|
|
278
|
+
variant: KasyButtonVariant.ghost,
|
|
277
279
|
expand: true,
|
|
278
280
|
isLoading: _loading,
|
|
279
281
|
onPressed: _loading ? null : _continue,
|
|
@@ -8,6 +8,7 @@ import 'package:kasy_kit/features/notifications/repositories/notifications_repos
|
|
|
8
8
|
import 'package:kasy_kit/features/onboarding/models/user_info.dart';
|
|
9
9
|
import 'package:kasy_kit/features/onboarding/providers/onboarding_model.dart';
|
|
10
10
|
import 'package:kasy_kit/features/onboarding/repositories/user_infos_repository.dart';
|
|
11
|
+
import 'package:logger/logger.dart';
|
|
11
12
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
12
13
|
|
|
13
14
|
part 'onboarding_provider.g.dart';
|
|
@@ -83,13 +84,18 @@ class OnboardingNotifier extends _$OnboardingNotifier {
|
|
|
83
84
|
await userStateNotifier.continueAsGuest();
|
|
84
85
|
|
|
85
86
|
// Now that the account exists, flush the answers collected during the
|
|
86
|
-
// questions (gender, age, …) that had nowhere to go before.
|
|
87
|
+
// questions (gender, age, …) that had nowhere to go before. Best-effort:
|
|
88
|
+
// a failed profile write must never block onboarding from finishing.
|
|
87
89
|
final userId = ref.read(userStateNotifierProvider).user.idOrNull;
|
|
88
90
|
final pending = state.pendingUserInfo;
|
|
89
91
|
if (userId != null && pending.isNotEmpty) {
|
|
90
92
|
final repository = ref.read(userInfosRepositoryProvider);
|
|
91
93
|
for (final info in pending) {
|
|
92
|
-
|
|
94
|
+
try {
|
|
95
|
+
await repository.save(userId, info);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
Logger().w('Failed to save onboarding answer: $e');
|
|
98
|
+
}
|
|
93
99
|
}
|
|
94
100
|
state = state.copyWith(pendingUserInfo: const []);
|
|
95
101
|
}
|
|
@@ -27,10 +27,21 @@ class _OnboardingJournalLoaderState extends ConsumerState<OnboardingLoader> {
|
|
|
27
27
|
// Run the real work (which now lazily creates the guest account) while the
|
|
28
28
|
// loader animation plays, so its network latency is hidden under the
|
|
29
29
|
// minimum display time instead of being added on top of it.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
//
|
|
31
|
+
// The completion call MUST always run, even if a backend write fails (e.g.
|
|
32
|
+
// setting the onboarded flag on a user document the backend trigger has not
|
|
33
|
+
// created yet). Otherwise the user is trapped on this loader forever. The
|
|
34
|
+
// account already exists and onboarding is remembered locally, so moving
|
|
35
|
+
// on is safe.
|
|
36
|
+
final minimumDisplay = Future<void>.delayed(
|
|
37
|
+
const Duration(milliseconds: 3500),
|
|
38
|
+
);
|
|
39
|
+
try {
|
|
40
|
+
await ref.onboardingNotifier.onOnboardingCompleted();
|
|
41
|
+
} catch (e) {
|
|
42
|
+
debugPrint('OnboardingLoader: completion error (continuing anyway): $e');
|
|
43
|
+
}
|
|
44
|
+
await minimumDisplay;
|
|
34
45
|
if (!mounted) return;
|
|
35
46
|
widget.onCompleted();
|
|
36
47
|
}
|
|
@@ -81,10 +81,10 @@ void main() {
|
|
|
81
81
|
|
|
82
82
|
// Confirm dialog appears with destructive action.
|
|
83
83
|
expect(find.text('Delete all notifications?'), findsOneWidget);
|
|
84
|
-
expect(find.text('
|
|
84
|
+
expect(find.text('Yes, delete'), findsOneWidget);
|
|
85
85
|
expect(find.text('Cancel'), findsOneWidget);
|
|
86
86
|
|
|
87
|
-
await tester.tap(find.text('
|
|
87
|
+
await tester.tap(find.text('Yes, delete'));
|
|
88
88
|
await tester.pumpAndSettle();
|
|
89
89
|
|
|
90
90
|
// After deletion, no notification tiles remain.
|