Skip to content

Commit 8f4f315

Browse files
committed
RecentDmConversationsPage: Show unread counts
This follows Vlad's suggestion to adjust the gap between the right edge of the text and the counter, from the Figma's 8px to 12px: https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/design.3A.20DM-conversation.20list/near/1672155 Fixes: #328
1 parent 879350e commit 8f4f315

File tree

3 files changed

+97
-6
lines changed

3 files changed

+97
-6
lines changed

lib/widgets/recent_dm_conversations.dart

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import 'dart:ui';
2+
13
import 'package:flutter/material.dart';
24

35
import '../model/narrow.dart';
46
import '../model/recent_dm_conversations.dart';
7+
import '../model/unreads.dart';
58
import 'content.dart';
69
import 'icons.dart';
710
import 'message_list.dart';
@@ -23,24 +26,30 @@ class RecentDmConversationsPage extends StatefulWidget {
2326

2427
class _RecentDmConversationsPageState extends State<RecentDmConversationsPage> with PerAccountStoreAwareStateMixin<RecentDmConversationsPage> {
2528
RecentDmConversationsView? model;
29+
Unreads? unreadsModel;
2630

2731
@override
2832
void onNewStore() {
2933
model?.removeListener(_modelChanged);
3034
model = PerAccountStoreWidget.of(context).recentDmConversationsView
3135
..addListener(_modelChanged);
36+
37+
unreadsModel?.removeListener(_modelChanged);
38+
unreadsModel = PerAccountStoreWidget.of(context).unreads
39+
..addListener(_modelChanged);
3240
}
3341

3442
@override
3543
void dispose() {
3644
model?.removeListener(_modelChanged);
45+
unreadsModel?.removeListener(_modelChanged);
3746
super.dispose();
3847
}
3948

4049
void _modelChanged() {
4150
setState(() {
42-
// The actual state lives in [model].
43-
// This method was called because that just changed.
51+
// The actual state lives in [model] and [unreadsModel].
52+
// This method was called because one of those just changed.
4453
});
4554
}
4655

@@ -51,14 +60,25 @@ class _RecentDmConversationsPageState extends State<RecentDmConversationsPage> w
5160
appBar: AppBar(title: const Text('Direct messages')),
5261
body: ListView.builder(
5362
itemCount: sorted.length,
54-
itemBuilder: (context, index) => RecentDmConversationsItem(narrow: sorted[index])));
63+
itemBuilder: (context, index) {
64+
final narrow = sorted[index];
65+
return RecentDmConversationsItem(
66+
narrow: narrow,
67+
unreadCount: unreadsModel!.countInDmNarrow(narrow),
68+
);
69+
}));
5570
}
5671
}
5772

5873
class RecentDmConversationsItem extends StatelessWidget {
59-
const RecentDmConversationsItem({super.key, required this.narrow});
74+
const RecentDmConversationsItem({
75+
super.key,
76+
required this.narrow,
77+
required this.unreadCount,
78+
});
6079

6180
final DmNarrow narrow;
81+
final int unreadCount;
6282

6383
@override
6484
Widget build(BuildContext context) {
@@ -108,7 +128,26 @@ class RecentDmConversationsItem extends StatelessWidget {
108128
overflow: TextOverflow.ellipsis,
109129
title))),
110130
const SizedBox(width: 12),
111-
// TODO(#253): Unread count
131+
unreadCount > 0
132+
? Padding(
133+
padding: const EdgeInsetsDirectional.only(end: 16),
134+
child: DecoratedBox(
135+
decoration: BoxDecoration(
136+
borderRadius: BorderRadius.circular(3),
137+
color: const Color.fromRGBO(102, 102, 153, 0.15),
138+
),
139+
child: Padding(
140+
padding: const EdgeInsetsDirectional.fromSTEB(4, 0, 4, 1),
141+
child: Text(
142+
style: const TextStyle(
143+
fontFamily: 'Source Sans 3',
144+
fontSize: 16,
145+
height: (18 / 16),
146+
fontFeatures: [FontFeature.enable('smcp')], // small caps
147+
color: Color(0xFF222222),
148+
).merge(weightVariableTextStyle(context)),
149+
unreadCount.toString()))))
150+
: const SizedBox(),
112151
])));
113152
}
114153
}

test/flutter_checks.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ extension ValueNotifierChecks<T> on Subject<ValueNotifier<T>> {
3535
Subject<T> get value => has((c) => c.value, 'value');
3636
}
3737

38+
extension TextChecks on Subject<Text> {
39+
Subject<String?> get data => has((t) => t.data, 'data');
40+
}
41+
3842
extension TextStyleChecks on Subject<TextStyle> {
3943
Subject<bool> get inherit => has((t) => t.inherit, 'inherit');
4044
Subject<List<FontVariation>?> get fontVariations => has((t) => t.fontVariations, 'fontVariations');

test/widgets/recent_dm_conversations_test.dart

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:zulip/widgets/recent_dm_conversations.dart';
1313
import 'package:zulip/widgets/store.dart';
1414

1515
import '../example_data.dart' as eg;
16+
import '../flutter_checks.dart';
1617
import '../model/binding.dart';
1718
import '../model/test_store.dart';
1819
import '../test_navigation.dart';
@@ -106,7 +107,7 @@ void main() {
106107
});
107108

108109
group('RecentDmConversationsItem', () {
109-
group('appearance', () {
110+
group('content/appearance', () {
110111
void checkAvatar(WidgetTester tester, DmNarrow narrow) {
111112
final shape = tester.widget<AvatarShape>(
112113
find.descendant(
@@ -145,6 +146,26 @@ void main() {
145146
}
146147
}
147148

149+
Future<void> markMessageAsRead(WidgetTester tester, Message message) async {
150+
final store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
151+
store.handleEvent(UpdateMessageFlagsAddEvent(
152+
id: 1, flag: MessageFlag.read, all: false, messages: [message.id]));
153+
await tester.pump();
154+
}
155+
156+
void checkUnreadCount(WidgetTester tester, int expectedCount) {
157+
final Text? textWidget = tester.widgetList<Text>(find.descendant(
158+
of: find.byType(RecentDmConversationsItem),
159+
matching: find.textContaining(RegExp(r'^\d+$'),
160+
))).singleOrNull;
161+
162+
if (expectedCount == 0) {
163+
check(textWidget).isNull();
164+
} else {
165+
check(textWidget).isNotNull().data.equals(expectedCount.toString());
166+
}
167+
}
168+
148169
group('self-1:1', () {
149170
testWidgets('has right title/avatar', (WidgetTester tester) async {
150171
final message = eg.dmMessage(from: eg.selfUser, to: []);
@@ -169,6 +190,15 @@ void main() {
169190
newNameForSelfUser: name);
170191
checkTitle(tester, name, 2);
171192
});
193+
194+
testWidgets('unread counts', (WidgetTester tester) async {
195+
final message = eg.dmMessage(from: eg.selfUser, to: []);
196+
await setupPage(tester, users: [], dmMessages: [message]);
197+
198+
checkUnreadCount(tester, 1);
199+
await markMessageAsRead(tester, message);
200+
checkUnreadCount(tester, 0);
201+
});
172202
});
173203

174204
group('1:1', () {
@@ -206,6 +236,15 @@ void main() {
206236
await setupPage(tester, users: [user], dmMessages: [message]);
207237
checkTitle(tester, user.fullName, 2);
208238
});
239+
240+
testWidgets('unread counts', (WidgetTester tester) async {
241+
final message = eg.dmMessage(from: eg.otherUser, to: [eg.selfUser]);
242+
await setupPage(tester, users: [], dmMessages: [message]);
243+
244+
checkUnreadCount(tester, 1);
245+
await markMessageAsRead(tester, message);
246+
checkUnreadCount(tester, 0);
247+
});
209248
});
210249

211250
group('group', () {
@@ -255,6 +294,15 @@ void main() {
255294
await setupPage(tester, users: users, dmMessages: [message]);
256295
checkTitle(tester, users.map((u) => u.fullName).join(', '), 2);
257296
});
297+
298+
testWidgets('unread counts', (WidgetTester tester) async {
299+
final message = eg.dmMessage(from: eg.thirdUser, to: [eg.selfUser, eg.otherUser]);
300+
await setupPage(tester, users: [], dmMessages: [message]);
301+
302+
checkUnreadCount(tester, 1);
303+
await markMessageAsRead(tester, message);
304+
checkUnreadCount(tester, 0);
305+
});
258306
});
259307
});
260308

0 commit comments

Comments
 (0)