From 57871ee0439f2cacf5b26fcb82043c639b956d15 Mon Sep 17 00:00:00 2001 From: Hanson Date: Tue, 15 Oct 2024 21:23:02 +0100 Subject: [PATCH 1/7] user [nfc]: Move getDisplayEmailFor method to lib/model/store.dart Moved method to PerAccountStore, renamed method from getDisplayEmailFor to userDisplayEmail and refactored usages to remove duplication. --- lib/model/store.dart | 27 +++++++++++++++++++++++++++ lib/widgets/profile.dart | 30 +----------------------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/lib/model/store.dart b/lib/model/store.dart index 94f6c56084..5d7fcda117 100644 --- a/lib/model/store.dart +++ b/lib/model/store.dart @@ -498,6 +498,33 @@ class PerAccountStore extends ChangeNotifier with EmojiStore, UserStore, Channel return byDate.difference(dateJoined).inDays >= realmWaitingPeriodThreshold; } + /// The given user's real email address, if known, for displaying in the UI. + /// + /// Returns null if self-user isn't able to see [user]'s real email address. + String? userDisplayEmail(User user) { + if (zulipFeatureLevel >= 163) { // TODO(server-7) + // A non-null value means self-user has access to [user]'s real email, + // while a null value means it doesn't have access to the email. + // Search for "delivery_email" in https://zulip.com/api/register-queue. + return user.deliveryEmail; + } else { + if (user.deliveryEmail != null) { + // A non-null value means self-user has access to [user]'s real email, + // while a null value doesn't necessarily mean it doesn't have access + // to the email, .... + return user.deliveryEmail; + } else if (emailAddressVisibility == EmailAddressVisibility.everyone) { + // ... we have to also check for [PerAccountStore.emailAddressVisibility]. + // See: + // * https://github.com/zulip/zulip-mobile/pull/5515#discussion_r997731727 + // * https://chat.zulip.org/#narrow/stream/378-api-design/topic/email.20address.20visibility/near/1296133 + return user.email; + } else { + return null; + } + } + } + //////////////////////////////// // Streams, topics, and stuff about them. diff --git a/lib/widgets/profile.dart b/lib/widgets/profile.dart index 00620cb82d..f1328b3367 100644 --- a/lib/widgets/profile.dart +++ b/lib/widgets/profile.dart @@ -2,12 +2,10 @@ import 'dart:convert'; import 'package:flutter/material.dart'; -import '../api/model/initial_snapshot.dart'; import '../api/model/model.dart'; import '../generated/l10n/zulip_localizations.dart'; import '../model/content.dart'; import '../model/narrow.dart'; -import '../model/store.dart'; import 'app_bar.dart'; import 'content.dart'; import 'message_list.dart'; @@ -36,32 +34,6 @@ class ProfilePage extends StatelessWidget { page: ProfilePage(userId: userId)); } - /// The given user's real email address, if known, for displaying in the UI. - /// - /// Returns null if self-user isn't able to see [user]'s real email address. - String? _getDisplayEmailFor(User user, {required PerAccountStore store}) { - if (store.zulipFeatureLevel >= 163) { // TODO(server-7) - // A non-null value means self-user has access to [user]'s real email, - // while a null value means it doesn't have access to the email. - // Search for "delivery_email" in https://zulip.com/api/register-queue. - return user.deliveryEmail; - } else { - if (user.deliveryEmail != null) { - // A non-null value means self-user has access to [user]'s real email, - // while a null value doesn't necessarily mean it doesn't have access - // to the email, .... - return user.deliveryEmail; - } else if (store.emailAddressVisibility == EmailAddressVisibility.everyone) { - // ... we have to also check for [PerAccountStore.emailAddressVisibility]. - // See: - // * https://github.com/zulip/zulip-mobile/pull/5515#discussion_r997731727 - // * https://chat.zulip.org/#narrow/stream/378-api-design/topic/email.20address.20visibility/near/1296133 - return user.email; - } else { - return null; - } - } - } @override Widget build(BuildContext context) { @@ -72,7 +44,7 @@ class ProfilePage extends StatelessWidget { return const _ProfileErrorPage(); } - final displayEmail = _getDisplayEmailFor(user, store: store); + final displayEmail = store.userDisplayEmail(user); final items = [ Center( child: Avatar(userId: userId, size: 200, borderRadius: 200 / 8)), From dd900b541b7e17645dc8f0327a1ebea98829fd1b Mon Sep 17 00:00:00 2001 From: Hanson Date: Tue, 12 Nov 2024 06:32:58 +0100 Subject: [PATCH 2/7] autocomplete test [nfc]: Fix brittle avatar image finder Add `findAvatarImage` to check userId instead of URL, making it resilient to changes in avatar image details like size. Previous finder used 'findNetworkImage' which would fail if the avatar size is different Relevant Discussion: https://chat.zulip.org/#narrow/channel/516-mobile-dev-help/topic/ComposeAutocomplete.20test.20failure.20help https://github.com/zulip/zulip-flutter/pull/995#discussion_r1826404745 --- test/widgets/autocomplete_test.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/widgets/autocomplete_test.dart b/test/widgets/autocomplete_test.dart index 8dc24d239e..ce43231abb 100644 --- a/test/widgets/autocomplete_test.dart +++ b/test/widgets/autocomplete_test.dart @@ -15,6 +15,7 @@ import 'package:zulip/model/store.dart'; import 'package:zulip/model/typing_status.dart'; import 'package:zulip/widgets/compose_box.dart'; import 'package:zulip/widgets/icons.dart'; +import 'package:zulip/widgets/content.dart'; import 'package:zulip/widgets/message_list.dart'; import '../api/fake_api.dart'; @@ -145,10 +146,13 @@ void main() { TestZulipBinding.ensureInitialized(); group('@-mentions', () { + + Finder findAvatarImage(int userId) => + find.byWidgetPredicate((widget) => widget is AvatarImage && widget.userId == userId); + void checkUserShown(User user, PerAccountStore store, {required bool expected}) { check(find.text(user.fullName).evaluate().length).equals(expected ? 1 : 0); - final avatarFinder = - findNetworkImage(store.tryResolveUrl(user.avatarUrl!).toString()); + final avatarFinder = findAvatarImage(user.userId); check(avatarFinder.evaluate().length).equals(expected ? 1 : 0); } From 995fdc2abb75f803d8d214f0c20ed72689f7fe69 Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Fri, 28 Feb 2025 18:37:02 -0500 Subject: [PATCH 3/7] autocomplete test [nfc]: Remove unused store from helper Signed-off-by: Zixuan James Li --- test/widgets/autocomplete_test.dart | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/widgets/autocomplete_test.dart b/test/widgets/autocomplete_test.dart index ce43231abb..351f0fbd60 100644 --- a/test/widgets/autocomplete_test.dart +++ b/test/widgets/autocomplete_test.dart @@ -150,7 +150,7 @@ void main() { Finder findAvatarImage(int userId) => find.byWidgetPredicate((widget) => widget is AvatarImage && widget.userId == userId); - void checkUserShown(User user, PerAccountStore store, {required bool expected}) { + void checkUserShown(User user, {required bool expected}) { check(find.text(user.fullName).evaluate().length).equals(expected ? 1 : 0); final avatarFinder = findAvatarImage(user.userId); check(avatarFinder.evaluate().length).equals(expected ? 1 : 0); @@ -170,33 +170,33 @@ void main() { await tester.pumpAndSettle(); // async computation; options appear // "User Two" and "User Three" appear, but not "User One" - checkUserShown(user1, store, expected: false); - checkUserShown(user2, store, expected: true); - checkUserShown(user3, store, expected: true); + checkUserShown(user1, expected: false); + checkUserShown(user2, expected: true); + checkUserShown(user3, expected: true); // Finishing autocomplete updates compose box; causes options to disappear await tester.tap(find.text('User Three')); await tester.pump(); check(tester.widget(composeInputFinder).controller!.text) .contains(userMention(user3, users: store)); - checkUserShown(user1, store, expected: false); - checkUserShown(user2, store, expected: false); - checkUserShown(user3, store, expected: false); + checkUserShown(user1, expected: false); + checkUserShown(user2, expected: false); + checkUserShown(user3, expected: false); // Then a new autocomplete intent brings up options again // TODO(#226): Remove this extra edit when this bug is fixed. await tester.enterText(composeInputFinder, 'hello @user tw'); await tester.enterText(composeInputFinder, 'hello @user two'); await tester.pumpAndSettle(); // async computation; options appear - checkUserShown(user2, store, expected: true); + checkUserShown(user2, expected: true); // Removing autocomplete intent causes options to disappear // TODO(#226): Remove one of these edits when this bug is fixed. await tester.enterText(composeInputFinder, ''); await tester.enterText(composeInputFinder, ' '); - checkUserShown(user1, store, expected: false); - checkUserShown(user2, store, expected: false); - checkUserShown(user3, store, expected: false); + checkUserShown(user1, expected: false); + checkUserShown(user2, expected: false); + checkUserShown(user3, expected: false); debugNetworkImageHttpClientProvider = null; }); From 1aca7267b1ae5fc5dac13b282e0d6e72eddd7d59 Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Fri, 28 Feb 2025 18:38:33 -0500 Subject: [PATCH 4/7] autocomplete test [nfc]: Use findExactly for checks Signed-off-by: Zixuan James Li --- test/widgets/autocomplete_test.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/widgets/autocomplete_test.dart b/test/widgets/autocomplete_test.dart index 351f0fbd60..d1ead17b3f 100644 --- a/test/widgets/autocomplete_test.dart +++ b/test/widgets/autocomplete_test.dart @@ -151,9 +151,8 @@ void main() { find.byWidgetPredicate((widget) => widget is AvatarImage && widget.userId == userId); void checkUserShown(User user, {required bool expected}) { - check(find.text(user.fullName).evaluate().length).equals(expected ? 1 : 0); - final avatarFinder = findAvatarImage(user.userId); - check(avatarFinder.evaluate().length).equals(expected ? 1 : 0); + check(find.text(user.fullName)).findsExactly(expected ? 1 : 0); + check(findAvatarImage(user.userId)).findsExactly(expected ? 1 : 0); } testWidgets('user options appear, disappear, and change correctly', (tester) async { @@ -206,7 +205,7 @@ void main() { final iconFinder = find.byIcon(ZulipIcons.three_person); final wildcardItemFinder = find.ancestor(of: richTextFinder, matching: find.ancestor(of: iconFinder, matching: find.byType(Row))); - check(wildcardItemFinder.evaluate().length).equals(expected ? 1 : 0); + check(wildcardItemFinder).findsExactly(expected ? 1 : 0); } testWidgets('wildcard options appear, disappear, and change correctly', (tester) async { From 61b64cdaaa89f997fa26a0d96cd93c92653a1478 Mon Sep 17 00:00:00 2001 From: Hanson Date: Wed, 9 Oct 2024 10:37:18 +0100 Subject: [PATCH 5/7] 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. Fixes: #913 Co-authored-by: Zixuan James Li Signed-off-by: Zixuan James Li --- lib/widgets/autocomplete.dart | 62 +++++++++++++++++++------ lib/widgets/theme.dart | 14 ++++++ test/widgets/autocomplete_test.dart | 71 +++++++++++++++++++++++++---- 3 files changed, 126 insertions(+), 21 deletions(-) diff --git a/lib/widgets/autocomplete.dart b/lib/widgets/autocomplete.dart index 5da053c329..946e500c19 100644 --- a/lib/widgets/autocomplete.dart +++ b/lib/widgets/autocomplete.dart @@ -11,6 +11,8 @@ import '../model/autocomplete.dart'; import '../model/compose.dart'; import '../model/narrow.dart'; import 'compose_box.dart'; +import 'text.dart'; +import 'theme.dart'; abstract class AutocompleteField extends StatefulWidget { const AutocompleteField({ @@ -218,6 +220,8 @@ class ComposeAutocomplete extends AutocompleteField _MentionAutocompleteItem( option: option, narrow: narrow), @@ -227,6 +231,9 @@ class ComposeAutocomplete extends AutocompleteField= 247; // TODO(server-9) final localizations = ZulipLocalizations.of(context); - final description = switch (wildcardOption) { + return switch (wildcardOption) { WildcardMentionOption.all || WildcardMentionOption.everyone => isDmNarrow ? localizations.wildcardMentionAllDmDescription : isChannelWildcardAvailable @@ -256,32 +263,61 @@ class _MentionAutocompleteItem extends StatelessWidget { : localizations.wildcardMentionStreamDescription, WildcardMentionOption.topic => localizations.wildcardMentionTopicDescription, }; - return Text.rich(TextSpan(text: '${wildcardOption.canonicalString} ', children: [ - TextSpan(text: description, style: TextStyle(fontSize: 12, - color: DefaultTextStyle.of(context).style.color?.withValues(alpha: 0.8)))])); } @override Widget build(BuildContext context) { final store = PerAccountStoreWidget.of(context); + final designVariables = DesignVariables.of(context); + Widget avatar; - Widget label; + String label; + String? sublabel; switch (option) { case UserMentionAutocompleteResult(:var userId): final user = store.getUser(userId)!; // must exist because UserMentionAutocompleteResult - avatar = Avatar(userId: userId, size: 32, borderRadius: 3); // web uses 21px - label = Text(user.fullName); + avatar = Avatar(userId: userId, size: 36, borderRadius: 4); + label = user.fullName; + sublabel = store.userDisplayEmail(user); case WildcardMentionAutocompleteResult(:var wildcardOption): - avatar = const Icon(ZulipIcons.three_person, size: 29); // web uses 19px - label = wildcardLabel(wildcardOption, context: context, store: store); + avatar = SizedBox.square(dimension: 36, + child: const Icon(ZulipIcons.three_person, size: 24)); + label = wildcardOption.canonicalString; + sublabel = wildcardSublabel(wildcardOption, context: context, store: store); } + final labelWidget = Text( + label, + style: TextStyle( + fontSize: 18, + height: 20 / 18, + color: designVariables.contextMenuItemLabel, + ).merge(weightVariableTextStyle(context, + wght: sublabel == null ? 500 : 600)), + overflow: TextOverflow.ellipsis, + maxLines: 1); + + final sublabelWidget = sublabel == null ? null : Text( + sublabel, + style: TextStyle( + fontSize: 14, + height: 16 / 14, + color: designVariables.contextMenuItemMeta), + overflow: TextOverflow.ellipsis, + maxLines: 1); + return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + padding: const EdgeInsetsDirectional.fromSTEB(4, 4, 8, 4), child: Row(children: [ avatar, - const SizedBox(width: 8), - label, + const SizedBox(width: 6), + Expanded(child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + labelWidget, + if (sublabelWidget != null) sublabelWidget, + ])), ])); } } diff --git a/lib/widgets/theme.dart b/lib/widgets/theme.dart index ed3380e005..8d35a610dc 100644 --- a/lib/widgets/theme.dart +++ b/lib/widgets/theme.dart @@ -136,6 +136,8 @@ class DesignVariables extends ThemeExtension { composeBoxBg: const Color(0xffffffff), contextMenuCancelText: const Color(0xff222222), contextMenuItemBg: const Color(0xff6159e1), + contextMenuItemLabel: const Color(0xff242631), + contextMenuItemMeta: const Color(0xff626573), contextMenuItemText: const Color(0xff381da7), editorButtonPressedBg: Colors.black.withValues(alpha: 0.06), foreground: const Color(0xff000000), @@ -184,6 +186,8 @@ class DesignVariables extends ThemeExtension { composeBoxBg: const Color(0xff0f0f0f), contextMenuCancelText: const Color(0xffffffff).withValues(alpha: 0.75), contextMenuItemBg: const Color(0xff7977fe), + contextMenuItemLabel: const Color(0xffdfe1e8), + contextMenuItemMeta: const Color(0xff9194a3), contextMenuItemText: const Color(0xff9398fd), editorButtonPressedBg: Colors.white.withValues(alpha: 0.06), foreground: const Color(0xffffffff), @@ -240,6 +244,8 @@ class DesignVariables extends ThemeExtension { required this.composeBoxBg, required this.contextMenuCancelText, required this.contextMenuItemBg, + required this.contextMenuItemLabel, + required this.contextMenuItemMeta, required this.contextMenuItemText, required this.editorButtonPressedBg, required this.foreground, @@ -297,6 +303,8 @@ class DesignVariables extends ThemeExtension { final Color composeBoxBg; final Color contextMenuCancelText; final Color contextMenuItemBg; + final Color contextMenuItemLabel; + final Color contextMenuItemMeta; final Color contextMenuItemText; final Color editorButtonPressedBg; final Color foreground; @@ -349,6 +357,8 @@ class DesignVariables extends ThemeExtension { Color? composeBoxBg, Color? contextMenuCancelText, Color? contextMenuItemBg, + Color? contextMenuItemLabel, + Color? contextMenuItemMeta, Color? contextMenuItemText, Color? editorButtonPressedBg, Color? foreground, @@ -396,6 +406,8 @@ class DesignVariables extends ThemeExtension { composeBoxBg: composeBoxBg ?? this.composeBoxBg, contextMenuCancelText: contextMenuCancelText ?? this.contextMenuCancelText, contextMenuItemBg: contextMenuItemBg ?? this.contextMenuItemBg, + contextMenuItemLabel: contextMenuItemLabel ?? this.contextMenuItemLabel, + contextMenuItemMeta: contextMenuItemMeta ?? this.contextMenuItemMeta, contextMenuItemText: contextMenuItemText ?? this.contextMenuItemBg, editorButtonPressedBg: editorButtonPressedBg ?? this.editorButtonPressedBg, foreground: foreground ?? this.foreground, @@ -450,6 +462,8 @@ class DesignVariables extends ThemeExtension { composeBoxBg: Color.lerp(composeBoxBg, other.composeBoxBg, t)!, contextMenuCancelText: Color.lerp(contextMenuCancelText, other.contextMenuCancelText, t)!, contextMenuItemBg: Color.lerp(contextMenuItemBg, other.contextMenuItemBg, t)!, + contextMenuItemLabel: Color.lerp(contextMenuItemLabel, other.contextMenuItemLabel, t)!, + contextMenuItemMeta: Color.lerp(contextMenuItemMeta, other.contextMenuItemMeta, t)!, contextMenuItemText: Color.lerp(contextMenuItemText, other.contextMenuItemBg, t)!, editorButtonPressedBg: Color.lerp(editorButtonPressedBg, other.editorButtonPressedBg, t)!, foreground: Color.lerp(foreground, other.foreground, t)!, diff --git a/test/widgets/autocomplete_test.dart b/test/widgets/autocomplete_test.dart index d1ead17b3f..2e0ad50156 100644 --- a/test/widgets/autocomplete_test.dart +++ b/test/widgets/autocomplete_test.dart @@ -14,7 +14,6 @@ import 'package:zulip/model/narrow.dart'; import 'package:zulip/model/store.dart'; import 'package:zulip/model/typing_status.dart'; import 'package:zulip/widgets/compose_box.dart'; -import 'package:zulip/widgets/icons.dart'; import 'package:zulip/widgets/content.dart'; import 'package:zulip/widgets/message_list.dart'; @@ -201,11 +200,7 @@ void main() { }); void checkWildcardShown(WildcardMentionOption wildcard, {required bool expected}) { - final richTextFinder = find.textContaining(wildcard.canonicalString, findRichText: true); - final iconFinder = find.byIcon(ZulipIcons.three_person); - final wildcardItemFinder = find.ancestor(of: richTextFinder, - matching: find.ancestor(of: iconFinder, matching: find.byType(Row))); - check(wildcardItemFinder).findsExactly(expected ? 1 : 0); + check(find.text(wildcard.canonicalString)).findsExactly(expected ? 1 : 0); } testWidgets('wildcard options appear, disappear, and change correctly', (tester) async { @@ -226,8 +221,7 @@ void main() { checkWildcardShown(WildcardMentionOption.stream, expected: false); // Finishing autocomplete updates compose box; causes options to disappear - await tester.tap(find.textContaining(WildcardMentionOption.channel.canonicalString, - findRichText: true)); + await tester.tap(find.text(WildcardMentionOption.channel.canonicalString)); await tester.pump(); check(tester.widget(composeInputFinder).controller!.text) .contains(wildcardMention(WildcardMentionOption.channel, store: store)); @@ -239,6 +233,67 @@ void main() { debugNetworkImageHttpClientProvider = null; }); + + group('sublabel', () { + Finder findLabelsForItem({required Finder itemFinder}) { + final itemColumn = find.ancestor( + of: itemFinder, + matching: find.byType(Column), + ).first; + return find.descendant(of: itemColumn, matching: find.byType(Text)); + } + + testWidgets('no sublabel when delivery email is unavailable', (tester) async { + final user = eg.user(fullName: 'User One', deliveryEmail: null); + final composeInputFinder = await setupToComposeInput(tester, users: [user]); + + // TODO(#226): Remove this extra edit when this bug is fixed. + await tester.enterText(composeInputFinder, 'hello @user '); + await tester.enterText(composeInputFinder, 'hello @user o'); + await tester.pumpAndSettle(); // async computation; options appear + + checkUserShown(user, expected: true); + check(find.text(user.email)).findsNothing(); + check(findLabelsForItem( + itemFinder: find.text(user.fullName))).findsOne(); + + debugNetworkImageHttpClientProvider = null; + }); + + testWidgets('show sublabel when delivery email is available', (tester) async { + final user = eg.user(fullName: 'User One', deliveryEmail: 'email1@email.com'); + final composeInputFinder = await setupToComposeInput(tester, users: [user]); + + // TODO(#226): Remove this extra edit when this bug is fixed. + await tester.enterText(composeInputFinder, 'hello @user '); + await tester.enterText(composeInputFinder, 'hello @user o'); + await tester.pumpAndSettle(); // async computation; options appear + + checkUserShown(user, expected: true); + check(find.text(user.deliveryEmail!)).findsOne(); + check(findLabelsForItem( + itemFinder: find.text(user.fullName))).findsExactly(2); + + debugNetworkImageHttpClientProvider = null; + }); + + testWidgets('show sublabel for wildcard mention items', (tester) async { + final composeInputFinder = await setupToComposeInput(tester, + narrow: const ChannelNarrow(1)); + + // TODO(#226): Remove this extra edit when this bug is fixed. + await tester.enterText(composeInputFinder, '@chann'); + await tester.enterText(composeInputFinder, '@channe'); + await tester.pumpAndSettle(); // async computation; options appear + + checkWildcardShown(WildcardMentionOption.channel, expected: true); + check(find.text('Notify channel')).findsOne(); + check(findLabelsForItem( + itemFinder: find.text('channel'))).findsExactly(2); + + debugNetworkImageHttpClientProvider = null; + }); + }); }); group('emoji', () { From f86a89362d9ef218e5cd4f9207a460bdae74ee77 Mon Sep 17 00:00:00 2001 From: Hanson Date: Sat, 9 Nov 2024 23:09:58 +0100 Subject: [PATCH 6/7] theme: Fix wrong variable in contextMenuItemText color field Fix some wrong color variables that were assigned to the contextMenuItemText field. --- lib/widgets/theme.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/widgets/theme.dart b/lib/widgets/theme.dart index 8d35a610dc..3fb70365c8 100644 --- a/lib/widgets/theme.dart +++ b/lib/widgets/theme.dart @@ -408,7 +408,7 @@ class DesignVariables extends ThemeExtension { contextMenuItemBg: contextMenuItemBg ?? this.contextMenuItemBg, contextMenuItemLabel: contextMenuItemLabel ?? this.contextMenuItemLabel, contextMenuItemMeta: contextMenuItemMeta ?? this.contextMenuItemMeta, - contextMenuItemText: contextMenuItemText ?? this.contextMenuItemBg, + contextMenuItemText: contextMenuItemText ?? this.contextMenuItemText, editorButtonPressedBg: editorButtonPressedBg ?? this.editorButtonPressedBg, foreground: foreground ?? this.foreground, icon: icon ?? this.icon, @@ -464,7 +464,7 @@ class DesignVariables extends ThemeExtension { contextMenuItemBg: Color.lerp(contextMenuItemBg, other.contextMenuItemBg, t)!, contextMenuItemLabel: Color.lerp(contextMenuItemLabel, other.contextMenuItemLabel, t)!, contextMenuItemMeta: Color.lerp(contextMenuItemMeta, other.contextMenuItemMeta, t)!, - contextMenuItemText: Color.lerp(contextMenuItemText, other.contextMenuItemBg, t)!, + contextMenuItemText: Color.lerp(contextMenuItemText, other.contextMenuItemText, t)!, editorButtonPressedBg: Color.lerp(editorButtonPressedBg, other.editorButtonPressedBg, t)!, foreground: Color.lerp(foreground, other.foreground, t)!, icon: Color.lerp(icon, other.icon, t)!, From 2841a13614adf5cf370eb86f6d9667ad9a47c227 Mon Sep 17 00:00:00 2001 From: Greg Price Date: Tue, 26 Nov 2024 13:54:18 -0800 Subject: [PATCH 7/7] autocomplete: Update emoji-results style to follow design This follows the new design for user-mention results, but adapted for emoji based on the design for the emoji picker. See comment for details. --- lib/widgets/autocomplete.dart | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/widgets/autocomplete.dart b/lib/widgets/autocomplete.dart index 946e500c19..e34946a31f 100644 --- a/lib/widgets/autocomplete.dart +++ b/lib/widgets/autocomplete.dart @@ -327,12 +327,13 @@ class _EmojiAutocompleteItem extends StatelessWidget { final EmojiAutocompleteResult option; - static const _size = 32.0; - static const _notoColorEmojiTextSize = 25.7; + static const _size = 24.0; + static const _notoColorEmojiTextSize = 19.3; @override Widget build(BuildContext context) { final store = PerAccountStoreWidget.of(context); + final designVariables = DesignVariables.of(context); final candidate = option.candidate; // TODO deduplicate this logic with [EmojiPickerListEntry] @@ -351,15 +352,26 @@ class _EmojiAutocompleteItem extends StatelessWidget { ? candidate.emojiName : [candidate.emojiName, ...candidate.aliases].join(", "); // TODO(#1080) + // TODO(design): emoji autocomplete results + // There's no design in Figma for emoji autocomplete results. + // Instead we adapt the design for the emoji picker to the + // context of autocomplete results as exemplified by _MentionAutocompleteItem. + // That means: emoji size, text size, text line-height, and font weight + // from emoji picker; text color (for contrast with background) and + // outer padding from _MentionAutocompleteItem; padding around emoji glyph + // to bring it to same size as avatar in _MentionAutocompleteItem. return Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: Row(children: [ if (glyph != null) ...[ - glyph, - const SizedBox(width: 8), + Padding(padding: const EdgeInsets.all(6), + child: glyph), + const SizedBox(width: 6), ], Expanded( child: Text( + style: TextStyle(fontSize: 17, height: 18 / 17, + color: designVariables.contextMenuItemLabel), maxLines: 2, overflow: TextOverflow.ellipsis, label)),