improved FutureBuilder usage
This commit is contained in:
parent
5bb63cc6ef
commit
3c9813c942
6 changed files with 153 additions and 114 deletions
|
@ -54,7 +54,7 @@ flutter {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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'
|
implementation 'com.github.bumptech.glide:glide:4.9.0'
|
||||||
annotationProcessor 'androidx.annotation:annotation:1.1.0'
|
annotationProcessor 'androidx.annotation:annotation:1.1.0'
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
|
||||||
|
|
|
@ -267,7 +267,6 @@ class BitmapWorkerTask extends AsyncTask<BitmapWorkerTask.MyTaskParams, Void, Bi
|
||||||
String uri = result.params.entry.getUri().toString();
|
String uri = result.params.entry.getUri().toString();
|
||||||
result.params.complete.accept(uri);
|
result.params.complete.accept(uri);
|
||||||
if (result.data != null) {
|
if (result.data != null) {
|
||||||
Log.d(LOG_TAG, "return bytes for uri=" + uri);
|
|
||||||
r.success(result.data);
|
r.success(result.data);
|
||||||
} else {
|
} else {
|
||||||
r.error("getImageBytes-null", "failed to get thumbnail for uri=" + uri, null);
|
r.error("getImageBytes-null", "failed to get thumbnail for uri=" + uri, null);
|
||||||
|
|
108
lib/image_fullscreen_overlay.dart
Normal file
108
lib/image_fullscreen_overlay.dart
Normal 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'])),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,8 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/image_fullscreen_overlay.dart';
|
||||||
import 'package:aves/model/image_fetcher.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:photo_view/photo_view.dart';
|
import 'package:photo_view/photo_view.dart';
|
||||||
import 'package:photo_view/photo_view_gallery.dart';
|
import 'package:photo_view/photo_view_gallery.dart';
|
||||||
|
|
||||||
|
@ -68,9 +66,8 @@ class ImageFullscreenPageState extends State<ImageFullscreenPage> {
|
||||||
),
|
),
|
||||||
if (_currentPage != null)
|
if (_currentPage != null)
|
||||||
FullscreenOverlay(
|
FullscreenOverlay(
|
||||||
entry: entries[_currentPage],
|
entries: entries,
|
||||||
index: _currentPage,
|
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'])),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,18 +7,23 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:transparent_image/transparent_image.dart';
|
import 'package:transparent_image/transparent_image.dart';
|
||||||
|
|
||||||
class Thumbnail extends StatefulWidget {
|
class Thumbnail extends StatefulWidget {
|
||||||
Thumbnail({Key key, @required this.entry, @required this.extent}) : super(key: key);
|
|
||||||
|
|
||||||
final Map entry;
|
final Map entry;
|
||||||
final double extent;
|
final double extent;
|
||||||
|
final double devicePixelRatio;
|
||||||
|
|
||||||
|
Thumbnail({
|
||||||
|
Key key,
|
||||||
|
@required this.entry,
|
||||||
|
@required this.extent,
|
||||||
|
@required this.devicePixelRatio,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ThumbnailState createState() => ThumbnailState();
|
ThumbnailState createState() => ThumbnailState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThumbnailState extends State<Thumbnail> {
|
class ThumbnailState extends State<Thumbnail> {
|
||||||
Future<Uint8List> loader;
|
Future<Uint8List> _byteLoader;
|
||||||
Uint8List bytes;
|
|
||||||
|
|
||||||
String get mimeType => widget.entry['mimeType'];
|
String get mimeType => widget.entry['mimeType'];
|
||||||
|
|
||||||
|
@ -27,6 +32,19 @@ class ThumbnailState extends State<Thumbnail> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.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
|
@override
|
||||||
|
@ -37,11 +55,6 @@ class ThumbnailState extends State<Thumbnail> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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 isVideo = mimeType.startsWith(MimeTypes.MIME_VIDEO);
|
||||||
var isGif = mimeType == MimeTypes.MIME_GIF;
|
var isGif = mimeType == MimeTypes.MIME_GIF;
|
||||||
var iconSize = widget.extent / 4;
|
var iconSize = widget.extent / 4;
|
||||||
|
@ -53,11 +66,9 @@ class ThumbnailState extends State<Thumbnail> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future: loader,
|
future: _byteLoader,
|
||||||
builder: (futureContext, AsyncSnapshot<Uint8List> snapshot) {
|
builder: (futureContext, AsyncSnapshot<Uint8List> snapshot) {
|
||||||
if (bytes == null && snapshot.connectionState == ConnectionState.done && !snapshot.hasError) {
|
var bytes = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : kTransparentImage;
|
||||||
bytes = snapshot.data;
|
|
||||||
}
|
|
||||||
return Stack(
|
return Stack(
|
||||||
alignment: AlignmentDirectional.bottomStart,
|
alignment: AlignmentDirectional.bottomStart,
|
||||||
children: [
|
children: [
|
||||||
|
@ -72,7 +83,7 @@ class ThumbnailState extends State<Thumbnail> {
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
constraints: BoxConstraints.tight(Size(dim, dim)),
|
constraints: BoxConstraints.tight(Size(dim, dim)),
|
||||||
child: Image.memory(
|
child: Image.memory(
|
||||||
bytes ?? kTransparentImage,
|
bytes,
|
||||||
width: dim,
|
width: dim,
|
||||||
height: dim,
|
height: dim,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
|
|
@ -19,7 +19,7 @@ class ThumbnailCollection extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var columnCount = 4;
|
var columnCount = 4;
|
||||||
var extent = MediaQuery.of(context).size.width / columnCount;
|
var mediaQuery = MediaQuery.of(context);
|
||||||
|
|
||||||
return DraggableScrollbar.arrows(
|
return DraggableScrollbar.arrows(
|
||||||
labelTextBuilder: (double offset) => Text(
|
labelTextBuilder: (double offset) => Text(
|
||||||
|
@ -43,18 +43,11 @@ class ThumbnailCollection extends StatelessWidget {
|
||||||
if (index >= sectionEntries.length) return null;
|
if (index >= sectionEntries.length) return null;
|
||||||
var entry = sectionEntries[index];
|
var entry = sectionEntries[index];
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => Navigator.push(
|
onTap: () => _showFullscreen(context, entry),
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => ImageFullscreenPage(
|
|
||||||
entries: entries,
|
|
||||||
initialUri: entry['uri'],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Thumbnail(
|
child: Thumbnail(
|
||||||
entry: entry,
|
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 {
|
class DaySectionHeader extends StatelessWidget {
|
||||||
|
@ -109,7 +114,7 @@ class SectionHeader extends StatelessWidget {
|
||||||
Shadow(
|
Shadow(
|
||||||
offset: Offset(0, 2),
|
offset: Offset(0, 2),
|
||||||
blurRadius: 3,
|
blurRadius: 3,
|
||||||
color: Colors.grey[900]
|
color: Colors.grey[900],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue