#526 collection: bulk converting
This commit is contained in:
parent
af6fd8f11b
commit
8f732608d0
16 changed files with 367 additions and 139 deletions
|
@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Collection: bulk converting
|
||||||
- Places: page & navigation entry
|
- Places: page & navigation entry
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -50,7 +50,7 @@ class ImageOpStreamHandler(private val activity: Activity, private val arguments
|
||||||
|
|
||||||
when (op) {
|
when (op) {
|
||||||
"delete" -> ioScope.launch { delete() }
|
"delete" -> ioScope.launch { delete() }
|
||||||
"export" -> ioScope.launch { export() }
|
"convert" -> ioScope.launch { convert() }
|
||||||
"move" -> ioScope.launch { move() }
|
"move" -> ioScope.launch { move() }
|
||||||
"rename" -> ioScope.launch { rename() }
|
"rename" -> ioScope.launch { rename() }
|
||||||
else -> endOfStream()
|
else -> endOfStream()
|
||||||
|
@ -121,7 +121,7 @@ class ImageOpStreamHandler(private val activity: Activity, private val arguments
|
||||||
endOfStream()
|
endOfStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun export() {
|
private suspend fun convert() {
|
||||||
if (arguments !is Map<*, *> || entryMapList.isEmpty()) {
|
if (arguments !is Map<*, *> || entryMapList.isEmpty()) {
|
||||||
endOfStream()
|
endOfStream()
|
||||||
return
|
return
|
||||||
|
@ -129,11 +129,12 @@ class ImageOpStreamHandler(private val activity: Activity, private val arguments
|
||||||
|
|
||||||
var destinationDir = arguments["destinationPath"] as String?
|
var destinationDir = arguments["destinationPath"] as String?
|
||||||
val mimeType = arguments["mimeType"] as String?
|
val mimeType = arguments["mimeType"] as String?
|
||||||
|
val lengthUnit = arguments["lengthUnit"] as String?
|
||||||
val width = (arguments["width"] as Number?)?.toInt()
|
val width = (arguments["width"] as Number?)?.toInt()
|
||||||
val height = (arguments["height"] as Number?)?.toInt()
|
val height = (arguments["height"] as Number?)?.toInt()
|
||||||
val nameConflictStrategy = NameConflictStrategy.get(arguments["nameConflictStrategy"] as String?)
|
val nameConflictStrategy = NameConflictStrategy.get(arguments["nameConflictStrategy"] as String?)
|
||||||
if (destinationDir == null || mimeType == null || width == null || height == null || nameConflictStrategy == null) {
|
if (destinationDir == null || mimeType == null || lengthUnit == null || width == null || height == null || nameConflictStrategy == null) {
|
||||||
error("export-args", "missing arguments", null)
|
error("convert-args", "missing arguments", null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,15 +142,15 @@ class ImageOpStreamHandler(private val activity: Activity, private val arguments
|
||||||
val firstEntry = entryMapList.first()
|
val firstEntry = entryMapList.first()
|
||||||
val provider = (firstEntry["uri"] as String?)?.let { Uri.parse(it) }?.let { getProvider(it) }
|
val provider = (firstEntry["uri"] as String?)?.let { Uri.parse(it) }?.let { getProvider(it) }
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
error("export-provider", "failed to find provider for entry=$firstEntry", null)
|
error("convert-provider", "failed to find provider for entry=$firstEntry", null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
destinationDir = ensureTrailingSeparator(destinationDir)
|
destinationDir = ensureTrailingSeparator(destinationDir)
|
||||||
val entries = entryMapList.map(::AvesEntry)
|
val entries = entryMapList.map(::AvesEntry)
|
||||||
provider.exportMultiple(activity, mimeType, destinationDir, entries, width, height, nameConflictStrategy, object : ImageOpCallback {
|
provider.convertMultiple(activity, mimeType, destinationDir, entries, lengthUnit, width, height, nameConflictStrategy, object : ImageOpCallback {
|
||||||
override fun onSuccess(fields: FieldMap) = success(fields)
|
override fun onSuccess(fields: FieldMap) = success(fields)
|
||||||
override fun onFailure(throwable: Throwable) = error("export-failure", "failed to export entries", throwable)
|
override fun onFailure(throwable: Throwable) = error("convert-failure", "failed to convert entries", throwable)
|
||||||
})
|
})
|
||||||
endOfStream()
|
endOfStream()
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,15 @@ class AvesEntry(map: FieldMap) {
|
||||||
val trashed = map["trashed"] as Boolean
|
val trashed = map["trashed"] as Boolean
|
||||||
val trashPath = map["trashPath"] as String?
|
val trashPath = map["trashPath"] as String?
|
||||||
|
|
||||||
|
private val isRotated: Boolean
|
||||||
|
get() = rotationDegrees % 180 == 90
|
||||||
|
|
||||||
|
val displayWidth: Int
|
||||||
|
get() = if (isRotated) height else width
|
||||||
|
|
||||||
|
val displayHeight: Int
|
||||||
|
get() = if (isRotated) width else height
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// convenience method
|
// convenience method
|
||||||
private fun toLong(o: Any?): Long? = when (o) {
|
private fun toLong(o: Any?): Long? = when (o) {
|
||||||
|
|
|
@ -169,11 +169,12 @@ abstract class ImageProvider {
|
||||||
throw UnsupportedOperationException("`scanPostMetadataEdit` is not supported by this image provider")
|
throw UnsupportedOperationException("`scanPostMetadataEdit` is not supported by this image provider")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun exportMultiple(
|
suspend fun convertMultiple(
|
||||||
activity: Activity,
|
activity: Activity,
|
||||||
imageExportMimeType: String,
|
imageExportMimeType: String,
|
||||||
targetDir: String,
|
targetDir: String,
|
||||||
entries: List<AvesEntry>,
|
entries: List<AvesEntry>,
|
||||||
|
lengthUnit: String,
|
||||||
width: Int,
|
width: Int,
|
||||||
height: Int,
|
height: Int,
|
||||||
nameConflictStrategy: NameConflictStrategy,
|
nameConflictStrategy: NameConflictStrategy,
|
||||||
|
@ -208,6 +209,7 @@ abstract class ImageProvider {
|
||||||
sourceEntry = entry,
|
sourceEntry = entry,
|
||||||
targetDir = targetDir,
|
targetDir = targetDir,
|
||||||
targetDirDocFile = targetDirDocFile,
|
targetDirDocFile = targetDirDocFile,
|
||||||
|
lengthUnit = lengthUnit,
|
||||||
width = width,
|
width = width,
|
||||||
height = height,
|
height = height,
|
||||||
nameConflictStrategy = nameConflictStrategy,
|
nameConflictStrategy = nameConflictStrategy,
|
||||||
|
@ -227,6 +229,7 @@ abstract class ImageProvider {
|
||||||
sourceEntry: AvesEntry,
|
sourceEntry: AvesEntry,
|
||||||
targetDir: String,
|
targetDir: String,
|
||||||
targetDirDocFile: DocumentFileCompat?,
|
targetDirDocFile: DocumentFileCompat?,
|
||||||
|
lengthUnit: String,
|
||||||
width: Int,
|
width: Int,
|
||||||
height: Int,
|
height: Int,
|
||||||
nameConflictStrategy: NameConflictStrategy,
|
nameConflictStrategy: NameConflictStrategy,
|
||||||
|
@ -266,6 +269,19 @@ abstract class ImageProvider {
|
||||||
sourceDocFile.copyTo(output)
|
sourceDocFile.copyTo(output)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
val targetWidthPx: Int
|
||||||
|
val targetHeightPx: Int
|
||||||
|
when (lengthUnit) {
|
||||||
|
LENGTH_UNIT_PERCENT -> {
|
||||||
|
targetWidthPx = sourceEntry.displayWidth * width / 100
|
||||||
|
targetHeightPx = sourceEntry.displayHeight * height / 100
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
targetWidthPx = width
|
||||||
|
targetHeightPx = height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val model: Any = if (MimeTypes.isHeic(sourceMimeType) && pageId != null) {
|
val model: Any = if (MimeTypes.isHeic(sourceMimeType) && pageId != null) {
|
||||||
MultiTrackImage(activity, sourceUri, pageId)
|
MultiTrackImage(activity, sourceUri, pageId)
|
||||||
} else if (sourceMimeType == MimeTypes.TIFF) {
|
} else if (sourceMimeType == MimeTypes.TIFF) {
|
||||||
|
@ -286,7 +302,7 @@ abstract class ImageProvider {
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.apply(glideOptions)
|
.apply(glideOptions)
|
||||||
.load(model)
|
.load(model)
|
||||||
.submit(width, height)
|
.submit(targetWidthPx, targetHeightPx)
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
var bitmap = target.get()
|
var bitmap = target.get()
|
||||||
if (MimeTypes.needRotationAfterGlide(sourceMimeType)) {
|
if (MimeTypes.needRotationAfterGlide(sourceMimeType)) {
|
||||||
|
@ -1209,6 +1225,8 @@ abstract class ImageProvider {
|
||||||
companion object {
|
companion object {
|
||||||
private val LOG_TAG = LogUtils.createTag<ImageProvider>()
|
private val LOG_TAG = LogUtils.createTag<ImageProvider>()
|
||||||
|
|
||||||
|
private const val LENGTH_UNIT_PERCENT = "percent"
|
||||||
|
|
||||||
val supportedExportMimeTypes = listOf(MimeTypes.BMP, MimeTypes.JPEG, MimeTypes.PNG, MimeTypes.WEBP)
|
val supportedExportMimeTypes = listOf(MimeTypes.BMP, MimeTypes.JPEG, MimeTypes.PNG, MimeTypes.WEBP)
|
||||||
|
|
||||||
// used when skipping a move/creation op because the target file already exists
|
// used when skipping a move/creation op because the target file already exists
|
||||||
|
|
|
@ -200,6 +200,9 @@
|
||||||
"keepScreenOnViewerOnly": "Viewer page only",
|
"keepScreenOnViewerOnly": "Viewer page only",
|
||||||
"keepScreenOnAlways": "Always",
|
"keepScreenOnAlways": "Always",
|
||||||
|
|
||||||
|
"lengthUnitPixel": "px",
|
||||||
|
"lengthUnitPercent": "%",
|
||||||
|
|
||||||
"mapStyleGoogleNormal": "Google Maps",
|
"mapStyleGoogleNormal": "Google Maps",
|
||||||
"mapStyleGoogleHybrid": "Google Maps (Hybrid)",
|
"mapStyleGoogleHybrid": "Google Maps (Hybrid)",
|
||||||
"mapStyleGoogleTerrain": "Google Maps (Terrain)",
|
"mapStyleGoogleTerrain": "Google Maps (Terrain)",
|
||||||
|
|
|
@ -25,6 +25,7 @@ enum EntrySetAction {
|
||||||
copy,
|
copy,
|
||||||
move,
|
move,
|
||||||
rename,
|
rename,
|
||||||
|
convert,
|
||||||
toggleFavourite,
|
toggleFavourite,
|
||||||
rotateCCW,
|
rotateCCW,
|
||||||
rotateCW,
|
rotateCW,
|
||||||
|
@ -45,13 +46,16 @@ class EntrySetActions {
|
||||||
EntrySetAction.selectNone,
|
EntrySetAction.selectNone,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// `null` items are converted to dividers
|
||||||
static const pageBrowsing = [
|
static const pageBrowsing = [
|
||||||
EntrySetAction.searchCollection,
|
EntrySetAction.searchCollection,
|
||||||
EntrySetAction.toggleTitleSearch,
|
EntrySetAction.toggleTitleSearch,
|
||||||
EntrySetAction.addShortcut,
|
EntrySetAction.addShortcut,
|
||||||
|
null,
|
||||||
EntrySetAction.map,
|
EntrySetAction.map,
|
||||||
EntrySetAction.slideshow,
|
EntrySetAction.slideshow,
|
||||||
EntrySetAction.stats,
|
EntrySetAction.stats,
|
||||||
|
null,
|
||||||
EntrySetAction.rescan,
|
EntrySetAction.rescan,
|
||||||
EntrySetAction.emptyBin,
|
EntrySetAction.emptyBin,
|
||||||
];
|
];
|
||||||
|
@ -67,6 +71,7 @@ class EntrySetActions {
|
||||||
EntrySetAction.rescan,
|
EntrySetAction.rescan,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// `null` items are converted to dividers
|
||||||
static const pageSelection = [
|
static const pageSelection = [
|
||||||
EntrySetAction.share,
|
EntrySetAction.share,
|
||||||
EntrySetAction.delete,
|
EntrySetAction.delete,
|
||||||
|
@ -74,10 +79,13 @@ class EntrySetActions {
|
||||||
EntrySetAction.copy,
|
EntrySetAction.copy,
|
||||||
EntrySetAction.move,
|
EntrySetAction.move,
|
||||||
EntrySetAction.rename,
|
EntrySetAction.rename,
|
||||||
|
EntrySetAction.convert,
|
||||||
EntrySetAction.toggleFavourite,
|
EntrySetAction.toggleFavourite,
|
||||||
|
null,
|
||||||
EntrySetAction.map,
|
EntrySetAction.map,
|
||||||
EntrySetAction.slideshow,
|
EntrySetAction.slideshow,
|
||||||
EntrySetAction.stats,
|
EntrySetAction.stats,
|
||||||
|
null,
|
||||||
EntrySetAction.rescan,
|
EntrySetAction.rescan,
|
||||||
// editing actions are in their subsection
|
// editing actions are in their subsection
|
||||||
];
|
];
|
||||||
|
@ -89,6 +97,7 @@ class EntrySetActions {
|
||||||
EntrySetAction.copy,
|
EntrySetAction.copy,
|
||||||
EntrySetAction.move,
|
EntrySetAction.move,
|
||||||
EntrySetAction.rename,
|
EntrySetAction.rename,
|
||||||
|
EntrySetAction.convert,
|
||||||
EntrySetAction.toggleFavourite,
|
EntrySetAction.toggleFavourite,
|
||||||
EntrySetAction.map,
|
EntrySetAction.map,
|
||||||
EntrySetAction.slideshow,
|
EntrySetAction.slideshow,
|
||||||
|
@ -163,6 +172,8 @@ extension ExtraEntrySetAction on EntrySetAction {
|
||||||
return context.l10n.collectionActionMove;
|
return context.l10n.collectionActionMove;
|
||||||
case EntrySetAction.rename:
|
case EntrySetAction.rename:
|
||||||
return context.l10n.entryActionRename;
|
return context.l10n.entryActionRename;
|
||||||
|
case EntrySetAction.convert:
|
||||||
|
return context.l10n.entryActionConvert;
|
||||||
case EntrySetAction.toggleFavourite:
|
case EntrySetAction.toggleFavourite:
|
||||||
// different data depending on toggle state
|
// different data depending on toggle state
|
||||||
return context.l10n.entryActionAddFavourite;
|
return context.l10n.entryActionAddFavourite;
|
||||||
|
@ -232,6 +243,8 @@ extension ExtraEntrySetAction on EntrySetAction {
|
||||||
return AIcons.move;
|
return AIcons.move;
|
||||||
case EntrySetAction.rename:
|
case EntrySetAction.rename:
|
||||||
return AIcons.name;
|
return AIcons.name;
|
||||||
|
case EntrySetAction.convert:
|
||||||
|
return AIcons.convert;
|
||||||
case EntrySetAction.toggleFavourite:
|
case EntrySetAction.toggleFavourite:
|
||||||
// different data depending on toggle state
|
// different data depending on toggle state
|
||||||
return AIcons.favourite;
|
return AIcons.favourite;
|
||||||
|
|
|
@ -15,6 +15,8 @@ enum DateFieldSource {
|
||||||
exifGpsDate,
|
exifGpsDate,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum LengthUnit { px, percent }
|
||||||
|
|
||||||
enum LocationEditAction {
|
enum LocationEditAction {
|
||||||
chooseOnMap,
|
chooseOnMap,
|
||||||
copyItem,
|
copyItem,
|
||||||
|
|
14
lib/model/metadata/enums/length_unit.dart
Normal file
14
lib/model/metadata/enums/length_unit.dart
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import 'package:aves/model/metadata/enums/enums.dart';
|
||||||
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
extension ExtraLengthUnit on LengthUnit {
|
||||||
|
String getText(BuildContext context) {
|
||||||
|
switch (this) {
|
||||||
|
case LengthUnit.px:
|
||||||
|
return context.l10n.lengthUnitPixel;
|
||||||
|
case LengthUnit.percent:
|
||||||
|
return context.l10n.lengthUnitPercent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:aves/model/entry.dart';
|
import 'package:aves/model/entry.dart';
|
||||||
|
import 'package:aves/model/metadata/enums/enums.dart';
|
||||||
import 'package:aves/services/common/image_op_events.dart';
|
import 'package:aves/services/common/image_op_events.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/services/media/enums.dart';
|
import 'package:aves/services/media/enums.dart';
|
||||||
|
@ -28,7 +29,7 @@ abstract class MediaEditService {
|
||||||
|
|
||||||
Stream<ExportOpEvent> export(
|
Stream<ExportOpEvent> export(
|
||||||
Iterable<AvesEntry> entries, {
|
Iterable<AvesEntry> entries, {
|
||||||
required EntryExportOptions options,
|
required EntryConvertOptions options,
|
||||||
required String destinationAlbum,
|
required String destinationAlbum,
|
||||||
required NameConflictStrategy nameConflictStrategy,
|
required NameConflictStrategy nameConflictStrategy,
|
||||||
});
|
});
|
||||||
|
@ -113,16 +114,17 @@ class PlatformMediaEditService implements MediaEditService {
|
||||||
@override
|
@override
|
||||||
Stream<ExportOpEvent> export(
|
Stream<ExportOpEvent> export(
|
||||||
Iterable<AvesEntry> entries, {
|
Iterable<AvesEntry> entries, {
|
||||||
required EntryExportOptions options,
|
required EntryConvertOptions options,
|
||||||
required String destinationAlbum,
|
required String destinationAlbum,
|
||||||
required NameConflictStrategy nameConflictStrategy,
|
required NameConflictStrategy nameConflictStrategy,
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
return _opStream
|
return _opStream
|
||||||
.receiveBroadcastStream(<String, dynamic>{
|
.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'export',
|
'op': 'convert',
|
||||||
'entries': entries.map((entry) => entry.toPlatformEntryMap()).toList(),
|
'entries': entries.map((entry) => entry.toPlatformEntryMap()).toList(),
|
||||||
'mimeType': options.mimeType,
|
'mimeType': options.mimeType,
|
||||||
|
'lengthUnit': options.lengthUnit.name,
|
||||||
'width': options.width,
|
'width': options.width,
|
||||||
'height': options.height,
|
'height': options.height,
|
||||||
'destinationPath': destinationAlbum,
|
'destinationPath': destinationAlbum,
|
||||||
|
@ -183,15 +185,17 @@ class PlatformMediaEditService implements MediaEditService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class EntryExportOptions extends Equatable {
|
class EntryConvertOptions extends Equatable {
|
||||||
final String mimeType;
|
final String mimeType;
|
||||||
|
final LengthUnit lengthUnit;
|
||||||
final int width, height;
|
final int width, height;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [mimeType, width, height];
|
List<Object?> get props => [mimeType, lengthUnit, width, height];
|
||||||
|
|
||||||
const EntryExportOptions({
|
const EntryConvertOptions({
|
||||||
required this.mimeType,
|
required this.mimeType,
|
||||||
|
required this.lengthUnit,
|
||||||
required this.width,
|
required this.width,
|
||||||
required this.height,
|
required this.height,
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,6 +32,7 @@ import 'package:aves/widgets/common/tile_extent_controller.dart';
|
||||||
import 'package:aves/widgets/dialogs/tile_view_dialog.dart';
|
import 'package:aves/widgets/dialogs/tile_view_dialog.dart';
|
||||||
import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart';
|
import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart';
|
||||||
import 'package:aves/widgets/search/search_delegate.dart';
|
import 'package:aves/widgets/search/search_delegate.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -342,7 +343,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
return [
|
return [
|
||||||
...EntrySetActions.general,
|
...EntrySetActions.general,
|
||||||
...isSelecting ? EntrySetActions.pageSelection : EntrySetActions.pageBrowsing,
|
...isSelecting ? EntrySetActions.pageSelection : EntrySetActions.pageBrowsing,
|
||||||
].where(isVisible).map((action) {
|
].whereNotNull().where(isVisible).map((action) {
|
||||||
final enabled = canApply(action);
|
final enabled = canApply(action);
|
||||||
return CaptionedButton(
|
return CaptionedButton(
|
||||||
iconButtonBuilder: (context, focusNode) => _buildButtonIcon(
|
iconButtonBuilder: (context, focusNode) => _buildButtonIcon(
|
||||||
|
@ -388,10 +389,21 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
|
|
||||||
final browsingMenuActions = EntrySetActions.pageBrowsing.where((v) => !browsingQuickActions.contains(v));
|
final browsingMenuActions = EntrySetActions.pageBrowsing.where((v) => !browsingQuickActions.contains(v));
|
||||||
final selectionMenuActions = EntrySetActions.pageSelection.where((v) => !selectionQuickActions.contains(v));
|
final selectionMenuActions = EntrySetActions.pageSelection.where((v) => !selectionQuickActions.contains(v));
|
||||||
final contextualMenuItems = [
|
final contextualMenuActions = (isSelecting ? selectionMenuActions : browsingMenuActions).where((v) => v == null || isVisible(v)).fold(<EntrySetAction?>[], (prev, v) {
|
||||||
...(isSelecting ? selectionMenuActions : browsingMenuActions).where(isVisible).map(
|
if (v == null && (prev.isEmpty || prev.last == null)) return prev;
|
||||||
(action) => _toMenuItem(action, enabled: canApply(action), selection: selection),
|
return [...prev, v];
|
||||||
),
|
});
|
||||||
|
if (contextualMenuActions.isNotEmpty && contextualMenuActions.last == null) {
|
||||||
|
contextualMenuActions.removeLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
final contextualMenuItems = <PopupMenuEntry<EntrySetAction>>[
|
||||||
|
...contextualMenuActions.map(
|
||||||
|
(action) {
|
||||||
|
if (action == null) return const PopupMenuDivider();
|
||||||
|
return _toMenuItem(action, enabled: canApply(action), selection: selection);
|
||||||
|
},
|
||||||
|
),
|
||||||
if (isSelecting && !settings.isReadOnly && appMode == AppMode.main && !isTrash)
|
if (isSelecting && !settings.isReadOnly && appMode == AppMode.main && !isTrash)
|
||||||
PopupMenuItem<EntrySetAction>(
|
PopupMenuItem<EntrySetAction>(
|
||||||
enabled: hasSelection,
|
enabled: hasSelection,
|
||||||
|
@ -630,6 +642,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
case EntrySetAction.copy:
|
case EntrySetAction.copy:
|
||||||
case EntrySetAction.move:
|
case EntrySetAction.move:
|
||||||
case EntrySetAction.rename:
|
case EntrySetAction.rename:
|
||||||
|
case EntrySetAction.convert:
|
||||||
case EntrySetAction.toggleFavourite:
|
case EntrySetAction.toggleFavourite:
|
||||||
case EntrySetAction.rotateCCW:
|
case EntrySetAction.rotateCCW:
|
||||||
case EntrySetAction.rotateCW:
|
case EntrySetAction.rotateCW:
|
||||||
|
|
|
@ -94,6 +94,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
case EntrySetAction.copy:
|
case EntrySetAction.copy:
|
||||||
case EntrySetAction.move:
|
case EntrySetAction.move:
|
||||||
case EntrySetAction.rename:
|
case EntrySetAction.rename:
|
||||||
|
case EntrySetAction.convert:
|
||||||
case EntrySetAction.rotateCCW:
|
case EntrySetAction.rotateCCW:
|
||||||
case EntrySetAction.rotateCW:
|
case EntrySetAction.rotateCW:
|
||||||
case EntrySetAction.flip:
|
case EntrySetAction.flip:
|
||||||
|
@ -145,6 +146,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
case EntrySetAction.copy:
|
case EntrySetAction.copy:
|
||||||
case EntrySetAction.move:
|
case EntrySetAction.move:
|
||||||
case EntrySetAction.rename:
|
case EntrySetAction.rename:
|
||||||
|
case EntrySetAction.convert:
|
||||||
case EntrySetAction.toggleFavourite:
|
case EntrySetAction.toggleFavourite:
|
||||||
case EntrySetAction.rotateCCW:
|
case EntrySetAction.rotateCCW:
|
||||||
case EntrySetAction.rotateCW:
|
case EntrySetAction.rotateCW:
|
||||||
|
@ -211,6 +213,9 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
case EntrySetAction.rename:
|
case EntrySetAction.rename:
|
||||||
_rename(context);
|
_rename(context);
|
||||||
break;
|
break;
|
||||||
|
case EntrySetAction.convert:
|
||||||
|
_convert(context);
|
||||||
|
break;
|
||||||
case EntrySetAction.toggleFavourite:
|
case EntrySetAction.toggleFavourite:
|
||||||
_toggleFavourite(context);
|
_toggleFavourite(context);
|
||||||
break;
|
break;
|
||||||
|
@ -379,6 +384,13 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
_browse(context);
|
_browse(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _convert(BuildContext context) {
|
||||||
|
final entries = _getTargetItems(context);
|
||||||
|
convert(context, entries);
|
||||||
|
|
||||||
|
_browse(context);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _toggleFavourite(BuildContext context) async {
|
Future<void> _toggleFavourite(BuildContext context) async {
|
||||||
final entries = _getTargetItems(context);
|
final entries = _getTargetItems(context);
|
||||||
if (entries.every((entry) => entry.isFavourite)) {
|
if (entries.every((entry) => entry.isFavourite)) {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/services/common/image_op_events.dart';
|
import 'package:aves/services/common/image_op_events.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/services/media/enums.dart';
|
import 'package:aves/services/media/enums.dart';
|
||||||
|
import 'package:aves/services/media/media_edit_service.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:aves/widgets/aves_app.dart';
|
import 'package:aves/widgets/aves_app.dart';
|
||||||
|
@ -27,6 +28,7 @@ import 'package:aves/widgets/common/action_mixins/size_aware.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
|
||||||
|
import 'package:aves/widgets/dialogs/convert_entry_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
|
import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
|
||||||
import 'package:aves/widgets/viewer/notifications.dart';
|
import 'package:aves/widgets/viewer/notifications.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
@ -34,6 +36,100 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
||||||
|
Future<void> convert(BuildContext context, Set<AvesEntry> targetEntries) async {
|
||||||
|
final options = await showDialog<EntryConvertOptions>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ConvertEntryDialog(entries: targetEntries),
|
||||||
|
routeSettings: const RouteSettings(name: ConvertEntryDialog.routeName),
|
||||||
|
);
|
||||||
|
if (options == null) return;
|
||||||
|
|
||||||
|
final destinationAlbum = await pickAlbum(context: context, moveType: MoveType.export);
|
||||||
|
if (destinationAlbum == null) return;
|
||||||
|
if (!await checkStoragePermissionForAlbums(context, {destinationAlbum})) return;
|
||||||
|
|
||||||
|
if (!await checkFreeSpaceForMove(context, targetEntries, destinationAlbum, MoveType.export)) return;
|
||||||
|
|
||||||
|
final selection = <AvesEntry>{};
|
||||||
|
await Future.forEach(targetEntries, (targetEntry) async {
|
||||||
|
if (targetEntry.isMultiPage) {
|
||||||
|
final multiPageInfo = await targetEntry.getMultiPageInfo();
|
||||||
|
if (multiPageInfo != null) {
|
||||||
|
if (targetEntry.isMotionPhoto) {
|
||||||
|
await multiPageInfo.extractMotionPhotoVideo();
|
||||||
|
}
|
||||||
|
if (multiPageInfo.pageCount > 1) {
|
||||||
|
selection.addAll(multiPageInfo.exportEntries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selection.add(targetEntry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final selectionCount = selection.length;
|
||||||
|
final source = context.read<CollectionSource>();
|
||||||
|
source.pauseMonitoring();
|
||||||
|
await showOpReport<ExportOpEvent>(
|
||||||
|
context: context,
|
||||||
|
opStream: mediaEditService.export(
|
||||||
|
selection,
|
||||||
|
options: options,
|
||||||
|
destinationAlbum: destinationAlbum,
|
||||||
|
nameConflictStrategy: NameConflictStrategy.rename,
|
||||||
|
),
|
||||||
|
itemCount: selectionCount,
|
||||||
|
onDone: (processed) async {
|
||||||
|
final successOps = processed.where((e) => e.success).toSet();
|
||||||
|
final exportedOps = successOps.where((e) => !e.skipped).toSet();
|
||||||
|
final newUris = exportedOps.map((v) => v.newFields['uri'] as String?).whereNotNull().toSet();
|
||||||
|
final isMainMode = context.read<ValueNotifier<AppMode>>().value == AppMode.main;
|
||||||
|
|
||||||
|
source.resumeMonitoring();
|
||||||
|
unawaited(source.refreshUris(newUris));
|
||||||
|
|
||||||
|
final l10n = context.l10n;
|
||||||
|
final showAction = isMainMode && newUris.isNotEmpty
|
||||||
|
? SnackBarAction(
|
||||||
|
label: l10n.showButtonLabel,
|
||||||
|
onPressed: () {
|
||||||
|
// local context may be deactivated when action is triggered after navigation
|
||||||
|
final context = AvesApp.navigatorKey.currentContext;
|
||||||
|
if (context != null) {
|
||||||
|
Navigator.maybeOf(context)?.pushAndRemoveUntil(
|
||||||
|
MaterialPageRoute(
|
||||||
|
settings: const RouteSettings(name: CollectionPage.routeName),
|
||||||
|
builder: (context) => CollectionPage(
|
||||||
|
source: source,
|
||||||
|
filters: {AlbumFilter(destinationAlbum, source.getAlbumDisplayName(context, destinationAlbum))},
|
||||||
|
highlightTest: (entry) => newUris.contains(entry.uri),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(route) => false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
final successCount = successOps.length;
|
||||||
|
if (successCount < selectionCount) {
|
||||||
|
final count = selectionCount - successCount;
|
||||||
|
showFeedback(
|
||||||
|
context,
|
||||||
|
l10n.collectionExportFailureFeedback(count),
|
||||||
|
showAction,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
showFeedback(
|
||||||
|
context,
|
||||||
|
l10n.genericSuccessFeedback,
|
||||||
|
showAction,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> doQuickMove(
|
Future<void> doQuickMove(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required MoveType moveType,
|
required MoveType moveType,
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import 'package:aves/model/entry.dart';
|
import 'package:aves/model/entry.dart';
|
||||||
|
import 'package:aves/model/metadata/enums/enums.dart';
|
||||||
|
import 'package:aves/model/metadata/enums/length_unit.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
import 'package:aves/services/media/media_edit_service.dart';
|
import 'package:aves/services/media/media_edit_service.dart';
|
||||||
|
import 'package:aves/theme/themes.dart';
|
||||||
import 'package:aves/utils/mime_utils.dart';
|
import 'package:aves/utils/mime_utils.dart';
|
||||||
import 'package:aves/widgets/common/basic/text_dropdown_button.dart';
|
import 'package:aves/widgets/common/basic/text_dropdown_button.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
@ -8,26 +11,29 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'aves_dialog.dart';
|
import 'aves_dialog.dart';
|
||||||
|
|
||||||
class ExportEntryDialog extends StatefulWidget {
|
class ConvertEntryDialog extends StatefulWidget {
|
||||||
static const routeName = '/dialog/export_entry';
|
static const routeName = '/dialog/convert_entry';
|
||||||
|
|
||||||
final AvesEntry entry;
|
final Set<AvesEntry> entries;
|
||||||
|
|
||||||
const ExportEntryDialog({
|
const ConvertEntryDialog({
|
||||||
super.key,
|
super.key,
|
||||||
required this.entry,
|
required this.entries,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ExportEntryDialog> createState() => _ExportEntryDialogState();
|
State<ConvertEntryDialog> createState() => _ConvertEntryDialogState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ExportEntryDialogState extends State<ExportEntryDialog> {
|
class _ConvertEntryDialogState extends State<ConvertEntryDialog> {
|
||||||
final TextEditingController _widthController = TextEditingController(), _heightController = TextEditingController();
|
final TextEditingController _widthController = TextEditingController(), _heightController = TextEditingController();
|
||||||
final ValueNotifier<bool> _isValidNotifier = ValueNotifier(false);
|
final ValueNotifier<bool> _isValidNotifier = ValueNotifier(false);
|
||||||
String _mimeType = MimeTypes.jpeg;
|
late String _mimeType;
|
||||||
|
late bool _sameSized;
|
||||||
|
late List<LengthUnit> _lengthUnitOptions;
|
||||||
|
late LengthUnit _lengthUnit;
|
||||||
|
|
||||||
AvesEntry get entry => widget.entry;
|
Set<AvesEntry> get entries => widget.entries;
|
||||||
|
|
||||||
static const imageExportFormats = [
|
static const imageExportFormats = [
|
||||||
MimeTypes.bmp,
|
MimeTypes.bmp,
|
||||||
|
@ -39,11 +45,31 @@ class _ExportEntryDialogState extends State<ExportEntryDialog> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_widthController.text = '${entry.isRotated ? entry.height : entry.width}';
|
_mimeType = MimeTypes.jpeg;
|
||||||
_heightController.text = '${entry.isRotated ? entry.width : entry.height}';
|
_sameSized = entries.map((entry) => entry.displaySize).toSet().length == 1;
|
||||||
|
_lengthUnitOptions = [
|
||||||
|
if (_sameSized) LengthUnit.px,
|
||||||
|
LengthUnit.percent,
|
||||||
|
];
|
||||||
|
_lengthUnit = _lengthUnitOptions.first;
|
||||||
|
_initDimensions();
|
||||||
_validate();
|
_validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _initDimensions() {
|
||||||
|
switch (_lengthUnit) {
|
||||||
|
case LengthUnit.px:
|
||||||
|
final displaySize = entries.first.displaySize;
|
||||||
|
_widthController.text = '${displaySize.width.round()}';
|
||||||
|
_heightController.text = '${displaySize.height.round()}';
|
||||||
|
break;
|
||||||
|
case LengthUnit.percent:
|
||||||
|
_widthController.text = '100';
|
||||||
|
_heightController.text = '100';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_widthController.dispose();
|
_widthController.dispose();
|
||||||
|
@ -56,6 +82,14 @@ class _ExportEntryDialogState extends State<ExportEntryDialog> {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
const contentHorizontalPadding = EdgeInsets.symmetric(horizontal: AvesDialog.defaultHorizontalContentPadding);
|
const contentHorizontalPadding = EdgeInsets.symmetric(horizontal: AvesDialog.defaultHorizontalContentPadding);
|
||||||
|
|
||||||
|
// used by the drop down to match input decoration
|
||||||
|
final textFieldDecorationBorder = Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.38), //Color(0xFFBDBDBD),
|
||||||
|
width: 1.0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return AvesDialog(
|
return AvesDialog(
|
||||||
scrollableContent: [
|
scrollableContent: [
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
@ -92,12 +126,25 @@ class _ExportEntryDialogState extends State<ExportEntryDialog> {
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
final width = int.tryParse(value);
|
final width = int.tryParse(value);
|
||||||
_heightController.text = width != null ? '${(width / entry.displayAspectRatio).round()}' : '';
|
if (width != null) {
|
||||||
|
switch (_lengthUnit) {
|
||||||
|
case LengthUnit.px:
|
||||||
|
_heightController.text = '${(width / entries.first.displayAspectRatio).round()}';
|
||||||
|
break;
|
||||||
|
case LengthUnit.percent:
|
||||||
|
_heightController.text = '$width';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_heightController.text = '';
|
||||||
|
}
|
||||||
_validate();
|
_validate();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
const Text(AvesEntry.resolutionSeparator),
|
const Text(AvesEntry.resolutionSeparator),
|
||||||
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _heightController,
|
controller: _heightController,
|
||||||
|
@ -105,11 +152,46 @@ class _ExportEntryDialogState extends State<ExportEntryDialog> {
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
final height = int.tryParse(value);
|
final height = int.tryParse(value);
|
||||||
_widthController.text = height != null ? '${(height * entry.displayAspectRatio).round()}' : '';
|
if (height != null) {
|
||||||
|
switch (_lengthUnit) {
|
||||||
|
case LengthUnit.px:
|
||||||
|
_widthController.text = '${(height * entries.first.displayAspectRatio).round()}';
|
||||||
|
break;
|
||||||
|
case LengthUnit.percent:
|
||||||
|
_widthController.text = '$height';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_widthController.text = '';
|
||||||
|
}
|
||||||
_validate();
|
_validate();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
TextDropdownButton<LengthUnit>(
|
||||||
|
values: _lengthUnitOptions,
|
||||||
|
valueText: (v) => v.getText(context),
|
||||||
|
value: _lengthUnit,
|
||||||
|
onChanged: _lengthUnitOptions.length > 1
|
||||||
|
? (v) {
|
||||||
|
if (v != null && _lengthUnit != v) {
|
||||||
|
_lengthUnit = v;
|
||||||
|
_initDimensions();
|
||||||
|
_validate();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
underline: Container(
|
||||||
|
height: 1.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: textFieldDecorationBorder,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
itemHeight: 60,
|
||||||
|
dropdownColor: Themes.thirdLayerColor(context),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -126,8 +208,9 @@ class _ExportEntryDialogState extends State<ExportEntryDialog> {
|
||||||
final width = int.tryParse(_widthController.text);
|
final width = int.tryParse(_widthController.text);
|
||||||
final height = int.tryParse(_heightController.text);
|
final height = int.tryParse(_heightController.text);
|
||||||
final options = (width != null && height != null)
|
final options = (width != null && height != null)
|
||||||
? EntryExportOptions(
|
? EntryConvertOptions(
|
||||||
mimeType: _mimeType,
|
mimeType: _mimeType,
|
||||||
|
lengthUnit: _lengthUnit,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
)
|
)
|
|
@ -327,9 +327,13 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
|
||||||
|
|
||||||
final browsingMenuActions = ChipSetActions.browsing.where((v) => !browsingQuickActions.contains(v));
|
final browsingMenuActions = ChipSetActions.browsing.where((v) => !browsingQuickActions.contains(v));
|
||||||
final selectionMenuActions = ChipSetActions.selection.where((v) => !selectionQuickActions.contains(v));
|
final selectionMenuActions = ChipSetActions.selection.where((v) => !selectionQuickActions.contains(v));
|
||||||
final contextualMenuActions = (isSelecting ? selectionMenuActions : browsingMenuActions).where((v) => v == null || isVisible(v)).toList();
|
final contextualMenuActions = (isSelecting ? selectionMenuActions : browsingMenuActions).where((v) => v == null || isVisible(v)).fold(<ChipSetAction?>[], (prev, v) {
|
||||||
if (contextualMenuActions.isNotEmpty && contextualMenuActions.first == null) contextualMenuActions.removeAt(0);
|
if (v == null && (prev.isEmpty || prev.last == null)) return prev;
|
||||||
if (contextualMenuActions.isNotEmpty && contextualMenuActions.last == null) contextualMenuActions.removeLast();
|
return [...prev, v];
|
||||||
|
});
|
||||||
|
if (contextualMenuActions.isNotEmpty && contextualMenuActions.last == null) {
|
||||||
|
contextualMenuActions.removeLast();
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...generalMenuItems,
|
...generalMenuItems,
|
||||||
|
|
|
@ -8,20 +8,14 @@ import 'package:aves/model/actions/share_actions.dart';
|
||||||
import 'package:aves/model/device.dart';
|
import 'package:aves/model/device.dart';
|
||||||
import 'package:aves/model/entry.dart';
|
import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/model/entry_metadata_edition.dart';
|
import 'package:aves/model/entry_metadata_edition.dart';
|
||||||
import 'package:aves/model/filters/album.dart';
|
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/settings/enums/enums.dart';
|
import 'package:aves/model/settings/enums/enums.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/vaults/vaults.dart';
|
import 'package:aves/model/vaults/vaults.dart';
|
||||||
import 'package:aves/services/common/image_op_events.dart';
|
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/services/media/enums.dart';
|
|
||||||
import 'package:aves/services/media/media_edit_service.dart';
|
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/widgets/aves_app.dart';
|
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
|
||||||
import 'package:aves/widgets/common/action_mixins/entry_storage.dart';
|
import 'package:aves/widgets/common/action_mixins/entry_storage.dart';
|
||||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||||
import 'package:aves/widgets/common/action_mixins/permission_aware.dart';
|
import 'package:aves/widgets/common/action_mixins/permission_aware.dart';
|
||||||
|
@ -32,8 +26,6 @@ import 'package:aves/widgets/dialogs/add_shortcut_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
import 'package:aves/widgets/dialogs/aves_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/entry_editors/rename_entry_dialog.dart';
|
import 'package:aves/widgets/dialogs/entry_editors/rename_entry_dialog.dart';
|
||||||
import 'package:aves/widgets/dialogs/export_entry_dialog.dart';
|
|
||||||
import 'package:aves/widgets/dialogs/pick_dialogs/album_pick_page.dart';
|
|
||||||
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
|
import 'package:aves/widgets/viewer/action/entry_info_action_delegate.dart';
|
||||||
import 'package:aves/widgets/viewer/action/printer.dart';
|
import 'package:aves/widgets/viewer/action/printer.dart';
|
||||||
import 'package:aves/widgets/viewer/action/single_entry_editor.dart';
|
import 'package:aves/widgets/viewer/action/single_entry_editor.dart';
|
||||||
|
@ -42,7 +34,6 @@ import 'package:aves/widgets/viewer/multipage/conductor.dart';
|
||||||
import 'package:aves/widgets/viewer/notifications.dart';
|
import 'package:aves/widgets/viewer/notifications.dart';
|
||||||
import 'package:aves/widgets/viewer/source_viewer_page.dart';
|
import 'package:aves/widgets/viewer/source_viewer_page.dart';
|
||||||
import 'package:aves/widgets/viewer/video/conductor.dart';
|
import 'package:aves/widgets/viewer/video/conductor.dart';
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
|
@ -197,7 +188,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
_move(context, targetEntry, moveType: MoveType.fromBin);
|
_move(context, targetEntry, moveType: MoveType.fromBin);
|
||||||
break;
|
break;
|
||||||
case EntryAction.convert:
|
case EntryAction.convert:
|
||||||
_convert(context, targetEntry);
|
convert(context, {targetEntry});
|
||||||
break;
|
break;
|
||||||
case EntryAction.print:
|
case EntryAction.print:
|
||||||
EntryPrinter(targetEntry).print(context);
|
EntryPrinter(targetEntry).print(context);
|
||||||
|
@ -412,98 +403,6 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _convert(BuildContext context, AvesEntry targetEntry) async {
|
|
||||||
final options = await showDialog<EntryExportOptions>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => ExportEntryDialog(entry: targetEntry),
|
|
||||||
routeSettings: const RouteSettings(name: ExportEntryDialog.routeName),
|
|
||||||
);
|
|
||||||
if (options == null) return;
|
|
||||||
|
|
||||||
final destinationAlbum = await pickAlbum(context: context, moveType: MoveType.export);
|
|
||||||
if (destinationAlbum == null) return;
|
|
||||||
if (!await checkStoragePermissionForAlbums(context, {destinationAlbum})) return;
|
|
||||||
|
|
||||||
if (!await checkFreeSpaceForMove(context, {targetEntry}, destinationAlbum, MoveType.export)) return;
|
|
||||||
|
|
||||||
final selection = <AvesEntry>{};
|
|
||||||
if (targetEntry.isMultiPage) {
|
|
||||||
final multiPageInfo = await targetEntry.getMultiPageInfo();
|
|
||||||
if (multiPageInfo != null) {
|
|
||||||
if (targetEntry.isMotionPhoto) {
|
|
||||||
await multiPageInfo.extractMotionPhotoVideo();
|
|
||||||
}
|
|
||||||
if (multiPageInfo.pageCount > 1) {
|
|
||||||
selection.addAll(multiPageInfo.exportEntries);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
selection.add(targetEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
final selectionCount = selection.length;
|
|
||||||
final source = context.read<CollectionSource>();
|
|
||||||
source.pauseMonitoring();
|
|
||||||
await showOpReport<ExportOpEvent>(
|
|
||||||
context: context,
|
|
||||||
opStream: mediaEditService.export(
|
|
||||||
selection,
|
|
||||||
options: options,
|
|
||||||
destinationAlbum: destinationAlbum,
|
|
||||||
nameConflictStrategy: NameConflictStrategy.rename,
|
|
||||||
),
|
|
||||||
itemCount: selectionCount,
|
|
||||||
onDone: (processed) async {
|
|
||||||
final successOps = processed.where((e) => e.success).toSet();
|
|
||||||
final exportedOps = successOps.where((e) => !e.skipped).toSet();
|
|
||||||
final newUris = exportedOps.map((v) => v.newFields['uri'] as String?).whereNotNull().toSet();
|
|
||||||
final isMainMode = context.read<ValueNotifier<AppMode>>().value == AppMode.main;
|
|
||||||
|
|
||||||
source.resumeMonitoring();
|
|
||||||
unawaited(source.refreshUris(newUris));
|
|
||||||
|
|
||||||
final l10n = context.l10n;
|
|
||||||
final showAction = isMainMode && newUris.isNotEmpty
|
|
||||||
? SnackBarAction(
|
|
||||||
label: l10n.showButtonLabel,
|
|
||||||
onPressed: () {
|
|
||||||
// local context may be deactivated when action is triggered after navigation
|
|
||||||
final context = AvesApp.navigatorKey.currentContext;
|
|
||||||
if (context != null) {
|
|
||||||
Navigator.maybeOf(context)?.pushAndRemoveUntil(
|
|
||||||
MaterialPageRoute(
|
|
||||||
settings: const RouteSettings(name: CollectionPage.routeName),
|
|
||||||
builder: (context) => CollectionPage(
|
|
||||||
source: source,
|
|
||||||
filters: {AlbumFilter(destinationAlbum, source.getAlbumDisplayName(context, destinationAlbum))},
|
|
||||||
highlightTest: (entry) => newUris.contains(entry.uri),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(route) => false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
final successCount = successOps.length;
|
|
||||||
if (successCount < selectionCount) {
|
|
||||||
final count = selectionCount - successCount;
|
|
||||||
showFeedback(
|
|
||||||
context,
|
|
||||||
l10n.collectionExportFailureFeedback(count),
|
|
||||||
showAction,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
showFeedback(
|
|
||||||
context,
|
|
||||||
l10n.genericSuccessFeedback,
|
|
||||||
showAction,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _move(BuildContext context, AvesEntry targetEntry, {required MoveType moveType}) => doMove(
|
Future<void> _move(BuildContext context, AvesEntry targetEntry, {required MoveType moveType}) => doMove(
|
||||||
context,
|
context,
|
||||||
moveType: moveType,
|
moveType: moveType,
|
||||||
|
|
|
@ -116,6 +116,8 @@
|
||||||
"keepScreenOnVideoPlayback",
|
"keepScreenOnVideoPlayback",
|
||||||
"keepScreenOnViewerOnly",
|
"keepScreenOnViewerOnly",
|
||||||
"keepScreenOnAlways",
|
"keepScreenOnAlways",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"mapStyleGoogleNormal",
|
"mapStyleGoogleNormal",
|
||||||
"mapStyleGoogleHybrid",
|
"mapStyleGoogleHybrid",
|
||||||
"mapStyleGoogleTerrain",
|
"mapStyleGoogleTerrain",
|
||||||
|
@ -600,6 +602,8 @@
|
||||||
"chipActionCreateVault",
|
"chipActionCreateVault",
|
||||||
"chipActionConfigureVault",
|
"chipActionConfigureVault",
|
||||||
"albumTierVaults",
|
"albumTierVaults",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
"vaultLockTypePassword",
|
"vaultLockTypePassword",
|
||||||
"newVaultWarningDialogMessage",
|
"newVaultWarningDialogMessage",
|
||||||
|
@ -627,6 +631,8 @@
|
||||||
"chipActionCreateVault",
|
"chipActionCreateVault",
|
||||||
"chipActionConfigureVault",
|
"chipActionConfigureVault",
|
||||||
"albumTierVaults",
|
"albumTierVaults",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
"vaultLockTypePassword",
|
"vaultLockTypePassword",
|
||||||
"newVaultWarningDialogMessage",
|
"newVaultWarningDialogMessage",
|
||||||
|
@ -650,6 +656,8 @@
|
||||||
|
|
||||||
"el": [
|
"el": [
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"drawerPlacePage",
|
"drawerPlacePage",
|
||||||
"placePageTitle",
|
"placePageTitle",
|
||||||
"placeEmpty"
|
"placeEmpty"
|
||||||
|
@ -657,6 +665,8 @@
|
||||||
|
|
||||||
"es": [
|
"es": [
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"drawerPlacePage",
|
"drawerPlacePage",
|
||||||
"placePageTitle",
|
"placePageTitle",
|
||||||
"placeEmpty"
|
"placeEmpty"
|
||||||
|
@ -668,6 +678,8 @@
|
||||||
"chipActionCreateVault",
|
"chipActionCreateVault",
|
||||||
"chipActionConfigureVault",
|
"chipActionConfigureVault",
|
||||||
"albumTierVaults",
|
"albumTierVaults",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
"vaultLockTypePassword",
|
"vaultLockTypePassword",
|
||||||
"newVaultWarningDialogMessage",
|
"newVaultWarningDialogMessage",
|
||||||
|
@ -725,6 +737,8 @@
|
||||||
"keepScreenOnVideoPlayback",
|
"keepScreenOnVideoPlayback",
|
||||||
"keepScreenOnViewerOnly",
|
"keepScreenOnViewerOnly",
|
||||||
"keepScreenOnAlways",
|
"keepScreenOnAlways",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"nameConflictStrategySkip",
|
"nameConflictStrategySkip",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
"vaultLockTypePassword",
|
"vaultLockTypePassword",
|
||||||
|
@ -1156,6 +1170,8 @@
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"drawerPlacePage",
|
"drawerPlacePage",
|
||||||
"placePageTitle",
|
"placePageTitle",
|
||||||
"placeEmpty"
|
"placeEmpty"
|
||||||
|
@ -1187,6 +1203,8 @@
|
||||||
"displayRefreshRatePreferHighest",
|
"displayRefreshRatePreferHighest",
|
||||||
"displayRefreshRatePreferLowest",
|
"displayRefreshRatePreferLowest",
|
||||||
"keepScreenOnVideoPlayback",
|
"keepScreenOnVideoPlayback",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"subtitlePositionTop",
|
"subtitlePositionTop",
|
||||||
"subtitlePositionBottom",
|
"subtitlePositionBottom",
|
||||||
"themeBrightnessLight",
|
"themeBrightnessLight",
|
||||||
|
@ -1793,6 +1811,8 @@
|
||||||
"keepScreenOnVideoPlayback",
|
"keepScreenOnVideoPlayback",
|
||||||
"keepScreenOnViewerOnly",
|
"keepScreenOnViewerOnly",
|
||||||
"keepScreenOnAlways",
|
"keepScreenOnAlways",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"mapStyleGoogleNormal",
|
"mapStyleGoogleNormal",
|
||||||
"mapStyleGoogleHybrid",
|
"mapStyleGoogleHybrid",
|
||||||
"mapStyleGoogleTerrain",
|
"mapStyleGoogleTerrain",
|
||||||
|
@ -2286,6 +2306,8 @@
|
||||||
|
|
||||||
"id": [
|
"id": [
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"drawerPlacePage",
|
"drawerPlacePage",
|
||||||
"placePageTitle",
|
"placePageTitle",
|
||||||
"placeEmpty"
|
"placeEmpty"
|
||||||
|
@ -2297,6 +2319,8 @@
|
||||||
"chipActionCreateVault",
|
"chipActionCreateVault",
|
||||||
"chipActionConfigureVault",
|
"chipActionConfigureVault",
|
||||||
"albumTierVaults",
|
"albumTierVaults",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
"vaultLockTypePassword",
|
"vaultLockTypePassword",
|
||||||
"newVaultWarningDialogMessage",
|
"newVaultWarningDialogMessage",
|
||||||
|
@ -2332,6 +2356,8 @@
|
||||||
"filterTaggedLabel",
|
"filterTaggedLabel",
|
||||||
"albumTierVaults",
|
"albumTierVaults",
|
||||||
"keepScreenOnVideoPlayback",
|
"keepScreenOnVideoPlayback",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"subtitlePositionTop",
|
"subtitlePositionTop",
|
||||||
"subtitlePositionBottom",
|
"subtitlePositionBottom",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
|
@ -2364,6 +2390,8 @@
|
||||||
|
|
||||||
"ko": [
|
"ko": [
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"drawerPlacePage",
|
"drawerPlacePage",
|
||||||
"placePageTitle",
|
"placePageTitle",
|
||||||
"placeEmpty"
|
"placeEmpty"
|
||||||
|
@ -2379,6 +2407,8 @@
|
||||||
"filterTaggedLabel",
|
"filterTaggedLabel",
|
||||||
"albumTierVaults",
|
"albumTierVaults",
|
||||||
"keepScreenOnVideoPlayback",
|
"keepScreenOnVideoPlayback",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
"vaultLockTypePassword",
|
"vaultLockTypePassword",
|
||||||
"newVaultWarningDialogMessage",
|
"newVaultWarningDialogMessage",
|
||||||
|
@ -2412,6 +2442,8 @@
|
||||||
"chipActionCreateVault",
|
"chipActionCreateVault",
|
||||||
"chipActionConfigureVault",
|
"chipActionConfigureVault",
|
||||||
"albumTierVaults",
|
"albumTierVaults",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
"vaultLockTypePassword",
|
"vaultLockTypePassword",
|
||||||
"newVaultWarningDialogMessage",
|
"newVaultWarningDialogMessage",
|
||||||
|
@ -2450,6 +2482,8 @@
|
||||||
"filterTaggedLabel",
|
"filterTaggedLabel",
|
||||||
"albumTierVaults",
|
"albumTierVaults",
|
||||||
"keepScreenOnVideoPlayback",
|
"keepScreenOnVideoPlayback",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"subtitlePositionTop",
|
"subtitlePositionTop",
|
||||||
"subtitlePositionBottom",
|
"subtitlePositionBottom",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
|
@ -2500,6 +2534,8 @@
|
||||||
"albumTierVaults",
|
"albumTierVaults",
|
||||||
"displayRefreshRatePreferHighest",
|
"displayRefreshRatePreferHighest",
|
||||||
"displayRefreshRatePreferLowest",
|
"displayRefreshRatePreferLowest",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
"vaultLockTypePassword",
|
"vaultLockTypePassword",
|
||||||
"wallpaperTargetHome",
|
"wallpaperTargetHome",
|
||||||
|
@ -2798,6 +2834,8 @@
|
||||||
|
|
||||||
"pl": [
|
"pl": [
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"drawerPlacePage",
|
"drawerPlacePage",
|
||||||
"placePageTitle",
|
"placePageTitle",
|
||||||
"placeEmpty"
|
"placeEmpty"
|
||||||
|
@ -2810,6 +2848,8 @@
|
||||||
"chipActionCreateVault",
|
"chipActionCreateVault",
|
||||||
"chipActionConfigureVault",
|
"chipActionConfigureVault",
|
||||||
"albumTierVaults",
|
"albumTierVaults",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
"vaultLockTypePassword",
|
"vaultLockTypePassword",
|
||||||
"newVaultWarningDialogMessage",
|
"newVaultWarningDialogMessage",
|
||||||
|
@ -2835,6 +2875,8 @@
|
||||||
|
|
||||||
"ro": [
|
"ro": [
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"drawerPlacePage",
|
"drawerPlacePage",
|
||||||
"placePageTitle",
|
"placePageTitle",
|
||||||
"placeEmpty"
|
"placeEmpty"
|
||||||
|
@ -2848,6 +2890,8 @@
|
||||||
"filterLocatedLabel",
|
"filterLocatedLabel",
|
||||||
"filterTaggedLabel",
|
"filterTaggedLabel",
|
||||||
"albumTierVaults",
|
"albumTierVaults",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
"vaultLockTypePassword",
|
"vaultLockTypePassword",
|
||||||
"newVaultWarningDialogMessage",
|
"newVaultWarningDialogMessage",
|
||||||
|
@ -2885,6 +2929,8 @@
|
||||||
"filterNoLocationLabel",
|
"filterNoLocationLabel",
|
||||||
"albumTierVaults",
|
"albumTierVaults",
|
||||||
"coordinateDms",
|
"coordinateDms",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
"vaultLockTypePassword",
|
"vaultLockTypePassword",
|
||||||
"otherDirectoryDescription",
|
"otherDirectoryDescription",
|
||||||
|
@ -3300,6 +3346,8 @@
|
||||||
"chipActionCreateVault",
|
"chipActionCreateVault",
|
||||||
"chipActionConfigureVault",
|
"chipActionConfigureVault",
|
||||||
"albumTierVaults",
|
"albumTierVaults",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
"vaultLockTypePassword",
|
"vaultLockTypePassword",
|
||||||
"newVaultWarningDialogMessage",
|
"newVaultWarningDialogMessage",
|
||||||
|
@ -3642,6 +3690,8 @@
|
||||||
"chipActionCreateVault",
|
"chipActionCreateVault",
|
||||||
"chipActionConfigureVault",
|
"chipActionConfigureVault",
|
||||||
"albumTierVaults",
|
"albumTierVaults",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
"vaultLockTypePassword",
|
"vaultLockTypePassword",
|
||||||
"newVaultWarningDialogMessage",
|
"newVaultWarningDialogMessage",
|
||||||
|
@ -3665,6 +3715,8 @@
|
||||||
|
|
||||||
"uk": [
|
"uk": [
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"drawerPlacePage",
|
"drawerPlacePage",
|
||||||
"placePageTitle",
|
"placePageTitle",
|
||||||
"placeEmpty"
|
"placeEmpty"
|
||||||
|
@ -3678,6 +3730,8 @@
|
||||||
"filterLocatedLabel",
|
"filterLocatedLabel",
|
||||||
"filterTaggedLabel",
|
"filterTaggedLabel",
|
||||||
"albumTierVaults",
|
"albumTierVaults",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
"vaultLockTypePassword",
|
"vaultLockTypePassword",
|
||||||
"newVaultWarningDialogMessage",
|
"newVaultWarningDialogMessage",
|
||||||
|
@ -3714,6 +3768,8 @@
|
||||||
"filterLocatedLabel",
|
"filterLocatedLabel",
|
||||||
"filterTaggedLabel",
|
"filterTaggedLabel",
|
||||||
"albumTierVaults",
|
"albumTierVaults",
|
||||||
|
"lengthUnitPixel",
|
||||||
|
"lengthUnitPercent",
|
||||||
"vaultLockTypePin",
|
"vaultLockTypePin",
|
||||||
"vaultLockTypePassword",
|
"vaultLockTypePassword",
|
||||||
"newVaultWarningDialogMessage",
|
"newVaultWarningDialogMessage",
|
||||||
|
|
Loading…
Reference in a new issue