#406 viewer: show rating & tags on overlay

This commit is contained in:
Thibault Deckers 2022-11-29 16:54:26 +01:00
parent eb0e51ed57
commit 92639a7066
20 changed files with 326 additions and 189 deletions

View file

@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
### Added
- Viewer: optionally show rating & tags on overlay
- Viewer: long press on rating quick action for quicker rating
- Search: missing address filter
- Lithuanian translation (thanks Gediminas Murauskas)

View file

@ -717,6 +717,7 @@
"settingsViewerShowMinimap": "Show minimap",
"settingsViewerShowInformation": "Show information",
"settingsViewerShowInformationSubtitle": "Show title, date, location, etc.",
"settingsViewerShowRatingTags": "Show rating & tags",
"settingsViewerShowShootingDetails": "Show shooting details",
"settingsViewerShowOverlayThumbnails": "Show thumbnails",
"settingsViewerEnableOverlayBlurEffect": "Blur effect",

View file

@ -8,6 +8,7 @@ import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/services/metadata/svg_metadata_service.dart';
import 'package:aves/theme/colors.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/viewer/info/metadata/metadata_dir.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
@ -109,7 +110,10 @@ extension ExtraAvesEntryInfo on AvesEntry {
for (final stream in knownStreams) {
final index = (stream[Keys.index] ?? 0) + 1;
final typeText = getTypeText(stream);
final dirName = 'Stream ${index.toString().padLeft(indexDigits, '0')}$typeText';
final dirName = [
'Stream ${index.toString().padLeft(indexDigits, '0')}',
typeText,
].join(Constants.separator);
final formattedStreamTags = VideoMetadataFormatter.formatInfo(stream);
if (formattedStreamTags.isNotEmpty) {
final color = colors.fromString(typeText);

View file

@ -79,6 +79,7 @@ class SettingsDefaults {
static const showOverlayOnOpening = true;
static const showOverlayMinimap = false;
static const showOverlayInfo = true;
static const showOverlayRatingTags = false;
static const showOverlayShootingDetails = false;
static const showOverlayThumbnailPreview = false;
static const viewerGestureSideTapNext = false;

View file

@ -103,6 +103,7 @@ class Settings extends ChangeNotifier {
static const showOverlayOnOpeningKey = 'show_overlay_on_opening';
static const showOverlayMinimapKey = 'show_overlay_minimap';
static const showOverlayInfoKey = 'show_overlay_info';
static const showOverlayRatingTagsKey = 'show_overlay_rating_tags';
static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details';
static const showOverlayThumbnailPreviewKey = 'show_overlay_thumbnail_preview';
static const viewerGestureSideTapNextKey = 'viewer_gesture_side_tap_next';
@ -507,6 +508,10 @@ class Settings extends ChangeNotifier {
set showOverlayInfo(bool newValue) => setAndNotify(showOverlayInfoKey, newValue);
bool get showOverlayRatingTags => getBool(showOverlayRatingTagsKey) ?? SettingsDefaults.showOverlayRatingTags;
set showOverlayRatingTags(bool newValue) => setAndNotify(showOverlayRatingTagsKey, newValue);
bool get showOverlayShootingDetails => getBool(showOverlayShootingDetailsKey) ?? SettingsDefaults.showOverlayShootingDetails;
set showOverlayShootingDetails(bool newValue) => setAndNotify(showOverlayShootingDetailsKey, newValue);
@ -932,6 +937,7 @@ class Settings extends ChangeNotifier {
case showOverlayOnOpeningKey:
case showOverlayMinimapKey:
case showOverlayInfoKey:
case showOverlayRatingTagsKey:
case showOverlayShootingDetailsKey:
case showOverlayThumbnailPreviewKey:
case viewerGestureSideTapNextKey:

View file

@ -1,5 +1,6 @@
import 'package:aves/model/entry.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/utils/constants.dart';
import 'package:flutter/services.dart';
abstract class EmbeddedDataService {
@ -37,7 +38,7 @@ class PlatformEmbeddedDataService implements EmbeddedDataService {
'mimeType': entry.mimeType,
'uri': entry.uri,
'sizeBytes': entry.sizeBytes,
'displayName': '${entry.bestTitle} • Video',
'displayName': ['${entry.bestTitle}', 'Video'].join(Constants.separator),
});
if (result != null) return result as Map;
} on PlatformException catch (e, stack) {
@ -51,7 +52,7 @@ class PlatformEmbeddedDataService implements EmbeddedDataService {
try {
final result = await _platform.invokeMethod('extractVideoEmbeddedPicture', <String, dynamic>{
'uri': entry.uri,
'displayName': '${entry.bestTitle} • Cover',
'displayName': ['${entry.bestTitle}', 'Cover'].join(Constants.separator),
});
if (result != null) return result as Map;
} on PlatformException catch (e, stack) {
@ -67,7 +68,7 @@ class PlatformEmbeddedDataService implements EmbeddedDataService {
'mimeType': entry.mimeType,
'uri': entry.uri,
'sizeBytes': entry.sizeBytes,
'displayName': '${entry.bestTitle}$props',
'displayName': ['${entry.bestTitle}', '$props'].join(Constants.separator),
'propPath': props,
'propMimeType': propMimeType,
});

View file

@ -1,10 +1,14 @@
import 'package:aves/utils/constants.dart';
import 'package:intl/intl.dart';
String formatDay(DateTime date, String locale) => DateFormat.yMMMd(locale).format(date);
String formatTime(DateTime date, String locale, bool use24hour) => (use24hour ? DateFormat.Hm(locale) : DateFormat.jm(locale)).format(date);
String formatDateTime(DateTime date, String locale, bool use24hour) => '${formatDay(date, locale)}${formatTime(date, locale, use24hour)}';
String formatDateTime(DateTime date, String locale, bool use24hour) => [
formatDay(date, locale),
formatTime(date, locale, use24hour),
].join(Constants.separator);
String formatFriendlyDuration(Duration d) {
final seconds = (d.inSeconds.remainder(Duration.secondsPerMinute)).toString().padLeft(2, '0');

View file

@ -5,6 +5,8 @@ import 'package:flutter/material.dart';
import 'package:latlong2/latlong.dart';
class Constants {
static const separator = '';
// `Color(0x00FFFFFF)` is different from `Color(0x00000000)` (or `Colors.transparent`)
// when used in gradients or lerping to it
static const transparentWhite = Color(0x00FFFFFF);

View file

@ -138,7 +138,7 @@ class _RandomTextSpanHighlighterState extends State<_RandomTextSpanHighlighter>
TextSpan(
children: [
...widget.spans.expandIndexed((i, v) => [
if (i != 0) const TextSpan(text: ''),
if (i != 0) const TextSpan(text: Constants.separator),
TextSpan(text: v, style: i == _highlightedIndex ? _animatedStyle.value : _baseStyle),
])
],

View file

@ -30,6 +30,18 @@ class ViewerOverlayPage extends StatelessWidget {
title: context.l10n.settingsViewerShowInformation,
subtitle: context.l10n.settingsViewerShowInformationSubtitle,
),
Selector<Settings, Tuple2<bool, bool>>(
selector: (context, s) => Tuple2(s.showOverlayInfo, s.showOverlayRatingTags),
builder: (context, s, child) {
final showInfo = s.item1;
final current = s.item2;
return SwitchListTile(
value: current,
onChanged: showInfo ? (v) => settings.showOverlayRatingTags = v : null,
title: Text(context.l10n.settingsViewerShowRatingTags),
);
},
),
Selector<Settings, Tuple2<bool, bool>>(
selector: (context, s) => Tuple2(s.showOverlayInfo, s.showOverlayShootingDetails),
builder: (context, s, child) {

View file

@ -0,0 +1,44 @@
import 'package:aves/model/entry.dart';
import 'package:aves/theme/format.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/viewer/multipage/controller.dart';
import 'package:aves/widgets/viewer/overlay/details/details.dart';
import 'package:decorated_icon/decorated_icon.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class OverlayDateRow extends StatelessWidget {
final AvesEntry entry;
final MultiPageController? multiPageController;
const OverlayDateRow({
super.key,
required this.entry,
required this.multiPageController,
});
@override
Widget build(BuildContext context) {
final locale = context.l10n.localeName;
final use24hour = context.select<MediaQueryData, bool>((v) => v.alwaysUse24HourFormat);
final date = entry.bestDate;
final dateText = date != null ? formatDateTime(date, locale, use24hour) : Constants.overlayUnknown;
final resolutionText = entry.isSvg
? entry.aspectRatioText
: entry.isSized
? entry.resolutionText
: '';
return Row(
children: [
DecoratedIcon(AIcons.date, size: ViewerDetailOverlayContent.iconSize, shadows: ViewerDetailOverlayContent.shadows(context)),
const SizedBox(width: ViewerDetailOverlayContent.iconPadding),
Expanded(flex: 3, child: Text(dateText, strutStyle: Constants.overflowStrutStyle)),
Expanded(flex: 2, child: Text(resolutionText, strutStyle: Constants.overflowStrutStyle)),
],
);
}
}

View file

@ -2,30 +2,21 @@ import 'dart:math';
import 'package:aves/model/entry.dart';
import 'package:aves/model/metadata/overlay.dart';
import 'package:aves/model/multipage.dart';
import 'package:aves/model/settings/enums/coordinate_format.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/format.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/viewer/multipage/controller.dart';
import 'package:aves/widgets/viewer/overlay/details/date.dart';
import 'package:aves/widgets/viewer/overlay/details/location.dart';
import 'package:aves/widgets/viewer/overlay/details/position_title.dart';
import 'package:aves/widgets/viewer/overlay/details/rating_tags.dart';
import 'package:aves/widgets/viewer/overlay/details/shooting.dart';
import 'package:aves/widgets/viewer/page_entry_builder.dart';
import 'package:decorated_icon/decorated_icon.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
const double _iconPadding = 8.0;
const double _iconSize = 16.0;
const double _interRowPadding = 2.0;
const double _subRowMinWidth = 300.0;
List<Shadow>? _shadows(BuildContext context) => Theme.of(context).brightness == Brightness.dark ? Constants.embossShadows : null;
class ViewerDetailOverlay extends StatefulWidget {
final List<AvesEntry> entries;
final int index;
@ -124,7 +115,13 @@ class ViewerDetailOverlayContent extends StatelessWidget {
final double availableWidth;
final MultiPageController? multiPageController;
static const double _interRowPadding = 2.0;
static const double _subRowMinWidth = 300.0;
static const padding = EdgeInsets.symmetric(vertical: 4, horizontal: 8);
static const double iconPadding = 8.0;
static const double iconSize = 16.0;
static List<Shadow>? shadows(BuildContext context) => Theme.of(context).brightness == Brightness.dark ? Constants.embossShadows : null;
const ViewerDetailOverlayContent({
super.key,
@ -138,15 +135,16 @@ class ViewerDetailOverlayContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
final infoMaxWidth = availableWidth - padding.horizontal;
final showRatingTags = settings.showOverlayRatingTags;
final showShooting = settings.showOverlayShootingDetails;
return AnimatedBuilder(
animation: pageEntry.metadataChangeNotifier,
builder: (context, child) {
final positionTitle = _PositionTitleRow(entry: pageEntry, collectionPosition: position, multiPageController: multiPageController);
final positionTitle = OverlayPositionTitleRow(entry: pageEntry, collectionPosition: position, multiPageController: multiPageController);
return DefaultTextStyle(
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
shadows: _shadows(context),
shadows: shadows(context),
),
softWrap: false,
overflow: TextOverflow.fade,
@ -185,6 +183,9 @@ class ViewerDetailOverlayContent extends StatelessWidget {
if (!collapsedLocation) {
rows.add(_buildLocationFullRow(context));
}
if (showRatingTags) {
rows.add(_buildRatingTagsFullRow(context));
}
return Column(
mainAxisSize: MainAxisSize.min,
@ -201,18 +202,24 @@ class ViewerDetailOverlayContent extends StatelessWidget {
Widget _buildDateSubRow(double subRowWidth) => SizedBox(
width: subRowWidth,
child: _DateRow(
child: OverlayDateRow(
entry: pageEntry,
multiPageController: multiPageController,
),
);
Widget _buildRatingTagsFullRow(BuildContext context) => _buildFullRowSwitcher(
context: context,
visible: pageEntry.rating != 0 || pageEntry.tags.isNotEmpty,
builder: (context) => OverlayRatingTagsRow(entry: pageEntry),
);
Widget _buildShootingFullRow(BuildContext context, double subRowWidth) => _buildFullRowSwitcher(
context: context,
visible: details != null && details!.isNotEmpty,
builder: (context) => SizedBox(
width: subRowWidth,
child: _ShootingRow(details!),
child: OverlayShootingRow(details: details!),
),
);
@ -220,20 +227,20 @@ class ViewerDetailOverlayContent extends StatelessWidget {
context: context,
subRowWidth: subRowWidth,
visible: details != null && details!.isNotEmpty,
builder: (context) => _ShootingRow(details!),
builder: (context) => OverlayShootingRow(details: details!),
);
Widget _buildLocationFullRow(BuildContext context) => _buildFullRowSwitcher(
context: context,
visible: pageEntry.hasGps,
builder: (context) => _LocationRow(entry: pageEntry),
builder: (context) => OverlayLocationRow(entry: pageEntry),
);
Widget _buildLocationSubRow(BuildContext context, double subRowWidth) => _buildSubRowSwitcher(
context: context,
subRowWidth: subRowWidth,
visible: pageEntry.hasGps,
builder: (context) => _LocationRow(entry: pageEntry),
builder: (context) => OverlayLocationRow(entry: pageEntry),
);
Widget _buildSubRowSwitcher({
@ -283,146 +290,3 @@ class ViewerDetailOverlayContent extends StatelessWidget {
: const SizedBox(),
);
}
class _LocationRow extends AnimatedWidget {
final AvesEntry entry;
_LocationRow({
required this.entry,
}) : super(listenable: entry.addressChangeNotifier);
@override
Widget build(BuildContext context) {
late final String location;
if (entry.hasAddress) {
location = entry.shortAddress;
} else {
final latLng = entry.latLng;
if (latLng != null) {
location = settings.coordinateFormat.format(context.l10n, latLng);
} else {
location = '';
}
}
return Row(
children: [
DecoratedIcon(AIcons.location, size: _iconSize, shadows: _shadows(context)),
const SizedBox(width: _iconPadding),
Expanded(child: Text(location, strutStyle: Constants.overflowStrutStyle)),
],
);
}
}
class _PositionTitleRow extends StatelessWidget {
final AvesEntry entry;
final String? collectionPosition;
final MultiPageController? multiPageController;
const _PositionTitleRow({
required this.entry,
required this.collectionPosition,
required this.multiPageController,
});
String? get title => entry.bestTitle;
bool get isNotEmpty => collectionPosition != null || multiPageController != null || title != null;
static const separator = '';
@override
Widget build(BuildContext context) {
Text toText({String? pagePosition}) => Text(
[
if (collectionPosition != null) collectionPosition,
if (pagePosition != null) pagePosition,
if (title != null) '${Constants.fsi}$title${Constants.pdi}',
].join(separator),
strutStyle: Constants.overflowStrutStyle);
if (multiPageController == null) return toText();
return StreamBuilder<MultiPageInfo?>(
stream: multiPageController!.infoStream,
builder: (context, snapshot) {
final multiPageInfo = multiPageController!.info;
String? pagePosition;
if (multiPageInfo != null) {
// page count may be 0 when we know an entry to have multiple pages
// but fail to get information about these pages
final pageCount = multiPageInfo.pageCount;
if (pageCount > 0) {
final page = multiPageInfo.getById(entry.pageId ?? entry.id) ?? multiPageInfo.defaultPage;
pagePosition = '${(page?.index ?? 0) + 1}/$pageCount';
}
}
return toText(pagePosition: pagePosition);
},
);
}
}
class _DateRow extends StatelessWidget {
final AvesEntry entry;
final MultiPageController? multiPageController;
const _DateRow({
required this.entry,
required this.multiPageController,
});
@override
Widget build(BuildContext context) {
final locale = context.l10n.localeName;
final use24hour = context.select<MediaQueryData, bool>((v) => v.alwaysUse24HourFormat);
final date = entry.bestDate;
final dateText = date != null ? formatDateTime(date, locale, use24hour) : Constants.overlayUnknown;
final resolutionText = entry.isSvg
? entry.aspectRatioText
: entry.isSized
? entry.resolutionText
: '';
return Row(
children: [
DecoratedIcon(AIcons.date, size: _iconSize, shadows: _shadows(context)),
const SizedBox(width: _iconPadding),
Expanded(flex: 3, child: Text(dateText, strutStyle: Constants.overflowStrutStyle)),
Expanded(flex: 2, child: Text(resolutionText, strutStyle: Constants.overflowStrutStyle)),
],
);
}
}
class _ShootingRow extends StatelessWidget {
final OverlayMetadata details;
const _ShootingRow(this.details);
@override
Widget build(BuildContext context) {
final locale = context.l10n.localeName;
final aperture = details.aperture;
final apertureText = aperture != null ? 'ƒ/${NumberFormat('0.0', locale).format(aperture)}' : Constants.overlayUnknown;
final focalLength = details.focalLength;
final focalLengthText = focalLength != null ? context.l10n.focalLength(NumberFormat('0.#', locale).format(focalLength)) : Constants.overlayUnknown;
final iso = details.iso;
final isoText = iso != null ? 'ISO$iso' : Constants.overlayUnknown;
return Row(
children: [
DecoratedIcon(AIcons.shooting, size: _iconSize, shadows: _shadows(context)),
const SizedBox(width: _iconPadding),
Expanded(child: Text(apertureText, strutStyle: Constants.overflowStrutStyle)),
Expanded(child: Text(details.exposureTime ?? Constants.overlayUnknown, strutStyle: Constants.overflowStrutStyle)),
Expanded(child: Text(focalLengthText, strutStyle: Constants.overflowStrutStyle)),
Expanded(child: Text(isoText, strutStyle: Constants.overflowStrutStyle)),
],
);
}
}

View file

@ -0,0 +1,40 @@
import 'package:aves/model/entry.dart';
import 'package:aves/model/settings/enums/coordinate_format.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/viewer/overlay/details/details.dart';
import 'package:decorated_icon/decorated_icon.dart';
import 'package:flutter/material.dart';
class OverlayLocationRow extends AnimatedWidget {
final AvesEntry entry;
OverlayLocationRow({
super.key,
required this.entry,
}) : super(listenable: entry.addressChangeNotifier);
@override
Widget build(BuildContext context) {
late final String location;
if (entry.hasAddress) {
location = entry.shortAddress;
} else {
final latLng = entry.latLng;
if (latLng != null) {
location = settings.coordinateFormat.format(context.l10n, latLng);
} else {
location = '';
}
}
return Row(
children: [
DecoratedIcon(AIcons.location, size: ViewerDetailOverlayContent.iconSize, shadows: ViewerDetailOverlayContent.shadows(context)),
const SizedBox(width: ViewerDetailOverlayContent.iconPadding),
Expanded(child: Text(location, strutStyle: Constants.overflowStrutStyle)),
],
);
}
}

View file

@ -0,0 +1,53 @@
import 'package:aves/model/entry.dart';
import 'package:aves/model/multipage.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/viewer/multipage/controller.dart';
import 'package:flutter/material.dart';
class OverlayPositionTitleRow extends StatelessWidget {
final AvesEntry entry;
final String? collectionPosition;
final MultiPageController? multiPageController;
const OverlayPositionTitleRow({
super.key,
required this.entry,
required this.collectionPosition,
required this.multiPageController,
});
String? get title => entry.bestTitle;
bool get isNotEmpty => collectionPosition != null || multiPageController != null || title != null;
@override
Widget build(BuildContext context) {
Text toText({String? pagePosition}) => Text(
[
if (collectionPosition != null) collectionPosition,
if (pagePosition != null) pagePosition,
if (title != null) '${Constants.fsi}$title${Constants.pdi}',
].join(Constants.separator),
strutStyle: Constants.overflowStrutStyle);
if (multiPageController == null) return toText();
return StreamBuilder<MultiPageInfo?>(
stream: multiPageController!.infoStream,
builder: (context, snapshot) {
final multiPageInfo = multiPageController!.info;
String? pagePosition;
if (multiPageInfo != null) {
// page count may be 0 when we know an entry to have multiple pages
// but fail to get information about these pages
final pageCount = multiPageInfo.pageCount;
if (pageCount > 0) {
final page = multiPageInfo.getById(entry.pageId ?? entry.id) ?? multiPageInfo.defaultPage;
pagePosition = '${(page?.index ?? 0) + 1}/$pageCount';
}
}
return toText(pagePosition: pagePosition);
},
);
}
}

View file

@ -0,0 +1,50 @@
import 'package:aves/model/entry.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/viewer/overlay/details/details.dart';
import 'package:decorated_icon/decorated_icon.dart';
import 'package:flutter/material.dart';
class OverlayRatingTagsRow extends AnimatedWidget {
final AvesEntry entry;
OverlayRatingTagsRow({
super.key,
required this.entry,
}) : super(listenable: entry.metadataChangeNotifier);
@override
Widget build(BuildContext context) {
final String ratingString;
final rating = entry.rating.clamp(-1, 5);
switch (rating) {
case -1:
ratingString = context.l10n.filterRatingRejectedLabel;
break;
case 0:
ratingString = '';
break;
default:
ratingString = '${'' * rating}${'' * (5 - rating)}';
break;
}
final tags = entry.tags.join(Constants.separator);
final hasTags = tags.isNotEmpty;
return Row(
children: [
if (ratingString.isNotEmpty) ...[
Text(ratingString),
if (hasTags) const Text(Constants.separator),
],
if (hasTags) ...[
DecoratedIcon(AIcons.tag, size: ViewerDetailOverlayContent.iconSize, shadows: ViewerDetailOverlayContent.shadows(context)),
const SizedBox(width: ViewerDetailOverlayContent.iconPadding),
Expanded(child: Text(tags)),
],
],
);
}
}

View file

@ -0,0 +1,39 @@
import 'package:aves/model/metadata/overlay.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/viewer/overlay/details/details.dart';
import 'package:decorated_icon/decorated_icon.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class OverlayShootingRow extends StatelessWidget {
final OverlayMetadata details;
const OverlayShootingRow({super.key, required this.details});
@override
Widget build(BuildContext context) {
final locale = context.l10n.localeName;
final aperture = details.aperture;
final apertureText = aperture != null ? 'ƒ/${NumberFormat('0.0', locale).format(aperture)}' : Constants.overlayUnknown;
final focalLength = details.focalLength;
final focalLengthText = focalLength != null ? context.l10n.focalLength(NumberFormat('0.#', locale).format(focalLength)) : Constants.overlayUnknown;
final iso = details.iso;
final isoText = iso != null ? 'ISO$iso' : Constants.overlayUnknown;
return Row(
children: [
DecoratedIcon(AIcons.shooting, size: ViewerDetailOverlayContent.iconSize, shadows: ViewerDetailOverlayContent.shadows(context)),
const SizedBox(width: ViewerDetailOverlayContent.iconPadding),
Expanded(child: Text(apertureText, strutStyle: Constants.overflowStrutStyle)),
Expanded(child: Text(details.exposureTime ?? Constants.overlayUnknown, strutStyle: Constants.overflowStrutStyle)),
Expanded(child: Text(focalLengthText, strutStyle: Constants.overflowStrutStyle)),
Expanded(child: Text(isoText, strutStyle: Constants.overflowStrutStyle)),
],
);
}
}

View file

@ -3,7 +3,7 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/themes.dart';
import 'package:aves/widgets/common/fx/blurred.dart';
import 'package:aves/widgets/viewer/multipage/conductor.dart';
import 'package:aves/widgets/viewer/overlay/details.dart';
import 'package:aves/widgets/viewer/overlay/details/details.dart';
import 'package:aves/widgets/viewer/overlay/minimap.dart';
import 'package:aves/widgets/viewer/page_entry_builder.dart';
import 'package:aves/widgets/viewer/visual/conductor.dart';

View file

@ -2,6 +2,7 @@ import 'package:aves/app_flavor.dart';
import 'package:aves/model/settings/defaults.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/basic/markdown_container.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_logo.dart';
@ -155,7 +156,7 @@ class _WelcomePageState extends State<WelcomePage> {
value: settings.isInstalledAppAccessAllowed,
onChanged: (v) => setState(() => settings.isInstalledAppAccessAllowed = v),
title: Text(l10n.settingsAllowInstalledAppAccess),
subtitle: Text([l10n.welcomeOptional, l10n.settingsAllowInstalledAppAccessSubtitle].join('')),
subtitle: Text([l10n.welcomeOptional, l10n.settingsAllowInstalledAppAccessSubtitle].join(Constants.separator)),
contentPadding: contentPadding,
),
if (canEnableErrorReporting)

View file

@ -46,6 +46,7 @@ Future<void> configureAndLaunch() async {
..showOverlayOnOpening = true
..showOverlayMinimap = false
..showOverlayInfo = true
..showOverlayRatingTags = false
..showOverlayShootingDetails = false
..showOverlayThumbnailPreview = false
..viewerUseCutout = true

View file

@ -443,6 +443,7 @@
"settingsViewerShowMinimap",
"settingsViewerShowInformation",
"settingsViewerShowInformationSubtitle",
"settingsViewerShowRatingTags",
"settingsViewerShowShootingDetails",
"settingsViewerShowOverlayThumbnails",
"settingsViewerEnableOverlayBlurEffect",
@ -596,17 +597,20 @@
"subtitlePositionBottom",
"widgetDisplayedItemRandom",
"widgetDisplayedItemMostRecent",
"settingsViewerShowRatingTags",
"settingsSubtitleThemeTextPositionTile",
"settingsSubtitleThemeTextPositionDialogTitle",
"settingsWidgetDisplayedItem"
],
"el": [
"filterNoAddressLabel"
"filterNoAddressLabel",
"settingsViewerShowRatingTags"
],
"es": [
"filterNoAddressLabel"
"filterNoAddressLabel",
"settingsViewerShowRatingTags"
],
"fa": [
@ -1053,6 +1057,7 @@
"settingsViewerShowMinimap",
"settingsViewerShowInformation",
"settingsViewerShowInformationSubtitle",
"settingsViewerShowRatingTags",
"settingsViewerShowShootingDetails",
"settingsViewerShowOverlayThumbnails",
"settingsViewerEnableOverlayBlurEffect",
@ -1201,7 +1206,8 @@
],
"fr": [
"filterNoAddressLabel"
"filterNoAddressLabel",
"settingsViewerShowRatingTags"
],
"gl": [
@ -1515,6 +1521,7 @@
"settingsViewerShowMinimap",
"settingsViewerShowInformation",
"settingsViewerShowInformationSubtitle",
"settingsViewerShowRatingTags",
"settingsViewerShowShootingDetails",
"settingsViewerShowOverlayThumbnails",
"settingsViewerEnableOverlayBlurEffect",
@ -1669,13 +1676,15 @@
"subtitlePositionBottom",
"widgetDisplayedItemRandom",
"widgetDisplayedItemMostRecent",
"settingsViewerShowRatingTags",
"settingsSubtitleThemeTextPositionTile",
"settingsSubtitleThemeTextPositionDialogTitle",
"settingsWidgetDisplayedItem"
],
"it": [
"filterNoAddressLabel"
"filterNoAddressLabel",
"settingsViewerShowRatingTags"
],
"ja": [
@ -1686,17 +1695,20 @@
"subtitlePositionBottom",
"widgetDisplayedItemRandom",
"widgetDisplayedItemMostRecent",
"settingsViewerShowRatingTags",
"settingsSubtitleThemeTextPositionTile",
"settingsSubtitleThemeTextPositionDialogTitle",
"settingsWidgetDisplayedItem"
],
"ko": [
"filterNoAddressLabel"
"filterNoAddressLabel",
"settingsViewerShowRatingTags"
],
"lt": [
"filterNoAddressLabel"
"filterNoAddressLabel",
"settingsViewerShowRatingTags"
],
"nb": [
@ -1788,6 +1800,7 @@
"settingsViewerOverlayTile",
"settingsViewerOverlayPageTitle",
"settingsViewerShowOverlayOnOpening",
"settingsViewerShowRatingTags",
"settingsViewerShowShootingDetails",
"settingsViewerEnableOverlayBlurEffect",
"settingsSlideshowShuffle",
@ -1828,6 +1841,7 @@
"subtitlePositionBottom",
"widgetDisplayedItemRandom",
"widgetDisplayedItemMostRecent",
"settingsViewerShowRatingTags",
"settingsSubtitleThemeTextPositionTile",
"settingsSubtitleThemeTextPositionDialogTitle",
"settingsWidgetDisplayedItem"
@ -2184,6 +2198,7 @@
"settingsViewerShowMinimap",
"settingsViewerShowInformation",
"settingsViewerShowInformationSubtitle",
"settingsViewerShowRatingTags",
"settingsViewerShowShootingDetails",
"settingsViewerShowOverlayThumbnails",
"settingsViewerEnableOverlayBlurEffect",
@ -2338,17 +2353,20 @@
"subtitlePositionBottom",
"widgetDisplayedItemRandom",
"widgetDisplayedItemMostRecent",
"settingsViewerShowRatingTags",
"settingsSubtitleThemeTextPositionTile",
"settingsSubtitleThemeTextPositionDialogTitle",
"settingsWidgetDisplayedItem"
],
"ro": [
"filterNoAddressLabel"
"filterNoAddressLabel",
"settingsViewerShowRatingTags"
],
"ru": [
"filterNoAddressLabel"
"filterNoAddressLabel",
"settingsViewerShowRatingTags"
],
"th": [
@ -2575,6 +2593,7 @@
"settingsViewerShowMinimap",
"settingsViewerShowInformation",
"settingsViewerShowInformationSubtitle",
"settingsViewerShowRatingTags",
"settingsViewerShowShootingDetails",
"settingsViewerShowOverlayThumbnails",
"settingsViewerEnableOverlayBlurEffect",
@ -2723,20 +2742,14 @@
],
"tr": [
"entryInfoActionExportMetadata",
"filterNoAddressLabel",
"subtitlePositionTop",
"subtitlePositionBottom",
"widgetDisplayedItemRandom",
"widgetDisplayedItemMostRecent",
"settingsSubtitleThemeTextPositionTile",
"settingsSubtitleThemeTextPositionDialogTitle",
"settingsWidgetDisplayedItem"
"settingsViewerShowRatingTags"
],
"zh": [
"filterNoAddressLabel",
"aboutLicensesFlutterPackagesSectionTitle",
"aboutLicensesDartPackagesSectionTitle"
"aboutLicensesDartPackagesSectionTitle",
"settingsViewerShowRatingTags"
]
}