-
Notifications
You must be signed in to change notification settings - Fork 306
new_dm: Add UI for starting new DM conversations #1322
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
b407738
to
d8818a2
Compare
Thanks for working on this @chimnayajith! I took a quick look at the implementation and checked the design. There are places where it currently does not match the Figma, for example: Container(
width: MediaQuery.of(context).size.width,
constraints: const BoxConstraints(
minHeight: 44,
maxHeight: 124,
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: designVariables.bgSearchInput,
),
child: SingleChildScrollView(
controller: scrollController,
child: Row(
children: [
Expanded(
child: Wrap(
spacing: 4,
runSpacing: 4, where the horizontal padding should be 14px; and the spacing between the pills should be 6px. I think there might be more places like this, so please sure to check your PR with the design and make sure that they match exactly. While working on the next revision, please also try to break down the sheet into reasonable widgets, instead of a single one that encompasses the entire new DM page. There are also things like collapsing the closing brackets into a single line; you can find examples of that throughout the codebase: // [...]
child: SafeArea(
child: SizedBox(height: 48,
child: Center(
child: ConstrainedBox(
// TODO(design): determine a suitable max width for bottom nav bar
constraints: const BoxConstraints(maxWidth: 600),
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
for (final navigationBarButton in navigationBarButtons)
Expanded(child: navigationBarButton),
]))))))); All those changes will help make your PR more reviewable. At the high-level, I think the user filtering code should re-use the |
0bc3e12
to
a6cc695
Compare
@PIG208 Thanks for the review! I’ve made the necessary changes to match the Figma, and refactored the sheet into smaller widgets. I also followed the code style guidelines you mentioned. I’ve pushed the revision—PTAL. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update! I went through the implementation of the design (mainly layout but not the colors) and left some comments. I haven't read the tests yet, but it should be a good amount of feedback to work on a new revision for.
width: 137, | ||
height: 48, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
icon: Icon(Icons.add, size: 24), | ||
label: Text(zulipLocalizations.newDmFabButtonLabel, style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a way to control/verify the spacing between the icon and the label? In Figma it's 8px but from this code it is not obvious if it complies to the design.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The extendedIconLabelSpacing
field for extended floating action buttons has a default value of 8.0. Should it still be specified?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. Because the default value can change (though unlikely), but we have a specific value in mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have given it in the revision pushed.
lib/widgets/new_dm_sheet.dart
Outdated
|
||
void showNewDmSheet(BuildContext context) { | ||
final store = PerAccountStoreWidget.of(context); | ||
showModalBottomSheet<dynamic>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's avoid dynamic
. We in general follow Flutter's style guide.
lib/widgets/new_dm_sheet.dart
Outdated
@override | ||
Widget build(BuildContext context) { | ||
return SizedBox( | ||
height: MediaQuery.of(context).size.height * 0.95, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't seem right to size it this way. If the goal is to avoid the top of the bottom sheet overlapping with the device's top inset, see useSafeArea
on showModalBottomSheet
.
lib/widgets/new_dm_sheet.dart
Outdated
} | ||
} | ||
|
||
class NewDmHeader extends StatelessWidget { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For helper widgets like this, let's make them private (_NewDmHeader
) unless it is otherwise necessary.
lib/widgets/new_dm_sheet.dart
Outdated
hintStyle: TextStyle( | ||
fontSize: 17, | ||
height: 1.0, | ||
color: designVariables.textInput.withFadedAlpha(0.5)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like we do have a Figma variable for this: label-search-prompt
.
lib/widgets/new_dm_sheet.dart
Outdated
children: [ | ||
Avatar(userId: userId, size: 22, borderRadius: 3), | ||
Padding( | ||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 3), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/widgets/new_dm_sheet.dart
Outdated
|
||
final List<User> filteredUsers; | ||
final Set<int> selectedUserIds; | ||
final void Function(int) onUserSelected; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name of this is a bit misleading, because "selected" seems to indicate that the user is added to the list of selected user, when in reality it is more like toggling the selection of the user.
How about something like void Function(int userId) onUserTapped
, with the parameter name.
Widget build(BuildContext context) { | ||
final designVariables = DesignVariables.of(context); | ||
|
||
return ListView.builder( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/widgets/new_dm_sheet.dart
Outdated
child: Padding( | ||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), | ||
child: Container( | ||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the outer Padding
redundant?
lib/widgets/new_dm_sheet.dart
Outdated
}); | ||
} | ||
|
||
// Scroll to the search field when the user selects a user |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, is this part of the UX specified in the design? It would be good to link to the relevant discussion/Figma if that's the case. Either way, I feel that it might be the best to leave this out from the initial implementation, to simplify things as we work it out.
lib/widgets/new_dm_sheet.dart
Outdated
size: 24, | ||
color: selectedUserIds.isEmpty | ||
? designVariables.icon.withFadedAlpha(0.5) | ||
: designVariables.icon)])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Square brackets are usually not wrapped, because they help indicate the end of a list and that the items before them are parallel.
lib/widgets/new_dm_sheet.dart
Outdated
super.key, | ||
required this.searchController, | ||
required this.scrollController, | ||
required this.selectedUserIds}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, curly brackets are not wrapped, because they indicate the end of a parameter list, function, etc.
9439a4a
to
e033db5
Compare
@PIG208 Pushed a revision. PTAL! |
@chimnayajith Are the screenshots in the PR description up to date? Please update them if not. |
I've updated the screenshots. Please take a look! |
Let's change "Add Person" to "Add user", which will be more consistent with the web app. I'm not sure why your screenshots have varied capitalization, but in any case, only the first word should be capitalized. |
Actually, it would probably be better to match the web app more fully, if it doesn't feel too long in the mobile context.
|
Why is there no "Add Person" in your last screenshot? What is different vs. the other ones? |
Continuing the discussion in CZO |
e033db5
to
126c4cc
Compare
126c4cc
to
a8bcda8
Compare
@PIG208 Pushed a revision with the mentioned changes in UI. PTAL Note: Uses |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @chimnayajith!
This is still in maintainer review with @PIG208, but I took a quick skim just now and also tried playing with this in the running app. Here's one high-level comment on the code, and a couple of things that jumped out at me in the UX.
return SafeArea( | ||
return Scaffold( | ||
body: SafeArea( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will mean a Scaffold
nested inside a Scaffold
. See the Scaffold docs on that:
https://main-api.flutter.dev/flutter/material/Scaffold-class.html#nested-scaffolds
Have you tried alternatives to nesting the Scaffolds?
zulipLocalizations.newDmFabButtonLabel, | ||
style: const TextStyle(fontSize: 20, height: 24 / 20) | ||
.merge(weightVariableTextStyle(context, wght: 500))), | ||
backgroundColor: designVariables.fabBg, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying out the PR, this color doesn't look right.
It looks like the screenshots in the PR description agree with what I see running it (good), but don't agree on this color with what's in Figma.
floatingActionButton: Container( | ||
constraints: const BoxConstraints( | ||
minWidth: 137, | ||
minHeight: 48), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, most of the time, as long as we get the paddings right, the size of the button should match the Figma design with normal UI scaling setting, so the constraints
is not needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update! I went through the implementation but haven't looked at the tests in this round. Most of the colors look good to me. The font height/size/weight also looks good. Once all the comments are addressed, this should match the design quite well. Using devtool will be helpful to confirm the actual sizes/paddings.
child: NewDmPicker(pageContext: context)))); | ||
} | ||
|
||
class NewDmPicker extends StatefulWidget { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: for NewDmPicker
, we can add a @visibleForTesting
annotation since we only use it for testing externally.
lib/widgets/new_dm_sheet.dart
Outdated
padding: const EdgeInsets.all(8), | ||
child: Icon( | ||
isSelected ? Icons.check_circle : Icons.circle_outlined, | ||
color: isSelected ? designVariables.radioFillSelected : designVariables.radioBorder, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is too wide (>80 characters); consider breaking this into multiple lines.
lib/widgets/new_dm_sheet.dart
Outdated
]))); | ||
}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: add a newline at the end of the file
lib/widgets/new_dm_sheet.dart
Outdated
class NewDmPicker extends StatefulWidget { | ||
const NewDmPicker({ | ||
super.key, | ||
required this.pageContext |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: need a trailing comma here
lib/widgets/new_dm_sheet.dart
Outdated
const _NewDmUserList({ | ||
required this.filteredUsers, | ||
required this.selectedUserIds, | ||
required this.onUserTapped |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: similar issue with missing trailing comma
lib/widgets/new_dm_sheet.dart
Outdated
Widget build(BuildContext context) { | ||
final designVariables = DesignVariables.of(context); | ||
final store = PerAccountStoreWidget.of(context); | ||
final user = store.getUser(userId)!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I don't feel confident that the userId
will always present. If it does, we need a comment justifying why it is always present; if it doesn't, we should be more defensive and find a way to not throw.
lib/widgets/new_dm_sheet.dart
Outdated
final user = store.getUser(userId)!; | ||
|
||
return Container( | ||
decoration: BoxDecoration(color: designVariables.bgMenuButtonSelected, borderRadius: BorderRadius.circular(3)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: missing indentation; this line should also be wrapped because it is too long (>80 characters)
lib/widgets/new_dm_sheet.dart
Outdated
children: [ | ||
_buildBackButton(context), | ||
Text(zulipLocalizations.newDmSheetScreenTitle, | ||
style: TextStyle(color: designVariables.title, | ||
fontSize: 20, height: 30 / 20) | ||
.merge(weightVariableTextStyle(context, wght: 600))), | ||
_buildNextButton(context), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final isSelected = selectedUserIds.contains(user.userId); | ||
|
||
return InkWell( | ||
onTap: () => onUserTapped(user.userId), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use splashFactory: NoSplash.splashFactory,
here; in general we avoid having Material's splash effect in the app.
lib/widgets/new_dm_sheet.dart
Outdated
Avatar(userId: user.userId, size: 32, borderRadius: 3), | ||
Expanded( | ||
child: Padding( | ||
padding: const EdgeInsetsDirectional.fromSTEB(8, 6.5, 0, 6.5), | ||
child: Text(user.fullName, | ||
style: TextStyle( | ||
fontSize: 17, | ||
height: 19 / 17, | ||
color: designVariables.textMessage) | ||
.merge(weightVariableTextStyle(context, wght: 500))))), | ||
]))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hii @chimnayajith, Since already a lot of reviews and updates have gone through the process. If you are not able to take this further or need some help, I will be happy to join this and proceed further. |
@Gaurav-Kushwaha-1225 Will be sending in a revision today, Got occupied with exams. |
49a7fbe
to
30b556d
Compare
@PIG208 Pushed a revision. PTAL. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! This looks a lot closer to the design now and I left some small comments on the implementation. This time, I also reviewed the tests.
child: Text(zulipLocalizations.newDmSheetScreenTitle, | ||
style: TextStyle(color: designVariables.title, | ||
fontSize: 20, height: 30 / 20) | ||
.merge(weightVariableTextStyle(context, wght: 600)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: indentation
lib/widgets/new_dm_sheet.dart
Outdated
} | ||
|
||
class _SelectedUserChip extends StatelessWidget { | ||
const _SelectedUserChip({ required this.userId }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove spaces around the parameter
lib/widgets/theme.dart
Outdated
@@ -197,6 +203,9 @@ class DesignVariables extends ThemeExtension<DesignVariables> { | |||
contextMenuItemMeta: const Color(0xff9194a3), | |||
contextMenuItemText: const Color(0xff9398fd), | |||
editorButtonPressedBg: Colors.white.withValues(alpha: 0.06), | |||
fabBg: const Color(0xff4f42c9), | |||
fabBgPressed: const Color(0xff6159e1), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -197,6 +203,9 @@ class DesignVariables extends ThemeExtension<DesignVariables> { | |||
contextMenuItemMeta: const Color(0xff9194a3), | |||
contextMenuItemText: const Color(0xff9398fd), | |||
editorButtonPressedBg: Colors.white.withValues(alpha: 0.06), | |||
fabBg: const Color(0xff4f42c9), | |||
fabBgPressed: const Color(0xff6159e1), | |||
fabLabel: const Color(0xffeceefc), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test/widgets/new_dm_sheet_test.dart
Outdated
await runAndCheck(tester, user: user); | ||
}); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: need a newline after this
test/widgets/new_dm_sheet_test.dart
Outdated
} | ||
|
||
testWidgets('navigates to message list on Next', (tester) async { | ||
final user = eg.user(userId: 1, fullName: 'Test User'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can skip specifying userId
because we don't rely on it being this particular value for the test.
test/widgets/new_dm_sheet_test.dart
Outdated
eg.user(userId: 1, fullName: 'Alice Anderson'), | ||
eg.user(userId: 2, fullName: 'Bob Brown'), | ||
eg.user(userId: 3, fullName: 'Alice Carter'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we can skip specifying user IDs here
test/widgets/new_dm_sheet_test.dart
Outdated
for (final user in users) { | ||
await store.addUser(user); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use store.addUsers
instead
test/widgets/new_dm_sheet_test.dart
Outdated
check(nextButton.onTap).isNull(); | ||
}); | ||
|
||
testWidgets('shows filtered users based on search', (tester) async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there are some other interesting filtering logic that can be tested. Such as checking that the self user is excluded from the list, the search is case-insensitive. Another useful test would be checking if unknown users are handled correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't able to write tests specifically for handling unknown users, but the logic relies on userDisplayName
, which is already covered in user_test.dart
. I also didn’t find any other tests that handle this specifically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realized that it's a product question as to whether we want to allow selecting the self user. I think we should, since that's allowed on web and the legacy app.
lib/widgets/new_dm_sheet.dart
Outdated
}); | ||
|
||
final BuildContext pageContext; | ||
final PerAccountStore store; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can skip passing this by replacing the _updateFilteredUsers
call with an overridden didChangeDependencies
.
@override
void didChangeDependencies() {
super.didChangeDependencies();
_handleSearchUpdate();
}
Hi @chimnayajith! Thanks for working on this. Since this issue is a launch blocker, we would prioritize reviewing this/getting this done first; the other issue that you are working on (#1344) is a launch goal, which means that it will likely receive less attention until we clear the blockers. |
30b556d
to
61ede77
Compare
Add a modal bottom sheet UI for starting direct messages: - Search and select users from global list - Support single and group DMs - Navigate to message list after selection Design reference: https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=4903-31879&p=f&t=pQP4QcxpccllCF7g-0 Fixes: zulip#127
61ede77
to
82b1b66
Compare
@PIG208 Pushed a revision. PTAL. I had missed the "No users found" screen earlier; just added it and included the screenshots in the PR description. There aren’t any design references for it, but it follows how it’s done in the RN app |
return Center( | ||
child: Padding( | ||
padding: const EdgeInsets.all(24), | ||
child: Text( | ||
zulipLocalizations.newDmSheetNoUsersFound, | ||
style: TextStyle( | ||
color: designVariables.labelMenuButton, | ||
fontSize: 16)))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will need a TODO(design)
comment since it is not a part of the Figma design.
decoration: isSelected ? BoxDecoration( | ||
color: designVariables.bgMenuButtonSelected, | ||
borderRadius: BorderRadius.circular(10)) : null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Container
shouldn't be necessary; we can use DecoratedBox
another nit: I think we usually have the null
case first, for reasons similar to why early returns are preferred.
color: designVariables.textMessage) | ||
.merge(weightVariableTextStyle(context, wght: 500))))), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
color: designVariables.textMessage) | |
.merge(weightVariableTextStyle(context, wght: 500))))), | |
color: designVariables.textMessage, | |
).merge(weightVariableTextStyle(context, wght: 500))))), |
This way the end boundary of TextStyle
is clear.
test/widgets/new_dm_sheet_test.dart
Outdated
check(nextButton.onTap).isNull(); | ||
}); | ||
|
||
testWidgets('shows filtered users based on search', (tester) async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realized that it's a product question as to whether we want to allow selecting the self user. I think we should, since that's allowed on web and the legacy app.
if (users.length == 1) { | ||
check(find.descendant( | ||
of: find.byType(ZulipAppBar), | ||
matching: find.text('DMs with ${users[0].fullName}'))).findsOne(); | ||
} else { | ||
final names = users.map((user) => user.fullName).join(', '); | ||
check(find.descendant( | ||
of: find.byType(ZulipAppBar), | ||
matching: find.text('DMs with $names'))).findsOne(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will probably be easier to pass the expected app bar title from the tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update! We have gone through the design in previous rounds. In this review I focused mostly on new changes like _NewDmButton
and the tests.
onTapUp: (_) => setState(() => _pressed = false), | ||
onTapCancel: () => setState(() => _pressed = false), | ||
borderRadius: BorderRadius.circular(28), | ||
child: Container( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use the animated version AnimatedContainer
here. The Figma design has specified the duration and curve for transitions.
Looks like it also handles the box shadow animation properly, so we can just complete the TODO from #1322 (comment), now that we do have a AnimatedScaleOnTap style widget.
zulipLocalizations.newDmFabButtonLabel, | ||
style: TextStyle(fontSize: 20, height: 24 / 20) | ||
.merge(weightVariableTextStyle(context, wght: 500)) | ||
.copyWith(color: fabLabelColor)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be fine to just specify the color when constructing TextStyle
, right?
@@ -138,3 +149,55 @@ class RecentDmConversationsItem extends StatelessWidget { | |||
])))); | |||
} | |||
} | |||
|
|||
class _NewDmButton extends StatefulWidget { | |||
final VoidCallback onPressed; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: inline the callback in the _NewDmButtonState.build
so that we can remove this parameter; it seems fitting that this widget is responsible for calling showNewDmSheet
final fabBgColor = _pressed ? designVariables.fabBgPressed : designVariables.fabBg; | ||
final fabLabelColor = _pressed ? designVariables.fabLabelPressed : designVariables.fabLabel; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: these lines are too long; wrap them so that they stay under 80 characters in width
import 'test_app.dart'; | ||
|
||
Future<void> setupSheet(WidgetTester tester, { | ||
required List<User> users |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: missing trailing comma
testWidgets('selecting a user', (tester) async { | ||
final user = eg.user(fullName: 'Test User'); | ||
await setupSheet(tester, users: [user]); | ||
Finder findNextButton() => find.widgetWithText(GestureDetector, 'Next'); | ||
|
||
check(find.byIcon(Icons.circle_outlined)).findsOne(); | ||
check(find.byIcon(Icons.check_circle)).findsNothing(); | ||
|
||
var nextButton = tester.widget<GestureDetector>(findNextButton()); | ||
check(nextButton.onTap).isNull(); | ||
final userTile = find.ancestor( | ||
of: find.text(user.fullName), | ||
matching: find.byType(InkWell)); | ||
await tester.tap(userTile); | ||
await tester.pump(); | ||
check(find.byIcon(Icons.check_circle)).findsOne(); | ||
check(find.byIcon(Icons.circle_outlined)).findsNothing(); | ||
nextButton = tester.widget<GestureDetector>(findNextButton()); | ||
check(nextButton.onTap).isNotNull(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
testWidgets('selecting a user', (tester) async { | |
final user = eg.user(fullName: 'Test User'); | |
await setupSheet(tester, users: [user]); | |
Finder findNextButton() => find.widgetWithText(GestureDetector, 'Next'); | |
check(find.byIcon(Icons.circle_outlined)).findsOne(); | |
check(find.byIcon(Icons.check_circle)).findsNothing(); | |
var nextButton = tester.widget<GestureDetector>(findNextButton()); | |
check(nextButton.onTap).isNull(); | |
final userTile = find.ancestor( | |
of: find.text(user.fullName), | |
matching: find.byType(InkWell)); | |
await tester.tap(userTile); | |
await tester.pump(); | |
check(find.byIcon(Icons.check_circle)).findsOne(); | |
check(find.byIcon(Icons.circle_outlined)).findsNothing(); | |
nextButton = tester.widget<GestureDetector>(findNextButton()); | |
check(nextButton.onTap).isNotNull(); | |
}); | |
testWidgets('selecting a user', (tester) async { | |
final user = eg.user(fullName: 'Test User'); | |
final userTileFinder = find.ancestor( | |
of: find.text(user.fullName), | |
matching: find.byType(InkWell)); | |
await setupSheet(tester, users: [user]); | |
final nextButton = tester.widget<GestureDetector>( | |
find.widgetWithText(GestureDetector, 'Next')); | |
check(find.byIcon(Icons.circle_outlined)).findsOne(); | |
check(find.byIcon(Icons.check_circle)).findsNothing(); | |
check(nextButton.onTap).isNull(); | |
await tester.tap(userTileFinder); | |
await tester.pump(); | |
check(find.byIcon(Icons.circle_outlined)).findsNothing(); | |
check(find.byIcon(Icons.check_circle)).findsOne(); | |
check(nextButton.onTap).isNotNull(); | |
}); |
By breaking the checks into two stanzas, we can make this a bit more readable. (We don't need to call tester.widget()
twice to see updates to nextButton.onTap
.)
|
||
testWidgets('opens new DM sheet on FAB tap', (tester) async { | ||
await setupPage(tester, users: [], dmMessages: []); | ||
final fab = find.ancestor( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Let's avoid abbreviations like this (here and in the test name) when possible, unless it comes from an outside source like the Figma design. In this case, we are just referring to the new DM button widget.
Pull Request
Description
This pull request adds the UI to starting new DM conversations.. A floating action button has been added to the
RecentDmConversationsPage
, which opens a bottom modal sheet, allowing users to select conversation participants and proceed to theMessageListPage
.Design reference: Figma
Related Issues
Screenshots
Light mode
Dark mode
Additional Notes
The user list is currently sorted based on the recency of direct messages.