diff --git a/lib/model/image_metadata.dart b/lib/model/image_metadata.dart index 59c90a3ad..2bcc28be6 100644 --- a/lib/model/image_metadata.dart +++ b/lib/model/image_metadata.dart @@ -114,18 +114,18 @@ class OverlayMetadata { this.exposureTime, this.focalLength, this.iso, - }) : aperture = aperture.replaceFirst('f', 'ƒ'); + }) : aperture = aperture?.replaceFirst('f', 'ƒ'); factory OverlayMetadata.fromMap(Map map) { return OverlayMetadata( - aperture: map['aperture'] ?? '', - exposureTime: map['exposureTime'] ?? '', - focalLength: map['focalLength'] ?? '', - iso: map['iso'] ?? '', + aperture: map['aperture'], + exposureTime: map['exposureTime'], + focalLength: map['focalLength'], + iso: map['iso'], ); } - bool get isEmpty => aperture.isEmpty && exposureTime.isEmpty && focalLength.isEmpty && iso.isEmpty; + bool get isEmpty => aperture == null && exposureTime == null && focalLength == null && iso == null; @override String toString() { diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart index 69e9e0c6d..285ad9957 100644 --- a/lib/utils/constants.dart +++ b/lib/utils/constants.dart @@ -18,7 +18,8 @@ class Constants { offset: Offset(0.5, 1.0), ); - static const String unknown = 'unknown'; + static const String overlayUnknown = '—'; // em dash + static const String infoUnknown = 'unknown'; static const pointNemo = Tuple2(-48.876667, -123.393333); diff --git a/lib/utils/durations.dart b/lib/utils/durations.dart index 9270148e3..68394e51b 100644 --- a/lib/utils/durations.dart +++ b/lib/utils/durations.dart @@ -27,6 +27,7 @@ class Durations { // fullscreen animations static const fullscreenPageAnimation = Duration(milliseconds: 300); static const fullscreenOverlayAnimation = Duration(milliseconds: 200); + static const fullscreenOverlayChangeAnimation = Duration(milliseconds: 150); // info static const mapStyleSwitchAnimation = Duration(milliseconds: 300); diff --git a/lib/widgets/fullscreen/info/basic_section.dart b/lib/widgets/fullscreen/info/basic_section.dart index 3aecf1667..6fa89b94c 100644 --- a/lib/widgets/fullscreen/info/basic_section.dart +++ b/lib/widgets/fullscreen/info/basic_section.dart @@ -29,7 +29,7 @@ class BasicSection extends StatelessWidget { @override Widget build(BuildContext context) { final date = entry.bestDate; - final dateText = date != null ? '${DateFormat.yMMMd().format(date)} • ${DateFormat.Hm().format(date)}' : Constants.unknown; + final dateText = date != null ? '${DateFormat.yMMMd().format(date)} • ${DateFormat.Hm().format(date)}' : Constants.infoUnknown; final showMegaPixels = entry.isPhoto && entry.megaPixels != null && entry.megaPixels > 0; final resolutionText = '${entry.resolutionText}${showMegaPixels ? ' (${entry.megaPixels} MP)' : ''}'; @@ -37,12 +37,12 @@ class BasicSection extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ InfoRowGroup({ - 'Title': entry.bestTitle ?? Constants.unknown, + 'Title': entry.bestTitle ?? Constants.infoUnknown, 'Date': dateText, if (entry.isVideo) ..._buildVideoRows(), if (!entry.isSvg) 'Resolution': resolutionText, - 'Size': entry.sizeBytes != null ? formatFilesize(entry.sizeBytes) : Constants.unknown, - 'URI': entry.uri ?? Constants.unknown, + 'Size': entry.sizeBytes != null ? formatFilesize(entry.sizeBytes) : Constants.infoUnknown, + 'URI': entry.uri ?? Constants.infoUnknown, if (entry.path != null) 'Path': entry.path, }), _buildChips(), diff --git a/lib/widgets/fullscreen/overlay/bottom.dart b/lib/widgets/fullscreen/overlay/bottom.dart index 38c5b1e06..58fbe7047 100644 --- a/lib/widgets/fullscreen/overlay/bottom.dart +++ b/lib/widgets/fullscreen/overlay/bottom.dart @@ -6,6 +6,7 @@ import 'package:aves/model/settings/coordinate_format.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/services/metadata_service.dart'; import 'package:aves/utils/constants.dart'; +import 'package:aves/utils/durations.dart'; import 'package:aves/widgets/common/fx/blurred.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/fullscreen/overlay/common.dart'; @@ -150,25 +151,21 @@ class _FullscreenBottomOverlayContent extends AnimatedWidget { final positionTitle = [ if (position != null) position, if (entry.bestTitle != null) entry.bestTitle, - ].join(' – '); + ].join(' — '); // em dash final hasShootingDetails = details != null && !details.isEmpty && settings.showOverlayShootingDetails; return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ if (positionTitle.isNotEmpty) Text(positionTitle, strutStyle: Constants.overflowStrutStyle), - if (entry.hasGps) - Container( - padding: EdgeInsets.only(top: _interRowPadding), - child: _LocationRow(entry: entry), - ), + _buildSoloLocationRow(), if (twoColumns) Padding( padding: EdgeInsets.only(top: _interRowPadding), child: Row( children: [ Container(width: subRowWidth, child: _DateRow(entry)), - if (hasShootingDetails) Container(width: subRowWidth, child: _ShootingRow(details)), + _buildDuoShootingRow(subRowWidth, hasShootingDetails), ], ), ) @@ -178,12 +175,7 @@ class _FullscreenBottomOverlayContent extends AnimatedWidget { width: subRowWidth, child: _DateRow(entry), ), - if (hasShootingDetails) - Container( - padding: EdgeInsets.only(top: _interRowPadding), - width: subRowWidth, - child: _ShootingRow(details), - ), + _buildSoloShootingRow(subRowWidth, hasShootingDetails), ], ], ); @@ -192,6 +184,58 @@ class _FullscreenBottomOverlayContent extends AnimatedWidget { ), ); } + + Widget _buildSoloLocationRow() => AnimatedSwitcher( + duration: Durations.fullscreenOverlayChangeAnimation, + switchInCurve: Curves.easeInOutCubic, + switchOutCurve: Curves.easeInOutCubic, + transitionBuilder: _soloTransition, + child: entry.hasGps + ? Container( + padding: EdgeInsets.only(top: _interRowPadding), + child: _LocationRow(entry: entry), + ) + : SizedBox.shrink(), + ); + + Widget _buildSoloShootingRow(double subRowWidth, bool hasShootingDetails) => AnimatedSwitcher( + duration: Durations.fullscreenOverlayChangeAnimation, + switchInCurve: Curves.easeInOutCubic, + switchOutCurve: Curves.easeInOutCubic, + transitionBuilder: _soloTransition, + child: hasShootingDetails + ? Container( + padding: EdgeInsets.only(top: _interRowPadding), + width: subRowWidth, + child: _ShootingRow(details), + ) + : SizedBox.shrink(), + ); + + Widget _buildDuoShootingRow(double subRowWidth, bool hasShootingDetails) => AnimatedSwitcher( + duration: Durations.fullscreenOverlayChangeAnimation, + switchInCurve: Curves.easeInOutCubic, + switchOutCurve: Curves.easeInOutCubic, + transitionBuilder: (child, animation) => FadeTransition( + opacity: animation, + child: child, + ), + child: hasShootingDetails + ? Container( + width: subRowWidth, + child: _ShootingRow(details), + ) + : SizedBox.shrink(), + ); + + static Widget _soloTransition(Widget child, Animation animation) => FadeTransition( + opacity: animation, + child: SizeTransition( + axisAlignment: 1, + sizeFactor: animation, + child: child, + ), + ); } class _LocationRow extends AnimatedWidget { @@ -228,7 +272,7 @@ class _DateRow extends StatelessWidget { @override Widget build(BuildContext context) { final date = entry.bestDate; - final dateText = date != null ? '${DateFormat.yMMMd().format(date)} • ${DateFormat.Hm().format(date)}' : Constants.unknown; + final dateText = date != null ? '${DateFormat.yMMMd().format(date)} • ${DateFormat.Hm().format(date)}' : Constants.infoUnknown; return Row( children: [ DecoratedIcon(AIcons.date, shadows: [Constants.embossShadow], size: _iconSize), @@ -251,10 +295,10 @@ class _ShootingRow extends StatelessWidget { children: [ DecoratedIcon(AIcons.shooting, shadows: [Constants.embossShadow], size: _iconSize), SizedBox(width: _iconPadding), - Expanded(child: Text(details.aperture, strutStyle: Constants.overflowStrutStyle)), - Expanded(child: Text(details.exposureTime, strutStyle: Constants.overflowStrutStyle)), - Expanded(child: Text(details.focalLength, strutStyle: Constants.overflowStrutStyle)), - Expanded(child: Text(details.iso, strutStyle: Constants.overflowStrutStyle)), + Expanded(child: Text(details.aperture ?? Constants.overlayUnknown, strutStyle: Constants.overflowStrutStyle)), + Expanded(child: Text(details.exposureTime ?? Constants.overlayUnknown, strutStyle: Constants.overflowStrutStyle)), + Expanded(child: Text(details.focalLength ?? Constants.overlayUnknown, strutStyle: Constants.overflowStrutStyle)), + Expanded(child: Text(details.iso ?? Constants.overlayUnknown, strutStyle: Constants.overflowStrutStyle)), ], ); }