#706 viewer: histogram
This commit is contained in:
parent
99dd7ec0ff
commit
05d4d01ef7
27 changed files with 651 additions and 143 deletions
|
@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
|
|||
- Video: improved seek accuracy, HDR support, AV1 support, playback speed from x0.25 to x4
|
||||
- support for animated AVIF (requires rescan)
|
||||
- Collection: filtering by rating range
|
||||
- Viewer: optionally show histogram on overlay
|
||||
- About: data usage
|
||||
|
||||
### Changed
|
||||
|
|
|
@ -233,6 +233,10 @@
|
|||
"nameConflictStrategyReplace": "Replace",
|
||||
"nameConflictStrategySkip": "Skip",
|
||||
|
||||
"overlayHistogramNone": "None",
|
||||
"overlayHistogramRGB": "RGB",
|
||||
"overlayHistogramLuminance": "Luminance",
|
||||
|
||||
"subtitlePositionTop": "Top",
|
||||
"subtitlePositionBottom": "Bottom",
|
||||
|
||||
|
@ -809,6 +813,7 @@
|
|||
"settingsViewerOverlayTile": "Overlay",
|
||||
"settingsViewerOverlayPageTitle": "Overlay",
|
||||
"settingsViewerShowOverlayOnOpening": "Show on opening",
|
||||
"settingsViewerShowHistogram": "Show histogram",
|
||||
"settingsViewerShowMinimap": "Show minimap",
|
||||
"settingsViewerShowInformation": "Show information",
|
||||
"settingsViewerShowInformationSubtitle": "Show title, date, location, etc.",
|
||||
|
|
|
@ -72,6 +72,7 @@ class SettingsDefaults {
|
|||
];
|
||||
static const showOverlayOnOpening = true;
|
||||
static const showOverlayMinimap = false;
|
||||
static const overlayHistogramStyle = OverlayHistogramStyle.none;
|
||||
static const showOverlayInfo = true;
|
||||
static const showOverlayDescription = false;
|
||||
static const showOverlayRatingTags = false;
|
||||
|
|
|
@ -14,6 +14,10 @@ mixin ViewerSettings on SettingsAccess {
|
|||
|
||||
set showOverlayMinimap(bool newValue) => set(SettingKeys.showOverlayMinimapKey, newValue);
|
||||
|
||||
OverlayHistogramStyle get overlayHistogramStyle => getEnumOrDefault(SettingKeys.overlayHistogramStyleKey, SettingsDefaults.overlayHistogramStyle, OverlayHistogramStyle.values);
|
||||
|
||||
set overlayHistogramStyle(OverlayHistogramStyle newValue) => set(SettingKeys.overlayHistogramStyleKey, newValue.toString());
|
||||
|
||||
bool get showOverlayInfo => getBool(SettingKeys.showOverlayInfoKey) ?? SettingsDefaults.showOverlayInfo;
|
||||
|
||||
set showOverlayInfo(bool newValue) => set(SettingKeys.showOverlayInfoKey, newValue);
|
||||
|
|
|
@ -151,6 +151,19 @@ extension ExtraSlideshowVideoPlaybackView on SlideshowVideoPlayback {
|
|||
}
|
||||
}
|
||||
|
||||
extension ExtraOverlayHistogramStyleView on OverlayHistogramStyle {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
case OverlayHistogramStyle.none:
|
||||
return context.l10n.overlayHistogramNone;
|
||||
case OverlayHistogramStyle.rgb:
|
||||
return context.l10n.overlayHistogramRGB;
|
||||
case OverlayHistogramStyle.luminance:
|
||||
return context.l10n.overlayHistogramLuminance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtraSubtitlePositionView on SubtitlePosition {
|
||||
String getName(BuildContext context) {
|
||||
switch (this) {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/view/view.dart';
|
||||
import 'package:aves/widgets/common/basic/scaffold.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/settings/common/tiles.dart';
|
||||
import 'package:aves_model/aves_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
@ -82,6 +84,14 @@ class ViewerOverlayPage extends StatelessWidget {
|
|||
onChanged: (v) => settings.showOverlayThumbnailPreview = v,
|
||||
title: context.l10n.settingsViewerShowOverlayThumbnails,
|
||||
),
|
||||
if (!useTvLayout)
|
||||
SettingsSelectionListTile<OverlayHistogramStyle>(
|
||||
values: OverlayHistogramStyle.values,
|
||||
getName: (context, v) => v.getName(context),
|
||||
selector: (context, s) => s.overlayHistogramStyle,
|
||||
onSelection: (v) => settings.overlayHistogramStyle = v,
|
||||
tileTitle: context.l10n.settingsViewerShowHistogram,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -108,3 +108,11 @@ class EntryMovedNotification extends Notification with EquatableMixin {
|
|||
|
||||
const EntryMovedNotification(this.moveType, this.entries);
|
||||
}
|
||||
|
||||
@immutable
|
||||
class FullImageLoadedNotification extends Notification {
|
||||
final AvesEntry entry;
|
||||
final ImageProvider image;
|
||||
|
||||
const FullImageLoadedNotification(this.entry, this.image);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ import 'package:aves/widgets/viewer/overlay/top.dart';
|
|||
import 'package:aves/widgets/viewer/overlay/video/video.dart';
|
||||
import 'package:aves/widgets/viewer/page_entry_builder.dart';
|
||||
import 'package:aves/widgets/viewer/video/conductor.dart';
|
||||
import 'package:aves/widgets/viewer/visual/conductor.dart';
|
||||
import 'package:aves/widgets/viewer/view/conductor.dart';
|
||||
import 'package:aves/widgets/viewer/visual/controller_mixin.dart';
|
||||
import 'package:aves_model/aves_model.dart';
|
||||
import 'package:aves_utils/aves_utils.dart';
|
||||
|
@ -512,6 +512,10 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
|||
bool _handleNotification(dynamic notification) {
|
||||
if (notification is FilterSelectedNotification) {
|
||||
_goToCollection(notification.filter);
|
||||
} else if (notification is FullImageLoadedNotification) {
|
||||
final viewStateController = context.read<ViewStateConductor>().getOrCreateController(notification.entry);
|
||||
// microtask so that listeners do not trigger during build
|
||||
scheduleMicrotask(() => viewStateController.fullImageNotifier.value = notification.image);
|
||||
} else if (notification is EntryDeletedNotification) {
|
||||
_onEntryRemoved(context, notification.entries);
|
||||
} else if (notification is EntryMovedNotification) {
|
||||
|
|
202
lib/widgets/viewer/overlay/histogram.dart
Normal file
202
lib/widgets/viewer/overlay/histogram.dart
Normal file
|
@ -0,0 +1,202 @@
|
|||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/widgets/viewer/overlay/top.dart';
|
||||
import 'package:aves_model/aves_model.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ImageHistogram extends StatefulWidget {
|
||||
final AvesEntry entry;
|
||||
final ImageProvider image;
|
||||
|
||||
const ImageHistogram({
|
||||
super.key,
|
||||
required this.entry,
|
||||
required this.image,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ImageHistogram> createState() => _ImageHistogramState();
|
||||
}
|
||||
|
||||
class _ImageHistogramState extends State<ImageHistogram> {
|
||||
Map<Color, List<double>> _levels = {};
|
||||
ImageStream? _imageStream;
|
||||
late ImageStreamListener _imageListener;
|
||||
|
||||
ImageProvider get imageProvider => widget.image;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_registerWidget(widget);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant ImageHistogram oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_unregisterWidget(oldWidget);
|
||||
_registerWidget(widget);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_unregisterWidget(widget);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _registerWidget(ImageHistogram widget) {
|
||||
_imageStream = imageProvider.resolve(ImageConfiguration.empty);
|
||||
_imageListener = ImageStreamListener((image, synchronousCall) {
|
||||
_updateLevels(image);
|
||||
});
|
||||
_imageStream?.addListener(_imageListener);
|
||||
}
|
||||
|
||||
void _unregisterWidget(ImageHistogram widget) {
|
||||
_imageStream?.removeListener(_imageListener);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IgnorePointer(
|
||||
child: CustomPaint(
|
||||
painter: _HistogramPainter(
|
||||
levels: _levels,
|
||||
borderColor: ViewerTopOverlay.componentBorderColor,
|
||||
),
|
||||
size: const Size(ViewerTopOverlay.componentDimension, ViewerTopOverlay.componentDimension * .6),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static const int bins = 256;
|
||||
static const int normMax = bins - 1;
|
||||
|
||||
Future<void> _updateLevels(ImageInfo info) async {
|
||||
final image = info.image;
|
||||
final data = (await image.toByteData(format: ImageByteFormat.rawExtendedRgba128))!;
|
||||
final floats = Float32List.view(data.buffer);
|
||||
|
||||
final newLevels = switch (settings.overlayHistogramStyle) {
|
||||
OverlayHistogramStyle.rgb => _computeRgbLevels(floats),
|
||||
OverlayHistogramStyle.luminance => _computeLuminanceLevels(floats),
|
||||
_ => <Color, List<double>>{},
|
||||
};
|
||||
|
||||
setState(() => _levels = newLevels);
|
||||
}
|
||||
|
||||
Map<Color, List<double>> _computeRgbLevels(Float32List floats) {
|
||||
final redLevels = List.filled(bins, 0);
|
||||
final greenLevels = List.filled(bins, 0);
|
||||
final blueLevels = List.filled(bins, 0);
|
||||
|
||||
final pixelCount = floats.length / 4;
|
||||
for (var i = 0; i < pixelCount; i += 4) {
|
||||
final a = floats[i + 3];
|
||||
if (a > 0) {
|
||||
final r = floats[i + 0];
|
||||
final g = floats[i + 1];
|
||||
final b = floats[i + 2];
|
||||
redLevels[(r * normMax).round()]++;
|
||||
greenLevels[(g * normMax).round()]++;
|
||||
blueLevels[(b * normMax).round()]++;
|
||||
}
|
||||
}
|
||||
|
||||
final max = [
|
||||
redLevels.max,
|
||||
greenLevels.max,
|
||||
blueLevels.max,
|
||||
].max;
|
||||
if (max == 0) return {};
|
||||
|
||||
final f = 1.0 / max;
|
||||
return {
|
||||
Colors.red: redLevels.map((v) => v * f).toList(),
|
||||
Colors.green: greenLevels.map((v) => v * f).toList(),
|
||||
Colors.blue: blueLevels.map((v) => v * f).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
Map<Color, List<double>> _computeLuminanceLevels(Float32List floats) {
|
||||
final lumLevels = List.filled(bins, 0);
|
||||
|
||||
final pixelCount = floats.length / 4;
|
||||
for (var i = 0; i < pixelCount; i += 4) {
|
||||
final a = floats[i + 3];
|
||||
if (a > 0) {
|
||||
final r = floats[i + 0];
|
||||
final g = floats[i + 1];
|
||||
final b = floats[i + 2];
|
||||
final c = Color.fromARGB((a * 255).round(), (r * 255).round(), (g * 255).round(), (b * 255).round());
|
||||
lumLevels[(c.computeLuminance() * normMax).round()]++;
|
||||
}
|
||||
}
|
||||
|
||||
final max = lumLevels.max;
|
||||
if (max == 0) return {};
|
||||
|
||||
final f = 1.0 / max;
|
||||
return {
|
||||
Colors.white: lumLevels.map((v) => v * f).toList(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class _HistogramPainter extends CustomPainter {
|
||||
final Map<Color, List<double>> levels;
|
||||
final Color borderColor;
|
||||
|
||||
late final Paint fill, borderStroke;
|
||||
|
||||
_HistogramPainter({
|
||||
required this.levels,
|
||||
this.borderColor = Colors.white,
|
||||
}) {
|
||||
fill = Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = const Color(0x33000000);
|
||||
borderStroke = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..color = borderColor;
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final backgroundRect = Rect.fromPoints(Offset.zero, Offset(size.width, size.height));
|
||||
canvas.drawRect(backgroundRect, fill);
|
||||
levels.forEach((color, values) => _drawLevels(canvas, size, color, values));
|
||||
canvas.drawRect(backgroundRect, borderStroke);
|
||||
}
|
||||
|
||||
void _drawLevels(Canvas canvas, Size size, Color color, List<double> values) {
|
||||
if (values.length < 2) return;
|
||||
|
||||
final xFactor = size.width / (values.length - 1);
|
||||
final yFactor = size.height;
|
||||
|
||||
final polyline = values.mapIndexed((i, v) => Offset(i * xFactor, size.height - v * yFactor)).toList();
|
||||
canvas.drawPoints(
|
||||
PointMode.polygon,
|
||||
polyline,
|
||||
Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..color = color);
|
||||
|
||||
polyline.add(Offset(size.width, size.height));
|
||||
polyline.add(Offset(0, size.height));
|
||||
canvas.drawPath(
|
||||
Path()..addPolygon(polyline, true),
|
||||
Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = color.withOpacity(.5));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||
}
|
|
@ -3,6 +3,7 @@ import 'dart:math';
|
|||
import 'package:aves/model/view_state.dart';
|
||||
import 'package:aves/widgets/editor/transform/controller.dart';
|
||||
import 'package:aves/widgets/editor/transform/transformation.dart';
|
||||
import 'package:aves/widgets/viewer/overlay/top.dart';
|
||||
import 'package:aves_utils/aves_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -11,8 +12,6 @@ import 'package:provider/provider.dart';
|
|||
class Minimap extends StatelessWidget {
|
||||
final ValueNotifier<ViewState> viewStateNotifier;
|
||||
|
||||
static const Size minimapSize = Size(96, 96);
|
||||
|
||||
const Minimap({
|
||||
super.key,
|
||||
required this.viewStateNotifier,
|
||||
|
@ -32,40 +31,40 @@ class Minimap extends StatelessWidget {
|
|||
builder: (context, snapshot) {
|
||||
final transformation = snapshot.data;
|
||||
return CustomPaint(
|
||||
painter: MinimapPainter(
|
||||
painter: _MinimapPainter(
|
||||
viewportSize: viewportSize,
|
||||
contentSize: contentSize,
|
||||
viewCenterOffset: viewState.position,
|
||||
viewScale: viewState.scale!,
|
||||
transformation: transformation,
|
||||
minimapBorderColor: Colors.white30,
|
||||
minimapBorderColor: ViewerTopOverlay.componentBorderColor,
|
||||
),
|
||||
size: minimapSize,
|
||||
size: const Size.square(ViewerTopOverlay.componentDimension),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MinimapPainter extends CustomPainter {
|
||||
class _MinimapPainter extends CustomPainter {
|
||||
final Size contentSize, viewportSize;
|
||||
final Offset viewCenterOffset;
|
||||
final double viewScale;
|
||||
final Transformation? transformation;
|
||||
final Color minimapBorderColor, viewportBorderColor;
|
||||
final Color minimapBorderColor;
|
||||
|
||||
late final Paint fill, minimapStroke, viewportStroke;
|
||||
|
||||
MinimapPainter({
|
||||
_MinimapPainter({
|
||||
required this.viewportSize,
|
||||
required this.contentSize,
|
||||
required this.viewCenterOffset,
|
||||
required this.viewScale,
|
||||
this.transformation,
|
||||
this.minimapBorderColor = Colors.white,
|
||||
this.viewportBorderColor = Colors.white,
|
||||
}) {
|
||||
fill = Paint()
|
||||
..style = PaintingStyle.fill
|
||||
|
@ -75,7 +74,7 @@ class MinimapPainter extends CustomPainter {
|
|||
..color = minimapBorderColor;
|
||||
viewportStroke = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..color = viewportBorderColor;
|
||||
..color = Colors.white;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:aves/model/multipage.dart';
|
||||
import 'package:aves/widgets/common/thumbnail/scroller.dart';
|
||||
import 'package:aves/widgets/viewer/multipage/controller.dart';
|
||||
import 'package:aves/widgets/viewer/visual/conductor.dart';
|
||||
import 'package:aves/widgets/viewer/view/conductor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
|
|
@ -5,9 +5,11 @@ 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/details.dart';
|
||||
import 'package:aves/widgets/viewer/overlay/histogram.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';
|
||||
import 'package:aves/widgets/viewer/view/conductor.dart';
|
||||
import 'package:aves_model/aves_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
@ -21,6 +23,9 @@ class ViewerTopOverlay extends StatelessWidget {
|
|||
final Size availableSize;
|
||||
final EdgeInsets? viewInsets, viewPadding;
|
||||
|
||||
static const Color componentBorderColor = Colors.white30;
|
||||
static const double componentDimension = 96;
|
||||
|
||||
const ViewerTopOverlay({
|
||||
super.key,
|
||||
required this.entries,
|
||||
|
@ -45,7 +50,7 @@ class ViewerTopOverlay extends StatelessWidget {
|
|||
final showInfo = settings.showOverlayInfo;
|
||||
|
||||
final viewStateConductor = context.read<ViewStateConductor>();
|
||||
final viewStateNotifier = viewStateConductor.getOrCreateController(pageEntry);
|
||||
final viewStateNotifier = viewStateConductor.getOrCreateController(pageEntry).viewStateNotifier;
|
||||
|
||||
final blurred = settings.enableBlurEffect;
|
||||
final viewInsetsPadding = (viewInsets ?? EdgeInsets.zero) + (viewPadding ?? EdgeInsets.zero);
|
||||
|
@ -79,6 +84,9 @@ class ViewerTopOverlay extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (settings.showOverlayMinimap)
|
||||
SafeArea(
|
||||
top: !showInfo,
|
||||
|
@ -95,7 +103,39 @@ class ViewerTopOverlay extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
),
|
||||
const Spacer(),
|
||||
if (settings.overlayHistogramStyle != OverlayHistogramStyle.none)
|
||||
SafeArea(
|
||||
top: !showInfo,
|
||||
minimum: EdgeInsets.only(
|
||||
left: viewInsetsPadding.left,
|
||||
right: viewInsetsPadding.right,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: FadeTransition(
|
||||
opacity: scale,
|
||||
child: Selector<ViewStateConductor, ValueNotifier<ImageProvider?>>(
|
||||
selector: (context, vsc) => vsc.getOrCreateController(pageEntry!).fullImageNotifier,
|
||||
builder: (context, fullImageNotifier, child) {
|
||||
return ValueListenableBuilder<ImageProvider?>(
|
||||
valueListenable: fullImageNotifier,
|
||||
builder: (context, fullImage, child) {
|
||||
if (fullImage == null || pageEntry == null) return const SizedBox();
|
||||
return ImageHistogram(
|
||||
entry: pageEntry,
|
||||
image: fullImage,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
|
|
@ -13,7 +13,7 @@ import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
|
|||
import 'package:aves/widgets/dialogs/wallpaper_settings_dialog.dart';
|
||||
import 'package:aves/widgets/viewer/overlay/viewer_buttons.dart';
|
||||
import 'package:aves/widgets/viewer/video/conductor.dart';
|
||||
import 'package:aves/widgets/viewer/visual/conductor.dart';
|
||||
import 'package:aves/widgets/viewer/view/conductor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
@ -94,7 +94,7 @@ class WallpaperButtons extends StatelessWidget with FeedbackMixin {
|
|||
}
|
||||
|
||||
Rect? _getVisibleRegion(BuildContext context) {
|
||||
final viewState = context.read<ViewStateConductor>().getOrCreateController(entry).value;
|
||||
final viewState = context.read<ViewStateConductor>().getOrCreateController(entry).viewState;
|
||||
final viewportSize = viewState.viewportSize;
|
||||
final contentSize = viewState.contentSize;
|
||||
final scale = viewState.scale;
|
||||
|
@ -107,7 +107,7 @@ class WallpaperButtons extends StatelessWidget with FeedbackMixin {
|
|||
}
|
||||
|
||||
Future<Uint8List?> _getBytes(BuildContext context, Rect displayRegion) async {
|
||||
final viewState = context.read<ViewStateConductor>().getOrCreateController(entry).value;
|
||||
final viewState = context.read<ViewStateConductor>().getOrCreateController(entry).viewState;
|
||||
final scale = viewState.scale;
|
||||
|
||||
final displaySize = entry.displaySize;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/widgets/viewer/multipage/conductor.dart';
|
||||
import 'package:aves/widgets/viewer/video/conductor.dart';
|
||||
import 'package:aves/widgets/viewer/visual/conductor.dart';
|
||||
import 'package:aves/widgets/viewer/view/conductor.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
|
65
lib/widgets/viewer/view/conductor.dart
Normal file
65
lib/widgets/viewer/view/conductor.dart
Normal file
|
@ -0,0 +1,65 @@
|
|||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/view_state.dart';
|
||||
import 'package:aves/widgets/viewer/view/controller.dart';
|
||||
import 'package:aves_magnifier/aves_magnifier.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
class ViewStateConductor {
|
||||
final List<ViewStateController> _controllers = [];
|
||||
Size _viewportSize = Size.zero;
|
||||
|
||||
static const maxControllerCount = 3;
|
||||
|
||||
Future<void> dispose() async {
|
||||
_controllers.forEach((v) => v.dispose());
|
||||
_controllers.clear();
|
||||
}
|
||||
|
||||
set viewportSize(Size size) => _viewportSize = size;
|
||||
|
||||
ViewStateController getOrCreateController(AvesEntry entry) {
|
||||
var controller = getController(entry);
|
||||
if (controller != null) {
|
||||
_controllers.remove(controller);
|
||||
} else {
|
||||
// try to initialize the view state to match magnifier initial state
|
||||
const initialScale = ScaleLevel(ref: ScaleReference.contained);
|
||||
final initialValue = ViewState(
|
||||
position: Offset.zero,
|
||||
scale: ScaleBoundaries(
|
||||
allowOriginalScaleBeyondRange: true,
|
||||
minScale: initialScale,
|
||||
maxScale: initialScale,
|
||||
initialScale: initialScale,
|
||||
viewportSize: _viewportSize,
|
||||
contentSize: entry.displaySize,
|
||||
).initialScale,
|
||||
viewportSize: _viewportSize,
|
||||
contentSize: entry.displaySize,
|
||||
);
|
||||
controller = ViewStateController(
|
||||
entry: entry,
|
||||
viewStateNotifier: ValueNotifier<ViewState>(initialValue),
|
||||
);
|
||||
}
|
||||
_controllers.insert(0, controller);
|
||||
while (_controllers.length > maxControllerCount) {
|
||||
_controllers.removeLast().dispose();
|
||||
}
|
||||
return controller;
|
||||
}
|
||||
|
||||
ViewStateController? getController(AvesEntry entry) {
|
||||
return _controllers.firstWhereOrNull((c) => c.entry.uri == entry.uri && c.entry.pageId == entry.pageId);
|
||||
}
|
||||
|
||||
void reset(AvesEntry entry) {
|
||||
final uris = <AvesEntry>{
|
||||
entry,
|
||||
...?entry.burstEntries,
|
||||
}.map((v) => v.uri).toSet();
|
||||
_controllers.removeWhere((v) => uris.contains(v.entry.uri));
|
||||
}
|
||||
}
|
21
lib/widgets/viewer/view/controller.dart
Normal file
21
lib/widgets/viewer/view/controller.dart
Normal file
|
@ -0,0 +1,21 @@
|
|||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/view_state.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
|
||||
class ViewStateController {
|
||||
final AvesEntry entry;
|
||||
final ValueNotifier<ViewState> viewStateNotifier;
|
||||
final ValueNotifier<ImageProvider?> fullImageNotifier = ValueNotifier(null);
|
||||
|
||||
ViewState get viewState => viewStateNotifier.value;
|
||||
|
||||
ViewStateController({
|
||||
required this.entry,
|
||||
required this.viewStateNotifier,
|
||||
});
|
||||
|
||||
void dispose() {
|
||||
viewStateNotifier.dispose();
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/model/view_state.dart';
|
||||
import 'package:aves_magnifier/aves_magnifier.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class ViewStateConductor {
|
||||
final List<Tuple2<String, ValueNotifier<ViewState>>> _controllers = [];
|
||||
Size _viewportSize = Size.zero;
|
||||
|
||||
static const maxControllerCount = 3;
|
||||
|
||||
Future<void> dispose() async {
|
||||
_controllers.clear();
|
||||
}
|
||||
|
||||
set viewportSize(Size size) => _viewportSize = size;
|
||||
|
||||
ValueNotifier<ViewState> getOrCreateController(AvesEntry entry) {
|
||||
var controller = _controllers.firstOrNull;
|
||||
if (controller == null || controller.item1 != entry.uri) {
|
||||
controller = _controllers.firstWhereOrNull((kv) => kv.item1 == entry.uri);
|
||||
if (controller != null) {
|
||||
_controllers.remove(controller);
|
||||
} else {
|
||||
// try to initialize the view state to match magnifier initial state
|
||||
const initialScale = ScaleLevel(ref: ScaleReference.contained);
|
||||
final initialValue = ViewState(
|
||||
position: Offset.zero,
|
||||
scale: ScaleBoundaries(
|
||||
allowOriginalScaleBeyondRange: true,
|
||||
minScale: initialScale,
|
||||
maxScale: initialScale,
|
||||
initialScale: initialScale,
|
||||
viewportSize: _viewportSize,
|
||||
contentSize: entry.displaySize,
|
||||
).initialScale,
|
||||
viewportSize: _viewportSize,
|
||||
contentSize: entry.displaySize,
|
||||
);
|
||||
controller = Tuple2(entry.uri, ValueNotifier<ViewState>(initialValue));
|
||||
}
|
||||
_controllers.insert(0, controller);
|
||||
while (_controllers.length > maxControllerCount) {
|
||||
_controllers.removeLast().item2.dispose();
|
||||
}
|
||||
}
|
||||
return controller.item2;
|
||||
}
|
||||
|
||||
void reset(AvesEntry entry) {
|
||||
final uris = <AvesEntry>{
|
||||
entry,
|
||||
...?entry.burstEntries,
|
||||
}.map((v) => v.uri).toSet();
|
||||
_controllers.removeWhere((kv) => uris.contains(kv.item1));
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ import 'package:aves/widgets/viewer/controls/controller.dart';
|
|||
import 'package:aves/widgets/viewer/controls/notifications.dart';
|
||||
import 'package:aves/widgets/viewer/hero.dart';
|
||||
import 'package:aves/widgets/viewer/video/conductor.dart';
|
||||
import 'package:aves/widgets/viewer/visual/conductor.dart';
|
||||
import 'package:aves/widgets/viewer/view/conductor.dart';
|
||||
import 'package:aves/widgets/viewer/visual/error.dart';
|
||||
import 'package:aves/widgets/viewer/visual/raster.dart';
|
||||
import 'package:aves/model/view_state.dart';
|
||||
|
@ -90,7 +90,7 @@ class _EntryPageViewState extends State<EntryPageView> with SingleTickerProvider
|
|||
|
||||
void _registerWidget(EntryPageView widget) {
|
||||
final entry = widget.pageEntry;
|
||||
_viewStateNotifier = context.read<ViewStateConductor>().getOrCreateController(entry);
|
||||
_viewStateNotifier = context.read<ViewStateConductor>().getOrCreateController(entry).viewStateNotifier;
|
||||
_magnifierController = AvesMagnifierController();
|
||||
_subscriptions.add(_magnifierController.stateStream.listen(_onViewStateChanged));
|
||||
_subscriptions.add(_magnifierController.scaleBoundariesStream.listen(_onViewScaleBoundariesChanged));
|
||||
|
|
|
@ -6,9 +6,10 @@ import 'package:aves/model/entry/extensions/images.dart';
|
|||
import 'package:aves/model/entry/extensions/props.dart';
|
||||
import 'package:aves/model/settings/enums/entry_background.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/widgets/common/fx/checkered_decoration.dart';
|
||||
import 'package:aves/widgets/viewer/visual/entry_page_view.dart';
|
||||
import 'package:aves/model/view_state.dart';
|
||||
import 'package:aves/widgets/common/fx/checkered_decoration.dart';
|
||||
import 'package:aves/widgets/viewer/controls/notifications.dart';
|
||||
import 'package:aves/widgets/viewer/visual/entry_page_view.dart';
|
||||
import 'package:aves_model/aves_model.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -105,6 +106,7 @@ class _RasterImageViewState extends State<RasterImageView> {
|
|||
void _onFullImageCompleted(ImageInfo image, bool synchronousCall) {
|
||||
_unregisterFullImage();
|
||||
_fullImageLoaded.value = true;
|
||||
FullImageLoadedNotification(entry, fullImageProvider).dispatch(context);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -20,6 +20,8 @@ enum KeepScreenOn { never, videoPlayback, viewerOnly, always }
|
|||
|
||||
enum MaxBrightness { never, viewerOnly, always }
|
||||
|
||||
enum OverlayHistogramStyle { none, rgb, luminance }
|
||||
|
||||
enum SlideshowVideoPlayback { skip, playMuted, playWithSound }
|
||||
|
||||
enum SubtitlePosition { top, bottom }
|
||||
|
|
|
@ -87,6 +87,7 @@ class SettingKeys {
|
|||
static const viewerQuickActionsKey = 'viewer_quick_actions';
|
||||
static const showOverlayOnOpeningKey = 'show_overlay_on_opening';
|
||||
static const showOverlayMinimapKey = 'show_overlay_minimap';
|
||||
static const overlayHistogramStyleKey = 'show_overlay_histogram';
|
||||
static const showOverlayInfoKey = 'show_overlay_info';
|
||||
static const showOverlayDescriptionKey = 'show_overlay_description';
|
||||
static const showOverlayRatingTagsKey = 'show_overlay_rating_tags';
|
||||
|
|
|
@ -98,10 +98,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
|
||||
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.1.0"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -13,6 +13,7 @@ class MpvVideoController extends AvesVideoController {
|
|||
late Player _instance;
|
||||
late VideoController _controller;
|
||||
late VideoStatus _status;
|
||||
bool _firstFrameRendered = false;
|
||||
final List<StreamSubscription> _subscriptions = [];
|
||||
final StreamController<VideoStatus> _statusStreamController = StreamController.broadcast();
|
||||
final StreamController<String?> _timedTextStreamController = StreamController.broadcast();
|
||||
|
@ -113,12 +114,17 @@ class MpvVideoController extends AvesVideoController {
|
|||
}
|
||||
|
||||
void _initController() {
|
||||
_firstFrameRendered = false;
|
||||
_controller = VideoController(
|
||||
_instance,
|
||||
configuration: VideoControllerConfiguration(
|
||||
enableHardwareAcceleration: settings.enableVideoHardwareAcceleration,
|
||||
),
|
||||
);
|
||||
_controller.waitUntilFirstFrameRendered.then((v) {
|
||||
_firstFrameRendered = true;
|
||||
_statusStreamController.add(_status);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -171,7 +177,7 @@ class MpvVideoController extends AvesVideoController {
|
|||
case VideoStatus.paused:
|
||||
case VideoStatus.playing:
|
||||
case VideoStatus.completed:
|
||||
return true;
|
||||
return _firstFrameRendered;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -98,10 +98,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
|
||||
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.1.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
@ -172,34 +172,34 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: media_kit
|
||||
sha256: "272a9f1dd77ed57b48707fdb0ec0e4a048ef958feccc0d0dd751135fe924b63a"
|
||||
sha256: f19151ff1a1724ed8675f066b40e74af6d155fc859cb74487daeae2cbeff53e0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.1.3+1"
|
||||
media_kit_libs_android_video:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: media_kit_libs_android_video
|
||||
sha256: ddb0d26ecba72bf7117e37e29b6a50f4ba198bbccb4e47246cae1812087dc721
|
||||
sha256: "0a533497d0a982c7146af7dbe226856ef13b05f6d87a6405b1d09d8b40aa2685"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.3.1"
|
||||
media_kit_native_event_loop:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: media_kit_native_event_loop
|
||||
sha256: "5351f0c28124b5358756515d8619abad182cdefe967468d7fb5b274737cc2f59"
|
||||
sha256: e37ce6fb5fa71b8cf513c6a6cd591367743a342a385e7da621a047dd8ef6f4a4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
version: "1.0.7"
|
||||
media_kit_video:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: media_kit_video
|
||||
sha256: "3ac0403d67710dfb2bf6aabfa6caff1b163e70fb7e1a88423bc1be569b4df6b3"
|
||||
sha256: e286992beee857fee78ce79ed21fd38addb5af0469e10a322d82cf074beb6214
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.1.3"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -16,9 +16,9 @@ dependencies:
|
|||
path: ../aves_utils
|
||||
collection:
|
||||
media_kit:
|
||||
media_kit_video:
|
||||
media_kit_native_event_loop:
|
||||
media_kit_libs_android_video:
|
||||
media_kit_native_event_loop:
|
||||
media_kit_video:
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints:
|
||||
|
|
32
pubspec.lock
32
pubspec.lock
|
@ -347,10 +347,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
|
||||
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.1.0"
|
||||
ffmpeg_kit_flutter:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -516,10 +516,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_markdown
|
||||
sha256: "4b1bfbb802d76320a1a46d9ce984106135093efd9d969765d07c2125af107bdf"
|
||||
sha256: "2b206d397dd7836ea60035b2d43825c8a303a76a5098e66f42d55a753e18d431"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.17"
|
||||
version: "0.6.17+1"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -835,34 +835,34 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: media_kit
|
||||
sha256: "272a9f1dd77ed57b48707fdb0ec0e4a048ef958feccc0d0dd751135fe924b63a"
|
||||
sha256: f19151ff1a1724ed8675f066b40e74af6d155fc859cb74487daeae2cbeff53e0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.1.3+1"
|
||||
media_kit_libs_android_video:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: media_kit_libs_android_video
|
||||
sha256: ddb0d26ecba72bf7117e37e29b6a50f4ba198bbccb4e47246cae1812087dc721
|
||||
sha256: "0a533497d0a982c7146af7dbe226856ef13b05f6d87a6405b1d09d8b40aa2685"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.3.1"
|
||||
media_kit_native_event_loop:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: media_kit_native_event_loop
|
||||
sha256: "5351f0c28124b5358756515d8619abad182cdefe967468d7fb5b274737cc2f59"
|
||||
sha256: e37ce6fb5fa71b8cf513c6a6cd591367743a342a385e7da621a047dd8ef6f4a4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
version: "1.0.7"
|
||||
media_kit_video:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: media_kit_video
|
||||
sha256: "3ac0403d67710dfb2bf6aabfa6caff1b163e70fb7e1a88423bc1be569b4df6b3"
|
||||
sha256: e286992beee857fee78ce79ed21fd38addb5af0469e10a322d82cf074beb6214
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.1.3"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1531,10 +1531,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "78cb6dea3e93148615109e58e42c35d1ffbf5ef66c44add673d0ab75f12ff3af"
|
||||
sha256: "3dd2388cc0c42912eee04434531a26a82512b9cb1827e0214430c9bcbddfe025"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.37"
|
||||
version: "6.0.38"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1691,10 +1691,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff
|
||||
sha256: f0c26453a2d47aa4c2570c6a033246a3fc62da2fe23c7ffdd0a7495086dc0247
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
version: "1.0.2"
|
||||
xml:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
@ -142,6 +142,9 @@
|
|||
"nameConflictStrategyRename",
|
||||
"nameConflictStrategyReplace",
|
||||
"nameConflictStrategySkip",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"subtitlePositionTop",
|
||||
"subtitlePositionBottom",
|
||||
"themeBrightnessLight",
|
||||
|
@ -487,6 +490,7 @@
|
|||
"settingsViewerOverlayTile",
|
||||
"settingsViewerOverlayPageTitle",
|
||||
"settingsViewerShowOverlayOnOpening",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowMinimap",
|
||||
"settingsViewerShowInformation",
|
||||
"settingsViewerShowInformationSubtitle",
|
||||
|
@ -640,6 +644,9 @@
|
|||
],
|
||||
|
||||
"be": [
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"viewerTransitionSlide",
|
||||
"viewerTransitionFade",
|
||||
"viewerTransitionZoomIn",
|
||||
|
@ -920,6 +927,7 @@
|
|||
"settingsViewerOverlayTile",
|
||||
"settingsViewerOverlayPageTitle",
|
||||
"settingsViewerShowOverlayOnOpening",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowMinimap",
|
||||
"settingsViewerShowInformation",
|
||||
"settingsViewerShowInformationSubtitle",
|
||||
|
@ -1127,6 +1135,9 @@
|
|||
"nameConflictStrategyRename",
|
||||
"nameConflictStrategyReplace",
|
||||
"nameConflictStrategySkip",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"subtitlePositionTop",
|
||||
"subtitlePositionBottom",
|
||||
"themeBrightnessLight",
|
||||
|
@ -1473,6 +1484,7 @@
|
|||
"settingsViewerOverlayTile",
|
||||
"settingsViewerOverlayPageTitle",
|
||||
"settingsViewerShowOverlayOnOpening",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowMinimap",
|
||||
"settingsViewerShowInformation",
|
||||
"settingsViewerShowInformationSubtitle",
|
||||
|
@ -1638,14 +1650,46 @@
|
|||
"filePickerUseThisFolder"
|
||||
],
|
||||
|
||||
"cs": [
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"settingsViewerShowHistogram"
|
||||
],
|
||||
|
||||
"de": [
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"settingsViewerShowHistogram"
|
||||
],
|
||||
|
||||
"el": [
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
"aboutDataUsageExternal",
|
||||
"settingsViewerShowHistogram"
|
||||
],
|
||||
|
||||
"es": [
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"settingsViewerShowHistogram"
|
||||
],
|
||||
|
||||
"eu": [
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"settingsViewerShowHistogram"
|
||||
],
|
||||
|
||||
"fa": [
|
||||
|
@ -1700,6 +1744,9 @@
|
|||
"maxBrightnessNever",
|
||||
"maxBrightnessAlways",
|
||||
"nameConflictStrategySkip",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"vaultLockTypePattern",
|
||||
"vaultLockTypePin",
|
||||
"vaultLockTypePassword",
|
||||
|
@ -1996,6 +2043,7 @@
|
|||
"settingsViewerOverlayTile",
|
||||
"settingsViewerOverlayPageTitle",
|
||||
"settingsViewerShowOverlayOnOpening",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowMinimap",
|
||||
"settingsViewerShowInformation",
|
||||
"settingsViewerShowInformationSubtitle",
|
||||
|
@ -2159,6 +2207,13 @@
|
|||
"filePickerUseThisFolder"
|
||||
],
|
||||
|
||||
"fr": [
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"settingsViewerShowHistogram"
|
||||
],
|
||||
|
||||
"gl": [
|
||||
"columnCount",
|
||||
"saveCopyButtonLabel",
|
||||
|
@ -2200,6 +2255,9 @@
|
|||
"lengthUnitPercent",
|
||||
"maxBrightnessNever",
|
||||
"maxBrightnessAlways",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"subtitlePositionTop",
|
||||
"subtitlePositionBottom",
|
||||
"themeBrightnessLight",
|
||||
|
@ -2537,6 +2595,7 @@
|
|||
"settingsViewerOverlayTile",
|
||||
"settingsViewerOverlayPageTitle",
|
||||
"settingsViewerShowOverlayOnOpening",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowMinimap",
|
||||
"settingsViewerShowInformation",
|
||||
"settingsViewerShowInformationSubtitle",
|
||||
|
@ -2863,6 +2922,9 @@
|
|||
"nameConflictStrategyRename",
|
||||
"nameConflictStrategyReplace",
|
||||
"nameConflictStrategySkip",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"subtitlePositionTop",
|
||||
"subtitlePositionBottom",
|
||||
"themeBrightnessLight",
|
||||
|
@ -3209,6 +3271,7 @@
|
|||
"settingsViewerOverlayTile",
|
||||
"settingsViewerOverlayPageTitle",
|
||||
"settingsViewerShowOverlayOnOpening",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowMinimap",
|
||||
"settingsViewerShowInformation",
|
||||
"settingsViewerShowInformationSubtitle",
|
||||
|
@ -3515,6 +3578,9 @@
|
|||
"nameConflictStrategyRename",
|
||||
"nameConflictStrategyReplace",
|
||||
"nameConflictStrategySkip",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"subtitlePositionTop",
|
||||
"subtitlePositionBottom",
|
||||
"themeBrightnessLight",
|
||||
|
@ -3861,6 +3927,7 @@
|
|||
"settingsViewerOverlayTile",
|
||||
"settingsViewerOverlayPageTitle",
|
||||
"settingsViewerShowOverlayOnOpening",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowMinimap",
|
||||
"settingsViewerShowInformation",
|
||||
"settingsViewerShowInformationSubtitle",
|
||||
|
@ -4026,6 +4093,27 @@
|
|||
"filePickerUseThisFolder"
|
||||
],
|
||||
|
||||
"hu": [
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"settingsViewerShowHistogram"
|
||||
],
|
||||
|
||||
"id": [
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"settingsViewerShowHistogram"
|
||||
],
|
||||
|
||||
"it": [
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"settingsViewerShowHistogram"
|
||||
],
|
||||
|
||||
"ja": [
|
||||
"columnCount",
|
||||
"saveCopyButtonLabel",
|
||||
|
@ -4046,6 +4134,9 @@
|
|||
"albumTierVaults",
|
||||
"maxBrightnessNever",
|
||||
"maxBrightnessAlways",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"subtitlePositionBottom",
|
||||
"videoResumptionModeNever",
|
||||
"videoResumptionModeAlways",
|
||||
|
@ -4068,6 +4159,7 @@
|
|||
"settingsConfirmationVaultDataLoss",
|
||||
"settingsCollectionBurstPatternsTile",
|
||||
"settingsCollectionBurstPatternsNone",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowDescription",
|
||||
"settingsVideoPlaybackTile",
|
||||
"settingsVideoPlaybackPageTitle",
|
||||
|
@ -4218,6 +4310,9 @@
|
|||
"nameConflictStrategyRename",
|
||||
"nameConflictStrategyReplace",
|
||||
"nameConflictStrategySkip",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"subtitlePositionTop",
|
||||
"subtitlePositionBottom",
|
||||
"themeBrightnessLight",
|
||||
|
@ -4564,6 +4659,7 @@
|
|||
"settingsViewerOverlayTile",
|
||||
"settingsViewerOverlayPageTitle",
|
||||
"settingsViewerShowOverlayOnOpening",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowMinimap",
|
||||
"settingsViewerShowInformation",
|
||||
"settingsViewerShowInformationSubtitle",
|
||||
|
@ -4729,6 +4825,13 @@
|
|||
"filePickerUseThisFolder"
|
||||
],
|
||||
|
||||
"ko": [
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"settingsViewerShowHistogram"
|
||||
],
|
||||
|
||||
"lt": [
|
||||
"columnCount",
|
||||
"saveCopyButtonLabel",
|
||||
|
@ -4754,6 +4857,9 @@
|
|||
"lengthUnitPercent",
|
||||
"maxBrightnessNever",
|
||||
"maxBrightnessAlways",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"vaultLockTypePattern",
|
||||
"vaultLockTypePin",
|
||||
"vaultLockTypePassword",
|
||||
|
@ -4796,6 +4902,7 @@
|
|||
"settingsConfirmationVaultDataLoss",
|
||||
"settingsCollectionBurstPatternsTile",
|
||||
"settingsCollectionBurstPatternsNone",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowDescription",
|
||||
"settingsVideoPlaybackTile",
|
||||
"settingsVideoPlaybackPageTitle",
|
||||
|
@ -4977,6 +5084,9 @@
|
|||
"nameConflictStrategyRename",
|
||||
"nameConflictStrategyReplace",
|
||||
"nameConflictStrategySkip",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"subtitlePositionTop",
|
||||
"subtitlePositionBottom",
|
||||
"themeBrightnessLight",
|
||||
|
@ -5323,6 +5433,7 @@
|
|||
"settingsViewerOverlayTile",
|
||||
"settingsViewerOverlayPageTitle",
|
||||
"settingsViewerShowOverlayOnOpening",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowMinimap",
|
||||
"settingsViewerShowInformation",
|
||||
"settingsViewerShowInformationSubtitle",
|
||||
|
@ -5608,6 +5719,9 @@
|
|||
"nameConflictStrategyRename",
|
||||
"nameConflictStrategyReplace",
|
||||
"nameConflictStrategySkip",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"subtitlePositionTop",
|
||||
"subtitlePositionBottom",
|
||||
"themeBrightnessLight",
|
||||
|
@ -5954,6 +6068,7 @@
|
|||
"settingsViewerOverlayTile",
|
||||
"settingsViewerOverlayPageTitle",
|
||||
"settingsViewerShowOverlayOnOpening",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowMinimap",
|
||||
"settingsViewerShowInformation",
|
||||
"settingsViewerShowInformationSubtitle",
|
||||
|
@ -6133,6 +6248,9 @@
|
|||
"cropAspectRatioSquare",
|
||||
"maxBrightnessNever",
|
||||
"maxBrightnessAlways",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"vaultLockTypePattern",
|
||||
"settingsVideoEnablePip",
|
||||
"videoResumptionModeNever",
|
||||
|
@ -6154,6 +6272,7 @@
|
|||
"settingsAskEverytime",
|
||||
"settingsCollectionBurstPatternsTile",
|
||||
"settingsCollectionBurstPatternsNone",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsVideoPlaybackTile",
|
||||
"settingsVideoPlaybackPageTitle",
|
||||
"settingsVideoResumptionModeTile",
|
||||
|
@ -6184,6 +6303,9 @@
|
|||
"albumTierVaults",
|
||||
"maxBrightnessNever",
|
||||
"maxBrightnessAlways",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"subtitlePositionTop",
|
||||
"subtitlePositionBottom",
|
||||
"vaultLockTypePattern",
|
||||
|
@ -6225,6 +6347,7 @@
|
|||
"settingsConfirmationVaultDataLoss",
|
||||
"settingsCollectionBurstPatternsTile",
|
||||
"settingsCollectionBurstPatternsNone",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowRatingTags",
|
||||
"settingsViewerShowDescription",
|
||||
"settingsVideoPlaybackTile",
|
||||
|
@ -6247,6 +6370,9 @@
|
|||
"nn": [
|
||||
"sourceStateCataloguing",
|
||||
"accessibilityAnimationsKeep",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"settingsVideoEnablePip",
|
||||
"widgetTapUpdateWidget",
|
||||
"authenticateToConfigureVault",
|
||||
|
@ -6266,6 +6392,7 @@
|
|||
"collectionActionShowTitleSearch",
|
||||
"collectionActionHideTitleSearch",
|
||||
"drawerCollectionAnimated",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsSlideshowAnimatedZoomEffect",
|
||||
"settingsHiddenItemsTabFilters",
|
||||
"settingsHiddenFiltersBanner",
|
||||
|
@ -6427,6 +6554,9 @@
|
|||
"maxBrightnessNever",
|
||||
"maxBrightnessAlways",
|
||||
"nameConflictStrategyReplace",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"subtitlePositionTop",
|
||||
"subtitlePositionBottom",
|
||||
"unitSystemMetric",
|
||||
|
@ -6721,6 +6851,7 @@
|
|||
"settingsViewerOverlayTile",
|
||||
"settingsViewerOverlayPageTitle",
|
||||
"settingsViewerShowOverlayOnOpening",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowMinimap",
|
||||
"settingsViewerShowInformationSubtitle",
|
||||
"settingsViewerShowRatingTags",
|
||||
|
@ -6883,14 +7014,25 @@
|
|||
"filePickerUseThisFolder"
|
||||
],
|
||||
|
||||
"pl": [
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"settingsViewerShowHistogram"
|
||||
],
|
||||
|
||||
"pt": [
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
"aboutDataUsageExternal",
|
||||
"settingsViewerShowHistogram"
|
||||
],
|
||||
|
||||
"ro": [
|
||||
|
@ -6904,6 +7046,9 @@
|
|||
"cropAspectRatioSquare",
|
||||
"maxBrightnessNever",
|
||||
"maxBrightnessAlways",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"videoResumptionModeNever",
|
||||
"videoResumptionModeAlways",
|
||||
"widgetTapUpdateWidget",
|
||||
|
@ -6916,6 +7061,7 @@
|
|||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"settingsAskEverytime",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsVideoPlaybackTile",
|
||||
"settingsVideoPlaybackPageTitle",
|
||||
"settingsVideoResumptionModeTile",
|
||||
|
@ -6923,6 +7069,13 @@
|
|||
"tagEditorDiscardDialogMessage"
|
||||
],
|
||||
|
||||
"ru": [
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"settingsViewerShowHistogram"
|
||||
],
|
||||
|
||||
"sk": [
|
||||
"itemCount",
|
||||
"columnCount",
|
||||
|
@ -6950,6 +7103,9 @@
|
|||
"lengthUnitPercent",
|
||||
"maxBrightnessNever",
|
||||
"maxBrightnessAlways",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"vaultLockTypePattern",
|
||||
"vaultLockTypePin",
|
||||
"vaultLockTypePassword",
|
||||
|
@ -7218,6 +7374,7 @@
|
|||
"settingsViewerOverlayTile",
|
||||
"settingsViewerOverlayPageTitle",
|
||||
"settingsViewerShowOverlayOnOpening",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowMinimap",
|
||||
"settingsViewerShowInformation",
|
||||
"settingsViewerShowInformationSubtitle",
|
||||
|
@ -7544,6 +7701,9 @@
|
|||
"nameConflictStrategyRename",
|
||||
"nameConflictStrategyReplace",
|
||||
"nameConflictStrategySkip",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"subtitlePositionTop",
|
||||
"subtitlePositionBottom",
|
||||
"themeBrightnessLight",
|
||||
|
@ -7890,6 +8050,7 @@
|
|||
"settingsViewerOverlayTile",
|
||||
"settingsViewerOverlayPageTitle",
|
||||
"settingsViewerShowOverlayOnOpening",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowMinimap",
|
||||
"settingsViewerShowInformation",
|
||||
"settingsViewerShowInformationSubtitle",
|
||||
|
@ -8083,6 +8244,9 @@
|
|||
"lengthUnitPercent",
|
||||
"maxBrightnessNever",
|
||||
"maxBrightnessAlways",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"vaultLockTypePattern",
|
||||
"vaultLockTypePin",
|
||||
"vaultLockTypePassword",
|
||||
|
@ -8285,6 +8449,7 @@
|
|||
"settingsViewerOverlayTile",
|
||||
"settingsViewerOverlayPageTitle",
|
||||
"settingsViewerShowOverlayOnOpening",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowMinimap",
|
||||
"settingsViewerShowInformation",
|
||||
"settingsViewerShowInformationSubtitle",
|
||||
|
@ -8471,6 +8636,9 @@
|
|||
"lengthUnitPercent",
|
||||
"maxBrightnessNever",
|
||||
"maxBrightnessAlways",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"vaultLockTypePattern",
|
||||
"vaultLockTypePin",
|
||||
"vaultLockTypePassword",
|
||||
|
@ -8511,6 +8679,7 @@
|
|||
"settingsConfirmationVaultDataLoss",
|
||||
"settingsCollectionBurstPatternsTile",
|
||||
"settingsCollectionBurstPatternsNone",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsVideoPlaybackTile",
|
||||
"settingsVideoPlaybackPageTitle",
|
||||
"settingsVideoResumptionModeTile",
|
||||
|
@ -8523,6 +8692,13 @@
|
|||
"tagPlaceholderState"
|
||||
],
|
||||
|
||||
"uk": [
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"settingsViewerShowHistogram"
|
||||
],
|
||||
|
||||
"zh": [
|
||||
"saveCopyButtonLabel",
|
||||
"chipActionGoToPlacePage",
|
||||
|
@ -8537,6 +8713,9 @@
|
|||
"lengthUnitPercent",
|
||||
"maxBrightnessNever",
|
||||
"maxBrightnessAlways",
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"vaultLockTypePattern",
|
||||
"vaultLockTypePin",
|
||||
"settingsVideoEnablePip",
|
||||
|
@ -8573,6 +8752,7 @@
|
|||
"settingsConfirmationVaultDataLoss",
|
||||
"settingsCollectionBurstPatternsTile",
|
||||
"settingsCollectionBurstPatternsNone",
|
||||
"settingsViewerShowHistogram",
|
||||
"settingsViewerShowDescription",
|
||||
"settingsVideoPlaybackTile",
|
||||
"settingsVideoPlaybackPageTitle",
|
||||
|
@ -8588,12 +8768,16 @@
|
|||
],
|
||||
|
||||
"zh_Hant": [
|
||||
"overlayHistogramNone",
|
||||
"overlayHistogramRGB",
|
||||
"overlayHistogramLuminance",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
"aboutDataUsageExternal",
|
||||
"settingsViewerShowHistogram"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue