#160 export: fixed svg, added size parameter
This commit is contained in:
parent
f7183156bf
commit
e548134d30
12 changed files with 176 additions and 40 deletions
|
@ -13,7 +13,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.bumptech.glide.signature.ObjectKey
|
import com.bumptech.glide.signature.ObjectKey
|
||||||
import deckers.thibault.aves.decoder.MultiTrackImage
|
import deckers.thibault.aves.decoder.MultiTrackImage
|
||||||
import deckers.thibault.aves.decoder.SvgThumbnail
|
import deckers.thibault.aves.decoder.SvgImage
|
||||||
import deckers.thibault.aves.decoder.TiffImage
|
import deckers.thibault.aves.decoder.TiffImage
|
||||||
import deckers.thibault.aves.decoder.VideoThumbnail
|
import deckers.thibault.aves.decoder.VideoThumbnail
|
||||||
import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation
|
import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation
|
||||||
|
@ -128,7 +128,7 @@ class ThumbnailFetcher internal constructor(
|
||||||
.submit(width, height)
|
.submit(width, height)
|
||||||
} else {
|
} else {
|
||||||
val model: Any = when {
|
val model: Any = when {
|
||||||
svgFetch -> SvgThumbnail(context, uri)
|
svgFetch -> SvgImage(context, uri)
|
||||||
tiffFetch -> TiffImage(context, uri, pageId)
|
tiffFetch -> TiffImage(context, uri, pageId)
|
||||||
multiTrackFetch -> MultiTrackImage(context, uri, pageId)
|
multiTrackFetch -> MultiTrackImage(context, uri, pageId)
|
||||||
else -> StorageUtils.getGlideSafeUri(uri, mimeType)
|
else -> StorageUtils.getGlideSafeUri(uri, mimeType)
|
||||||
|
|
|
@ -134,8 +134,10 @@ 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 width = arguments["width"] as Int?
|
||||||
|
val height = arguments["height"] as Int?
|
||||||
val nameConflictStrategy = NameConflictStrategy.get(arguments["nameConflictStrategy"] as String?)
|
val nameConflictStrategy = NameConflictStrategy.get(arguments["nameConflictStrategy"] as String?)
|
||||||
if (destinationDir == null || mimeType == null || nameConflictStrategy == null) {
|
if (destinationDir == null || mimeType == null || width == null || height == null || nameConflictStrategy == null) {
|
||||||
error("export-args", "failed because of missing arguments", null)
|
error("export-args", "failed because of missing arguments", null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -150,7 +152,7 @@ class ImageOpStreamHandler(private val activity: Activity, private val arguments
|
||||||
|
|
||||||
destinationDir = StorageUtils.ensureTrailingSeparator(destinationDir)
|
destinationDir = StorageUtils.ensureTrailingSeparator(destinationDir)
|
||||||
val entries = entryMapList.map(::AvesEntry)
|
val entries = entryMapList.map(::AvesEntry)
|
||||||
provider.exportMultiple(activity, mimeType, destinationDir, entries, nameConflictStrategy, object : ImageOpCallback {
|
provider.exportMultiple(activity, mimeType, destinationDir, entries, 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("export-failure", "failed to export entries", throwable)
|
||||||
})
|
})
|
||||||
|
|
|
@ -25,27 +25,27 @@ import kotlin.math.ceil
|
||||||
@GlideModule
|
@GlideModule
|
||||||
class SvgGlideModule : LibraryGlideModule() {
|
class SvgGlideModule : LibraryGlideModule() {
|
||||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
||||||
registry.append(SvgThumbnail::class.java, Bitmap::class.java, SvgLoader.Factory())
|
registry.append(SvgImage::class.java, Bitmap::class.java, SvgLoader.Factory())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SvgThumbnail(val context: Context, val uri: Uri)
|
class SvgImage(val context: Context, val uri: Uri)
|
||||||
|
|
||||||
internal class SvgLoader : ModelLoader<SvgThumbnail, Bitmap> {
|
internal class SvgLoader : ModelLoader<SvgImage, Bitmap> {
|
||||||
override fun buildLoadData(model: SvgThumbnail, width: Int, height: Int, options: Options): ModelLoader.LoadData<Bitmap> {
|
override fun buildLoadData(model: SvgImage, width: Int, height: Int, options: Options): ModelLoader.LoadData<Bitmap> {
|
||||||
return ModelLoader.LoadData(ObjectKey(model.uri), SvgFetcher(model, width, height))
|
return ModelLoader.LoadData(ObjectKey(model.uri), SvgFetcher(model, width, height))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handles(model: SvgThumbnail): Boolean = true
|
override fun handles(model: SvgImage): Boolean = true
|
||||||
|
|
||||||
internal class Factory : ModelLoaderFactory<SvgThumbnail, Bitmap> {
|
internal class Factory : ModelLoaderFactory<SvgImage, Bitmap> {
|
||||||
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<SvgThumbnail, Bitmap> = SvgLoader()
|
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<SvgImage, Bitmap> = SvgLoader()
|
||||||
|
|
||||||
override fun teardown() {}
|
override fun teardown() {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class SvgFetcher(val model: SvgThumbnail, val width: Int, val height: Int) : DataFetcher<Bitmap> {
|
internal class SvgFetcher(val model: SvgImage, val width: Int, val height: Int) : DataFetcher<Bitmap> {
|
||||||
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in Bitmap>) {
|
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in Bitmap>) {
|
||||||
val context = model.context
|
val context = model.context
|
||||||
val uri = model.uri
|
val uri = model.uri
|
||||||
|
|
|
@ -16,6 +16,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.commonsware.cwac.document.DocumentFileCompat
|
import com.commonsware.cwac.document.DocumentFileCompat
|
||||||
import deckers.thibault.aves.decoder.MultiTrackImage
|
import deckers.thibault.aves.decoder.MultiTrackImage
|
||||||
|
import deckers.thibault.aves.decoder.SvgImage
|
||||||
import deckers.thibault.aves.decoder.TiffImage
|
import deckers.thibault.aves.decoder.TiffImage
|
||||||
import deckers.thibault.aves.metadata.*
|
import deckers.thibault.aves.metadata.*
|
||||||
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDateMillis
|
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDateMillis
|
||||||
|
@ -82,6 +83,8 @@ abstract class ImageProvider {
|
||||||
imageExportMimeType: String,
|
imageExportMimeType: String,
|
||||||
targetDir: String,
|
targetDir: String,
|
||||||
entries: List<AvesEntry>,
|
entries: List<AvesEntry>,
|
||||||
|
width: Int,
|
||||||
|
height: Int,
|
||||||
nameConflictStrategy: NameConflictStrategy,
|
nameConflictStrategy: NameConflictStrategy,
|
||||||
callback: ImageOpCallback,
|
callback: ImageOpCallback,
|
||||||
) {
|
) {
|
||||||
|
@ -120,6 +123,8 @@ abstract class ImageProvider {
|
||||||
sourceEntry = entry,
|
sourceEntry = entry,
|
||||||
targetDir = targetDir,
|
targetDir = targetDir,
|
||||||
targetDirDocFile = targetDirDocFile,
|
targetDirDocFile = targetDirDocFile,
|
||||||
|
width = width,
|
||||||
|
height = height,
|
||||||
nameConflictStrategy = nameConflictStrategy,
|
nameConflictStrategy = nameConflictStrategy,
|
||||||
exportMimeType = exportMimeType,
|
exportMimeType = exportMimeType,
|
||||||
)
|
)
|
||||||
|
@ -138,6 +143,8 @@ abstract class ImageProvider {
|
||||||
sourceEntry: AvesEntry,
|
sourceEntry: AvesEntry,
|
||||||
targetDir: String,
|
targetDir: String,
|
||||||
targetDirDocFile: DocumentFileCompat,
|
targetDirDocFile: DocumentFileCompat,
|
||||||
|
width: Int,
|
||||||
|
height: Int,
|
||||||
nameConflictStrategy: NameConflictStrategy,
|
nameConflictStrategy: NameConflictStrategy,
|
||||||
exportMimeType: String,
|
exportMimeType: String,
|
||||||
): FieldMap {
|
): FieldMap {
|
||||||
|
@ -178,6 +185,8 @@ abstract class ImageProvider {
|
||||||
MultiTrackImage(activity, sourceUri, pageId)
|
MultiTrackImage(activity, sourceUri, pageId)
|
||||||
} else if (sourceMimeType == MimeTypes.TIFF) {
|
} else if (sourceMimeType == MimeTypes.TIFF) {
|
||||||
TiffImage(activity, sourceUri, pageId)
|
TiffImage(activity, sourceUri, pageId)
|
||||||
|
} else if (sourceMimeType == MimeTypes.SVG) {
|
||||||
|
SvgImage(activity, sourceUri)
|
||||||
} else {
|
} else {
|
||||||
StorageUtils.getGlideSafeUri(sourceUri, sourceMimeType)
|
StorageUtils.getGlideSafeUri(sourceUri, sourceMimeType)
|
||||||
}
|
}
|
||||||
|
@ -192,7 +201,7 @@ abstract class ImageProvider {
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.apply(glideOptions)
|
.apply(glideOptions)
|
||||||
.load(model)
|
.load(model)
|
||||||
.submit()
|
.submit(width, height)
|
||||||
try {
|
try {
|
||||||
var bitmap = target.get()
|
var bitmap = target.get()
|
||||||
if (MimeTypes.needRotationAfterGlide(sourceMimeType)) {
|
if (MimeTypes.needRotationAfterGlide(sourceMimeType)) {
|
||||||
|
|
|
@ -291,6 +291,8 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"exportEntryDialogFormat": "Format:",
|
"exportEntryDialogFormat": "Format:",
|
||||||
|
"exportEntryDialogWidth": "Width",
|
||||||
|
"exportEntryDialogHeight": "Height",
|
||||||
|
|
||||||
"renameEntryDialogLabel": "New name",
|
"renameEntryDialogLabel": "New name",
|
||||||
|
|
||||||
|
|
|
@ -179,6 +179,8 @@
|
||||||
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Voulez-vous vraiment supprimer ces albums et leur élément ?} other{Voulez-vous vraiment supprimer ces albums et leurs {count} éléments ?}}",
|
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Voulez-vous vraiment supprimer ces albums et leur élément ?} other{Voulez-vous vraiment supprimer ces albums et leurs {count} éléments ?}}",
|
||||||
|
|
||||||
"exportEntryDialogFormat": "Format :",
|
"exportEntryDialogFormat": "Format :",
|
||||||
|
"exportEntryDialogWidth": "Largeur",
|
||||||
|
"exportEntryDialogHeight": "Hauteur",
|
||||||
|
|
||||||
"renameEntryDialogLabel": "Nouveau nom",
|
"renameEntryDialogLabel": "Nouveau nom",
|
||||||
|
|
||||||
|
|
|
@ -179,6 +179,8 @@
|
||||||
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, other{이 앨범들의 항목 {count}개를 삭제하시겠습니까?}}",
|
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, other{이 앨범들의 항목 {count}개를 삭제하시겠습니까?}}",
|
||||||
|
|
||||||
"exportEntryDialogFormat": "형식:",
|
"exportEntryDialogFormat": "형식:",
|
||||||
|
"exportEntryDialogWidth": "가로",
|
||||||
|
"exportEntryDialogHeight": "세로",
|
||||||
|
|
||||||
"renameEntryDialogLabel": "이름",
|
"renameEntryDialogLabel": "이름",
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import 'package:aves/services/common/output_buffer.dart';
|
||||||
import 'package:aves/services/common/service_policy.dart';
|
import 'package:aves/services/common/service_policy.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:equatable/equatable.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:streams_channel/streams_channel.dart';
|
import 'package:streams_channel/streams_channel.dart';
|
||||||
|
@ -87,7 +88,7 @@ abstract class MediaFileService {
|
||||||
|
|
||||||
Stream<ExportOpEvent> export(
|
Stream<ExportOpEvent> export(
|
||||||
Iterable<AvesEntry> entries, {
|
Iterable<AvesEntry> entries, {
|
||||||
required String mimeType,
|
required EntryExportOptions options,
|
||||||
required String destinationAlbum,
|
required String destinationAlbum,
|
||||||
required NameConflictStrategy nameConflictStrategy,
|
required NameConflictStrategy nameConflictStrategy,
|
||||||
});
|
});
|
||||||
|
@ -368,7 +369,7 @@ class PlatformMediaFileService implements MediaFileService {
|
||||||
@override
|
@override
|
||||||
Stream<ExportOpEvent> export(
|
Stream<ExportOpEvent> export(
|
||||||
Iterable<AvesEntry> entries, {
|
Iterable<AvesEntry> entries, {
|
||||||
required String mimeType,
|
required EntryExportOptions options,
|
||||||
required String destinationAlbum,
|
required String destinationAlbum,
|
||||||
required NameConflictStrategy nameConflictStrategy,
|
required NameConflictStrategy nameConflictStrategy,
|
||||||
}) {
|
}) {
|
||||||
|
@ -377,7 +378,9 @@ class PlatformMediaFileService implements MediaFileService {
|
||||||
.receiveBroadcastStream(<String, dynamic>{
|
.receiveBroadcastStream(<String, dynamic>{
|
||||||
'op': 'export',
|
'op': 'export',
|
||||||
'entries': entries.map(_toPlatformEntryMap).toList(),
|
'entries': entries.map(_toPlatformEntryMap).toList(),
|
||||||
'mimeType': mimeType,
|
'mimeType': options.mimeType,
|
||||||
|
'width': options.width,
|
||||||
|
'height': options.height,
|
||||||
'destinationPath': destinationAlbum,
|
'destinationPath': destinationAlbum,
|
||||||
'nameConflictStrategy': nameConflictStrategy.toPlatform(),
|
'nameConflictStrategy': nameConflictStrategy.toPlatform(),
|
||||||
})
|
})
|
||||||
|
@ -434,3 +437,18 @@ class PlatformMediaFileService implements MediaFileService {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class EntryExportOptions extends Equatable {
|
||||||
|
final String mimeType;
|
||||||
|
final int width, height;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [mimeType, width, height];
|
||||||
|
|
||||||
|
const EntryExportOptions({
|
||||||
|
required this.mimeType,
|
||||||
|
required this.width,
|
||||||
|
required this.height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -293,7 +293,10 @@ class _GeoMapState extends State<GeoMap> {
|
||||||
// node size: 64 by default, higher means faster indexing but slower search
|
// node size: 64 by default, higher means faster indexing but slower search
|
||||||
nodeSize: nodeSize,
|
nodeSize: nodeSize,
|
||||||
points: markers,
|
points: markers,
|
||||||
createCluster: GeoEntry.createCluster,
|
// use lambda instead of tear-off because of runtime exception when using
|
||||||
|
// `T Function(BaseCluster, double, double)` for `T Function(BaseCluster?, double?, double?)`
|
||||||
|
// ignore: unnecessary_lambdas
|
||||||
|
createCluster: (base, lng, lat) => GeoEntry.createCluster(base, lng, lat),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:aves/model/entry.dart';
|
import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
|
import 'package:aves/services/media/media_file_service.dart';
|
||||||
import 'package:aves/utils/mime_utils.dart';
|
import 'package:aves/utils/mime_utils.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -19,6 +20,8 @@ class ExportEntryDialog extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ExportEntryDialogState extends State<ExportEntryDialog> {
|
class _ExportEntryDialogState extends State<ExportEntryDialog> {
|
||||||
|
final TextEditingController _widthController = TextEditingController(), _heightController = TextEditingController();
|
||||||
|
final ValueNotifier<bool> _isValidNotifier = ValueNotifier(false);
|
||||||
String _mimeType = MimeTypes.jpeg;
|
String _mimeType = MimeTypes.jpeg;
|
||||||
|
|
||||||
AvesEntry get entry => widget.entry;
|
AvesEntry get entry => widget.entry;
|
||||||
|
@ -30,13 +33,33 @@ class _ExportEntryDialogState extends State<ExportEntryDialog> {
|
||||||
MimeTypes.webp,
|
MimeTypes.webp,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_widthController.text = '${entry.isRotated ? entry.height : entry.width}';
|
||||||
|
_heightController.text = '${entry.isRotated ? entry.width : entry.height}';
|
||||||
|
_validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_widthController.dispose();
|
||||||
|
_heightController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
return AvesDialog(
|
return AvesDialog(
|
||||||
content: Row(
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(context.l10n.exportEntryDialogFormat),
|
Text(l10n.exportEntryDialogFormat),
|
||||||
const SizedBox(width: AvesDialog.controlCaptionPadding),
|
const SizedBox(width: AvesDialog.controlCaptionPadding),
|
||||||
DropdownButton<String>(
|
DropdownButton<String>(
|
||||||
items: imageExportFormats.map((mimeType) {
|
items: imageExportFormats.map((mimeType) {
|
||||||
|
@ -54,16 +77,73 @@ class _ExportEntryDialogState extends State<ExportEntryDialog> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||||
|
textBaseline: TextBaseline.alphabetic,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: _widthController,
|
||||||
|
decoration: InputDecoration(labelText: l10n.exportEntryDialogWidth),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
onChanged: (value) {
|
||||||
|
final width = int.tryParse(value);
|
||||||
|
_heightController.text = width != null ? '${(width / entry.displayAspectRatio).round()}' : '';
|
||||||
|
_validate();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Text(AvesEntry.resolutionSeparator),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: _heightController,
|
||||||
|
decoration: InputDecoration(labelText: l10n.exportEntryDialogHeight),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
onChanged: (value) {
|
||||||
|
final height = int.tryParse(value);
|
||||||
|
_widthController.text = height != null ? '${(height * entry.displayAspectRatio).round()}' : '';
|
||||||
|
_validate();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||||
),
|
),
|
||||||
TextButton(
|
ValueListenableBuilder<bool>(
|
||||||
onPressed: () => Navigator.pop(context, _mimeType),
|
valueListenable: _isValidNotifier,
|
||||||
child: Text(context.l10n.applyButtonLabel),
|
builder: (context, isValid, child) {
|
||||||
|
return TextButton(
|
||||||
|
onPressed: isValid
|
||||||
|
? () {
|
||||||
|
final width = int.tryParse(_widthController.text);
|
||||||
|
final height = int.tryParse(_heightController.text);
|
||||||
|
final options = (width != null && height != null)
|
||||||
|
? EntryExportOptions(
|
||||||
|
mimeType: _mimeType,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
)
|
)
|
||||||
|
: null;
|
||||||
|
Navigator.pop(context, options);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: Text(l10n.applyButtonLabel),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _validate() async {
|
||||||
|
final width = int.tryParse(_widthController.text);
|
||||||
|
final height = int.tryParse(_heightController.text);
|
||||||
|
_isValidNotifier.value = (width ?? 0) > 0 && (height ?? 0) > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,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_file_service.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||||
|
@ -203,11 +204,11 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
|
|
||||||
if (!await checkFreeSpaceForMove(context, {entry}, destinationAlbum, MoveType.export)) return;
|
if (!await checkFreeSpaceForMove(context, {entry}, destinationAlbum, MoveType.export)) return;
|
||||||
|
|
||||||
final mimeType = await showDialog<String>(
|
final options = await showDialog<EntryExportOptions>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => ExportEntryDialog(entry: entry),
|
builder: (context) => ExportEntryDialog(entry: entry),
|
||||||
);
|
);
|
||||||
if (mimeType == null) return;
|
if (options == null) return;
|
||||||
|
|
||||||
final selection = <AvesEntry>{};
|
final selection = <AvesEntry>{};
|
||||||
if (entry.isMultiPage) {
|
if (entry.isMultiPage) {
|
||||||
|
@ -231,7 +232,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
// TODO TLAD [SVG] export separately from raster images (sending bytes, like frame captures)
|
// TODO TLAD [SVG] export separately from raster images (sending bytes, like frame captures)
|
||||||
opStream: mediaFileService.export(
|
opStream: mediaFileService.export(
|
||||||
selection,
|
selection,
|
||||||
mimeType: mimeType,
|
options: options,
|
||||||
destinationAlbum: destinationAlbum,
|
destinationAlbum: destinationAlbum,
|
||||||
nameConflictStrategy: NameConflictStrategy.rename,
|
nameConflictStrategy: NameConflictStrategy.rename,
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,5 +1,22 @@
|
||||||
{
|
{
|
||||||
|
"de": [
|
||||||
|
"exportEntryDialogWidth",
|
||||||
|
"exportEntryDialogHeight"
|
||||||
|
],
|
||||||
|
|
||||||
|
"es": [
|
||||||
|
"exportEntryDialogWidth",
|
||||||
|
"exportEntryDialogHeight"
|
||||||
|
],
|
||||||
|
|
||||||
|
"pt": [
|
||||||
|
"exportEntryDialogWidth",
|
||||||
|
"exportEntryDialogHeight"
|
||||||
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
|
"exportEntryDialogWidth",
|
||||||
|
"exportEntryDialogHeight",
|
||||||
"appExportCovers",
|
"appExportCovers",
|
||||||
"settingsThumbnailShowFavouriteIcon"
|
"settingsThumbnailShowFavouriteIcon"
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in a new issue