mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-04-18 14:42:54 +00:00
add option to reopen images with context menu
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
This commit is contained in:
parent
19a53a879b
commit
587740f306
5 changed files with 124 additions and 85 deletions
|
|
@ -1,5 +1,9 @@
|
|||
# Changelog
|
||||
|
||||
## 0.1.4
|
||||
|
||||
- Fix: Several minor issues with the user interface
|
||||
|
||||
## 0.1.3
|
||||
|
||||
- New: Video stabilization
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 662b8ddafcbf1c789f54c93da51ebb0514ba1f81
|
||||
Subproject commit f633c60dfe0edf36a8ed91804dba7a2879b5bc52
|
||||
|
|
@ -44,8 +44,9 @@ class MediaFileService {
|
|||
delete = false;
|
||||
}
|
||||
|
||||
final messages =
|
||||
await twonlyDB.messagesDao.getMessagesByMediaId(mediaId);
|
||||
final messages = await twonlyDB.messagesDao.getMessagesByMediaId(
|
||||
mediaId,
|
||||
);
|
||||
|
||||
// in case messages in empty the file will be deleted, as delete is true by default
|
||||
|
||||
|
|
@ -63,16 +64,18 @@ class MediaFileService {
|
|||
// This branch will prevent to reach the next if condition, with would otherwise store the image for two days
|
||||
// delete = true; // do not overwrite a previous delete = false
|
||||
// this is just to make it easier to understand :)
|
||||
} else if (message.openedAt!
|
||||
.isAfter(clock.now().subtract(const Duration(days: 2)))) {
|
||||
} else if (message.openedAt!.isAfter(
|
||||
clock.now().subtract(const Duration(days: 2)),
|
||||
)) {
|
||||
// In case the image was opened, but send with unlimited time or no authentication.
|
||||
if (message.senderId == null) {
|
||||
delete = false;
|
||||
} else {
|
||||
// Check weather the image was send in a group. Then the images is preserved for two days in case another person stores the image.
|
||||
// This also allows to reopen this image for two days.
|
||||
final group =
|
||||
await twonlyDB.groupsDao.getGroup(message.groupId);
|
||||
final group = await twonlyDB.groupsDao.getGroup(
|
||||
message.groupId,
|
||||
);
|
||||
if (group != null && !group.isDirectChat) {
|
||||
delete = false;
|
||||
}
|
||||
|
|
@ -93,8 +96,9 @@ class MediaFileService {
|
|||
}
|
||||
|
||||
Future<void> updateFromDB() async {
|
||||
final updated =
|
||||
await twonlyDB.mediaFilesDao.getMediaFileById(mediaFile.mediaId);
|
||||
final updated = await twonlyDB.mediaFilesDao.getMediaFileById(
|
||||
mediaFile.mediaId,
|
||||
);
|
||||
if (updated != null) {
|
||||
mediaFile = updated;
|
||||
}
|
||||
|
|
@ -151,8 +155,9 @@ class MediaFileService {
|
|||
mediaFile.mediaId,
|
||||
MediaFilesCompanion(
|
||||
requiresAuthentication: Value(requiresAuthentication),
|
||||
displayLimitInMilliseconds:
|
||||
requiresAuthentication ? const Value(12000) : const Value.absent(),
|
||||
displayLimitInMilliseconds: requiresAuthentication
|
||||
? const Value(12000)
|
||||
: const Value.absent(),
|
||||
),
|
||||
);
|
||||
await updateFromDB();
|
||||
|
|
@ -208,6 +213,13 @@ class MediaFileService {
|
|||
}
|
||||
}
|
||||
|
||||
// Media was send with unlimited display limit time and without auth required
|
||||
// and the temp media file still exists, then the media file can be reopened again...
|
||||
bool get canBeOpenedAgain =>
|
||||
!mediaFile.requiresAuthentication &&
|
||||
mediaFile.displayLimitInMilliseconds == null &&
|
||||
tempPath.existsSync();
|
||||
|
||||
bool get imagePreviewAvailable =>
|
||||
thumbnailPath.existsSync() || storedPath.existsSync();
|
||||
|
||||
|
|
@ -293,8 +305,10 @@ class MediaFileService {
|
|||
extension = 'm4a';
|
||||
}
|
||||
}
|
||||
final mediaBaseDir =
|
||||
buildDirectoryPath(directory, globalApplicationSupportDirectory);
|
||||
final mediaBaseDir = buildDirectoryPath(
|
||||
directory,
|
||||
globalApplicationSupportDirectory,
|
||||
);
|
||||
return File(
|
||||
join(mediaBaseDir.path, '${mediaFile.mediaId}$namePrefix.$extension'),
|
||||
);
|
||||
|
|
@ -303,29 +317,29 @@ class MediaFileService {
|
|||
File get tempPath => _buildFilePath('tmp');
|
||||
File get storedPath => _buildFilePath('stored');
|
||||
File get thumbnailPath => _buildFilePath(
|
||||
'stored',
|
||||
namePrefix: '.thumbnail',
|
||||
extensionParam: 'webp',
|
||||
);
|
||||
'stored',
|
||||
namePrefix: '.thumbnail',
|
||||
extensionParam: 'webp',
|
||||
);
|
||||
File get encryptedPath => _buildFilePath(
|
||||
'tmp',
|
||||
namePrefix: '.encrypted',
|
||||
);
|
||||
'tmp',
|
||||
namePrefix: '.encrypted',
|
||||
);
|
||||
File get uploadRequestPath => _buildFilePath(
|
||||
'tmp',
|
||||
namePrefix: '.upload',
|
||||
);
|
||||
'tmp',
|
||||
namePrefix: '.upload',
|
||||
);
|
||||
File get originalPath => _buildFilePath(
|
||||
'tmp',
|
||||
namePrefix: '.original',
|
||||
);
|
||||
'tmp',
|
||||
namePrefix: '.original',
|
||||
);
|
||||
File get ffmpegOutputPath => _buildFilePath(
|
||||
'tmp',
|
||||
namePrefix: '.ffmpeg',
|
||||
);
|
||||
'tmp',
|
||||
namePrefix: '.ffmpeg',
|
||||
);
|
||||
File get overlayImagePath => _buildFilePath(
|
||||
'tmp',
|
||||
namePrefix: '.overlay',
|
||||
extensionParam: 'png',
|
||||
);
|
||||
'tmp',
|
||||
namePrefix: '.overlay',
|
||||
extensionParam: 'png',
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,12 +57,10 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
|||
widget.mediaService.mediaFile.displayLimitInMilliseconds != null) {
|
||||
return;
|
||||
}
|
||||
if (widget.mediaService.tempPath.existsSync()) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_canBeReopened = true;
|
||||
});
|
||||
}
|
||||
if (widget.mediaService.tempPath.existsSync() && mounted) {
|
||||
setState(() {
|
||||
_canBeReopened = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +68,7 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
|||
if (widget.message.openedAt == null || widget.message.mediaStored) {
|
||||
return;
|
||||
}
|
||||
if (widget.mediaService.tempPath.existsSync() &&
|
||||
if (widget.mediaService.canBeOpenedAgain &&
|
||||
widget.message.senderId != null) {
|
||||
await sendCipherText(
|
||||
widget.message.senderId!,
|
||||
|
|
@ -123,8 +121,14 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
|||
|
||||
final addData = widget.message.additionalMessageData;
|
||||
if (addData != null) {
|
||||
final info =
|
||||
getBubbleInfo(context, widget.message, null, null, null, 200);
|
||||
final info = getBubbleInfo(
|
||||
context,
|
||||
widget.message,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
200,
|
||||
);
|
||||
final data = AdditionalMessageData.fromBuffer(addData);
|
||||
if (data.hasLink() && widget.message.mediaStored) {
|
||||
imageBorderRadius = const BorderRadius.only(
|
||||
|
|
@ -138,8 +142,12 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
|||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||
),
|
||||
padding:
|
||||
const EdgeInsets.only(left: 10, top: 6, bottom: 6, right: 10),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 10,
|
||||
top: 6,
|
||||
bottom: 6,
|
||||
right: 10,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: info.color,
|
||||
borderRadius: const BorderRadius.only(
|
||||
|
|
@ -170,7 +178,8 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
|||
onTap: (widget.message.type == MessageType.media.name) ? onTap : null,
|
||||
child: SizedBox(
|
||||
width: (widget.minWidth > 150) ? widget.minWidth : 150,
|
||||
height: (widget.message.mediaStored &&
|
||||
height:
|
||||
(widget.message.mediaStored &&
|
||||
widget.mediaService.imagePreviewAvailable)
|
||||
? 271
|
||||
: null,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// ignore_for_file: inference_failure_on_function_invocation
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
|
@ -39,57 +40,67 @@ class MessageContextMenu extends StatelessWidget {
|
|||
final VoidCallback onResponseTriggered;
|
||||
|
||||
Future<void> reopenMediaFile(BuildContext context) async {
|
||||
final isAuth = await authenticateUser(
|
||||
context.lang.authRequestReopenImage,
|
||||
force: false,
|
||||
);
|
||||
if (message.senderId == null) {
|
||||
final isAuth = await authenticateUser(
|
||||
context.lang.authRequestReopenImage,
|
||||
force: false,
|
||||
);
|
||||
if (!isAuth) return;
|
||||
}
|
||||
|
||||
if (isAuth && context.mounted && mediaFileService != null) {
|
||||
final galleryItems = [
|
||||
MemoryItem(mediaService: mediaFileService!, messages: []),
|
||||
];
|
||||
if (!context.mounted || mediaFileService == null) return;
|
||||
|
||||
await Navigator.push(
|
||||
context,
|
||||
PageRouteBuilder(
|
||||
opaque: false,
|
||||
pageBuilder: (context, a1, a2) => MemoriesPhotoSliderView(
|
||||
galleryItems: galleryItems,
|
||||
if (message.senderId != null) {
|
||||
// notify the sender
|
||||
await sendCipherText(
|
||||
message.senderId!,
|
||||
pb.EncryptedContent(
|
||||
mediaUpdate: pb.EncryptedContent_MediaUpdate(
|
||||
type: pb.EncryptedContent_MediaUpdate_Type.REOPENED,
|
||||
targetMessageId: message.messageId,
|
||||
),
|
||||
),
|
||||
);
|
||||
await twonlyDB.messagesDao.updateMessageId(
|
||||
message.messageId,
|
||||
const MessagesCompanion(openedAt: Value(null)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
|
||||
final galleryItems = [
|
||||
MemoryItem(mediaService: mediaFileService!, messages: []),
|
||||
];
|
||||
|
||||
await Navigator.push(
|
||||
context,
|
||||
PageRouteBuilder(
|
||||
opaque: false,
|
||||
pageBuilder: (context, a1, a2) => MemoriesPhotoSliderView(
|
||||
galleryItems: galleryItems,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var canBeOpenedAgain = false;
|
||||
// in case this is a media send from this user...
|
||||
if (mediaFileService != null && message.senderId == null) {
|
||||
// and the media was send with unlimited display limit time and without auth required...
|
||||
if (!mediaFileService!.mediaFile.requiresAuthentication &&
|
||||
mediaFileService!.mediaFile.displayLimitInMilliseconds == null) {
|
||||
// and the temp media file still exists
|
||||
if (mediaFileService!.tempPath.existsSync()) {
|
||||
// the media file can be opened again...
|
||||
canBeOpenedAgain = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ContextMenu(
|
||||
items: [
|
||||
if (!message.isDeletedFromSender)
|
||||
ContextMenuItem(
|
||||
title: context.lang.react,
|
||||
onTap: () async {
|
||||
final layer = await showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.black,
|
||||
builder: (context) {
|
||||
return const EmojiPickerBottom();
|
||||
},
|
||||
) as EmojiLayerData?;
|
||||
final layer =
|
||||
await showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.black,
|
||||
builder: (context) {
|
||||
return const EmojiPickerBottom();
|
||||
},
|
||||
)
|
||||
as EmojiLayerData?;
|
||||
if (layer == null) return;
|
||||
|
||||
await twonlyDB.reactionsDao.updateMyReaction(
|
||||
|
|
@ -111,7 +122,7 @@ class MessageContextMenu extends StatelessWidget {
|
|||
},
|
||||
icon: FontAwesomeIcons.faceLaugh,
|
||||
),
|
||||
if (canBeOpenedAgain)
|
||||
if (mediaFileService?.canBeOpenedAgain ?? false)
|
||||
ContextMenuItem(
|
||||
title: context.lang.contextMenuViewAgain,
|
||||
onTap: () => reopenMediaFile(context),
|
||||
|
|
@ -153,8 +164,8 @@ class MessageContextMenu extends StatelessWidget {
|
|||
null,
|
||||
customOk:
|
||||
(message.senderId == null && !message.isDeletedFromSender)
|
||||
? context.lang.deleteOkBtnForAll
|
||||
: context.lang.deleteOkBtnForMe,
|
||||
? context.lang.deleteOkBtnForAll
|
||||
: context.lang.deleteOkBtnForMe,
|
||||
);
|
||||
if (delete) {
|
||||
if (message.senderId == null && !message.isDeletedFromSender) {
|
||||
|
|
@ -173,8 +184,9 @@ class MessageContextMenu extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
} else {
|
||||
await twonlyDB.messagesDao
|
||||
.deleteMessagesById(message.messageId);
|
||||
await twonlyDB.messagesDao.deleteMessagesById(
|
||||
message.messageId,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue