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) {
return Stack(
children: [
RichText(
text: TextSpan(
Text.rich(
TextSpan(
children: [
if (leadingBuilder != null)
WidgetSpan(
@ -52,8 +52,8 @@ class OutlinedText extends StatelessWidget {
],
),
),
RichText(
text: TextSpan(
Text.rich(
TextSpan(
children: [
if (leadingBuilder != null)
WidgetSpan(

View file

@ -34,7 +34,7 @@ class AndroidFileUtils {
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) {
if (albumDirectory != null) {

View file

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

View file

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

View file

@ -67,8 +67,9 @@ class SectionHeader extends StatelessWidget {
}
// 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;
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
if (sectionKey is String) {
// 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;
@ -83,7 +84,7 @@ class SectionHeader extends StatelessWidget {
TextSpan(
text: '\u200A' * (hasLeading ? 23 : 1),
// 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
if (hasTrailing)
TextSpan(text: '\u200A' * 17),
@ -94,10 +95,11 @@ class SectionHeader extends StatelessWidget {
],
),
textDirection: TextDirection.ltr,
textScaleFactor: textScaleFactor,
)..layout(BoxConstraints(maxWidth: maxWidth), parentUsesSize: true);
headerExtent = para.getMaxIntrinsicHeight(maxWidth);
}
headerExtent = max(headerExtent, TitleSectionHeader.leadingDimension) + TitleSectionHeader.padding.vertical;
headerExtent = max(headerExtent, TitleSectionHeader.leadingDimension * textScaleFactor) + TitleSectionHeader.padding.vertical;
return headerExtent;
}
}
@ -127,8 +129,8 @@ class TitleSectionHeader extends StatelessWidget {
alignment: AlignmentDirectional.centerStart,
padding: padding,
constraints: const BoxConstraints(minHeight: leadingDimension),
child: RichText(
text: TextSpan(
child: Text.rich(
TextSpan(
children: [
WidgetSpan(
alignment: widgetSpanAlignment,

View file

@ -44,7 +44,7 @@ class SectionedListLayoutProvider extends StatelessWidget {
final sectionEntryCount = sections[sectionKey].length;
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;
currentIndex += sectionChildCount;

View file

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

View file

@ -28,7 +28,7 @@ class MenuRow extends StatelessWidget {
Icon(icon),
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);
});
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
final lineHeight = 16 * textScaleFactor;
return Padding(
padding: const EdgeInsetsDirectional.only(start: AvesFilterChip.buttonBorderWidth / 2 + 6, end: 8),
child: LayoutBuilder(
@ -55,11 +58,11 @@ class FilterTable extends StatelessWidget {
if (showPercentIndicator)
LinearPercentIndicator(
percent: percent,
lineHeight: 16,
lineHeight: lineHeight,
backgroundColor: Colors.white24,
progressColor: stringToColor(label),
animation: true,
padding: const EdgeInsets.symmetric(horizontal: 16),
padding: EdgeInsets.symmetric(horizontal: lineHeight),
center: Text(NumberFormat.percentPattern().format(percent)),
),
Text(

View file

@ -51,41 +51,46 @@ class StatsPage extends StatelessWidget {
if (collection.isEmpty) {
child = const EmptyContent();
} 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 imagesByMimeTypes = Map.fromEntries(byMimeTypes.entries.where((kv) => kv.key.startsWith('image/')));
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(
children: [
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),
],
),
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'),
],
),
),
mimeDonuts,
locationIndicator,
..._buildTopFilters('Top Countries', entryCountPerCountry, (s) => LocationFilter(LocationLevel.country, s)),
..._buildTopFilters('Top Places', entryCountPerPlace, (s) => LocationFilter(LocationLevel.place, s)),
..._buildTopFilters('Top Tags', entryCountPerTag, (s) => TagFilter(s)),
@ -132,8 +137,10 @@ class StatsPage extends StatelessWidget {
];
return LayoutBuilder(builder: (context, constraints) {
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
final minWidth = mimeDonutMinWidth * textScaleFactor;
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(
width: dim,
@ -161,11 +168,8 @@ class StatsPage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: seriesData
.map((kv) => RichText(
overflow: TextOverflow.fade,
softWrap: false,
maxLines: 1,
text: TextSpan(
.map((kv) => Text.rich(
TextSpan(
children: [
WidgetSpan(
alignment: PlaceholderAlignment.middle,
@ -178,6 +182,9 @@ class StatsPage extends StatelessWidget {
TextSpan(text: '${kv.value}', style: const TextStyle(color: Colors.white70)),
],
),
overflow: TextOverflow.fade,
softWrap: false,
maxLines: 1,
))
.toList(),
),
@ -186,7 +193,7 @@ class StatsPage extends StatelessWidget {
donut,
legend,
];
return availableWidth > mimeDonutMinWidth * 2
return availableWidth > minWidth * 2
? Row(
mainAxisSize: MainAxisSize.min,
children: children,