Refactor Message/MessageState to make adding more Message Types simpler
This commit is contained in:
parent
b9984a3598
commit
ddfc7fc43c
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
import 'package:cwtch/models/servers.dart';
|
import 'package:cwtch/models/servers.dart';
|
||||||
import 'package:cwtch/notification_manager.dart';
|
import 'package:cwtch/notification_manager.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -115,7 +116,7 @@ class CwtchNotifier {
|
||||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.getMessageKey(idx);
|
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.getMessageKey(idx);
|
||||||
if (key == null) break;
|
if (key == null) break;
|
||||||
try {
|
try {
|
||||||
var message = Provider.of<MessageState>(key.currentContext!, listen: false);
|
var message = Provider.of<MessageMetadata>(key.currentContext!, listen: false);
|
||||||
if (message == null) break;
|
if (message == null) break;
|
||||||
message.ackd = true;
|
message.ackd = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -138,7 +139,7 @@ class CwtchNotifier {
|
||||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.getMessageKey(idx);
|
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.getMessageKey(idx);
|
||||||
if (key == null) break;
|
if (key == null) break;
|
||||||
try {
|
try {
|
||||||
var message = Provider.of<MessageState>(key.currentContext!, listen: false);
|
var message = Provider.of<MessageMetadata>(key.currentContext!, listen: false);
|
||||||
if (message == null) break;
|
if (message == null) break;
|
||||||
message.ackd = true;
|
message.ackd = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -156,7 +157,7 @@ class CwtchNotifier {
|
||||||
var idx = data["Index"];
|
var idx = data["Index"];
|
||||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.getMessageKey(idx);
|
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.getMessageKey(idx);
|
||||||
try {
|
try {
|
||||||
var message = Provider.of<MessageState>(key!.currentContext!, listen: false);
|
var message = Provider.of<MessageMetadata>(key!.currentContext!, listen: false);
|
||||||
message.error = true;
|
message.error = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore, we likely have an old key that has been replaced with an actual signature
|
// ignore, we likely have an old key that has been replaced with an actual signature
|
||||||
|
@ -169,7 +170,7 @@ class CwtchNotifier {
|
||||||
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.getMessageKey(idx);
|
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.getMessageKey(idx);
|
||||||
if (key == null) break;
|
if (key == null) break;
|
||||||
try {
|
try {
|
||||||
var message = Provider.of<MessageState>(key.currentContext!, listen: false);
|
var message = Provider.of<MessageMetadata>(key.currentContext!, listen: false);
|
||||||
if (message == null) break;
|
if (message == null) break;
|
||||||
message.error = true;
|
message.error = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
137
lib/model.dart
137
lib/model.dart
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cwtch/widgets/messagerow.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:cwtch/models/servers.dart';
|
import 'package:cwtch/models/servers.dart';
|
||||||
import 'package:cwtch/widgets/messagebubble.dart';
|
import 'package:cwtch/widgets/messagebubble.dart';
|
||||||
|
@ -343,7 +344,7 @@ class ContactInfoState extends ChangeNotifier {
|
||||||
late int _unreadMessages = 0;
|
late int _unreadMessages = 0;
|
||||||
late int _totalMessages = 0;
|
late int _totalMessages = 0;
|
||||||
late DateTime _lastMessageTime;
|
late DateTime _lastMessageTime;
|
||||||
late Map<String, GlobalKey<MessageBubbleState>> keys;
|
late Map<String, GlobalKey<MessageRowState>> keys;
|
||||||
|
|
||||||
// todo: a nicer way to model contacts, groups and other "entities"
|
// todo: a nicer way to model contacts, groups and other "entities"
|
||||||
late bool _isGroup;
|
late bool _isGroup;
|
||||||
|
@ -375,7 +376,7 @@ class ContactInfoState extends ChangeNotifier {
|
||||||
this._savePeerHistory = savePeerHistory;
|
this._savePeerHistory = savePeerHistory;
|
||||||
this._lastMessageTime = lastMessageTime == null ? DateTime.fromMillisecondsSinceEpoch(0) : lastMessageTime;
|
this._lastMessageTime = lastMessageTime == null ? DateTime.fromMillisecondsSinceEpoch(0) : lastMessageTime;
|
||||||
this._server = server;
|
this._server = server;
|
||||||
keys = Map<String, GlobalKey<MessageBubbleState>>();
|
keys = Map<String, GlobalKey<MessageRowState>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
String get nickname => this._nickname;
|
String get nickname => this._nickname;
|
||||||
|
@ -451,137 +452,11 @@ class ContactInfoState extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalKey<MessageBubbleState> getMessageKey(String index) {
|
GlobalKey<MessageRowState> getMessageKey(String index) {
|
||||||
if (keys[index] == null) {
|
if (keys[index] == null) {
|
||||||
keys[index] = GlobalKey<MessageBubbleState>();
|
keys[index] = GlobalKey<MessageRowState>();
|
||||||
}
|
}
|
||||||
GlobalKey<MessageBubbleState> ret = keys[index]!;
|
GlobalKey<MessageRowState> ret = keys[index]!;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageState extends ChangeNotifier {
|
|
||||||
final String profileOnion;
|
|
||||||
final String contactHandle;
|
|
||||||
final int messageIndex;
|
|
||||||
late dynamic _message;
|
|
||||||
late int _overlay;
|
|
||||||
late String _inviteTarget;
|
|
||||||
late String _inviteNick;
|
|
||||||
late DateTime _timestamp;
|
|
||||||
late String _senderOnion;
|
|
||||||
late int _flags;
|
|
||||||
String? _senderImage;
|
|
||||||
late String _signature = "";
|
|
||||||
late bool _ackd = false;
|
|
||||||
late bool _error = false;
|
|
||||||
late bool _loaded = false;
|
|
||||||
late bool _malformed = false;
|
|
||||||
|
|
||||||
MessageState({
|
|
||||||
required BuildContext context,
|
|
||||||
required this.profileOnion,
|
|
||||||
required this.contactHandle,
|
|
||||||
required this.messageIndex,
|
|
||||||
}) {
|
|
||||||
this._senderOnion = profileOnion;
|
|
||||||
tryLoad(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
get message => this._message;
|
|
||||||
get overlay => this._overlay;
|
|
||||||
get timestamp => this._timestamp;
|
|
||||||
int get flags => this._flags;
|
|
||||||
set flags(int newVal) {
|
|
||||||
this._flags = newVal;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get ackd => this._ackd;
|
|
||||||
bool get error => this._error;
|
|
||||||
bool get malformed => this._malformed;
|
|
||||||
bool get loaded => this._loaded;
|
|
||||||
get senderOnion => this._senderOnion;
|
|
||||||
get senderImage => this._senderImage;
|
|
||||||
get signature => this._signature;
|
|
||||||
get isInvite => this.overlay == 100 || this.overlay == 101;
|
|
||||||
get inviteTarget => this._inviteTarget;
|
|
||||||
get inviteNick => this._inviteNick;
|
|
||||||
|
|
||||||
set ackd(bool newVal) {
|
|
||||||
this._ackd = newVal;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
set error(bool newVal) {
|
|
||||||
this._error = newVal;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
set malformed(bool newVal) {
|
|
||||||
this._malformed = newVal;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
set loaded(bool newVal) {
|
|
||||||
// quickly-arriving messages get discarded before loading sometimes
|
|
||||||
if (!hasListeners) return;
|
|
||||||
this._loaded = newVal;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void tryLoad(BuildContext context) {
|
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.GetMessage(profileOnion, contactHandle, messageIndex).then((jsonMessage) {
|
|
||||||
try {
|
|
||||||
dynamic messageWrapper = jsonDecode(jsonMessage);
|
|
||||||
if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') {
|
|
||||||
this._senderOnion = profileOnion;
|
|
||||||
Future.delayed(const Duration(milliseconds: 2), () {
|
|
||||||
tryLoad(context);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dynamic message = jsonDecode(messageWrapper['Message']);
|
|
||||||
this._message = message['d'] as dynamic;
|
|
||||||
this._overlay = int.parse(message['o'].toString());
|
|
||||||
this._timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!;
|
|
||||||
this._senderOnion = messageWrapper['PeerID'];
|
|
||||||
this._senderImage = messageWrapper['ContactImage'];
|
|
||||||
this._flags = int.parse(messageWrapper['Flags'].toString(), radix: 2);
|
|
||||||
|
|
||||||
// If this is a group, store the signature
|
|
||||||
if (contactHandle.length == 32) {
|
|
||||||
this._signature = messageWrapper['Signature'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this is an invite, get the contact handle
|
|
||||||
if (this.isInvite) {
|
|
||||||
if (message['d'].toString().length == 56) {
|
|
||||||
this._inviteTarget = message['d'];
|
|
||||||
var targetContact = Provider.of<ProfileInfoState>(context).contactList.getContact(this._inviteTarget);
|
|
||||||
this._inviteNick = targetContact == null ? message['d'] : targetContact.nickname;
|
|
||||||
} else {
|
|
||||||
var parts = message['d'].toString().split("||");
|
|
||||||
if (parts.length == 2) {
|
|
||||||
var jsonObj = jsonDecode(utf8.fuse(base64).decode(parts[1].substring(5)));
|
|
||||||
this._inviteTarget = jsonObj['GroupID'];
|
|
||||||
this._inviteNick = jsonObj['GroupName'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loaded = true;
|
|
||||||
|
|
||||||
//update ackd and error last as they are changenotified
|
|
||||||
this.ackd = messageWrapper['Acknowledged'];
|
|
||||||
if (messageWrapper['Error'] != null) {
|
|
||||||
this.error = true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this._overlay = -1;
|
|
||||||
this.loaded = true;
|
|
||||||
this.malformed = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../main.dart';
|
||||||
|
import 'messages/invitemessage.dart';
|
||||||
|
import 'messages/malformedmessage.dart';
|
||||||
|
import 'messages/quotedmessage.dart';
|
||||||
|
import 'messages/textmessage.dart';
|
||||||
|
|
||||||
|
abstract class Message {
|
||||||
|
MessageMetadata getMetadata();
|
||||||
|
Widget getWidget(BuildContext context);
|
||||||
|
Widget getPreviewWidget(BuildContext context);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Message> messageHandler(BuildContext context, String profileOnion, String contactHandle, int index) {
|
||||||
|
try {
|
||||||
|
var rawMessageEnvelopeFuture = Provider.of<FlwtchState>(context, listen: false).cwtch.GetMessage(profileOnion, contactHandle, index);
|
||||||
|
return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) {
|
||||||
|
dynamic messageWrapper = jsonDecode(rawMessageEnvelope);
|
||||||
|
if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') {
|
||||||
|
return Future.delayed(Duration(seconds: 2), () {
|
||||||
|
return messageHandler(context, profileOnion, contactHandle, index).then((value) => value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic message = jsonDecode(messageWrapper['Message']);
|
||||||
|
var content = message['d'] as dynamic;
|
||||||
|
var overlay = int.parse(message['o'].toString());
|
||||||
|
|
||||||
|
// Construct the initial metadata
|
||||||
|
var timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!;
|
||||||
|
var senderHandle = messageWrapper['PeerID'];
|
||||||
|
var senderImage = messageWrapper['ContactImage'];
|
||||||
|
var flags = int.parse(messageWrapper['Flags'].toString(), radix: 2);
|
||||||
|
var ackd = messageWrapper['Acknowledged'];
|
||||||
|
var error = messageWrapper['Error'] != null;
|
||||||
|
|
||||||
|
String? signature;
|
||||||
|
// If this is a group, store the signature
|
||||||
|
if (contactHandle.length == 32) {
|
||||||
|
signature = messageWrapper['Signature'];
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata = MessageMetadata(profileOnion, contactHandle, index, timestamp, senderHandle, senderImage, signature, flags, ackd, error);
|
||||||
|
|
||||||
|
switch (overlay) {
|
||||||
|
case 1:
|
||||||
|
return TextMessage(metadata, content);
|
||||||
|
case 100:
|
||||||
|
case 101:
|
||||||
|
return InviteMessage(overlay, metadata, content);
|
||||||
|
case 10:
|
||||||
|
return QuotedMessage(metadata, content);
|
||||||
|
default:
|
||||||
|
// Metadata is valid, content is not..
|
||||||
|
return MalformedMessage(metadata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return Future.value(MalformedMessage(MessageMetadata(profileOnion, contactHandle, index, DateTime.now(), "", "", null, 0, false, true)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageMetadata extends ChangeNotifier {
|
||||||
|
// meta-metadata
|
||||||
|
final String profileOnion;
|
||||||
|
final String contactHandle;
|
||||||
|
final int messageIndex;
|
||||||
|
|
||||||
|
final DateTime timestamp;
|
||||||
|
final String senderHandle;
|
||||||
|
final String? senderImage;
|
||||||
|
int _flags;
|
||||||
|
bool _ackd;
|
||||||
|
bool _error;
|
||||||
|
|
||||||
|
final String? signature;
|
||||||
|
|
||||||
|
int get flags => this._flags;
|
||||||
|
set flags(int newVal) {
|
||||||
|
this._flags = newVal;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get ackd => this._ackd;
|
||||||
|
set ackd(bool newVal) {
|
||||||
|
this._ackd = newVal;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get error => this._error;
|
||||||
|
set error(bool newVal) {
|
||||||
|
this._error = newVal;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageMetadata(this.profileOnion, this.contactHandle, this.messageIndex, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._flags, this._ackd, this._error);
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
|
import 'package:cwtch/widgets/invitationbubble.dart';
|
||||||
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
|
import 'package:cwtch/widgets/messagebubble.dart';
|
||||||
|
import 'package:cwtch/widgets/messagerow.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../../model.dart';
|
||||||
|
|
||||||
|
class InviteMessage extends Message {
|
||||||
|
final MessageMetadata metadata;
|
||||||
|
final String content;
|
||||||
|
final int overlay;
|
||||||
|
|
||||||
|
InviteMessage(this.overlay, this.metadata, this.content);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getWidget(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: this.metadata,
|
||||||
|
builder: (bcontext, child) {
|
||||||
|
String idx = Provider.of<ContactInfoState>(context).isGroup == true && this.metadata.signature != null ? this.metadata.signature! : this.metadata.messageIndex.toString();
|
||||||
|
|
||||||
|
String inviteTarget;
|
||||||
|
String inviteNick;
|
||||||
|
|
||||||
|
if (this.content.length == 56) {
|
||||||
|
inviteTarget = this.content;
|
||||||
|
var targetContact = Provider.of<ProfileInfoState>(context).contactList.getContact(inviteTarget);
|
||||||
|
inviteNick = targetContact == null ? this.content : targetContact.nickname;
|
||||||
|
} else {
|
||||||
|
var parts = this.content.toString().split("||");
|
||||||
|
if (parts.length == 2) {
|
||||||
|
var jsonObj = jsonDecode(utf8.fuse(base64).decode(parts[1].substring(5)));
|
||||||
|
inviteTarget = jsonObj['GroupID'];
|
||||||
|
inviteNick = jsonObj['GroupName'];
|
||||||
|
} else {
|
||||||
|
return MessageRow(MalformedBubble());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MessageRow(InvitationBubble(overlay, inviteTarget, inviteNick), key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getPreviewWidget(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: this.metadata,
|
||||||
|
builder: (bcontext, child) {
|
||||||
|
String inviteTarget;
|
||||||
|
String inviteNick;
|
||||||
|
|
||||||
|
if (this.content.length == 56) {
|
||||||
|
inviteTarget = this.content;
|
||||||
|
var targetContact = Provider.of<ProfileInfoState>(context).contactList.getContact(inviteTarget);
|
||||||
|
inviteNick = targetContact == null ? this.content : targetContact.nickname;
|
||||||
|
} else {
|
||||||
|
var parts = this.content.toString().split("||");
|
||||||
|
if (parts.length == 2) {
|
||||||
|
var jsonObj = jsonDecode(utf8.fuse(base64).decode(parts[1].substring(5)));
|
||||||
|
inviteTarget = jsonObj['GroupID'];
|
||||||
|
inviteNick = jsonObj['GroupName'];
|
||||||
|
} else {
|
||||||
|
return MalformedBubble();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return InvitationBubble(overlay, inviteTarget, inviteNick);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
MessageMetadata getMetadata() {
|
||||||
|
return this.metadata;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
|
import 'package:cwtch/widgets/messagerow.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class MalformedMessage extends Message {
|
||||||
|
final MessageMetadata metadata;
|
||||||
|
MalformedMessage(this.metadata);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getWidget(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: this.metadata,
|
||||||
|
builder: (context, child) {
|
||||||
|
return MessageRow(MalformedBubble());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getPreviewWidget(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: this.metadata,
|
||||||
|
builder: (bcontext, child) {
|
||||||
|
return MalformedBubble();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
MessageMetadata getMetadata() {
|
||||||
|
return this.metadata;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
|
import 'package:cwtch/models/messages/malformedmessage.dart';
|
||||||
|
import 'package:cwtch/widgets/messagebubble.dart';
|
||||||
|
import 'package:cwtch/widgets/messagerow.dart';
|
||||||
|
import 'package:cwtch/widgets/quotedmessage.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../../main.dart';
|
||||||
|
import '../../model.dart';
|
||||||
|
|
||||||
|
class LocallyIndexedMessage {
|
||||||
|
final dynamic message;
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
LocallyIndexedMessage(this.message, this.index);
|
||||||
|
|
||||||
|
LocallyIndexedMessage.fromJson(Map<String, dynamic> json)
|
||||||
|
: message = json['Message'],
|
||||||
|
index = json['LocalIndex'];
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'Message': message,
|
||||||
|
'LocalIndex': index,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class QuotedMessage extends Message {
|
||||||
|
final MessageMetadata metadata;
|
||||||
|
final String content;
|
||||||
|
QuotedMessage(this.metadata, this.content);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getPreviewWidget(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: this.metadata,
|
||||||
|
builder: (bcontext, child) {
|
||||||
|
try {
|
||||||
|
dynamic message = jsonDecode(this.content);
|
||||||
|
return MessageBubble(message["body"]);
|
||||||
|
} catch (e) {
|
||||||
|
return MalformedMessage(this.metadata).getWidget(context);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
MessageMetadata getMetadata() {
|
||||||
|
return this.metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getWidget(BuildContext context) {
|
||||||
|
try {
|
||||||
|
dynamic message = jsonDecode(this.content);
|
||||||
|
|
||||||
|
var quotedMessagePotentials = Provider.of<FlwtchState>(context).cwtch.GetMessageByContentHash(metadata.profileOnion, metadata.contactHandle, message["quotedHash"]);
|
||||||
|
int messageIndex = metadata.messageIndex;
|
||||||
|
var quotedMessage = quotedMessagePotentials.then((matchingMessages) {
|
||||||
|
// reverse order the messages from newest to oldest and return the
|
||||||
|
// first matching message where it's index is less than the index of this
|
||||||
|
// message
|
||||||
|
try {
|
||||||
|
var list = (jsonDecode(matchingMessages) as List<dynamic>).map((data) => LocallyIndexedMessage.fromJson(data)).toList();
|
||||||
|
LocallyIndexedMessage candidate = list.reversed.firstWhere((element) => messageIndex < element.index, orElse: () {
|
||||||
|
return list.firstWhere((element) => messageIndex > element.index);
|
||||||
|
});
|
||||||
|
return candidate;
|
||||||
|
} catch (e) {
|
||||||
|
// Malformed Message will be returned...
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: this.metadata,
|
||||||
|
builder: (bcontext, child) {
|
||||||
|
String idx = Provider.of<ContactInfoState>(context).isGroup == true && this.metadata.signature != null ? this.metadata.signature! : this.metadata.messageIndex.toString();
|
||||||
|
return MessageRow(
|
||||||
|
QuotedMessageBubble(message["body"], quotedMessage.then((localIndex) {
|
||||||
|
if (localIndex != null) {
|
||||||
|
return messageHandler(context, metadata.profileOnion, metadata.contactHandle, localIndex.index);
|
||||||
|
}
|
||||||
|
return Future.value(MalformedMessage(this.metadata));
|
||||||
|
})),
|
||||||
|
key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return MalformedMessage(this.metadata).getWidget(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
|
import 'package:cwtch/widgets/messagebubble.dart';
|
||||||
|
import 'package:cwtch/widgets/messagerow.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../../model.dart';
|
||||||
|
|
||||||
|
class TextMessage extends Message {
|
||||||
|
final MessageMetadata metadata;
|
||||||
|
final String content;
|
||||||
|
TextMessage(this.metadata, this.content);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getPreviewWidget(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: this.metadata,
|
||||||
|
builder: (bcontext, child) {
|
||||||
|
return MessageBubble(this.content);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
MessageMetadata getMetadata() {
|
||||||
|
return this.metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getWidget(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: this.metadata,
|
||||||
|
builder: (bcontext, child) {
|
||||||
|
String idx = Provider.of<ContactInfoState>(context).isGroup == true && this.metadata.signature != null ? this.metadata.signature! : this.metadata.messageIndex.toString();
|
||||||
|
return MessageRow(MessageBubble(this.content), key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
import 'package:cwtch/widgets/profileimage.dart';
|
import 'package:cwtch/widgets/profileimage.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
@ -108,20 +109,19 @@ class _MessageViewState extends State<MessageView> {
|
||||||
if (Provider.of<AppState>(context).selectedConversation != null && Provider.of<AppState>(context).selectedIndex != null) {
|
if (Provider.of<AppState>(context).selectedConversation != null && Provider.of<AppState>(context).selectedIndex != null) {
|
||||||
Provider.of<FlwtchState>(context)
|
Provider.of<FlwtchState>(context)
|
||||||
.cwtch
|
.cwtch
|
||||||
.GetMessage(Provider.of<AppState>(context).selectedProfile!, Provider.of<AppState>(context).selectedConversation!, Provider.of<AppState>(context).selectedIndex!).then((data) {
|
.GetMessage(Provider.of<AppState>(context).selectedProfile!, Provider.of<AppState>(context).selectedConversation!, Provider.of<AppState>(context).selectedIndex!)
|
||||||
|
.then((data) {
|
||||||
try {
|
try {
|
||||||
var messageWrapper = jsonDecode(data! as String);
|
var messageWrapper = jsonDecode(data! as String);
|
||||||
var bytes1 = utf8.encode(messageWrapper["PeerID"]+messageWrapper['Message']);
|
var bytes1 = utf8.encode(messageWrapper["PeerID"] + messageWrapper['Message']);
|
||||||
var digest1 = sha256.convert(bytes1);
|
var digest1 = sha256.convert(bytes1);
|
||||||
var contentHash = base64Encode(digest1.bytes);
|
var contentHash = base64Encode(digest1.bytes);
|
||||||
var quotedMessage = "{\"quotedHash\":\""+contentHash+"\",\"body\":\""+ctrlrCompose.value.text+"\"}";
|
var quotedMessage = "{\"quotedHash\":\"" + contentHash + "\",\"body\":\"" + ctrlrCompose.value.text + "\"}";
|
||||||
ChatMessage cm = new ChatMessage(o: 10, d: quotedMessage);
|
ChatMessage cm = new ChatMessage(o: 10, d: quotedMessage);
|
||||||
Provider.of<FlwtchState>(context, listen: false)
|
Provider.of<FlwtchState>(context, listen: false)
|
||||||
.cwtch
|
.cwtch
|
||||||
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm));
|
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm));
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
|
|
||||||
}
|
|
||||||
Provider.of<AppState>(context, listen: false).selectedIndex = null;
|
Provider.of<AppState>(context, listen: false).selectedIndex = null;
|
||||||
_sendMessageHelper();
|
_sendMessageHelper();
|
||||||
});
|
});
|
||||||
|
@ -201,24 +201,17 @@ class _MessageViewState extends State<MessageView> {
|
||||||
var children;
|
var children;
|
||||||
if (Provider.of<AppState>(context).selectedConversation != null && Provider.of<AppState>(context).selectedIndex != null) {
|
if (Provider.of<AppState>(context).selectedConversation != null && Provider.of<AppState>(context).selectedIndex != null) {
|
||||||
var quoted = FutureBuilder(
|
var quoted = FutureBuilder(
|
||||||
future: Provider.of<FlwtchState>(context)
|
future: messageHandler(context, Provider.of<AppState>(context).selectedProfile!, Provider.of<AppState>(context).selectedConversation!, Provider.of<AppState>(context).selectedIndex!),
|
||||||
.cwtch
|
|
||||||
.GetMessage(Provider.of<AppState>(context).selectedProfile!, Provider.of<AppState>(context).selectedConversation!, Provider.of<AppState>(context).selectedIndex!),
|
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
try {
|
var message = snapshot.data! as Message;
|
||||||
var messageWrapper = jsonDecode(snapshot.data! as String);
|
|
||||||
dynamic message = jsonDecode(messageWrapper['Message']);
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsets.all(5),
|
margin: EdgeInsets.all(5),
|
||||||
padding: EdgeInsets.all(5),
|
padding: EdgeInsets.all(5),
|
||||||
color: messageWrapper['PeerID'] != Provider.of<AppState>(context).selectedProfile
|
color: message.getMetadata().senderHandle != Provider.of<AppState>(context).selectedProfile
|
||||||
? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor()
|
? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor()
|
||||||
: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
|
: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
|
||||||
child: Text(message["d"]));
|
child: message.getPreviewWidget(context));
|
||||||
} catch (e) {
|
|
||||||
return MalformedBubble();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return Text("");
|
return Text("");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -15,6 +16,12 @@ import 'messagebubbledecorations.dart';
|
||||||
// Like MessageBubble but for displaying chat overlay 100/101 invitations
|
// Like MessageBubble but for displaying chat overlay 100/101 invitations
|
||||||
// Offers the user an accept/reject button if they don't have a matching contact already
|
// Offers the user an accept/reject button if they don't have a matching contact already
|
||||||
class InvitationBubble extends StatefulWidget {
|
class InvitationBubble extends StatefulWidget {
|
||||||
|
final int overlay;
|
||||||
|
final String inviteTarget;
|
||||||
|
final String inviteNick;
|
||||||
|
|
||||||
|
InvitationBubble(this.overlay, this.inviteTarget, this.inviteNick);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
InvitationBubbleState createState() => InvitationBubbleState();
|
InvitationBubbleState createState() => InvitationBubbleState();
|
||||||
}
|
}
|
||||||
|
@ -25,32 +32,22 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (Provider.of<MessageState>(context).malformed) {
|
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
||||||
return MalformedBubble();
|
var isGroup = widget.overlay == 101;
|
||||||
}
|
isAccepted = Provider.of<ProfileInfoState>(context).contactList.getContact(widget.inviteTarget) != null;
|
||||||
|
|
||||||
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
|
|
||||||
var isGroup = Provider.of<MessageState>(context).overlay == 101;
|
|
||||||
isAccepted = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).inviteTarget) != null;
|
|
||||||
var prettyDate = "";
|
|
||||||
var borderRadiousEh = 15.0;
|
var borderRadiousEh = 15.0;
|
||||||
var showGroupInvite = Provider.of<Settings>(context).isExperimentEnabled(TapirGroupsExperiment);
|
var showGroupInvite = Provider.of<Settings>(context).isExperimentEnabled(TapirGroupsExperiment);
|
||||||
rejected = Provider.of<MessageState>(context).flags & 0x01 == 0x01;
|
rejected = Provider.of<MessageMetadata>(context).flags & 0x01 == 0x01;
|
||||||
var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
|
var prettyDate = DateFormat.yMd().add_jm().format(Provider.of<MessageMetadata>(context).timestamp);
|
||||||
|
|
||||||
if (Provider.of<MessageState>(context).timestamp != null) {
|
|
||||||
// user-configurable timestamps prolly ideal? #todo
|
|
||||||
prettyDate = DateFormat.yMd().add_jm().format(Provider.of<MessageState>(context).timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the sender is not us, then we want to give them a nickname...
|
// If the sender is not us, then we want to give them a nickname...
|
||||||
var senderDisplayStr = "";
|
var senderDisplayStr = "";
|
||||||
if (!fromMe && Provider.of<MessageState>(context).senderOnion != null) {
|
if (!fromMe) {
|
||||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).senderOnion);
|
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
senderDisplayStr = contact.nickname;
|
senderDisplayStr = contact.nickname;
|
||||||
} else {
|
} else {
|
||||||
senderDisplayStr = Provider.of<MessageState>(context).senderOnion;
|
senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +58,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
|
|
||||||
// If we receive an invite for ourselves, treat it as a bug. The UI no longer allows this so it could have only come from
|
// If we receive an invite for ourselves, treat it as a bug. The UI no longer allows this so it could have only come from
|
||||||
// some kind of malfeasance.
|
// some kind of malfeasance.
|
||||||
var selfInvite = Provider.of<MessageState>(context).inviteNick == Provider.of<ProfileInfoState>(context).onion;
|
var selfInvite = widget.inviteNick == Provider.of<ProfileInfoState>(context).onion;
|
||||||
if (selfInvite) {
|
if (selfInvite) {
|
||||||
return MalformedBubble();
|
return MalformedBubble();
|
||||||
}
|
}
|
||||||
|
@ -69,16 +66,15 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
var wdgMessage = isGroup && !showGroupInvite
|
var wdgMessage = isGroup && !showGroupInvite
|
||||||
? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning)
|
? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning)
|
||||||
: fromMe
|
: fromMe
|
||||||
? senderInviteChrome(AppLocalizations.of(context)!.sendAnInvitation,
|
? senderInviteChrome(
|
||||||
isGroup ? Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).inviteTarget)!.nickname : Provider.of<MessageState>(context).message, myKey)
|
AppLocalizations.of(context)!.sendAnInvitation, isGroup ? Provider.of<ProfileInfoState>(context).contactList.getContact(widget.inviteTarget)!.nickname : widget.inviteTarget)
|
||||||
: (inviteChrome(isGroup ? AppLocalizations.of(context)!.inviteToGroup : AppLocalizations.of(context)!.contactSuggestion, Provider.of<MessageState>(context).inviteNick,
|
: (inviteChrome(isGroup ? AppLocalizations.of(context)!.inviteToGroup : AppLocalizations.of(context)!.contactSuggestion, widget.inviteNick, widget.inviteTarget));
|
||||||
Provider.of<MessageState>(context).inviteTarget, myKey));
|
|
||||||
|
|
||||||
Widget wdgDecorations;
|
Widget wdgDecorations;
|
||||||
if (isGroup && !showGroupInvite) {
|
if (isGroup && !showGroupInvite) {
|
||||||
wdgDecorations = Text('\u202F');
|
wdgDecorations = Text('\u202F');
|
||||||
} else if (fromMe) {
|
} else if (fromMe) {
|
||||||
wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageState>(context).ackd, errored: Provider.of<MessageState>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
||||||
} else if (isAccepted) {
|
} else if (isAccepted) {
|
||||||
wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F');
|
wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F');
|
||||||
} else if (this.rejected) {
|
} else if (this.rejected) {
|
||||||
|
@ -131,22 +127,22 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
setState(() {
|
setState(() {
|
||||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||||
var contact = Provider.of<ContactInfoState>(context, listen: false).onion;
|
var contact = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||||
var idx = Provider.of<MessageState>(context, listen: false).messageIndex;
|
var idx = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.UpdateMessageFlags(profileOnion, contact, idx, Provider.of<MessageState>(context, listen: false).flags | 0x01);
|
Provider.of<FlwtchState>(context, listen: false).cwtch.UpdateMessageFlags(profileOnion, contact, idx, Provider.of<MessageMetadata>(context, listen: false).flags | 0x01);
|
||||||
Provider.of<MessageState>(context).flags |= 0x01;
|
Provider.of<MessageMetadata>(context).flags |= 0x01;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _btnAccept() {
|
void _btnAccept() {
|
||||||
setState(() {
|
setState(() {
|
||||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, Provider.of<MessageState>(context, listen: false).message);
|
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, widget.inviteTarget);
|
||||||
isAccepted = true;
|
isAccepted = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct an invite chrome for the sender
|
// Construct an invite chrome for the sender
|
||||||
Widget senderInviteChrome(String chrome, String targetName, String myKey) {
|
Widget senderInviteChrome(String chrome, String targetName) {
|
||||||
return Wrap(children: [
|
return Wrap(children: [
|
||||||
SelectableText(
|
SelectableText(
|
||||||
chrome + '\u202F',
|
chrome + '\u202F',
|
||||||
|
@ -159,7 +155,6 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
),
|
),
|
||||||
SelectableText(
|
SelectableText(
|
||||||
targetName + '\u202F',
|
targetName + '\u202F',
|
||||||
key: Key(myKey),
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
||||||
),
|
),
|
||||||
|
@ -171,7 +166,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct an invite chrome
|
// Construct an invite chrome
|
||||||
Widget inviteChrome(String chrome, String targetName, String targetId, String myKey) {
|
Widget inviteChrome(String chrome, String targetName, String targetId) {
|
||||||
return Wrap(children: [
|
return Wrap(children: [
|
||||||
SelectableText(
|
SelectableText(
|
||||||
chrome + '\u202F',
|
chrome + '\u202F',
|
||||||
|
@ -184,7 +179,6 @@ class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
),
|
),
|
||||||
SelectableText(
|
SelectableText(
|
||||||
targetName + '\u202F',
|
targetName + '\u202F',
|
||||||
key: Key(myKey),
|
|
||||||
style: TextStyle(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor()),
|
style: TextStyle(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor()),
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -8,6 +9,10 @@ import '../settings.dart';
|
||||||
import 'messagebubbledecorations.dart';
|
import 'messagebubbledecorations.dart';
|
||||||
|
|
||||||
class MessageBubble extends StatefulWidget {
|
class MessageBubble extends StatefulWidget {
|
||||||
|
final String content;
|
||||||
|
|
||||||
|
MessageBubble(this.content);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MessageBubbleState createState() => MessageBubbleState();
|
MessageBubbleState createState() => MessageBubbleState();
|
||||||
}
|
}
|
||||||
|
@ -17,33 +22,30 @@ class MessageBubbleState extends State<MessageBubble> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
|
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
||||||
var prettyDate = "";
|
var prettyDate = "";
|
||||||
var borderRadiousEh = 15.0;
|
var borderRadiousEh = 15.0;
|
||||||
var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
|
// var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
|
||||||
|
|
||||||
if (Provider.of<MessageState>(context).timestamp != null) {
|
DateTime messageDate = Provider.of<MessageMetadata>(context).timestamp;
|
||||||
// user-configurable timestamps prolly ideal? #todo
|
|
||||||
DateTime messageDate = Provider.of<MessageState>(context).timestamp;
|
|
||||||
prettyDate = DateFormat.yMd().add_jm().format(messageDate.toLocal());
|
prettyDate = DateFormat.yMd().add_jm().format(messageDate.toLocal());
|
||||||
}
|
|
||||||
|
|
||||||
// If the sender is not us, then we want to give them a nickname...
|
// If the sender is not us, then we want to give them a nickname...
|
||||||
var senderDisplayStr = "";
|
var senderDisplayStr = "";
|
||||||
if (!fromMe && Provider.of<MessageState>(context).senderOnion != null) {
|
if (!fromMe) {
|
||||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).senderOnion);
|
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
senderDisplayStr = contact.nickname;
|
senderDisplayStr = contact.nickname;
|
||||||
} else {
|
} else {
|
||||||
senderDisplayStr = Provider.of<MessageState>(context).senderOnion;
|
senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var wdgSender = SelectableText(senderDisplayStr,
|
var wdgSender = SelectableText(senderDisplayStr,
|
||||||
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor()));
|
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor()));
|
||||||
|
|
||||||
var wdgMessage = SelectableText(
|
var wdgMessage = SelectableText(
|
||||||
(Provider.of<MessageState>(context).message ?? "") + '\u202F',
|
widget.content + '\u202F',
|
||||||
key: Key(myKey),
|
//key: Key(myKey),
|
||||||
focusNode: _focus,
|
focusNode: _focus,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||||
|
@ -52,9 +54,9 @@ class MessageBubbleState extends State<MessageBubble> {
|
||||||
textWidthBasis: TextWidthBasis.longestLine,
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
);
|
);
|
||||||
|
|
||||||
var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageState>(context).ackd, errored: Provider.of<MessageState>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
||||||
|
|
||||||
var error = Provider.of<MessageState>(context).error;
|
var error = Provider.of<MessageMetadata>(context).error;
|
||||||
|
|
||||||
return LayoutBuilder(builder: (context, constraints) {
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
//print(constraints.toString()+", "+constraints.maxWidth.toString());
|
//print(constraints.toString()+", "+constraints.maxWidth.toString());
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
|
import 'package:cwtch/models/message.dart';
|
||||||
|
import 'package:cwtch/models/messages/malformedmessage.dart';
|
||||||
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
|
import 'package:cwtch/widgets/messageloadingbubble.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import '../main.dart';
|
||||||
import '../model.dart';
|
import '../model.dart';
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
import 'messagerow.dart';
|
import 'messagerow.dart';
|
||||||
|
@ -68,22 +73,22 @@ class _MessageListState extends State<MessageList> {
|
||||||
itemCount: Provider.of<ContactInfoState>(outerContext).totalMessages,
|
itemCount: Provider.of<ContactInfoState>(outerContext).totalMessages,
|
||||||
reverse: true, // NOTE: There seems to be a bug in flutter that corrects the mouse wheel scroll, but not the drag direction...
|
reverse: true, // NOTE: There seems to be a bug in flutter that corrects the mouse wheel scroll, but not the drag direction...
|
||||||
itemBuilder: (itemBuilderContext, index) {
|
itemBuilder: (itemBuilderContext, index) {
|
||||||
var trueIndex = Provider.of<ContactInfoState>(outerContext).totalMessages - index - 1;
|
var profileOnion = Provider.of<ProfileInfoState>(outerContext, listen: false).onion;
|
||||||
return ChangeNotifierProvider(
|
var contactHandle = Provider.of<ContactInfoState>(outerContext, listen: false).onion;
|
||||||
key: ValueKey(trueIndex),
|
var messageIndex = Provider.of<ContactInfoState>(outerContext).totalMessages - index - 1;
|
||||||
create: (x) => MessageState(
|
|
||||||
context: itemBuilderContext,
|
return FutureBuilder(
|
||||||
profileOnion: Provider.of<ProfileInfoState>(outerContext, listen: false).onion,
|
future: messageHandler(outerContext, profileOnion, contactHandle, messageIndex),
|
||||||
// We don't want to listen for updates to the contact handle...
|
builder: (context, snapshot) {
|
||||||
contactHandle: Provider.of<ContactInfoState>(x, listen: false).onion,
|
if (snapshot.hasData) {
|
||||||
messageIndex: trueIndex,
|
var message = snapshot.data as Message;
|
||||||
),
|
// Already includes MessageRow,,
|
||||||
builder: (bcontext, child) {
|
return message.getWidget(context);
|
||||||
String idx = Provider.of<ContactInfoState>(outerContext).isGroup == true && Provider.of<MessageState>(bcontext).signature.isEmpty == false
|
} else {
|
||||||
? Provider.of<MessageState>(bcontext).signature
|
return MessageLoadingBubble();
|
||||||
: trueIndex.toString();
|
}
|
||||||
return RepaintBoundary(child: MessageRow(key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx)));
|
},
|
||||||
});
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: null)))
|
: null)))
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:cwtch/widgets/quotedmessage.dart';
|
import 'package:cwtch/models/message.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cwtch/widgets/profileimage.dart';
|
import 'package:cwtch/widgets/profileimage.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -9,36 +9,29 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import '../main.dart';
|
import '../main.dart';
|
||||||
import '../model.dart';
|
import '../model.dart';
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
import 'invitationbubble.dart';
|
|
||||||
import 'malformedbubble.dart';
|
|
||||||
import 'messagebubble.dart';
|
|
||||||
import 'messageloadingbubble.dart';
|
|
||||||
|
|
||||||
class MessageRow extends StatefulWidget {
|
class MessageRow extends StatefulWidget {
|
||||||
MessageRow({Key? key}) : super(key: key);
|
final Widget child;
|
||||||
|
MessageRow(this.child, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_MessageRowState createState() => _MessageRowState();
|
MessageRowState createState() => MessageRowState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MessageRowState extends State<MessageRow> {
|
class MessageRowState extends State<MessageRow> {
|
||||||
|
bool showMenu = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
|
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
||||||
var malformed = Provider.of<MessageState>(context).malformed;
|
|
||||||
|
|
||||||
// If the message is malformed then override fromme as we can't trust it
|
Widget wdgIcons = Visibility(
|
||||||
if (malformed) {
|
visible: this.showMenu,
|
||||||
fromMe = false;
|
child: IconButton(
|
||||||
}
|
|
||||||
|
|
||||||
Widget wdgBubble =
|
|
||||||
Flexible(flex: 3, fit: FlexFit.loose, child: Provider.of<MessageState>(context).loaded == true ? widgetForOverlay(Provider.of<MessageState>(context).overlay) : MessageLoadingBubble());
|
|
||||||
Widget wdgIcons = IconButton(
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageState>(context).messageIndex;
|
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context).messageIndex;
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.reply, color: Provider.of<Settings>(context).theme.dropShadowColor()));
|
icon: Icon(Icons.reply, color: Provider.of<Settings>(context).theme.dropShadowColor())));
|
||||||
Widget wdgSpacer = Expanded(child: SizedBox(width: 60, height: 10));
|
Widget wdgSpacer = Expanded(child: SizedBox(width: 60, height: 10));
|
||||||
var widgetRow = <Widget>[];
|
var widgetRow = <Widget>[];
|
||||||
|
|
||||||
|
@ -46,7 +39,7 @@ class _MessageRowState extends State<MessageRow> {
|
||||||
widgetRow = <Widget>[
|
widgetRow = <Widget>[
|
||||||
wdgSpacer,
|
wdgSpacer,
|
||||||
wdgIcons,
|
wdgIcons,
|
||||||
wdgBubble,
|
Flexible(flex: 3, fit: FlexFit.loose, child: widget.child),
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
var contact = Provider.of<ContactInfoState>(context);
|
var contact = Provider.of<ContactInfoState>(context);
|
||||||
|
@ -56,7 +49,7 @@ class _MessageRowState extends State<MessageRow> {
|
||||||
padding: EdgeInsets.all(4.0),
|
padding: EdgeInsets.all(4.0),
|
||||||
child: ProfileImage(
|
child: ProfileImage(
|
||||||
diameter: 48.0,
|
diameter: 48.0,
|
||||||
imagePath: Provider.of<MessageState>(context).senderImage ?? contact.imagePath,
|
imagePath: Provider.of<MessageMetadata>(context).senderImage ?? contact.imagePath,
|
||||||
//maskOut: contact.status != "Authenticated",
|
//maskOut: contact.status != "Authenticated",
|
||||||
border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor(),
|
border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor(),
|
||||||
badgeTextColor: Colors.red, badgeColor: Colors.red,
|
badgeTextColor: Colors.red, badgeColor: Colors.red,
|
||||||
|
@ -64,30 +57,36 @@ class _MessageRowState extends State<MessageRow> {
|
||||||
|
|
||||||
widgetRow = <Widget>[
|
widgetRow = <Widget>[
|
||||||
wdgPortrait,
|
wdgPortrait,
|
||||||
wdgBubble,
|
Flexible(flex: 3, fit: FlexFit.loose, child: widget.child),
|
||||||
wdgIcons,
|
wdgIcons,
|
||||||
wdgSpacer,
|
wdgSpacer,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return Padding(padding: EdgeInsets.all(2), child: Row(mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, children: widgetRow));
|
return MouseRegion(
|
||||||
}
|
// For desktop...
|
||||||
|
|
||||||
Widget widgetForOverlay(int o) {
|
onHover: (event) {
|
||||||
switch (o) {
|
setState(() {
|
||||||
case 1:
|
this.showMenu = true;
|
||||||
return MessageBubble();
|
});
|
||||||
case 100:
|
},
|
||||||
case 101:
|
onExit: (event) {
|
||||||
return InvitationBubble();
|
setState(() {
|
||||||
case 10:
|
this.showMenu = false;
|
||||||
return QuotedMessageBubble();
|
});
|
||||||
}
|
},
|
||||||
return MalformedBubble();
|
child: GestureDetector(
|
||||||
|
|
||||||
|
// Swipe to quote
|
||||||
|
onHorizontalDragEnd: (details) {
|
||||||
|
Provider.of<AppState>(context, listen: false).selectedIndex = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
||||||
|
},
|
||||||
|
child: Padding(padding: EdgeInsets.all(2), child: Row(mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, children: widgetRow))));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _btnAdd() {
|
void _btnAdd() {
|
||||||
var sender = Provider.of<MessageState>(context, listen: false).senderOnion;
|
var sender = Provider.of<MessageMetadata>(context, listen: false).senderHandle;
|
||||||
if (sender == null || sender == "") {
|
if (sender == null || sender == "") {
|
||||||
print("sender not yet loaded");
|
print("sender not yet loaded");
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'package:cwtch/models/message.dart';
|
||||||
|
|
||||||
import 'package:cwtch/main.dart';
|
|
||||||
import 'package:cwtch/widgets/malformedbubble.dart';
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
|
import 'package:cwtch/widgets/messageloadingbubble.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../model.dart';
|
import '../model.dart';
|
||||||
|
@ -10,23 +9,12 @@ import 'package:intl/intl.dart';
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
import 'messagebubbledecorations.dart';
|
import 'messagebubbledecorations.dart';
|
||||||
|
|
||||||
class LocallyIndexedMessage {
|
|
||||||
final dynamic message;
|
|
||||||
final int index;
|
|
||||||
|
|
||||||
LocallyIndexedMessage(this.message, this.index);
|
|
||||||
|
|
||||||
LocallyIndexedMessage.fromJson(Map<String, dynamic> json)
|
|
||||||
: message = json['Message'],
|
|
||||||
index = json['LocalIndex'];
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
'Message': message,
|
|
||||||
'LocalIndex': index,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class QuotedMessageBubble extends StatefulWidget {
|
class QuotedMessageBubble extends StatefulWidget {
|
||||||
|
final Future<Message> quotedMessage;
|
||||||
|
final String body;
|
||||||
|
|
||||||
|
QuotedMessageBubble(this.body, this.quotedMessage);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
QuotedMessageBubbleState createState() => QuotedMessageBubbleState();
|
QuotedMessageBubbleState createState() => QuotedMessageBubbleState();
|
||||||
}
|
}
|
||||||
|
@ -36,54 +24,28 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
|
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
||||||
var prettyDate = "";
|
var prettyDate = "";
|
||||||
var borderRadiousEh = 15.0;
|
var borderRadiousEh = 15.0;
|
||||||
var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
|
|
||||||
|
|
||||||
try {
|
DateTime messageDate = Provider.of<MessageMetadata>(context).timestamp;
|
||||||
dynamic message = jsonDecode(Provider.of<MessageState>(context).message);
|
|
||||||
|
|
||||||
var quotedMessagePotentials =
|
|
||||||
Provider.of<FlwtchState>(context).cwtch.GetMessageByContentHash(Provider.of<MessageState>(context).profileOnion, Provider.of<MessageState>(context).contactHandle, message["quotedHash"]);
|
|
||||||
int messageIndex = Provider.of<MessageState>(context).messageIndex;
|
|
||||||
var quotedMessage = quotedMessagePotentials.then((matchingMessages) {
|
|
||||||
// reverse order the messages from newest to oldest and return the
|
|
||||||
// first matching message where it's index is less than the index of this
|
|
||||||
// message
|
|
||||||
try {
|
|
||||||
var list = (jsonDecode(matchingMessages) as List<dynamic>).map((data) => LocallyIndexedMessage.fromJson(data)).toList();
|
|
||||||
LocallyIndexedMessage candidate = list.reversed.firstWhere((element) => messageIndex < element.index, orElse: () {
|
|
||||||
return list.firstWhere((element) => messageIndex > element.index);
|
|
||||||
});
|
|
||||||
return candidate;
|
|
||||||
} catch (e) {
|
|
||||||
// Malformed Message will be returned...
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Provider.of<MessageState>(context).timestamp != null) {
|
|
||||||
// user-configurable timestamps prolly ideal? #todo
|
|
||||||
DateTime messageDate = Provider.of<MessageState>(context).timestamp;
|
|
||||||
prettyDate = DateFormat.yMd().add_jm().format(messageDate.toLocal());
|
prettyDate = DateFormat.yMd().add_jm().format(messageDate.toLocal());
|
||||||
}
|
|
||||||
|
|
||||||
// If the sender is not us, then we want to give them a nickname...
|
// If the sender is not us, then we want to give them a nickname...
|
||||||
var senderDisplayStr = "";
|
var senderDisplayStr = "";
|
||||||
if (!fromMe && Provider.of<MessageState>(context).senderOnion != null) {
|
if (!fromMe) {
|
||||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).senderOnion);
|
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
senderDisplayStr = contact.nickname;
|
senderDisplayStr = contact.nickname;
|
||||||
} else {
|
} else {
|
||||||
senderDisplayStr = Provider.of<MessageState>(context).senderOnion;
|
senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var wdgSender = SelectableText(senderDisplayStr,
|
var wdgSender = SelectableText(senderDisplayStr,
|
||||||
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor()));
|
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor()));
|
||||||
|
|
||||||
var wdgMessage = SelectableText(
|
var wdgMessage = SelectableText(
|
||||||
(message["body"] ?? "") + '\u202F',
|
widget.body + '\u202F',
|
||||||
key: Key(myKey),
|
|
||||||
focusNode: _focus,
|
focusNode: _focus,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||||
|
@ -93,27 +55,26 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
|
||||||
);
|
);
|
||||||
|
|
||||||
var wdgQuote = FutureBuilder(
|
var wdgQuote = FutureBuilder(
|
||||||
future: quotedMessage,
|
future: widget.quotedMessage,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
var lim = (snapshot.data! as LocallyIndexedMessage);
|
var qMessage = (snapshot.data! as Message);
|
||||||
var limmessage = lim.message;
|
|
||||||
// Swap the background color for quoted tweets..
|
// Swap the background color for quoted tweets..
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsets.all(5),
|
margin: EdgeInsets.all(5),
|
||||||
padding: EdgeInsets.all(5),
|
padding: EdgeInsets.all(5),
|
||||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor() : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
|
color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor() : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor(),
|
||||||
child: Text(jsonDecode(limmessage)["d"]));
|
child: qMessage.getPreviewWidget(context));
|
||||||
} else {
|
} else {
|
||||||
// This should be almost instantly resolved, any failure likely means an issue in decoding...
|
// This should be almost instantly resolved, any failure likely means an issue in decoding...
|
||||||
return MalformedBubble();
|
return MessageLoadingBubble();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageState>(context).ackd, errored: Provider.of<MessageState>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
||||||
|
|
||||||
var error = Provider.of<MessageState>(context).error;
|
var error = Provider.of<MessageMetadata>(context).error;
|
||||||
|
|
||||||
return LayoutBuilder(builder: (context, constraints) {
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
return RepaintBoundary(
|
return RepaintBoundary(
|
||||||
|
@ -143,8 +104,5 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: fromMe ? [wdgQuote, wdgMessage, wdgDecorations] : [wdgSender, wdgQuote, wdgMessage, wdgDecorations])))));
|
children: fromMe ? [wdgQuote, wdgMessage, wdgDecorations] : [wdgSender, wdgQuote, wdgMessage, wdgDecorations])))));
|
||||||
});
|
});
|
||||||
} catch (e) {
|
|
||||||
return MalformedBubble();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ packages:
|
||||||
name: charcode
|
name: charcode
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.3.1"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -375,7 +375,7 @@ packages:
|
||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.0"
|
version: "0.4.1"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
Loading…
Reference in New Issue