ask before deleting image

This commit is contained in:
otsmr 2025-11-09 23:32:46 +01:00
parent bc2c998850
commit 6d86af155c
7 changed files with 216 additions and 138 deletions

View file

@ -818,5 +818,7 @@
"deleteChatAfterAMonth": "einem Monat.", "deleteChatAfterAMonth": "einem Monat.",
"deleteChatAfterAYear": "einem Jahr.", "deleteChatAfterAYear": "einem Jahr.",
"yourTwonlyScore": "Dein twonly-Score", "yourTwonlyScore": "Dein twonly-Score",
"registrationClosed": "Aufgrund des aktuell sehr hohen Aufkommens haben wir die Registrierung vorübergehend deaktiviert, damit der Dienst zuverlässig bleibt. Bitte versuche es in ein paar Tagen noch einmal." "registrationClosed": "Aufgrund des aktuell sehr hohen Aufkommens haben wir die Registrierung vorübergehend deaktiviert, damit der Dienst zuverlässig bleibt. Bitte versuche es in ein paar Tagen noch einmal.",
"dialogAskDeleteMediaFilePopTitle": "Bist du sicher, dass du dein Meisterwerk löschen möchtest?",
"dialogAskDeleteMediaFilePopDelete": "Löschen"
} }

View file

@ -596,5 +596,7 @@
"deleteChatAfterAMonth": "one month.", "deleteChatAfterAMonth": "one month.",
"deleteChatAfterAYear": "one year.", "deleteChatAfterAYear": "one year.",
"yourTwonlyScore": "Your twonly-Score", "yourTwonlyScore": "Your twonly-Score",
"registrationClosed": "Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days." "registrationClosed": "Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days.",
"dialogAskDeleteMediaFilePopTitle": "Are you sure you want to delete your masterpiece?",
"dialogAskDeleteMediaFilePopDelete": "Delete"
} }

View file

@ -2683,6 +2683,18 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days.'** /// **'Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days.'**
String get registrationClosed; String get registrationClosed;
/// No description provided for @dialogAskDeleteMediaFilePopTitle.
///
/// In en, this message translates to:
/// **'Are you sure you want to delete your masterpiece?'**
String get dialogAskDeleteMediaFilePopTitle;
/// No description provided for @dialogAskDeleteMediaFilePopDelete.
///
/// In en, this message translates to:
/// **'Delete'**
String get dialogAskDeleteMediaFilePopDelete;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate

View file

@ -1481,4 +1481,11 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get registrationClosed => String get registrationClosed =>
'Aufgrund des aktuell sehr hohen Aufkommens haben wir die Registrierung vorübergehend deaktiviert, damit der Dienst zuverlässig bleibt. Bitte versuche es in ein paar Tagen noch einmal.'; 'Aufgrund des aktuell sehr hohen Aufkommens haben wir die Registrierung vorübergehend deaktiviert, damit der Dienst zuverlässig bleibt. Bitte versuche es in ein paar Tagen noch einmal.';
@override
String get dialogAskDeleteMediaFilePopTitle =>
'Bist du sicher, dass du dein Meisterwerk löschen möchtest?';
@override
String get dialogAskDeleteMediaFilePopDelete => 'Löschen';
} }

View file

@ -1471,4 +1471,11 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get registrationClosed => String get registrationClosed =>
'Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days.'; 'Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days.';
@override
String get dialogAskDeleteMediaFilePopTitle =>
'Are you sure you want to delete your masterpiece?';
@override
String get dialogAskDeleteMediaFilePopDelete => 'Delete';
} }

View file

@ -75,7 +75,7 @@ class _FilterLayerState extends State<FilterLayer> {
List<Widget> pages = [ List<Widget> pages = [
const FilterSkeleton(), const FilterSkeleton(),
const DateTimeFilter(), const DateTimeFilter(),
const LocationFilter(), // const LocationFilter(),
const FilterSkeleton(), const FilterSkeleton(),
]; ];

View file

@ -264,6 +264,40 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
]; ];
} }
Future<bool?> _showBackDialog() {
return showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(
context.lang.dialogAskDeleteMediaFilePopTitle,
),
actions: [
FilledButton(
child: Text(context.lang.dialogAskDeleteMediaFilePopDelete),
onPressed: () {
Navigator.pop(context, true);
},
),
TextButton(
child: Text(context.lang.cancel),
onPressed: () {
Navigator.pop(context, false);
},
),
],
);
},
);
}
Future<void> askToCloseThenClose() async {
final shouldPop = await _showBackDialog() ?? false;
if (mounted && shouldPop) {
Navigator.pop(context);
}
}
List<Widget> get actionsAtTheTop { List<Widget> get actionsAtTheTop {
if (layers.isNotEmpty && if (layers.isNotEmpty &&
layers.last.isEditing && layers.last.isEditing &&
@ -275,7 +309,14 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
FontAwesomeIcons.xmark, FontAwesomeIcons.xmark,
tooltipText: context.lang.close, tooltipText: context.lang.close,
onPressed: () async { onPressed: () async {
Navigator.pop(context, false); final nonImageFilterLayer = layers.where(
(x) => x is! BackgroundLayerData && x is! FilterLayerData,
);
if (nonImageFilterLayer.isEmpty) {
Navigator.pop(context, false);
} else {
await askToCloseThenClose();
}
}, },
), ),
Expanded(child: Container()), Expanded(child: Container()),
@ -446,154 +487,161 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
pixelRatio = MediaQuery.of(context).devicePixelRatio; pixelRatio = MediaQuery.of(context).devicePixelRatio;
return Scaffold( return PopScope<bool?>(
backgroundColor: canPop: false,
widget.sharedFromGallery ? null : Colors.white.withAlpha(0), onPopInvokedWithResult: (bool didPop, bool? result) async {
resizeToAvoidBottomInset: false, if (didPop) return;
body: Stack( await askToCloseThenClose();
fit: StackFit.expand, },
children: [ child: Scaffold(
GestureDetector( backgroundColor:
onTapDown: (details) { widget.sharedFromGallery ? null : Colors.white.withAlpha(0),
if (details.globalPosition.dy > 60) { resizeToAvoidBottomInset: false,
tabDownPosition = details.globalPosition.dy - 60; body: Stack(
} else { fit: StackFit.expand,
tabDownPosition = details.globalPosition.dy; children: [
} GestureDetector(
}, onTapDown: (details) {
onTap: () { if (details.globalPosition.dy > 60) {
if (layers.any((x) => x.isEditing)) { tabDownPosition = details.globalPosition.dy - 60;
return; } else {
} tabDownPosition = details.globalPosition.dy;
layers = layers.where((x) => !x.isDeleted).toList(); }
undoLayers.clear(); },
removedLayers.clear(); onTap: () {
layers.add( if (layers.any((x) => x.isEditing)) {
TextLayerData( return;
offset: Offset(0, tabDownPosition), }
textLayersBefore: layers.whereType<TextLayerData>().length, layers = layers.where((x) => !x.isDeleted).toList();
), undoLayers.clear();
); removedLayers.clear();
setState(() {}); layers.add(
}, TextLayerData(
child: MediaViewSizing( offset: Offset(0, tabDownPosition),
requiredHeight: 90, textLayersBefore: layers.whereType<TextLayerData>().length,
bottomNavigation: ColoredBox( ),
color: Theme.of(context).colorScheme.surface, );
child: Row( setState(() {});
mainAxisAlignment: MainAxisAlignment.center, },
children: [ child: MediaViewSizing(
SaveToGalleryButton( requiredHeight: 90,
storeImageAsOriginal: storeImageAsOriginal, bottomNavigation: ColoredBox(
mediaService: mediaService, color: Theme.of(context).colorScheme.surface,
displayButtonLabel: widget.sendToGroup == null, child: Row(
isLoading: loadingImage, mainAxisAlignment: MainAxisAlignment.center,
), children: [
if (widget.sendToGroup != null) const SizedBox(width: 10), SaveToGalleryButton(
if (widget.sendToGroup != null) storeImageAsOriginal: storeImageAsOriginal,
OutlinedButton( mediaService: mediaService,
style: OutlinedButton.styleFrom( displayButtonLabel: widget.sendToGroup == null,
iconColor: Theme.of(context).colorScheme.primary, isLoading: loadingImage,
foregroundColor:
Theme.of(context).colorScheme.primary,
),
onPressed: pushShareImageView,
child: const FaIcon(FontAwesomeIcons.userPlus),
), ),
SizedBox(width: widget.sendToGroup == null ? 20 : 10), if (widget.sendToGroup != null) const SizedBox(width: 10),
FilledButton.icon( if (widget.sendToGroup != null)
icon: sendingOrLoadingImage OutlinedButton(
? SizedBox( style: OutlinedButton.styleFrom(
height: 12, iconColor: Theme.of(context).colorScheme.primary,
width: 12, foregroundColor:
child: CircularProgressIndicator( Theme.of(context).colorScheme.primary,
strokeWidth: 2, ),
color: Theme.of(context) onPressed: pushShareImageView,
.colorScheme child: const FaIcon(FontAwesomeIcons.userPlus),
.inversePrimary, ),
), SizedBox(width: widget.sendToGroup == null ? 20 : 10),
) FilledButton.icon(
: const FaIcon(FontAwesomeIcons.solidPaperPlane), icon: sendingOrLoadingImage
onPressed: () async { ? SizedBox(
if (sendingOrLoadingImage) return; height: 12,
if (widget.sendToGroup == null) { width: 12,
return pushShareImageView(); child: CircularProgressIndicator(
} strokeWidth: 2,
await sendImageToSinglePerson(); color: Theme.of(context)
}, .colorScheme
style: ButtonStyle( .inversePrimary,
padding: WidgetStateProperty.all<EdgeInsets>( ),
const EdgeInsets.symmetric( )
vertical: 10, : const FaIcon(FontAwesomeIcons.solidPaperPlane),
horizontal: 30, onPressed: () async {
if (sendingOrLoadingImage) return;
if (widget.sendToGroup == null) {
return pushShareImageView();
}
await sendImageToSinglePerson();
},
style: ButtonStyle(
padding: WidgetStateProperty.all<EdgeInsets>(
const EdgeInsets.symmetric(
vertical: 10,
horizontal: 30,
),
), ),
), ),
label: Text(
(widget.sendToGroup == null)
? context.lang.shareImagedEditorShareWith
: substringBy(widget.sendToGroup!.groupName, 15),
style: const TextStyle(fontSize: 17),
),
), ),
label: Text( ],
(widget.sendToGroup == null) ),
? context.lang.shareImagedEditorShareWith
: substringBy(widget.sendToGroup!.groupName, 15),
style: const TextStyle(fontSize: 17),
),
),
],
), ),
), child: SizedBox(
child: SizedBox( height: currentImage.height / pixelRatio,
height: currentImage.height / pixelRatio, width: currentImage.width / pixelRatio,
width: currentImage.width / pixelRatio, child: Stack(
child: Stack( children: [
children: [ if (videoController != null)
if (videoController != null) Positioned.fill(
Positioned.fill( child: VideoPlayer(videoController!),
child: VideoPlayer(videoController!), ),
), Screenshot(
Screenshot( controller: screenshotController,
controller: screenshotController, child: LayersViewer(
child: LayersViewer( layers: layers.where((x) => !x.isDeleted).toList(),
layers: layers.where((x) => !x.isDeleted).toList(), onUpdate: () {
onUpdate: () { for (final layer in layers) {
for (final layer in layers) { layer.isEditing = false;
layer.isEditing = false; if (layer.isDeleted) {
if (layer.isDeleted) { removedLayers.add(layer);
removedLayers.add(layer); }
} }
} layers = layers.where((x) => !x.isDeleted).toList();
layers = layers.where((x) => !x.isDeleted).toList(); setState(() {});
setState(() {}); },
}, ),
), ),
), ],
], ),
), ),
), ),
), ),
), Positioned(
Positioned( top: 10,
top: 10, left: 5,
left: 5, right: 0,
right: 0,
child: SafeArea(
child: Row(
children: actionsAtTheTop,
),
),
),
Positioned(
right: 6,
top: 100,
child: Container(
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.symmetric(vertical: 16),
child: SafeArea( child: SafeArea(
child: Column( child: Row(
mainAxisAlignment: MainAxisAlignment.center, children: actionsAtTheTop,
children: actionsAtTheRight,
), ),
), ),
), ),
), Positioned(
], right: 6,
top: 100,
child: Container(
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.symmetric(vertical: 16),
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: actionsAtTheRight,
),
),
),
),
],
),
), ),
); );
} }