improved FutureBuilder usage

This commit is contained in:
Thibault Deckers 2019-07-23 23:05:43 +09:00
parent 5bb63cc6ef
commit 3c9813c942
6 changed files with 153 additions and 114 deletions

View file

@ -54,7 +54,7 @@ flutter {
}
dependencies {
implementation 'com.drewnoakes:metadata-extractor:2.12.0'
implementation 'com.drewnoakes:metadata-extractor:2.11.0'
implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'androidx.annotation:annotation:1.1.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'

View file

@ -267,7 +267,6 @@ class BitmapWorkerTask extends AsyncTask<BitmapWorkerTask.MyTaskParams, Void, Bi
String uri = result.params.entry.getUri().toString();
result.params.complete.accept(uri);
if (result.data != null) {
Log.d(LOG_TAG, "return bytes for uri=" + uri);
r.success(result.data);
} else {
r.error("getImageBytes-null", "failed to get thumbnail for uri=" + uri, null);

View file

@ -0,0 +1,108 @@
import 'package:aves/model/image_entry.dart';
import 'package:aves/model/image_fetcher.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class FullscreenOverlay extends StatefulWidget {
final List<Map> entries;
final int index;
FullscreenOverlay({this.entries, this.index});
@override
State<StatefulWidget> createState() => _FullscreenOverlayState();
}
class _FullscreenOverlayState extends State<FullscreenOverlay> {
Future<Map> _detailLoader;
Map _lastDetails;
Map get entry => widget.entries[widget.index];
int get total => widget.entries.length;
@override
void initState() {
super.initState();
initDetailLoader();
}
@override
void didUpdateWidget(FullscreenOverlay oldWidget) {
super.didUpdateWidget(oldWidget);
initDetailLoader();
}
initDetailLoader() {
_detailLoader = ImageFetcher.getOverlayMetadata(entry['path']);
}
@override
Widget build(BuildContext context) {
var viewInsets = MediaQuery.of(context).viewInsets;
var date = ImageEntry.getBestDate(entry);
var subRowConstraints = BoxConstraints(maxWidth: 400);
return IgnorePointer(
child: Container(
padding: EdgeInsets.all(8.0).add(EdgeInsets.only(bottom: viewInsets.bottom)),
color: Colors.black45,
child: DefaultTextStyle(
style: TextStyle(
shadows: [
Shadow(
color: Colors.black87,
offset: Offset(0.0, 1.0),
)
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${widget.index + 1}/$total ${entry['title']}',
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4),
ConstrainedBox(
constraints: subRowConstraints,
child: Row(
children: [
Icon(Icons.calendar_today, size: 16),
SizedBox(width: 8),
Expanded(child: Text('${DateFormat.yMMMd().format(date)} ${DateFormat.Hm().format(date)}')),
Expanded(child: Text('${entry['width']} × ${entry['height']}')),
],
),
),
SizedBox(height: 4),
FutureBuilder(
future: _detailLoader,
builder: (futureContext, AsyncSnapshot<Map> snapshot) {
if (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) {
_lastDetails = snapshot.data;
}
return (_lastDetails == null || _lastDetails.isEmpty)
? Text('')
: ConstrainedBox(
constraints: subRowConstraints,
child: Row(
children: [
Icon(Icons.camera, size: 16),
SizedBox(width: 8),
Expanded(child: Text((_lastDetails['aperture'] as String).replaceAll('f', 'ƒ'))),
Expanded(child: Text(_lastDetails['exposureTime'])),
Expanded(child: Text(_lastDetails['focalLength'])),
Expanded(child: Text(_lastDetails['iso'])),
],
),
);
},
)
],
),
),
),
);
}
}

View file

@ -1,10 +1,8 @@
import 'dart:io';
import 'dart:math';
import 'package:aves/model/image_entry.dart';
import 'package:aves/model/image_fetcher.dart';
import 'package:aves/image_fullscreen_overlay.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
@ -68,9 +66,8 @@ class ImageFullscreenPageState extends State<ImageFullscreenPage> {
),
if (_currentPage != null)
FullscreenOverlay(
entry: entries[_currentPage],
entries: entries,
index: _currentPage,
total: entries.length,
),
],
),
@ -106,84 +103,3 @@ class ImageFullscreenPageState extends State<ImageFullscreenPage> {
);
}
}
class FullscreenOverlay extends StatelessWidget {
final Map entry;
final int index, total;
FullscreenOverlay({this.entry, this.index, this.total});
@override
Widget build(BuildContext context) {
var viewInsets = MediaQuery.of(context).viewInsets;
var date = ImageEntry.getBestDate(entry);
return IgnorePointer(
child: Container(
padding: EdgeInsets.all(8.0).add(EdgeInsets.only(bottom: viewInsets.bottom)),
color: Colors.black45,
child: DefaultTextStyle(
style: TextStyle(
shadows: [
Shadow(
color: Colors.black87,
offset: Offset(0.0, 1.0),
)
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${index + 1} / $total ${entry['title']}',
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4),
Row(
children: [
Padding(
padding: EdgeInsets.only(right: 8.0),
child: Icon(
Icons.calendar_today,
size: 16,
),
),
Expanded(child: Text('${DateFormat.yMMMd().format(date)} ${DateFormat.Hm().format(date)}')),
Expanded(child: Text('${entry['width']} × ${entry['height']}')),
],
),
SizedBox(height: 4),
FutureBuilder(
future: ImageFetcher.getOverlayMetadata(entry['path']),
builder: (futureContext, AsyncSnapshot<Map> snapshot) {
if (snapshot.connectionState != ConnectionState.done || snapshot.hasError) {
return Text('');
}
var metadata = snapshot.data;
if (metadata.isEmpty) {
return Text('');
}
return Row(
children: [
Padding(
padding: EdgeInsets.only(right: 8.0),
child: Icon(
Icons.camera,
size: 16,
),
),
Expanded(child: Text((metadata['aperture'] as String).replaceAll('f', 'ƒ'))),
Expanded(child: Text(metadata['exposureTime'])),
Expanded(child: Text(metadata['focalLength'])),
Expanded(child: Text(metadata['iso'])),
],
);
},
)
],
),
),
),
);
}
}

