Repository navigation
Conversation
rajveermalviya
left a comment
There was a problem hiding this comment.
Thanks @sm-sayedi! Comments below, including one about the query implementation, haven't looked at the tests yet.
There was a problem hiding this comment.
nit: commit message
icons: Edit `search` icon to remove the additional space around it
The icon was edited in Inkspace and cleaned up using the following guide:
https://zulip.readthedocs.io/en/latest/subsystems/icons.html#preparing-icons-for-use-with-zulip
s/Inkspace/Inkscape
| @@ -161,6 +167,9 @@ class _TopicListState extends State<_TopicList> with PerAccountStoreAwareStateMi | |||
|
|
|||
| @override | |||
| Widget build(BuildContext context) { | |||
| final store = PerAccountStoreWidget.of(context); | |||
| final zulipLocalization = ZulipLocalizations.of(context); | |||
There was a problem hiding this comment.
nit: zulipLocalizations (not zulipLocalization).
Also remove another instance of final zulipLocalizations = ZulipLocalizations.of(context); below.
| @override | ||
| Widget build(BuildContext context) { | ||
| final designVariables = DesignVariables.of(context); | ||
| final zulipLocalization = ZulipLocalizations.of(context); |
There was a problem hiding this comment.
nit: zulipLocalizations (not zulipLocalization).
| final filteredTopics = _filterQuery == null | ||
| ? channelTopics | ||
| : channelTopics.where((e) => | ||
| (e.name.displayName ?? store.realmEmptyTopicDisplayName).toLowerCase() | ||
| .contains(_filterQuery!.toLowerCase())); |
There was a problem hiding this comment.
I think we want this search to behave in the same way the current topic autocomplete behaves. See #1384 (comment).
So we should probably use relevant bits from lib/model/autocomplete.dart for topic autocomplete, just like emoji picker search.
c385b09 to
3ce261b
Compare
|
Thanks @rajveermalviya for the review. Changes pushed, PTAL. |
rajveermalviya
left a comment
There was a problem hiding this comment.
Thanks for the revision @sm-sayedi! All LGTM, some small comments below.
After addressing them please mark it for the integration review.
| void _handleViewModelUpdate() { | ||
| setState(() { | ||
| _resultsToDisplay = List.unmodifiable(_viewModel!.results); | ||
| _resultsToDisplay = List.unmodifiable(_viewModel?.results ?? []); |
There was a problem hiding this comment.
nit: _handleViewModelUpdate will always have non-null _viewModel, right?
| _resultsToDisplay = List.unmodifiable(_viewModel?.results ?? []); | |
| _resultsToDisplay = List.unmodifiable(_viewModel!.results ?? []); |
|
|
||
| class _TopicListState extends State<_TopicList> with PerAccountStoreAwareStateMixin { | ||
| Topics? topicsModel; | ||
| final TextEditingController _controller = TextEditingController(); |
There was a problem hiding this comment.
nit: let's move this instantiation inside initState, and make this a late declaration.
| final TextEditingController _controller = TextEditingController(); | |
| late TextEditingController _controller; |
| _controller.dispose(); | ||
| _topicAutocompleteModel?.dispose(); | ||
| unreadsModel?.removeListener(_unreadsModelChanged); |
There was a problem hiding this comment.
The order of these should be reverse of their initialization.
3ce261b to
85d085a
Compare
|
Thanks Rajesh for the review. Changes pushed, marking for Chris's review. |
85d085a to
b85fd69
Compare
|
Looks like this has gathered some conflicts; would you rebase please? I hope to get to this soon. |
b85fd69 to
82e2889
Compare
|
(Conflicts resolved.) |
82e2889 to
3ebabb0
Compare
| child: Icon(size: 24, ZulipIcons.search)), | ||
| child: SizedBox.square(dimension: 24, | ||
| child: Icon(size: 20, ZulipIcons.search))), |
There was a problem hiding this comment.
icons: Edit `search` icon to remove the additional space around it
This change looks like it adds, or restores, some amount of additional space around the icon? What's the actual intent of this commit? If this change is part of that intent, why isn't the ZulipIcons.search in lib/widgets/home.dart also touched?
There was a problem hiding this comment.
Thanks for the catch. The search icon in other places should've been changed too; done in the new revision. The only exception is the search icon in the main-menu sheet. The Figma design seems to be inconsistent with the icon sizes of the menu buttons, ranging from 20px to 24px. We use 24px for all of them.
The actual intent of the commit is to have the search icon without any surrounding space, so we can exactly match the Figma design where the icon is used. Discovered the need for this while implementing the search field in the topic list page.
There was a problem hiding this comment.
The Figma design seems to be inconsistent with the icon sizes of the menu buttons, ranging from 20px to 24px.
Worth being precise about this, I think: I don't actually see an inconsistency here. I'm seeing consistent 24px squares for all the menu buttons, at the icons/24/foo level:
There's a nested node on all of them that's less than 24px, e.g. 22px with the "clock" icon:
where the square box wraps the icon's glyph tightly, leaving no surrounding whitespace. But the icon asset we actually export from the Figma—linked by the "go to main component" button—is the 24px version with the surrounding whitespace:
That looks to be the case with the "search" icon too:
There was a problem hiding this comment.
So I don't think there's a change to be made here, and we should just keep the asset unchanged, without adding padding in the widget tree to compensate anywhere. Am I missing something, though?
There was a problem hiding this comment.
The inconsistency that I was referring to was about the nested node inside the 24px square box. But now that you mentioned that we export the main icon component that has internal padding, we can drop the commit.
| } | ||
| return topic.displayName != raw | ||
| && AutocompleteQuery.lowercaseAndStripDiacritics(topic.displayName!).contains(_normalized); | ||
| return AutocompleteQuery.lowercaseAndStripDiacritics( |
There was a problem hiding this comment.
autocomplete: Include the result when the query matches the topic
Commit-message nit: let's mention "topic autocomplete" in the summary line. Maybe:
autocomplete: In topic autocomplete, include exact matches
| final int channelId; | ||
|
|
||
| Iterable<TopicName> _topics = []; | ||
| Iterable<GetChannelTopicsEntry> _topics = []; |
There was a problem hiding this comment.
autocomplete: Change TopicAutocompleteResult.topic type
Can this be marked NFC, or is there some behavior change I'm missing?
There was a problem hiding this comment.
Yep, that's NFC. Thanks for bringing it up.
| required this.channelId, | ||
| }); | ||
| }) { | ||
| store.topics.addListener(_fetch); |
There was a problem hiding this comment.
autocomplete: Search again when there is a change in topics
This code is more indirect than it needs to be: a topics change triggers _fetch, which doesn't actually fetch anything (the data is already present, right), and then that calls _startSearch. This would read more transparently if it were store.topics.addListener(_startSearch).
How about:
- In a prep commit, remove the mutable
_topicsfield and the.._fetch()inTopicAutocompleteView.init. Instead, putfinal topics = await store.topics.getChannelTopics(channelId)at the top ofcomputeResults. - Then this line in this commit can change to
store.topics.addListener(_startSearch).
?
There was a problem hiding this comment.
That's a nice improvement, thanks.
| ], | ||
| ), | ||
| ); |
There was a problem hiding this comment.
nit: coalesce closing parens
7602c9e to
f467175
Compare
|
Thanks for the review, Chris. Revision pushed. Also, dropped the commit |
0c2ae40 to
400fe2e
Compare
|
Thanks for those points. New changes pushed. Also addressed some feedback from a review Claude did. |
400fe2e to
b7c5085
Compare
Replace the way a search in progress learns it is stale: instead of `shouldStop` comparing the query to the one the search started with, and checking `hasListeners` to notice disposal, count searches with a generation number, as MessageListView does for fetches. A search captures the generation when it starts and stops when it finds the number has moved on; `_startSearch` bumps it, and so does `dispose`. The query comparison could not tell apart two searches for an equal query, which the widgets code does start: it sets `query` on each change to the text field, including changes that leave the query the same, like backspace on iOS transiently expanding the selection (see ComposeContentAutocomplete.autocompleteIntent). Then both searches ran to completion and notified twice. Worse, when a search re-runs over changed data with the query unchanged, as topic-link autocomplete (zulip#2137) and topic-list filtering (zulip#2326) will do, the stale search could finish last and clobber fresh results. A generation tells the two searches apart. Bumping the generation in `dispose` replaces the `hasListeners` check, and `_startSearch` checks it after `computeResults` returns, so an implementation that returns without ever awaiting `shouldStop` cannot notify a disposed view-model. Disposal also has to block a search from starting afterward: TopicAutocompleteView starts one when its topics fetch completes, which can be after disposal, and the `hasListeners` check used to stop that search at its first `shouldStop`.
Replace the way a search in progress learns it is stale: instead of `shouldStop` comparing the query to the one the search started with, and checking `hasListeners` to notice disposal, count searches with a generation number. (This is the same pattern as `MessageListView.generation`, which invalidates stale fetches.) A search captures the generation when it starts and stops when it finds the number has moved on; `_startSearch` bumps it, and so does `dispose`. The query comparison could not tell apart two searches for an equal query, which the widgets code does start: it sets `query` on each change to the text field, including changes that leave the query the same, like backspace on iOS transiently expanding the selection (see ComposeContentAutocomplete.autocompleteIntent). Then both searches ran to completion and notified twice. Worse, when a search re-runs over changed data with the query unchanged, as topic-link autocomplete (zulip#2137) and topic-list filtering (zulip#2326) will do, the stale search could finish last and clobber fresh results. A generation tells the two searches apart. Bumping the generation in `dispose` replaces the `hasListeners` check, and `_startSearch` checks it after `computeResults` returns, so an implementation that returns without ever awaiting `shouldStop` cannot notify a disposed view-model. Disposal also has to block a search from starting afterward: TopicAutocompleteView starts one when its topics fetch completes, which can be after disposal, and the `hasListeners` check used to stop that search at its first `shouldStop`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the way a search in progress learns it is stale: instead of `shouldStop` comparing the query to the one the search started with, count searches with a generation number. (This is the same pattern as `MessageListView.generation`, which invalidates stale fetches.) A search captures the generation when it starts and stops when it finds the number has moved on, and `_startSearch` checks it again after `computeResults` returns. The query comparison could not tell apart two searches for an equal query, which the widgets code does start: it sets `query` on each change to the text field, including changes that leave the query the same. In the emoji picker and the compose box's topic input, whose query is the whole text, that is any cursor move. Then both searches ran to completion and notified twice. Worse, when a search re-runs over changed data with the query unchanged, as topic-link autocomplete (zulip#2137) and topic-list filtering (zulip#2326) will do, the stale search could finish last and clobber fresh results. A generation tells the two searches apart. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the way a search in progress learns it is stale: instead of `shouldStop` comparing the query to the one the search started with, count searches with a generation number. (This is the same pattern as `MessageListView.generation`, which invalidates stale fetches.) A search captures the generation when it starts and stops when it finds the number has moved on, and `_startSearch` checks it again after `computeResults` returns. The query comparison could not tell apart two searches for an equal query, which the widgets code does start: it sets `query` on each change to the text field, including changes that leave the query the same. In the emoji picker and the compose box's topic input, whose query is the whole text, that is any cursor move. Then both searches ran to completion and notified twice. Worse, when a search re-runs over changed data with the query unchanged, as topic-link autocomplete (zulip#2137) and topic-list filtering (zulip#2326) will do, the stale search could finish last and clobber fresh results. A generation tells the two searches apart. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the way a search in progress learns it is stale: instead of `shouldStop` comparing the query to the one the search started with, count searches with a generation number. (This is the same pattern as `MessageListView.generation`, which invalidates stale fetches.) A search captures the generation when it starts and stops when it finds the number has moved on, and `_startSearch` checks it again after `computeResults` returns. The query comparison could not tell apart two searches for an equal query, which the widgets code does start: it sets `query` on each change to the text field, including changes that leave the query the same. In the emoji picker and the compose box's topic input, whose query is the whole text, that is any cursor move. Then both searches ran to completion and notified twice. Worse, when a search re-runs over changed data with the query unchanged, as topic-link autocomplete (zulip#2137) and topic-list filtering (zulip#2326) will do, the stale search could finish last and clobber fresh results. A generation tells the two searches apart. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Before this commit, `shouldStop` detected a stale search by checking whether the query had changed. That cannot tell apart two searches for an equal query, which the widgets code does start: it sets `query` on each change to the text field, including changes that leave the query the same. Both searches then ran to completion and notified twice, and once searches re-run over changed data (zulip#2137, zulip#2326), the stale one could finish last and clobber fresh results. So count searches with a generation number instead, like `MessageListView.generation`: capture it when a search starts, and stop when it has moved on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Before this commit, `shouldStop` detected a stale search by checking whether the query had changed. That cannot tell apart two searches for an equal query, which the widgets code does start: it sets `query` on each change to the text field, including changes that leave the query the same. (For example, any cursor movement in the compose box's topic input.) Both searches would then run to completion and notify twice; the fresh results win only because the newer search happens to be scheduled last. Soon, changes to the data being searched will also start a same-query search (zulip#2137, zulip#2326: e.g. on any change to `Topics`), and we don't want the results to depend on which of two searches over different data finishes first. So count searches with a generation number instead: capture it when a search starts, and stop when it has moved on. (This follows the pattern of `MessageListView.generation` for invalidating stale fetches.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Before this commit, `shouldStop` detected a stale search by checking whether the query had changed. That cannot tell apart two searches for an equal query, which the widget code does start: it sets `query` on each change to the text field, including changes that leave the query the same. (For example, any cursor movement in the compose box's topic input.) Both searches then run to completion and notify twice. That's harmless today beyond the wasted work, since both see the same data. But soon, changes to the data being searched will also start a same-query search (zulip#2137, zulip#2326: e.g. on any change to `Topics`), and then we don't want the results to depend on which of two searches over different data finishes first. So count searches with a generation number instead: capture it when a search starts, and stop when it has moved on. (This follows the pattern of `MessageListView.generation` for invalidating stale fetches.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Before this commit, `shouldStop` detected a stale search by checking whether the query had changed. That cannot tell apart two searches for an equal query, which the widget code does start: it sets `query` on each change to the text field, including changes that leave the query the same. (For example, any cursor movement in the compose box's topic input.) Both searches then run to completion and notify twice. That's harmless today beyond the wasted work, since both see the same data. But soon, changes to the data being searched will also start a same-query search (zulip#2137, zulip#2326: e.g. on any change to `Topics`), and then we don't want the results to depend on which of two searches over different data finishes first. So count searches with a generation number instead: capture it when a search starts, and stop when it has moved on. (This follows the pattern of `MessageListView.generation` for invalidating stale fetches.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
c8995b2 to
5903be4
Compare
|
(The CI failure seems to be unrelated to the PR; see discussion: #mobile-team > Multiple CI failures @ 💬.) |
5903be4 to
64592e6
Compare
Before this commit, `shouldStop` detected a stale search by checking whether the query had changed. That cannot tell apart two searches for an equal query, which the widget code does start: it sets `query` on each change to the text field, including changes that leave the query the same. (For example, any cursor movement in the compose box's topic input.) Both searches then run to completion and notify twice. That's harmless today beyond the wasted work, since both see the same data. But soon, changes to the data being searched will also start a same-query search (zulip#2137, zulip#2326: e.g. on any change to `Topics`), and then we don't want the results to depend on which of two searches over different data finishes first. So count searches with a generation number instead: capture it when a search starts, and stop when it has moved on. (This follows the pattern of `MessageListView.generation` for invalidating stale fetches.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
64592e6 to
129fbbe
Compare
Previously, in topic autocomplete, the query "t" would not include a topic named "t" (an exact match). I think this is not desired and it's not what the web does. This also matches what Greg suggested in an earlier review of this part of the code: zulip#627 (comment)
Change it from TopicName to GetChannelTopicsEntry. This allows future commits to access maxId when the TopicAutocomplete machinery is used for topic filtering on the topic-list page.
Soon, we'll add more tests into this new group.
Move the topic fetch from TopicAutocompleteView.init to computeResults. This simplifies the implementation: computeResults just awaits Topics.getChannelTopics, which caches its result, instead of running a separate fetch and restarting the search when it completes. This also gives us a place to handle a failed fetch, which was a TODO before. On failure, computeResults returns null, so the results are left unchanged and listeners aren't notified; the user can then re-trigger the fetch by changing the query. The main motivation, though, is to prepare for the next commit, which will register _startSearch as a listener on the Topics model so that the results update when the topics change.
Previously the results only reflected the topics as of when the autocomplete was opened. Now they update as topics change, for example when a message arrives in a new topic, or a topic is moved. This will be needed in upcoming commits, where we reuse the TopicAutocomplete machinery for filtering on the topic-list page.
129fbbe to
07e740c
Compare
Fixes #1384.
Figma design: https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=6821-37083&t=IUNKC8IkYmIvIb5d-0
Screenshots
Screen recording
Screen.Recording.2026-05-24.at.5.08.12.PM.mov