mirror front camera for android

This commit is contained in:
otsmr 2025-04-29 15:08:24 +02:00
parent 06715e8ac9
commit 0d36289169
7 changed files with 111 additions and 71 deletions

View file

@ -47,6 +47,7 @@ class MediaUploadMetadata {
late DateTime messageSendAt; late DateTime messageSendAt;
late bool isVideo; late bool isVideo;
late bool videoWithAudio; late bool videoWithAudio;
late bool mirrorVideo;
MediaUploadMetadata(); MediaUploadMetadata();
@ -54,6 +55,7 @@ class MediaUploadMetadata {
return { return {
'contactIds': contactIds, 'contactIds': contactIds,
'isRealTwonly': isRealTwonly, 'isRealTwonly': isRealTwonly,
'mirrorVideo': mirrorVideo,
'maxShowTime': maxShowTime, 'maxShowTime': maxShowTime,
'isVideo': isVideo, 'isVideo': isVideo,
'videoWithAudio': videoWithAudio, 'videoWithAudio': videoWithAudio,
@ -67,6 +69,7 @@ class MediaUploadMetadata {
state.isRealTwonly = json['isRealTwonly']; state.isRealTwonly = json['isRealTwonly'];
state.videoWithAudio = json['videoWithAudio']; state.videoWithAudio = json['videoWithAudio'];
state.isVideo = json['isVideo']; state.isVideo = json['isVideo'];
state.mirrorVideo = json['mirrorVideo'];
state.maxShowTime = json['maxShowTime']; state.maxShowTime = json['maxShowTime'];
state.maxShowTime = json['maxShowTime']; state.maxShowTime = json['maxShowTime'];
state.messageSendAt = DateTime.parse(json['messageSendAt']); state.messageSendAt = DateTime.parse(json['messageSendAt']);

View file

@ -99,6 +99,7 @@ class MediaMessageContent extends MessageContent {
final int maxShowTime; final int maxShowTime;
final bool isRealTwonly; final bool isRealTwonly;
final bool isVideo; final bool isVideo;
final bool mirrorVideo;
final List<int>? downloadToken; final List<int>? downloadToken;
final List<int>? encryptionKey; final List<int>? encryptionKey;
final List<int>? encryptionMac; final List<int>? encryptionMac;
@ -108,6 +109,7 @@ class MediaMessageContent extends MessageContent {
required this.maxShowTime, required this.maxShowTime,
required this.isRealTwonly, required this.isRealTwonly,
required this.isVideo, required this.isVideo,
required this.mirrorVideo,
this.downloadToken, this.downloadToken,
this.encryptionKey, this.encryptionKey,
this.encryptionMac, this.encryptionMac,
@ -131,6 +133,7 @@ class MediaMessageContent extends MessageContent {
maxShowTime: json['maxShowTime'], maxShowTime: json['maxShowTime'],
isRealTwonly: json['isRealTwonly'], isRealTwonly: json['isRealTwonly'],
isVideo: json['isVideo'] ?? false, isVideo: json['isVideo'] ?? false,
mirrorVideo: json['mirrorVideo'] ?? false,
); );
} }
@ -144,6 +147,7 @@ class MediaMessageContent extends MessageContent {
'isRealTwonly': isRealTwonly, 'isRealTwonly': isRealTwonly,
'maxShowTime': maxShowTime, 'maxShowTime': maxShowTime,
'isVideo': isVideo, 'isVideo': isVideo,
'mirrorVideo': mirrorVideo,
}; };
} }
} }

View file

@ -20,13 +20,13 @@ import 'package:twonly/src/services/notification_service.dart';
import 'package:video_compress/video_compress.dart'; import 'package:video_compress/video_compress.dart';
Future sendMediaFile( Future sendMediaFile(
List<int> userIds, List<int> userIds,
Uint8List imageBytes, Uint8List imageBytes,
bool isRealTwonly, bool isRealTwonly,
int maxShowTime, int maxShowTime,
XFile? videoFilePath, XFile? videoFilePath,
bool? enableVideoAudio, bool? enableVideoAudio,
) async { bool mirrorVideo) async {
MediaUploadMetadata metadata = MediaUploadMetadata(); MediaUploadMetadata metadata = MediaUploadMetadata();
metadata.contactIds = userIds; metadata.contactIds = userIds;
metadata.isRealTwonly = isRealTwonly; metadata.isRealTwonly = isRealTwonly;
@ -34,6 +34,7 @@ Future sendMediaFile(
metadata.isVideo = videoFilePath != null; metadata.isVideo = videoFilePath != null;
metadata.videoWithAudio = enableVideoAudio != null && enableVideoAudio; metadata.videoWithAudio = enableVideoAudio != null && enableVideoAudio;
metadata.maxShowTime = maxShowTime; metadata.maxShowTime = maxShowTime;
metadata.mirrorVideo = mirrorVideo;
int? mediaUploadId = await twonlyDatabase.mediaUploadsDao.insertMediaUpload( int? mediaUploadId = await twonlyDatabase.mediaUploadsDao.insertMediaUpload(
MediaUploadsCompanion( MediaUploadsCompanion(
@ -62,62 +63,62 @@ Future retryMediaUpload() async {
final lockingHandleMediaFile = Mutex(); final lockingHandleMediaFile = Mutex();
Future handleSingleMediaFile(int mediaUploadId) async { Future handleSingleMediaFile(int mediaUploadId) async {
// await lockingHandleMediaFile.protect(() async { await lockingHandleMediaFile.protect(() async {
MediaUpload? media = await twonlyDatabase.mediaUploadsDao MediaUpload? media = await twonlyDatabase.mediaUploadsDao
.getMediaUploadById(mediaUploadId) .getMediaUploadById(mediaUploadId)
.getSingleOrNull(); .getSingleOrNull();
if (media == null) return; if (media == null) return;
try { try {
switch (media.state) { switch (media.state) {
case UploadState.pending: case UploadState.pending:
await handleAddToMessageDb(media); await handleAddToMessageDb(media);
break; break;
case UploadState.addedToMessagesDb: case UploadState.addedToMessagesDb:
await handleCompressionState(media); await handleCompressionState(media);
break; break;
case UploadState.isCompressed: case UploadState.isCompressed:
await handleEncryptionState(media); await handleEncryptionState(media);
break; break;
case UploadState.isEncrypted: case UploadState.isEncrypted:
if (!await handleGetUploadToken(media)) { if (!await handleGetUploadToken(media)) {
return; // recoverable error. try again when connected again to the server... return; // recoverable error. try again when connected again to the server...
} }
break; break;
case UploadState.hasUploadToken: case UploadState.hasUploadToken:
if (!await handleUpload(media)) { if (!await handleUpload(media)) {
return; // recoverable error. try again when connected again to the server... return; // recoverable error. try again when connected again to the server...
} }
break; break;
case UploadState.isUploaded: case UploadState.isUploaded:
if (!await handleNotifyReceiver(media)) { if (!await handleNotifyReceiver(media)) {
return; // recoverable error. try again when connected again to the server... return; // recoverable error. try again when connected again to the server...
} }
break; break;
case UploadState.receiverNotified: case UploadState.receiverNotified:
return; return;
}
// this will be called until there is an recoverable error OR
// the upload is ready
await handleSingleMediaFile(mediaUploadId);
} catch (e) {
// if the messageIds are already there notify the user about this error...
if (media.messageIds != null) {
for (int messageId in media.messageIds!) {
await twonlyDatabase.messagesDao.updateMessageByMessageId(
messageId,
MessagesCompanion(
errorWhileSending: Value(true),
),
);
} }
} catch (e) {
// if the messageIds are already there notify the user about this error...
if (media.messageIds != null) {
for (int messageId in media.messageIds!) {
await twonlyDatabase.messagesDao.updateMessageByMessageId(
messageId,
MessagesCompanion(
errorWhileSending: Value(true),
),
);
}
}
await twonlyDatabase.mediaUploadsDao.deleteMediaUpload(mediaUploadId);
Logger("media_send.dart")
.shout("Non recoverable error while sending media file: $e");
return;
} }
await twonlyDatabase.mediaUploadsDao.deleteMediaUpload(mediaUploadId); });
Logger("media_send.dart") // this will be called until there is an recoverable error OR
.shout("Non recoverable error while sending media file: $e"); // the upload is ready
} await handleSingleMediaFile(mediaUploadId);
// });
} }
Future handleAddToMessageDb(MediaUpload media) async { Future handleAddToMessageDb(MediaUpload media) async {
@ -137,6 +138,7 @@ Future handleAddToMessageDb(MediaUpload media) async {
maxShowTime: media.metadata.maxShowTime, maxShowTime: media.metadata.maxShowTime,
isRealTwonly: media.metadata.isRealTwonly, isRealTwonly: media.metadata.isRealTwonly,
isVideo: media.metadata.isVideo, isVideo: media.metadata.isVideo,
mirrorVideo: media.metadata.mirrorVideo,
).toJson(), ).toJson(),
), ),
), ),
@ -357,7 +359,11 @@ Future<bool> handleUpload(MediaUpload media) async {
), ),
); );
await deleteMediaFile(media, "encrypted"); try {
await deleteMediaFile(media, "encrypted");
} catch (e) {
Logger("media_send.dart").shout("$e");
}
return true; return true;
} }
@ -395,6 +401,7 @@ Future<bool> handleNotifyReceiver(MediaUpload media) async {
maxShowTime: media.metadata.maxShowTime, maxShowTime: media.metadata.maxShowTime,
isRealTwonly: media.metadata.isRealTwonly, isRealTwonly: media.metadata.isRealTwonly,
isVideo: media.metadata.isVideo, isVideo: media.metadata.isVideo,
mirrorVideo: media.metadata.mirrorVideo,
encryptionKey: media.encryptionData!.encryptionKey, encryptionKey: media.encryptionData!.encryptionKey,
encryptionMac: media.encryptionData!.encryptionMac, encryptionMac: media.encryptionData!.encryptionMac,
encryptionNonce: media.encryptionData!.encryptionNonce, encryptionNonce: media.encryptionData!.encryptionNonce,

View file

@ -232,6 +232,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
videoFilePath: videoFilePath, videoFilePath: videoFilePath,
imageBytes: imageBytes, imageBytes: imageBytes,
sendTo: widget.sendTo, sendTo: widget.sendTo,
mirrorVideo: isFront && Platform.isAndroid,
), ),
transitionsBuilder: (context, animation, secondaryAnimation, child) { transitionsBuilder: (context, animation, secondaryAnimation, child) {
return child; return child;

View file

@ -30,11 +30,17 @@ List<Layer> removedLayers = [];
const gMediaShowInfinite = 999999; const gMediaShowInfinite = 999999;
class ShareImageEditorView extends StatefulWidget { class ShareImageEditorView extends StatefulWidget {
const ShareImageEditorView( const ShareImageEditorView({
{super.key, this.imageBytes, this.sendTo, this.videoFilePath}); super.key,
this.imageBytes,
this.sendTo,
this.videoFilePath,
required this.mirrorVideo,
});
final Future<Uint8List?>? imageBytes; final Future<Uint8List?>? imageBytes;
final XFile? videoFilePath; final XFile? videoFilePath;
final Contact? sendTo; final Contact? sendTo;
final bool mirrorVideo;
@override @override
State<ShareImageEditorView> createState() => _ShareImageEditorView(); State<ShareImageEditorView> createState() => _ShareImageEditorView();
} }
@ -289,6 +295,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
maxShowTime: maxShowTime, maxShowTime: maxShowTime,
preselectedUser: widget.sendTo, preselectedUser: widget.sendTo,
videoFilePath: widget.videoFilePath, videoFilePath: widget.videoFilePath,
mirrorVideo: widget.mirrorVideo,
), ),
), ),
); );
@ -357,6 +364,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
maxShowTime, maxShowTime,
widget.videoFilePath, widget.videoFilePath,
videoWithAudio, videoWithAudio,
widget.mirrorVideo,
); );
if (context.mounted) { if (context.mounted) {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
@ -405,7 +413,12 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
child: Stack( child: Stack(
children: [ children: [
if (videoController != null) if (videoController != null)
Positioned.fill(child: VideoPlayer(videoController!)), Positioned.fill(
child: Transform.flip(
flipX: widget.mirrorVideo,
child: VideoPlayer(videoController!),
),
),
Screenshot( Screenshot(
controller: screenshotController, controller: screenshotController,
child: LayersViewer( child: LayersViewer(

View file

@ -21,12 +21,14 @@ class ShareImageView extends StatefulWidget {
{super.key, {super.key,
required this.imageBytesFuture, required this.imageBytesFuture,
required this.isRealTwonly, required this.isRealTwonly,
required this.mirrorVideo,
required this.maxShowTime, required this.maxShowTime,
this.preselectedUser, this.preselectedUser,
required this.videoFilePath, required this.videoFilePath,
this.enableVideoAudio}); this.enableVideoAudio});
final Future<Uint8List?> imageBytesFuture; final Future<Uint8List?> imageBytesFuture;
final bool isRealTwonly; final bool isRealTwonly;
final bool mirrorVideo;
final int maxShowTime; final int maxShowTime;
final XFile? videoFilePath; final XFile? videoFilePath;
final Contact? preselectedUser; final Contact? preselectedUser;
@ -232,12 +234,14 @@ class _ShareImageView extends State<ShareImageView> {
sendingImage = true; sendingImage = true;
}); });
sendMediaFile( sendMediaFile(
_selectedUserIds.toList(), _selectedUserIds.toList(),
imageBytes!, imageBytes!,
widget.isRealTwonly, widget.isRealTwonly,
widget.maxShowTime, widget.maxShowTime,
widget.videoFilePath, widget.videoFilePath,
widget.enableVideoAudio); widget.enableVideoAudio,
widget.mirrorVideo,
);
if (context.mounted) { if (context.mounted) {
if (widget.preselectedUser != null) { if (widget.preselectedUser != null) {
Navigator.pop(context, true); Navigator.pop(context, true);

View file

@ -47,6 +47,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
int maxShowTime = 999999; int maxShowTime = 999999;
double progress = 0; double progress = 0;
bool isRealTwonly = false; bool isRealTwonly = false;
bool mirrorVideo = false;
bool isDownloading = false; bool isDownloading = false;
bool showSendTextMessageInput = false; bool showSendTextMessageInput = false;
@ -120,6 +121,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
maxShowTime = 999999; maxShowTime = 999999;
imageSaving = false; imageSaving = false;
imageSaved = false; imageSaved = false;
mirrorVideo = false;
progress = 0; progress = 0;
isDownloading = false; isDownloading = false;
isRealTwonly = false; isRealTwonly = false;
@ -226,6 +228,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
setState(() { setState(() {
maxShowTime = content.maxShowTime; maxShowTime = content.maxShowTime;
isDownloading = false; isDownloading = false;
mirrorVideo = content.mirrorVideo;
}); });
} }
@ -412,7 +415,12 @@ class _MediaViewerViewState extends State<MediaViewerView> {
child: Stack( child: Stack(
children: [ children: [
if (videoController != null) if (videoController != null)
Positioned.fill(child: VideoPlayer(videoController!)), Positioned.fill(
child: Transform.flip(
flipX: mirrorVideo,
child: VideoPlayer(videoController!),
),
),
if (imageBytes != null) if (imageBytes != null)
Positioned.fill( Positioned.fill(
child: Image.memory( child: Image.memory(