New Cwtch Library Integration #258

Merged
sarah merged 14 commits from cwtch-lib-integration into trunk 2021-12-10 21:02:02 +00:00
13 changed files with 137 additions and 67 deletions
Showing only changes of commit c9319d32d0 - Show all commits

View File

@ -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" -> {

View File

@ -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()

View File

@ -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

View File

@ -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() {
sarah marked this conversation as resolved
Review

ideally internal. the public setter for totalMessages can call, and updateMessageCaches should internally inc totalMessages

ideally internal. the public setter for totalMessages can call, and updateMessageCaches should internally inc totalMessages
this.messageCache.insert(0, null);
}
} }

View File

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

View File

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

View File

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

View File

@ -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());

View File

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

View File

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

View File

@ -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...

View File

@ -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();
}
}

View File

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