mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-08-07 21:52:26 -04:00
debugging
This commit is contained in:
parent
f780a60d69
commit
0e4606f35e
20 changed files with 521 additions and 321 deletions
|
@ -18,7 +18,7 @@ class AuthorInputQueue {
|
|||
_onError = onError,
|
||||
_inputSource = inputSource,
|
||||
_outputPosition = outputPosition,
|
||||
_lastMessage = outputPosition?.message,
|
||||
_lastMessage = outputPosition?.message.content,
|
||||
_messageIntegrity = messageIntegrity,
|
||||
_currentPosition = inputSource.currentWindow.last;
|
||||
|
||||
|
@ -43,8 +43,8 @@ class AuthorInputQueue {
|
|||
////////////////////////////////////////////////////////////////////////////
|
||||
// Public interface
|
||||
|
||||
// Check if there are no messages in this queue to reconcile
|
||||
bool get isEmpty => _currentMessage == null;
|
||||
// Check if there are no messages left in this queue to reconcile
|
||||
bool get isDone => _isDone;
|
||||
|
||||
// Get the current message that needs reconciliation
|
||||
proto.Message? get current => _currentMessage;
|
||||
|
@ -58,6 +58,9 @@ class AuthorInputQueue {
|
|||
// Remove a reconciled message and move to the next message
|
||||
// Returns true if there is more work to do
|
||||
Future<bool> consume() async {
|
||||
if (_isDone) {
|
||||
return false;
|
||||
}
|
||||
while (true) {
|
||||
_lastMessage = _currentMessage;
|
||||
|
||||
|
@ -66,6 +69,7 @@ class AuthorInputQueue {
|
|||
// Get more window if we need to
|
||||
if (!await _updateWindow()) {
|
||||
// Window is not available so this queue can't work right now
|
||||
_isDone = true;
|
||||
return false;
|
||||
}
|
||||
final nextMessage = _inputSource.currentWindow
|
||||
|
@ -73,9 +77,9 @@ class AuthorInputQueue {
|
|||
|
||||
// Drop the 'offline' elements because we don't reconcile
|
||||
// anything until it has been confirmed to be committed to the DHT
|
||||
if (nextMessage.isOffline) {
|
||||
continue;
|
||||
}
|
||||
// if (nextMessage.isOffline) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if (_lastMessage != null) {
|
||||
// Ensure the timestamp is not moving backward
|
||||
|
@ -112,7 +116,7 @@ class AuthorInputQueue {
|
|||
outer:
|
||||
while (true) {
|
||||
// Iterate through current window backward
|
||||
for (var i = _inputSource.currentWindow.elements.length;
|
||||
for (var i = _inputSource.currentWindow.elements.length - 1;
|
||||
i >= 0 && _currentPosition >= 0;
|
||||
i--, _currentPosition--) {
|
||||
final elem = _inputSource.currentWindow.elements[i];
|
||||
|
@ -134,13 +138,24 @@ class AuthorInputQueue {
|
|||
if (!await _updateWindow()) {
|
||||
// Window is not available or things are empty so this
|
||||
// queue can't work right now
|
||||
_isDone = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The current position should be equal to the first message to process
|
||||
// and the current window to process should not be empty
|
||||
return _inputSource.currentWindow.elements.isNotEmpty;
|
||||
// _currentPosition points to either before the input source starts
|
||||
// or the position of the previous element. We still need to set the
|
||||
// _currentMessage to the previous element so consume() can compare
|
||||
// against it if we can.
|
||||
if (_currentPosition >= 0) {
|
||||
_currentMessage = _inputSource.currentWindow
|
||||
.elements[_currentPosition - _inputSource.currentWindow.first].value;
|
||||
}
|
||||
|
||||
// After this consume(), the currentPosition and _currentMessage should
|
||||
// be equal to the first message to process and the current window to
|
||||
// process should not be empty
|
||||
return consume();
|
||||
}
|
||||
|
||||
// Slide the window toward the current position and load the batch around it
|
||||
|
@ -186,6 +201,9 @@ class AuthorInputQueue {
|
|||
int _currentPosition;
|
||||
// The current message we're looking at
|
||||
proto.Message? _currentMessage;
|
||||
// If we have reached the end
|
||||
bool _isDone = false;
|
||||
|
||||
// Desired maximum window length
|
||||
static const int _maxWindowLength = 256;
|
||||
}
|
||||
|
|
|
@ -21,9 +21,10 @@ class AuthorInputSource {
|
|||
{required DHTLogStateData<proto.Message> cubitState,
|
||||
required this.cubit}) {
|
||||
_currentWindow = InputWindow(
|
||||
elements: cubitState.elements,
|
||||
first: cubitState.tail - cubitState.elements.length,
|
||||
last: cubitState.tail - 1);
|
||||
elements: cubitState.window,
|
||||
first: (cubitState.windowTail - cubitState.window.length) %
|
||||
cubitState.length,
|
||||
last: (cubitState.windowTail - 1) % cubitState.length);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -12,7 +12,7 @@ import 'output_position.dart';
|
|||
|
||||
class MessageReconciliation {
|
||||
MessageReconciliation(
|
||||
{required TableDBArrayCubit<proto.ReconciledMessage> output,
|
||||
{required TableDBArrayProtobufCubit<proto.ReconciledMessage> output,
|
||||
required void Function(Object, StackTrace?) onError})
|
||||
: _outputCubit = output,
|
||||
_onError = onError;
|
||||
|
@ -23,7 +23,7 @@ class MessageReconciliation {
|
|||
TypedKey author,
|
||||
DHTLogStateData<proto.Message> inputMessagesCubitState,
|
||||
DHTLogCubit<proto.Message> inputMessagesCubit) {
|
||||
if (inputMessagesCubitState.elements.isEmpty) {
|
||||
if (inputMessagesCubitState.window.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -84,11 +84,11 @@ class MessageReconciliation {
|
|||
_outputCubit.operate((arr) async {
|
||||
var pos = arr.length - 1;
|
||||
while (pos >= 0) {
|
||||
final message = await arr.getProtobuf(proto.Message.fromBuffer, pos);
|
||||
final message = await arr.get(pos);
|
||||
if (message == null) {
|
||||
throw StateError('should have gotten last message');
|
||||
}
|
||||
if (message.author.toVeilid() == author) {
|
||||
if (message.content.author.toVeilid() == author) {
|
||||
return OutputPosition(message, pos);
|
||||
}
|
||||
pos--;
|
||||
|
@ -99,11 +99,11 @@ class MessageReconciliation {
|
|||
// Process a list of author input queues and insert their messages
|
||||
// into the output array, performing validation steps along the way
|
||||
Future<void> _reconcileInputQueues({
|
||||
required TableDBArray reconciledArray,
|
||||
required TableDBArrayProtobuf<proto.ReconciledMessage> reconciledArray,
|
||||
required List<AuthorInputQueue> inputQueues,
|
||||
}) async {
|
||||
// Ensure queues all have something to do
|
||||
inputQueues.removeWhere((q) => q.isEmpty);
|
||||
inputQueues.removeWhere((q) => q.isDone);
|
||||
if (inputQueues.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
@ -124,8 +124,7 @@ class MessageReconciliation {
|
|||
// Get the timestamp for this output position
|
||||
var currentOutputMessage = firstOutputPos == null
|
||||
? null
|
||||
: await reconciledArray.getProtobuf(
|
||||
proto.Message.fromBuffer, firstOutputPos);
|
||||
: await reconciledArray.get(firstOutputPos);
|
||||
|
||||
var currentOutputPos = firstOutputPos ?? 0;
|
||||
|
||||
|
@ -143,7 +142,7 @@ class MessageReconciliation {
|
|||
for (final inputQueue in inputQueues) {
|
||||
final inputCurrent = inputQueue.current!;
|
||||
if (currentOutputMessage == null ||
|
||||
inputCurrent.timestamp <= currentOutputMessage.timestamp) {
|
||||
inputCurrent.timestamp < currentOutputMessage.content.timestamp) {
|
||||
toInsert.add(inputCurrent);
|
||||
added = true;
|
||||
|
||||
|
@ -156,7 +155,7 @@ class MessageReconciliation {
|
|||
}
|
||||
// Remove empty queues now that we're done iterating
|
||||
if (someQueueEmpty) {
|
||||
inputQueues.removeWhere((q) => q.isEmpty);
|
||||
inputQueues.removeWhere((q) => q.isDone);
|
||||
}
|
||||
|
||||
if (toInsert.length >= _maxReconcileChunk) {
|
||||
|
@ -166,13 +165,24 @@ class MessageReconciliation {
|
|||
|
||||
// Perform insertions in bulk
|
||||
if (toInsert.isNotEmpty) {
|
||||
await reconciledArray.insertAllProtobuf(currentOutputPos, toInsert);
|
||||
final reconciledTime = Veilid.instance.now().toInt64();
|
||||
|
||||
// Add reconciled timestamps
|
||||
final reconciledInserts = toInsert
|
||||
.map((message) => proto.ReconciledMessage()
|
||||
..reconciledTime = reconciledTime
|
||||
..content = message)
|
||||
.toList();
|
||||
|
||||
await reconciledArray.insertAll(currentOutputPos, reconciledInserts);
|
||||
|
||||
toInsert.clear();
|
||||
} else {
|
||||
// If there's nothing to insert at this position move to the next one
|
||||
currentOutputPos++;
|
||||
currentOutputMessage = await reconciledArray.getProtobuf(
|
||||
proto.Message.fromBuffer, currentOutputPos);
|
||||
currentOutputMessage = (currentOutputPos == reconciledArray.length)
|
||||
? null
|
||||
: await reconciledArray.get(currentOutputPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -180,7 +190,7 @@ class MessageReconciliation {
|
|||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Map<TypedKey, AuthorInputSource> _inputSources = {};
|
||||
final TableDBArrayCubit<proto.ReconciledMessage> _outputCubit;
|
||||
final TableDBArrayProtobufCubit<proto.ReconciledMessage> _outputCubit;
|
||||
final void Function(Object, StackTrace?) _onError;
|
||||
|
||||
static const int _maxReconcileChunk = 65536;
|
||||
|
|
|
@ -6,7 +6,7 @@ import '../../../proto/proto.dart' as proto;
|
|||
@immutable
|
||||
class OutputPosition extends Equatable {
|
||||
const OutputPosition(this.message, this.pos);
|
||||
final proto.Message message;
|
||||
final proto.ReconciledMessage message;
|
||||
final int pos;
|
||||
@override
|
||||
List<Object?> get props => [message, pos];
|
||||
|
|
|
@ -22,14 +22,17 @@ class RenderStateElement {
|
|||
if (!isLocal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (sent && !sentOffline) {
|
||||
if (reconciledTimestamp != null) {
|
||||
return MessageSendState.delivered;
|
||||
}
|
||||
if (reconciledTimestamp != null) {
|
||||
return MessageSendState.sent;
|
||||
if (sent) {
|
||||
if (!sentOffline) {
|
||||
return MessageSendState.sent;
|
||||
} else {
|
||||
return MessageSendState.sending;
|
||||
}
|
||||
}
|
||||
return MessageSendState.sending;
|
||||
return null;
|
||||
}
|
||||
|
||||
proto.Message message;
|
||||
|
@ -66,7 +69,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||
Future<void> close() async {
|
||||
await _initWait();
|
||||
|
||||
await _sendingMessagesQueue.close();
|
||||
await _unsentMessagesQueue.close();
|
||||
await _sentSubscription?.cancel();
|
||||
await _rcvdSubscription?.cancel();
|
||||
await _reconciledSubscription?.cancel();
|
||||
|
@ -78,11 +81,11 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||
|
||||
// Initialize everything
|
||||
Future<void> _init() async {
|
||||
_sendingMessagesQueue = PersistentQueue<proto.Message>(
|
||||
table: 'SingleContactSendingMessages',
|
||||
_unsentMessagesQueue = PersistentQueue<proto.Message>(
|
||||
table: 'SingleContactUnsentMessages',
|
||||
key: _remoteConversationRecordKey.toString(),
|
||||
fromBuffer: proto.Message.fromBuffer,
|
||||
closure: _processSendingMessages,
|
||||
closure: _processUnsentMessages,
|
||||
);
|
||||
|
||||
// Make crypto
|
||||
|
@ -144,13 +147,16 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||
// Open reconciled chat record key
|
||||
Future<void> _initReconciledMessagesCubit() async {
|
||||
final tableName =
|
||||
_localConversationRecordKey.toString().replaceAll(':', '_');
|
||||
_reconciledMessagesTableDBName(_localConversationRecordKey);
|
||||
|
||||
final crypto = await _makeLocalMessagesCrypto();
|
||||
|
||||
_reconciledMessagesCubit = TableDBArrayCubit(
|
||||
open: () async => TableDBArray.make(table: tableName, crypto: crypto),
|
||||
decodeElement: proto.ReconciledMessage.fromBuffer);
|
||||
_reconciledMessagesCubit = TableDBArrayProtobufCubit(
|
||||
open: () async => TableDBArrayProtobuf.make(
|
||||
table: tableName,
|
||||
crypto: crypto,
|
||||
fromBuffer: proto.ReconciledMessage.fromBuffer),
|
||||
);
|
||||
|
||||
_reconciliation = MessageReconciliation(
|
||||
output: _reconciledMessagesCubit!,
|
||||
|
@ -200,6 +206,9 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||
_activeAccountInfo.localAccount.identityMaster.identityPublicTypedKey(),
|
||||
sentMessages,
|
||||
_sentMessagesCubit!);
|
||||
|
||||
// Update the view
|
||||
_renderState();
|
||||
}
|
||||
|
||||
// Called when the received messages cubit gets a change
|
||||
|
@ -211,11 +220,14 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||
|
||||
_reconciliation.reconcileMessages(
|
||||
_remoteIdentityPublicKey, rcvdMessages, _rcvdMessagesCubit!);
|
||||
|
||||
// Update the view
|
||||
_renderState();
|
||||
}
|
||||
|
||||
// Called when the reconciled messages window gets a change
|
||||
void _updateReconciledMessagesState(
|
||||
TableDBArrayBusyState<proto.ReconciledMessage> avmessages) {
|
||||
TableDBArrayProtobufBusyState<proto.ReconciledMessage> avmessages) {
|
||||
// Update the view
|
||||
_renderState();
|
||||
}
|
||||
|
@ -237,7 +249,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||
}
|
||||
|
||||
// Async process to send messages in the background
|
||||
Future<void> _processSendingMessages(IList<proto.Message> messages) async {
|
||||
Future<void> _processUnsentMessages(IList<proto.Message> messages) async {
|
||||
// Go through and assign ids to all the messages in order
|
||||
proto.Message? previousMessage;
|
||||
final processedMessages = messages.toList();
|
||||
|
@ -258,7 +270,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||
// Get all sent messages
|
||||
final sentMessages = _sentMessagesCubit?.state.state.asData?.value;
|
||||
// Get all items in the unsent queue
|
||||
final sendingMessages = _sendingMessagesQueue.queue;
|
||||
// final unsentMessages = _unsentMessagesQueue.queue;
|
||||
|
||||
// If we aren't ready to render a state, say we're loading
|
||||
if (reconciledMessages == null || sentMessages == null) {
|
||||
|
@ -267,63 +279,49 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||
}
|
||||
|
||||
// Generate state for each message
|
||||
// final reconciledMessagesMap =
|
||||
// IMap<String, proto.ReconciledMessage>.fromValues(
|
||||
// keyMapper: (x) => x.content.authorUniqueIdString,
|
||||
// values: reconciledMessages.elements,
|
||||
// );
|
||||
final sentMessagesMap =
|
||||
IMap<String, OnlineElementState<proto.Message>>.fromValues(
|
||||
keyMapper: (x) => x.value.authorUniqueIdString,
|
||||
values: sentMessages.elements,
|
||||
);
|
||||
final reconciledMessagesMap =
|
||||
IMap<String, proto.ReconciledMessage>.fromValues(
|
||||
keyMapper: (x) => x.content.authorUniqueIdString,
|
||||
values: reconciledMessages.elements,
|
||||
);
|
||||
final sendingMessagesMap = IMap<String, proto.Message>.fromValues(
|
||||
keyMapper: (x) => x.authorUniqueIdString,
|
||||
values: sendingMessages,
|
||||
values: sentMessages.window,
|
||||
);
|
||||
// final unsentMessagesMap = IMap<String, proto.Message>.fromValues(
|
||||
// keyMapper: (x) => x.authorUniqueIdString,
|
||||
// values: unsentMessages,
|
||||
// );
|
||||
|
||||
final renderedElements = <String, RenderStateElement>{};
|
||||
final renderedElements = <RenderStateElement>[];
|
||||
|
||||
for (final m in reconciledMessagesMap.entries) {
|
||||
renderedElements[m.key] = RenderStateElement(
|
||||
message: m.value.content,
|
||||
isLocal: m.value.content.author.toVeilid() ==
|
||||
_activeAccountInfo.localAccount.identityMaster
|
||||
.identityPublicTypedKey(),
|
||||
reconciledTimestamp: Timestamp.fromInt64(m.value.reconciledTime),
|
||||
);
|
||||
}
|
||||
for (final m in sentMessagesMap.entries) {
|
||||
renderedElements.putIfAbsent(
|
||||
m.key,
|
||||
() => RenderStateElement(
|
||||
message: m.value.value,
|
||||
isLocal: true,
|
||||
))
|
||||
..sent = true
|
||||
..sentOffline = m.value.isOffline;
|
||||
}
|
||||
for (final m in sendingMessagesMap.entries) {
|
||||
renderedElements
|
||||
.putIfAbsent(
|
||||
m.key,
|
||||
() => RenderStateElement(
|
||||
message: m.value,
|
||||
isLocal: true,
|
||||
))
|
||||
.sent = false;
|
||||
for (final m in reconciledMessages.elements) {
|
||||
final isLocal = m.content.author.toVeilid() ==
|
||||
_activeAccountInfo.localAccount.identityMaster
|
||||
.identityPublicTypedKey();
|
||||
final reconciledTimestamp = Timestamp.fromInt64(m.reconciledTime);
|
||||
final sm =
|
||||
isLocal ? sentMessagesMap[m.content.authorUniqueIdString] : null;
|
||||
final sent = isLocal && sm != null;
|
||||
final sentOffline = isLocal && sm != null && sm.isOffline;
|
||||
|
||||
renderedElements.add(RenderStateElement(
|
||||
message: m.content,
|
||||
isLocal: isLocal,
|
||||
reconciledTimestamp: reconciledTimestamp,
|
||||
sent: sent,
|
||||
sentOffline: sentOffline,
|
||||
));
|
||||
}
|
||||
|
||||
// Render the state
|
||||
final messageKeys = renderedElements.entries
|
||||
.toIList()
|
||||
.sort((x, y) => x.key.compareTo(y.key));
|
||||
final renderedState = messageKeys
|
||||
final renderedState = renderedElements
|
||||
.map((x) => MessageState(
|
||||
content: x.value.message,
|
||||
sentTimestamp: Timestamp.fromInt64(x.value.message.timestamp),
|
||||
reconciledTimestamp: x.value.reconciledTimestamp,
|
||||
sendState: x.value.sendState))
|
||||
content: x.message,
|
||||
sentTimestamp: Timestamp.fromInt64(x.message.timestamp),
|
||||
reconciledTimestamp: x.reconciledTimestamp,
|
||||
sendState: x.sendState))
|
||||
.toIList();
|
||||
|
||||
// Emit the rendered state
|
||||
|
@ -340,7 +338,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||
..timestamp = Veilid.instance.now().toInt64();
|
||||
|
||||
// Put in the queue
|
||||
_sendingMessagesQueue.addSync(message);
|
||||
_unsentMessagesQueue.addSync(message);
|
||||
|
||||
// Update the view
|
||||
_renderState();
|
||||
|
@ -358,7 +356,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||
|
||||
static String _reconciledMessagesTableDBName(
|
||||
TypedKey localConversationRecordKey) =>
|
||||
'msg_$localConversationRecordKey';
|
||||
'msg_${localConversationRecordKey.toString().replaceAll(':', '_')}';
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
@ -375,14 +373,14 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||
|
||||
DHTLogCubit<proto.Message>? _sentMessagesCubit;
|
||||
DHTLogCubit<proto.Message>? _rcvdMessagesCubit;
|
||||
TableDBArrayCubit<proto.ReconciledMessage>? _reconciledMessagesCubit;
|
||||
TableDBArrayProtobufCubit<proto.ReconciledMessage>? _reconciledMessagesCubit;
|
||||
|
||||
late final MessageReconciliation _reconciliation;
|
||||
|
||||
late final PersistentQueue<proto.Message> _sendingMessagesQueue;
|
||||
late final PersistentQueue<proto.Message> _unsentMessagesQueue;
|
||||
|
||||
StreamSubscription<DHTLogBusyState<proto.Message>>? _sentSubscription;
|
||||
StreamSubscription<DHTLogBusyState<proto.Message>>? _rcvdSubscription;
|
||||
StreamSubscription<TableDBArrayBusyState<proto.ReconciledMessage>>?
|
||||
StreamSubscription<TableDBArrayProtobufBusyState<proto.ReconciledMessage>>?
|
||||
_reconciledSubscription;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ MessageState _$MessageStateFromJson(Map<String, dynamic> json) {
|
|||
/// @nodoc
|
||||
mixin _$MessageState {
|
||||
// Content of the message
|
||||
@JsonKey(fromJson: proto.messageFromJson, toJson: proto.messageToJson)
|
||||
@JsonKey(fromJson: messageFromJson, toJson: messageToJson)
|
||||
proto.Message get content =>
|
||||
throw _privateConstructorUsedError; // Sent timestamp
|
||||
Timestamp get sentTimestamp =>
|
||||
|
@ -43,7 +43,7 @@ abstract class $MessageStateCopyWith<$Res> {
|
|||
_$MessageStateCopyWithImpl<$Res, MessageState>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{@JsonKey(fromJson: proto.messageFromJson, toJson: proto.messageToJson)
|
||||
{@JsonKey(fromJson: messageFromJson, toJson: messageToJson)
|
||||
proto.Message content,
|
||||
Timestamp sentTimestamp,
|
||||
Timestamp? reconciledTimestamp,
|
||||
|
@ -98,7 +98,7 @@ abstract class _$$MessageStateImplCopyWith<$Res>
|
|||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{@JsonKey(fromJson: proto.messageFromJson, toJson: proto.messageToJson)
|
||||
{@JsonKey(fromJson: messageFromJson, toJson: messageToJson)
|
||||
proto.Message content,
|
||||
Timestamp sentTimestamp,
|
||||
Timestamp? reconciledTimestamp,
|
||||
|
@ -146,7 +146,7 @@ class __$$MessageStateImplCopyWithImpl<$Res>
|
|||
@JsonSerializable()
|
||||
class _$MessageStateImpl with DiagnosticableTreeMixin implements _MessageState {
|
||||
const _$MessageStateImpl(
|
||||
{@JsonKey(fromJson: proto.messageFromJson, toJson: proto.messageToJson)
|
||||
{@JsonKey(fromJson: messageFromJson, toJson: messageToJson)
|
||||
required this.content,
|
||||
required this.sentTimestamp,
|
||||
required this.reconciledTimestamp,
|
||||
|
@ -157,7 +157,7 @@ class _$MessageStateImpl with DiagnosticableTreeMixin implements _MessageState {
|
|||
|
||||
// Content of the message
|
||||
@override
|
||||
@JsonKey(fromJson: proto.messageFromJson, toJson: proto.messageToJson)
|
||||
@JsonKey(fromJson: messageFromJson, toJson: messageToJson)
|
||||
final proto.Message content;
|
||||
// Sent timestamp
|
||||
@override
|
||||
|
@ -220,7 +220,7 @@ class _$MessageStateImpl with DiagnosticableTreeMixin implements _MessageState {
|
|||
|
||||
abstract class _MessageState implements MessageState {
|
||||
const factory _MessageState(
|
||||
{@JsonKey(fromJson: proto.messageFromJson, toJson: proto.messageToJson)
|
||||
{@JsonKey(fromJson: messageFromJson, toJson: messageToJson)
|
||||
required final proto.Message content,
|
||||
required final Timestamp sentTimestamp,
|
||||
required final Timestamp? reconciledTimestamp,
|
||||
|
@ -230,7 +230,7 @@ abstract class _MessageState implements MessageState {
|
|||
_$MessageStateImpl.fromJson;
|
||||
|
||||
@override // Content of the message
|
||||
@JsonKey(fromJson: proto.messageFromJson, toJson: proto.messageToJson)
|
||||
@JsonKey(fromJson: messageFromJson, toJson: messageToJson)
|
||||
proto.Message get content;
|
||||
@override // Sent timestamp
|
||||
Timestamp get sentTimestamp;
|
||||
|
|
|
@ -271,18 +271,18 @@ class ChatComponent extends StatelessWidget {
|
|||
child: DecoratedBox(
|
||||
decoration: const BoxDecoration(),
|
||||
child: Chat(
|
||||
theme: chatTheme,
|
||||
// emojiEnlargementBehavior:
|
||||
// EmojiEnlargementBehavior.multi,
|
||||
messages: chatMessages,
|
||||
//onAttachmentPressed: _handleAttachmentPressed,
|
||||
//onMessageTap: _handleMessageTap,
|
||||
//onPreviewDataFetched: _handlePreviewDataFetched,
|
||||
onSendPressed: _handleSendPressed,
|
||||
//showUserAvatars: false,
|
||||
//showUserNames: true,
|
||||
user: _localUser,
|
||||
),
|
||||
theme: chatTheme,
|
||||
// emojiEnlargementBehavior:
|
||||
// EmojiEnlargementBehavior.multi,
|
||||
messages: chatMessages,
|
||||
//onAttachmentPressed: _handleAttachmentPressed,
|
||||
//onMessageTap: _handleMessageTap,
|
||||
//onPreviewDataFetched: _handlePreviewDataFetched,
|
||||
onSendPressed: _handleSendPressed,
|
||||
//showUserAvatars: false,
|
||||
//showUserNames: true,
|
||||
user: _localUser,
|
||||
emptyState: const EmptyChatWidget()),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import '../../theme/models/scale_scheme.dart';
|
||||
|
||||
class NoConversationWidget extends StatelessWidget {
|
||||
const NoConversationWidget({super.key});
|
||||
|
@ -7,28 +10,32 @@ class NoConversationWidget extends StatelessWidget {
|
|||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
) =>
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.emoji_people_outlined,
|
||||
color: Theme.of(context).disabledColor,
|
||||
size: 48,
|
||||
),
|
||||
Text(
|
||||
'Choose A Conversation To Chat',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).disabledColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.diversity_3,
|
||||
color: scale.primaryScale.subtleBorder,
|
||||
size: 48,
|
||||
),
|
||||
Text(
|
||||
translate('chat.start_a_conversation'),
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: scale.primaryScale.subtleBorder,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue