-
Notifications
You must be signed in to change notification settings - Fork 233
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
Animate lightbox bars #224
Conversation
Wraps the AppBar and the BottomAppBar used on the lightbox screen with AnimatedContainers. Now, when toggling their visibility, they should not removed from the widget tree completely, but instead their height be changed. This achieves a slide-in animation.
Preview: appBarAnimation.mp4 |
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.
Hi @onli, welcome and thanks for the PR and screen recording!
I've edited the PR description to add the Fixes:
line, so this PR is now linked with its associated issue.
Please also format the commit message according to our style. (Which reminds me, we should probably move parts of that style guide over to this newer repo soon. 🙂)
The substantive comment I have so far is that this needs to work on devices where the OS intrudes on the app's space at the top and bottom of the screen. To see what I mean, here's a recording on an iPhone 14 Pro simulator:
I particularly notice:
- The "in" position of the top bar has changed (it shouldn't change)
- The content of the top bar (text; close button) isn't showing (it should show)
(The video shows lots of frames being dropped in the animation, but I think we can ignore that; I think it doesn't represent what would happen in a real device in production.)
I don't yet know how best to handle this, which is why I left it as a followup to the initial lightbox implementation in #29. The other thought that comes to mind when using absolute heights (like 80
) is that the layout should look reasonable even when the user changes their preferred text size in their device's accessibility settings. But I haven't tested that with this revision yet.
lib/widgets/lightbox.dart
Outdated
final timestampText = DateFormat.yMMMd( | ||
/* TODO(i18n): Pass selected language here, I think? */) |
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: keep newline before .yMMMd(
instead of after
lib/widgets/lightbox.dart
Outdated
.format(DateTime.fromMillisecondsSinceEpoch( | ||
widget.message.timestamp * 1000)); |
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: keep on one line
lib/widgets/lightbox.dart
Outdated
final appBar = PreferredSize( | ||
preferredSize: Size(MediaQuery.of(context).size.width, kToolbarHeight), | ||
child: AnimatedContainer( | ||
duration: const Duration(milliseconds: 100), | ||
curve: Curves.easeIn, | ||
height: _headerFooterVisible ? kToolbarHeight : 0, | ||
child: AppBar( |
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: two-space indent here (and elsewhere), like surrounding code
lib/widgets/lightbox.dart
Outdated
style: themeData.textTheme.titleLarge! | ||
.copyWith(color: appBarForegroundColor)), |
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: This might fit well on one line after applying two-space indent instead of four. (Also on the "Make smaller, like a subtitle" style below.)
If you haven't already, you could try to to run the app in profile mode. There the performance should be closer to the performance on the real device, even in the simulator.
I see the problem. Guess I should have tested it in the emulator as well :) I'll check whether I can solve this.
VSCode/dart styles did not like this project code style and autoformatted the file differently, which I then tried to combat by copying just my changes from a simple editor, which did not work perfectly. Just to explain where the style divergence is coming from. Maybe this could be solved in general with some configuration of analysis_options.yaml (also possible that my setup was not optimal there, I usually write Flutter code on a different PC). I can fix those manually, if I can solve the main issue. |
Yeah, we should fix this — thanks for the reminder. I just filed #229 for that and will look into it. Sorry for the frustrating editing experience. |
@chrisbobbe If we wrap the Scaffold with a Safearea the AppBar gets painted below the OS toolbar - that might be acceptable I think, maybe even the best solution in general, but it would be different than before and probably best to be tackled as an app wide discussion. So I'd like to try something else first - using This also worked on Linux. But I would not be too surprised if it does not work on iOS ;) But maybe you could give this a try. |
One fun and helpful feature in Flutter is that you can always cause it to arrange the UI as if it were on some other platform that's not the one it's actually running on: debugDefaultTargetPlatformOverride = true; which you can put in the Then to simulate the notch that some iPhones have, you can use a Between those two tools, it should be possible to reproduce the issue and so to verify that that fix resolves it. We'll also want a unit test for this issue, and ideally for the animation itself. (This file doesn't currently have any tests because it was written when we were still in an experimental phase, but we're generally aiming for a higher standard of testing now that we're committed to building the app and also now that we've built out more test infrastructure to support it.) Those same tools should make it possible to write that test, too. (In the test you'll want TargetPlatformVariant and the |
For controlling this in a test, I believe you again want MediaQuery, setting the To see examples of that, use a search like |
When testing this PR with I am not aware of how I could use a MediaQuery to simulate a notch (I know it only to query things, not set things), but I can do so with Androids dev settings: I would still advice to test it on iOS proper if available. We ran into a bunch of details that were only detectable like that, some were even only noticeable when running on a device. SafeArea + AppBar handling is such an area where things felt off.
No problem :) Could you describe a bit more what exactly you would want tested? I would recommend against testing that the widget tree has the animation widget, that'd be an example for a useless test that will just produce work when this widget gets reworked. It would be nice to test for the pathological rendering shown in the initial review, but I'd need an idea how to test for that (also, hard to write that test when the current code does not produce that situation on my systems). Maybe just a test that the AppBar is not too small, given the unusual way that it is wrapped? That its title visible could also make for a sound one. |
Cool.
Yeah, this particular aspect is something I would expect not to vary by platform; I think the MediaQuery data will be the more relevant thing for exercising it.
Ah, OK. Well, how do you query it? You use the static method What those do: So to change the data that all the widgets in a whole subtree will get when they call MediaQuery static methods, you insert a MediaQuery widget above them. Probably most convenient is to put it just above the Specifically if you want to make everything behave as if a notch is present, the property you want to change is the main one that So that looks like, in return GlobalStoreWidget(
child: MediaQuery(
data: MediaQuery.of(context).copyWith(padding: EdgeInsets.only(top: 160)),
child: MaterialApp(
title: 'Zulip',
theme: theme,
home: const ChooseAccountPage()))); where 160 is an arbitrary value that just makes an unreasonably tall notch, to make its effects extra conspicuous. That won't cause an actual black notch-shaped area to appear, the way the one in Android's dev settings will do. But it will cause The other fun Flutter feature that is helpful for development here is timeDilation:
For testing the notch / safe-area issue, what I'd want is something like:
The key criterion for such a test would be that it fails on an implementation, like the one in the original version of the PR, where the issue was present 🙂. I often find it useful to check out an old version of the For testing the animation, I agree it wouldn't be helpful to check that the widget tree has the animation widget. One way to check that the animation is present would be: do the |
Add tests that check existence of an image widget and for the animations
I added a first round of tests. Note the visibility change of I can add a second round of test for the notch next and then change the minor formatting comments above. |
Hmm, interesting. This is probably a place where Flutter could provide a better error message. What I see when I try removing one of those
Ideally it would mention the variable by name in the error message, and you wouldn't have to look through the stack trace to work out what it means. When you do look at the stack, though, here's the inmost function: bool debugAssertAllPaintingVarsUnset(String reason, { bool debugDisableShadowsOverride = false }) {
assert(() {
if (debugDisableShadows != debugDisableShadowsOverride ||
debugNetworkImageHttpClientProvider != null ||
debugOnPaintImage != null ||
debugInvertOversizedImages ||
debugImageOverheadAllowance != _imageOverheadAllowanceDefault) {
throw FlutterError(reason);
// … So that at least gives you a list of potentially-relevant variables. I guess you were perhaps looking at the next caller up: assert(debugAssertAllGesturesVarsUnset(
'The value of a gestures debug variable was changed by the test.',
));
assert(debugAssertAllPaintingVarsUnset(
'The value of a painting debug variable was changed by the test.',
debugDisableShadowsOverride: disableShadows,
)); |
Sounds good. Do take a look also at the details of the animation, using |
Okay, two commits more:
Hm. That's a good catch. The animations of AppBar and BottomAppBar are not completely in sync, the AppBar behaves a bit strange (actually, the AppBar vanishing looks worse in the video than in the emulator, some frames seem to be skipped): animated_transitions_slow.mp4But I'd lean towards "That is good enough for now". Do you agree? PS:
Exactly :) I also saw the list of variables of course, but was convinced the shadow was the issue. Even debugged it, the variable was |
Yeah, the behavior of the top app bar is rather delayed (when on the way out), and then abrupt. The time dilation is very useful for pinning down what's happening; but it felt off to me even at normal speed, which is why I tried looking closer using time dilation. Another oddity which becomes visible when using a large time dilation (like 50) is that the text in the app bar shrinks from both above and below, and then the app bar as a whole slides up out of the way. When I spotted that, I was also simulating a very tall notch as described above at #224 (comment) . I think the issue where there's a delay before the app bar moves is actually fairly noticeable in the UX, so I'd want to fix that before merging something. The odd shrinking behavior might be tolerable on its own — I'm not sure whether it's noticeable in normal circumstances — but it seems like another symptom and may provide another clue. In the code, I'd recommend taking a look specifically at this bit: height: _headerFooterVisible ? AppBar.preferredHeightFor(context,
MediaQuery.of(context).size)
: 0, I don't think that's the sort of arguments that |
d3edfb5
to
c7c3b1f
Compare
You're right. This really used the complete height, which warped the animation. By using kToolbarHeight combined with animated_transitions_slow_synced.mp4Tests still pass. |
That video definitely looks much improved. But it still doesn't look quite right: when the top app bar is sliding out, the text should slide out with it, as if the app bar is a piece of physical material and the text is written on it. Instead the text gets cut off from both below and above:
In the new revision the code looks like this: height: _headerFooterVisible ? AppBar.preferredHeightFor(
context,
Size(0, MediaQuery.of(context).padding.top + kToolbarHeight)
) But here's the definition of static double preferredHeightFor(BuildContext context, Size preferredSize) {
if (preferredSize is _PreferredAppBarSize && preferredSize.toolbarHeight == null) {
return (AppBarTheme.of(context).toolbarHeight ?? kToolbarHeight) + (preferredSize.bottomHeight ?? 0);
}
return preferredSize.height;
} So it never makes sense to call that function with a value you've just created with the I realize that it's tricky to work out the logic to get this animation to work correctly. But I'm not going to want to merge a version that half-does it and produces behavior that feels glitchy. Until we have a version that gets it right, it's better to simply not have the animation. The Flutter upstream style guide has a nice section about this principle: |
Hi :)
I disagree. I did test this now again on Android and Linux. The animation looks fine, also when slowed down. Yes, the text does get cut not perfectly; This will not be noticed negatively (and can't be noticed at all on regular speed). The AnimatedContainer is a good solution for this animation, not a workaround, not unused code put there for completeness. We shouldn't overcomplicate this. Or do you have a better solution in mind?
You are completely right there. The function fits semantically, but not with the current code. Maybe this can be called in a more reasonable way, but probably the best solution for here would be to just not use it. I wouldn't mind to push that change, if we can go forward? |
@onli Are you interested in addressing the feedback above to finish this up into something we can merge? If not, that's fine; I'm just going through our old PRs to make sure we take care of them one way or another. |
Hi @gnprice, thanks for asking. I think I should let it go, our approaches for this specific animation do not seem to be compatible - and I have no alternative solution to suggest currently than the AnimatedContainer. |
Sounds good. Thanks for the reply, and for the PR. |
Wraps the AppBar and the BottomAppBar used on the lightbox screen with AnimatedContainers. Now, when toggling their visibility, they should not be removed from the widget tree completely, but instead their height be changed. This achieves a slide-in animation.
Note the handling of the AppBar being a PreferredSize widget, which forces the additional wrapper.
Fixes: #38
(chris: edited to add "Fixes:" line)