improved text scale factor handling

This commit is contained in:
Thibault Deckers 2021-02-23 11:23:56 +09:00
parent d383eeb565
commit 1a69749539
17 changed files with 98 additions and 75 deletions

View file

@ -55,7 +55,7 @@ class LocationFilter extends CollectionFilter {
final flag = countryCodeToFlag(_countryCode); final flag = countryCodeToFlag(_countryCode);
// as of Flutter v1.22.3, emoji shadows are rendered as colorful duplicates, // as of Flutter v1.22.3, emoji shadows are rendered as colorful duplicates,
// not filled with the shadow color as expected, so we remove them // not filled with the shadow color as expected, so we remove them
if (flag != null) return Text(flag, style: TextStyle(fontSize: size, shadows: [])); if (flag != null) return Text(flag, style: TextStyle(fontSize: size, shadows: []), textScaleFactor: 1.0,);
return Icon(_location.isEmpty ? AIcons.locationOff : AIcons.location, size: size); return Icon(_location.isEmpty ? AIcons.locationOff : AIcons.location, size: size);
} }

View file

@ -46,7 +46,7 @@ class _AppReferenceState extends State<AppReference> {
builder: (context, snapshot) { builder: (context, snapshot) {
return LinkChip( return LinkChip(
leading: AvesLogo( leading: AvesLogo(
size: style.fontSize * 1.25, size: style.fontSize * MediaQuery.textScaleFactorOf(context) * 1.25,
), ),
text: 'Aves ${snapshot.data?.version}', text: 'Aves ${snapshot.data?.version}',
url: 'https://github.com/deckerst/aves', url: 'https://github.com/deckerst/aves',

View file

@ -31,7 +31,7 @@ class AboutCredits extends StatelessWidget {
), ),
alignment: PlaceholderAlignment.middle, alignment: PlaceholderAlignment.middle,
), ),
TextSpan(text: ', under ISC License.'), TextSpan(text: 'under ISC License.'),
], ],
), ),
), ),

View file

@ -48,9 +48,7 @@ class _CollectionPageState extends State<CollectionPage> {
), ),
), ),
), ),
drawer: AppDrawer( drawer: AppDrawer(),
source: collection.source,
),
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
), ),
), ),

View file

@ -40,7 +40,14 @@ class LinkChip extends StatelessWidget {
leading, leading,
SizedBox(width: 8), SizedBox(width: 8),
], ],
Text(text), Flexible(
child: Text(
text,
softWrap: false,
overflow: TextOverflow.fade,
maxLines: 1,
),
),
SizedBox(width: 8), SizedBox(width: 8),
Builder( Builder(
builder: (context) => Icon( builder: (context) => Icon(

View file

@ -15,17 +15,19 @@ class MenuRow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
final iconSize = IconTheme.of(context).size * textScaleFactor;
return Row( return Row(
children: [ children: [
if (checked != null) ...[ if (checked != null) ...[
Opacity( Opacity(
opacity: checked ? 1 : 0, opacity: checked ? 1 : 0,
child: Icon(AIcons.checked), child: Icon(AIcons.checked, size: iconSize),
), ),
SizedBox(width: 8), SizedBox(width: 8),
], ],
if (icon != null) ...[ if (icon != null) ...[
Icon(icon), Icon(icon, size: iconSize),
SizedBox(width: 8), SizedBox(width: 8),
], ],
Expanded(child: Text(text)), Expanded(child: Text(text)),

View file

@ -26,7 +26,6 @@ class AvesFilterChip extends StatefulWidget {
static const double minChipHeight = kMinInteractiveDimension; static const double minChipHeight = kMinInteractiveDimension;
static const double minChipWidth = 80; static const double minChipWidth = 80;
static const double maxChipWidth = 160; static const double maxChipWidth = 160;
static const double iconSize = 20;
const AvesFilterChip({ const AvesFilterChip({
Key key, Key key,
@ -88,7 +87,8 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const iconSize = AvesFilterChip.iconSize; final textScaleFactor = MediaQuery.textScaleFactorOf(context);
final iconSize = 20 * textScaleFactor;
final hasBackground = widget.background != null; final hasBackground = widget.background != null;
final leading = filter.iconBuilder(context, iconSize, showGenericIcon: widget.showGenericIcon, embossed: hasBackground); final leading = filter.iconBuilder(context, iconSize, showGenericIcon: widget.showGenericIcon, embossed: hasBackground);

View file

@ -7,6 +7,7 @@ import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/utils/constants.dart';
import 'package:decorated_icon/decorated_icon.dart'; import 'package:decorated_icon/decorated_icon.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class VideoIcon extends StatelessWidget { class VideoIcon extends StatelessWidget {
final AvesEntry entry; final AvesEntry entry;
@ -172,10 +173,26 @@ class IconUtils {
static Widget getAlbumIcon({ static Widget getAlbumIcon({
@required BuildContext context, @required BuildContext context,
@required String album, @required String album,
double size = 24, double size,
bool embossed = false, bool embossed = false,
}) { }) {
Widget buildIcon(IconData icon) => embossed ? DecoratedIcon(icon, shadows: [Constants.embossShadow], size: size) : Icon(icon, size: size); size ??= IconTheme.of(context).size;
Widget buildIcon(IconData icon) => embossed
? MediaQuery(
// `DecoratedIcon` internally uses `Text`,
// which size depends on the ambient `textScaleFactor`
// but we already accommodate for it upstream
data: context.read<MediaQueryData>().copyWith(textScaleFactor: 1.0),
child: DecoratedIcon(
icon,
shadows: [Constants.embossShadow],
size: size,
),
)
: Icon(
icon,
size: size,
);
switch (androidFileUtils.getAlbumType(album)) { switch (androidFileUtils.getAlbumType(album)) {
case AlbumType.camera: case AlbumType.camera:
return buildIcon(AIcons.cameraAlbum); return buildIcon(AIcons.cameraAlbum);

View file

@ -14,20 +14,17 @@ import 'package:aves/widgets/debug/storage.dart';
import 'package:aves/widgets/viewer/info/common.dart'; import 'package:aves/widgets/viewer/info/common.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
class AppDebugPage extends StatefulWidget { class AppDebugPage extends StatefulWidget {
static const routeName = '/debug'; static const routeName = '/debug';
final CollectionSource source;
const AppDebugPage({this.source});
@override @override
State<StatefulWidget> createState() => _AppDebugPageState(); State<StatefulWidget> createState() => _AppDebugPageState();
} }
class _AppDebugPageState extends State<AppDebugPage> { class _AppDebugPageState extends State<AppDebugPage> {
CollectionSource get source => widget.source; CollectionSource get source => context.read<CollectionSource>();
Set<AvesEntry> get visibleEntries => source.visibleEntries; Set<AvesEntry> get visibleEntries => source.visibleEntries;

View file

@ -0,0 +1,35 @@
import 'package:aves/model/filters/album.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/widgets/common/identity/aves_icons.dart';
import 'package:aves/widgets/drawer/collection_tile.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class AlbumTile extends StatelessWidget {
final String album;
const AlbumTile(this.album);
@override
Widget build(BuildContext context) {
final source = context.read<CollectionSource>();
final uniqueName = source.getUniqueAlbumName(album);
return CollectionNavTile(
leading: IconUtils.getAlbumIcon(
context: context,
album: album,
),
title: uniqueName,
trailing: androidFileUtils.isOnRemovableStorage(album)
? Icon(
AIcons.removableStorage,
size: 16,
color: Colors.grey,
)
: null,
filter: AlbumFilter(album, uniqueName),
);
}
}

View file

@ -1,7 +1,6 @@
import 'dart:ui'; import 'dart:ui';
import 'package:aves/model/availability.dart'; import 'package:aves/model/availability.dart';
import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/favourite.dart'; import 'package:aves/model/filters/favourite.dart';
import 'package:aves/model/filters/mime.dart'; import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/source/album.dart'; import 'package:aves/model/source/album.dart';
@ -14,9 +13,9 @@ import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/widgets/about/about_page.dart'; import 'package:aves/widgets/about/about_page.dart';
import 'package:aves/widgets/about/news_badge.dart'; import 'package:aves/widgets/about/news_badge.dart';
import 'package:aves/widgets/common/extensions/media_query.dart'; import 'package:aves/widgets/common/extensions/media_query.dart';
import 'package:aves/widgets/common/identity/aves_icons.dart';
import 'package:aves/widgets/common/identity/aves_logo.dart'; import 'package:aves/widgets/common/identity/aves_logo.dart';
import 'package:aves/widgets/debug/app_debug_page.dart'; import 'package:aves/widgets/debug/app_debug_page.dart';
import 'package:aves/widgets/drawer/album_tile.dart';
import 'package:aves/widgets/drawer/collection_tile.dart'; import 'package:aves/widgets/drawer/collection_tile.dart';
import 'package:aves/widgets/drawer/tile.dart'; import 'package:aves/widgets/drawer/tile.dart';
import 'package:aves/widgets/filter_grids/albums_page.dart'; import 'package:aves/widgets/filter_grids/albums_page.dart';
@ -28,10 +27,6 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class AppDrawer extends StatefulWidget { class AppDrawer extends StatefulWidget {
final CollectionSource source;
const AppDrawer({@required this.source});
@override @override
_AppDrawerState createState() => _AppDrawerState(); _AppDrawerState createState() => _AppDrawerState();
} }
@ -39,7 +34,7 @@ class AppDrawer extends StatefulWidget {
class _AppDrawerState extends State<AppDrawer> { class _AppDrawerState extends State<AppDrawer> {
Future<bool> _newVersionLoader; Future<bool> _newVersionLoader;
CollectionSource get source => widget.source; CollectionSource get source => context.read<CollectionSource>();
@override @override
void initState() { void initState() {
@ -72,6 +67,7 @@ class _AppDrawerState extends State<AppDrawer> {
child: Selector<MediaQueryData, double>( child: Selector<MediaQueryData, double>(
selector: (c, mq) => mq.effectiveBottomPadding, selector: (c, mq) => mq.effectiveBottomPadding,
builder: (c, mqPaddingBottom, child) { builder: (c, mqPaddingBottom, child) {
final iconTheme = IconTheme.of(context);
return SingleChildScrollView( return SingleChildScrollView(
padding: EdgeInsets.only(bottom: mqPaddingBottom), padding: EdgeInsets.only(bottom: mqPaddingBottom),
child: Theme( child: Theme(
@ -79,10 +75,13 @@ class _AppDrawerState extends State<AppDrawer> {
// color used by `ExpansionTile` for leading icon // color used by `ExpansionTile` for leading icon
unselectedWidgetColor: Colors.white, unselectedWidgetColor: Colors.white,
), ),
child: IconTheme(
data: iconTheme.copyWith(size: iconTheme.size * MediaQuery.textScaleFactorOf(context)),
child: Column( child: Column(
children: drawerItems, children: drawerItems,
), ),
), ),
),
); );
}, },
), ),
@ -123,23 +122,6 @@ class _AppDrawerState extends State<AppDrawer> {
); );
} }
Widget _buildAlbumTile(String album) {
final uniqueName = source.getUniqueAlbumName(album);
return CollectionNavTile(
source: source,
leading: IconUtils.getAlbumIcon(context: context, album: album),
title: uniqueName,
trailing: androidFileUtils.isOnRemovableStorage(album)
? Icon(
AIcons.removableStorage,
size: 16,
color: Colors.grey,
)
: null,
filter: AlbumFilter(album, uniqueName),
);
}
Widget _buildSpecialAlbumSection() { Widget _buildSpecialAlbumSection() {
return StreamBuilder( return StreamBuilder(
stream: source.eventBus.on<AlbumsChangedEvent>(), stream: source.eventBus.on<AlbumsChangedEvent>(),
@ -154,7 +136,7 @@ class _AppDrawerState extends State<AppDrawer> {
return Column( return Column(
children: [ children: [
Divider(), Divider(),
...specialAlbums.map(_buildAlbumTile), ...specialAlbums.map((album) => AlbumTile(album)),
], ],
); );
}); });
@ -163,21 +145,18 @@ class _AppDrawerState extends State<AppDrawer> {
// tiles // tiles
Widget get allCollectionTile => CollectionNavTile( Widget get allCollectionTile => CollectionNavTile(
source: source,
leading: Icon(AIcons.allCollection), leading: Icon(AIcons.allCollection),
title: 'All collection', title: 'All collection',
filter: null, filter: null,
); );
Widget get videoTile => CollectionNavTile( Widget get videoTile => CollectionNavTile(
source: source,
leading: Icon(AIcons.video), leading: Icon(AIcons.video),
title: 'Videos', title: 'Videos',
filter: MimeFilter(MimeTypes.anyVideo), filter: MimeFilter(MimeTypes.anyVideo),
); );
Widget get favouriteTile => CollectionNavTile( Widget get favouriteTile => CollectionNavTile(
source: source,
leading: Icon(AIcons.favourite), leading: Icon(AIcons.favourite),
title: 'Favourites', title: 'Favourites',
filter: FavouriteFilter(), filter: FavouriteFilter(),
@ -191,7 +170,7 @@ class _AppDrawerState extends State<AppDrawer> {
builder: (context, _) => Text('${source.rawAlbums.length}'), builder: (context, _) => Text('${source.rawAlbums.length}'),
), ),
routeName: AlbumListPage.routeName, routeName: AlbumListPage.routeName,
pageBuilder: (_) => AlbumListPage(source: source), pageBuilder: (_) => AlbumListPage(),
); );
Widget get countryListTile => NavTile( Widget get countryListTile => NavTile(
@ -202,7 +181,7 @@ class _AppDrawerState extends State<AppDrawer> {
builder: (context, _) => Text('${source.sortedCountries.length}'), builder: (context, _) => Text('${source.sortedCountries.length}'),
), ),
routeName: CountryListPage.routeName, routeName: CountryListPage.routeName,
pageBuilder: (_) => CountryListPage(source: source), pageBuilder: (_) => CountryListPage(),
); );
Widget get tagListTile => NavTile( Widget get tagListTile => NavTile(
@ -213,7 +192,7 @@ class _AppDrawerState extends State<AppDrawer> {
builder: (context, _) => Text('${source.sortedTags.length}'), builder: (context, _) => Text('${source.sortedTags.length}'),
), ),
routeName: TagListPage.routeName, routeName: TagListPage.routeName,
pageBuilder: (_) => TagListPage(source: source), pageBuilder: (_) => TagListPage(),
); );
Widget get settingsTile => NavTile( Widget get settingsTile => NavTile(
@ -244,6 +223,6 @@ class _AppDrawerState extends State<AppDrawer> {
title: 'Debug', title: 'Debug',
topLevel: false, topLevel: false,
routeName: AppDebugPage.routeName, routeName: AppDebugPage.routeName,
pageBuilder: (_) => AppDebugPage(source: source), pageBuilder: (_) => AppDebugPage(),
); );
} }

View file

@ -4,9 +4,9 @@ import 'package:aves/model/source/collection_source.dart';
import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/collection/collection_page.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CollectionNavTile extends StatelessWidget { class CollectionNavTile extends StatelessWidget {
final CollectionSource source;
final Widget leading; final Widget leading;
final String title; final String title;
final Widget trailing; final Widget trailing;
@ -14,7 +14,6 @@ class CollectionNavTile extends StatelessWidget {
final CollectionFilter filter; final CollectionFilter filter;
const CollectionNavTile({ const CollectionNavTile({
@required this.source,
@required this.leading, @required this.leading,
@required this.title, @required this.title,
this.trailing, this.trailing,
@ -44,7 +43,7 @@ class CollectionNavTile extends StatelessWidget {
MaterialPageRoute( MaterialPageRoute(
settings: RouteSettings(name: CollectionPage.routeName), settings: RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage(CollectionLens( builder: (context) => CollectionPage(CollectionLens(
source: source, source: context.read<CollectionSource>(),
filters: [filter], filters: [filter],
)), )),
), ),

View file

@ -20,12 +20,9 @@ import 'package:tuple/tuple.dart';
class AlbumListPage extends StatelessWidget { class AlbumListPage extends StatelessWidget {
static const routeName = '/albums'; static const routeName = '/albums';
final CollectionSource source;
const AlbumListPage({@required this.source});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final source = context.read<CollectionSource>();
return Selector<Settings, Tuple3<AlbumChipGroupFactor, ChipSortFactor, Set<CollectionFilter>>>( return Selector<Settings, Tuple3<AlbumChipGroupFactor, ChipSortFactor, Set<CollectionFilter>>>(
selector: (context, s) => Tuple3(s.albumGroupFactor, s.albumSortFactor, s.pinnedFilters), selector: (context, s) => Tuple3(s.albumGroupFactor, s.albumSortFactor, s.pinnedFilters),
builder: (context, s, child) { builder: (context, s, child) {

View file

@ -180,9 +180,7 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
), ),
), ),
), ),
drawer: AppDrawer( drawer: AppDrawer(),
source: source,
),
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
), ),
); );

View file

@ -19,12 +19,9 @@ import 'package:tuple/tuple.dart';
class CountryListPage extends StatelessWidget { class CountryListPage extends StatelessWidget {
static const routeName = '/countries'; static const routeName = '/countries';
final CollectionSource source;
const CountryListPage({@required this.source});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final source = context.read<CollectionSource>();
return Selector<Settings, Tuple2<ChipSortFactor, Set<CollectionFilter>>>( return Selector<Settings, Tuple2<ChipSortFactor, Set<CollectionFilter>>>(
selector: (context, s) => Tuple2(s.countrySortFactor, s.pinnedFilters), selector: (context, s) => Tuple2(s.countrySortFactor, s.pinnedFilters),
builder: (context, s, child) { builder: (context, s, child) {
@ -39,7 +36,7 @@ class CountryListPage extends StatelessWidget {
settings.pinnedFilters.contains(filter) ? ChipAction.unpin : ChipAction.pin, settings.pinnedFilters.contains(filter) ? ChipAction.unpin : ChipAction.pin,
ChipAction.hide, ChipAction.hide,
], ],
filterSections: _getCountryEntries(), filterSections: _getCountryEntries(source),
emptyBuilder: () => EmptyContent( emptyBuilder: () => EmptyContent(
icon: AIcons.location, icon: AIcons.location,
text: 'No countries', text: 'No countries',
@ -50,7 +47,7 @@ class CountryListPage extends StatelessWidget {
); );
} }
Map<ChipSectionKey, List<FilterGridItem<LocationFilter>>> _getCountryEntries() { Map<ChipSectionKey, List<FilterGridItem<LocationFilter>>> _getCountryEntries(CollectionSource source) {
final filters = source.sortedCountries.map((location) => LocationFilter(LocationLevel.country, location)).toSet(); final filters = source.sortedCountries.map((location) => LocationFilter(LocationLevel.country, location)).toSet();
final sorted = FilterNavigationPage.sort(settings.countrySortFactor, source, filters); final sorted = FilterNavigationPage.sort(settings.countrySortFactor, source, filters);

View file

@ -19,12 +19,9 @@ import 'package:tuple/tuple.dart';
class TagListPage extends StatelessWidget { class TagListPage extends StatelessWidget {
static const routeName = '/tags'; static const routeName = '/tags';
final CollectionSource source;
const TagListPage({@required this.source});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final source = context.read<CollectionSource>();
return Selector<Settings, Tuple2<ChipSortFactor, Set<CollectionFilter>>>( return Selector<Settings, Tuple2<ChipSortFactor, Set<CollectionFilter>>>(
selector: (context, s) => Tuple2(s.tagSortFactor, s.pinnedFilters), selector: (context, s) => Tuple2(s.tagSortFactor, s.pinnedFilters),
builder: (context, s, child) { builder: (context, s, child) {
@ -39,7 +36,7 @@ class TagListPage extends StatelessWidget {
settings.pinnedFilters.contains(filter) ? ChipAction.unpin : ChipAction.pin, settings.pinnedFilters.contains(filter) ? ChipAction.unpin : ChipAction.pin,
ChipAction.hide, ChipAction.hide,
], ],
filterSections: _getTagEntries(), filterSections: _getTagEntries(source),
emptyBuilder: () => EmptyContent( emptyBuilder: () => EmptyContent(
icon: AIcons.tag, icon: AIcons.tag,
text: 'No tags', text: 'No tags',
@ -50,7 +47,7 @@ class TagListPage extends StatelessWidget {
); );
} }
Map<ChipSectionKey, List<FilterGridItem<TagFilter>>> _getTagEntries() { Map<ChipSectionKey, List<FilterGridItem<TagFilter>>> _getTagEntries(CollectionSource source) {
final filters = source.sortedTags.map((tag) => TagFilter(tag)).toSet(); final filters = source.sortedTags.map((tag) => TagFilter(tag)).toSet();
final sorted = FilterNavigationPage.sort(settings.tagSortFactor, source, filters); final sorted = FilterNavigationPage.sort(settings.tagSortFactor, source, filters);

View file

@ -140,7 +140,7 @@ class _HomePageState extends State<HomePage> {
case AlbumListPage.routeName: case AlbumListPage.routeName:
return DirectMaterialPageRoute( return DirectMaterialPageRoute(
settings: RouteSettings(name: AlbumListPage.routeName), settings: RouteSettings(name: AlbumListPage.routeName),
builder: (_) => AlbumListPage(source: source), builder: (_) => AlbumListPage(),
); );
case SearchPage.routeName: case SearchPage.routeName:
return SearchPageRoute( return SearchPageRoute(