filter grid: background image progressive loading

This commit is contained in:
Thibault Deckers 2020-06-09 11:00:35 +09:00
parent 20c40020c0
commit ff9420fce7
2 changed files with 65 additions and 123 deletions

View file

@ -10,7 +10,7 @@ class AvesFilterChip extends StatefulWidget {
final CollectionFilter filter; final CollectionFilter filter;
final bool removable; final bool removable;
final bool showGenericIcon; final bool showGenericIcon;
final Decoration decoration; final Widget background;
final Widget details; final Widget details;
final HeroType heroType; final HeroType heroType;
final FilterCallback onPressed; final FilterCallback onPressed;
@ -28,7 +28,7 @@ class AvesFilterChip extends StatefulWidget {
this.filter, this.filter,
this.removable = false, this.removable = false,
this.showGenericIcon = true, this.showGenericIcon = true,
this.decoration, this.background,
this.details, this.details,
this.heroType = HeroType.onTap, this.heroType = HeroType.onTap,
@required this.onPressed, @required this.onPressed,
@ -64,11 +64,12 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final hasBackground = widget.background != null;
final leading = filter.iconBuilder(context, AvesFilterChip.iconSize, showGenericIcon: widget.showGenericIcon); final leading = filter.iconBuilder(context, AvesFilterChip.iconSize, showGenericIcon: widget.showGenericIcon);
final trailing = widget.removable ? const Icon(AIcons.clear, size: AvesFilterChip.iconSize) : null; final trailing = widget.removable ? const Icon(AIcons.clear, size: AvesFilterChip.iconSize) : null;
Widget content = Row( Widget content = Row(
mainAxisSize: widget.decoration != null ? MainAxisSize.max : MainAxisSize.min, mainAxisSize: hasBackground ? MainAxisSize.max : MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (leading != null) ...[ if (leading != null) ...[
@ -105,7 +106,7 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
child: content, child: content,
); );
if (widget.decoration != null) { if (hasBackground) {
content = Center( content = Center(
child: ColoredBox( child: ColoredBox(
color: Colors.black54, color: Colors.black54,
@ -132,12 +133,19 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
maxWidth: AvesFilterChip.maxChipWidth, maxWidth: AvesFilterChip.maxChipWidth,
minHeight: AvesFilterChip.minChipHeight, minHeight: AvesFilterChip.minChipHeight,
), ),
decoration: widget.decoration, child: Stack(
child: Tooltip( fit: StackFit.passthrough,
children: [
if (widget.background != null)
ClipRRect(
borderRadius: borderRadius,
child: widget.background,
),
Tooltip(
message: filter.tooltip, message: filter.tooltip,
preferBelow: false, preferBelow: false,
child: Material( child: Material(
color: widget.decoration != null ? Colors.transparent : Theme.of(context).scaffoldBackgroundColor, color: hasBackground ? Colors.transparent : Theme.of(context).scaffoldBackgroundColor,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: borderRadius, borderRadius: borderRadius,
), ),
@ -172,6 +180,8 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
), ),
), ),
), ),
],
),
); );
if (widget.heroType == HeroType.always || widget.heroType == HeroType.onTap && _tapped) { if (widget.heroType == HeroType.always || widget.heroType == HeroType.onTap && _tapped) {

View file

@ -1,4 +1,3 @@
import 'dart:typed_data';
import 'dart:ui'; import 'dart:ui';
import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/collection_lens.dart';
@ -7,17 +6,16 @@ import 'package:aves/model/filters/album.dart';
import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/model/settings.dart'; import 'package:aves/model/settings.dart';
import 'package:aves/services/image_file_service.dart';
import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/widgets/album/collection_page.dart'; import 'package:aves/widgets/album/collection_page.dart';
import 'package:aves/widgets/album/thumbnail/raster.dart';
import 'package:aves/widgets/album/thumbnail/vector.dart';
import 'package:aves/widgets/app_drawer.dart'; import 'package:aves/widgets/app_drawer.dart';
import 'package:aves/widgets/common/aves_filter_chip.dart'; import 'package:aves/widgets/common/aves_filter_chip.dart';
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class FilterNavigationPage extends StatelessWidget { class FilterNavigationPage extends StatelessWidget {
@ -158,7 +156,7 @@ class FilterGridPage extends StatelessWidget {
} }
} }
class DecoratedFilterChip extends StatefulWidget { class DecoratedFilterChip extends StatelessWidget {
final CollectionSource source; final CollectionSource source;
final CollectionFilter filter; final CollectionFilter filter;
final ImageEntry entry; final ImageEntry entry;
@ -171,92 +169,26 @@ class DecoratedFilterChip extends StatefulWidget {
@required this.onPressed, @required this.onPressed,
}); });
@override
_DecoratedFilterChipState createState() => _DecoratedFilterChipState();
}
class _DecoratedFilterChipState extends State<DecoratedFilterChip> {
CollectionSource get source => widget.source;
CollectionFilter get filter => widget.filter;
ImageEntry get entry => widget.entry;
Future<Uint8List> _svgByteLoader;
@override
void initState() {
super.initState();
_svgByteLoader = _initSvgByteLoader();
}
@override
void didUpdateWidget(DecoratedFilterChip oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.entry != entry) {
_svgByteLoader = _initSvgByteLoader();
}
}
Future<Uint8List> _initSvgByteLoader() async {
if (entry == null || !entry.isSvg) return null;
final uri = entry.uri;
final bytes = await ImageFileService.getImage(uri, entry.mimeType);
if (bytes == null || bytes.isEmpty) return bytes;
final svgRoot = await svg.fromSvgBytes(bytes, uri);
const extent = FilterGridPage.maxCrossAxisExtent;
final picture = svgRoot.toPicture(size: const Size(extent, extent));
final uiImage = await picture.toImage(extent.ceil(), extent.ceil());
final data = await uiImage.toByteData(format: ImageByteFormat.png);
return data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (entry == null || !entry.isSvg) { Widget backgroundImage;
Decoration decoration;
if (entry != null) { if (entry != null) {
decoration = BoxDecoration( backgroundImage = entry.isSvg
image: DecorationImage( ? ThumbnailVectorImage(
image: ThumbnailProvider( entry: entry,
extent: FilterGridPage.maxCrossAxisExtent,
)
: ThumbnailRasterImage(
entry: entry, entry: entry,
extent: FilterGridPage.maxCrossAxisExtent, extent: FilterGridPage.maxCrossAxisExtent,
),
fit: BoxFit.cover,
),
borderRadius: AvesFilterChip.borderRadius,
); );
} }
return _buildChip(decoration);
}
return FutureBuilder(
future: _svgByteLoader,
builder: (context, AsyncSnapshot<Uint8List> snapshot) {
Decoration decoration;
if (!snapshot.hasError && snapshot.connectionState == ConnectionState.done) {
decoration = BoxDecoration(
image: DecorationImage(
image: MemoryImage(snapshot.data),
fit: BoxFit.cover,
),
borderRadius: AvesFilterChip.borderRadius,
);
}
return _buildChip(decoration);
},
);
}
AvesFilterChip _buildChip(Decoration decoration) {
return AvesFilterChip( return AvesFilterChip(
filter: filter, filter: filter,
showGenericIcon: false, showGenericIcon: false,
decoration: decoration, background: backgroundImage,
details: _buildDetails(filter), details: _buildDetails(filter),
onPressed: widget.onPressed, onPressed: onPressed,
); );
} }