apply text scale factor
This commit is contained in:
parent
033dd84282
commit
810f32d542
10 changed files with 85 additions and 68 deletions
|
@ -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(
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -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)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue