video: fixed aspect ratio, handle thumbnail loading error
This commit is contained in:
parent
cc0283d393
commit
a7f9163ec3
6 changed files with 30 additions and 33 deletions
|
@ -71,22 +71,22 @@ class ImageEntry {
|
||||||
|
|
||||||
bool get isVideo => mimeType.startsWith(MimeTypes.MIME_VIDEO);
|
bool get isVideo => mimeType.startsWith(MimeTypes.MIME_VIDEO);
|
||||||
|
|
||||||
int getMegaPixels() {
|
double get aspectRatio => height == 0 ? 1 : width / height;
|
||||||
return ((width * height) / 1000000).round();
|
|
||||||
}
|
|
||||||
|
|
||||||
DateTime getBestDate() {
|
int get megaPixels => (width * height / 1000000).round();
|
||||||
|
|
||||||
|
DateTime get bestDate {
|
||||||
if (sourceDateTakenMillis != null && sourceDateTakenMillis > 0) return DateTime.fromMillisecondsSinceEpoch(sourceDateTakenMillis);
|
if (sourceDateTakenMillis != null && sourceDateTakenMillis > 0) return DateTime.fromMillisecondsSinceEpoch(sourceDateTakenMillis);
|
||||||
if (dateModifiedSecs != null && dateModifiedSecs > 0) return DateTime.fromMillisecondsSinceEpoch(dateModifiedSecs * 1000);
|
if (dateModifiedSecs != null && dateModifiedSecs > 0) return DateTime.fromMillisecondsSinceEpoch(dateModifiedSecs * 1000);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime getMonthTaken() {
|
DateTime get monthTaken {
|
||||||
final d = getBestDate();
|
final d = bestDate;
|
||||||
return d == null ? null : DateTime(d.year, d.month);
|
return d == null ? null : DateTime(d.year, d.month);
|
||||||
}
|
}
|
||||||
|
|
||||||
String getDurationText() {
|
String get durationText {
|
||||||
final d = Duration(milliseconds: durationMillis);
|
final d = Duration(milliseconds: durationMillis);
|
||||||
|
|
||||||
String twoDigits(int n) {
|
String twoDigits(int n) {
|
||||||
|
|
|
@ -55,7 +55,7 @@ class ThumbnailState extends State<Thumbnail> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final fontSize = (widget.extent / 8).roundToDouble();
|
final fontSize = min(14.0, (widget.extent / 8).roundToDouble());
|
||||||
final iconSize = fontSize * 2;
|
final iconSize = fontSize * 2;
|
||||||
return DefaultTextStyle(
|
return DefaultTextStyle(
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
@ -86,12 +86,14 @@ class ThumbnailState extends State<Thumbnail> {
|
||||||
return Container(
|
return Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
constraints: BoxConstraints.tight(Size(dim, dim)),
|
constraints: BoxConstraints.tight(Size(dim, dim)),
|
||||||
child: Image.memory(
|
child: bytes.length > 0
|
||||||
|
? Image.memory(
|
||||||
bytes,
|
bytes,
|
||||||
width: dim,
|
width: dim,
|
||||||
height: dim,
|
height: dim,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
)
|
||||||
|
: Icon(Icons.error),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
@ -123,7 +125,7 @@ class VideoTag extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsets.all(1),
|
margin: EdgeInsets.all(1),
|
||||||
padding: EdgeInsets.only(right: 4),
|
padding: EdgeInsets.only(right: iconSize / 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Color(0xBB000000),
|
color: Color(0xBB000000),
|
||||||
borderRadius: BorderRadius.all(
|
borderRadius: BorderRadius.all(
|
||||||
|
@ -139,9 +141,7 @@ class VideoTag extends StatelessWidget {
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
),
|
),
|
||||||
SizedBox(width: 2),
|
SizedBox(width: 2),
|
||||||
Text(
|
Text(entry.durationText)
|
||||||
entry.getDurationText(),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,7 +16,7 @@ class ThumbnailCollection extends StatelessWidget {
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
|
|
||||||
ThumbnailCollection({Key key, this.entries, this.done})
|
ThumbnailCollection({Key key, this.entries, this.done})
|
||||||
: sections = groupBy(entries, (entry) => entry.getMonthTaken()),
|
: sections = groupBy(entries, (entry) => entry.monthTaken),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -213,15 +213,9 @@ class ImagePageState extends State<ImagePage> with AutomaticKeepAliveClientMixin
|
||||||
builder: (galleryContext, index) {
|
builder: (galleryContext, index) {
|
||||||
final entry = widget.entries[index];
|
final entry = widget.entries[index];
|
||||||
if (entry.isVideo) {
|
if (entry.isVideo) {
|
||||||
final screenSize = MediaQuery.of(galleryContext).size;
|
|
||||||
final videoAspectRatio = entry.width / entry.height;
|
|
||||||
final childWidth = min(screenSize.width, entry.width);
|
|
||||||
final childHeight = childWidth / videoAspectRatio;
|
|
||||||
debugPrint('ImagePageState video path=${entry.path} childWidth=$childWidth childHeight=$childHeight var=$videoAspectRatio car=${childWidth / childHeight}');
|
|
||||||
|
|
||||||
return PhotoViewGalleryPageOptions.customChild(
|
return PhotoViewGalleryPageOptions.customChild(
|
||||||
child: AvesVideo(entry: entry),
|
child: AvesVideo(entry: entry),
|
||||||
childSize: Size(childWidth, childHeight),
|
childSize: MediaQuery.of(galleryContext).size,
|
||||||
// no heroTag because `Chewie` already internally builds one with the videoController
|
// no heroTag because `Chewie` already internally builds one with the videoController
|
||||||
minScale: PhotoViewComputedScale.contained,
|
minScale: PhotoViewComputedScale.contained,
|
||||||
initialScale: PhotoViewComputedScale.contained,
|
initialScale: PhotoViewComputedScale.contained,
|
||||||
|
@ -264,15 +258,18 @@ class AvesVideoState extends State<AvesVideo> {
|
||||||
VideoPlayerController videoPlayerController;
|
VideoPlayerController videoPlayerController;
|
||||||
ChewieController chewieController;
|
ChewieController chewieController;
|
||||||
|
|
||||||
|
ImageEntry get entry => widget.entry;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
videoPlayerController = VideoPlayerController.file(
|
videoPlayerController = VideoPlayerController.file(
|
||||||
File(widget.entry.path),
|
File(entry.path),
|
||||||
// ensure the first frame is shown after the video is initialized
|
);
|
||||||
)..initialize().then((_) => setState(() {}));
|
|
||||||
chewieController = ChewieController(
|
chewieController = ChewieController(
|
||||||
videoPlayerController: videoPlayerController,
|
videoPlayerController: videoPlayerController,
|
||||||
|
aspectRatio: entry.aspectRatio,
|
||||||
|
autoInitialize: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,9 +37,9 @@ class InfoPageState extends State<InfoPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final date = entry.getBestDate();
|
final date = entry.bestDate;
|
||||||
final dateText = '${DateFormat.yMMMd().format(date)} – ${DateFormat.Hm().format(date)}';
|
final dateText = '${DateFormat.yMMMd().format(date)} – ${DateFormat.Hm().format(date)}';
|
||||||
final resolutionText = '${entry.width} × ${entry.height}${entry.isVideo ? '' : ' (${entry.getMegaPixels()} MP)'}';
|
final resolutionText = '${entry.width} × ${entry.height}${entry.isVideo ? '' : ' (${entry.megaPixels} MP)'}';
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
|
@ -75,7 +75,7 @@ class InfoPageState extends State<InfoPage> {
|
||||||
SectionRow('File'),
|
SectionRow('File'),
|
||||||
InfoRow('Title', entry.title),
|
InfoRow('Title', entry.title),
|
||||||
InfoRow('Date', dateText),
|
InfoRow('Date', dateText),
|
||||||
if (entry.isVideo) InfoRow('Duration', entry.getDurationText()),
|
if (entry.isVideo) InfoRow('Duration', entry.durationText),
|
||||||
InfoRow('Resolution', resolutionText),
|
InfoRow('Resolution', resolutionText),
|
||||||
InfoRow('Size', formatFilesize(entry.sizeBytes)),
|
InfoRow('Size', formatFilesize(entry.sizeBytes)),
|
||||||
InfoRow('Path', entry.path),
|
InfoRow('Path', entry.path),
|
||||||
|
|
|
@ -150,7 +150,7 @@ class _FullscreenBottomOverlayContent extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final subRowWidth = min(400.0, maxWidth);
|
final subRowWidth = min(400.0, maxWidth);
|
||||||
final date = entry.getBestDate();
|
final date = entry.bestDate;
|
||||||
return DefaultTextStyle(
|
return DefaultTextStyle(
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
shadows: [
|
shadows: [
|
||||||
|
|
Loading…
Reference in a new issue