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 license page. #57588
New license page. #57588
Conversation
@Hixie The new UI require changes to the tests in about_test.dart. The changes involve adding navigation of packages to show the license content, something the tests current assume is unneeded. Anyway, is it okay to update these tests without writing a design doc? |
Yeah that should be fine. Thanks for checking. |
Okay, so I am looking for feedback now. I think these are the right people to tag. @HansMuller, @timsneath, @Hixie. |
MaterialLocalizations.of(context); | ||
final double gutterSize = _getGutterSize(context); | ||
|
||
if (widget.scrollController == null) { |
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.
there's a lot of duplicated content here, might be worth seeing if there's a way to factor it out so the branches are only the pieces that differ
typedef _ActionBuilder = List<Widget> Function( | ||
BuildContext context, | ||
_ActionLevel actionLevel, | ||
); |
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.
these can all just be on one line
); | ||
} | ||
|
||
Widget _buildPackagesView(final BuildContext _, final bool isLateral) { |
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.
here and in other places where you have build methods, might be better to factor those out into their own widgets
Overall this looks great. I'll defer to @HansMuller or @goderbauer for more detailed review. |
Okay. I'll follow your guidance once I get their feedback. I don't want to be making changes over the top of what they may be reviewing. |
expand: false, | ||
builder: (BuildContext context, ScrollController controller) { | ||
return MouseRegion( | ||
// Workaround bug where things behind sheet still get mouse hover events. |
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.
Can you turn this into a TODO saying to remove the workaround, and reference the issue that describes the bug (and if there isn't one, please file one)?
packages/flutter_localizations/lib/src/material_localizations.dart
Outdated
Show resolved
Hide resolved
@@ -72,6 +72,14 @@ | |||
"description": "The title for the Flutter licenses page." | |||
}, | |||
|
|||
"licensesPackageDetailTextZero": "No licenses", |
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.
Shouldn't we just skip (and not display) any packages that have no licenses at all? That way we don't need to do translation for the zero case. You could update the description to say that it isn't allowed to have a value of zero.
Sorry, I got caught up this week. I'll make the changes this weekend. |
@TonicArtos have you had a chance to take a look at the changes? |
@gspencergoog Sorry for the delay. It is a pretty messed up time. I've gone through the comments and implemented each request. They are referred on the specific commit addressing them. It all looks fine to me on my devices and with the local tests. However, I don't understand why customer_testing-windows failed on the CI system. The issue looks like an image pixel comparison check. Is there some way to get more detail on this so I can fix it? |
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.
Thanks for the update! It's looking pretty good, I just saw some small things to address.
); | ||
} | ||
|
||
Widget _packageLicensePage(BuildContext _, Object args, ScrollController sc) { |
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.
Use a more descriptive name for sc
. (e.g. scrollController
).
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.
Thanks.
), | ||
), | ||
], | ||
final MaterialLocalizations localisations = MaterialLocalizations.of(context); |
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.
localisations => localizations. We try to use American English spellings, for consistency.
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.
Ah, the z. It kills me. Yeah, okay, I should have stuck to the US spelling no matter how it does the head in.
@@ -623,3 +982,651 @@ Widget _defaultApplicationIcon(BuildContext context) { | |||
// TODO(ianh): Get this from the embedder somehow. | |||
return null; | |||
} | |||
|
|||
double _getGutterSize(BuildContext context) => MediaQuery.of(context).size.width >= 720 ? 24 : 12; |
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.
We try not to have "magic" numbers. It's best to have a constant for each of the numbers like this, so that they're self-documenting.
For instance:
static const int _narrowGutterWidthLimit = 720;
static const int _wideGutterSize = 24;
static const int _narrowGutterSize = 12;
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.
You're right. I remember thinking I would fix this when I wrote it. I clearly didn't.
|
||
Widget _packageLicensePage(BuildContext _, Object args, ScrollController sc) { | ||
assert(args is _DetailArguments); | ||
final _DetailArguments a = args as _DetailArguments; |
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.
Use a more descriptive name for a
. e.g. detailArguments
.
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#avoid-abbreviations
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.
Sorry. It looks like I was trying to avoid the stuttering caused by the cast. The style guide should have been followed.
style: Theme.of(context).textTheme.bodyText2, | ||
textAlign: TextAlign.center, | ||
), | ||
Container(height: 18.0), |
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.
Here, and the next Container
, please define this constant as a separate static const variable to make it self documenting.
Also, consider using a SizedBox
instead of a Container
: SizedBox
can be a const object, and so it's more performant.
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.
Thanks.
return LayoutBuilder( | ||
builder: (BuildContext context, BoxConstraints constraints) { | ||
final double availableWidth = constraints.maxWidth; | ||
if (availableWidth >= (widget.breakpoint ?? 840)) { |
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.
Make 840 into a constant with a name.
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.
Thanks.
@override | ||
String licensesPackageDetailText(int licenseCount) { | ||
switch (licenseCount) { | ||
case 0: |
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.
If you skip packages with zero licenses, this case should assert.
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.
Added assertion licenseCount >= 1, removed case 0.
String firstPackage; | ||
|
||
void addLicense(LicenseEntry entry) { | ||
for (final String package in entry.packages) { |
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.
Perhaps skip packages with zero licenses?
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.
I said below in #57588 (comment) that I would do this. However, going over the code again it really is already the case that each package always has one or more licenses. This is because we only know about a package should it be recorded as a package to which a given license applies.
Obviously the code must not be clear enough as is, so I'll go through this part and added comments to try and clarify it.
String get licensesPackageDetailTextOther => '\$licenseCount licenses'; | ||
|
||
@override | ||
String get licensesPackageDetailTextZero => 'No licenses'; |
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.
All of these "Zero" cases would be eliminated if you skip packages with zero licenses.
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.
The license page is an inversion of the data hierarchy. LicenseRegistry collects licenses and includes the packages from which those licenses came from as a property on each license. As such, there is a many-to-many relationship between licenses and packages with the constraint that each package has at least one license, and each license has at least one package.
So the zero case is not checked for, since by implementation, it should come up. However, it is undeniably more robust to add checking for, and the exclusion of, packages that have zero licenses. So I will do this.
However. I would like to highlight, should you include special dispensation for zero license packages, it may be arguable that it is better to highlight packages with zero licenses by using a special unlicensed, or commons, page. But, I also suggest that it is unneeded and zealous work to add this. We should never normally encounter packages with zero licenses. Unless, of course, there is a design change down the line.
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.
I just realised why this zero case keeps getting regenerated. It is coming from the default text generated for the different locales from when I ran gen_missing_localizations.dart as instructed to do when adding to material_localizations.
As for the |
So I did the rebase. See if this will work. I can redo it with a squash if you like. |
I am not entirely sure this is the right way to go about this.
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.
// the safe area is sufficiently respected. | ||
expect( | ||
tester.getTopLeft(find.text('Licenses')), | ||
const Offset(16.0 + safeareaPadding, 18 + safeareaPadding), |
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.
Be consistent about doubles: either use 16
, or 18.0
.
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.
Okay, I'll go through the change list and conform to 18.0
everywhere. On closer inspection it seems to be the convention.
Okay I addressed your last comment. I also went through the CL one last time and noticed the documentation still mentioned the localised zero case was required, so I fixed that too.
I see no reason to hold back, once these last two commits complete the CI process and the tree goes green again. Oh, if you want to talk about the MDFlow thing and seeing if that can go somewhere, I'd be happy to. I think with work it could become something quite good. Otherwise, or in addition, I'll move on and see about getting all this done again for the Cupertino side of things too. Over at #57638. |
Thanks Tonic! Great work, we really appreciate it! I'll commit this as soon as the tree is green and it passes the CI tests. |
@gspencergoog Do you want me to squash the PR or are you going to do that? |
No, no need to squash: it'll do that automatically when I merge. The tree's been extra red: we had an issue in our device lab. |
…ve zero licenses.
… that have zero licenses." This reverts commit f1b9fdf.
…ve zero licenses.
This amends #57588 by adding code that also handles the zero license case, and adds translation strings for that.
onTap: () { | ||
print('draggable'); | ||
}, |
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.
This print (and probably the entire gesture detector?) should probably be removed?
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.
Ooh, yes, good point. @TonicArtos can you take care of that?
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.
Yes. It is late here. I'll get caught up and get the fix sorted tomorrow.
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.
OK, no huge rush: most of us will be on holiday until Monday (it's Independence Day in the U.S.).
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.
I went through my journal. It turns out the GestureDetector is left from when I was looking at #59741. I forgot to remove it once I figured what was happening.
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.
PR #60730 Remove superfluous GestureDetector.
Sure, that would be great. |
if (widget.scrollController == null) { | ||
page = Scaffold( | ||
appBar: AppBar( | ||
title: _PackageLicensePageTitle(title, subtitle, theme.primaryTextTheme), |
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.
Why did you specify theme.primaryTextTheme
?
It may break color system when AppBarTheme
is used for example.
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.
Indeed, it broke my background color.
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.
I would have preferred for this to (private) widget to have been factored a little differently - to not have used primaryTextTheme.
If you can file an issue with a simple test case that demonstrates a regression, we'll try and fix it.
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.
If you can file an issue with a simple test case that demonstrates a regression, we'll try and fix it.
Okay, I've created an issue: #63239.
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.
I tried to fix at #63249, but I’m welcome to be closed and refactored by your pull request.
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.
I would have preferred for this to (private) widget to have been factored a little differently - to not have used primaryTextTheme.
If you can file an issue with a simple test case that demonstrates a regression, we'll try and fix it.
Sorry about the issue. What do you think the original solution should have been?
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.
What do you think the original solution should have been?
I think #63249 is the simplest fix for existing implementation.
This is a PR addressing flutter#57226 - Proposal: New UI for Licenses Page. This PR replaces the previous single panel license page with one that uses a master/detail flow (MDFlow) to display packages and their respective licenses. The License Page API remains unchanged. The logic for processing the license data is kept largely the same. This PR changes how the licenses are displayed, by introducing a responsive UI using the master/detail UI pattern. For now I am calling it Master Detail Flow, or MDFlow. MDFlow manifests as two layouts depending on the screen size. On small and medium displays, as determined by the breakpoints given by the Material Design Spec, MDFlow utilises a nested layout. On large displays, MDFlow uses a two panel (lateral) layout. MDFlow is implemented in this PR using a Navigator for the nested layout, and a Stack for the lateral layout. The master and detail views are built using builders. For the interactive component, detail pages are requested from the master view using a proxy obtained by a widget lookup on the build context; MasterDetailFlow.of(context).
This amends flutter#57588 by adding code that also handles the zero license case, and adds translation strings for that.
Description
This is a PR addressing #57226 - Proposal: New UI for Licenses Page.
This PR replaces the previous single panel license page with one that uses a master/detail flow (MDFlow) to display packages and their respective licenses.
Existing behaviour
The existing license page is a single list of all licenses collected from the packages linked in the application. Also, at the top of the list is an entry which displays some information about the application.
Issues with existing behaviour
Because all versions of a license must be displayed, some packages have many tens of similar entries in this list, often with little more than a date change. Should a user wish to see licenses for a particular package, they must hunt through the list to find the package and its licenses.
The existing license page does not adjust for the size of the screen and can present extremely long strings of legalese running across the screen.
How this PR addresses the above (Changes)
The License Page API remains unchanged. The logic for processing the license data is kept largely the same. This PR changes how the licenses are displayed, by introducing a responsive UI using the master/detail UI pattern. For now I am calling it Master Detail Flow, or MDFlow.
MDFlow manifests as two layouts depending on the screen size. On small and medium displays, as determined by the breakpoints given by the Material Design Spec, MDFlow utilises a nested layout. On large displays, MDFlow uses a two panel (lateral) layout. MDFlow is implemented in this PR using a Navigator for the nested layout, and a Stack for the lateral layout. The master and detail views are built using builders. For the interactive component, detail pages are requested from the master view using a proxy obtained by a widget lookup on the build context;
MasterDetailFlow.of(context)
.Demo of new license page
New License Page showing packages (small and medium displays)
New License Page showing licenses for a package (small and medium displays)
New License Page (large displays)
Existing License Page (all displays)
Related Issues
Resolves #57226
Resolves #18281
Tests
Widget tests for the new UI remain to be updated.
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]
). This will ensure a smooth and quick review process.///
).flutter analyze --flutter-repo
) does not report any problems on my PR.Breaking Change
Did any tests fail when you ran them? Please read Handling breaking changes.