handle TransactionTooLargeException when sharing
This commit is contained in:
parent
c276ac904b
commit
d8a1d21f6c
36 changed files with 272 additions and 307 deletions
|
@ -10,6 +10,7 @@ import android.net.Uri
|
|||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.TransactionTooLargeException
|
||||
import android.util.Log
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
|
@ -280,7 +281,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
|
|||
val title = call.argument<String>("title")
|
||||
val urisByMimeType = call.argument<Map<String, List<String>>>("urisByMimeType")
|
||||
if (urisByMimeType == null) {
|
||||
result.error("setAs-args", "missing arguments", null)
|
||||
result.error("share-args", "missing arguments", null)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -288,15 +289,14 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
|
|||
val mimeTypes = urisByMimeType.keys.toTypedArray()
|
||||
|
||||
// simplify share intent for a single item, as some apps can handle one item but not more
|
||||
val started = if (uriList.size == 1) {
|
||||
val intent = if (uriList.size == 1) {
|
||||
val uri = uriList.first()
|
||||
val mimeType = mimeTypes.first()
|
||||
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
Intent(Intent.ACTION_SEND)
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
.setType(mimeType)
|
||||
.putExtra(Intent.EXTRA_STREAM, getShareableUri(context, uri))
|
||||
safeStartActivityChooser(title, intent)
|
||||
} else {
|
||||
var mimeType = "*/*"
|
||||
if (mimeTypes.size == 1) {
|
||||
|
@ -311,14 +311,21 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler {
|
|||
}
|
||||
}
|
||||
|
||||
val intent = Intent(Intent.ACTION_SEND_MULTIPLE)
|
||||
Intent(Intent.ACTION_SEND_MULTIPLE)
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList)
|
||||
.setType(mimeType)
|
||||
safeStartActivityChooser(title, intent)
|
||||
}
|
||||
|
||||
result.success(started)
|
||||
try {
|
||||
val started = safeStartActivityChooser(title, intent)
|
||||
result.success(started)
|
||||
} catch (e: Exception) {
|
||||
if (e is TransactionTooLargeException || e.cause is TransactionTooLargeException) {
|
||||
result.error("share-large", "transaction too large with ${uriList.size} URIs", e)
|
||||
} else {
|
||||
result.error("share-exception", "failed to share ${uriList.size} URIs", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun safeStartActivity(intent: Intent): Boolean {
|
||||
|
|
|
@ -438,6 +438,8 @@
|
|||
"genericFailureFeedback": "Failed",
|
||||
"genericDangerWarningDialogMessage": "Are you sure?",
|
||||
|
||||
"tooManyItemsErrorDialogMessage": "Try again with fewer items.",
|
||||
|
||||
"menuActionConfigureView": "View",
|
||||
"menuActionSelect": "Select",
|
||||
"menuActionSelectAll": "Select all",
|
||||
|
|
|
@ -148,32 +148,34 @@ class PlatformAndroidAppService implements AndroidAppService {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<bool> shareEntries(Iterable<AvesEntry> entries) async {
|
||||
// loosen MIME type to a generic one, so we can share with badly defined apps
|
||||
// e.g. Google Lens declares receiving "image/jpeg" only, but it can actually handle more formats
|
||||
final urisByMimeType = groupBy<AvesEntry, String>(entries, (e) => e.mimeTypeAnySubtype).map((k, v) => MapEntry(k, v.map((e) => e.uri).toList()));
|
||||
Future<bool> shareEntries(Iterable<AvesEntry> entries) {
|
||||
return _share(groupBy<AvesEntry, String>(
|
||||
entries,
|
||||
// loosen MIME type to a generic one, so we can share with badly defined apps
|
||||
// e.g. Google Lens declares receiving "image/jpeg" only, but it can actually handle more formats
|
||||
(e) => e.mimeTypeAnySubtype,
|
||||
).map((k, v) => MapEntry(k, v.map((e) => e.uri).toList())));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> shareSingle(String uri, String mimeType) {
|
||||
return _share({
|
||||
mimeType: [uri]
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> _share(Map<String, List<String>> urisByMimeType) async {
|
||||
try {
|
||||
final result = await _platform.invokeMethod('share', <String, dynamic>{
|
||||
'urisByMimeType': urisByMimeType,
|
||||
});
|
||||
if (result != null) return result as bool;
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> shareSingle(String uri, String mimeType) async {
|
||||
try {
|
||||
final result = await _platform.invokeMethod('share', <String, dynamic>{
|
||||
'urisByMimeType': {
|
||||
mimeType: [uri]
|
||||
},
|
||||
});
|
||||
if (result != null) return result as bool;
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
if (e.code == 'share-large') {
|
||||
throw TooManyItemsException();
|
||||
} else {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -207,3 +209,5 @@ class PlatformAndroidAppService implements AndroidAppService {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TooManyItemsException implements Exception {}
|
||||
|
|
|
@ -17,6 +17,7 @@ import 'package:aves/model/settings/settings.dart';
|
|||
import 'package:aves/model/source/analysis_controller.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/services/android_app_service.dart';
|
||||
import 'package:aves/services/common/image_op_events.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
|
@ -252,11 +253,21 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
return groupedEntries.expand((entry) => entry.burstEntries ?? {entry}).toSet();
|
||||
}
|
||||
|
||||
void _share(BuildContext context) {
|
||||
Future<void> _share(BuildContext context) async {
|
||||
final entries = _getTargetItems(context);
|
||||
androidAppService.shareEntries(entries).then((success) {
|
||||
if (!success) showNoMatchingAppDialog(context);
|
||||
});
|
||||
try {
|
||||
if (!await androidAppService.shareEntries(entries)) {
|
||||
await showNoMatchingAppDialog(context);
|
||||
}
|
||||
} on TooManyItemsException catch (_) {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AvesDialog(
|
||||
content: Text(context.l10n.tooManyItemsErrorDialogMessage),
|
||||
actions: const [OkButton()],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _rescan(BuildContext context) {
|
||||
|
@ -447,25 +458,20 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
if (unsupported.isEmpty) return supported;
|
||||
|
||||
final unsupportedTypes = unsupported.map((entry) => entry.mimeType).toSet().map(MimeUtils.displayType).toList()..sort();
|
||||
final l10n = context.l10n;
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final l10n = context.l10n;
|
||||
return AvesDialog(
|
||||
content: Text(l10n.unsupportedTypeDialogMessage(unsupportedTypes.length, unsupportedTypes.join(', '))),
|
||||
actions: [
|
||||
builder: (context) => AvesDialog(
|
||||
content: Text(l10n.unsupportedTypeDialogMessage(unsupportedTypes.length, unsupportedTypes.join(', '))),
|
||||
actions: [
|
||||
const CancelButton(),
|
||||
if (supported.isNotEmpty)
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(l10n.continueButtonLabel),
|
||||
),
|
||||
if (supported.isNotEmpty)
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(l10n.continueButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == null || !confirmed) return null;
|
||||
|
||||
|
@ -536,21 +542,16 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
Future<void> removeLocation(BuildContext context, Set<AvesEntry> entries) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AvesDialog(
|
||||
content: Text(context.l10n.genericDangerWarningDialogMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.l10n.applyButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
builder: (context) => AvesDialog(
|
||||
content: Text(context.l10n.genericDangerWarningDialogMessage),
|
||||
actions: [
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.l10n.applyButtonLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == null || !confirmed) return;
|
||||
|
||||
|
|
|
@ -123,21 +123,16 @@ mixin EntryEditorMixin {
|
|||
if (entries.any((entry) => entry.isMotionPhoto) && types.contains(MetadataType.xmp)) {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AvesDialog(
|
||||
content: Text(context.l10n.removeEntryMetadataMotionPhotoXmpWarningDialogMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.l10n.applyButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
builder: (context) => AvesDialog(
|
||||
content: Text(context.l10n.removeEntryMetadataMotionPhotoXmpWarningDialogMessage),
|
||||
actions: [
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.l10n.applyButtonLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == null || !confirmed) return null;
|
||||
}
|
||||
|
|
|
@ -136,21 +136,20 @@ mixin FeedbackMixin {
|
|||
int? itemCount,
|
||||
VoidCallback? onCancel,
|
||||
void Function(Set<T> processed)? onDone,
|
||||
}) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => ReportOverlay<T>(
|
||||
opStream: opStream,
|
||||
itemCount: itemCount,
|
||||
onCancel: onCancel,
|
||||
onDone: (processed) {
|
||||
Navigator.pop(context);
|
||||
onDone?.call(processed);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}) =>
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => ReportOverlay<T>(
|
||||
opStream: opStream,
|
||||
itemCount: itemCount,
|
||||
onCancel: onCancel,
|
||||
onDone: (processed) {
|
||||
Navigator.pop(context);
|
||||
onDone?.call(processed);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class ReportOverlay<T> extends StatefulWidget {
|
||||
|
|
|
@ -54,10 +54,7 @@ mixin PermissionAwareMixin {
|
|||
return AvesDialog(
|
||||
content: Text(l10n.storageAccessDialogMessage(directory, volume)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
||||
|
@ -72,17 +69,10 @@ mixin PermissionAwareMixin {
|
|||
if (!await deviceService.isSystemFilePickerEnabled()) {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AvesDialog(
|
||||
content: Text(context.l10n.missingSystemFilePickerDialogMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
builder: (context) => AvesDialog(
|
||||
content: Text(context.l10n.missingSystemFilePickerDialogMessage),
|
||||
actions: const [OkButton()],
|
||||
),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
@ -103,12 +93,7 @@ mixin PermissionAwareMixin {
|
|||
final volume = dir.getVolumeDescription(context);
|
||||
return AvesDialog(
|
||||
content: Text(context.l10n.restrictedAccessDialogMessage(directory, volume)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
||||
),
|
||||
],
|
||||
actions: const [OkButton()],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -85,12 +85,7 @@ mixin SizeAwareMixin {
|
|||
final volume = destinationVolume.getDescription(context);
|
||||
return AvesDialog(
|
||||
content: Text(l10n.notEnoughSpaceDialogMessage(neededSize, freeSize, volume)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
||||
),
|
||||
],
|
||||
actions: const [OkButton()],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -94,10 +94,7 @@ class _ColorPickerDialogState extends State<ColorPickerDialog> {
|
|||
)
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, color),
|
||||
child: Text(context.l10n.applyButtonLabel),
|
||||
|
|
|
@ -87,10 +87,7 @@ class _AddShortcutDialogState extends State<AddShortcutDialog> {
|
|||
),
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isValidNotifier,
|
||||
builder: (context, isValid, child) {
|
||||
|
|
|
@ -110,10 +110,7 @@ class _AvesConfirmationDialogState extends State<_AvesConfirmationDialog> {
|
|||
),
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (_skip.value) {
|
||||
|
|
|
@ -152,19 +152,34 @@ class DialogTitle extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
void showNoMatchingAppDialog(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AvesDialog(
|
||||
Future<void> showNoMatchingAppDialog(BuildContext context) => showDialog(
|
||||
context: context,
|
||||
builder: (context) => AvesDialog(
|
||||
content: Text(context.l10n.noMatchingAppDialogMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
actions: const [OkButton()],
|
||||
),
|
||||
);
|
||||
|
||||
class CancelButton extends StatelessWidget {
|
||||
const CancelButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OkButton extends StatelessWidget {
|
||||
const OkButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,10 +86,7 @@ class _AvesSelectionDialogState<T> extends State<AvesSelectionDialog<T>> {
|
|||
if (verticalPadding != 0) SizedBox(height: verticalPadding),
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
if (needConfirmation)
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, _selectedValue),
|
||||
|
|
|
@ -88,10 +88,7 @@ class _DurationDialogState extends State<DurationDialog> {
|
|||
),
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
AnimatedBuilder(
|
||||
animation: Listenable.merge([_minutes, _seconds]),
|
||||
builder: (context, child) {
|
||||
|
|
|
@ -112,10 +112,7 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
|||
),
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: () => _submit(context),
|
||||
child: Text(l10n.applyButtonLabel),
|
||||
|
|
|
@ -44,10 +44,7 @@ class _EditEntryTitleDescriptionDialogState extends State<EditEntryTitleDescript
|
|||
const SizedBox(height: 8),
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: fields.isEmpty ? null : () => _submit(context),
|
||||
child: Text(context.l10n.applyButtonLabel),
|
||||
|
|
|
@ -126,10 +126,7 @@ class _EditEntryLocationDialogState extends State<EditEntryLocationDialog> {
|
|||
const SizedBox(height: 8),
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isValidNotifier,
|
||||
builder: (context, isValid, child) {
|
||||
|
|
|
@ -99,10 +99,7 @@ class _EditEntryRatingDialogState extends State<EditEntryRatingDialog> {
|
|||
),
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: isValid ? () => _submit(context) : null,
|
||||
child: Text(l10n.applyButtonLabel),
|
||||
|
|
|
@ -80,10 +80,7 @@ class _RemoveEntryMetadataDialogState extends State<RemoveEntryMetadataDialog> {
|
|||
),
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isValidNotifier,
|
||||
builder: (context, isValid, child) {
|
||||
|
|
|
@ -58,10 +58,7 @@ class _RenameEntryDialogState extends State<RenameEntryDialog> {
|
|||
onSubmitted: (_) => _submit(context),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isValidNotifier,
|
||||
builder: (context, isValid, child) {
|
||||
|
|
|
@ -114,10 +114,7 @@ class _ExportEntryDialogState extends State<ExportEntryDialog> {
|
|||
const SizedBox(height: 16),
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isValidNotifier,
|
||||
builder: (context, isValid, child) {
|
||||
|
|
|
@ -155,10 +155,7 @@ class _CoverSelectionDialogState extends State<CoverSelectionDialog> {
|
|||
spacing: AvesDialog.buttonPadding.horizontal / 2,
|
||||
overflowAlignment: OverflowBarAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
final entry = _isCustomEntry ? _customEntry : null;
|
||||
|
|
|
@ -90,10 +90,7 @@ class _CreateAlbumDialogState extends State<CreateAlbumDialog> {
|
|||
),
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isValidNotifier,
|
||||
builder: (context, isValid, child) {
|
||||
|
|
|
@ -57,10 +57,7 @@ class _RenameAlbumDialogState extends State<RenameAlbumDialog> {
|
|||
);
|
||||
}),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _isValidNotifier,
|
||||
builder: (context, isValid, child) {
|
||||
|
|
|
@ -145,10 +145,7 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
|
|||
),
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
key: const Key('button-apply'),
|
||||
onPressed: () {
|
||||
|
|
|
@ -57,10 +57,7 @@ class _VideoSpeedDialogState extends State<VideoSpeedDialog> {
|
|||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: () => _submit(context),
|
||||
child: Text(context.l10n.applyButtonLabel),
|
||||
|
|
|
@ -80,10 +80,7 @@ class _VideoStreamSelectionDialogState extends State<VideoStreamSelectionDialog>
|
|||
]
|
||||
: null,
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
if (canSelect)
|
||||
TextButton(
|
||||
onPressed: () => _submit(context),
|
||||
|
|
|
@ -39,10 +39,7 @@ class _WallpaperSettingsDialogState extends State<WallpaperSettingsDialog> {
|
|||
)
|
||||
],
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, Tuple2<WallpaperTarget, bool>(_selectedTarget, _useScrollEffect)),
|
||||
child: Text(context.l10n.applyButtonLabel),
|
||||
|
|
|
@ -234,21 +234,16 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> with
|
|||
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AvesDialog(
|
||||
content: Text(filters.length == 1 ? l10n.deleteSingleAlbumConfirmationDialogMessage(todoCount) : l10n.deleteMultiAlbumConfirmationDialogMessage(todoCount)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(l10n.deleteButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
builder: (context) => AvesDialog(
|
||||
content: Text(filters.length == 1 ? l10n.deleteSingleAlbumConfirmationDialogMessage(todoCount) : l10n.deleteMultiAlbumConfirmationDialogMessage(todoCount)),
|
||||
actions: [
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(l10n.deleteButtonLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == null || !confirmed) return;
|
||||
|
||||
|
|
|
@ -53,21 +53,16 @@ class ChipActionDelegate {
|
|||
Future<void> _hide(BuildContext context, CollectionFilter filter) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AvesDialog(
|
||||
content: Text(context.l10n.hideFilterConfirmationDialogMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.l10n.hideButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
builder: (context) => AvesDialog(
|
||||
content: Text(context.l10n.hideFilterConfirmationDialogMessage),
|
||||
actions: [
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.l10n.hideButtonLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == null || !confirmed) return;
|
||||
|
||||
|
|
|
@ -304,21 +304,16 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
|
|||
Future<void> _hide(BuildContext context, Set<T> filters) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AvesDialog(
|
||||
content: Text(context.l10n.hideFilterConfirmationDialogMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.l10n.hideButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
builder: (context) => AvesDialog(
|
||||
content: Text(context.l10n.hideFilterConfirmationDialogMessage),
|
||||
actions: [
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.l10n.hideButtonLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == null || !confirmed) return;
|
||||
|
||||
|
|
|
@ -54,10 +54,7 @@ class _AppExportItemSelectionDialogState extends State<AppExportItemSelectionDia
|
|||
);
|
||||
}).toList(),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: _selectedItems.isEmpty ? null : () => Navigator.pop(context, _selectedItems),
|
||||
child: Text(context.l10n.applyButtonLabel),
|
||||
|
|
|
@ -114,27 +114,22 @@ class SettingsTileDisplayForceTvLayout extends SettingsTile {
|
|||
selector: (context, s) => s.forceTvLayout,
|
||||
onChanged: (v) async {
|
||||
if (v) {
|
||||
final l10n = context.l10n;
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final l10n = context.l10n;
|
||||
return AvesDialog(
|
||||
content: Text([
|
||||
l10n.settingsModificationWarningDialogMessage,
|
||||
l10n.genericDangerWarningDialogMessage,
|
||||
].join('\n\n')),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(l10n.applyButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
builder: (context) => AvesDialog(
|
||||
content: Text([
|
||||
l10n.settingsModificationWarningDialogMessage,
|
||||
l10n.genericDangerWarningDialogMessage,
|
||||
].join('\n\n')),
|
||||
actions: [
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(l10n.applyButtonLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == null || !confirmed) return;
|
||||
}
|
||||
|
|
|
@ -229,21 +229,16 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
|
|||
Future<void> _convertMotionPhotoToStillImage(BuildContext context, AvesEntry targetEntry) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AvesDialog(
|
||||
content: Text(context.l10n.genericDangerWarningDialogMessage),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.l10n.applyButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
builder: (context) => AvesDialog(
|
||||
content: Text(context.l10n.genericDangerWarningDialogMessage),
|
||||
actions: [
|
||||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.l10n.applyButtonLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == null || !confirmed) return;
|
||||
|
||||
|
|
|
@ -66,21 +66,19 @@ abstract class AvesVideoController {
|
|||
|
||||
final resume = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AvesDialog(
|
||||
content: Text(context.l10n.videoResumeDialogMessage(formatFriendlyDuration(Duration(milliseconds: resumeTime)))),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(context.l10n.videoStartOverButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.l10n.videoResumeButtonLabel),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
builder: (context) => AvesDialog(
|
||||
content: Text(context.l10n.videoResumeDialogMessage(formatFriendlyDuration(Duration(milliseconds: resumeTime)))),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(context.l10n.videoStartOverButtonLabel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.l10n.videoResumeButtonLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (resume == null || !resume) return 0;
|
||||
return resumeTime;
|
||||
|
|
|
@ -226,6 +226,7 @@
|
|||
"genericSuccessFeedback",
|
||||
"genericFailureFeedback",
|
||||
"genericDangerWarningDialogMessage",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"menuActionConfigureView",
|
||||
"menuActionSelect",
|
||||
"menuActionSelectAll",
|
||||
|
@ -572,6 +573,7 @@
|
|||
"cs": [
|
||||
"filterLocatedLabel",
|
||||
"filterTaggedLabel",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"settingsModificationWarningDialogMessage",
|
||||
"settingsViewerShowDescription",
|
||||
"settingsDisplayUseTvInterface"
|
||||
|
@ -581,6 +583,7 @@
|
|||
"columnCount",
|
||||
"filterLocatedLabel",
|
||||
"filterTaggedLabel",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"settingsModificationWarningDialogMessage",
|
||||
"settingsViewerShowDescription",
|
||||
"settingsAccessibilityShowPinchGestureAlternatives",
|
||||
|
@ -590,11 +593,16 @@
|
|||
"el": [
|
||||
"filterLocatedLabel",
|
||||
"filterTaggedLabel",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"settingsModificationWarningDialogMessage",
|
||||
"settingsViewerShowDescription",
|
||||
"settingsDisplayUseTvInterface"
|
||||
],
|
||||
|
||||
"es": [
|
||||
"tooManyItemsErrorDialogMessage"
|
||||
],
|
||||
|
||||
"fa": [
|
||||
"clearTooltip",
|
||||
"videoActionPause",
|
||||
|
@ -708,6 +716,7 @@
|
|||
"genericSuccessFeedback",
|
||||
"genericFailureFeedback",
|
||||
"genericDangerWarningDialogMessage",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"menuActionConfigureView",
|
||||
"menuActionSelect",
|
||||
"menuActionSelectAll",
|
||||
|
@ -1035,6 +1044,10 @@
|
|||
"filePickerUseThisFolder"
|
||||
],
|
||||
|
||||
"fr": [
|
||||
"tooManyItemsErrorDialogMessage"
|
||||
],
|
||||
|
||||
"gl": [
|
||||
"columnCount",
|
||||
"entryActionShareImageOnly",
|
||||
|
@ -1151,6 +1164,7 @@
|
|||
"genericSuccessFeedback",
|
||||
"genericFailureFeedback",
|
||||
"genericDangerWarningDialogMessage",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"menuActionConfigureView",
|
||||
"menuActionSelect",
|
||||
"menuActionSelectAll",
|
||||
|
@ -1507,9 +1521,14 @@
|
|||
"filePickerUseThisFolder"
|
||||
],
|
||||
|
||||
"id": [
|
||||
"tooManyItemsErrorDialogMessage"
|
||||
],
|
||||
|
||||
"it": [
|
||||
"filterLocatedLabel",
|
||||
"filterTaggedLabel",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"settingsModificationWarningDialogMessage",
|
||||
"settingsViewerShowDescription",
|
||||
"settingsDisplayUseTvInterface"
|
||||
|
@ -1526,6 +1545,7 @@
|
|||
"keepScreenOnVideoPlayback",
|
||||
"subtitlePositionTop",
|
||||
"subtitlePositionBottom",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"settingsModificationWarningDialogMessage",
|
||||
"settingsViewerShowDescription",
|
||||
"settingsAccessibilityShowPinchGestureAlternatives",
|
||||
|
@ -1533,11 +1553,16 @@
|
|||
"settingsWidgetDisplayedItem"
|
||||
],
|
||||
|
||||
"ko": [
|
||||
"tooManyItemsErrorDialogMessage"
|
||||
],
|
||||
|
||||
"lt": [
|
||||
"columnCount",
|
||||
"filterLocatedLabel",
|
||||
"filterTaggedLabel",
|
||||
"keepScreenOnVideoPlayback",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"settingsModificationWarningDialogMessage",
|
||||
"settingsViewerShowDescription",
|
||||
"settingsAccessibilityShowPinchGestureAlternatives",
|
||||
|
@ -1552,6 +1577,7 @@
|
|||
"filterLocatedLabel",
|
||||
"filterTaggedLabel",
|
||||
"keepScreenOnVideoPlayback",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"settingsModificationWarningDialogMessage",
|
||||
"settingsViewerShowDescription",
|
||||
"settingsAccessibilityShowPinchGestureAlternatives",
|
||||
|
@ -1574,6 +1600,7 @@
|
|||
"subtitlePositionBottom",
|
||||
"widgetDisplayedItemRandom",
|
||||
"widgetDisplayedItemMostRecent",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"settingsModificationWarningDialogMessage",
|
||||
"settingsViewerShowRatingTags",
|
||||
"settingsViewerShowDescription",
|
||||
|
@ -1603,6 +1630,7 @@
|
|||
"editEntryLocationDialogSetCustom",
|
||||
"locationPickerUseThisLocationButton",
|
||||
"removeEntryMetadataMotionPhotoXmpWarningDialogMessage",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"viewDialogSortSectionTitle",
|
||||
"viewDialogReverseSortOrder",
|
||||
"aboutLinkPolicy",
|
||||
|
@ -1870,20 +1898,30 @@
|
|||
"wallpaperUseScrollEffect"
|
||||
],
|
||||
|
||||
"pl": [
|
||||
"tooManyItemsErrorDialogMessage"
|
||||
],
|
||||
|
||||
"pt": [
|
||||
"columnCount",
|
||||
"filterLocatedLabel",
|
||||
"filterTaggedLabel",
|
||||
"widgetDisplayedItemMostRecent",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"settingsModificationWarningDialogMessage",
|
||||
"settingsViewerShowRatingTags",
|
||||
"settingsAccessibilityShowPinchGestureAlternatives",
|
||||
"settingsDisplayUseTvInterface"
|
||||
],
|
||||
|
||||
"ro": [
|
||||
"tooManyItemsErrorDialogMessage"
|
||||
],
|
||||
|
||||
"ru": [
|
||||
"filterLocatedLabel",
|
||||
"filterTaggedLabel",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"settingsModificationWarningDialogMessage",
|
||||
"settingsDisplayUseTvInterface"
|
||||
],
|
||||
|
@ -1922,6 +1960,7 @@
|
|||
"editEntryDateDialogExtractFromTitle",
|
||||
"editEntryDateDialogShift",
|
||||
"removeEntryMetadataDialogTitle",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"collectionActionShowTitleSearch",
|
||||
"collectionActionHideTitleSearch",
|
||||
"collectionActionAddShortcut",
|
||||
|
@ -2235,9 +2274,18 @@
|
|||
"filePickerUseThisFolder"
|
||||
],
|
||||
|
||||
"tr": [
|
||||
"tooManyItemsErrorDialogMessage"
|
||||
],
|
||||
|
||||
"uk": [
|
||||
"tooManyItemsErrorDialogMessage"
|
||||
],
|
||||
|
||||
"zh": [
|
||||
"filterLocatedLabel",
|
||||
"filterTaggedLabel",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"settingsModificationWarningDialogMessage",
|
||||
"settingsViewerShowDescription",
|
||||
"settingsAccessibilityShowPinchGestureAlternatives",
|
||||
|
@ -2248,6 +2296,7 @@
|
|||
"columnCount",
|
||||
"filterLocatedLabel",
|
||||
"filterTaggedLabel",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"settingsModificationWarningDialogMessage",
|
||||
"settingsViewerShowDescription",
|
||||
"settingsAccessibilityShowPinchGestureAlternatives",
|
||||
|
|
Loading…
Reference in a new issue