Add topic-list page
Stacked on top of #1491.
Some non-goals of this change are deferred to #1499. In this implementation, we fetch the topics but do not handle all events to receive live-updates. It's expected that when topics are resolved/unresolved or moved, or when new messages arrived, the changes to the topic-list will not be seen until the next fetch.
We also skip thinning down the app bar, since that will require work on app bars on message-list page too.
The PR is structured to encourage side-by-side comparison with similar existing code. Namely _TopicItem from lib/widgets/inbox.dart and MessageListAppBarTitle.
Fixes: #1158
screenshots (taken on my Android device, hence the left-aligned app bar!)
| light | dark | |
|---|---|---|
| regular | ||
| unsubscribed | ||
| unknown channel |
| small | regular | large |
|---|---|---|
| message-list | channel action sheet |
|---|---|
| non-muted topic | muted topic |
|---|---|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS:
Updated the PR to implement a intentional deviation from the Figma design on aligning items, discussed in #mobile-team > topic list item alignment @ π¬.
For the "small" and "large" screenshots, I think it would look much better if the checkmarks and indicators on the right scaled too.
Let's name the option "List of topics". I think that's a bit easier to parse, and it's what we're currently testing for the web app UI.
For the "small" and "large" screenshots, I think it would look much better if the checkmarks and indicators on the right scaled too.
Seems reasonable. I'd want to clamp the scale factor, for the reasons described at #1023.
(For existing examples of implementing that, search for clamp in the code.)
Thanks both! Updated the PR and the screenshots. I picked 1.5 to be the maxScaleFactor for icons, since that appears to be what we are using in most other places.
I clamped only the icons, though, not the topic name text. Because I think #1023 applies to scaling relatively unimportant information.
Works for me, thanks!
The app bar's back button and "Channel feed" button are broken; sometimes when I tap it doesn't respond and this gets logged:
======== Exception caught by foundation library ====================================================
The following assertion was thrown while dispatching notifications for WidgetStatesController:
setState() or markNeedsBuild() called when widget tree was locked.
This _IconButtonM3 widget cannot be marked as needing to build because the framework is locked.
The widget on which setState() or markNeedsBuild() was called was: _IconButtonM3
style: ButtonStyle#2426d(mouseCursor: WidgetStateMapper<MouseCursor?>({WidgetState.disabled: null, WidgetState.any: null}))
dependencies: [IconButtonTheme, IconTheme, InheritedCupertinoTheme, _InheritedTheme, _LocalizationsScope-[GlobalKey#2fb94]]
state: _ButtonStyleState#9e3dc
When the exception was thrown, this was the stack:
#0 Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:5273:9)
#1 Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:5283:6)
#2 State.setState (package:flutter/src/widgets/framework.dart:1219:15)
#3 _ButtonStyleState.handleStatesControllerChange (package:flutter/src/material/button_style_button.dart:326:5)
#4 ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:435:24)
#5 WidgetStatesController.update (package:flutter/src/widgets/widget_state.dart:1152:7)
#6 _InkResponseState.updateHighlight (package:flutter/src/material/ink_well.dart:995:26)
#7 _InkResponseState.handleTapCancel (package:flutter/src/material/ink_well.dart:1211:5)
#8 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:345:24)
#9 TapGestureRecognizer.handleTapCancel (package:flutter/src/gestures/tap.dart:800:11)
#10 BaseTapGestureRecognizer._checkCancel (package:flutter/src/gestures/tap.dart:388:5)
#11 BaseTapGestureRecognizer.resolve (package:flutter/src/gestures/tap.dart:336:7)
#12 OneSequenceGestureRecognizer.dispose (package:flutter/src/gestures/recognizer.dart:470:5)
#13 PrimaryPointerGestureRecognizer.dispose (package:flutter/src/gestures/recognizer.dart:765:11)
#14 RawGestureDetectorState.dispose (package:flutter/src/widgets/gesture_detector.dart:1529:18)
#15 StatefulElement.unmount (package:flutter/src/widgets/framework.dart:5922:11)
#16 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2075:13)
#17 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#18 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#19 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#20 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#21 SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:6994:14)
#22 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#23 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#24 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#25 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#26 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#27 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#28 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#29 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#30 SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:6994:14)
#31 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#32 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#33 SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:6994:14)
#34 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#35 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#36 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#37 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#38 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#39 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#40 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#41 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#42 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#43 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#44 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#45 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#46 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#47 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#48 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#49 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#50 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#51 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#52 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#53 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#54 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#55 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#56 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#57 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#58 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#59 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#60 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#61 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#62 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#63 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#64 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#65 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#66 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#67 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#68 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#69 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#70 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#71 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#72 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#73 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#74 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#75 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#76 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#77 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#78 ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:5763:14)
#79 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#80 _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:2073:7)
#81 SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:6994:14)
#82 _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:2071:13)
#83 ListIterable.forEach (dart:_internal/iterable.dart:49:13)
#84 _InactiveElements._unmountAll (package:flutter/src/widgets/framework.dart:2084:25)
#85 BuildOwner.lockState (package:flutter/src/widgets/framework.dart:2965:15)
#86 BuildOwner.finalizeTree (package:flutter/src/widgets/framework.dart:3288:7)
#87 WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:1266:19)
#88 RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:495:5)
#89 SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1438:15)
#90 SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1351:9)
#91 SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1204:5)
#92 _invoke (dart:ui/hooks.dart:331:13)
#93 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:444:5)
#94 _drawFrame (dart:ui/hooks.dart:303:31)
The WidgetStatesController sending notification was: WidgetStatesController#d045d({})
====================================================================================================
Looks like an upstream regression, https://github.com/flutter/flutter/issues/168445, that's being worked on.
Edit: Oh, I guess that issue's description says "app crash"βthe app isn't crashing for me, but I am seeing "setState() or markNeedsBuild() called when widget tree was locked." like in a stack trace posted there: https://github.com/flutter/flutter/issues/168445#issuecomment-2861654745
We should use less than 28px padding at the start of each topic row. From the issue description:
For this issue, differences from the design there:
- The design shows a "pinned topics" feature, which is a possible future Zulip feature but doesn't currently exist. Pending that feature, we'll leave out the "pinned" icon, and avoid using up horizontal space for it.
For the layout, if we use an empty box as a placeholder for an icon when it's absent, it should scale exactly the same way as the icon does. See the start of the topic text being misaligned in the "large" screenshot:
screenshot
I started a discussion about the chevron-down icon in the app bar: #mobile-team > topic list: chevron-down icon in app bar? @ π¬
Thanks for the review! I adjusted the padding at the start of each row to 6px (matching the start padding the "pinned" icon would have), and addressed the other issues. The screenshots have been updated.
Looks like an upstream regression, https://github.com/flutter/flutter/issues/168445, that's being worked on.
Thanks for looking into this! Yeah, for this one, we might need https://github.com/flutter/flutter/pull/168546 to land first.
The app bar's back button and "Channel feed" button are broken; sometimes when I tap it doesn't respond
AFAICT the symptoms of that issue don't reproduce in a release build; the buttons work fine the first time. (I spent a few minutes just now trying to reproduce, in order to evaluate whether the issue will affect the upcoming beta v0.0.29.)
In a debug build, I also see it on other screens even without this PR.
So definitely an annoyance in dev β plus might be a sign of further bugginess that's just harder to spot the symptoms of β and will be good to see fixed upstream. But I don't think it needs to interact with merging this PR.
When I rebase this onto current main, the second commit has some tools/check errors:
$ tools/check analyze l10n
Running analyze...
Analyzing zulip-flutter...
error β’ Missing concrete implementation of 'getter abstract class
ZulipLocalizations.topicsButtonLabel' β’
lib/generated/l10n/zulip_localizations_de.dart:8:7 β’
non_abstract_class_inherits_abstract_member
1 issue found. (ran in 2.8s)
Running l10n...
Error: there were updates to l10n:
M lib/generated/l10n/zulip_localizations_de.dart
FAILED: analyze l10n
To rerun the suites that failed, run:
$ tools/check analyze l10n
Could you rebase and fix those please?
Thanks! Just updated this. I found that it also happens to the last commit. All commits that contain changes adding new strings to translate are probably affected, since we just recently added de (#1522).
I like that the channel icon in the app bar is colorized! We should do that in the app bar of the message-list page, following Figma; I'll send a PR. -> #1524
Thanks! Updated the PR.
Thanks, looks good! Marking for Greg's review.