overlay: fixed partial shooting details, added layout change animation
This commit is contained in:
parent
02095dfb56
commit
6a8122e456
5 changed files with 75 additions and 29 deletions
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue