diff --git a/lib/model/image_entry.dart b/lib/model/image_entry.dart index b9573346d..f15963d33 100644 --- a/lib/model/image_entry.dart +++ b/lib/model/image_entry.dart @@ -196,14 +196,20 @@ class ImageEntry { } } - bool get portrait => rotationDegrees % 180 == 90; + bool get isPortrait => rotationDegrees % 180 == 90; + + String get resolutionText { + final w = width ?? '?'; + final h = height ?? '?'; + return isPortrait ? '$h × $w' : '$w × $h'; + } double get displayAspectRatio { if (width == 0 || height == 0) return 1; - return portrait ? height / width : width / height; + return isPortrait ? height / width : width / height; } - Size get displaySize => portrait ? Size(height.toDouble(), width.toDouble()) : Size(width.toDouble(), height.toDouble()); + Size get displaySize => isPortrait ? Size(height.toDouble(), width.toDouble()) : Size(width.toDouble(), height.toDouble()); int get megaPixels => width != null && height != null ? (width * height / 1000000).round() : null; @@ -371,7 +377,10 @@ class ImageEntry { final contentId = newFields['contentId']; if (contentId is int) this.contentId = contentId; final sourceTitle = newFields['title']; - if (sourceTitle is String) this.sourceTitle = sourceTitle; + if (sourceTitle is String) { + this.sourceTitle = sourceTitle; + _bestTitle = null; + } final dateModifiedSecs = newFields['dateModifiedSecs']; if (dateModifiedSecs is int) this.dateModifiedSecs = dateModifiedSecs; final rotationDegrees = newFields['rotationDegrees']; @@ -381,6 +390,8 @@ class ImageEntry { await metadataDb.saveEntries({this}); await metadataDb.saveMetadata({catalogMetadata}); + + metadataChangeNotifier.notifyListeners(); } Future rename(String newName) async { @@ -390,8 +401,6 @@ class ImageEntry { if (newFields.isEmpty) return false; await _applyNewFields(newFields); - _bestTitle = null; - metadataChangeNotifier.notifyListeners(); return true; } diff --git a/lib/widgets/collection/thumbnail/raster.dart b/lib/widgets/collection/thumbnail/raster.dart index 0cc0b9e35..78544997a 100644 --- a/lib/widgets/collection/thumbnail/raster.dart +++ b/lib/widgets/collection/thumbnail/raster.dart @@ -67,7 +67,7 @@ class _ThumbnailRasterImageState extends State { } void _unregisterWidget(ThumbnailRasterImage widget) { - widget.entry.imageChangeNotifier?.removeListener(_onImageChanged); + widget.entry.imageChangeNotifier.removeListener(_onImageChanged); _pauseProvider(); } diff --git a/lib/widgets/fullscreen/fullscreen_debug_page.dart b/lib/widgets/fullscreen/fullscreen_debug_page.dart index 74c1133aa..2a79c4481 100644 --- a/lib/widgets/fullscreen/fullscreen_debug_page.dart +++ b/lib/widgets/fullscreen/fullscreen_debug_page.dart @@ -78,7 +78,7 @@ class FullscreenDebugPage extends StatelessWidget { 'sourceRotationDegrees': '${entry.sourceRotationDegrees}', 'rotationDegrees': '${entry.rotationDegrees}', 'isFlipped': '${entry.isFlipped}', - 'portrait': '${entry.portrait}', + 'portrait': '${entry.isPortrait}', 'displayAspectRatio': '${entry.displayAspectRatio}', 'displaySize': '${entry.displaySize}', }), diff --git a/lib/widgets/fullscreen/info/basic_section.dart b/lib/widgets/fullscreen/info/basic_section.dart index 0c158273f..3aecf1667 100644 --- a/lib/widgets/fullscreen/info/basic_section.dart +++ b/lib/widgets/fullscreen/info/basic_section.dart @@ -31,7 +31,7 @@ class BasicSection extends StatelessWidget { final date = entry.bestDate; final dateText = date != null ? '${DateFormat.yMMMd().format(date)} • ${DateFormat.Hm().format(date)}' : Constants.unknown; final showMegaPixels = entry.isPhoto && entry.megaPixels != null && entry.megaPixels > 0; - final resolutionText = '${entry.width ?? '?'} × ${entry.height ?? '?'}${showMegaPixels ? ' (${entry.megaPixels} MP)' : ''}'; + final resolutionText = '${entry.resolutionText}${showMegaPixels ? ' (${entry.megaPixels} MP)' : ''}'; return Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/widgets/fullscreen/info/info_page.dart b/lib/widgets/fullscreen/info/info_page.dart index df207102f..75ad1e238 100644 --- a/lib/widgets/fullscreen/info/info_page.dart +++ b/lib/widgets/fullscreen/info/info_page.dart @@ -36,48 +36,13 @@ class InfoPage extends StatefulWidget { class InfoPageState extends State { final ScrollController _scrollController = ScrollController(); bool _scrollStartFromTop = false; - List _metadata = []; - String _loadedMetadataUri; CollectionLens get collection => widget.collection; ImageEntry get entry => widget.entryNotifier.value; - bool get isVisible => widget.visibleNotifier.value; - - @override - void initState() { - super.initState(); - _registerWidget(widget); - _getMetadata(); - } - - @override - void didUpdateWidget(InfoPage oldWidget) { - super.didUpdateWidget(oldWidget); - _unregisterWidget(oldWidget); - _registerWidget(widget); - _getMetadata(); - } - - @override - void dispose() { - _unregisterWidget(widget); - super.dispose(); - } - - void _registerWidget(InfoPage widget) { - widget.visibleNotifier.addListener(_getMetadata); - } - - void _unregisterWidget(InfoPage widget) { - widget.visibleNotifier.removeListener(_getMetadata); - } - @override Widget build(BuildContext context) { - const horizontalPadding = EdgeInsets.symmetric(horizontal: 8); - final appBar = SliverAppBar( leading: IconButton( key: Key('back-button'), @@ -99,64 +64,20 @@ class InfoPageState extends State { builder: (c, mq, child) { final mqWidth = mq.item1; final mqViewInsetsBottom = mq.item2; - final split = mqWidth > 400; - return ValueListenableBuilder( valueListenable: widget.entryNotifier, builder: (context, entry, child) { - if (entry == null) return SizedBox.shrink(); - - final locationAtTop = split && entry.hasGps; - final locationSection = LocationSection( - collection: collection, - entry: entry, - showTitle: !locationAtTop, - visibleNotifier: widget.visibleNotifier, - onFilter: _goToCollection, - ); - final basicAndLocationSliver = locationAtTop - ? SliverToBoxAdapter( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded(child: BasicSection(entry: entry, collection: collection, onFilter: _goToCollection)), - SizedBox(width: 8), - Expanded(child: locationSection), - ], - ), + return entry != null + ? _InfoPageContent( + collection: collection, + entry: entry, + visibleNotifier: widget.visibleNotifier, + scrollController: _scrollController, + appBar: appBar, + split: mqWidth > 400, + mqViewInsetsBottom: mqViewInsetsBottom, ) - : SliverList( - delegate: SliverChildListDelegate.fixed( - [ - BasicSection(entry: entry, collection: collection, onFilter: _goToCollection), - locationSection, - ], - ), - ); - final metadataSliver = MetadataSectionSliver( - entry: entry, - metadata: _metadata, - ); - - return AnimationLimiter( - // we update the limiter key after fetching the metadata of a new entry, - // in order to restart the staggered animation of the metadata section - key: Key(_loadedMetadataUri), - child: CustomScrollView( - controller: _scrollController, - slivers: [ - appBar, - SliverPadding( - padding: horizontalPadding + EdgeInsets.only(top: 8), - sliver: basicAndLocationSliver, - ), - SliverPadding( - padding: horizontalPadding + EdgeInsets.only(bottom: 8 + mqViewInsetsBottom), - sliver: metadataSliver, - ), - ], - ), - ); + : SizedBox.shrink(); }, ); }, @@ -198,12 +119,141 @@ class InfoPageState extends State { curve: Curves.easeInOut, ); } +} + +class _InfoPageContent extends StatefulWidget { + final CollectionLens collection; + final ImageEntry entry; + final ValueNotifier visibleNotifier; + final ScrollController scrollController; + final SliverAppBar appBar; + final bool split; + final double mqViewInsetsBottom; + + const _InfoPageContent({ + Key key, + @required this.collection, + @required this.entry, + @required this.visibleNotifier, + @required this.scrollController, + @required this.appBar, + @required this.split, + @required this.mqViewInsetsBottom, + }) : super(key: key); + + @override + _InfoPageContentState createState() => _InfoPageContentState(); +} + +class _InfoPageContentState extends State<_InfoPageContent> { + List _metadata = []; + String _loadedMetadataUri; + + static const horizontalPadding = EdgeInsets.symmetric(horizontal: 8); + + CollectionLens get collection => widget.collection; + + ImageEntry get entry => widget.entry; + + bool get isVisible => widget.visibleNotifier.value; + + @override + void initState() { + super.initState(); + _registerWidget(widget); + _getMetadata(); + } + + @override + void didUpdateWidget(_InfoPageContent oldWidget) { + super.didUpdateWidget(oldWidget); + _unregisterWidget(oldWidget); + _registerWidget(widget); + _getMetadata(); + } + + @override + void dispose() { + _unregisterWidget(widget); + super.dispose(); + } + + void _registerWidget(_InfoPageContent widget) { + widget.visibleNotifier.addListener(_getMetadata); + widget.entry.metadataChangeNotifier.addListener(_onMetadataChanged); + } + + void _unregisterWidget(_InfoPageContent widget) { + widget.visibleNotifier.removeListener(_getMetadata); + widget.entry.metadataChangeNotifier.removeListener(_onMetadataChanged); + } + + @override + Widget build(BuildContext context) { + final locationAtTop = widget.split && entry.hasGps; + final locationSection = LocationSection( + collection: collection, + entry: entry, + showTitle: !locationAtTop, + visibleNotifier: widget.visibleNotifier, + onFilter: _goToCollection, + ); + final basicAndLocationSliver = locationAtTop + ? SliverToBoxAdapter( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: BasicSection(entry: entry, collection: collection, onFilter: _goToCollection)), + SizedBox(width: 8), + Expanded(child: locationSection), + ], + ), + ) + : SliverList( + delegate: SliverChildListDelegate.fixed( + [ + BasicSection(entry: entry, collection: collection, onFilter: _goToCollection), + locationSection, + ], + ), + ); + final metadataSliver = MetadataSectionSliver( + entry: entry, + metadata: _metadata, + ); + + return AnimationLimiter( + // we update the limiter key after fetching the metadata of a new entry, + // in order to restart the staggered animation of the metadata section + key: Key(_loadedMetadataUri), + child: CustomScrollView( + controller: widget.scrollController, + slivers: [ + widget.appBar, + SliverPadding( + padding: horizontalPadding + EdgeInsets.only(top: 8), + sliver: basicAndLocationSliver, + ), + SliverPadding( + padding: horizontalPadding + EdgeInsets.only(bottom: 8 + widget.mqViewInsetsBottom), + sliver: metadataSliver, + ), + ], + ), + ); + } void _goToCollection(CollectionFilter filter) { if (collection == null) return; FilterNotification(filter).dispatch(context); } + void _onMetadataChanged() { + _metadata = []; + _loadedMetadataUri = null; + _getMetadata(); + } + // fetch and hold metadata in the page widget and not in the section sliver, // so that we can refresh and limit the staggered animation of the metadata section Future _getMetadata() async { diff --git a/lib/widgets/fullscreen/overlay/bottom.dart b/lib/widgets/fullscreen/overlay/bottom.dart index fd1183db2..30216ad98 100644 --- a/lib/widgets/fullscreen/overlay/bottom.dart +++ b/lib/widgets/fullscreen/overlay/bottom.dart @@ -229,13 +229,12 @@ class _DateRow extends StatelessWidget { Widget build(BuildContext context) { final date = entry.bestDate; final dateText = date != null ? '${DateFormat.yMMMd().format(date)} • ${DateFormat.Hm().format(date)}' : Constants.unknown; - final resolution = '${entry.width ?? '?'} × ${entry.height ?? '?'}'; return Row( children: [ DecoratedIcon(AIcons.date, shadows: [Constants.embossShadow], size: _iconSize), SizedBox(width: _iconPadding), Expanded(flex: 3, child: Text(dateText, strutStyle: Constants.overflowStrutStyle)), - if (!entry.isSvg) Expanded(flex: 2, child: Text(resolution, strutStyle: Constants.overflowStrutStyle)), + if (!entry.isSvg) Expanded(flex: 2, child: Text(entry.resolutionText, strutStyle: Constants.overflowStrutStyle)), ], ); }