apply text scale factor

This commit is contained in:
Thibault Deckers 2020-05-24 10:21:43 +09:00
parent 033dd84282
commit 810f32d542
10 changed files with 85 additions and 68 deletions

View file

@ -27,8 +27,8 @@ class OutlinedText extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( return Stack(
children: [ children: [
RichText( Text.rich(
text: TextSpan( TextSpan(
children: [ children: [
if (leadingBuilder != null) if (leadingBuilder != null)
WidgetSpan( WidgetSpan(
@ -52,8 +52,8 @@ class OutlinedText extends StatelessWidget {
], ],
), ),
), ),
RichText( Text.rich(
text: TextSpan( TextSpan(
children: [ children: [
if (leadingBuilder != null) if (leadingBuilder != null)
WidgetSpan( WidgetSpan(

View file

@ -34,7 +34,7 @@ class AndroidFileUtils {
StorageVolume getStorageVolume(String path) => storageVolumes.firstWhere((v) => path.startsWith(v.path), orElse: () => null); StorageVolume getStorageVolume(String path) => storageVolumes.firstWhere((v) => path.startsWith(v.path), orElse: () => null);
bool isOnRemovableStorage(String path) => getStorageVolume(path).isRemovable; bool isOnRemovableStorage(String path) => getStorageVolume(path)?.isRemovable ?? false;
AlbumType getAlbumType(String albumDirectory) { AlbumType getAlbumType(String albumDirectory) {
if (albumDirectory != null) { if (albumDirectory != null) {

View file

@ -17,8 +17,8 @@ class AboutPage extends StatelessWidget {
delegate: SliverChildListDelegate( delegate: SliverChildListDelegate(
[ [
Center( Center(
child: RichText( child: Text.rich(
text: TextSpan( TextSpan(
children: [ children: [
const TextSpan(text: 'Made with ❤️ and '), const TextSpan(text: 'Made with ❤️ and '),
WidgetSpan( WidgetSpan(

View file

@ -59,11 +59,12 @@ class _LicensesState extends State<Licenses> {
padding: const EdgeInsetsDirectional.only(start: 8), padding: const EdgeInsetsDirectional.only(start: 8),
child: Row( child: Row(
children: [ children: [
Text( Expanded(
'Open-source licenses', child: Text(
style: Theme.of(context).textTheme.headline6.copyWith(fontFamily: 'Concourse Caps'), 'Open-Source Licenses',
style: Theme.of(context).textTheme.headline6.copyWith(fontFamily: 'Concourse Caps'),
),
), ),
const Spacer(),
PopupMenuButton<LicenseSort>( PopupMenuButton<LicenseSort>(
itemBuilder: (context) => [ itemBuilder: (context) => [
PopupMenuItem( PopupMenuItem(

View file

@ -67,8 +67,9 @@ class SectionHeader extends StatelessWidget {
} }
// TODO TLAD cache header extent computation? // TODO TLAD cache header extent computation?
static double computeHeaderHeight(CollectionSource source, dynamic sectionKey, double scrollableWidth) { static double computeHeaderHeight(BuildContext context, CollectionSource source, dynamic sectionKey, double scrollableWidth) {
var headerExtent = 0.0; var headerExtent = 0.0;
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
if (sectionKey is String) { if (sectionKey is String) {
// only compute height for album headers, as they're the only likely ones to split on multiple lines // only compute height for album headers, as they're the only likely ones to split on multiple lines
final hasLeading = androidFileUtils.getAlbumType(sectionKey) != AlbumType.regular; final hasLeading = androidFileUtils.getAlbumType(sectionKey) != AlbumType.regular;
@ -83,7 +84,7 @@ class SectionHeader extends StatelessWidget {
TextSpan( TextSpan(
text: '\u200A' * (hasLeading ? 23 : 1), text: '\u200A' * (hasLeading ? 23 : 1),
// force a higher first line to match leading icon/selector dimension // force a higher first line to match leading icon/selector dimension
style: const TextStyle(height: 2.3), style: TextStyle(height: 2.3 * textScaleFactor),
), // 23 hair spaces match a width of 40.0 ), // 23 hair spaces match a width of 40.0
if (hasTrailing) if (hasTrailing)
TextSpan(text: '\u200A' * 17), TextSpan(text: '\u200A' * 17),
@ -94,10 +95,11 @@ class SectionHeader extends StatelessWidget {
], ],
), ),
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
textScaleFactor: textScaleFactor,
)..layout(BoxConstraints(maxWidth: maxWidth), parentUsesSize: true); )..layout(BoxConstraints(maxWidth: maxWidth), parentUsesSize: true);
headerExtent = para.getMaxIntrinsicHeight(maxWidth); headerExtent = para.getMaxIntrinsicHeight(maxWidth);
} }
headerExtent = max(headerExtent, TitleSectionHeader.leadingDimension) + TitleSectionHeader.padding.vertical; headerExtent = max(headerExtent, TitleSectionHeader.leadingDimension * textScaleFactor) + TitleSectionHeader.padding.vertical;
return headerExtent; return headerExtent;
} }
} }
@ -127,8 +129,8 @@ class TitleSectionHeader extends StatelessWidget {
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
padding: padding, padding: padding,
constraints: const BoxConstraints(minHeight: leadingDimension), constraints: const BoxConstraints(minHeight: leadingDimension),
child: RichText( child: Text.rich(
text: TextSpan( TextSpan(
children: [ children: [
WidgetSpan( WidgetSpan(
alignment: widgetSpanAlignment, alignment: widgetSpanAlignment,

View file

@ -44,7 +44,7 @@ class SectionedListLayoutProvider extends StatelessWidget {
final sectionEntryCount = sections[sectionKey].length; final sectionEntryCount = sections[sectionKey].length;
final sectionChildCount = 1 + (sectionEntryCount / columnCount).ceil(); final sectionChildCount = 1 + (sectionEntryCount / columnCount).ceil();
final headerExtent = showHeaders ? SectionHeader.computeHeaderHeight(source, sectionKey, scrollableWidth) : 0.0; final headerExtent = showHeaders ? SectionHeader.computeHeaderHeight(context, source, sectionKey, scrollableWidth) : 0.0;
final sectionFirstIndex = currentIndex; final sectionFirstIndex = currentIndex;
currentIndex += sectionChildCount; currentIndex += sectionChildCount;

View file

@ -50,18 +50,22 @@ class _AppDrawerState extends State<AppDrawer> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Align(
children: [ alignment: AlignmentDirectional.centerStart,
const AvesLogo(size: 64), child: Wrap(
const SizedBox(width: 16), spacing: 16,
const Text( crossAxisAlignment: WrapCrossAlignment.center,
'Aves', children: [
style: TextStyle( const AvesLogo(size: 64),
fontSize: 44, const Text(
fontFamily: 'Concourse Caps', 'Aves',
style: TextStyle(
fontSize: 44,
fontFamily: 'Concourse Caps',
),
), ),
), ],
], ),
), ),
], ],
), ),

View file

@ -28,7 +28,7 @@ class MenuRow extends StatelessWidget {
Icon(icon), Icon(icon),
const SizedBox(width: 8), const SizedBox(width: 8),
], ],
Text(text), Expanded(child: Text(text)),
], ],
); );
} }

View file

@ -32,6 +32,9 @@ class FilterTable extends StatelessWidget {
return c != 0 ? c : compareAsciiUpperCase(kv1.key, kv2.key); return c != 0 ? c : compareAsciiUpperCase(kv1.key, kv2.key);
}); });
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
final lineHeight = 16 * textScaleFactor;
return Padding( return Padding(
padding: const EdgeInsetsDirectional.only(start: AvesFilterChip.buttonBorderWidth / 2 + 6, end: 8), padding: const EdgeInsetsDirectional.only(start: AvesFilterChip.buttonBorderWidth / 2 + 6, end: 8),
child: LayoutBuilder( child: LayoutBuilder(
@ -55,11 +58,11 @@ class FilterTable extends StatelessWidget {
if (showPercentIndicator) if (showPercentIndicator)
LinearPercentIndicator( LinearPercentIndicator(
percent: percent, percent: percent,
lineHeight: 16, lineHeight: lineHeight,
backgroundColor: Colors.white24, backgroundColor: Colors.white24,
progressColor: stringToColor(label), progressColor: stringToColor(label),
animation: true, animation: true,
padding: const EdgeInsets.symmetric(horizontal: 16), padding: EdgeInsets.symmetric(horizontal: lineHeight),
center: Text(NumberFormat.percentPattern().format(percent)), center: Text(NumberFormat.percentPattern().format(percent)),
), ),
Text( Text(

View file

@ -51,41 +51,46 @@ class StatsPage extends StatelessWidget {
if (collection.isEmpty) { if (collection.isEmpty) {
child = const EmptyContent(); child = const EmptyContent();
} else { } else {
final catalogued = entries.where((entry) => entry.isCatalogued);
final withGps = catalogued.where((entry) => entry.hasGps);
final withGpsPercent = withGps.length / collection.entryCount;
final byMimeTypes = groupBy(entries, (entry) => entry.mimeType).map<String, int>((k, v) => MapEntry(k, v.length)); final byMimeTypes = groupBy(entries, (entry) => entry.mimeType).map<String, int>((k, v) => MapEntry(k, v.length));
final imagesByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('image/'))); final imagesByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('image/')));
final videoByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('video/'))); final videoByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('video/')));
final mimeDonuts = Wrap(
alignment: WrapAlignment.center,
children: [
_buildMimeDonut(context, (sum) => Intl.plural(sum, one: 'image', other: 'images'), imagesByMimeTypes),
_buildMimeDonut(context, (sum) => Intl.plural(sum, one: 'video', other: 'videos'), videoByMimeTypes),
],
);
final catalogued = entries.where((entry) => entry.isCatalogued);
final withGps = catalogued.where((entry) => entry.hasGps);
final withGpsPercent = withGps.length / collection.entryCount;
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
final lineHeight = 16 * textScaleFactor;
final locationIndicator = Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
LinearPercentIndicator(
percent: withGpsPercent,
lineHeight: lineHeight,
backgroundColor: Colors.white24,
progressColor: Theme.of(context).accentColor,
animation: true,
leading: const Icon(AIcons.location),
// right padding to match leading, so that inside label is aligned with outside label below
padding: EdgeInsets.symmetric(horizontal: lineHeight) + const EdgeInsets.only(right: 24),
center: Text(NumberFormat.percentPattern().format(withGpsPercent)),
),
const SizedBox(height: 8),
Text('${withGps.length} ${Intl.plural(withGps.length, one: 'item', other: 'items')} with location'),
],
),
);
child = ListView( child = ListView(
children: [ children: [
Wrap( mimeDonuts,
alignment: WrapAlignment.center, locationIndicator,
children: [
_buildMimeDonut(context, (sum) => Intl.plural(sum, one: 'image', other: 'images'), imagesByMimeTypes),
_buildMimeDonut(context, (sum) => Intl.plural(sum, one: 'video', other: 'videos'), videoByMimeTypes),
],
),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
LinearPercentIndicator(
percent: withGpsPercent,
lineHeight: 16,
backgroundColor: Colors.white24,
progressColor: Theme.of(context).accentColor,
animation: true,
leading: const Icon(AIcons.location),
// right padding to match leading, so that inside label is aligned with outside label below
padding: const EdgeInsets.symmetric(horizontal: 16) + const EdgeInsets.only(right: 24),
center: Text(NumberFormat.percentPattern().format(withGpsPercent)),
),
const SizedBox(height: 8),
Text('${withGps.length} ${Intl.plural(withGps.length, one: 'item', other: 'items')} with location'),
],
),
),
..._buildTopFilters('Top Countries', entryCountPerCountry, (s) => LocationFilter(LocationLevel.country, s)), ..._buildTopFilters('Top Countries', entryCountPerCountry, (s) => LocationFilter(LocationLevel.country, s)),
..._buildTopFilters('Top Places', entryCountPerPlace, (s) => LocationFilter(LocationLevel.place, s)), ..._buildTopFilters('Top Places', entryCountPerPlace, (s) => LocationFilter(LocationLevel.place, s)),
..._buildTopFilters('Top Tags', entryCountPerTag, (s) => TagFilter(s)), ..._buildTopFilters('Top Tags', entryCountPerTag, (s) => TagFilter(s)),
@ -132,8 +137,10 @@ class StatsPage extends StatelessWidget {
]; ];
return LayoutBuilder(builder: (context, constraints) { return LayoutBuilder(builder: (context, constraints) {
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
final minWidth = mimeDonutMinWidth * textScaleFactor;
final availableWidth = constraints.maxWidth; final availableWidth = constraints.maxWidth;
final dim = max(mimeDonutMinWidth, availableWidth / (availableWidth > 4 * mimeDonutMinWidth ? 4 : 2)); final dim = max(minWidth, availableWidth / (availableWidth > 4 * minWidth ? 4 : (availableWidth > 2 * minWidth ? 2 : 1)));
final donut = Container( final donut = Container(
width: dim, width: dim,
@ -161,11 +168,8 @@ class StatsPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: seriesData children: seriesData
.map((kv) => RichText( .map((kv) => Text.rich(
overflow: TextOverflow.fade, TextSpan(
softWrap: false,
maxLines: 1,
text: TextSpan(
children: [ children: [
WidgetSpan( WidgetSpan(
alignment: PlaceholderAlignment.middle, alignment: PlaceholderAlignment.middle,
@ -178,6 +182,9 @@ class StatsPage extends StatelessWidget {
TextSpan(text: '${kv.value}', style: const TextStyle(color: Colors.white70)), TextSpan(text: '${kv.value}', style: const TextStyle(color: Colors.white70)),
], ],
), ),
overflow: TextOverflow.fade,
softWrap: false,
maxLines: 1,
)) ))
.toList(), .toList(),
), ),
@ -186,7 +193,7 @@ class StatsPage extends StatelessWidget {
donut, donut,
legend, legend,
]; ];
return availableWidth > mimeDonutMinWidth * 2 return availableWidth > minWidth * 2
? Row( ? Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: children, children: children,