Much improved message caching
continuous-integration/drone/pr Build is pending
Details
continuous-integration/drone/pr Build is pending
Details
This commit is contained in:
parent
c42be6224d
commit
c9319d32d0
|
@ -184,6 +184,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
||||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||||
val conversation = a.getInt("conversation").toLong()
|
val conversation = a.getInt("conversation").toLong()
|
||||||
val indexI = a.getInt("index").toLong()
|
val indexI = a.getInt("index").toLong()
|
||||||
|
Log.i("FlwtchWorker", "Cwtch GetMessage " + profile + " " + conversation.toString() + " " + indexI.toString())
|
||||||
return Result.success(Data.Builder().putString("result", Cwtch.getMessage(profile, conversation, indexI)).build())
|
return Result.success(Data.Builder().putString("result", Cwtch.getMessage(profile, conversation, indexI)).build())
|
||||||
}
|
}
|
||||||
"GetMessageByID" -> {
|
"GetMessageByID" -> {
|
||||||
|
|
|
@ -30,6 +30,7 @@ import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
|
import cwtch.Cwtch
|
||||||
|
|
||||||
class MainActivity: FlutterActivity() {
|
class MainActivity: FlutterActivity() {
|
||||||
override fun provideSplashScreen(): SplashScreen? = SplashView()
|
override fun provideSplashScreen(): SplashScreen? = SplashView()
|
||||||
|
|
|
@ -130,6 +130,10 @@ class CwtchNotifier {
|
||||||
case "NewMessageFromPeer":
|
case "NewMessageFromPeer":
|
||||||
notificationManager.notify("New Message From Peer!");
|
notificationManager.notify("New Message From Peer!");
|
||||||
var identifier = int.parse(data["ConversationID"]);
|
var identifier = int.parse(data["ConversationID"]);
|
||||||
|
var messageID = int.parse(data["Index"]);
|
||||||
|
var timestamp = DateTime.tryParse(data['TimestampReceived'])!;
|
||||||
|
var senderHandle = data['RemotePeer'];
|
||||||
|
var senderImage = data['Picture'];
|
||||||
|
|
||||||
// We might not have received a contact created for this contact yet...
|
// We might not have received a contact created for this contact yet...
|
||||||
// In that case the **next** event we receive will actually update these values...
|
// In that case the **next** event we receive will actually update these values...
|
||||||
|
@ -140,6 +144,7 @@ class CwtchNotifier {
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.newMarker++;
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.newMarker++;
|
||||||
}
|
}
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, DateTime.now());
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, DateTime.now());
|
||||||
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.updateMessageCache(identifier, messageID, timestamp, senderHandle, senderImage, data["Data"], "");
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages++;
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages++;
|
||||||
|
|
||||||
// We only ever see messages from authenticated peers.
|
// We only ever see messages from authenticated peers.
|
||||||
|
@ -156,11 +161,12 @@ class CwtchNotifier {
|
||||||
break;
|
break;
|
||||||
case "IndexedAcknowledgement":
|
case "IndexedAcknowledgement":
|
||||||
var conversation = int.parse(data["ConversationID"]);
|
var conversation = int.parse(data["ConversationID"]);
|
||||||
var message_index = int.parse(data["Index"]);
|
var messageID = int.parse(data["Index"]);
|
||||||
|
|
||||||
var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(conversation);
|
var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(conversation);
|
||||||
// We return -1 for protocol message acks if there is no message
|
// We return -1 for protocol message acks if there is no message
|
||||||
if (message_index == -1) break;
|
if (messageID == -1) break;
|
||||||
var key = contact!.getMessageKeyOrFail(conversation, message_index, contact.lastMessageTime);
|
var key = contact!.getMessageKeyOrFail(conversation, messageID, contact.lastMessageTime);
|
||||||
if (key == null) break;
|
if (key == null) break;
|
||||||
try {
|
try {
|
||||||
var message = Provider.of<MessageMetadata>(key.currentContext!, listen: false);
|
var message = Provider.of<MessageMetadata>(key.currentContext!, listen: false);
|
||||||
|
@ -181,11 +187,19 @@ class CwtchNotifier {
|
||||||
var identifier = int.parse(data["ConversationID"]);
|
var identifier = int.parse(data["ConversationID"]);
|
||||||
if (data["ProfileOnion"] != data["RemotePeer"]) {
|
if (data["ProfileOnion"] != data["RemotePeer"]) {
|
||||||
var idx = int.parse(data["Index"]);
|
var idx = int.parse(data["Index"]);
|
||||||
|
var senderHandle = data['RemotePeer'];
|
||||||
|
var senderImage = data['Picture'];
|
||||||
|
var timestampSent = DateTime.tryParse(data['TimestampSent'])!;
|
||||||
var currentTotal = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages;
|
var currentTotal = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages;
|
||||||
|
|
||||||
// Only bother to do anything if we know about the group and the provided index is greater than our current total...
|
// Only bother to do anything if we know about the group and the provided index is greater than our current total...
|
||||||
if (currentTotal != null && idx >= currentTotal) {
|
if (currentTotal != null && idx >= currentTotal) {
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages = idx + 1;
|
profileCN
|
||||||
|
.getProfile(data["ProfileOnion"])
|
||||||
|
?.contactList
|
||||||
|
.getContact(identifier)!
|
||||||
|
.updateMessageCache(identifier, idx, timestampSent, senderHandle, senderImage, data["Data"], data["Signature"]);
|
||||||
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages++;
|
||||||
|
|
||||||
//if not currently open
|
//if not currently open
|
||||||
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != identifier) {
|
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != identifier) {
|
||||||
|
@ -194,7 +208,6 @@ class CwtchNotifier {
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.newMarker++;
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.newMarker++;
|
||||||
}
|
}
|
||||||
|
|
||||||
var timestampSent = DateTime.tryParse(data['TimestampSent'])!;
|
|
||||||
// TODO: There are 2 timestamps associated with a new group message - time sent and time received.
|
// TODO: There are 2 timestamps associated with a new group message - time sent and time received.
|
||||||
// Sent refers to the time a profile alleges they sent a message
|
// Sent refers to the time a profile alleges they sent a message
|
||||||
// Received refers to the time we actually saw the message from the server
|
// Received refers to the time we actually saw the message from the server
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:cwtch/config.dart';
|
import 'package:cwtch/config.dart';
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
import 'package:cwtch/widgets/messagerow.dart';
|
import 'package:cwtch/widgets/messagerow.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:cwtch/models/profileservers.dart';
|
import 'package:cwtch/models/profileservers.dart';
|
||||||
|
@ -501,6 +502,12 @@ ContactAuthorization stringToContactAuthorization(String authStr) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MessageCache {
|
||||||
|
final MessageMetadata metadata;
|
||||||
|
final String wrapper;
|
||||||
|
MessageCache(this.metadata, this.wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
class ContactInfoState extends ChangeNotifier {
|
class ContactInfoState extends ChangeNotifier {
|
||||||
final String profileOnion;
|
final String profileOnion;
|
||||||
final int identifier;
|
final int identifier;
|
||||||
|
@ -515,6 +522,7 @@ class ContactInfoState extends ChangeNotifier {
|
||||||
late int _totalMessages = 0;
|
late int _totalMessages = 0;
|
||||||
late DateTime _lastMessageTime;
|
late DateTime _lastMessageTime;
|
||||||
late Map<String, GlobalKey<MessageRowState>> keys;
|
late Map<String, GlobalKey<MessageRowState>> keys;
|
||||||
|
late List<MessageCache?> messageCache;
|
||||||
int _newMarker = 0;
|
int _newMarker = 0;
|
||||||
DateTime _newMarkerClearAt = DateTime.now();
|
DateTime _newMarkerClearAt = DateTime.now();
|
||||||
|
|
||||||
|
@ -546,6 +554,7 @@ class ContactInfoState extends ChangeNotifier {
|
||||||
this._lastMessageTime = lastMessageTime == null ? DateTime.fromMillisecondsSinceEpoch(0) : lastMessageTime;
|
this._lastMessageTime = lastMessageTime == null ? DateTime.fromMillisecondsSinceEpoch(0) : lastMessageTime;
|
||||||
this._server = server;
|
this._server = server;
|
||||||
this._archived = archived;
|
this._archived = archived;
|
||||||
|
this.messageCache = List.empty(growable: true);
|
||||||
keys = Map<String, GlobalKey<MessageRowState>>();
|
keys = Map<String, GlobalKey<MessageRowState>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -677,4 +686,12 @@ class ContactInfoState extends ChangeNotifier {
|
||||||
GlobalKey<MessageRowState> ret = keys[index]!;
|
GlobalKey<MessageRowState> ret = keys[index]!;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateMessageCache(int conversation, int messageID, DateTime timestamp, String senderHandle, String senderImage, String data, String signature) {
|
||||||
|
this.messageCache.insert(0, MessageCache(MessageMetadata(profileOnion, conversation, messageID, timestamp, senderHandle, senderImage, signature, {}, false, false), data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void bumpMessageCache() {
|
||||||
|
this.messageCache.insert(0, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,11 +28,43 @@ const GroupConversationHandleLength = 32;
|
||||||
|
|
||||||
abstract class Message {
|
abstract class Message {
|
||||||
MessageMetadata getMetadata();
|
MessageMetadata getMetadata();
|
||||||
Widget getWidget(BuildContext context);
|
Widget getWidget(BuildContext context, Key key);
|
||||||
Widget getPreviewWidget(BuildContext context);
|
Widget getPreviewWidget(BuildContext context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Message compileOverlay(MessageMetadata metadata, String messageData) {
|
||||||
|
try {
|
||||||
|
dynamic message = jsonDecode(messageData);
|
||||||
|
var content = message['d'] as dynamic;
|
||||||
|
var overlay = int.parse(message['o'].toString());
|
||||||
|
|
||||||
|
switch (overlay) {
|
||||||
|
case TextMessageOverlay:
|
||||||
|
return TextMessage(metadata, content);
|
||||||
|
case SuggestContactOverlay:
|
||||||
|
case InviteGroupOverlay:
|
||||||
|
return InviteMessage(overlay, metadata, content);
|
||||||
|
case QuotedMessageOverlay:
|
||||||
|
return QuotedMessage(metadata, content);
|
||||||
|
case FileShareOverlay:
|
||||||
|
return FileMessage(metadata, content);
|
||||||
|
default:
|
||||||
|
// Metadata is valid, content is not..
|
||||||
|
return MalformedMessage(metadata);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return MalformedMessage(metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Message> messageHandler(BuildContext context, String profileOnion, int conversationIdentifier, int index, {bool byID = false}) {
|
Future<Message> messageHandler(BuildContext context, String profileOnion, int conversationIdentifier, int index, {bool byID = false}) {
|
||||||
|
var cache = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(conversationIdentifier)!.messageCache;
|
||||||
|
if (cache.length > index) {
|
||||||
|
if (cache[index] != null) {
|
||||||
|
return Future.value(compileOverlay(cache[index]!.metadata, cache[index]!.wrapper));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Future<dynamic> rawMessageEnvelopeFuture;
|
Future<dynamic> rawMessageEnvelopeFuture;
|
||||||
|
|
||||||
|
@ -43,7 +75,7 @@ Future<Message> messageHandler(BuildContext context, String profileOnion, int co
|
||||||
}
|
}
|
||||||
|
|
||||||
return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) {
|
return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) {
|
||||||
var metadata = MessageMetadata(profileOnion, conversationIdentifier, index, -1, DateTime.now(), "", "", "", <String, String>{}, false, true);
|
var metadata = MessageMetadata(profileOnion, conversationIdentifier, index, DateTime.now(), "", "", "", <String, String>{}, false, true);
|
||||||
try {
|
try {
|
||||||
dynamic messageWrapper = jsonDecode(rawMessageEnvelope);
|
dynamic messageWrapper = jsonDecode(rawMessageEnvelope);
|
||||||
// There are 2 conditions in which this error condition can be met:
|
// There are 2 conditions in which this error condition can be met:
|
||||||
|
@ -58,7 +90,7 @@ Future<Message> messageHandler(BuildContext context, String profileOnion, int co
|
||||||
if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') {
|
if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') {
|
||||||
return Future.delayed(Duration(seconds: 2), () {
|
return Future.delayed(Duration(seconds: 2), () {
|
||||||
print("Tail recursive call to messageHandler called. This should be a rare event. If you see multiples of this log over a short period of time please log it as a bug.");
|
print("Tail recursive call to messageHandler called. This should be a rare event. If you see multiples of this log over a short period of time please log it as a bug.");
|
||||||
return messageHandler(context, profileOnion, conversationIdentifier, index, byID: byID).then((value) => value);
|
return messageHandler(context, profileOnion, conversationIdentifier, -1, byID: byID).then((value) => value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,33 +103,16 @@ Future<Message> messageHandler(BuildContext context, String profileOnion, int co
|
||||||
var ackd = messageWrapper['Acknowledged'];
|
var ackd = messageWrapper['Acknowledged'];
|
||||||
var error = messageWrapper['Error'] != null;
|
var error = messageWrapper['Error'] != null;
|
||||||
var signature = messageWrapper['Signature'];
|
var signature = messageWrapper['Signature'];
|
||||||
metadata = MessageMetadata(profileOnion, conversationIdentifier, index, messageID, timestamp, senderHandle, senderImage, signature, attributes, ackd, error);
|
metadata = MessageMetadata(profileOnion, conversationIdentifier, messageID, timestamp, senderHandle, senderImage, signature, attributes, ackd, error);
|
||||||
|
|
||||||
dynamic message = jsonDecode(messageWrapper['Message']);
|
return compileOverlay(metadata, messageWrapper['Message']);
|
||||||
var content = message['d'] as dynamic;
|
|
||||||
var overlay = int.parse(message['o'].toString());
|
|
||||||
|
|
||||||
switch (overlay) {
|
|
||||||
case TextMessageOverlay:
|
|
||||||
return TextMessage(metadata, content);
|
|
||||||
case SuggestContactOverlay:
|
|
||||||
case InviteGroupOverlay:
|
|
||||||
return InviteMessage(overlay, metadata, content);
|
|
||||||
case QuotedMessageOverlay:
|
|
||||||
return QuotedMessage(metadata, content);
|
|
||||||
case FileShareOverlay:
|
|
||||||
return FileMessage(metadata, content);
|
|
||||||
default:
|
|
||||||
// Metadata is valid, content is not..
|
|
||||||
return MalformedMessage(metadata);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
EnvironmentConfig.debugLog("an error! " + e.toString());
|
EnvironmentConfig.debugLog("an error! " + e.toString());
|
||||||
return MalformedMessage(metadata);
|
return MalformedMessage(metadata);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Future.value(MalformedMessage(MessageMetadata(profileOnion, conversationIdentifier, index, -1, DateTime.now(), "", "", "", <String, String>{}, false, true)));
|
return Future.value(MalformedMessage(MessageMetadata(profileOnion, conversationIdentifier, -1, DateTime.now(), "", "", "", <String, String>{}, false, true)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +120,6 @@ class MessageMetadata extends ChangeNotifier {
|
||||||
// meta-metadata
|
// meta-metadata
|
||||||
final String profileOnion;
|
final String profileOnion;
|
||||||
final int conversationIdentifier;
|
final int conversationIdentifier;
|
||||||
final int messageIndex;
|
|
||||||
final int messageID;
|
final int messageID;
|
||||||
|
|
||||||
final DateTime timestamp;
|
final DateTime timestamp;
|
||||||
|
@ -131,6 +145,5 @@ class MessageMetadata extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageMetadata(this.profileOnion, this.conversationIdentifier, this.messageIndex, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._attributes, this._ackd,
|
MessageMetadata(this.profileOnion, this.conversationIdentifier, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._attributes, this._ackd, this._error);
|
||||||
this._error);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,9 @@ class FileMessage extends Message {
|
||||||
FileMessage(this.metadata, this.content);
|
FileMessage(this.metadata, this.content);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getWidget(BuildContext context) {
|
Widget getWidget(BuildContext context, Key key) {
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
|
key: key,
|
||||||
value: this.metadata,
|
value: this.metadata,
|
||||||
builder: (bcontext, child) {
|
builder: (bcontext, child) {
|
||||||
dynamic shareObj = jsonDecode(this.content);
|
dynamic shareObj = jsonDecode(this.content);
|
||||||
|
@ -34,9 +35,7 @@ class FileMessage extends Message {
|
||||||
return MessageRow(MalformedBubble());
|
return MessageRow(MalformedBubble());
|
||||||
}
|
}
|
||||||
|
|
||||||
var lrt = Provider.of<ContactInfoState>(bcontext).lastMessageTime;
|
return MessageRow(FileBubble(nameSuggestion, rootHash, nonce, fileSize));
|
||||||
return MessageRow(FileBubble(nameSuggestion, rootHash, nonce, fileSize),
|
|
||||||
key: Provider.of<ContactInfoState>(bcontext).getMessageKey(this.metadata.conversationIdentifier, this.metadata.messageID, lrt));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,9 @@ class InviteMessage extends Message {
|
||||||
InviteMessage(this.overlay, this.metadata, this.content);
|
InviteMessage(this.overlay, this.metadata, this.content);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getWidget(BuildContext context) {
|
Widget getWidget(BuildContext context, Key key) {
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
|
key: key,
|
||||||
value: this.metadata,
|
value: this.metadata,
|
||||||
builder: (bcontext, child) {
|
builder: (bcontext, child) {
|
||||||
String inviteTarget;
|
String inviteTarget;
|
||||||
|
@ -40,8 +41,7 @@ class InviteMessage extends Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var lrt = Provider.of<ContactInfoState>(bcontext).lastMessageTime;
|
var lrt = Provider.of<ContactInfoState>(bcontext).lastMessageTime;
|
||||||
return MessageRow(InvitationBubble(overlay, inviteTarget, inviteNick, invite),
|
return MessageRow(InvitationBubble(overlay, inviteTarget, inviteNick, invite));
|
||||||
key: Provider.of<ContactInfoState>(bcontext).getMessageKey(this.metadata.conversationIdentifier, this.metadata.messageID, lrt));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,9 @@ class MalformedMessage extends Message {
|
||||||
MalformedMessage(this.metadata);
|
MalformedMessage(this.metadata);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getWidget(BuildContext context) {
|
Widget getWidget(BuildContext context, Key key) {
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
|
key: key,
|
||||||
value: this.metadata,
|
value: this.metadata,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return MessageRow(MalformedBubble());
|
return MessageRow(MalformedBubble());
|
||||||
|
|
|
@ -51,7 +51,7 @@ class QuotedMessage extends Message {
|
||||||
dynamic message = jsonDecode(this.content);
|
dynamic message = jsonDecode(this.content);
|
||||||
return Text(message["body"]);
|
return Text(message["body"]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return MalformedMessage(this.metadata).getWidget(context);
|
return MalformedMessage(this.metadata).getWidget(context, Key("malformed"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -62,16 +62,15 @@ class QuotedMessage extends Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getWidget(BuildContext context) {
|
Widget getWidget(BuildContext context, Key key) {
|
||||||
try {
|
try {
|
||||||
dynamic message = jsonDecode(this.content);
|
dynamic message = jsonDecode(this.content);
|
||||||
|
|
||||||
if (message["body"] == null || message["quotedHash"] == null) {
|
if (message["body"] == null || message["quotedHash"] == null) {
|
||||||
return MalformedMessage(this.metadata).getWidget(context);
|
return MalformedMessage(this.metadata).getWidget(context, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
var quotedMessagePotentials = Provider.of<FlwtchState>(context).cwtch.GetMessageByContentHash(metadata.profileOnion, metadata.conversationIdentifier, message["quotedHash"]);
|
var quotedMessagePotentials = Provider.of<FlwtchState>(context).cwtch.GetMessageByContentHash(metadata.profileOnion, metadata.conversationIdentifier, message["quotedHash"]);
|
||||||
int messageIndex = metadata.messageIndex;
|
|
||||||
Future<LocallyIndexedMessage?> quotedMessage = quotedMessagePotentials.then((matchingMessages) {
|
Future<LocallyIndexedMessage?> quotedMessage = quotedMessagePotentials.then((matchingMessages) {
|
||||||
if (matchingMessages == "[]") {
|
if (matchingMessages == "[]") {
|
||||||
return null;
|
return null;
|
||||||
|
@ -81,9 +80,7 @@ class QuotedMessage extends Message {
|
||||||
// message
|
// message
|
||||||
try {
|
try {
|
||||||
var list = (jsonDecode(matchingMessages) as List<dynamic>).map((data) => LocallyIndexedMessage.fromJson(data)).toList();
|
var list = (jsonDecode(matchingMessages) as List<dynamic>).map((data) => LocallyIndexedMessage.fromJson(data)).toList();
|
||||||
LocallyIndexedMessage candidate = list.reversed.firstWhere((element) => messageIndex < element.index, orElse: () {
|
LocallyIndexedMessage candidate = list.reversed.first;
|
||||||
return list.firstWhere((element) => messageIndex > element.index);
|
|
||||||
});
|
|
||||||
return candidate;
|
return candidate;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Malformed Message will be returned...
|
// Malformed Message will be returned...
|
||||||
|
@ -92,20 +89,18 @@ class QuotedMessage extends Message {
|
||||||
});
|
});
|
||||||
|
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
|
key: key,
|
||||||
value: this.metadata,
|
value: this.metadata,
|
||||||
builder: (bcontext, child) {
|
builder: (bcontext, child) {
|
||||||
var lrt = Provider.of<ContactInfoState>(bcontext).lastMessageTime;
|
return MessageRow(QuotedMessageBubble(message["body"], quotedMessage.then((LocallyIndexedMessage? localIndex) {
|
||||||
return MessageRow(
|
if (localIndex != null) {
|
||||||
QuotedMessageBubble(message["body"], quotedMessage.then((LocallyIndexedMessage? localIndex) {
|
return messageHandler(context, metadata.profileOnion, metadata.conversationIdentifier, localIndex.index);
|
||||||
if (localIndex != null) {
|
}
|
||||||
return messageHandler(context, metadata.profileOnion, metadata.conversationIdentifier, localIndex.index);
|
return MalformedMessage(this.metadata);
|
||||||
}
|
})));
|
||||||
return MalformedMessage(this.metadata);
|
|
||||||
})),
|
|
||||||
key: Provider.of<ContactInfoState>(bcontext).getMessageKey(this.metadata.conversationIdentifier, this.metadata.messageID, lrt));
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return MalformedMessage(this.metadata).getWidget(context);
|
return MalformedMessage(this.metadata).getWidget(context, key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,14 +31,15 @@ class TextMessage extends Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getWidget(BuildContext context) {
|
Widget getWidget(BuildContext context, Key key) {
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
|
key: key,
|
||||||
value: this.metadata,
|
value: this.metadata,
|
||||||
builder: (bcontext, child) {
|
builder: (bcontext, child) {
|
||||||
var lrt = Provider.of<ContactInfoState>(bcontext).lastMessageTime;
|
var lrt = Provider.of<ContactInfoState>(bcontext).lastMessageTime;
|
||||||
var key = Provider.of<ContactInfoState>(bcontext).getMessageKey(this.metadata.conversationIdentifier, this.metadata.messageID, lrt);
|
// var key = Provider.of<ContactInfoState>(bcontext).getMessageKey(this.metadata.conversationIdentifier, this.metadata.messageID, lrt);
|
||||||
|
|
||||||
return MessageRow(MessageBubble(this.content), key: key);
|
return MessageRow(MessageBubble(this.content));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
|
import 'package:cwtch/config.dart';
|
||||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
import 'package:cwtch/models/message.dart';
|
import 'package:cwtch/models/message.dart';
|
||||||
import 'package:cwtch/models/messages/quotedmessage.dart';
|
import 'package:cwtch/models/messages/quotedmessage.dart';
|
||||||
|
@ -213,6 +214,7 @@ class _MessageViewState extends State<MessageView> {
|
||||||
ctrlrCompose.clear();
|
ctrlrCompose.clear();
|
||||||
focusNode.requestFocus();
|
focusNode.requestFocus();
|
||||||
Future.delayed(const Duration(milliseconds: 80), () {
|
Future.delayed(const Duration(milliseconds: 80), () {
|
||||||
|
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(Provider.of<ContactInfoState>(context, listen: false).identifier)?.bumpMessageCache();
|
||||||
Provider.of<ContactInfoState>(context, listen: false).totalMessages++;
|
Provider.of<ContactInfoState>(context, listen: false).totalMessages++;
|
||||||
Provider.of<ContactInfoState>(context, listen: false).newMarker++;
|
Provider.of<ContactInfoState>(context, listen: false).newMarker++;
|
||||||
// Resort the contact list...
|
// Resort the contact list...
|
||||||
|
|
|
@ -79,13 +79,15 @@ class _MessageListState extends State<MessageList> {
|
||||||
var contactHandle = Provider.of<ContactInfoState>(outerContext, listen: false).identifier;
|
var contactHandle = Provider.of<ContactInfoState>(outerContext, listen: false).identifier;
|
||||||
var messageIndex = index;
|
var messageIndex = index;
|
||||||
|
|
||||||
|
// var key = Provider.of<ContactInfoState>(outerContext, listen: false).getMessageKey(contactHandle, Provider.of<ContactInfoState>(outerContext).totalMessages - index, DateTime.now());
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
|
//key: Provider.of<ContactInfoState>(outerContext, listen: false).getMessageKey(contactHandle, Provider.of<ContactInfoState>(outerContext).totalMessages - index, DateTime.now()),
|
||||||
future: messageHandler(outerContext, profileOnion, contactHandle, messageIndex),
|
future: messageHandler(outerContext, profileOnion, contactHandle, messageIndex),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
var message = snapshot.data as Message;
|
var message = snapshot.data as Message;
|
||||||
// Already includes MessageRow,,
|
var key = Provider.of<ContactInfoState>(outerContext, listen: false).getMessageKey(contactHandle, message.getMetadata().messageID, DateTime.now());
|
||||||
return message.getWidget(context);
|
return message.getWidget(context, key);
|
||||||
} else {
|
} else {
|
||||||
return MessageLoadingBubble();
|
return MessageLoadingBubble();
|
||||||
}
|
}
|
||||||
|
@ -97,3 +99,23 @@ class _MessageListState extends State<MessageList> {
|
||||||
])));
|
])));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CachedMessage extends Message {
|
||||||
|
@override
|
||||||
|
MessageMetadata getMetadata() {
|
||||||
|
// TODO: implement getMetadata
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getPreviewWidget(BuildContext context) {
|
||||||
|
// TODO: implement getPreviewWidget
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getWidget(BuildContext context, Key key) {
|
||||||
|
// TODO: implement getWidget
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
index = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
index = Provider.of<MessageMetadata>(context, listen: false).messageID;
|
||||||
_controller = AnimationController(vsync: this);
|
_controller = AnimationController(vsync: this);
|
||||||
_controller.addListener(() {
|
_controller.addListener(() {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -75,7 +75,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget wdgIcons = Visibility(
|
Widget wdgIcons = Visibility(
|
||||||
visible: Provider.of<AppState>(context).hoveredIndex == Provider.of<MessageMetadata>(context).messageIndex,
|
visible: Provider.of<AppState>(context).hoveredIndex == Provider.of<MessageMetadata>(context).messageID,
|
||||||
maintainSize: true,
|
maintainSize: true,
|
||||||
maintainAnimation: true,
|
maintainAnimation: true,
|
||||||
maintainState: true,
|
maintainState: true,
|
||||||
|
@ -169,7 +169,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
||||||
// For desktop...
|
// For desktop...
|
||||||
onHover: (event) {
|
onHover: (event) {
|
||||||
setState(() {
|
setState(() {
|
||||||
Provider.of<AppState>(context, listen: false).hoveredIndex = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
Provider.of<AppState>(context, listen: false).hoveredIndex = Provider.of<MessageMetadata>(context, listen: false).messageID;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onExit: (event) {
|
onExit: (event) {
|
||||||
|
@ -204,7 +204,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
||||||
children: widgetRow,
|
children: widgetRow,
|
||||||
)))));
|
)))));
|
||||||
var mark = Provider.of<ContactInfoState>(context).newMarker;
|
var mark = Provider.of<ContactInfoState>(context).newMarker;
|
||||||
if (mark > 0 && mark == Provider.of<MessageMetadata>(context).messageIndex + 1) {
|
if (mark > 0 && Provider.of<ContactInfoState>(context).messageCache[mark]?.metadata.messageID == Provider.of<MessageMetadata>(context).messageID) {
|
||||||
return Column(crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [Align(alignment: Alignment.center, child: _bubbleNew()), mr]);
|
return Column(crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [Align(alignment: Alignment.center, child: _bubbleNew()), mr]);
|
||||||
} else {
|
} else {
|
||||||
return mr;
|
return mr;
|
||||||
|
@ -251,12 +251,17 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
||||||
}
|
}
|
||||||
|
|
||||||
void _btnGoto() {
|
void _btnGoto() {
|
||||||
selectConversation(context, Provider.of<MessageMetadata>(context, listen: false).conversationIdentifier);
|
var id = Provider.of<ProfileInfoState>(context, listen: false).contactList.findContact(Provider.of<MessageMetadata>(context, listen: false).senderHandle)?.identifier;
|
||||||
|
if (id == null) {
|
||||||
|
// Can't happen
|
||||||
|
} else {
|
||||||
|
selectConversation(context, id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _btnAdd() {
|
void _btnAdd() {
|
||||||
var sender = Provider.of<MessageMetadata>(context, listen: false).senderHandle;
|
var sender = Provider.of<MessageMetadata>(context, listen: false).senderHandle;
|
||||||
if (sender == null || sender == "") {
|
if (sender == "") {
|
||||||
print("sender not yet loaded");
|
print("sender not yet loaded");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue