filter grid: background image progressive loading
This commit is contained in:
parent
20c40020c0
commit
ff9420fce7
2 changed files with 65 additions and 123 deletions
|
@ -10,7 +10,7 @@ class AvesFilterChip extends StatefulWidget {
|
|||
final CollectionFilter filter;
|
||||
final bool removable;
|
||||
final bool showGenericIcon;
|
||||
final Decoration decoration;
|
||||
final Widget background;
|
||||
final Widget details;
|
||||
final HeroType heroType;
|
||||
final FilterCallback onPressed;
|
||||
|
@ -28,7 +28,7 @@ class AvesFilterChip extends StatefulWidget {
|
|||
this.filter,
|
||||
this.removable = false,
|
||||
this.showGenericIcon = true,
|
||||
this.decoration,
|
||||
this.background,
|
||||
this.details,
|
||||
this.heroType = HeroType.onTap,
|
||||
@required this.onPressed,
|
||||
|
@ -64,11 +64,12 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasBackground = widget.background != null;
|
||||
final leading = filter.iconBuilder(context, AvesFilterChip.iconSize, showGenericIcon: widget.showGenericIcon);
|
||||
final trailing = widget.removable ? const Icon(AIcons.clear, size: AvesFilterChip.iconSize) : null;
|
||||
|
||||
Widget content = Row(
|
||||
mainAxisSize: widget.decoration != null ? MainAxisSize.max : MainAxisSize.min,
|
||||
mainAxisSize: hasBackground ? MainAxisSize.max : MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (leading != null) ...[
|
||||
|
@ -105,7 +106,7 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
child: content,
|
||||
);
|
||||
|
||||
if (widget.decoration != null) {
|
||||
if (hasBackground) {
|
||||
content = Center(
|
||||
child: ColoredBox(
|
||||
color: Colors.black54,
|
||||
|
@ -132,45 +133,54 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
maxWidth: AvesFilterChip.maxChipWidth,
|
||||
minHeight: AvesFilterChip.minChipHeight,
|
||||
),
|
||||
decoration: widget.decoration,
|
||||
child: Tooltip(
|
||||
message: filter.tooltip,
|
||||
preferBelow: false,
|
||||
child: Material(
|
||||
color: widget.decoration != null ? Colors.transparent : Theme.of(context).scaffoldBackgroundColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: widget.onPressed != null
|
||||
? () {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => widget.onPressed(filter));
|
||||
setState(() => _tapped = true);
|
||||
}
|
||||
: null,
|
||||
borderRadius: borderRadius,
|
||||
child: FutureBuilder(
|
||||
future: _colorFuture,
|
||||
builder: (context, AsyncSnapshot<Color> snapshot) {
|
||||
final outlineColor = snapshot.hasData ? snapshot.data : Colors.transparent;
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: outlineColor,
|
||||
width: AvesFilterChip.outlineWidth,
|
||||
),
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
position: DecorationPosition.foreground,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: content,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Stack(
|
||||
fit: StackFit.passthrough,
|
||||
children: [
|
||||
if (widget.background != null)
|
||||
ClipRRect(
|
||||
borderRadius: borderRadius,
|
||||
child: widget.background,
|
||||
),
|
||||
Tooltip(
|
||||
message: filter.tooltip,
|
||||
preferBelow: false,
|
||||
child: Material(
|
||||
color: hasBackground ? Colors.transparent : Theme.of(context).scaffoldBackgroundColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: widget.onPressed != null
|
||||
? () {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => widget.onPressed(filter));
|
||||
setState(() => _tapped = true);
|
||||
}
|
||||
: null,
|
||||
borderRadius: borderRadius,
|
||||
child: FutureBuilder(
|
||||
future: _colorFuture,
|
||||
builder: (context, AsyncSnapshot<Color> snapshot) {
|
||||
final outlineColor = snapshot.hasData ? snapshot.data : Colors.transparent;
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: outlineColor,
|
||||
width: AvesFilterChip.outlineWidth,
|
||||
),
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
position: DecorationPosition.foreground,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: content,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
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/image_entry.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/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/common/aves_filter_chip.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/image_providers/thumbnail_provider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class FilterNavigationPage extends StatelessWidget {
|
||||
|
@ -158,7 +156,7 @@ class FilterGridPage extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class DecoratedFilterChip extends StatefulWidget {
|
||||
class DecoratedFilterChip extends StatelessWidget {
|
||||
final CollectionSource source;
|
||||
final CollectionFilter filter;
|
||||
final ImageEntry entry;
|
||||
|
@ -171,92 +169,26 @@ class DecoratedFilterChip extends StatefulWidget {
|
|||
@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
|
||||
Widget build(BuildContext context) {
|
||||
if (entry == null || !entry.isSvg) {
|
||||
Decoration decoration;
|
||||
if (entry != null) {
|
||||
decoration = BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: ThumbnailProvider(
|
||||
Widget backgroundImage;
|
||||
if (entry != null) {
|
||||
backgroundImage = entry.isSvg
|
||||
? ThumbnailVectorImage(
|
||||
entry: entry,
|
||||
extent: FilterGridPage.maxCrossAxisExtent,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
borderRadius: AvesFilterChip.borderRadius,
|
||||
);
|
||||
}
|
||||
return _buildChip(decoration);
|
||||
)
|
||||
: ThumbnailRasterImage(
|
||||
entry: entry,
|
||||
extent: FilterGridPage.maxCrossAxisExtent,
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
filter: filter,
|
||||
showGenericIcon: false,
|
||||
decoration: decoration,
|
||||
background: backgroundImage,
|
||||
details: _buildDetails(filter),
|
||||
onPressed: widget.onPressed,
|
||||
onPressed: onPressed,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue