Skip to content

Commit 2b6c6ef

Browse files
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. Relaxed avatar finder requirements so it accepts avatar URLs with size identifiers too. Fixes: #913
1 parent f86498e commit 2b6c6ef

File tree

3 files changed

+86
-26
lines changed

3 files changed

+86
-26
lines changed

lib/widgets/autocomplete.dart

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import 'package:flutter/material.dart';
22

3-
import 'content.dart';
4-
import 'store.dart';
3+
import '../api/model/model.dart';
54
import '../model/autocomplete.dart';
65
import '../model/compose.dart';
76
import '../model/narrow.dart';
87
import 'compose_box.dart';
8+
import 'content.dart';
9+
import 'store.dart';
10+
import 'text.dart';
11+
import 'theme.dart';
912

1013
abstract class AutocompleteField<QueryT extends AutocompleteQuery, ResultT extends AutocompleteResult> extends StatefulWidget {
1114
const AutocompleteField({
@@ -193,27 +196,61 @@ class ComposeAutocomplete extends AutocompleteField<MentionAutocompleteQuery, Me
193196
);
194197
}
195198

199+
196200
@override
197201
Widget buildItem(BuildContext context, int index, MentionAutocompleteResult option) {
202+
final designVariables = DesignVariables.of(context);
198203
Widget avatar;
199204
String label;
205+
String? metadata;
206+
200207
switch (option) {
201208
case UserMentionAutocompleteResult(:var userId):
202-
avatar = Avatar(userId: userId, size: 32, borderRadius: 3);
203-
label = PerAccountStoreWidget.of(context).users[userId]!.fullName;
209+
final store = PerAccountStoreWidget.of(context);
210+
final user = store.users[userId]!;
211+
avatar = Avatar(userId: userId, size: 36, borderRadius: 4);
212+
label = user.fullName;
213+
metadata = getDisplayEmailFor(user, store: store);
204214
}
205215
return InkWell(
206216
onTap: () {
207217
_onTapOption(context, option);
208218
},
219+
highlightColor: designVariables.editorButtonPressedBg,
220+
splashFactory: NoSplash.splashFactory,
221+
splashColor: Colors.transparent,
222+
borderRadius: BorderRadius.circular(8),
209223
child: Padding(
210-
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
224+
padding: const EdgeInsets.only(left: 4, right: 8, top: 4, bottom: 4),
211225
child: Row(
212226
children: [
213227
avatar,
214-
const SizedBox(width: 8),
215-
Text(label),
216-
])));
228+
const SizedBox(width: 6),
229+
Expanded(
230+
child: Column(
231+
mainAxisSize: MainAxisSize.min,
232+
crossAxisAlignment: CrossAxisAlignment.start,
233+
children: [
234+
Text(
235+
style: TextStyle(
236+
fontSize: 18,
237+
height: 20/18,
238+
color: designVariables.contextMenuItemLabel)
239+
.merge(weightVariableTextStyle(context, wght: 600)),
240+
overflow: TextOverflow.ellipsis,
241+
maxLines: 1,
242+
label,
243+
244+
),
245+
if(metadata != null) Text(
246+
style: TextStyle(
247+
fontSize: 14,
248+
height: 16/14,
249+
color: designVariables.contextMenuItemMeta),
250+
overflow: TextOverflow.ellipsis,
251+
maxLines: 1,
252+
metadata,
253+
)]))])));
217254
}
218255
}
219256

lib/widgets/theme.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
121121
contextMenuCancelText: const Color(0xff222222),
122122
contextMenuItemBg: const Color(0xff6159e1),
123123
contextMenuItemText: const Color(0xff381da7),
124+
contextMenuItemLabel: const Color(0xff242631),
125+
contextMenuItemMeta: const Color(0xff626573),
126+
editorButtonPressedBg: const Color(0xff000000).withValues(alpha: 0.06),
124127
icon: const Color(0xff666699),
125128
labelCounterUnread: const Color(0xff222222),
126129
labelEdited: const HSLColor.fromAHSL(0.35, 0, 0, 0).toColor(),
@@ -157,6 +160,9 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
157160
contextMenuCancelText: const Color(0xffffffff).withValues(alpha: 0.75),
158161
contextMenuItemBg: const Color(0xff7977fe),
159162
contextMenuItemText: const Color(0xff9398fd),
163+
contextMenuItemLabel: const Color(0xffdfe1e8),
164+
contextMenuItemMeta: const Color(0xff9194a3),
165+
editorButtonPressedBg: const Color(0xffffffff).withValues(alpha: 0.06),
160166
icon: const Color(0xff7070c2),
161167
labelCounterUnread: const Color(0xffffffff).withValues(alpha: 0.7),
162168
labelEdited: const HSLColor.fromAHSL(0.35, 0, 0, 1).toColor(),
@@ -200,6 +206,9 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
200206
required this.contextMenuCancelText,
201207
required this.contextMenuItemBg,
202208
required this.contextMenuItemText,
209+
required this.contextMenuItemLabel,
210+
required this.contextMenuItemMeta,
211+
required this.editorButtonPressedBg,
203212
required this.icon,
204213
required this.labelCounterUnread,
205214
required this.labelEdited,
@@ -244,6 +253,9 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
244253
final Color contextMenuCancelText;
245254
final Color contextMenuItemBg;
246255
final Color contextMenuItemText;
256+
final Color contextMenuItemLabel;
257+
final Color contextMenuItemMeta;
258+
final Color editorButtonPressedBg;
247259
final Color icon;
248260
final Color labelCounterUnread;
249261
final Color labelEdited;
@@ -283,6 +295,9 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
283295
Color? contextMenuCancelText,
284296
Color? contextMenuItemBg,
285297
Color? contextMenuItemText,
298+
Color? contextMenuItemLabel,
299+
Color? contextMenuItemMeta,
300+
Color? editorButtonPressedBg,
286301
Color? icon,
287302
Color? labelCounterUnread,
288303
Color? labelEdited,
@@ -317,6 +332,9 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
317332
contextMenuCancelText: contextMenuCancelText ?? this.contextMenuCancelText,
318333
contextMenuItemBg: contextMenuItemBg ?? this.contextMenuItemBg,
319334
contextMenuItemText: contextMenuItemText ?? this.contextMenuItemBg,
335+
contextMenuItemLabel: contextMenuItemLabel ?? this.contextMenuItemLabel,
336+
contextMenuItemMeta: contextMenuItemMeta ?? this.contextMenuItemMeta,
337+
editorButtonPressedBg: editorButtonPressedBg ?? this.editorButtonPressedBg,
320338
icon: icon ?? this.icon,
321339
labelCounterUnread: labelCounterUnread ?? this.labelCounterUnread,
322340
labelEdited: labelEdited ?? this.labelEdited,
@@ -358,6 +376,9 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
358376
contextMenuCancelText: Color.lerp(contextMenuCancelText, other.contextMenuCancelText, t)!,
359377
contextMenuItemBg: Color.lerp(contextMenuItemBg, other.contextMenuItemBg, t)!,
360378
contextMenuItemText: Color.lerp(contextMenuItemText, other.contextMenuItemBg, t)!,
379+
contextMenuItemMeta: Color.lerp(contextMenuItemMeta, other.contextMenuItemMeta, t)!,
380+
contextMenuItemLabel: Color.lerp(contextMenuItemLabel, other.contextMenuItemLabel, t)!,
381+
editorButtonPressedBg: Color.lerp(editorButtonPressedBg, other.editorButtonPressedBg, t)!,
361382
icon: Color.lerp(icon, other.icon, t)!,
362383
labelCounterUnread: Color.lerp(labelCounterUnread, other.labelCounterUnread, t)!,
363384
labelEdited: Color.lerp(labelEdited, other.labelEdited, t)!,

test/widgets/autocomplete_test.dart

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -108,37 +108,39 @@ void main() {
108108
group('ComposeAutocomplete', () {
109109

110110
Finder findNetworkImage(String url) {
111+
String trimmedUrl = url.substring(0, url.length -4); //Remove the extension (.png) from the URL
111112
return find.byWidgetPredicate((widget) => switch(widget) {
112-
Image(image: NetworkImage(url: var imageUrl)) when imageUrl == url
113-
=> true,
113+
Image(image: NetworkImage(url: var imageUrl)) when imageUrl.startsWith(trimmedUrl)
114+
=> true,
114115
_ => false,
115116
});
116117
}
117118

118119
void checkUserShown(User user, PerAccountStore store, {required bool expected}) {
119120
check(find.text(user.fullName).evaluate().length).equals(expected ? 1 : 0);
121+
check(find.text(user.deliveryEmail!).evaluate().length).equals(expected ? 1 : 0);
120122
final avatarFinder =
121123
findNetworkImage(store.tryResolveUrl(user.avatarUrl!).toString());
122124
check(avatarFinder.evaluate().length).equals(expected ? 1 : 0);
123125
}
124126

125127
testWidgets('options appear, disappear, and change correctly', (tester) async {
126-
final user1 = eg.user(userId: 1, fullName: 'User One', avatarUrl: 'user1.png');
127-
final user2 = eg.user(userId: 2, fullName: 'User Two', avatarUrl: 'user2.png');
128-
final user3 = eg.user(userId: 3, fullName: 'User Three', avatarUrl: 'user3.png');
129-
final composeInputFinder = await setupToComposeInput(tester, users: [user1, user2, user3]);
130-
final store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
131-
132-
// Options are filtered correctly for query
133-
// TODO(#226): Remove this extra edit when this bug is fixed.
134-
await tester.enterText(composeInputFinder, 'hello @user ');
135-
await tester.enterText(composeInputFinder, 'hello @user t');
136-
await tester.pumpAndSettle(); // async computation; options appear
137-
138-
// "User Two" and "User Three" appear, but not "User One"
139-
checkUserShown(user1, store, expected: false);
140-
checkUserShown(user2, store, expected: true);
141-
checkUserShown(user3, store, expected: true);
128+
final user1 = eg.user(userId: 1, fullName: 'User One', avatarUrl: 'user1.png', deliveryEmail: '[email protected]');
129+
final user2 = eg.user(userId: 2, fullName: 'User Two', avatarUrl: 'user2.png', deliveryEmail: '[email protected]');
130+
final user3 = eg.user(userId: 3, fullName: 'User Three', avatarUrl: 'user3.png', deliveryEmail: '[email protected]');
131+
final composeInputFinder = await setupToComposeInput(tester, users: [user1, user2, user3]);
132+
final store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
133+
134+
// Options are filtered correctly for query
135+
// TODO(#226): Remove this extra edit when this bug is fixed.
136+
await tester.enterText(composeInputFinder, 'hello @user ');
137+
await tester.enterText(composeInputFinder, 'hello @user t');
138+
await tester.pumpAndSettle(); // async computation; options appear
139+
140+
// "User Two" and "User Three" appear, but not "User One"
141+
checkUserShown(user1, store, expected: false);
142+
checkUserShown(user2, store, expected: true);
143+
checkUserShown(user3, store, expected: true);
142144

143145
// Finishing autocomplete updates compose box; causes options to disappear
144146
await tester.tap(find.text('User Three'));

0 commit comments

Comments
 (0)