Skip to content

Commit fba1301

Browse files
fombalangHanson
authored and
Hanson
committed
autocomplete: Implement new design for @-mention autocomplete items
Implemented new design for @-mention autocomplete items. Added new `contextMenuItemLabel` and `contextMenuItemMeta` color variables to `designVariables` class. Updated autocomplete tests. Checks delivery email is visible only when its not null, and the server generated fake email isn't shown. Also fixes minor bug in example_data.dart which causes delivery email to always be shown in tests. Fixes: #913
1 parent 28c4539 commit fba1301

File tree

3 files changed

+112
-22
lines changed

3 files changed

+112
-22
lines changed

lib/widgets/autocomplete.dart

+45-8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import '../model/autocomplete.dart';
88
import '../model/compose.dart';
99
import '../model/narrow.dart';
1010
import 'compose_box.dart';
11+
import 'text.dart';
12+
import 'theme.dart';
1113

1214
abstract class AutocompleteField<QueryT extends AutocompleteQuery, ResultT extends AutocompleteResult> extends StatefulWidget {
1315
const AutocompleteField({
@@ -210,6 +212,8 @@ class ComposeAutocomplete extends AutocompleteField<ComposeAutocompleteQuery, Co
210212

211213
@override
212214
Widget buildItem(BuildContext context, int index, ComposeAutocompleteResult option) {
215+
final designVariables = DesignVariables.of(context);
216+
213217
final child = switch (option) {
214218
MentionAutocompleteResult() => _MentionAutocompleteItem(option: option),
215219
EmojiAutocompleteResult() => _EmojiAutocompleteItem(option: option),
@@ -218,6 +222,9 @@ class ComposeAutocomplete extends AutocompleteField<ComposeAutocompleteQuery, Co
218222
onTap: () {
219223
_onTapOption(context, option);
220224
},
225+
highlightColor: designVariables.editorButtonPressedBg,
226+
splashFactory: NoSplash.splashFactory,
227+
borderRadius: BorderRadius.circular(5),
221228
child: child);
222229
}
223230
}
@@ -229,21 +236,51 @@ class _MentionAutocompleteItem extends StatelessWidget {
229236

230237
@override
231238
Widget build(BuildContext context) {
239+
final designVariables = DesignVariables.of(context);
240+
232241
Widget avatar;
233242
String label;
243+
String? subLabel;
234244
switch (option) {
235245
case UserMentionAutocompleteResult(:var userId):
236-
avatar = Avatar(userId: userId, size: 32, borderRadius: 3);
237-
label = PerAccountStoreWidget.of(context).users[userId]!.fullName;
246+
final store = PerAccountStoreWidget.of(context);
247+
final user = store.users[userId]!;
248+
avatar = Avatar(userId: userId, size: 36, borderRadius: 4);
249+
label = user.fullName;
250+
subLabel = store.userDisplayEmail(user, store: store);
238251
}
239252

240253
return Padding(
241-
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
242-
child: Row(children: [
243-
avatar,
244-
const SizedBox(width: 8),
245-
Text(label),
246-
]));
254+
padding: const EdgeInsetsDirectional.fromSTEB(4, 4, 8, 4),
255+
child: Row(
256+
children: [
257+
avatar,
258+
const SizedBox(width: 6),
259+
Expanded(child: Column(
260+
mainAxisSize: MainAxisSize.min,
261+
crossAxisAlignment: CrossAxisAlignment.start,
262+
children: [
263+
Text(
264+
style: TextStyle(
265+
fontSize: 18,
266+
height: 20 / 18,
267+
color: designVariables.contextMenuItemLabel,
268+
)
269+
.merge(weightVariableTextStyle(context, wght: 600)),
270+
overflow: TextOverflow.ellipsis,
271+
maxLines: 1,
272+
label),
273+
if (subLabel != null) Text(
274+
style: TextStyle(
275+
fontSize: 14,
276+
height: 16 / 14,
277+
color: designVariables.contextMenuItemMeta,
278+
),
279+
overflow: TextOverflow.ellipsis,
280+
maxLines: 1,
281+
subLabel),
282+
])),
283+
]));
247284
}
248285
}
249286

lib/widgets/theme.dart

+14
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
133133
composeBoxBg: const Color(0xffffffff),
134134
contextMenuCancelText: const Color(0xff222222),
135135
contextMenuItemBg: const Color(0xff6159e1),
136+
contextMenuItemLabel: const Color(0xff242631),
137+
contextMenuItemMeta: const Color(0xff626573),
136138
contextMenuItemText: const Color(0xff381da7),
137139
editorButtonPressedBg: Colors.black.withValues(alpha: 0.06),
138140
foreground: const Color(0xff000000),
@@ -182,6 +184,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
182184
composeBoxBg: const Color(0xff0f0f0f),
183185
contextMenuCancelText: const Color(0xffffffff).withValues(alpha: 0.75),
184186
contextMenuItemBg: const Color(0xff7977fe),
187+
contextMenuItemLabel: const Color(0xffdfe1e8),
188+
contextMenuItemMeta: const Color(0xff9194a3),
185189
contextMenuItemText: const Color(0xff9398fd),
186190
editorButtonPressedBg: Colors.white.withValues(alpha: 0.06),
187191
foreground: const Color(0xffffffff),
@@ -239,6 +243,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
239243
required this.composeBoxBg,
240244
required this.contextMenuCancelText,
241245
required this.contextMenuItemBg,
246+
required this.contextMenuItemLabel,
247+
required this.contextMenuItemMeta,
242248
required this.contextMenuItemText,
243249
required this.editorButtonPressedBg,
244250
required this.foreground,
@@ -297,6 +303,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
297303
final Color composeBoxBg;
298304
final Color contextMenuCancelText;
299305
final Color contextMenuItemBg;
306+
final Color contextMenuItemLabel;
307+
final Color contextMenuItemMeta;
300308
final Color contextMenuItemText;
301309
final Color editorButtonPressedBg;
302310
final Color foreground;
@@ -350,6 +358,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
350358
Color? composeBoxBg,
351359
Color? contextMenuCancelText,
352360
Color? contextMenuItemBg,
361+
Color? contextMenuItemLabel,
362+
Color? contextMenuItemMeta,
353363
Color? contextMenuItemText,
354364
Color? editorButtonPressedBg,
355365
Color? foreground,
@@ -398,6 +408,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
398408
composeBoxBg: composeBoxBg ?? this.composeBoxBg,
399409
contextMenuCancelText: contextMenuCancelText ?? this.contextMenuCancelText,
400410
contextMenuItemBg: contextMenuItemBg ?? this.contextMenuItemBg,
411+
contextMenuItemLabel: contextMenuItemLabel ?? this.contextMenuItemLabel,
412+
contextMenuItemMeta: contextMenuItemMeta ?? this.contextMenuItemMeta,
401413
contextMenuItemText: contextMenuItemText ?? this.contextMenuItemBg,
402414
editorButtonPressedBg: editorButtonPressedBg ?? this.editorButtonPressedBg,
403415
foreground: foreground ?? this.foreground,
@@ -453,6 +465,8 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
453465
composeBoxBg: Color.lerp(composeBoxBg, other.composeBoxBg, t)!,
454466
contextMenuCancelText: Color.lerp(contextMenuCancelText, other.contextMenuCancelText, t)!,
455467
contextMenuItemBg: Color.lerp(contextMenuItemBg, other.contextMenuItemBg, t)!,
468+
contextMenuItemLabel: Color.lerp(contextMenuItemLabel, other.contextMenuItemLabel, t)!,
469+
contextMenuItemMeta: Color.lerp(contextMenuItemMeta, other.contextMenuItemMeta, t)!,
456470
contextMenuItemText: Color.lerp(contextMenuItemText, other.contextMenuItemBg, t)!,
457471
editorButtonPressedBg: Color.lerp(editorButtonPressedBg, other.editorButtonPressedBg, t)!,
458472
foreground: Color.lerp(foreground, other.foreground, t)!,

test/widgets/autocomplete_test.dart

+53-14
Original file line numberDiff line numberDiff line change
@@ -131,16 +131,18 @@ void main() {
131131
Finder findAvatarImage(int userId) =>
132132
find.byWidgetPredicate((widget) => widget is AvatarImage && widget.userId == userId);
133133

134-
void checkUserShown(User user, PerAccountStore store, {required bool expected}) {
134+
void checkUserShown(User user, {required bool expected, bool? deliveryEmailExpected}) {
135+
deliveryEmailExpected ??= expected;
135136
check(find.text(user.fullName).evaluate().length).equals(expected ? 1 : 0);
137+
check(find.text(user.deliveryEmail?? "").evaluate().length).equals(deliveryEmailExpected ? 1 : 0);
136138
final avatarFinder = findAvatarImage(user.userId);
137139
check(avatarFinder.evaluate().length).equals(expected ? 1 : 0);
138140
}
139141

140142
testWidgets('options appear, disappear, and change correctly', (tester) async {
141-
final user1 = eg.user(userId: 1, fullName: 'User One', avatarUrl: 'user1.png');
142-
final user2 = eg.user(userId: 2, fullName: 'User Two', avatarUrl: 'user2.png');
143-
final user3 = eg.user(userId: 3, fullName: 'User Three', avatarUrl: 'user3.png');
143+
final user1 = eg.user(userId: 1, fullName: 'User One', avatarUrl: 'user1.png',deliveryEmail: '[email protected]');
144+
final user2 = eg.user(userId: 2, fullName: 'User Two', avatarUrl: 'user2.png', deliveryEmail: '[email protected]');
145+
final user3 = eg.user(userId: 3, fullName: 'User Three', avatarUrl: 'user3.png', deliveryEmail: '[email protected]');
144146
final composeInputFinder = await setupToComposeInput(tester, users: [user1, user2, user3]);
145147
final store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
146148

@@ -151,33 +153,70 @@ void main() {
151153
await tester.pumpAndSettle(); // async computation; options appear
152154

153155
// "User Two" and "User Three" appear, but not "User One"
154-
checkUserShown(user1, store, expected: false);
155-
checkUserShown(user2, store, expected: true);
156-
checkUserShown(user3, store, expected: true);
156+
checkUserShown(user1, expected: false);
157+
checkUserShown(user2, expected: true);
158+
checkUserShown(user3, expected: true);
157159

158160
// Finishing autocomplete updates compose box; causes options to disappear
159161
await tester.tap(find.text('User Three'));
160162
await tester.pump();
161163
check(tester.widget<TextField>(composeInputFinder).controller!.text)
162164
.contains(mention(user3, users: store.users));
163-
checkUserShown(user1, store, expected: false);
164-
checkUserShown(user2, store, expected: false);
165-
checkUserShown(user3, store, expected: false);
165+
checkUserShown(user1, expected: false);
166+
checkUserShown(user2, expected: false);
167+
checkUserShown(user3, expected: false);
166168

167169
// Then a new autocomplete intent brings up options again
168170
// TODO(#226): Remove this extra edit when this bug is fixed.
169171
await tester.enterText(composeInputFinder, 'hello @user tw');
170172
await tester.enterText(composeInputFinder, 'hello @user two');
171173
await tester.pumpAndSettle(); // async computation; options appear
172-
checkUserShown(user2, store, expected: true);
174+
checkUserShown(user2, expected: true);
173175

174176
// Removing autocomplete intent causes options to disappear
175177
// TODO(#226): Remove one of these edits when this bug is fixed.
176178
await tester.enterText(composeInputFinder, '');
177179
await tester.enterText(composeInputFinder, ' ');
178-
checkUserShown(user1, store, expected: false);
179-
checkUserShown(user2, store, expected: false);
180-
checkUserShown(user3, store, expected: false);
180+
checkUserShown(user1, expected: false);
181+
checkUserShown(user2, expected: false);
182+
checkUserShown(user3, expected: false);
183+
184+
debugNetworkImageHttpClientProvider = null;
185+
});
186+
187+
testWidgets('delivery email not visible when unavailable', (tester) async {
188+
final user1 = eg.user(userId: 1, fullName: 'User One', avatarUrl: 'user1.png',);
189+
final composeInputFinder = await setupToComposeInput(tester, users: [user1]);
190+
191+
TypingNotifier.debugEnable = false;
192+
addTearDown(TypingNotifier.debugReset);
193+
194+
// Options are filtered correctly for query
195+
// TODO(#226): Remove this extra edit when this bug is fixed.
196+
await tester.enterText(composeInputFinder, 'hello @user ');
197+
await tester.enterText(composeInputFinder, 'hello @user o');
198+
await tester.pumpAndSettle(); // async computation; options appear
199+
200+
// Check "User One"'s delivery email is not visible
201+
checkUserShown(user1, expected: true, deliveryEmailExpected: false);
202+
203+
debugNetworkImageHttpClientProvider = null;
204+
});
205+
testWidgets('delivery email visible when available', (tester) async {
206+
final user1 = eg.user(userId: 1, fullName: 'User One', avatarUrl: 'user1.png', deliveryEmail: '[email protected]');
207+
final composeInputFinder = await setupToComposeInput(tester, users: [user1]);
208+
209+
TypingNotifier.debugEnable = false;
210+
addTearDown(TypingNotifier.debugReset);
211+
212+
// Options are filtered correctly for query
213+
// TODO(#226): Remove this extra edit when this bug is fixed.
214+
await tester.enterText(composeInputFinder, 'hello @user ');
215+
await tester.enterText(composeInputFinder, 'hello @user o');
216+
await tester.pumpAndSettle(); // async computation; options appear
217+
218+
// Check "User One"'s delivery email is visible
219+
checkUserShown(user1, expected: true, deliveryEmailExpected: true);
181220

182221
debugNetworkImageHttpClientProvider = null;
183222
});

0 commit comments

Comments
 (0)