diff --git a/lib/image_fullscreen_page.dart b/lib/image_fullscreen_page.dart index af8ea84e4..a99c44fcf 100644 --- a/lib/image_fullscreen_page.dart +++ b/lib/image_fullscreen_page.dart @@ -3,7 +3,9 @@ import 'dart:math'; import 'package:aves/image_fullscreen_overlay.dart'; import 'package:aves/model/image_entry.dart'; +import 'package:aves/utils/file_utils.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'; @@ -53,55 +55,63 @@ class ImageFullscreenPageState extends State with SingleTic @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.black, - body: Stack( - children: [ - PhotoViewGallery.builder( - itemCount: entries.length, - builder: (context, index) { - final entry = entries[index]; - return PhotoViewGalleryPageOptions( - imageProvider: FileImage(File(entry.path)), - heroTag: entry.uri, - minScale: PhotoViewComputedScale.contained, - initialScale: PhotoViewComputedScale.contained, - onTapUp: (tapContext, details, value) => _overlayVisible.value = !_overlayVisible.value, - ); - }, - loadingChild: Center( - child: CircularProgressIndicator(), - ), - pageController: _pageController, - onPageChanged: (index) { - debugPrint('onPageChanged: index=$index'); - setState(() => _currentPage = index); - }, - transitionOnUserGestures: true, - scrollPhysics: BouncingScrollPhysics(), - ), - if (_currentPage != null) ...[ - SlideTransition( - position: _topOverlayOffset, - child: FullscreenTopOverlay( - entries: entries, - index: _currentPage, - ), - ), - Positioned( - bottom: 0, - child: SlideTransition( - position: _bottomOverlayOffset, - child: FullscreenBottomOverlay( - entries: entries, - index: _currentPage, + return PageView( + scrollDirection: Axis.vertical, + children: [ + ClipRect( + child: Scaffold( + backgroundColor: Colors.black, + body: Stack( + children: [ + PhotoViewGallery.builder( + itemCount: entries.length, + builder: (context, index) { + final entry = entries[index]; + return PhotoViewGalleryPageOptions( + imageProvider: FileImage(File(entry.path)), + heroTag: entry.uri, + minScale: PhotoViewComputedScale.contained, + initialScale: PhotoViewComputedScale.contained, + onTapUp: (tapContext, details, value) => _overlayVisible.value = !_overlayVisible.value, + ); + }, + loadingChild: Center( + child: CircularProgressIndicator(), + ), + pageController: _pageController, + onPageChanged: (index) { + debugPrint('onPageChanged: index=$index'); + setState(() => _currentPage = index); + }, + transitionOnUserGestures: true, + scrollPhysics: BouncingScrollPhysics(), ), - ), - ) - ] - ], - ), - resizeToAvoidBottomInset: false, + if (_currentPage != null) ...[ + SlideTransition( + position: _topOverlayOffset, + child: FullscreenTopOverlay( + entries: entries, + index: _currentPage, + ), + ), + Positioned( + bottom: 0, + child: SlideTransition( + position: _bottomOverlayOffset, + child: FullscreenBottomOverlay( + entries: entries, + index: _currentPage, + ), + ), + ) + ] + ], + ), + resizeToAvoidBottomInset: false, + ), + ), + InfoPage(entry: entries[_currentPage]), + ], // Hero( // tag: uri, // child: Stack( @@ -140,3 +150,52 @@ class ImageFullscreenPageState extends State with SingleTic _overlayAnimationController.reverse(); } } + +class InfoPage extends StatelessWidget { + final ImageEntry entry; + + const InfoPage({this.entry}); + + @override + Widget build(BuildContext context) { + final date = entry.getBestDate(); + return Scaffold( + appBar: AppBar( + title: Text('Info'), + ), + body: Padding( + padding: EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InfoRow('Title', entry.title), + InfoRow('Date', '${DateFormat.yMMMd().format(date)} – ${DateFormat.Hm().format(date)}'), + InfoRow('Size', formatFilesize(entry.sizeBytes)), + InfoRow('Resolution', '${entry.width} × ${entry.height} (${entry.getMegaPixels()} MP)'), + InfoRow('Path', entry.path), + ], + ), + ), + ); + } +} + +class InfoRow extends StatelessWidget { + final String label, value; + + const InfoRow(this.label, this.value); + + @override + Widget build(BuildContext context) { + return Wrap( + children: [ + Text( + label, + style: TextStyle(color: Colors.white70), + ), + SizedBox(width: 8), + Text(value), + ], + ); + } +} diff --git a/lib/model/image_entry.dart b/lib/model/image_entry.dart index c1b723a67..031f5b478 100644 --- a/lib/model/image_entry.dart +++ b/lib/model/image_entry.dart @@ -65,6 +65,10 @@ class ImageEntry { }; } + int getMegaPixels() { + return ((width * height) / 1000000).round(); + } + DateTime getBestDate() { if (sourceDateTakenMillis != null && sourceDateTakenMillis > 0) return DateTime.fromMillisecondsSinceEpoch(sourceDateTakenMillis); if (dateModifiedSecs != null && dateModifiedSecs > 0) return DateTime.fromMillisecondsSinceEpoch(dateModifiedSecs * 1000); diff --git a/lib/thumbnail_collection.dart b/lib/thumbnail_collection.dart index 3d90e4c90..544b1d1af 100644 --- a/lib/thumbnail_collection.dart +++ b/lib/thumbnail_collection.dart @@ -30,25 +30,27 @@ class ThumbnailCollection extends StatelessWidget { ), ); } - return DraggableScrollbar.arrows( - labelTextBuilder: (double offset) => Text( - "${offset ~/ 1}", - style: TextStyle(color: Colors.blueGrey), - ), - controller: scrollController, - child: CustomScrollView( + return SafeArea( + child: DraggableScrollbar.arrows( + labelTextBuilder: (double offset) => Text( + "${offset ~/ 1}", + style: TextStyle(color: Colors.blueGrey), + ), controller: scrollController, - slivers: [ - SliverAppBar( - title: Text('Aves - All'), - floating: true, - ), - ...sections.keys.map((sectionKey) => SectionSliver( - entries: entries, - sections: sections, - sectionKey: sectionKey, - )), - ], + child: CustomScrollView( + controller: scrollController, + slivers: [ + SliverAppBar( + title: Text('Aves - All'), + floating: true, + ), + ...sections.keys.map((sectionKey) => SectionSliver( + entries: entries, + sections: sections, + sectionKey: sectionKey, + )), + ], + ), ), ); } diff --git a/lib/utils/file_utils.dart b/lib/utils/file_utils.dart new file mode 100644 index 000000000..0dbad3563 --- /dev/null +++ b/lib/utils/file_utils.dart @@ -0,0 +1,38 @@ +String formatFilesize(int size, {int round = 2}) { + int divider = 1024; + + if (size < divider) return '$size B'; + + if (size < divider * divider && size % divider == 0) { + return '${(size / divider).toStringAsFixed(0)} KB'; + } + if (size < divider * divider) { + return '${(size / divider).toStringAsFixed(round)} KB'; + } + + if (size < divider * divider * divider && size % divider == 0) { + return '${(size / (divider * divider)).toStringAsFixed(0)} MB'; + } + if (size < divider * divider * divider) { + return '${(size / divider / divider).toStringAsFixed(round)} MB'; + } + + if (size < divider * divider * divider * divider && size % divider == 0) { + return '${(size / (divider * divider * divider)).toStringAsFixed(0)} GB'; + } + if (size < divider * divider * divider * divider) { + return '${(size / divider / divider / divider).toStringAsFixed(round)} GB'; + } + + if (size < divider * divider * divider * divider * divider && size % divider == 0) { + return '${(size / divider / divider / divider / divider).toStringAsFixed(0)} TB'; + } + if (size < divider * divider * divider * divider * divider) { + return '${(size / divider / divider / divider / divider).toStringAsFixed(round)} TB'; + } + + if (size < divider * divider * divider * divider * divider * divider && size % divider == 0) { + return '${(size / divider / divider / divider / divider / divider).toStringAsFixed(0)} PB'; + } + return '${(size / divider / divider / divider / divider / divider).toStringAsFixed(round)} PB'; +}