mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 11:18:41 +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),
|
||||
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),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue