Skip to content

Commit 23e3be4

Browse files
committed
new-dm: Support unselecting a user by tapping chip in input
This isn't specifically mentioned in the Figma, but I think it's really helpful. Without this, the only way to deselect a user is to find them again in the list of results. That could be annoying because the user will often go offscreen in the results list as soon as you tap it, because we reset the query and scroll state when you tap it. Discussion about adding an "x" to the chip, to make this behavior more visible: https://chat.zulip.org/#narrow/channel/516-mobile-dev-help/topic/Follow.20up.20on.20comments.20.20on.20new.20Dm.20Sheet/near/2185229
1 parent b6254ea commit 23e3be4

File tree

2 files changed

+47
-23
lines changed

2 files changed

+47
-23
lines changed

lib/widgets/new_dm_sheet.dart

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ class _NewDmPickerState extends State<NewDmPicker> with PerAccountStoreAwareStat
133133
_NewDmHeader(selectedUserIds: selectedUserIds),
134134
_NewDmSearchBar(
135135
controller: searchController,
136-
selectedUserIds: selectedUserIds),
136+
selectedUserIds: selectedUserIds,
137+
unselectUser: _unselectUser),
137138
Expanded(
138139
child: _NewDmUserList(
139140
filteredUsers: filteredUsers,
@@ -215,10 +216,12 @@ class _NewDmSearchBar extends StatelessWidget {
215216
const _NewDmSearchBar({
216217
required this.controller,
217218
required this.selectedUserIds,
219+
required this.unselectUser,
218220
});
219221

220222
final TextEditingController controller;
221223
final Set<int> selectedUserIds;
224+
final void Function(int) unselectUser;
222225

223226
// void _removeUser
224227

@@ -266,7 +269,7 @@ class _NewDmSearchBar extends StatelessWidget {
266269
crossAxisAlignment: WrapCrossAlignment.center,
267270
children: [
268271
for (final userId in selectedUserIds)
269-
_SelectedUserChip(userId: userId),
272+
_SelectedUserChip(userId: userId, unselectUser: unselectUser),
270273
// The IntrinsicWidth lets the text field participate in the Wrap
271274
// when its content fits on the same line with a user chip,
272275
// by preventing it from expanding to fill the available width. See:
@@ -277,9 +280,13 @@ class _NewDmSearchBar extends StatelessWidget {
277280
}
278281

279282
class _SelectedUserChip extends StatelessWidget {
280-
const _SelectedUserChip({required this.userId});
283+
const _SelectedUserChip({
284+
required this.userId,
285+
required this.unselectUser,
286+
});
281287

282288
final int userId;
289+
final void Function(int) unselectUser;
283290

284291
@override
285292
Widget build(BuildContext context) {
@@ -288,24 +295,26 @@ class _SelectedUserChip extends StatelessWidget {
288295
final clampedTextScaler = MediaQuery.textScalerOf(context)
289296
.clamp(maxScaleFactor: 1.5);
290297

291-
return DecoratedBox(
292-
decoration: BoxDecoration(
293-
color: designVariables.bgMenuButtonSelected,
294-
borderRadius: BorderRadius.circular(3)),
295-
child: Row(mainAxisSize: MainAxisSize.min, children: [
296-
Avatar(userId: userId, size: clampedTextScaler.scale(22), borderRadius: 3),
297-
Flexible(
298-
child: Padding(
299-
padding: const EdgeInsetsDirectional.fromSTEB(5, 3, 4, 3),
300-
child: Text(store.userDisplayName(userId),
301-
textScaler: clampedTextScaler,
302-
maxLines: 1,
303-
overflow: TextOverflow.ellipsis,
304-
style: TextStyle(
305-
fontSize: 16,
306-
height: 16 / 16,
307-
color: designVariables.labelMenuButton)))),
308-
]));
298+
return GestureDetector(
299+
onTap: () => unselectUser(userId),
300+
child: DecoratedBox(
301+
decoration: BoxDecoration(
302+
color: designVariables.bgMenuButtonSelected,
303+
borderRadius: BorderRadius.circular(3)),
304+
child: Row(mainAxisSize: MainAxisSize.min, children: [
305+
Avatar(userId: userId, size: clampedTextScaler.scale(22), borderRadius: 3),
306+
Flexible(
307+
child: Padding(
308+
padding: const EdgeInsetsDirectional.fromSTEB(5, 3, 4, 3),
309+
child: Text(store.userDisplayName(userId),
310+
textScaler: clampedTextScaler,
311+
maxLines: 1,
312+
overflow: TextOverflow.ellipsis,
313+
style: TextStyle(
314+
fontSize: 16,
315+
height: 16 / 16,
316+
color: designVariables.labelMenuButton)))),
317+
])));
309318
}
310319
}
311320

test/widgets/new_dm_sheet_test.dart

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,15 @@ void main() {
6363
Finder findUserTile(User user) =>
6464
find.widgetWithText(InkWell, user.fullName).first;
6565

66-
Finder findUserChip(User user) =>
67-
find.byWidgetPredicate((widget) =>
66+
Finder findUserChip(User user) {
67+
final findAvatar = find.byWidgetPredicate((widget) =>
6868
widget is Avatar
6969
&& widget.userId == user.userId
7070
&& widget.size == 22);
7171

72+
return find.ancestor(of: findAvatar, matching: find.byType(GestureDetector));
73+
}
74+
7275
testWidgets('shows header with correct buttons', (tester) async {
7376
await setupSheet(tester, users: []);
7477

@@ -201,6 +204,18 @@ void main() {
201204
}
202205
}
203206

207+
208+
testWidgets('tapping user chip deselects the user', (tester) async {
209+
await setupSheet(tester, users: [eg.selfUser, eg.otherUser, eg.thirdUser]);
210+
211+
await tester.tap(findUserTile(eg.otherUser));
212+
await tester.pump();
213+
checkUserSelected(tester, eg.otherUser, true);
214+
await tester.tap(findUserChip(eg.otherUser));
215+
await tester.pump();
216+
checkUserSelected(tester, eg.otherUser, false);
217+
});
218+
204219
testWidgets('selecting and deselecting a user', (tester) async {
205220
final user = eg.user(fullName: 'Test User');
206221
await setupSheet(tester, users: [eg.selfUser, user]);

0 commit comments

Comments
 (0)