View file

@ -7,18 +7,23 @@ import 'package:flutter/material.dart';
import 'package:transparent_image/transparent_image.dart';
class Thumbnail extends StatefulWidget {
Thumbnail({Key key, @required this.entry, @required this.extent}) : super(key: key);
final Map entry;
final double extent;
final double devicePixelRatio;
Thumbnail({
Key key,
@required this.entry,
@required this.extent,
@required this.devicePixelRatio,
}) : super(key: key);
@override
ThumbnailState createState() => ThumbnailState();
}
class ThumbnailState extends State<Thumbnail> {
Future<Uint8List> loader;
Uint8List bytes;
Future<Uint8List> _byteLoader;
String get mimeType => widget.entry['mimeType'];
@ -27,6 +32,19 @@ class ThumbnailState extends State<Thumbnail> {
@override
void initState() {
super.initState();
initByteLoader();
}
@override
void didUpdateWidget(Thumbnail oldWidget) {
super.didUpdateWidget(oldWidget);
if (uri == oldWidget.entry['uri'] && widget.extent == oldWidget.extent) return;
initByteLoader();
}
initByteLoader() {
var dim = (widget.extent * widget.devicePixelRatio).round();
_byteLoader = ImageFetcher.getImageBytes(widget.entry, dim, dim);
}
@override
@ -37,11 +55,6 @@ class ThumbnailState extends State<Thumbnail> {
@override
Widget build(BuildContext context) {
if (loader == null) {
var dim = (widget.extent * MediaQuery.of(context).devicePixelRatio).round();
loader = ImageFetcher.getImageBytes(widget.entry, dim, dim);
}
var isVideo = mimeType.startsWith(MimeTypes.MIME_VIDEO);
var isGif = mimeType == MimeTypes.MIME_GIF;
var iconSize = widget.extent / 4;
@ -53,11 +66,9 @@ class ThumbnailState extends State<Thumbnail> {
),
),
child: FutureBuilder(
future: loader,
future: _byteLoader,
builder: (futureContext, AsyncSnapshot<Uint8List> snapshot) {
if (bytes == null && snapshot.connectionState == ConnectionState.done && !snapshot.hasError) {
bytes = snapshot.data;
}
var bytes = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : kTransparentImage;
return Stack(
alignment: AlignmentDirectional.bottomStart,
children: [
@ -72,7 +83,7 @@ class ThumbnailState extends State<Thumbnail> {
alignment: Alignment.center,
constraints: BoxConstraints.tight(Size(dim, dim)),
child: Image.memory(
bytes ?? kTransparentImage,
bytes,
width: dim,
height: dim,
fit: BoxFit.cover,

View file

@ -19,7 +19,7 @@ class ThumbnailCollection extends StatelessWidget {
@override
Widget build(BuildContext context) {
var columnCount = 4;
var extent = MediaQuery.of(context).size.width / columnCount;
var mediaQuery = MediaQuery.of(context);
return DraggableScrollbar.arrows(
labelTextBuilder: (double offset) => Text(
@ -43,18 +43,11 @@ class ThumbnailCollection extends StatelessWidget {
if (index >= sectionEntries.length) return null;
var entry = sectionEntries[index];
return GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ImageFullscreenPage(
entries: entries,
initialUri: entry['uri'],
),
),
),
onTap: () => _showFullscreen(context, entry),
child: Thumbnail(
entry: entry,
extent: extent,
extent: mediaQuery.size.width / columnCount,
devicePixelRatio: mediaQuery.devicePixelRatio,
),
);
},
@ -69,6 +62,18 @@ class ThumbnailCollection extends StatelessWidget {
),
);
}
Future _showFullscreen(BuildContext context, Map entry) {
return Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ImageFullscreenPage(
entries: entries,
initialUri: entry['uri'],
),
),
);
}
}
class DaySectionHeader extends StatelessWidget {
@ -109,7 +114,7 @@ class SectionHeader extends StatelessWidget {
Shadow(
offset: Offset(0, 2),
blurRadius: 3,
color: Colors.grey[900]
color: Colors.grey[900],
),
],
),