mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:08:40 +00:00
fix #48
This commit is contained in:
parent
d9741ee877
commit
06feb2d18c
10 changed files with 178 additions and 52 deletions
|
|
@ -85,6 +85,8 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
|||
|
||||
for (final message in widget.messages) {
|
||||
if (icons.length == 2) break;
|
||||
if (kindsAlreadyShown.contains(message.kind)) continue;
|
||||
kindsAlreadyShown.add(message.kind);
|
||||
|
||||
MessageSendState state = messageSendStateFromMessage(message);
|
||||
late Color color;
|
||||
|
|
@ -94,14 +96,13 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
|||
getMessageColorFromType(TextMessageContent(text: ""), twonlyColor);
|
||||
} else {
|
||||
MessageContent? content = MessageContent.fromJson(
|
||||
message.kind, jsonDecode(message.contentJson!));
|
||||
message.kind,
|
||||
jsonDecode(message.contentJson!),
|
||||
);
|
||||
if (content == null) continue;
|
||||
color = getMessageColorFromType(content, twonlyColor);
|
||||
}
|
||||
|
||||
if (kindsAlreadyShown.contains(message.kind)) continue;
|
||||
kindsAlreadyShown.add(message.kind);
|
||||
|
||||
Widget icon = Placeholder();
|
||||
|
||||
switch (state) {
|
||||
|
|
@ -140,6 +141,11 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
|||
break;
|
||||
}
|
||||
|
||||
if (message.kind == MessageKind.storedMediaFile) {
|
||||
icon = FaIcon(FontAwesomeIcons.floppyDisk, size: 12, color: color);
|
||||
text = "Stored in gallery";
|
||||
}
|
||||
|
||||
icons.add(icon);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,13 +76,13 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
|||
.get();
|
||||
}
|
||||
|
||||
Future openedAllTextMessages(int contactId) {
|
||||
Future openedAllNonMediaMessages(int contactId) {
|
||||
final updates = MessagesCompanion(openedAt: Value(DateTime.now()));
|
||||
return (update(messages)
|
||||
..where((t) =>
|
||||
t.contactId.equals(contactId) &
|
||||
t.openedAt.isNull() &
|
||||
t.kind.equals(MessageKind.textMessage.name)))
|
||||
t.kind.equals(MessageKind.media.name).not()))
|
||||
.write(updates);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
|
||||
enum MessageKind {
|
||||
textMessage,
|
||||
storedMediaFile,
|
||||
media,
|
||||
contactRequest,
|
||||
profileChange,
|
||||
|
|
@ -20,7 +21,7 @@ Map<String, Color> messageKindColors = {
|
|||
Color getMessageColorFromType(MessageContent content, Color primary) {
|
||||
Color color;
|
||||
|
||||
if (content is TextMessageContent) {
|
||||
if (content is TextMessageContent || content is StoredMediaFileContent) {
|
||||
color = messageKindColors["text"]!;
|
||||
} else {
|
||||
if (content is MediaMessageContent) {
|
||||
|
|
@ -96,6 +97,8 @@ class MessageContent {
|
|||
return TextMessageContent.fromJson(json);
|
||||
case MessageKind.profileChange:
|
||||
return ProfileContent.fromJson(json);
|
||||
case MessageKind.storedMediaFile:
|
||||
return StoredMediaFileContent.fromJson(json);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
@ -172,6 +175,20 @@ class TextMessageContent extends MessageContent {
|
|||
}
|
||||
}
|
||||
|
||||
class StoredMediaFileContent extends MessageContent {
|
||||
int messageId;
|
||||
StoredMediaFileContent({required this.messageId});
|
||||
|
||||
static StoredMediaFileContent fromJson(Map json) {
|
||||
return StoredMediaFileContent(messageId: json['messageId']);
|
||||
}
|
||||
|
||||
@override
|
||||
Map toJson() {
|
||||
return {'messageId': messageId};
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileContent extends MessageContent {
|
||||
String avatarSvg;
|
||||
String displayName;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class UserData {
|
|||
String? avatarSvg;
|
||||
String? avatarJson;
|
||||
int? avatarCounter;
|
||||
int? defaultShowTime;
|
||||
|
||||
final int userId;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
|||
)
|
||||
..avatarSvg = json['avatarSvg'] as String?
|
||||
..avatarJson = json['avatarJson'] as String?
|
||||
..avatarCounter = (json['avatarCounter'] as num?)?.toInt();
|
||||
..avatarCounter = (json['avatarCounter'] as num?)?.toInt()
|
||||
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt();
|
||||
|
||||
Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||
'username': instance.username,
|
||||
|
|
@ -21,5 +22,6 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'avatarSvg': instance.avatarSvg,
|
||||
'avatarJson': instance.avatarJson,
|
||||
'avatarCounter': instance.avatarCounter,
|
||||
'defaultShowTime': instance.defaultShowTime,
|
||||
'userId': instance.userId,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -221,18 +221,28 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
|||
break;
|
||||
default:
|
||||
if (message.kind != MessageKind.textMessage &&
|
||||
message.kind != MessageKind.media) {
|
||||
message.kind != MessageKind.media &&
|
||||
message.kind != MessageKind.storedMediaFile) {
|
||||
Logger("handleServerMessages")
|
||||
.shout("Got unknown MessageKind $message");
|
||||
} else {
|
||||
String content = jsonEncode(message.content!.toJson());
|
||||
|
||||
bool acknowledgeByUser = false;
|
||||
DateTime? openedAt;
|
||||
if (message.kind == MessageKind.storedMediaFile) {
|
||||
acknowledgeByUser = true;
|
||||
openedAt = DateTime.now();
|
||||
}
|
||||
|
||||
final update = MessagesCompanion(
|
||||
contactId: Value(fromUserId),
|
||||
kind: Value(message.kind),
|
||||
messageOtherId: Value(message.messageId),
|
||||
contentJson: Value(content),
|
||||
acknowledgeByServer: Value(true),
|
||||
acknowledgeByUser: Value(acknowledgeByUser),
|
||||
openedAt: Value(openedAt),
|
||||
downloadState: Value(message.kind == MessageKind.media
|
||||
? DownloadState.pending
|
||||
: DownloadState.downloaded),
|
||||
|
|
|
|||
|
|
@ -149,6 +149,7 @@ String getPushNotificationText(String key, String userName) {
|
|||
"newImage": "%userName% hat dir ein Bild gesendet.",
|
||||
"contactRequest": "%userName% möchte sich mir dir vernetzen.",
|
||||
"acceptRequest": "%userName% ist jetzt mit dir vernetzt.",
|
||||
"storedMediaFile": "%userName% hat dein Bild gespeichert."
|
||||
};
|
||||
} else {
|
||||
pushNotificationText = {
|
||||
|
|
@ -158,6 +159,7 @@ String getPushNotificationText(String key, String userName) {
|
|||
"newImage": "%userName% has sent you an image.",
|
||||
"contactRequest": "%userName% wants to connect with you.",
|
||||
"acceptRequest": "%userName% is now connected with you.",
|
||||
"storedMediaFile": "%userName% has stored your image."
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -199,6 +201,11 @@ Future localPushNotificationNewMessage(
|
|||
msg = getPushNotificationText("acceptRequest", getContactDisplayName(user));
|
||||
}
|
||||
|
||||
if (message.kind == my.MessageKind.storedMediaFile) {
|
||||
msg =
|
||||
getPushNotificationText("storedMediaFile", getContactDisplayName(user));
|
||||
}
|
||||
|
||||
if (msg == "") {
|
||||
Logger("localPushNotificationNewMessage")
|
||||
.shout("No push notification type defined!");
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import 'package:twonly/src/database/twonly_database.dart';
|
|||
import 'package:twonly/src/providers/api/media.dart';
|
||||
import 'package:twonly/src/providers/send_next_media_to.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
import 'package:twonly/src/views/camera_to_share/share_image_view.dart';
|
||||
import 'dart:async';
|
||||
import 'package:flutter/services.dart';
|
||||
|
|
@ -35,7 +36,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
bool _imageSaved = false;
|
||||
bool _imageSaving = false;
|
||||
bool _isRealTwonly = false;
|
||||
int _maxShowTime = 18;
|
||||
int maxShowTime = 999999;
|
||||
String? sendNextMediaToUserName;
|
||||
|
||||
ImageItem currentImage = ImageItem();
|
||||
|
|
@ -44,9 +45,20 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initAsync();
|
||||
loadImage(widget.imageBytes);
|
||||
}
|
||||
|
||||
void initAsync() async {
|
||||
final user = await getUser();
|
||||
if (user == null) return;
|
||||
if (user.defaultShowTime != null) {
|
||||
setState(() {
|
||||
maxShowTime = user.defaultShowTime!;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
layers.clear();
|
||||
|
|
@ -114,24 +126,25 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
),
|
||||
const SizedBox(height: 8),
|
||||
NotificationBadge(
|
||||
count: _maxShowTime == 999999 ? "∞" : _maxShowTime.toString(),
|
||||
count: maxShowTime == 999999 ? "∞" : maxShowTime.toString(),
|
||||
// count: "",
|
||||
child: ActionButton(
|
||||
FontAwesomeIcons.stopwatch,
|
||||
tooltipText: context.lang.protectAsARealTwonly,
|
||||
// disable: _isRealTwonly,
|
||||
onPressed: () async {
|
||||
if (_maxShowTime == 999999) {
|
||||
_maxShowTime = 4;
|
||||
} else if (_maxShowTime >= 22) {
|
||||
_maxShowTime = 999999;
|
||||
if (maxShowTime == 999999) {
|
||||
maxShowTime = 4;
|
||||
} else if (maxShowTime >= 22) {
|
||||
maxShowTime = 999999;
|
||||
} else {
|
||||
_maxShowTime = _maxShowTime + 4;
|
||||
maxShowTime = maxShowTime + 8;
|
||||
}
|
||||
setState(() {});
|
||||
|
||||
// _maxShowTime =
|
||||
// _isRealTwonly = !_isRealTwonly;
|
||||
var user = await getUser();
|
||||
if (user != null) {
|
||||
user.defaultShowTime = maxShowTime;
|
||||
updateUser(user);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
@ -145,7 +158,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
onPressed: () async {
|
||||
_isRealTwonly = !_isRealTwonly;
|
||||
if (_isRealTwonly) {
|
||||
_maxShowTime = 12;
|
||||
maxShowTime = 12;
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
|
|
@ -369,7 +382,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
builder: (context) => ShareImageView(
|
||||
imageBytesFuture: imageBytes,
|
||||
isRealTwonly: _isRealTwonly,
|
||||
maxShowTime: _maxShowTime,
|
||||
maxShowTime: maxShowTime,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -387,7 +400,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
[sendNextMediaToUserId],
|
||||
imageBytes!,
|
||||
_isRealTwonly,
|
||||
_maxShowTime,
|
||||
maxShowTime,
|
||||
);
|
||||
Navigator.popUntil(context, (route) => route.isFirst);
|
||||
globalUpdateOfHomeViewPageIndex(1);
|
||||
|
|
@ -402,7 +415,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
builder: (context) => ShareImageView(
|
||||
imageBytesFuture: imageBytes,
|
||||
isRealTwonly: _isRealTwonly,
|
||||
maxShowTime: _maxShowTime,
|
||||
maxShowTime: maxShowTime,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -74,7 +74,9 @@ class ChatListEntry extends StatelessWidget {
|
|||
}
|
||||
} else if (content is MediaMessageContent && !content.isVideo) {
|
||||
Color color = getMessageColorFromType(
|
||||
content, Theme.of(context).colorScheme.primary);
|
||||
content,
|
||||
Theme.of(context).colorScheme.primary,
|
||||
);
|
||||
|
||||
child = GestureDetector(
|
||||
onTap: () {
|
||||
|
|
@ -112,6 +114,26 @@ class ChatListEntry extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
);
|
||||
} else if (message.kind == MessageKind.storedMediaFile) {
|
||||
child = Container(
|
||||
padding: EdgeInsets.all(5),
|
||||
width: 150,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: messageKindColors["text"]!,
|
||||
width: 1.0,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: MessageSendStateIcon(
|
||||
[message],
|
||||
mainAxisAlignment:
|
||||
right ? MainAxisAlignment.center : MainAxisAlignment.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Align(
|
||||
|
|
@ -181,9 +203,8 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
|||
notifyContactAboutOpeningMessage(widget.userid, msg.messageOtherId!);
|
||||
}
|
||||
}
|
||||
if (updated) {
|
||||
twonlyDatabase.messagesDao.openedAllTextMessages(widget.userid);
|
||||
} else {
|
||||
twonlyDatabase.messagesDao.openedAllNonMediaMessages(widget.userid);
|
||||
if (!updated) {
|
||||
// The stream should be get an update, so only update the UI when all are opened
|
||||
setState(() {
|
||||
messages = msgs;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
bool isRealTwonly = false;
|
||||
bool isDownloading = false;
|
||||
|
||||
bool imageSaved = false;
|
||||
bool imageSaving = false;
|
||||
|
||||
List<Message> allMediaFiles = [];
|
||||
late StreamSubscription<List<Message>> _subscription;
|
||||
|
||||
|
|
@ -95,10 +98,11 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
MediaMessageContent.fromJson(jsonDecode(current.contentJson!));
|
||||
|
||||
setState(() {
|
||||
// reset current image values
|
||||
imageBytes = null;
|
||||
canBeSeenUntil = null;
|
||||
maxShowTime = 999999;
|
||||
imageSaving = false;
|
||||
imageSaved = false;
|
||||
progress = 0;
|
||||
isDownloading = false;
|
||||
isRealTwonly = false;
|
||||
|
|
@ -281,7 +285,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
),
|
||||
AnimatedPositioned(
|
||||
duration: Duration(milliseconds: 200), // Animation duration
|
||||
bottom: showShortReactions ? 130 : 90,
|
||||
bottom: showShortReactions ? 100 : 90,
|
||||
left: showShortReactions ? 0 : 150,
|
||||
right: showShortReactions ? 0 : 150,
|
||||
curve: Curves.linearToEaseOut,
|
||||
|
|
@ -341,32 +345,65 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
),
|
||||
if (imageBytes != null)
|
||||
Positioned(
|
||||
bottom: 30,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton.outlined(
|
||||
icon: FaIcon(FontAwesomeIcons.camera),
|
||||
onPressed: () async {
|
||||
context
|
||||
.read<SendNextMediaTo>()
|
||||
.updateSendNextMediaTo(widget.userId.toInt());
|
||||
globalUpdateOfHomeViewPageIndex(0);
|
||||
Navigator.popUntil(context, (route) => route.isFirst);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||
EdgeInsets.symmetric(vertical: 10, horizontal: 30),
|
||||
if (maxShowTime == 999999)
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
iconColor: imageSaved
|
||||
? Theme.of(context).colorScheme.outline
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
foregroundColor: imageSaved
|
||||
? Theme.of(context).colorScheme.outline
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
imageSaving = true;
|
||||
});
|
||||
encryptAndSendMessage(
|
||||
null,
|
||||
widget.userId,
|
||||
MessageJson(
|
||||
kind: MessageKind.storedMediaFile,
|
||||
messageId: allMediaFiles.first.messageId,
|
||||
content: StoredMediaFileContent(
|
||||
messageId: allMediaFiles.first.messageId,
|
||||
),
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
);
|
||||
final res = await saveImageToGallery(imageBytes!);
|
||||
if (res == null) {
|
||||
setState(() {
|
||||
imageSaving = false;
|
||||
imageSaved = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
imageSaving
|
||||
? SizedBox(
|
||||
width: 10,
|
||||
height: 10,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 1))
|
||||
: imageSaved
|
||||
? Icon(Icons.check)
|
||||
: FaIcon(FontAwesomeIcons.floppyDisk),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
IconButton(
|
||||
icon: SizedBox(
|
||||
width: 40,
|
||||
height: 40,
|
||||
width: 30,
|
||||
height: 30,
|
||||
child: GridView.count(
|
||||
crossAxisCount: 2,
|
||||
children: List.generate(
|
||||
|
|
@ -391,14 +428,10 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
showShortReactions = !showShortReactions;
|
||||
selectedShortReaction = -1;
|
||||
});
|
||||
// context.read<SendNextMediaTo>().updateSendNextMediaTo(
|
||||
// widget.otherUser.userId.toInt());
|
||||
// globalUpdateOfHomeViewPageIndex(0);
|
||||
// Navigator.popUntil(context, (route) => route.isFirst);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||
EdgeInsets.symmetric(vertical: 10, horizontal: 30),
|
||||
EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -416,7 +449,23 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
},
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||
EdgeInsets.symmetric(vertical: 10, horizontal: 30),
|
||||
EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
IconButton.outlined(
|
||||
icon: FaIcon(FontAwesomeIcons.camera),
|
||||
onPressed: () async {
|
||||
context
|
||||
.read<SendNextMediaTo>()
|
||||
.updateSendNextMediaTo(widget.userId.toInt());
|
||||
globalUpdateOfHomeViewPageIndex(0);
|
||||
Navigator.popUntil(context, (route) => route.isFirst);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||
EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
Loading…
Reference in a new issue