make images visible before sending #356 and remove dependencies #333

This commit is contained in:
otsmr 2025-12-26 21:10:32 +01:00
parent 27483bccd6
commit 910f5f79fa
22 changed files with 244 additions and 64 deletions

@ -1 +1 @@
Subproject commit fb66274bf729cde6f7184ec6f7f9ea89f12450fd Subproject commit 83475a912851acb6a718ea32a6f0f754d64a50d8

View file

@ -457,5 +457,6 @@
"gotLinkFromFriend": "Ja, der Link kommt direkt von der Person.", "gotLinkFromFriend": "Ja, der Link kommt direkt von der Person.",
"couldNotVerifyUsername": "{username} konnte nicht verifiziert werden", "couldNotVerifyUsername": "{username} konnte nicht verifiziert werden",
"linkPubkeyDoesNotMatch": "Der öffentliche Schlüssel im Link stimmt nicht mit dem für diesen Kontakt gespeicherten öffentlichen Schlüssel überein. Triff die Person persönlich und scanne den QR-Code direkt!", "linkPubkeyDoesNotMatch": "Der öffentliche Schlüssel im Link stimmt nicht mit dem für diesen Kontakt gespeicherten öffentlichen Schlüssel überein. Triff die Person persönlich und scanne den QR-Code direkt!",
"startWithCameraOpen": "Mit geöffneter Kamera starten" "startWithCameraOpen": "Mit geöffneter Kamera starten",
"showImagePreviewWhenSending": "Bildvorschau bei der Auswahl von Empfängern anzeigen"
} }

View file

@ -487,5 +487,6 @@
"gotLinkFromFriend": "Yes, I got the link from my friend!", "gotLinkFromFriend": "Yes, I got the link from my friend!",
"couldNotVerifyUsername": "Could not verify {username}", "couldNotVerifyUsername": "Could not verify {username}",
"linkPubkeyDoesNotMatch": "The public key in the link does not match the public key stored for this contact. Try to meet your friend in person and scan the QR code directly!", "linkPubkeyDoesNotMatch": "The public key in the link does not match the public key stored for this contact. Try to meet your friend in person and scan the QR code directly!",
"startWithCameraOpen": "Start with camera open" "startWithCameraOpen": "Start with camera open",
"showImagePreviewWhenSending": "Display image preview when selecting recipients"
} }

View file

@ -2845,6 +2845,12 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'Start with camera open'** /// **'Start with camera open'**
String get startWithCameraOpen; String get startWithCameraOpen;
/// No description provided for @showImagePreviewWhenSending.
///
/// In en, this message translates to:
/// **'Display image preview when selecting recipients'**
String get showImagePreviewWhenSending;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate

View file

@ -1573,4 +1573,8 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get startWithCameraOpen => 'Mit geöffneter Kamera starten'; String get startWithCameraOpen => 'Mit geöffneter Kamera starten';
@override
String get showImagePreviewWhenSending =>
'Bildvorschau bei der Auswahl von Empfängern anzeigen';
} }

View file

@ -1563,4 +1563,8 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get startWithCameraOpen => 'Start with camera open'; String get startWithCameraOpen => 'Start with camera open';
@override
String get showImagePreviewWhenSending =>
'Display image preview when selecting recipients';
} }

View file

