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 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,45 +133,54 @@ 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,
|
||||||
message: filter.tooltip,
|
children: [
|
||||||
preferBelow: false,
|
if (widget.background != null)
|
||||||
child: Material(
|
ClipRRect(
|
||||||
color: widget.decoration != null ? Colors.transparent : Theme.of(context).scaffoldBackgroundColor,
|
borderRadius: borderRadius,
|
||||||
shape: RoundedRectangleBorder(
|
child: widget.background,
|
||||||
borderRadius: borderRadius,
|
),
|
||||||
),
|
Tooltip(
|
||||||
child: InkWell(
|
message: filter.tooltip,
|
||||||
onTap: widget.onPressed != null
|
preferBelow: false,
|
||||||
? () {
|
child: Material(
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => widget.onPressed(filter));
|
color: hasBackground ? Colors.transparent : Theme.of(context).scaffoldBackgroundColor,
|
||||||
setState(() => _tapped = true);
|
shape: RoundedRectangleBorder(
|
||||||
}
|
borderRadius: borderRadius,
|
||||||
: null,
|
),
|
||||||
borderRadius: borderRadius,
|
child: InkWell(
|
||||||
child: FutureBuilder(
|
onTap: widget.onPressed != null
|
||||||
future: _colorFuture,
|
? () {
|
||||||
builder: (context, AsyncSnapshot<Color> snapshot) {
|
WidgetsBinding.instance.addPostFrameCallback((_) => widget.onPressed(filter));
|
||||||
final outlineColor = snapshot.hasData ? snapshot.data : Colors.transparent;
|
setState(() => _tapped = true);
|
||||||
return DecoratedBox(
|
}
|
||||||
decoration: BoxDecoration(
|
: null,
|
||||||
border: Border.all(
|
borderRadius: borderRadius,
|
||||||
color: outlineColor,
|
child: FutureBuilder(
|
||||||
width: AvesFilterChip.outlineWidth,
|
future: _colorFuture,
|
||||||
),
|
builder: (context, AsyncSnapshot<Color> snapshot) {
|
||||||
borderRadius: borderRadius,
|
final outlineColor = snapshot.hasData ? snapshot.data : Colors.transparent;
|
||||||
),
|
return DecoratedBox(
|
||||||
position: DecorationPosition.foreground,
|
decoration: BoxDecoration(
|
||||||
child: Padding(
|
border: Border.all(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
color: outlineColor,
|
||||||
child: content,
|
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 '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) {
|
backgroundImage = entry.isSvg
|
||||||
decoration = BoxDecoration(
|
? ThumbnailVectorImage(
|
||||||
image: DecorationImage(
|
|
||||||
image: ThumbnailProvider(
|
|
||||||
entry: entry,
|
entry: entry,
|
||||||
extent: FilterGridPage.maxCrossAxisExtent,
|
extent: FilterGridPage.maxCrossAxisExtent,
|
||||||
),
|
)
|
||||||
fit: BoxFit.cover,
|
: ThumbnailRasterImage(
|
||||||
),
|
entry: entry,
|
||||||
borderRadius: AvesFilterChip.borderRadius,
|
extent: FilterGridPage.maxCrossAxisExtent,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue