Skip to content
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

new_dm: Add UI for starting new DM conversations #1322

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

chimnayajith
Copy link

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 the MessageListPage.

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.

@gnprice gnprice added the maintainer review PR ready for review by Zulip maintainers label Feb 4, 2025
@chimnayajith chimnayajith force-pushed the 127-new-dm branch 4 times, most recently from b407738 to d8818a2 Compare February 6, 2025 18:21
@PIG208 PIG208 self-requested a review February 12, 2025 20:46
@PIG208
Copy link
Member

PIG208 commented Feb 12, 2025

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,

Screenshot from 2025-02-12 17-49-54

where the horizontal padding should be 14px;

image

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 AutocompleteView API, similar to what EmojiPicker does. However, We don't have a implementation for user auto-completion. While MentionAutocompleteView is close, it is also responsible for other tasks. Supporting that might require a good amount of refactoring and is less suited for a new contributor. So let's just try to focus on getting the UI right for this PR.

@chimnayajith chimnayajith force-pushed the 127-new-dm branch 3 times, most recently from 0bc3e12 to a6cc695 Compare February 16, 2025 06:23
@chimnayajith
Copy link
Author

@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.

Copy link
Member

@PIG208 PIG208 left a 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.

Comment on lines 70 to 71
width: 137,
height: 48,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of setting the size definitely, let's express this in terms of paddings and minWidth/minHeight.

image

Because the "new dm" string can get translated (thus ends up having different width, or the user has a non-default font size, the actual width and height can vary.

Comment on lines 76 to 77
icon: Icon(Icons.add, size: 24),
label: Text(zulipLocalizations.newDmFabButtonLabel, style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500)),
Copy link
Member

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.

Copy link
Author

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?

Copy link
Member

@PIG208 PIG208 Feb 25, 2025

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.

Copy link
Author

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.


void showNewDmSheet(BuildContext context) {
final store = PerAccountStoreWidget.of(context);
showModalBottomSheet<dynamic>(
Copy link
Member

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.

@override
Widget build(BuildContext context) {
return SizedBox(
height: MediaQuery.of(context).size.height * 0.95,
Copy link
Member

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.

}
}

class NewDmHeader extends StatelessWidget {
Copy link
Member

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.

hintStyle: TextStyle(
fontSize: 17,
height: 1.0,
color: designVariables.textInput.withFadedAlpha(0.5)),
Copy link
Member

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.

children: [
Avatar(userId: userId, size: 22, borderRadius: 3),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 3),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The end inset is not 5px:

image

Suggested change
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 3),
padding: EdgeInsetsDirectional.fromSTEB(5, 3, 4, 3),

We use EdgeInsetsDirectional because the start/end insets are different. This is for RTL where left and right are flipped.


final List<User> filteredUsers;
final Set<int> selectedUserIds;
final void Function(int) onUserSelected;
Copy link
Member

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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an 8px top padding that applies to the scrollable:

image

I assume that's something that allows you to scroll over. Perhaps ListView.padding will be helpful.

Comment on lines 303 to 306
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
Copy link
Member

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?

});
}

// Scroll to the search field when the user selects a user
Copy link
Member

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.

size: 24,
color: selectedUserIds.isEmpty
? designVariables.icon.withFadedAlpha(0.5)
: designVariables.icon)]));
Copy link
Member

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.

super.key,
required this.searchController,
required this.scrollController,
required this.selectedUserIds});
Copy link
Member

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.

@chimnayajith chimnayajith force-pushed the 127-new-dm branch 2 times, most recently from 6463fb7 to 9439a4a Compare February 24, 2025 15:58
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
maintainer review PR ready for review by Zulip maintainers
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support starting a new DM conversation
3 participants