@ -59,6 +59,9 @@ class UserData {
@JsonKey(defaultValue: true) @JsonKey(defaultValue: true)
bool showFeedbackShortcut = true; bool showFeedbackShortcut = true;
@JsonKey(defaultValue: true)
bool showShowImagePreviewWhenSending = true;
@JsonKey(defaultValue: true) @JsonKey(defaultValue: true)
bool startWithCameraOpen = true; bool startWithCameraOpen = true;

View file

@ -32,6 +32,8 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
..requestedAudioPermission = ..requestedAudioPermission =
json['requestedAudioPermission'] as bool? ?? false json['requestedAudioPermission'] as bool? ?? false
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true ..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
..showShowImagePreviewWhenSending =
json['showShowImagePreviewWhenSending'] as bool? ?? true
..startWithCameraOpen = json['startWithCameraOpen'] as bool? ?? true ..startWithCameraOpen = json['startWithCameraOpen'] as bool? ?? true
..preSelectedEmojies = (json['preSelectedEmojies'] as List<dynamic>?) ..preSelectedEmojies = (json['preSelectedEmojies'] as List<dynamic>?)
?.map((e) => e as String) ?.map((e) => e as String)
@ -94,6 +96,8 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
'defaultShowTime': instance.defaultShowTime, 'defaultShowTime': instance.defaultShowTime,
'requestedAudioPermission': instance.requestedAudioPermission, 'requestedAudioPermission': instance.requestedAudioPermission,
'showFeedbackShortcut': instance.showFeedbackShortcut, 'showFeedbackShortcut': instance.showFeedbackShortcut,
'showShowImagePreviewWhenSending':
instance.showShowImagePreviewWhenSending,
'startWithCameraOpen': instance.startWithCameraOpen, 'startWithCameraOpen': instance.startWithCameraOpen,
'preSelectedEmojies': instance.preSelectedEmojies, 'preSelectedEmojies': instance.preSelectedEmojies,
'autoDownloadOptions': instance.autoDownloadOptions, 'autoDownloadOptions': instance.autoDownloadOptions,

View file

@ -87,6 +87,7 @@ Future<MediaFileService?> initializeMediaUpload(
Future<void> insertMediaFileInMessagesTable( Future<void> insertMediaFileInMessagesTable(
MediaFileService mediaService, MediaFileService mediaService,
List<String> groupIds, List<String> groupIds,
Future<Uint8List?>? imageStoreAwait,
) async { ) async {
await twonlyDB.mediaFilesDao.updateAllMediaFiles( await twonlyDB.mediaFilesDao.updateAllMediaFiles(
const MediaFilesCompanion( const MediaFilesCompanion(
@ -117,6 +118,13 @@ Future<void> insertMediaFileInMessagesTable(
} }
} }
if (imageStoreAwait != null) {
if (await imageStoreAwait == null) {
Log.error('image store as original did return false...');
return;
}
}
unawaited(startBackgroundMediaUpload(mediaService)); unawaited(startBackgroundMediaUpload(mediaService));
} }

View file

@ -0,0 +1,104 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as io;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:twonly/src/utils/log.dart';
class ScreenshotImage {
ScreenshotImage({
this.image,
this.imageBytes,
this.imageBytesFuture,
this.file,
});
io.Image? image;
Uint8List? imageBytes;
Future<Uint8List>? imageBytesFuture;
File? file;
Future<Uint8List?> getBytes() async {
if (imageBytes != null) {
return imageBytes;
}
if (imageBytesFuture != null) {
return imageBytesFuture;
}
if (file != null) {
return file!.readAsBytes();
}
if (image == null) return null;
final img = await image!.toByteData(format: io.ImageByteFormat.png);
if (img == null) {
Log.error('Got no image');
return null;
}
return imageBytes = img.buffer.asUint8List();
}
}
class ScreenshotController {
ScreenshotController() {
_containerKey = GlobalKey();
}
late GlobalKey _containerKey;
Future<ScreenshotImage?> capture({double? pixelRatio}) async {
try {
final findRenderObject = _containerKey.currentContext?.findRenderObject();
if (findRenderObject == null) {
return null;
}
final boundary = findRenderObject as RenderRepaintBoundary;
final context = _containerKey.currentContext;
var tmpPixelRatio = pixelRatio;
if (tmpPixelRatio == null) {
if (context != null && context.mounted) {
tmpPixelRatio =
tmpPixelRatio ?? MediaQuery.of(context).devicePixelRatio;
}
}
final image = await boundary.toImage(pixelRatio: tmpPixelRatio ?? 1);
return ScreenshotImage(image: image);
} catch (e) {
Log.error(e);
}
return null;
}
}
class Screenshot extends StatefulWidget {
const Screenshot({
required this.child,
required this.controller,
super.key,
});
final Widget? child;
final ScreenshotController controller;
@override
State<Screenshot> createState() {
return ScreenshotState();
}
}
class ScreenshotState extends State<Screenshot> with TickerProviderStateMixin {
late ScreenshotController _controller;
@override
void initState() {
super.initState();
_controller = widget.controller;
}
@override
Widget build(BuildContext context) {
return RepaintBoundary(
key: _controller._containerKey,
child: widget.child,
);
}
}

View file

@ -1,6 +1,6 @@
import 'package:camera/camera.dart'; import 'package:camera/camera.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:screenshot/screenshot.dart'; import 'package:twonly/src/utils/screenshot.dart';
import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart'; import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart';
import 'package:twonly/src/views/components/media_view_sizing.dart'; import 'package:twonly/src/views/components/media_view_sizing.dart';

View file

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:camera/camera.dart'; import 'package:camera/camera.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -19,6 +18,7 @@ import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/qr.dart'; import 'package:twonly/src/utils/qr.dart';
import 'package:twonly/src/utils/screenshot.dart';
import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart'; import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart';
import 'package:twonly/src/views/camera/camera_preview_components/permissions_view.dart'; import 'package:twonly/src/views/camera/camera_preview_components/permissions_view.dart';
@ -316,7 +316,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
Future<void> takePicture() async { Future<void> takePicture() async {
if (_sharePreviewIsShown || _isVideoRecording) return; if (_sharePreviewIsShown || _isVideoRecording) return;
late Future<Uint8List?> imageBytes;
setState(() { setState(() {
_sharePreviewIsShown = true; _sharePreviewIsShown = true;
@ -345,10 +344,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
return; return;
} }
imageBytes = mc.screenshotController final image = await mc.screenshotController
.capture(pixelRatio: MediaQuery.of(context).devicePixelRatio); .capture(pixelRatio: MediaQuery.of(context).devicePixelRatio);
if (await pushMediaEditor(imageBytes, null)) { if (await pushMediaEditor(image, null)) {
return; return;
} }
setState(() { setState(() {
@ -357,7 +356,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
} }
Future<bool> pushMediaEditor( Future<bool> pushMediaEditor(
Future<Uint8List?>? imageBytes, ScreenshotImage? imageBytes,
File? videoFilePath, { File? videoFilePath, {
bool sharedFromGallery = false, bool sharedFromGallery = false,
MediaType? mediaType, MediaType? mediaType,
@ -478,7 +477,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
Log.info('Picket from gallery: ${pickedFile.path}'); Log.info('Picket from gallery: ${pickedFile.path}');
File? videoFilePath; File? videoFilePath;
Future<Uint8List>? imageBytes; ScreenshotImage? image;
MediaType? mediaType; MediaType? mediaType;
final isImage = final isImage =
@ -487,13 +486,13 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
if (pickedFile.name.contains('.gif')) { if (pickedFile.name.contains('.gif')) {
mediaType = MediaType.gif; mediaType = MediaType.gif;
} }
imageBytes = pickedFile.readAsBytes(); image = ScreenshotImage(imageBytesFuture: pickedFile.readAsBytes());
} else { } else {
videoFilePath = File(pickedFile.path); videoFilePath = File(pickedFile.path);
} }
await pushMediaEditor( await pushMediaEditor(
imageBytes, image,
videoFilePath, videoFilePath,
sharedFromGallery: true, sharedFromGallery: true,
mediaType: mediaType, mediaType: mediaType,

View file

@ -5,13 +5,13 @@ import 'package:drift/drift.dart' show Value;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart'; import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart';
import 'package:screenshot/screenshot.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart';
import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/services/signal/session.signal.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/qr.dart'; import 'package:twonly/src/utils/qr.dart';
import 'package:twonly/src/utils/screenshot.dart';
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart'; import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart';
import 'package:twonly/src/views/camera/painters/barcode_detector_painter.dart'; import 'package:twonly/src/views/camera/painters/barcode_detector_painter.dart';

View file

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
@ -15,7 +16,7 @@ class SaveToGalleryButton extends StatefulWidget {
required this.mediaService, required this.mediaService,
super.key, super.key,
}); });
final Future<bool> Function() storeImageAsOriginal; final Future<Uint8List?> Function() storeImageAsOriginal;
final bool displayButtonLabel; final bool displayButtonLabel;
final MediaFileService mediaService; final MediaFileService mediaService;
final bool isLoading; final bool isLoading;

View file

@ -3,8 +3,8 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:hand_signature/signature.dart'; import 'package:hand_signature/signature.dart';
// ignore: implementation_imports // ignore: implementation_imports
import 'package:hand_signature/src/utils.dart'; import 'package:hand_signature/src/utils.dart';
import 'package:screenshot/screenshot.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/screenshot.dart';
import 'package:twonly/src/views/camera/image_editor/action_button.dart'; import 'package:twonly/src/views/camera/image_editor/action_button.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; import 'package:twonly/src/views/camera/image_editor/data/layer.dart';

View file

@ -6,7 +6,6 @@ import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:screenshot/screenshot.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart';
@ -15,6 +14,7 @@ import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/screenshot.dart';
import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart'; import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart';
import 'package:twonly/src/views/camera/camera_preview_components/save_to_gallery.dart'; import 'package:twonly/src/views/camera/camera_preview_components/save_to_gallery.dart';
@ -33,14 +33,15 @@ List<Layer> undoLayers = [];
List<Layer> removedLayers = []; List<Layer> removedLayers = [];
class ShareImageEditorView extends StatefulWidget { class ShareImageEditorView extends StatefulWidget {
const ShareImageEditorView( const ShareImageEditorView({
{required this.sharedFromGallery, required this.sharedFromGallery,
required this.mediaFileService, required this.mediaFileService,
super.key, super.key,
this.imageBytesFuture, this.imageBytesFuture,
this.sendToGroup, this.sendToGroup,
this.mainCameraController}); this.mainCameraController,
final Future<Uint8List?>? imageBytesFuture; });
final ScreenshotImage? imageBytesFuture;
final Group? sendToGroup; final Group? sendToGroup;
final bool sharedFromGallery; final bool sharedFromGallery;
final MediaFileService mediaFileService; final MediaFileService mediaFileService;
@ -84,9 +85,11 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
loadImage(widget.imageBytesFuture!); loadImage(widget.imageBytesFuture!);
} else { } else {
if (widget.mediaFileService.tempPath.existsSync()) { if (widget.mediaFileService.tempPath.existsSync()) {
loadImage(widget.mediaFileService.tempPath.readAsBytes()); loadImage(ScreenshotImage(file: widget.mediaFileService.tempPath));
} else if (widget.mediaFileService.originalPath.existsSync()) { } else if (widget.mediaFileService.originalPath.existsSync()) {
loadImage(widget.mediaFileService.originalPath.readAsBytes()); loadImage(
ScreenshotImage(file: widget.mediaFileService.originalPath),
);
} }
} }
} }
@ -383,11 +386,11 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
} }
} }
Future<Uint8List?> getEditedImageBytes() async { Future<ScreenshotImage?> getEditedImageBytes() async {
if (layers.length == 1) { if (layers.length == 1) {
if (layers.first is BackgroundLayerData) { if (layers.first is BackgroundLayerData) {
final image = (layers.first as BackgroundLayerData).image.bytes; final image = (layers.first as BackgroundLayerData).image.bytes;
return image; return ScreenshotImage(imageBytes: image);
} }
} }
@ -412,22 +415,31 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
return image; return image;
} }
Future<bool> storeImageAsOriginal() async { Future<Uint8List?> storeImageAsOriginal() async {
if (mediaService.overlayImagePath.existsSync()) { if (mediaService.overlayImagePath.existsSync()) {
mediaService.overlayImagePath.deleteSync(); mediaService.overlayImagePath.deleteSync();
} }
if (mediaService.tempPath.existsSync()) { if (mediaService.tempPath.existsSync()) {
mediaService.tempPath.deleteSync(); mediaService.tempPath.deleteSync();
} }
if (mediaService.originalPath.existsSync()) {
mediaService.originalPath.deleteSync();
}
var bytes = imageBytes;
if (media.type == MediaType.gif) { if (media.type == MediaType.gif) {
mediaService.originalPath.writeAsBytesSync(imageBytes!.toList()); mediaService.originalPath.writeAsBytesSync(imageBytes!.toList());
} else { } else {
final imageBytes = await getEditedImageBytes(); final image = await getEditedImageBytes();
if (imageBytes == null) return false; if (image == null) return null;
bytes = await image.getBytes();
if (bytes == null) {
Log.error('imageBytes are empty');
return null;
}
if (media.type == MediaType.image || media.type == MediaType.gif) { if (media.type == MediaType.image || media.type == MediaType.gif) {
mediaService.originalPath.writeAsBytesSync(imageBytes); mediaService.originalPath.writeAsBytesSync(bytes!);
} else if (media.type == MediaType.video) { } else if (media.type == MediaType.video) {
mediaService.overlayImagePath.writeAsBytesSync(imageBytes); mediaService.overlayImagePath.writeAsBytesSync(bytes!);
} else { } else {
Log.error('MediaType not supported: ${media.type}'); Log.error('MediaType not supported: ${media.type}');
} }
@ -447,12 +459,11 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
.renameSync(MediaFileService(mediaFile).storedPath.path); .renameSync(MediaFileService(mediaFile).storedPath.path);
} }
} }
return true; return bytes;
} }
Future<void> loadImage(Future<Uint8List?> imageBytesFuture) async { Future<void> loadImage(ScreenshotImage imageBytesFuture) async {
imageBytes = await imageBytesFuture; imageBytes = await imageBytesFuture.getBytes();
// store this image so it can be used as a draft in case the app is restarted // store this image so it can be used as a draft in case the app is restarted
mediaService.originalPath.writeAsBytesSync(imageBytes!.toList()); mediaService.originalPath.writeAsBytesSync(imageBytes!.toList());
@ -486,18 +497,18 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
sendingOrLoadingImage = true; sendingOrLoadingImage = true;
}); });
await storeImageAsOriginal();
if (!context.mounted) return; if (!context.mounted) return;
// Insert media file into the messages database and start uploading process in the background // Insert media file into the messages database and start uploading process in the background
await insertMediaFileInMessagesTable( unawaited(
insertMediaFileInMessagesTable(
mediaService, mediaService,
[widget.sendToGroup!.groupId], [widget.sendToGroup!.groupId],
storeImageAsOriginal(),
),
); );
if (context.mounted) { if (context.mounted) {
// ignore: use_build_context_synchronously
Navigator.pop(context, true); Navigator.pop(context, true);
} }
} }

View file

@ -2,11 +2,13 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/daos/groups.dao.dart'; import 'package:twonly/src/database/daos/groups.dao.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
@ -26,7 +28,7 @@ class ShareImageView extends StatefulWidget {
}); });
final HashSet<String> selectedGroupIds; final HashSet<String> selectedGroupIds;
final void Function(String, bool) updateSelectedGroupIds; final void Function(String, bool) updateSelectedGroupIds;
final Future<bool>? mediaStoreFuture; final Future<Uint8List?>? mediaStoreFuture;
final MediaFileService mediaFileService; final MediaFileService mediaFileService;
@override @override
@ -41,6 +43,7 @@ class _ShareImageView extends State<ShareImageView> {
bool sendingImage = false; bool sendingImage = false;
bool mediaStoreFutureReady = false; bool mediaStoreFutureReady = false;
Uint8List? _imageBytes;
bool hideArchivedUsers = true; bool hideArchivedUsers = true;
final TextEditingController searchUserName = TextEditingController(); final TextEditingController searchUserName = TextEditingController();
late StreamSubscription<List<Group>> allGroupSub; late StreamSubscription<List<Group>> allGroupSub;
@ -63,10 +66,9 @@ class _ShareImageView extends State<ShareImageView> {
Future<void> initAsync() async { Future<void> initAsync() async {
if (widget.mediaStoreFuture != null) { if (widget.mediaStoreFuture != null) {
await widget.mediaStoreFuture; _imageBytes = await widget.mediaStoreFuture;
} }
mediaStoreFutureReady = true; mediaStoreFutureReady = true;
// unawaited(startBackgroundMediaUpload(widget.mediaFileService));
if (!mounted) return; if (!mounted) return;
setState(() {}); setState(() {});
} }
@ -235,12 +237,31 @@ class _ShareImageView extends State<ShareImageView> {
), ),
), ),
floatingActionButton: SizedBox( floatingActionButton: SizedBox(
height: 120, height: 148,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row( child: Column(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
if (widget.mediaFileService.mediaFile.type == MediaType.image &&
_imageBytes != null &&
gUser.showShowImagePreviewWhenSending)
SizedBox(
height: 100,
child: Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
border:
Border.all(color: context.color.primary, width: 3),
color: context.color.primary,
borderRadius: BorderRadius.circular(10),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(7),
child: Image.memory(_imageBytes!),
),
),
),
FilledButton.icon( FilledButton.icon(
icon: !mediaStoreFutureReady || sendingImage icon: !mediaStoreFutureReady || sendingImage
? SizedBox( ? SizedBox(
@ -265,6 +286,7 @@ class _ShareImageView extends State<ShareImageView> {
await insertMediaFileInMessagesTable( await insertMediaFileInMessagesTable(
widget.mediaFileService, widget.mediaFileService,
widget.selectedGroupIds.toList(), widget.selectedGroupIds.toList(),
null,
); );
if (context.mounted) { if (context.mounted) {
@ -288,7 +310,7 @@ class _ShareImageView extends State<ShareImageView> {
), ),
), ),
label: Text( label: Text(
context.lang.shareImagedEditorSendImage, '${context.lang.shareImagedEditorSendImage} (${widget.selectedGroupIds.length})',
style: const TextStyle(fontSize: 17), style: const TextStyle(fontSize: 17),
), ),
), ),

View file

@ -151,6 +151,7 @@ class _MessageInputState extends State<MessageInput> {
await insertMediaFileInMessagesTable( await insertMediaFileInMessagesTable(
mediaFileService, mediaFileService,
[widget.group.groupId], [widget.group.groupId],
null,
); );
} }

View file

@ -174,7 +174,8 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
); );
}; };
} }
if (mediaFile.uploadState == UploadState.preprocessing) { if (mediaFile.uploadState == UploadState.preprocessing ||
mediaFile.uploadState == UploadState.initialized) {
text = context.lang.inProcess; text = context.lang.inProcess;
} }
} }

