kasy-cli 1.7.0 → 1.7.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/package.json
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
|
|
1
3
|
import 'package:flutter/material.dart';
|
|
2
4
|
import 'package:flutter/rendering.dart';
|
|
3
5
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
@@ -5,6 +7,7 @@ import 'package:kasy_kit/components/kasy_app_bar.dart';
|
|
|
5
7
|
import 'package:kasy_kit/core/theme/theme.dart';
|
|
6
8
|
import 'package:kasy_kit/features/notifications/providers/models/notification.dart'
|
|
7
9
|
as app;
|
|
10
|
+
import 'package:kasy_kit/features/notifications/providers/models/notification_list.dart';
|
|
8
11
|
import 'package:kasy_kit/features/notifications/providers/notifications_provider.dart';
|
|
9
12
|
import 'package:kasy_kit/features/notifications/ui/components/notification_settings_sheet.dart';
|
|
10
13
|
import 'package:kasy_kit/features/notifications/ui/components/notification_tile.dart';
|
|
@@ -19,6 +22,7 @@ class NotificationsPage extends ConsumerStatefulWidget {
|
|
|
19
22
|
|
|
20
23
|
class _NotificationsPageState extends ConsumerState<NotificationsPage> {
|
|
21
24
|
final ScrollController _scrollController = ScrollController();
|
|
25
|
+
Timer? _autoReadTimer;
|
|
22
26
|
|
|
23
27
|
@override
|
|
24
28
|
void initState() {
|
|
@@ -29,6 +33,7 @@ class _NotificationsPageState extends ConsumerState<NotificationsPage> {
|
|
|
29
33
|
|
|
30
34
|
@override
|
|
31
35
|
void dispose() {
|
|
36
|
+
_autoReadTimer?.cancel();
|
|
32
37
|
_scrollController.removeListener(_onScrollChange);
|
|
33
38
|
_scrollController.dispose();
|
|
34
39
|
super.dispose();
|
|
@@ -47,27 +52,50 @@ class _NotificationsPageState extends ConsumerState<NotificationsPage> {
|
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
void requestReadAll() {
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
_autoReadTimer?.cancel();
|
|
56
|
+
_autoReadTimer = Timer(const Duration(milliseconds: 1500), () {
|
|
57
|
+
if (!mounted) return;
|
|
52
58
|
ref.read(notificationsProvider.notifier).readAll();
|
|
53
59
|
});
|
|
54
60
|
}
|
|
55
61
|
|
|
62
|
+
bool _hasUnread(AsyncValue<NotificationsList> state) {
|
|
63
|
+
if (!state.hasValue) return false;
|
|
64
|
+
return state.value!.data.any((n) => !n.seen);
|
|
65
|
+
}
|
|
66
|
+
|
|
56
67
|
@override
|
|
57
68
|
Widget build(BuildContext context) {
|
|
58
69
|
final notificationsState = ref.watch(notificationsProvider);
|
|
70
|
+
final hasUnread = _hasUnread(notificationsState);
|
|
59
71
|
|
|
60
72
|
return KasyOverlayScaffold(
|
|
61
73
|
title: t.notifications.title,
|
|
62
74
|
appBarStyle: KasyAppBarStyle.rootTab,
|
|
63
75
|
scrollController: _scrollController,
|
|
64
76
|
trailing: Builder(
|
|
65
|
-
builder: (ctx) =>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
builder: (ctx) => Row(
|
|
78
|
+
mainAxisSize: MainAxisSize.min,
|
|
79
|
+
children: [
|
|
80
|
+
if (hasUnread)
|
|
81
|
+
KasyChromeOrbIconButton(
|
|
82
|
+
key: const Key('mark_all_read_button'),
|
|
83
|
+
icon: KasyIcons.check,
|
|
84
|
+
iconSize: 18,
|
|
85
|
+
foregroundColor: ctx.colors.onSurface,
|
|
86
|
+
onPressed: () =>
|
|
87
|
+
ref.read(notificationsProvider.notifier).readAll(),
|
|
88
|
+
tooltip: t.notifications.mark_all_read,
|
|
89
|
+
),
|
|
90
|
+
if (hasUnread) const SizedBox(width: KasySpacing.xs),
|
|
91
|
+
KasyChromeOrbIconButton(
|
|
92
|
+
icon: KasyIcons.moreVert,
|
|
93
|
+
iconSize: 18,
|
|
94
|
+
foregroundColor: ctx.colors.onSurface,
|
|
95
|
+
onPressed: () => showNotificationSettingsSheet(ctx),
|
|
96
|
+
tooltip: 'Opções',
|
|
97
|
+
),
|
|
98
|
+
],
|
|
71
99
|
),
|
|
72
100
|
),
|
|
73
101
|
onRefresh: () async {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
1
2
|
import 'package:flutter_test/flutter_test.dart';
|
|
2
3
|
import 'package:kasy_kit/core/data/models/user.dart';
|
|
3
4
|
import 'package:kasy_kit/core/states/models/user_state.dart';
|
|
@@ -8,61 +9,86 @@ import 'package:kasy_kit/features/notifications/ui/widgets/notification_tile.dar
|
|
|
8
9
|
import '../../../test_utils.dart';
|
|
9
10
|
|
|
10
11
|
void main() {
|
|
11
|
-
|
|
12
|
+
UserState authenticatedUser() => UserState(
|
|
13
|
+
user: User.authenticated(
|
|
14
|
+
id: '1',
|
|
15
|
+
email: 'user@email.com',
|
|
16
|
+
name: 'user name',
|
|
17
|
+
onboarded: true,
|
|
18
|
+
creationDate: DateTime.now().subtract(const Duration(days: 4)),
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
group('NotificationsPage — rendering', () {
|
|
23
|
+
testWidgets('renders title and notification tiles', (tester) async {
|
|
24
|
+
await tester.pumpPage(
|
|
25
|
+
userState: authenticatedUser(),
|
|
26
|
+
home: NotificationsPage(),
|
|
27
|
+
);
|
|
28
|
+
await tester.pumpAndSettle();
|
|
29
|
+
|
|
30
|
+
expect(find.byType(NotificationsPage), findsOneWidget);
|
|
31
|
+
expect(find.text('Notifications'), findsOneWidget);
|
|
32
|
+
expect(find.byType(NotificationTile), findsAtLeastNWidgets(3));
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
group('NotificationsPage — auto-read behavior', () {
|
|
12
37
|
testWidgets(
|
|
13
|
-
'
|
|
38
|
+
'after 1.5s on screen, all notifications are marked as read',
|
|
14
39
|
(tester) async {
|
|
15
40
|
await tester.pumpPage(
|
|
16
|
-
userState:
|
|
17
|
-
user: User.authenticated(
|
|
18
|
-
id: '1',
|
|
19
|
-
email: 'user@email.com',
|
|
20
|
-
name: 'user name',
|
|
21
|
-
onboarded: true,
|
|
22
|
-
creationDate: DateTime.now().subtract(const Duration(days: 4)),
|
|
23
|
-
),
|
|
24
|
-
),
|
|
41
|
+
userState: authenticatedUser(),
|
|
25
42
|
home: NotificationsPage(),
|
|
26
43
|
);
|
|
27
|
-
await tester.pump(const Duration(
|
|
44
|
+
await tester.pump(const Duration(milliseconds: 1600));
|
|
28
45
|
await tester.pumpAndSettle();
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
|
|
47
|
+
final tiles = tester
|
|
48
|
+
.widgetList<NotificationTileComponent>(
|
|
49
|
+
find.byType(NotificationTileComponent),
|
|
50
|
+
)
|
|
51
|
+
.toList();
|
|
52
|
+
expect(tiles, isNotEmpty);
|
|
53
|
+
for (final tile in tiles) {
|
|
54
|
+
expect(
|
|
55
|
+
tile.notification.readAt,
|
|
56
|
+
isNotNull,
|
|
57
|
+
reason: 'All notifications should be read after auto-read fires',
|
|
58
|
+
);
|
|
59
|
+
}
|
|
32
60
|
},
|
|
33
61
|
);
|
|
62
|
+
});
|
|
34
63
|
|
|
64
|
+
group('NotificationsPage — mark all read button', () {
|
|
35
65
|
testWidgets(
|
|
36
|
-
'
|
|
66
|
+
'tapping the button marks all notifications as read immediately',
|
|
37
67
|
(tester) async {
|
|
38
68
|
await tester.pumpPage(
|
|
39
|
-
userState:
|
|
40
|
-
user: User.authenticated(
|
|
41
|
-
id: '1',
|
|
42
|
-
email: 'user@email.com',
|
|
43
|
-
name: 'user name',
|
|
44
|
-
onboarded: true,
|
|
45
|
-
creationDate: DateTime.now().subtract(const Duration(days: 4)),
|
|
46
|
-
),
|
|
47
|
-
),
|
|
69
|
+
userState: authenticatedUser(),
|
|
48
70
|
home: NotificationsPage(),
|
|
49
71
|
);
|
|
50
|
-
var firstNotification = tester.firstWidget<NotificationTileComponent>(
|
|
51
|
-
find.byType(NotificationTileComponent),
|
|
52
|
-
);
|
|
53
|
-
expect(firstNotification.notification.readAt, isNull);
|
|
54
|
-
|
|
55
|
-
await tester.pump(const Duration(seconds: 3));
|
|
56
72
|
await tester.pumpAndSettle();
|
|
57
|
-
firstNotification = tester.firstWidget<NotificationTileComponent>(
|
|
58
|
-
find.byType(NotificationTileComponent),
|
|
59
|
-
);
|
|
60
73
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
)
|
|
74
|
+
// Auto-read may have already run during pumpAndSettle.
|
|
75
|
+
// The button is hidden when no unread notifications remain — that is
|
|
76
|
+
// expected and itself a correct behavior.
|
|
77
|
+
final button = find.byKey(const Key('mark_all_read_button'));
|
|
78
|
+
if (button.evaluate().isNotEmpty) {
|
|
79
|
+
await tester.tap(button);
|
|
80
|
+
await tester.pumpAndSettle();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
final tiles = tester
|
|
84
|
+
.widgetList<NotificationTileComponent>(
|
|
85
|
+
find.byType(NotificationTileComponent),
|
|
86
|
+
)
|
|
87
|
+
.toList();
|
|
88
|
+
expect(tiles, isNotEmpty);
|
|
89
|
+
for (final tile in tiles) {
|
|
90
|
+
expect(tile.notification.readAt, isNotNull);
|
|
91
|
+
}
|
|
66
92
|
},
|
|
67
93
|
);
|
|
68
94
|
});
|