app_bar [nfc]: Centralize _getEffectiveCenterTitle in our wrapper#1141
Conversation
| super.key, | ||
| super.titleSpacing, | ||
| required super.title, | ||
| required Widget? Function(bool Function(ThemeData) willCenterTitle) buildTitle, |
There was a problem hiding this comment.
It took me a while to parse how this works. Maybe we can try to implement this as a widget? A concept would be like:
class _TitleBuilder extends StatelessWidget {
const _TitleBuilder({
required this.centerTitle,
required this.actionsCount,
required this.buildTitle,
});
final bool? centerTitle;
final int actionsCount;
final Widget Function(bool centerTitle) buildTitle;
// Copied from [AppBar._getEffectiveCenterTitle].
bool willCenterTitle(BuildContext context) {
final theme = Theme.of(context);
bool platformCenter() {
switch (theme.platform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return false;
case TargetPlatform.iOS:
case TargetPlatform.macOS:
return actionsCount == 0 || actionsCount < 2;
}
}
return centerTitle
?? theme.appBarTheme.centerTitle
?? platformCenter();
}
@override
Widget build(BuildContext context) {
return buildTitle(willCenterTitle(context));
}
}Letting buildTitle take a bool simplifies the API a bit and with a widget we can replace the callback constructed below with a method.
There was a problem hiding this comment.
Oh nice, this is great! Thanks for the idea.
|
Thanks for working on this! Left a comment on the proposed |
8633634 to
c097bec
Compare
|
Thanks for the review! Revision pushed. I arranged it so it includes an exact copy of |
|
Note to self; I think there's a comment in the message-list code I've forgotten to remove in this revision: // The helper [_getEffectiveCenterTitle] relies on the fact that we
// have at most one action here. |
gnprice
left a comment
There was a problem hiding this comment.
Thanks to you both!
Some small comments below, and then a further adjustment to the API.
| const TestZulipApp({ | ||
| super.key, | ||
| this.accountId, | ||
| this.skipAssertAccountExists, |
There was a problem hiding this comment.
| this.skipAssertAccountExists, | |
| this.skipAssertAccountExists = false, |
(and make the field non-nullable)
This is meant to treat null and false the same, right? So may as well make that more explicit by defaulting straight to false.
| if (accountId != null && skipAssertAccountExists != true) { | ||
| final account = GlobalStoreWidget.of(context).getAccount(accountId!); | ||
| assert(account != null, | ||
| 'TestZulipApp() was called with [accountId] but a corresponding ' |
There was a problem hiding this comment.
nit: put these conditionals inside the assert (with an IIFE)
| 'TestZulipApp() was called with [accountId] but a corresponding ' | ||
| 'Account was not found in the GlobalStore. ' | ||
| 'If [child] needs per-account data, consider calling ' | ||
| '`testBinding.globalStore.add` before pumping `TestZulipApp`. ' | ||
| 'If [child] is not specific to an account, omit [accountId]. ' | ||
| 'If you are testing behavior when an account is logged out, ' | ||
| 'consider building ZulipApp instead of TestZulipApp ' | ||
| 'or passing skipAssertAccountExists.'); |
There was a problem hiding this comment.
nit: for a long dev-facing error message like this, use FlutterError.fromParts — grep our code for a few examples
| /// based on [centerTitle], the theme, the platform, and [actions]. | ||
| /// Useful if [title] is a container whose children should align the same way, | ||
| /// such as a [Column] with multiple lines of text. | ||
| // TODO send a PR upstream to replace our `willCenterTitle` code |
There was a problem hiding this comment.
nit:
| // TODO send a PR upstream to replace our `willCenterTitle` code | |
| // TODO(upstream) send a PR to replace our `willCenterTitle` code |
I think the TODO(upstream) form is helpful for grepping.
|
|
||
| return Scaffold( | ||
| appBar: ZulipAppBar(title: Text(user.fullName)), | ||
| appBar: ZulipAppBar(buildTitle: (_) => Text(user.fullName)), |
There was a problem hiding this comment.
These diff hunks are kind of unfortunate, and I think an upstream PR for this would need to avoid them. Instead the new more-complex way of building a title should be opt-in.
| super.key, | ||
| super.titleSpacing, | ||
| required super.title, | ||
| required Widget Function(bool willCenterTitle) buildTitle, |
There was a problem hiding this comment.
| required Widget Function(bool willCenterTitle) buildTitle, | |
| Widget? title, | |
| Widget Function(bool willCenterTitle)? buildTitle, |
Then assert exactly one of those is non-null; and use that one. Much like hint and hintText etc. on upstream's InputDecoration.
c097bec to
6e734b8
Compare
|
Thanks for the review! Revision pushed, atop #1290 for the sake of CI. |
gnprice
left a comment
There was a problem hiding this comment.
Thanks! This looks good now; and I've also now read the tests, and I appreciate the thorough set of test cases. One small comment in the tests.
| final titlePosition = tester.getCenter(find.text('a')).dx; | ||
| final isCentered = titlePosition > ((1 / 3) * titleAreaRightEdgeOffset); | ||
| check(titlePosition).isLessThan((2 / 3) * titleAreaRightEdgeOffset); |
There was a problem hiding this comment.
How about:
| final titlePosition = tester.getCenter(find.text('a')).dx; | |
| final isCentered = titlePosition > ((1 / 3) * titleAreaRightEdgeOffset); | |
| check(titlePosition).isLessThan((2 / 3) * titleAreaRightEdgeOffset); | |
| final titlePosition = tester.getTopLeft(find.text('a')).dx; | |
| final isCentered = titlePosition > ((1 / 3) * titleAreaRightEdgeOffset); | |
| check(titlePosition).isLessThan((2 / 3) * titleAreaRightEdgeOffset); |
Otherwise this test implicitly relies on the title having its size shrink to its contents, rather than filling its parent's size.
This is nice and explicit compared to the behavior I got when I simply forgot to call `testBinding.globalStore.add` when working on some upcoming tests.
This logic is sort of complicated, and duplicated from upstream. Better to put it centrally in our ZulipAppBar wrapper, instead of in message_list.dart. As a bonus, we also have it handle `actions` instead of assuming there are none.
6e734b8 to
79ada37
Compare
|
Thanks for the review! Revision pushed. |
|
Thanks! Looks good; merging. |
And make
TestZulipAppmore helpful, too, since I noticed an opportunity for that.