overlay: fixed partial shooting details, added layout change animation

This commit is contained in:
Thibault Deckers 2020-11-10 22:22:43 +09:00
parent 02095dfb56
commit 6a8122e456
5 changed files with 75 additions and 29 deletions

View file

@ -114,18 +114,18 @@ class OverlayMetadata {
this.exposureTime, this.exposureTime,
this.focalLength, this.focalLength,
this.iso, this.iso,
}) : aperture = aperture.replaceFirst('f', 'ƒ'); }) : aperture = aperture?.replaceFirst('f', 'ƒ');
factory OverlayMetadata.fromMap(Map map) { factory OverlayMetadata.fromMap(Map map) {
return OverlayMetadata( return OverlayMetadata(
aperture: map['aperture'] ?? '', aperture: map['aperture'],
exposureTime: map['exposureTime'] ?? '', exposureTime: map['exposureTime'],
focalLength: map['focalLength'] ?? '', focalLength: map['focalLength'],
iso: map['iso'] ?? '', 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 @override
String toString() { String toString() {

View file

@ -18,7 +18,8 @@ class Constants {
offset: Offset(0.5, 1.0), 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); static const pointNemo = Tuple2(-48.876667, -123.393333);

View file

@ -27,6 +27,7 @@ class Durations {
// fullscreen animations // fullscreen animations
static const fullscreenPageAnimation = Duration(milliseconds: 300); static const fullscreenPageAnimation = Duration(milliseconds: 300);
static const fullscreenOverlayAnimation = Duration(milliseconds: 200); static const fullscreenOverlayAnimation = Duration(milliseconds: 200);
static const fullscreenOverlayChangeAnimation = Duration(milliseconds: 150);
// info // info
static const mapStyleSwitchAnimation = Duration(milliseconds: 300); static const mapStyleSwitchAnimation = Duration(milliseconds: 300);

View file

@ -29,7 +29,7 @@ class BasicSection extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final date = entry.bestDate; 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 showMegaPixels = entry.isPhoto && entry.megaPixels != null && entry.megaPixels > 0;
final resolutionText = '${entry.resolutionText}${showMegaPixels ? ' (${entry.megaPixels} MP)' : ''}'; final resolutionText = '${entry.resolutionText}${showMegaPixels ? ' (${entry.megaPixels} MP)' : ''}';
@ -37,12 +37,12 @@ class BasicSection extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
InfoRowGroup({ InfoRowGroup({
'Title': entry.bestTitle ?? Constants.unknown, 'Title': entry.bestTitle ?? Constants.infoUnknown,
'Date': dateText, 'Date': dateText,
if (entry.isVideo) ..._buildVideoRows(), if (entry.isVideo) ..._buildVideoRows(),
if (!entry.isSvg) 'Resolution': resolutionText, if (!entry.isSvg) 'Resolution': resolutionText,
'Size': entry.sizeBytes != null ? formatFilesize(entry.sizeBytes) : Constants.unknown, 'Size': entry.sizeBytes != null ? formatFilesize(entry.sizeBytes) : Constants.infoUnknown,
'URI': entry.uri ?? Constants.unknown, 'URI': entry.uri ?? Constants.infoUnknown,
if (entry.path != null) 'Path': entry.path, if (entry.path != null) 'Path': entry.path,
}), }),
_buildChips(), _buildChips(),

View file

@ -6,6 +6,7 @@ import 'package:aves/model/settings/coordinate_format.dart';
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/metadata_service.dart'; import 'package:aves/services/metadata_service.dart';
import 'package:aves/utils/constants.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/fx/blurred.dart';
import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/fullscreen/overlay/common.dart'; import 'package:aves/widgets/fullscreen/overlay/common.dart';
@ -150,25 +151,21 @@ class _FullscreenBottomOverlayContent extends AnimatedWidget {
final positionTitle = [ final positionTitle = [
if (position != null) position, if (position != null) position,
if (entry.bestTitle != null) entry.bestTitle, if (entry.bestTitle != null) entry.bestTitle,
].join(' '); ].join(' '); // em dash
final hasShootingDetails = details != null && !details.isEmpty && settings.showOverlayShootingDetails; final hasShootingDetails = details != null && !details.isEmpty && settings.showOverlayShootingDetails;
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (positionTitle.isNotEmpty) Text(positionTitle, strutStyle: Constants.overflowStrutStyle), if (positionTitle.isNotEmpty) Text(positionTitle, strutStyle: Constants.overflowStrutStyle),
if (entry.hasGps) _buildSoloLocationRow(),
Container(
padding: EdgeInsets.only(top: _interRowPadding),
child: _LocationRow(entry: entry),
),
if (twoColumns) if (twoColumns)
Padding( Padding(
padding: EdgeInsets.only(top: _interRowPadding), padding: EdgeInsets.only(top: _interRowPadding),
child: Row( child: Row(
children: [ children: [
Container(width: subRowWidth, child: _DateRow(entry)), 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, width: subRowWidth,
child: _DateRow(entry), child: _DateRow(entry),
), ),
if (hasShootingDetails) _buildSoloShootingRow(subRowWidth, hasShootingDetails),
Container(
padding: EdgeInsets.only(top: _interRowPadding),
width: subRowWidth,
child: _ShootingRow(details),
),
], ],
], ],
); );
@ -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<double> animation) => FadeTransition(
opacity: animation,
child: SizeTransition(
axisAlignment: 1,
sizeFactor: animation,
child: child,
),
);
} }
class _LocationRow extends AnimatedWidget { class _LocationRow extends AnimatedWidget {
@ -228,7 +272,7 @@ class _DateRow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final date = entry.bestDate; 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( return Row(
children: [ children: [
DecoratedIcon(AIcons.date, shadows: [Constants.embossShadow], size: _iconSize), DecoratedIcon(AIcons.date, shadows: [Constants.embossShadow], size: _iconSize),
@ -251,10 +295,10 @@ class _ShootingRow extends StatelessWidget {
children: [ children: [
DecoratedIcon(AIcons.shooting, shadows: [Constants.embossShadow], size: _iconSize), DecoratedIcon(AIcons.shooting, shadows: [Constants.embossShadow], size: _iconSize),
SizedBox(width: _iconPadding), SizedBox(width: _iconPadding),
Expanded(child: Text(details.aperture, strutStyle: Constants.overflowStrutStyle)), Expanded(child: Text(details.aperture ?? Constants.overlayUnknown, strutStyle: Constants.overflowStrutStyle)),
Expanded(child: Text(details.exposureTime, strutStyle: Constants.overflowStrutStyle)), Expanded(child: Text(details.exposureTime ?? Constants.overlayUnknown, strutStyle: Constants.overflowStrutStyle)),
Expanded(child: Text(details.focalLength, strutStyle: Constants.overflowStrutStyle)), Expanded(child: Text(details.focalLength ?? Constants.overlayUnknown, strutStyle: Constants.overflowStrutStyle)),
Expanded(child: Text(details.iso, strutStyle: Constants.overflowStrutStyle)), Expanded(child: Text(details.iso ?? Constants.overlayUnknown, strutStyle: Constants.overflowStrutStyle)),
], ],
); );
} }