This commit is contained in:
otsmr 2025-04-29 22:26:38 +02:00
parent 98b4c60e21
commit 586c4aa16a
14 changed files with 129 additions and 22 deletions

View file

@ -32,7 +32,7 @@ class NotificationService: UNNotificationServiceExtension {
bestAttemptContent.body = data!.body;
bestAttemptContent.threadIdentifier = String(format: "%d", data!.notificationId)
} else {
bestAttemptContent.title = "\(bestAttemptContent.title) [failed to decrypt]"
bestAttemptContent.title = "\(bestAttemptContent.title) [10]"
}
contentHandler(bestAttemptContent)
@ -60,6 +60,7 @@ enum PushKind: String, Codable {
case storedMediaFile
case reaction
case testNotification
case reopenedMedia
}
import CryptoKit
@ -106,8 +107,6 @@ func getPushNotificationData(pushDataJson: String) -> (title: String, body: Stri
// Handle the push notification based on the pushKind
if let pushKind = pushKind {
let bestAttemptContent = UNMutableNotificationContent()
if pushKind == .testNotification {
return ("Test Notification", "This is a test notification.", 0)
} else if displayName != nil && fromUserId != nil {
@ -180,6 +179,8 @@ func determinePushKind(from message: String) -> PushKind? {
return .reaction
} else if message.contains("testNotification") {
return .testNotification
} else if message.contains("reopenedMedia") {
return .reopenedMedia
} else {
return nil // Unknown PushKind
}
@ -319,7 +320,8 @@ func getPushNotificationText(pushKind: PushKind) -> String {
.contactRequest: "möchte sich mit dir vernetzen.",
.acceptRequest: "ist jetzt mit dir vernetzt.",
.storedMediaFile: "hat dein Bild gespeichert.",
.reaction: "hat auf dein Bild reagiert."
.reaction: "hat auf dein Bild reagiert.",
.reopenedMedia: "Dein Bild wurde erneut geöffnet."
]
} else { // Default to English
pushNotificationText = [
@ -330,7 +332,8 @@ func getPushNotificationText(pushKind: PushKind) -> String {
.contactRequest: "wants to connect with you.",
.acceptRequest: "is now connected with you.",
.storedMediaFile: "has stored your image.",
.reaction: "has reacted to your image."
.reaction: "has reacted to your image.",
.reopenedMedia: "Your image was reopened."
]
}
@ -353,7 +356,8 @@ func getPushNotificationTextWithoutUserId(pushKind: PushKind) -> String {
.contactRequest: "Du hast eine Kontaktanfrage erhalten.",
.acceptRequest: "Deine Kontaktanfrage wurde angenommen.",
.storedMediaFile: "Dein Bild wurde gespeichert.",
.reaction: "Du hast eine Reaktion auf dein Bild erhalten."
.reaction: "Du hast eine Reaktion auf dein Bild erhalten.",
.reopenedMedia: "hat dein Bild erneut geöffnet."
]
} else { // Default to English
pushNotificationText = [
@ -364,7 +368,8 @@ func getPushNotificationTextWithoutUserId(pushKind: PushKind) -> String {
.contactRequest: "You got a contact request.",
.acceptRequest: "Your contact request has been accepted.",
.storedMediaFile: "Your image has been saved.",
.reaction: "You got a reaction to your image."
.reaction: "You got a reaction to your image.",
.reopenedMedia: "has reopened your image."
]
}

View file

@ -4,6 +4,7 @@ import 'package:twonly/src/database/tables/contacts_table.dart';
enum MessageKind {
textMessage,
storedMediaFile,
reopenedMedia,
media,
contactRequest,
profileChange,

View file

@ -80,7 +80,9 @@
"messageSendState_Sending": "Wird gesendet",
"messageSendState_TapToLoad": "Tippe zum Laden",
"messageSendState_Loading": "Herunterladen",
"messageStoredInGalery": "In Gallerie gespeichert",
"messageStoredInGalery": "Gespeichert",
"messageReopened": "Erneut geöffnet",
"@messageReopened": {},
"imageEditorDrawOk": "Zeichnung machen",
"settingsTitle": "Einstellungen",
"settingsChats": "Chats",

View file

@ -136,6 +136,8 @@
"@messageSendState_Loading": {},
"messageStoredInGalery": "Stored in gallery",
"@messageStoredInGalery": {},
"messageReopened": "Re-opened",
"@messageReopened": {},
"imageEditorDrawOk": "Take drawing",
"@imageEditorDrawOk": {},
"settingsTitle": "Settings",

View file

@ -491,6 +491,12 @@ abstract class AppLocalizations {
/// **'Stored in gallery'**
String get messageStoredInGalery;
/// No description provided for @messageReopened.
///
/// In en, this message translates to:
/// **'Re-opened'**
String get messageReopened;
/// No description provided for @imageEditorDrawOk.
///
/// In en, this message translates to:

View file

@ -206,7 +206,10 @@ class AppLocalizationsDe extends AppLocalizations {
String get messageSendState_Loading => 'Herunterladen';
@override
String get messageStoredInGalery => 'In Gallerie gespeichert';
String get messageStoredInGalery => 'Gespeichert';
@override
String get messageReopened => 'Erneut geöffnet';
@override
String get imageEditorDrawOk => 'Zeichnung machen';

View file

@ -208,6 +208,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get messageStoredInGalery => 'Stored in gallery';
@override
String get messageReopened => 'Re-opened';
@override
String get imageEditorDrawOk => 'Take drawing';

View file

@ -19,7 +19,7 @@ Color getMessageColorFromType(MessageContent content, BuildContext context) {
}
}
} else {
return Colors.black;
return (isDarkMode(context)) ? Colors.white : Colors.black;
}
}
return color;
@ -85,6 +85,8 @@ class MessageContent {
return StoredMediaFileContent.fromJson(json);
case MessageKind.pushKey:
return PushKeyContent.fromJson(json);
case MessageKind.reopenedMedia:
return ReopenedMediaFileContent.fromJson(json);
default:
return null;
}
@ -188,6 +190,20 @@ class StoredMediaFileContent extends MessageContent {
}
}
class ReopenedMediaFileContent extends MessageContent {
int messageId;
ReopenedMediaFileContent({required this.messageId});
static ReopenedMediaFileContent fromJson(Map json) {
return ReopenedMediaFileContent(messageId: json['messageId']);
}
@override
Map toJson() {
return {'messageId': messageId};
}
}
class ProfileContent extends MessageContent {
String avatarSvg;
String displayName;

View file

@ -274,6 +274,12 @@ Future<Uint8List?> readMediaFile(int mediaId, String type) async {
return await file.readAsBytes();
}
Future<bool> existsMediaFile(int mediaId, String type) async {
String basePath = await getMediaFilePath(mediaId, "received");
File file = File("$basePath.$type");
return await file.exists();
}
Future<void> writeMediaFile(int mediaId, String type, Uint8List data) async {
String basePath = await getMediaFilePath(mediaId, "received");
File file = File("$basePath.$type");

View file

@ -130,7 +130,8 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
default:
if (message.kind != MessageKind.textMessage &&
message.kind != MessageKind.media &&
message.kind != MessageKind.storedMediaFile) {
message.kind != MessageKind.storedMediaFile &&
message.kind != MessageKind.reopenedMedia) {
Logger("handleServerMessages")
.shout("Got unknown MessageKind $message");
} else if (message.content == null || message.messageId == null) {
@ -147,7 +148,8 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
bool acknowledgeByUser = false;
DateTime? openedAt;
if (message.kind == MessageKind.storedMediaFile) {
if (message.kind == MessageKind.storedMediaFile ||
message.kind == MessageKind.reopenedMedia) {
acknowledgeByUser = true;
openedAt = DateTime.now();
}
@ -159,6 +161,9 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
if (content is TextMessageContent) {
responseToMessageId = content.responseToMessageId;
}
if (content is ReopenedMediaFileContent) {
responseToMessageId = content.messageId;
}
if (content is StoredMediaFileContent) {
responseToMessageId = content.messageId;
await twonlyDatabase.messagesDao.updateMessageByOtherUser(

View file

@ -163,7 +163,8 @@ enum PushKind {
contactRequest,
acceptRequest,
storedMediaFile,
testNotification
testNotification,
reopenedMedia
}
extension PushKindExtension on PushKind {
@ -532,7 +533,8 @@ String getPushNotificationTextWithoutUserId(PushKind pushKind) {
PushKind.contactRequest.name: "Du hast eine Kontaktanfrage erhalten.",
PushKind.acceptRequest.name: "Deine Kontaktanfrage wurde angenommen.",
PushKind.storedMediaFile.name: "Dein Bild wurde gespeichert.",
PushKind.reaction.name: "Du hast eine Reaktion auf dein Bild erhalten."
PushKind.reaction.name: "Du hast eine Reaktion auf dein Bild erhalten.",
PushKind.reopenedMedia.name: "Dein Bild wurde erneut geöffnet."
};
} else {
pushNotificationText = {
@ -543,7 +545,8 @@ String getPushNotificationTextWithoutUserId(PushKind pushKind) {
PushKind.contactRequest.name: "You got a contact request.",
PushKind.acceptRequest.name: "Your contact request has been accepted.",
PushKind.storedMediaFile.name: "Your image has been saved.",
PushKind.reaction.name: "You got a reaction to your image."
PushKind.reaction.name: "You got a reaction to your image.",
PushKind.reopenedMedia.name: "Your image was reopened."
};
}
return pushNotificationText[pushKind.name] ?? "";
@ -563,7 +566,8 @@ String getPushNotificationText(PushKind pushKind) {
PushKind.contactRequest.name: "möchte sich mir dir vernetzen.",
PushKind.acceptRequest.name: "ist jetzt mit dir vernetzt.",
PushKind.storedMediaFile.name: "hat dein Bild gespeichert.",
PushKind.reaction.name: "hat auf dein Bild reagiert."
PushKind.reaction.name: "hat auf dein Bild reagiert.",
PushKind.reopenedMedia.name: "hat dein Bild erneut geöffnet."
};
} else {
pushNotificationText = {
@ -574,7 +578,8 @@ String getPushNotificationText(PushKind pushKind) {
PushKind.contactRequest.name: "wants to connect with you.",
PushKind.acceptRequest.name: "is now connected with you.",
PushKind.storedMediaFile.name: "has stored your image.",
PushKind.reaction.name: "has reacted to your image."
PushKind.reaction.name: "has reacted to your image.",
PushKind.reopenedMedia.name: "has reopened your image."
};
}
return pushNotificationText[pushKind.name] ?? "";

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart';
@ -118,11 +119,35 @@ class ChatListEntry extends StatelessWidget {
Widget getReactionRow() {
List<Widget> children = [];
bool hasOneTextReaction = false;
bool hasOneStored = false;
bool hasOneReopened = false;
for (final reaction in reactions) {
MessageContent? content = MessageContent.fromJson(
reaction.kind, jsonDecode(reaction.contentJson!));
if (content is StoredMediaFileContent || message.mediaStored) {
// if (content is StoredMediaFileContent || message.mediaStored) {
// if (hasOneStored) continue;
// hasOneStored = true;
// children.add(
// Expanded(
// child: Align(
// alignment: Alignment.bottomRight,
// child: Padding(
// padding: EdgeInsets.only(right: 3),
// child: FaIcon(
// FontAwesomeIcons.floppyDisk,
// size: 12,
// color: Colors.blue,
// ),
// ),
// ),
// ),
// );
// }
if (content is ReopenedMediaFileContent) {
if (hasOneReopened) continue;
hasOneReopened = true;
children.add(
Expanded(
child: Align(
@ -130,16 +155,15 @@ class ChatListEntry extends StatelessWidget {
child: Padding(
padding: EdgeInsets.only(right: 3),
child: FaIcon(
FontAwesomeIcons.floppyDisk,
FontAwesomeIcons.repeat,
size: 12,
color: Colors.blue,
color: Colors.yellow,
),
),
),
),
);
}
// only show one reaction
if (hasOneTextReaction) continue;
@ -282,6 +306,30 @@ class ChatListEntry extends StatelessWidget {
);
child = GestureDetector(
onDoubleTap: () async {
if (message.openedAt == null && message.messageOtherId != null) {
return;
}
if (await existsMediaFile(message.messageId, "png")) {
encryptAndSendMessage(
null,
contact.userId,
MessageJson(
kind: MessageKind.reopenedMedia,
messageId: message.messageId,
content: ReopenedMediaFileContent(
messageId: message.messageOtherId!,
),
timestamp: DateTime.now(),
),
pushKind: PushKind.reopenedMedia,
);
await twonlyDatabase.messagesDao.updateMessageByMessageId(
message.messageId,
MessagesCompanion(openedAt: Value(null)),
);
}
},
onTap: () {
if (message.kind == MessageKind.media) {
if (message.downloadState == DownloadState.downloaded &&

View file

@ -103,7 +103,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
progressTimer?.cancel();
if (allMediaFiles.isNotEmpty) {
try {
if (!imageSaved) {
if (!imageSaved && maxShowTime != gMediaShowInfinite) {
await deleteMediaFile(allMediaFiles.first.messageId, "mp4");
await deleteMediaFile(allMediaFiles.first.messageId, "png");
}

View file

@ -144,6 +144,11 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
text = context.lang.messageStoredInGalery;
}
if (message.kind == MessageKind.reopenedMedia) {
icon = FaIcon(FontAwesomeIcons.repeat, size: 12, color: color);
text = context.lang.messageReopened;
}
if (message.errorWhileSending) {
icon =
FaIcon(FontAwesomeIcons.circleExclamation, size: 12, color: color);