use improved message content

This commit is contained in:
otsmr 2025-02-04 22:42:18 +01:00
parent d3bc0dc135
commit 2e7b0edce3
15 changed files with 260 additions and 190 deletions

View file

@ -20,11 +20,11 @@ class FlameCounterWidget extends StatelessWidget {
const SizedBox(width: 5),
Text(
user.flameCounter.toString(),
style: const TextStyle(fontSize: 12),
style: const TextStyle(fontSize: 13),
),
Text(
(maxTotalMediaCounter == user.totalMediaCounter) ? "❤️‍🔥" : "🔥",
style: const TextStyle(fontSize: 10),
style: const TextStyle(fontSize: 14),
),
],
);

View file

@ -9,6 +9,7 @@ class Layer {
bool isEditing;
bool isDeleted;
bool hasCustomActionButtons;
bool showCustomButtons;
Layer({
this.offset = const Offset(0, 0),
@ -16,6 +17,7 @@ class Layer {
this.isEditing = false,
this.isDeleted = false,
this.hasCustomActionButtons = false,
this.showCustomButtons = true,
this.rotation = 0,
this.scale = 1,
});

View file

@ -98,7 +98,7 @@ class _DrawLayerState extends State<DrawLayer> {
),
),
),
if (widget.layerData.isEditing)
if (widget.layerData.isEditing && widget.layerData.showCustomButtons)
Positioned(
top: 5,
left: 5,
@ -143,7 +143,7 @@ class _DrawLayerState extends State<DrawLayer> {
],
),
),
if (widget.layerData.isEditing)
if (widget.layerData.isEditing && widget.layerData.showCustomButtons)
Positioned(
right: 20,
top: 50,

View file

@ -70,11 +70,10 @@ class MessageSendStateIcon extends StatelessWidget {
}
bool isDownloading = false;
if (message.messageContent != null &&
message.messageContent!.downloadToken != null) {
final content = message.messageContent;
if (message.messageReceived && content is MediaMessageContent) {
final test = context.watch<DownloadChangeProvider>().currentlyDownloading;
isDownloading =
test.contains(message.messageContent!.downloadToken.toString());
isDownloading = test.contains(content.downloadToken.toString());
}
if (isDownloading) {

View file

@ -1,7 +1,4 @@
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 {
textMessage,
@ -38,24 +35,17 @@ extension MessageKindExtension on MessageKind {
}
}
// so _$MessageKindEnumMap gets generated
@JsonSerializable()
class _MessageKind {
MessageKind? kind;
}
@JsonSerializable()
// TODO: use message as base class, remove kind and flatten content
class Message {
@Int64Converter()
final MessageKind kind;
final MessageContent? content;
final MessageContent content;
final int? messageId;
DateTime timestamp;
Message(
{required this.kind,
this.messageId,
this.content,
required this.content,
required this.timestamp});
@override
@ -63,19 +53,85 @@ class Message {
return 'Message(kind: $kind, content: $content, timestamp: $timestamp)';
}
factory Message.fromJson(Map<String, dynamic> json) =>
_$MessageFromJson(json);
Map<String, dynamic> toJson() => _$MessageToJson(this);
static Message fromJson(Map<String, dynamic> json) => Message(
kind: MessageKindExtension.fromString(json["kind"]),
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 {
final String? text;
final List<int>? downloadToken;
MessageContent();
MessageContent({required this.text, required this.downloadToken});
factory MessageContent.fromJson(Map<String, dynamic> json) =>
_$MessageContentFromJson(json);
Map<String, dynamic> toJson() => _$MessageContentToJson(this);
static MessageContent fromJson(Map json) {
switch (json['type']) {
case 'MediaMessageContent':
return MediaMessageContent.fromJson(json);
case 'TextMessageContent':
return TextMessageContent.fromJson(json);
default:
return MessageContent();
}
}
Map toJson() {
return {};
}
}
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,
};
}
}

View file

@ -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,
};

View file

@ -39,6 +39,8 @@ class DbMessage {
return isMedia();
}
bool get messageReceived => messageOtherId != null;
bool isMedia() {
return messageKind == MessageKind.image || messageKind == MessageKind.video;
}
@ -84,7 +86,7 @@ class DbMessages extends CvModelBase {
final messageKind = CvField<int>(columnMessageKind);
static const columnMessageContentJson = "message_json";
final messageContentJson = CvField<String?>(columnMessageContentJson);
final messageContentJson = CvField<String>(columnMessageContentJson);
static const columnMessageOpenedAt = "message_opened_at";
final messageOpenedAt = CvField<DateTime?>(columnMessageOpenedAt);
@ -112,7 +114,7 @@ class DbMessages extends CvModelBase {
$columnMessageKind INTEGER NOT NULL,
$columnMessageAcknowledgeByUser INTEGER NOT NULL DEFAULT 0,
$columnMessageAcknowledgeByServer INTEGER NOT NULL DEFAULT 0,
$columnMessageContentJson TEXT DEFAULT NULL,
$columnMessageContentJson TEXT NOT NULL,
$columnMessageOpenedAt DATETIME DEFAULT NULL,
$columnSendOrReceivedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
$columnUpdatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
@ -145,12 +147,12 @@ class DbMessages extends CvModelBase {
return null;
}
static Future<int?> insertMyMessage(int userIdFrom, MessageKind kind,
{String? jsonContent}) async {
static Future<int?> insertMyMessage(
int userIdFrom, MessageKind kind, MessageContent content) async {
try {
int messageId = await dbProvider.db!.insert(tableName, {
columnMessageKind: kind.index,
columnMessageContentJson: jsonContent,
columnMessageContentJson: jsonEncode(content.toJson()),
columnOtherUserId: userIdFrom,
columnSendOrReceivedAt: DateTime.now().toIso8601String()
});
@ -235,7 +237,9 @@ class DbMessages extends CvModelBase {
List<DbMessage> receivedByOther = messages
.where((c) => c.messageOtherId != null && c.messageOpenedAt == null)
.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
List<DbMessage> notAckByServer =
@ -343,19 +347,16 @@ class DbMessages extends CvModelBase {
if (messageOpenedAt != null) {
messageOpenedAt = DateTime.tryParse(fromDb[i][columnMessageOpenedAt]);
}
dynamic content = fromDb[i][columnMessageContentJson];
if (content != null) {
content = MessageContent.fromJson(
int? messageOtherId = fromDb[i][columnMessageOtherId];
MessageContent content = MessageContent.fromJson(
jsonDecode(fromDb[i][columnMessageContentJson]));
}
MessageKind messageKind =
MessageKindExtension.fromIndex(fromDb[i][columnMessageKind]);
bool isDownloaded = true;
if (messageKind == MessageKind.image ||
messageKind == MessageKind.video) {
if (messageOtherId != null) {
if (content is MediaMessageContent) {
// 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(
@ -363,7 +364,7 @@ class DbMessages extends CvModelBase {
sendOrReceivedAt:
DateTime.tryParse(fromDb[i][columnSendOrReceivedAt])!,
messageId: fromDb[i][columnMessageId],
messageOtherId: fromDb[i][columnMessageOtherId],
messageOtherId: messageOtherId,
otherUserId: fromDb[i][columnOtherUserId],
messageKind: messageKind,
messageContent: content,

View file

@ -30,7 +30,9 @@ Future tryTransmitMessages() async {
Uint8List? bytes = box.get("retransmit-$msgId-textmessage");
if (bytes != null) {
Result resp = await apiProvider.sendTextMessage(
Int64(retransmit[i].otherUserId), bytes);
Int64(retransmit[i].otherUserId),
bytes,
);
if (resp.isSuccess) {
DbMessages.acknowledgeMessageByServer(msgId);
@ -42,7 +44,16 @@ Future tryTransmitMessages() async {
Uint8List? encryptedMedia = await box.get("retransmit-$msgId-media");
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 {
MessageContent content = MessageContent(text: message, downloadToken: null);
MessageContent content = TextMessageContent(text: message);
int? messageId = await DbMessages.insertMyMessage(
target.toInt(), MessageKind.textMessage,
jsonContent: jsonEncode(content.toJson()));
target.toInt(),
MessageKind.textMessage,
content,
);
if (messageId == null) return;
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
Future uploadMediaFile(
int messageId, Int64 target, Uint8List encryptedMedia) async {
int messageId,
Int64 target,
Uint8List encryptedMedia,
bool isRealTwonly,
int maxShowTime,
) async {
Box box = await getMediaStorage();
if ((await box.get("retransmit-$messageId-media") == null)) {
@ -133,15 +151,31 @@ Future uploadMediaFile(
Message(
kind: MessageKind.image,
messageId: messageId,
content: MessageContent(text: null, downloadToken: uploadToken),
content: MediaMessageContent(
downloadToken: uploadToken,
maxShowTime: maxShowTime,
isRealTwonly: isRealTwonly,
),
timestamp: DateTime.now(),
),
);
}
Future encryptAndUploadMediaFile(Int64 target, Uint8List imageBytes) async {
int? messageId =
await DbMessages.insertMyMessage(target.toInt(), MessageKind.image);
Future encryptAndUploadMediaFile(
Int64 target,
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;
Uint8List? encryptBytes = await SignalHelper.encryptBytes(imageBytes, target);
@ -151,11 +185,16 @@ Future encryptAndUploadMediaFile(Int64 target, Uint8List imageBytes) async {
return;
}
await uploadMediaFile(messageId, target, encryptBytes);
await uploadMediaFile(
messageId, target, encryptBytes, isRealTwonly, maxShowTime);
}
Future sendImage(List<Int64> userIds, Uint8List imageBytes, bool isRealTwonly,
int maxShowTime) async {
Future sendImage(
List<Int64> userIds,
Uint8List imageBytes,
bool isRealTwonly,
int maxShowTime,
) async {
// 1. set notifier provider
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++) {
encryptAndUploadMediaFile(userIds[i], imageBytesCompressed);
encryptAndUploadMediaFile(
userIds[i],
imageBytesCompressed,
isRealTwonly,
maxShowTime,
);
}
}
@ -198,6 +242,7 @@ Future userOpenedOtherMessage(int fromUserId, int messageOtherId) async {
Message(
kind: MessageKind.opened,
messageId: messageOtherId,
content: MessageContent(),
timestamp: DateTime.now(),
),
);

View file

@ -129,7 +129,7 @@ Future<client.Response> handleNewMessage(
Logger("handleServerMessages")
.shout("Got unknown MessageKind $message");
} else {
String content = jsonEncode(message.content!.toJson());
String content = jsonEncode(message.content.toJson());
int? messageId = await DbMessages.insertOtherMessage(
fromUserId.toInt(), message.kind, message.messageId!, content);
@ -142,6 +142,7 @@ Future<client.Response> handleNewMessage(
Message(
kind: MessageKind.ack,
messageId: message.messageId!,
content: MessageContent(),
timestamp: DateTime.now(),
),
);
@ -151,17 +152,17 @@ Future<client.Response> handleNewMessage(
await DbContacts.checkAndUpdateFlames(fromUserId.toInt(),
timestamp: message.timestamp);
dynamic content = message.content!;
final content = message.content;
if (content is MediaMessageContent) {
List<int> downloadToken = content.downloadToken;
Box box = await getMediaStorage();
box.put("${downloadToken}_fromUserId", fromUserId.toInt());
tryDownloadMedia(downloadToken);
}
}
}
}
}
var ok = client.Response_Ok()..none = true;
return client.Response()..ok = ok;
}

View file

@ -99,7 +99,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
child: ActionButton(
FontAwesomeIcons.stopwatch,
tooltipText: context.lang.protectAsARealTwonly,
disable: _isRealTwonly,
// disable: _isRealTwonly,
onPressed: () async {
if (_maxShowTime == 999999) {
_maxShowTime = 4;
@ -184,7 +184,13 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
Uint8List? image;
if (layers.length > 1) {
for (var x in layers) {
x.showCustomButtons = false;
}
image = await screenshotController.capture(pixelRatio: pixelRatio);
for (var x in layers) {
x.showCustomButtons = true;
}
} else if (layers.length == 1) {
if (layers.first is BackgroundLayerData) {
image = (layers.first as BackgroundLayerData).image.bytes;
@ -313,14 +319,13 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
const SizedBox(width: 20),
FilledButton.icon(
icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
onPressed: () async {
Uint8List? imageBytes = await getMergedImage();
if (imageBytes == null || !context.mounted) return;
onPressed: () {
Future<Uint8List?> imageBytes = getMergedImage();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ShareImageView(
imageBytes: imageBytes,
imageBytesFuture: imageBytes,
isRealTwonly: _isRealTwonly,
maxShowTime: _maxShowTime,
),

View file

@ -15,10 +15,10 @@ import 'package:twonly/src/views/home_view.dart';
class ShareImageView extends StatefulWidget {
const ShareImageView(
{super.key,
required this.imageBytes,
required this.imageBytesFuture,
required this.isRealTwonly,
required this.maxShowTime});
final Uint8List imageBytes;
final Future<Uint8List?> imageBytesFuture;
final bool isRealTwonly;
final int maxShowTime;
@ -31,21 +31,23 @@ class _ShareImageView extends State<ShareImageView> {
List<Contact> _otherUsers = [];
List<Contact> _bestFriends = [];
int maxTotalMediaCounter = 0;
Uint8List? imageBytes;
final HashSet<Int64> _selectedUserIds = HashSet<Int64>();
final TextEditingController searchUserName = TextEditingController();
@override
void initState() {
super.initState();
_loadUsers();
_loadAsync();
}
Future<void> _loadUsers() async {
Future<void> _loadAsync() async {
final users = await DbContacts.getActiveUsers();
setState(() {
_users = users;
_updateUsers(_users);
});
imageBytes = await widget.imageBytesFuture;
}
Future _updateUsers(List<Contact> users) async {
@ -153,11 +155,23 @@ class _ShareImageView extends State<ShareImageView> {
mainAxisAlignment: MainAxisAlignment.end,
children: [
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 {
if (imageBytes == null || _selectedUserIds.isEmpty) {
return;
}
sendImage(
_selectedUserIds.toList(),
widget.imageBytes,
imageBytes!,
widget.isRealTwonly,
widget.maxShowTime,
);
@ -171,7 +185,11 @@ class _ShareImageView extends State<ShareImageView> {
padding: WidgetStateProperty.all<EdgeInsets>(
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(
context.lang.shareImagedEditorSendImage,
style: TextStyle(fontSize: 17),

View file

@ -25,24 +25,29 @@ class ChatListEntry extends StatelessWidget {
MessageSendState state = message.getSendState();
bool isDownloading = false;
if (message.messageContent != null &&
message.messageContent!.downloadToken != null) {
List<int> token = [];
final content = message.messageContent;
if (message.messageReceived && content is MediaMessageContent) {
token = content.downloadToken;
isDownloading = context
.watch<DownloadChangeProvider>()
.currentlyDownloading
.contains(message.messageContent!.downloadToken!.toString());
.contains(token.toString());
}
Widget child = Container();
switch (message.messageKind) {
case MessageKind.textMessage:
if (content is TextMessageContent) {
child = Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.8,
),
padding: EdgeInsets.symmetric(
vertical: 4, horizontal: 10), // Add some padding around the text
vertical: 4,
horizontal: 10), // Add some padding around the text
decoration: BoxDecoration(
color: right
? const Color.fromARGB(107, 124, 77, 255)
@ -51,7 +56,7 @@ class ChatListEntry extends StatelessWidget {
borderRadius: BorderRadius.circular(12.0), // Set border radius
),
child: Text(
message.messageContent!.text!,
content.text,
style: TextStyle(
color: Colors.white, // Set text color for contrast
fontSize: 17,
@ -59,6 +64,7 @@ class ChatListEntry extends StatelessWidget {
textAlign: TextAlign.left, // Center the text
),
);
}
break;
case MessageKind.image:
Color color =
@ -74,7 +80,6 @@ class ChatListEntry extends StatelessWidget {
}),
);
} else {
List<int> token = message.messageContent!.downloadToken!;
tryDownloadMedia(token, force: true);
}
}

View file

@ -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/user_context_menu.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/providers/api/api.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: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.
class ChatListView extends StatefulWidget {
const ChatListView({super.key});
@ -169,7 +156,6 @@ class UserListItem extends StatefulWidget {
}
class _UserListItem extends State<UserListItem> {
int flames = 0;
int lastMessageInSeconds = 0;
@override
@ -186,13 +172,15 @@ class _UserListItem extends State<UserListItem> {
MessageSendState state = widget.lastMessage.getSendState();
bool isDownloading = false;
if (widget.lastMessage.messageContent != null &&
widget.lastMessage.messageContent!.downloadToken != null) {
final content = widget.lastMessage.messageContent;
List<int> token = [];
if (widget.lastMessage.messageReceived && content is MediaMessageContent) {
token = content.downloadToken;
isDownloading = context
.watch<DownloadChangeProvider>()
.currentlyDownloading
.contains(
widget.lastMessage.messageContent!.downloadToken!.toString());
.contains(token.toString());
}
return UserContextMenu(
@ -216,7 +204,6 @@ class _UserListItem extends State<UserListItem> {
onTap: () {
if (isDownloading) return;
if (!widget.lastMessage.isDownloaded) {
List<int> token = widget.lastMessage.messageContent!.downloadToken!;
tryDownloadMedia(token, force: true);
return;
}

View file

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/components/media_view_sizing.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/providers/api/api.dart';
@ -25,11 +26,14 @@ class _MediaViewerViewState extends State<MediaViewerView> {
}
Future _initAsync() async {
List<int> token = widget.message.messageContent!.downloadToken!;
final content = widget.message.messageContent;
if (content is MediaMessageContent) {
List<int> token = content.downloadToken;
_imageByte =
await getDownloadedMedia(token, widget.message.messageOtherId!);
setState(() {});
}
}
@override
Widget build(BuildContext context) {

View file

@ -45,6 +45,7 @@ class _SearchUsernameView extends State<SearchUsernameView> {
Message(
kind: MessageKind.contactRequest,
timestamp: DateTime.now(),
content: MessageContent(),
),
);
}
@ -179,6 +180,7 @@ class _ContactsListViewState extends State<ContactsListView> {
Message(
kind: MessageKind.rejectRequest,
timestamp: DateTime.now(),
content: MessageContent(),
),
);
},
@ -193,6 +195,7 @@ class _ContactsListViewState extends State<ContactsListView> {
Message(
kind: MessageKind.acceptRequest,
timestamp: DateTime.now(),
content: MessageContent(),
),
);
},