#706 viewer: histogram

This commit is contained in:
Thibault Deckers 2023-08-11 23:00:33 +02:00
parent 99dd7ec0ff
commit 05d4d01ef7
27 changed files with 651 additions and 143 deletions

View file

@ -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

View file

@ -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.",

View file

@ -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;

View file

@ -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);

View file

@ -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) {

View file

@ -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,
),
],
),
),

View file

@ -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);
}

View file

@ -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) {

View 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;
}

View file

@ -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

View file

@ -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';

View file

@ -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,
);
},
);
},
),
),
),
),
],
),
],
);
},

View file

@ -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;

View file

@ -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';

View 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));
}
}

View 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();
}
}

View file

@ -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));
}
}

View file

@ -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));

View file

@ -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

View file

@ -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 }

View file

@ -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';

View file

@ -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:

View file

@ -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;
}
}

View file

@ -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:

View file

@ -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:

View file

@ -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:

View file

@ -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"
]
}