View file

@ -92,6 +92,16 @@ class _AppearanceViewState extends State<AppearanceView> {
}); });
} }
Future<void> toggleShowImagePreviewWhenSending() async {
await updateUserdata((u) {
u.showShowImagePreviewWhenSending = !u.showShowImagePreviewWhenSending;
return u;
});
setState(() {
// gUser
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final selectedTheme = context.watch<SettingsChangeProvider>().themeMode; final selectedTheme = context.watch<SettingsChangeProvider>().themeMode;
@ -127,6 +137,14 @@ class _AppearanceViewState extends State<AppearanceView> {
onChanged: (a) => toggleStartWithCameraOpen(), onChanged: (a) => toggleStartWithCameraOpen(),
), ),
), ),
ListTile(
title: Text(context.lang.showImagePreviewWhenSending),
onTap: toggleShowImagePreviewWhenSending,
trailing: Switch(
value: gUser.showShowImagePreviewWhenSending,
onChanged: (a) => toggleShowImagePreviewWhenSending(),
),
),
], ],
), ),
); );

View file

@ -882,10 +882,9 @@ packages:
hand_signature: hand_signature:
dependency: "direct main" dependency: "direct main"
description: description:
name: hand_signature path: "dependencies/hand_signature"
sha256: "05b40d3b2d1885a5dda126f26db386660aa46e497b63c96feb91d3198a667eea" relative: true
url: "https://pub.dev" source: path
source: hosted
version: "3.1.0+2" version: "3.1.0+2"
hashlib: hashlib:
dependency: "direct main" dependency: "direct main"
@ -1532,14 +1531,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.28.0" version: "0.28.0"
screenshot:
dependency: "direct main"
description:
name: screenshot
sha256: "63817697a7835e6ce82add4228e15d233b74d42975c143ad8cfe07009fab866b"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
scrollable_positioned_list: scrollable_positioned_list:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -78,13 +78,11 @@ dependencies:
gal: ^2.3.1 gal: ^2.3.1
get: ^4.7.2 get: ^4.7.2
google_mlkit_barcode_scanning: ^0.14.1 google_mlkit_barcode_scanning: ^0.14.1
hand_signature: ^3.0.3
image: ^4.3.0 image: ^4.3.0
no_screenshot: ^0.3.1 no_screenshot: ^0.3.1
permission_handler: ^12.0.0+1 permission_handler: ^12.0.0+1
provider: ^6.1.2 provider: ^6.1.2
restart_app: ^1.3.2 restart_app: ^1.3.2
screenshot: ^3.0.0
sentry_flutter: ^9.8.0 sentry_flutter: ^9.8.0
app_links: ^7.0.0 app_links: ^7.0.0
in_app_purchase: ^3.2.3 in_app_purchase: ^3.2.3
@ -101,6 +99,7 @@ dependencies:
mutex: ^3.1.0 mutex: ^3.1.0
introduction_screen: ^4.0.0 introduction_screen: ^4.0.0
qr_flutter: ^4.1.0 qr_flutter: ^4.1.0
hand_signature: ^3.0.3
dependency_overrides: dependency_overrides:
dots_indicator: dots_indicator:
@ -123,6 +122,8 @@ dependency_overrides:
path: ./dependencies/adaptive_number path: ./dependencies/adaptive_number
ed25519_edwards: ed25519_edwards:
path: ./dependencies/ed25519_edwards path: ./dependencies/ed25519_edwards
hand_signature:
path: ./dependencies/hand_signature
hashlib_codecs: hashlib_codecs:
path: ./dependencies/hashlib_codecs path: ./dependencies/hashlib_codecs
optional: optional: