mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 16:28:40 +00:00
use improved message content
This commit is contained in:
parent
d3bc0dc135
commit
2e7b0edce3
15 changed files with 260 additions and 190 deletions
|
|
@ -20,11 +20,11 @@ class FlameCounterWidget extends StatelessWidget {
|
||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
Text(
|
Text(
|
||||||
user.flameCounter.toString(),
|
user.flameCounter.toString(),
|
||||||
style: const TextStyle(fontSize: 12),
|
style: const TextStyle(fontSize: 13),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
(maxTotalMediaCounter == user.totalMediaCounter) ? "❤️🔥" : "🔥",
|
(maxTotalMediaCounter == user.totalMediaCounter) ? "❤️🔥" : "🔥",
|
||||||
style: const TextStyle(fontSize: 10),
|
style: const TextStyle(fontSize: 14),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ class Layer {
|
||||||
bool isEditing;
|
bool isEditing;
|
||||||
bool isDeleted;
|
bool isDeleted;
|
||||||
bool hasCustomActionButtons;
|
bool hasCustomActionButtons;
|
||||||
|
bool showCustomButtons;
|
||||||
|
|
||||||
Layer({
|
Layer({
|
||||||
this.offset = const Offset(0, 0),
|
this.offset = const Offset(0, 0),
|
||||||
|
|
@ -16,6 +17,7 @@ class Layer {
|
||||||
this.isEditing = false,
|
this.isEditing = false,
|
||||||
this.isDeleted = false,
|
this.isDeleted = false,
|
||||||
this.hasCustomActionButtons = false,
|
this.hasCustomActionButtons = false,
|
||||||
|
this.showCustomButtons = true,
|
||||||
this.rotation = 0,
|
this.rotation = 0,
|
||||||
this.scale = 1,
|
this.scale = 1,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ class _DrawLayerState extends State<DrawLayer> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.layerData.isEditing)
|
if (widget.layerData.isEditing && widget.layerData.showCustomButtons)
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 5,
|
top: 5,
|
||||||
left: 5,
|
left: 5,
|
||||||
|
|
@ -143,7 +143,7 @@ class _DrawLayerState extends State<DrawLayer> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.layerData.isEditing)
|
if (widget.layerData.isEditing && widget.layerData.showCustomButtons)
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 20,
|
right: 20,
|
||||||
top: 50,
|
top: 50,
|
||||||
|
|
|
||||||
|
|
@ -70,11 +70,10 @@ class MessageSendStateIcon extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isDownloading = false;
|
bool isDownloading = false;
|
||||||
if (message.messageContent != null &&
|
final content = message.messageContent;
|
||||||
message.messageContent!.downloadToken != null) {
|
if (message.messageReceived && content is MediaMessageContent) {
|
||||||
final test = context.watch<DownloadChangeProvider>().currentlyDownloading;
|
final test = context.watch<DownloadChangeProvider>().currentlyDownloading;
|
||||||
isDownloading =
|
isDownloading = test.contains(content.downloadToken.toString());
|
||||||
test.contains(message.messageContent!.downloadToken.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDownloading) {
|
if (isDownloading) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
import 'package:twonly/src/utils/json.dart';
|
|
||||||
part 'message.g.dart';
|
|
||||||
|
|
||||||
enum MessageKind {
|
enum MessageKind {
|
||||||
textMessage,
|
textMessage,
|
||||||
|
|
@ -38,24 +35,17 @@ extension MessageKindExtension on MessageKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// so _$MessageKindEnumMap gets generated
|
// TODO: use message as base class, remove kind and flatten content
|
||||||
@JsonSerializable()
|
|
||||||
class _MessageKind {
|
|
||||||
MessageKind? kind;
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class Message {
|
class Message {
|
||||||
@Int64Converter()
|
|
||||||
final MessageKind kind;
|
final MessageKind kind;
|
||||||
final MessageContent? content;
|
final MessageContent content;
|
||||||
final int? messageId;
|
final int? messageId;
|
||||||
DateTime timestamp;
|
DateTime timestamp;
|
||||||
|
|
||||||
Message(
|
Message(
|
||||||
{required this.kind,
|
{required this.kind,
|
||||||
this.messageId,
|
this.messageId,
|
||||||
this.content,
|
required this.content,
|
||||||
required this.timestamp});
|
required this.timestamp});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -63,19 +53,85 @@ class Message {
|
||||||
return 'Message(kind: $kind, content: $content, timestamp: $timestamp)';
|
return 'Message(kind: $kind, content: $content, timestamp: $timestamp)';
|
||||||
}
|
}
|
||||||
|
|
||||||
factory Message.fromJson(Map<String, dynamic> json) =>
|
static Message fromJson(Map<String, dynamic> json) => Message(
|
||||||
_$MessageFromJson(json);
|
kind: MessageKindExtension.fromString(json["kind"]),
|
||||||
Map<String, dynamic> toJson() => _$MessageToJson(this);
|
messageId: (json['messageId'] as num?)?.toInt(),
|
||||||
|
content:
|
||||||
|
MessageContent.fromJson(json['content'] as Map<String, dynamic>),
|
||||||
|
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||||
|
'kind': kind.name,
|
||||||
|
'content': content.toJson(),
|
||||||
|
'messageId': messageId,
|
||||||
|
'timestamp': timestamp.toIso8601String(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class MessageContent {
|
class MessageContent {
|
||||||
final String? text;
|
MessageContent();
|
||||||
final List<int>? downloadToken;
|
|
||||||
|
|
||||||
MessageContent({required this.text, required this.downloadToken});
|
static MessageContent fromJson(Map json) {
|
||||||
|
switch (json['type']) {
|
||||||
|
case 'MediaMessageContent':
|
||||||
|
return MediaMessageContent.fromJson(json);
|
||||||
|
case 'TextMessageContent':
|
||||||
|
return TextMessageContent.fromJson(json);
|
||||||
|
default:
|
||||||
|
return MessageContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
factory MessageContent.fromJson(Map<String, dynamic> json) =>
|
Map toJson() {
|
||||||
_$MessageContentFromJson(json);
|
return {};
|
||||||
Map<String, dynamic> toJson() => _$MessageContentToJson(this);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MediaMessageContent extends MessageContent {
|
||||||
|
final List<int> downloadToken;
|
||||||
|
final int maxShowTime;
|
||||||
|
final bool isRealTwonly;
|
||||||
|
MediaMessageContent({
|
||||||
|
required this.downloadToken,
|
||||||
|
required this.maxShowTime,
|
||||||
|
required this.isRealTwonly,
|
||||||
|
});
|
||||||
|
|
||||||
|
static MediaMessageContent fromJson(Map json) {
|
||||||
|
return MediaMessageContent(
|
||||||
|
downloadToken: List<int>.from(json['downloadToken']),
|
||||||
|
maxShowTime: json['maxShowTime'],
|
||||||
|
isRealTwonly: json['isRealTwonly'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map toJson() {
|
||||||
|
return {
|
||||||
|
'type': 'MediaMessageContent',
|
||||||
|
'downloadToken': downloadToken,
|
||||||
|
'isRealTwonly': isRealTwonly,
|
||||||
|
'maxShowTime': maxShowTime,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextMessageContent extends MessageContent {
|
||||||
|
final String text;
|
||||||
|
TextMessageContent({required this.text});
|
||||||
|
|
||||||
|
static TextMessageContent fromJson(Map json) {
|
||||||
|
return TextMessageContent(
|
||||||
|
text: json['text'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map toJson() {
|
||||||
|
return {
|
||||||
|
'type': 'TextMessageContent',
|
||||||
|
'text': text,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'message.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
_MessageKind _$MessageKindFromJson(Map<String, dynamic> json) => _MessageKind()
|
|
||||||
..kind = $enumDecodeNullable(_$MessageKindEnumMap, json['kind']);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$MessageKindToJson(_MessageKind instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'kind': _$MessageKindEnumMap[instance.kind],
|
|
||||||
};
|
|
||||||
|
|
||||||
const _$MessageKindEnumMap = {
|
|
||||||
MessageKind.textMessage: 'textMessage',
|
|
||||||
MessageKind.image: 'image',
|
|
||||||
MessageKind.video: 'video',
|
|
||||||
MessageKind.contactRequest: 'contactRequest',
|
|
||||||
MessageKind.rejectRequest: 'rejectRequest',
|
|
||||||
MessageKind.acceptRequest: 'acceptRequest',
|
|
||||||
MessageKind.opened: 'opened',
|
|
||||||
MessageKind.ack: 'ack',
|
|
||||||
};
|
|
||||||
|
|
||||||
Message _$MessageFromJson(Map<String, dynamic> json) => Message(
|
|
||||||
kind: $enumDecode(_$MessageKindEnumMap, json['kind']),
|
|
||||||
messageId: (json['messageId'] as num?)?.toInt(),
|
|
||||||
content: json['content'] == null
|
|
||||||
? null
|
|
||||||
: MessageContent.fromJson(json['content'] as Map<String, dynamic>),
|
|
||||||
timestamp: DateTime.parse(json['timestamp'] as String),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$MessageToJson(Message instance) => <String, dynamic>{
|
|
||||||
'kind': _$MessageKindEnumMap[instance.kind]!,
|
|
||||||
'content': instance.content,
|
|
||||||
'messageId': instance.messageId,
|
|
||||||
'timestamp': instance.timestamp.toIso8601String(),
|
|
||||||
};
|
|
||||||
|
|
||||||
MessageContent _$MessageContentFromJson(Map<String, dynamic> json) =>
|
|
||||||
MessageContent(
|
|
||||||
text: json['text'] as String?,
|
|
||||||
downloadToken: (json['downloadToken'] as List<dynamic>?)
|
|
||||||
?.map((e) => (e as num).toInt())
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$MessageContentToJson(MessageContent instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'text': instance.text,
|
|
||||||
'downloadToken': instance.downloadToken,
|
|
||||||
};
|
|
||||||
|
|
@ -39,6 +39,8 @@ class DbMessage {
|
||||||
return isMedia();
|
return isMedia();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get messageReceived => messageOtherId != null;
|
||||||
|
|
||||||
bool isMedia() {
|
bool isMedia() {
|
||||||
return messageKind == MessageKind.image || messageKind == MessageKind.video;
|
return messageKind == MessageKind.image || messageKind == MessageKind.video;
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +86,7 @@ class DbMessages extends CvModelBase {
|
||||||
final messageKind = CvField<int>(columnMessageKind);
|
final messageKind = CvField<int>(columnMessageKind);
|
||||||
|
|
||||||
static const columnMessageContentJson = "message_json";
|
static const columnMessageContentJson = "message_json";
|
||||||
final messageContentJson = CvField<String?>(columnMessageContentJson);
|
final messageContentJson = CvField<String>(columnMessageContentJson);
|
||||||
|
|
||||||
static const columnMessageOpenedAt = "message_opened_at";
|
static const columnMessageOpenedAt = "message_opened_at";
|
||||||
final messageOpenedAt = CvField<DateTime?>(columnMessageOpenedAt);
|
final messageOpenedAt = CvField<DateTime?>(columnMessageOpenedAt);
|
||||||
|
|
@ -112,7 +114,7 @@ class DbMessages extends CvModelBase {
|
||||||
$columnMessageKind INTEGER NOT NULL,
|
$columnMessageKind INTEGER NOT NULL,
|
||||||
$columnMessageAcknowledgeByUser INTEGER NOT NULL DEFAULT 0,
|
$columnMessageAcknowledgeByUser INTEGER NOT NULL DEFAULT 0,
|
||||||
$columnMessageAcknowledgeByServer INTEGER NOT NULL DEFAULT 0,
|
$columnMessageAcknowledgeByServer INTEGER NOT NULL DEFAULT 0,
|
||||||
$columnMessageContentJson TEXT DEFAULT NULL,
|
$columnMessageContentJson TEXT NOT NULL,
|
||||||
$columnMessageOpenedAt DATETIME DEFAULT NULL,
|
$columnMessageOpenedAt DATETIME DEFAULT NULL,
|
||||||
$columnSendOrReceivedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
$columnSendOrReceivedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
$columnUpdatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
|
$columnUpdatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
|
@ -145,12 +147,12 @@ class DbMessages extends CvModelBase {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<int?> insertMyMessage(int userIdFrom, MessageKind kind,
|
static Future<int?> insertMyMessage(
|
||||||
{String? jsonContent}) async {
|
int userIdFrom, MessageKind kind, MessageContent content) async {
|
||||||
try {
|
try {
|
||||||
int messageId = await dbProvider.db!.insert(tableName, {
|
int messageId = await dbProvider.db!.insert(tableName, {
|
||||||
columnMessageKind: kind.index,
|
columnMessageKind: kind.index,
|
||||||
columnMessageContentJson: jsonContent,
|
columnMessageContentJson: jsonEncode(content.toJson()),
|
||||||
columnOtherUserId: userIdFrom,
|
columnOtherUserId: userIdFrom,
|
||||||
columnSendOrReceivedAt: DateTime.now().toIso8601String()
|
columnSendOrReceivedAt: DateTime.now().toIso8601String()
|
||||||
});
|
});
|
||||||
|
|
@ -235,7 +237,9 @@ class DbMessages extends CvModelBase {
|
||||||
List<DbMessage> receivedByOther = messages
|
List<DbMessage> receivedByOther = messages
|
||||||
.where((c) => c.messageOtherId != null && c.messageOpenedAt == null)
|
.where((c) => c.messageOtherId != null && c.messageOpenedAt == null)
|
||||||
.toList();
|
.toList();
|
||||||
if (receivedByOther.isNotEmpty) return receivedByOther[0];
|
if (receivedByOther.isNotEmpty) {
|
||||||
|
return receivedByOther[receivedByOther.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
// check if there is a message which was not ack by the server
|
// check if there is a message which was not ack by the server
|
||||||
List<DbMessage> notAckByServer =
|
List<DbMessage> notAckByServer =
|
||||||
|
|
@ -343,19 +347,16 @@ class DbMessages extends CvModelBase {
|
||||||
if (messageOpenedAt != null) {
|
if (messageOpenedAt != null) {
|
||||||
messageOpenedAt = DateTime.tryParse(fromDb[i][columnMessageOpenedAt]);
|
messageOpenedAt = DateTime.tryParse(fromDb[i][columnMessageOpenedAt]);
|
||||||
}
|
}
|
||||||
dynamic content = fromDb[i][columnMessageContentJson];
|
int? messageOtherId = fromDb[i][columnMessageOtherId];
|
||||||
if (content != null) {
|
MessageContent content = MessageContent.fromJson(
|
||||||
content = MessageContent.fromJson(
|
jsonDecode(fromDb[i][columnMessageContentJson]));
|
||||||
jsonDecode(fromDb[i][columnMessageContentJson]));
|
|
||||||
}
|
|
||||||
MessageKind messageKind =
|
MessageKind messageKind =
|
||||||
MessageKindExtension.fromIndex(fromDb[i][columnMessageKind]);
|
MessageKindExtension.fromIndex(fromDb[i][columnMessageKind]);
|
||||||
bool isDownloaded = true;
|
bool isDownloaded = true;
|
||||||
if (messageKind == MessageKind.image ||
|
if (messageOtherId != null) {
|
||||||
messageKind == MessageKind.video) {
|
if (content is MediaMessageContent) {
|
||||||
// when the media was send from the user itself the content is null
|
// when the media was send from the user itself the content is null
|
||||||
if (content != null) {
|
isDownloaded = await isMediaDownloaded(content.downloadToken);
|
||||||
isDownloaded = await isMediaDownloaded(content.downloadToken!);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
parsedUsers.add(
|
parsedUsers.add(
|
||||||
|
|
@ -363,7 +364,7 @@ class DbMessages extends CvModelBase {
|
||||||
sendOrReceivedAt:
|
sendOrReceivedAt:
|
||||||
DateTime.tryParse(fromDb[i][columnSendOrReceivedAt])!,
|
DateTime.tryParse(fromDb[i][columnSendOrReceivedAt])!,
|
||||||
messageId: fromDb[i][columnMessageId],
|
messageId: fromDb[i][columnMessageId],
|
||||||
messageOtherId: fromDb[i][columnMessageOtherId],
|
messageOtherId: messageOtherId,
|
||||||
otherUserId: fromDb[i][columnOtherUserId],
|
otherUserId: fromDb[i][columnOtherUserId],
|
||||||
messageKind: messageKind,
|
messageKind: messageKind,
|
||||||
messageContent: content,
|
messageContent: content,
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,9 @@ Future tryTransmitMessages() async {
|
||||||
Uint8List? bytes = box.get("retransmit-$msgId-textmessage");
|
Uint8List? bytes = box.get("retransmit-$msgId-textmessage");
|
||||||
if (bytes != null) {
|
if (bytes != null) {
|
||||||
Result resp = await apiProvider.sendTextMessage(
|
Result resp = await apiProvider.sendTextMessage(
|
||||||
Int64(retransmit[i].otherUserId), bytes);
|
Int64(retransmit[i].otherUserId),
|
||||||
|
bytes,
|
||||||
|
);
|
||||||
|
|
||||||
if (resp.isSuccess) {
|
if (resp.isSuccess) {
|
||||||
DbMessages.acknowledgeMessageByServer(msgId);
|
DbMessages.acknowledgeMessageByServer(msgId);
|
||||||
|
|
@ -42,7 +44,16 @@ Future tryTransmitMessages() async {
|
||||||
|
|
||||||
Uint8List? encryptedMedia = await box.get("retransmit-$msgId-media");
|
Uint8List? encryptedMedia = await box.get("retransmit-$msgId-media");
|
||||||
if (encryptedMedia != null) {
|
if (encryptedMedia != null) {
|
||||||
uploadMediaFile(msgId, Int64(retransmit[i].otherUserId), encryptedMedia);
|
final content = retransmit[i].messageContent;
|
||||||
|
if (content is MediaMessageContent) {
|
||||||
|
uploadMediaFile(
|
||||||
|
msgId,
|
||||||
|
Int64(retransmit[i].otherUserId),
|
||||||
|
encryptedMedia,
|
||||||
|
content.isRealTwonly,
|
||||||
|
content.maxShowTime,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -75,11 +86,13 @@ Future<Result> encryptAndSendMessage(Int64 userId, Message msg) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future sendTextMessage(Int64 target, String message) async {
|
Future sendTextMessage(Int64 target, String message) async {
|
||||||
MessageContent content = MessageContent(text: message, downloadToken: null);
|
MessageContent content = TextMessageContent(text: message);
|
||||||
|
|
||||||
int? messageId = await DbMessages.insertMyMessage(
|
int? messageId = await DbMessages.insertMyMessage(
|
||||||
target.toInt(), MessageKind.textMessage,
|
target.toInt(),
|
||||||
jsonContent: jsonEncode(content.toJson()));
|
MessageKind.textMessage,
|
||||||
|
content,
|
||||||
|
);
|
||||||
if (messageId == null) return;
|
if (messageId == null) return;
|
||||||
|
|
||||||
Message msg = Message(
|
Message msg = Message(
|
||||||
|
|
@ -94,7 +107,12 @@ Future sendTextMessage(Int64 target, String message) async {
|
||||||
|
|
||||||
// this will send the media file and ensures retransmission when errors occur
|
// this will send the media file and ensures retransmission when errors occur
|
||||||
Future uploadMediaFile(
|
Future uploadMediaFile(
|
||||||
int messageId, Int64 target, Uint8List encryptedMedia) async {
|
int messageId,
|
||||||
|
Int64 target,
|
||||||
|
Uint8List encryptedMedia,
|
||||||
|
bool isRealTwonly,
|
||||||
|
int maxShowTime,
|
||||||
|
) async {
|
||||||
Box box = await getMediaStorage();
|
Box box = await getMediaStorage();
|
||||||
|
|
||||||
if ((await box.get("retransmit-$messageId-media") == null)) {
|
if ((await box.get("retransmit-$messageId-media") == null)) {
|
||||||
|
|
@ -133,15 +151,31 @@ Future uploadMediaFile(
|
||||||
Message(
|
Message(
|
||||||
kind: MessageKind.image,
|
kind: MessageKind.image,
|
||||||
messageId: messageId,
|
messageId: messageId,
|
||||||
content: MessageContent(text: null, downloadToken: uploadToken),
|
content: MediaMessageContent(
|
||||||
|
downloadToken: uploadToken,
|
||||||
|
maxShowTime: maxShowTime,
|
||||||
|
isRealTwonly: isRealTwonly,
|
||||||
|
),
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future encryptAndUploadMediaFile(Int64 target, Uint8List imageBytes) async {
|
Future encryptAndUploadMediaFile(
|
||||||
int? messageId =
|
Int64 target,
|
||||||
await DbMessages.insertMyMessage(target.toInt(), MessageKind.image);
|
Uint8List imageBytes,
|
||||||
|
bool isRealTwonly,
|
||||||
|
int maxShowTime,
|
||||||
|
) async {
|
||||||
|
int? messageId = await DbMessages.insertMyMessage(
|
||||||
|
target.toInt(),
|
||||||
|
MessageKind.image,
|
||||||
|
MediaMessageContent(
|
||||||
|
downloadToken: [],
|
||||||
|
maxShowTime: maxShowTime,
|
||||||
|
isRealTwonly: isRealTwonly,
|
||||||
|
));
|
||||||
|
// isRealTwonly,
|
||||||
if (messageId == null) return;
|
if (messageId == null) return;
|
||||||
|
|
||||||
Uint8List? encryptBytes = await SignalHelper.encryptBytes(imageBytes, target);
|
Uint8List? encryptBytes = await SignalHelper.encryptBytes(imageBytes, target);
|
||||||
|
|
@ -151,11 +185,16 @@ Future encryptAndUploadMediaFile(Int64 target, Uint8List imageBytes) async {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await uploadMediaFile(messageId, target, encryptBytes);
|
await uploadMediaFile(
|
||||||
|
messageId, target, encryptBytes, isRealTwonly, maxShowTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future sendImage(List<Int64> userIds, Uint8List imageBytes, bool isRealTwonly,
|
Future sendImage(
|
||||||
int maxShowTime) async {
|
List<Int64> userIds,
|
||||||
|
Uint8List imageBytes,
|
||||||
|
bool isRealTwonly,
|
||||||
|
int maxShowTime,
|
||||||
|
) async {
|
||||||
// 1. set notifier provider
|
// 1. set notifier provider
|
||||||
|
|
||||||
Uint8List? imageBytesCompressed = await getCompressedImage(imageBytes);
|
Uint8List? imageBytesCompressed = await getCompressedImage(imageBytes);
|
||||||
|
|
@ -165,7 +204,12 @@ Future sendImage(List<Int64> userIds, Uint8List imageBytes, bool isRealTwonly,
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < userIds.length; i++) {
|
for (int i = 0; i < userIds.length; i++) {
|
||||||
encryptAndUploadMediaFile(userIds[i], imageBytesCompressed);
|
encryptAndUploadMediaFile(
|
||||||
|
userIds[i],
|
||||||
|
imageBytesCompressed,
|
||||||
|
isRealTwonly,
|
||||||
|
maxShowTime,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,6 +242,7 @@ Future userOpenedOtherMessage(int fromUserId, int messageOtherId) async {
|
||||||
Message(
|
Message(
|
||||||
kind: MessageKind.opened,
|
kind: MessageKind.opened,
|
||||||
messageId: messageOtherId,
|
messageId: messageOtherId,
|
||||||
|
content: MessageContent(),
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ Future<client.Response> handleNewMessage(
|
||||||
Logger("handleServerMessages")
|
Logger("handleServerMessages")
|
||||||
.shout("Got unknown MessageKind $message");
|
.shout("Got unknown MessageKind $message");
|
||||||
} else {
|
} else {
|
||||||
String content = jsonEncode(message.content!.toJson());
|
String content = jsonEncode(message.content.toJson());
|
||||||
int? messageId = await DbMessages.insertOtherMessage(
|
int? messageId = await DbMessages.insertOtherMessage(
|
||||||
fromUserId.toInt(), message.kind, message.messageId!, content);
|
fromUserId.toInt(), message.kind, message.messageId!, content);
|
||||||
|
|
||||||
|
|
@ -142,6 +142,7 @@ Future<client.Response> handleNewMessage(
|
||||||
Message(
|
Message(
|
||||||
kind: MessageKind.ack,
|
kind: MessageKind.ack,
|
||||||
messageId: message.messageId!,
|
messageId: message.messageId!,
|
||||||
|
content: MessageContent(),
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -151,13 +152,13 @@ Future<client.Response> handleNewMessage(
|
||||||
await DbContacts.checkAndUpdateFlames(fromUserId.toInt(),
|
await DbContacts.checkAndUpdateFlames(fromUserId.toInt(),
|
||||||
timestamp: message.timestamp);
|
timestamp: message.timestamp);
|
||||||
|
|
||||||
dynamic content = message.content!;
|
final content = message.content;
|
||||||
List<int> downloadToken = content.downloadToken;
|
if (content is MediaMessageContent) {
|
||||||
|
List<int> downloadToken = content.downloadToken;
|
||||||
Box box = await getMediaStorage();
|
Box box = await getMediaStorage();
|
||||||
box.put("${downloadToken}_fromUserId", fromUserId.toInt());
|
box.put("${downloadToken}_fromUserId", fromUserId.toInt());
|
||||||
|
tryDownloadMedia(downloadToken);
|
||||||
tryDownloadMedia(downloadToken);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
child: ActionButton(
|
child: ActionButton(
|
||||||
FontAwesomeIcons.stopwatch,
|
FontAwesomeIcons.stopwatch,
|
||||||
tooltipText: context.lang.protectAsARealTwonly,
|
tooltipText: context.lang.protectAsARealTwonly,
|
||||||
disable: _isRealTwonly,
|
// disable: _isRealTwonly,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (_maxShowTime == 999999) {
|
if (_maxShowTime == 999999) {
|
||||||
_maxShowTime = 4;
|
_maxShowTime = 4;
|
||||||
|
|
@ -184,7 +184,13 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
Uint8List? image;
|
Uint8List? image;
|
||||||
|
|
||||||
if (layers.length > 1) {
|
if (layers.length > 1) {
|
||||||
|
for (var x in layers) {
|
||||||
|
x.showCustomButtons = false;
|
||||||
|
}
|
||||||
image = await screenshotController.capture(pixelRatio: pixelRatio);
|
image = await screenshotController.capture(pixelRatio: pixelRatio);
|
||||||
|
for (var x in layers) {
|
||||||
|
x.showCustomButtons = true;
|
||||||
|
}
|
||||||
} else if (layers.length == 1) {
|
} else if (layers.length == 1) {
|
||||||
if (layers.first is BackgroundLayerData) {
|
if (layers.first is BackgroundLayerData) {
|
||||||
image = (layers.first as BackgroundLayerData).image.bytes;
|
image = (layers.first as BackgroundLayerData).image.bytes;
|
||||||
|
|
@ -313,14 +319,13 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
const SizedBox(width: 20),
|
const SizedBox(width: 20),
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||||
onPressed: () async {
|
onPressed: () {
|
||||||
Uint8List? imageBytes = await getMergedImage();
|
Future<Uint8List?> imageBytes = getMergedImage();
|
||||||
if (imageBytes == null || !context.mounted) return;
|
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => ShareImageView(
|
builder: (context) => ShareImageView(
|
||||||
imageBytes: imageBytes,
|
imageBytesFuture: imageBytes,
|
||||||
isRealTwonly: _isRealTwonly,
|
isRealTwonly: _isRealTwonly,
|
||||||
maxShowTime: _maxShowTime,
|
maxShowTime: _maxShowTime,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@ import 'package:twonly/src/views/home_view.dart';
|
||||||
class ShareImageView extends StatefulWidget {
|
class ShareImageView extends StatefulWidget {
|
||||||
const ShareImageView(
|
const ShareImageView(
|
||||||
{super.key,
|
{super.key,
|
||||||
required this.imageBytes,
|
required this.imageBytesFuture,
|
||||||
required this.isRealTwonly,
|
required this.isRealTwonly,
|
||||||
required this.maxShowTime});
|
required this.maxShowTime});
|
||||||
final Uint8List imageBytes;
|
final Future<Uint8List?> imageBytesFuture;
|
||||||
final bool isRealTwonly;
|
final bool isRealTwonly;
|
||||||
final int maxShowTime;
|
final int maxShowTime;
|
||||||
|
|
||||||
|
|
@ -31,21 +31,23 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
List<Contact> _otherUsers = [];
|
List<Contact> _otherUsers = [];
|
||||||
List<Contact> _bestFriends = [];
|
List<Contact> _bestFriends = [];
|
||||||
int maxTotalMediaCounter = 0;
|
int maxTotalMediaCounter = 0;
|
||||||
|
Uint8List? imageBytes;
|
||||||
final HashSet<Int64> _selectedUserIds = HashSet<Int64>();
|
final HashSet<Int64> _selectedUserIds = HashSet<Int64>();
|
||||||
final TextEditingController searchUserName = TextEditingController();
|
final TextEditingController searchUserName = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_loadUsers();
|
_loadAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadUsers() async {
|
Future<void> _loadAsync() async {
|
||||||
final users = await DbContacts.getActiveUsers();
|
final users = await DbContacts.getActiveUsers();
|
||||||
setState(() {
|
setState(() {
|
||||||
_users = users;
|
_users = users;
|
||||||
_updateUsers(_users);
|
_updateUsers(_users);
|
||||||
});
|
});
|
||||||
|
imageBytes = await widget.imageBytesFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _updateUsers(List<Contact> users) async {
|
Future _updateUsers(List<Contact> users) async {
|
||||||
|
|
@ -153,11 +155,23 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
icon: imageBytes == null
|
||||||
|
? SizedBox(
|
||||||
|
height: 12,
|
||||||
|
width: 12,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
color: Theme.of(context).colorScheme.inversePrimary,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
if (imageBytes == null || _selectedUserIds.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
sendImage(
|
sendImage(
|
||||||
_selectedUserIds.toList(),
|
_selectedUserIds.toList(),
|
||||||
widget.imageBytes,
|
imageBytes!,
|
||||||
widget.isRealTwonly,
|
widget.isRealTwonly,
|
||||||
widget.maxShowTime,
|
widget.maxShowTime,
|
||||||
);
|
);
|
||||||
|
|
@ -168,10 +182,14 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
globalUpdateOfHomeViewPageIndex(0);
|
globalUpdateOfHomeViewPageIndex(0);
|
||||||
},
|
},
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||||
EdgeInsets.symmetric(vertical: 10, horizontal: 30),
|
EdgeInsets.symmetric(vertical: 10, horizontal: 30),
|
||||||
),
|
),
|
||||||
),
|
backgroundColor: WidgetStateProperty.all<Color>(
|
||||||
|
imageBytes == null || _selectedUserIds.isEmpty
|
||||||
|
? Theme.of(context).colorScheme.secondary
|
||||||
|
: Theme.of(context).colorScheme.primary,
|
||||||
|
)),
|
||||||
label: Text(
|
label: Text(
|
||||||
context.lang.shareImagedEditorSendImage,
|
context.lang.shareImagedEditorSendImage,
|
||||||
style: TextStyle(fontSize: 17),
|
style: TextStyle(fontSize: 17),
|
||||||
|
|
|
||||||
|
|
@ -25,40 +25,46 @@ class ChatListEntry extends StatelessWidget {
|
||||||
MessageSendState state = message.getSendState();
|
MessageSendState state = message.getSendState();
|
||||||
|
|
||||||
bool isDownloading = false;
|
bool isDownloading = false;
|
||||||
if (message.messageContent != null &&
|
List<int> token = [];
|
||||||
message.messageContent!.downloadToken != null) {
|
|
||||||
|
final content = message.messageContent;
|
||||||
|
if (message.messageReceived && content is MediaMessageContent) {
|
||||||
|
token = content.downloadToken;
|
||||||
isDownloading = context
|
isDownloading = context
|
||||||
.watch<DownloadChangeProvider>()
|
.watch<DownloadChangeProvider>()
|
||||||
.currentlyDownloading
|
.currentlyDownloading
|
||||||
.contains(message.messageContent!.downloadToken!.toString());
|
.contains(token.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget child = Container();
|
Widget child = Container();
|
||||||
|
|
||||||
switch (message.messageKind) {
|
switch (message.messageKind) {
|
||||||
case MessageKind.textMessage:
|
case MessageKind.textMessage:
|
||||||
child = Container(
|
if (content is TextMessageContent) {
|
||||||
constraints: BoxConstraints(
|
child = Container(
|
||||||
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
constraints: BoxConstraints(
|
||||||
),
|
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
vertical: 4, horizontal: 10), // Add some padding around the text
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: right
|
|
||||||
? const Color.fromARGB(107, 124, 77, 255)
|
|
||||||
: const Color.fromARGB(
|
|
||||||
83, 68, 137, 255), // Set the background color
|
|
||||||
borderRadius: BorderRadius.circular(12.0), // Set border radius
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
message.messageContent!.text!,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white, // Set text color for contrast
|
|
||||||
fontSize: 17,
|
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.left, // Center the text
|
padding: EdgeInsets.symmetric(
|
||||||
),
|
vertical: 4,
|
||||||
);
|
horizontal: 10), // Add some padding around the text
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: right
|
||||||
|
? const Color.fromARGB(107, 124, 77, 255)
|
||||||
|
: const Color.fromARGB(
|
||||||
|
83, 68, 137, 255), // Set the background color
|
||||||
|
borderRadius: BorderRadius.circular(12.0), // Set border radius
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
content.text,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white, // Set text color for contrast
|
||||||
|
fontSize: 17,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.left, // Center the text
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case MessageKind.image:
|
case MessageKind.image:
|
||||||
Color color =
|
Color color =
|
||||||
|
|
@ -74,7 +80,6 @@ class ChatListEntry extends StatelessWidget {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
List<int> token = message.messageContent!.downloadToken!;
|
|
||||||
tryDownloadMedia(token, force: true);
|
tryDownloadMedia(token, force: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import 'package:twonly/src/components/message_send_state_icon.dart';
|
||||||
import 'package:twonly/src/components/notification_badge.dart';
|
import 'package:twonly/src/components/notification_badge.dart';
|
||||||
import 'package:twonly/src/components/user_context_menu.dart';
|
import 'package:twonly/src/components/user_context_menu.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import 'package:twonly/src/model/contacts_model.dart';
|
||||||
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/model/messages_model.dart';
|
import 'package:twonly/src/model/messages_model.dart';
|
||||||
import 'package:twonly/src/providers/api/api.dart';
|
import 'package:twonly/src/providers/api/api.dart';
|
||||||
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
import 'package:twonly/src/providers/contacts_change_provider.dart';
|
||||||
|
|
@ -19,20 +20,6 @@ import 'package:twonly/src/views/profile_view.dart';
|
||||||
import 'package:twonly/src/views/chats/search_username_view.dart';
|
import 'package:twonly/src/views/chats/search_username_view.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ChatItem {
|
|
||||||
const ChatItem(
|
|
||||||
{required this.username,
|
|
||||||
required this.flames,
|
|
||||||
required this.userId,
|
|
||||||
required this.state,
|
|
||||||
required this.lastMessageInSeconds});
|
|
||||||
final String username;
|
|
||||||
final int lastMessageInSeconds;
|
|
||||||
final int flames;
|
|
||||||
final int userId;
|
|
||||||
final MessageSendState state;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Displays a list of SampleItems.
|
/// Displays a list of SampleItems.
|
||||||
class ChatListView extends StatefulWidget {
|
class ChatListView extends StatefulWidget {
|
||||||
const ChatListView({super.key});
|
const ChatListView({super.key});
|
||||||
|
|
@ -169,7 +156,6 @@ class UserListItem extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _UserListItem extends State<UserListItem> {
|
class _UserListItem extends State<UserListItem> {
|
||||||
int flames = 0;
|
|
||||||
int lastMessageInSeconds = 0;
|
int lastMessageInSeconds = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -186,13 +172,15 @@ class _UserListItem extends State<UserListItem> {
|
||||||
MessageSendState state = widget.lastMessage.getSendState();
|
MessageSendState state = widget.lastMessage.getSendState();
|
||||||
bool isDownloading = false;
|
bool isDownloading = false;
|
||||||
|
|
||||||
if (widget.lastMessage.messageContent != null &&
|
final content = widget.lastMessage.messageContent;
|
||||||
widget.lastMessage.messageContent!.downloadToken != null) {
|
List<int> token = [];
|
||||||
|
|
||||||
|
if (widget.lastMessage.messageReceived && content is MediaMessageContent) {
|
||||||
|
token = content.downloadToken;
|
||||||
isDownloading = context
|
isDownloading = context
|
||||||
.watch<DownloadChangeProvider>()
|
.watch<DownloadChangeProvider>()
|
||||||
.currentlyDownloading
|
.currentlyDownloading
|
||||||
.contains(
|
.contains(token.toString());
|
||||||
widget.lastMessage.messageContent!.downloadToken!.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return UserContextMenu(
|
return UserContextMenu(
|
||||||
|
|
@ -216,7 +204,6 @@ class _UserListItem extends State<UserListItem> {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (isDownloading) return;
|
if (isDownloading) return;
|
||||||
if (!widget.lastMessage.isDownloaded) {
|
if (!widget.lastMessage.isDownloaded) {
|
||||||
List<int> token = widget.lastMessage.messageContent!.downloadToken!;
|
|
||||||
tryDownloadMedia(token, force: true);
|
tryDownloadMedia(token, force: true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ 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/src/components/media_view_sizing.dart';
|
import 'package:twonly/src/components/media_view_sizing.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import 'package:twonly/src/model/contacts_model.dart';
|
||||||
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/model/messages_model.dart';
|
import 'package:twonly/src/model/messages_model.dart';
|
||||||
import 'package:twonly/src/providers/api/api.dart';
|
import 'package:twonly/src/providers/api/api.dart';
|
||||||
|
|
||||||
|
|
@ -25,10 +26,13 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _initAsync() async {
|
Future _initAsync() async {
|
||||||
List<int> token = widget.message.messageContent!.downloadToken!;
|
final content = widget.message.messageContent;
|
||||||
_imageByte =
|
if (content is MediaMessageContent) {
|
||||||
await getDownloadedMedia(token, widget.message.messageOtherId!);
|
List<int> token = content.downloadToken;
|
||||||
setState(() {});
|
_imageByte =
|
||||||
|
await getDownloadedMedia(token, widget.message.messageOtherId!);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ class _SearchUsernameView extends State<SearchUsernameView> {
|
||||||
Message(
|
Message(
|
||||||
kind: MessageKind.contactRequest,
|
kind: MessageKind.contactRequest,
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
|
content: MessageContent(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -179,6 +180,7 @@ class _ContactsListViewState extends State<ContactsListView> {
|
||||||
Message(
|
Message(
|
||||||
kind: MessageKind.rejectRequest,
|
kind: MessageKind.rejectRequest,
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
|
content: MessageContent(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -193,6 +195,7 @@ class _ContactsListViewState extends State<ContactsListView> {
|
||||||
Message(
|
Message(
|
||||||
kind: MessageKind.acceptRequest,
|
kind: MessageKind.acceptRequest,
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
|
content: MessageContent(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue