mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-01-23 14:01:06 -05:00
pagination work
This commit is contained in:
parent
4082d1dd76
commit
5473bd2ee4
@ -2,11 +2,13 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:async_tools/async_tools.dart';
|
import 'package:async_tools/async_tools.dart';
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:veilid_support/veilid_support.dart';
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import '../../account_manager/account_manager.dart';
|
import '../../account_manager/account_manager.dart';
|
||||||
import '../../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
|
import '../../tools/tools.dart';
|
||||||
import '../models/models.dart';
|
import '../models/models.dart';
|
||||||
import 'reconciliation/reconciliation.dart';
|
import 'reconciliation/reconciliation.dart';
|
||||||
|
|
||||||
@ -42,7 +44,7 @@ class RenderStateElement {
|
|||||||
bool sentOffline;
|
bool sentOffline;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef SingleContactMessagesState = AsyncValue<IList<MessageState>>;
|
typedef SingleContactMessagesState = AsyncValue<MessagesState>;
|
||||||
|
|
||||||
// Cubit that processes single-contact chats
|
// Cubit that processes single-contact chats
|
||||||
// Builds the reconciled chat record from the local and remote conversation keys
|
// Builds the reconciled chat record from the local and remote conversation keys
|
||||||
@ -60,6 +62,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
_localMessagesRecordKey = localMessagesRecordKey,
|
_localMessagesRecordKey = localMessagesRecordKey,
|
||||||
_remoteConversationRecordKey = remoteConversationRecordKey,
|
_remoteConversationRecordKey = remoteConversationRecordKey,
|
||||||
_remoteMessagesRecordKey = remoteMessagesRecordKey,
|
_remoteMessagesRecordKey = remoteMessagesRecordKey,
|
||||||
|
_commandController = StreamController(),
|
||||||
super(const AsyncValue.loading()) {
|
super(const AsyncValue.loading()) {
|
||||||
// Async Init
|
// Async Init
|
||||||
_initWait.add(_init);
|
_initWait.add(_init);
|
||||||
@ -69,6 +72,8 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
await _initWait();
|
await _initWait();
|
||||||
|
|
||||||
|
await _commandController.close();
|
||||||
|
await _commandRunnerFut;
|
||||||
await _unsentMessagesQueue.close();
|
await _unsentMessagesQueue.close();
|
||||||
await _sentSubscription?.cancel();
|
await _sentSubscription?.cancel();
|
||||||
await _rcvdSubscription?.cancel();
|
await _rcvdSubscription?.cancel();
|
||||||
@ -99,6 +104,9 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
|
|
||||||
// Remote messages key
|
// Remote messages key
|
||||||
await _initRcvdMessagesCubit();
|
await _initRcvdMessagesCubit();
|
||||||
|
|
||||||
|
// Command execution background process
|
||||||
|
_commandRunnerFut = Future.delayed(Duration.zero, _commandRunner);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make crypto
|
// Make crypto
|
||||||
@ -191,6 +199,34 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
_sendMessage(message: message);
|
_sendMessage(message: message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run a chat command
|
||||||
|
void runCommand(String command) {
|
||||||
|
final (cmd, rest) = command.splitOnce(' ');
|
||||||
|
|
||||||
|
if (kDebugMode) {
|
||||||
|
if (cmd == '/repeat' && rest != null) {
|
||||||
|
final (countStr, text) = rest.splitOnce(' ');
|
||||||
|
final count = int.tryParse(countStr);
|
||||||
|
if (count != null) {
|
||||||
|
runCommandRepeat(count, text ?? '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run a repeat command
|
||||||
|
void runCommandRepeat(int count, String text) {
|
||||||
|
_commandController.sink.add(() async {
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
final protoMessageText = proto.Message_Text()
|
||||||
|
..text = text.replaceAll(RegExp(r'\$n\b'), i.toString());
|
||||||
|
final message = proto.Message()..text = protoMessageText;
|
||||||
|
_sendMessage(message: message);
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
// Internal implementation
|
// Internal implementation
|
||||||
|
|
||||||
@ -220,9 +256,6 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
|
|
||||||
_reconciliation.reconcileMessages(
|
_reconciliation.reconcileMessages(
|
||||||
_remoteIdentityPublicKey, rcvdMessages, _rcvdMessagesCubit!);
|
_remoteIdentityPublicKey, rcvdMessages, _rcvdMessagesCubit!);
|
||||||
|
|
||||||
// Update the view
|
|
||||||
_renderState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called when the reconciled messages window gets a change
|
// Called when the reconciled messages window gets a change
|
||||||
@ -296,7 +329,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
|
|
||||||
final renderedElements = <RenderStateElement>[];
|
final renderedElements = <RenderStateElement>[];
|
||||||
|
|
||||||
for (final m in reconciledMessages.elements) {
|
for (final m in reconciledMessages.windowElements) {
|
||||||
final isLocal = m.content.author.toVeilid() ==
|
final isLocal = m.content.author.toVeilid() ==
|
||||||
_activeAccountInfo.localAccount.identityMaster
|
_activeAccountInfo.localAccount.identityMaster
|
||||||
.identityPublicTypedKey();
|
.identityPublicTypedKey();
|
||||||
@ -316,7 +349,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render the state
|
// Render the state
|
||||||
final renderedState = renderedElements
|
final messages = renderedElements
|
||||||
.map((x) => MessageState(
|
.map((x) => MessageState(
|
||||||
content: x.message,
|
content: x.message,
|
||||||
sentTimestamp: Timestamp.fromInt64(x.message.timestamp),
|
sentTimestamp: Timestamp.fromInt64(x.message.timestamp),
|
||||||
@ -325,7 +358,12 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
.toIList();
|
.toIList();
|
||||||
|
|
||||||
// Emit the rendered state
|
// Emit the rendered state
|
||||||
emit(AsyncValue.data(renderedState));
|
emit(AsyncValue.data(MessagesState(
|
||||||
|
windowMessages: messages,
|
||||||
|
length: reconciledMessages.length,
|
||||||
|
windowTail: reconciledMessages.windowTail,
|
||||||
|
windowCount: reconciledMessages.windowCount,
|
||||||
|
follow: reconciledMessages.follow)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _sendMessage({required proto.Message message}) {
|
void _sendMessage({required proto.Message message}) {
|
||||||
@ -344,6 +382,12 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
_renderState();
|
_renderState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _commandRunner() async {
|
||||||
|
await for (final command in _commandController.stream) {
|
||||||
|
await command();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
// Static utility functions
|
// Static utility functions
|
||||||
|
|
||||||
@ -383,4 +427,6 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
StreamSubscription<DHTLogBusyState<proto.Message>>? _rcvdSubscription;
|
StreamSubscription<DHTLogBusyState<proto.Message>>? _rcvdSubscription;
|
||||||
StreamSubscription<TableDBArrayProtobufBusyState<proto.ReconciledMessage>>?
|
StreamSubscription<TableDBArrayProtobufBusyState<proto.ReconciledMessage>>?
|
||||||
_reconciledSubscription;
|
_reconciledSubscription;
|
||||||
|
final StreamController<Future<void> Function()> _commandController;
|
||||||
|
late final Future<void> _commandRunnerFut;
|
||||||
}
|
}
|
||||||
|
27
lib/chat/models/messages_state.dart
Normal file
27
lib/chat/models/messages_state.dart
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
import 'message_state.dart';
|
||||||
|
|
||||||
|
part 'messages_state.freezed.dart';
|
||||||
|
part 'messages_state.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class MessagesState with _$MessagesState {
|
||||||
|
const factory MessagesState({
|
||||||
|
// List of messages in the window
|
||||||
|
required IList<MessageState> windowMessages,
|
||||||
|
// Total number of messages
|
||||||
|
required int length,
|
||||||
|
// One past the end of the last element
|
||||||
|
required int windowTail,
|
||||||
|
// The total number of elements to try to keep in 'messages'
|
||||||
|
required int windowCount,
|
||||||
|
// If we should have the tail following the array
|
||||||
|
required bool follow,
|
||||||
|
}) = _MessagesState;
|
||||||
|
|
||||||
|
factory MessagesState.fromJson(dynamic json) =>
|
||||||
|
_$MessagesStateFromJson(json as Map<String, dynamic>);
|
||||||
|
}
|
268
lib/chat/models/messages_state.freezed.dart
Normal file
268
lib/chat/models/messages_state.freezed.dart
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'messages_state.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
|
MessagesState _$MessagesStateFromJson(Map<String, dynamic> json) {
|
||||||
|
return _MessagesState.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$MessagesState {
|
||||||
|
// List of messages in the window
|
||||||
|
IList<MessageState> get windowMessages =>
|
||||||
|
throw _privateConstructorUsedError; // Total number of messages
|
||||||
|
int get length =>
|
||||||
|
throw _privateConstructorUsedError; // One past the end of the last element
|
||||||
|
int get windowTail =>
|
||||||
|
throw _privateConstructorUsedError; // The total number of elements to try to keep in 'messages'
|
||||||
|
int get windowCount =>
|
||||||
|
throw _privateConstructorUsedError; // If we should have the tail following the array
|
||||||
|
bool get follow => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$MessagesStateCopyWith<MessagesState> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $MessagesStateCopyWith<$Res> {
|
||||||
|
factory $MessagesStateCopyWith(
|
||||||
|
MessagesState value, $Res Function(MessagesState) then) =
|
||||||
|
_$MessagesStateCopyWithImpl<$Res, MessagesState>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{IList<MessageState> windowMessages,
|
||||||
|
int length,
|
||||||
|
int windowTail,
|
||||||
|
int windowCount,
|
||||||
|
bool follow});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$MessagesStateCopyWithImpl<$Res, $Val extends MessagesState>
|
||||||
|
implements $MessagesStateCopyWith<$Res> {
|
||||||
|
_$MessagesStateCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? windowMessages = null,
|
||||||
|
Object? length = null,
|
||||||
|
Object? windowTail = null,
|
||||||
|
Object? windowCount = null,
|
||||||
|
Object? follow = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
windowMessages: null == windowMessages
|
||||||
|
? _value.windowMessages
|
||||||
|
: windowMessages // ignore: cast_nullable_to_non_nullable
|
||||||
|
as IList<MessageState>,
|
||||||
|
length: null == length
|
||||||
|
? _value.length
|
||||||
|
: length // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
windowTail: null == windowTail
|
||||||
|
? _value.windowTail
|
||||||
|
: windowTail // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
windowCount: null == windowCount
|
||||||
|
? _value.windowCount
|
||||||
|
: windowCount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
follow: null == follow
|
||||||
|
? _value.follow
|
||||||
|
: follow // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$MessagesStateImplCopyWith<$Res>
|
||||||
|
implements $MessagesStateCopyWith<$Res> {
|
||||||
|
factory _$$MessagesStateImplCopyWith(
|
||||||
|
_$MessagesStateImpl value, $Res Function(_$MessagesStateImpl) then) =
|
||||||
|
__$$MessagesStateImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{IList<MessageState> windowMessages,
|
||||||
|
int length,
|
||||||
|
int windowTail,
|
||||||
|
int windowCount,
|
||||||
|
bool follow});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$MessagesStateImplCopyWithImpl<$Res>
|
||||||
|
extends _$MessagesStateCopyWithImpl<$Res, _$MessagesStateImpl>
|
||||||
|
implements _$$MessagesStateImplCopyWith<$Res> {
|
||||||
|
__$$MessagesStateImplCopyWithImpl(
|
||||||
|
_$MessagesStateImpl _value, $Res Function(_$MessagesStateImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? windowMessages = null,
|
||||||
|
Object? length = null,
|
||||||
|
Object? windowTail = null,
|
||||||
|
Object? windowCount = null,
|
||||||
|
Object? follow = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$MessagesStateImpl(
|
||||||
|
windowMessages: null == windowMessages
|
||||||
|
? _value.windowMessages
|
||||||
|
: windowMessages // ignore: cast_nullable_to_non_nullable
|
||||||
|
as IList<MessageState>,
|
||||||
|
length: null == length
|
||||||
|
? _value.length
|
||||||
|
: length // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
windowTail: null == windowTail
|
||||||
|
? _value.windowTail
|
||||||
|
: windowTail // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
windowCount: null == windowCount
|
||||||
|
? _value.windowCount
|
||||||
|
: windowCount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
follow: null == follow
|
||||||
|
? _value.follow
|
||||||
|
: follow // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$MessagesStateImpl
|
||||||
|
with DiagnosticableTreeMixin
|
||||||
|
implements _MessagesState {
|
||||||
|
const _$MessagesStateImpl(
|
||||||
|
{required this.windowMessages,
|
||||||
|
required this.length,
|
||||||
|
required this.windowTail,
|
||||||
|
required this.windowCount,
|
||||||
|
required this.follow});
|
||||||
|
|
||||||
|
factory _$MessagesStateImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$MessagesStateImplFromJson(json);
|
||||||
|
|
||||||
|
// List of messages in the window
|
||||||
|
@override
|
||||||
|
final IList<MessageState> windowMessages;
|
||||||
|
// Total number of messages
|
||||||
|
@override
|
||||||
|
final int length;
|
||||||
|
// One past the end of the last element
|
||||||
|
@override
|
||||||
|
final int windowTail;
|
||||||
|
// The total number of elements to try to keep in 'messages'
|
||||||
|
@override
|
||||||
|
final int windowCount;
|
||||||
|
// If we should have the tail following the array
|
||||||
|
@override
|
||||||
|
final bool follow;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
|
||||||
|
return 'MessagesState(windowMessages: $windowMessages, length: $length, windowTail: $windowTail, windowCount: $windowCount, follow: $follow)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
super.debugFillProperties(properties);
|
||||||
|
properties
|
||||||
|
..add(DiagnosticsProperty('type', 'MessagesState'))
|
||||||
|
..add(DiagnosticsProperty('windowMessages', windowMessages))
|
||||||
|
..add(DiagnosticsProperty('length', length))
|
||||||
|
..add(DiagnosticsProperty('windowTail', windowTail))
|
||||||
|
..add(DiagnosticsProperty('windowCount', windowCount))
|
||||||
|
..add(DiagnosticsProperty('follow', follow));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$MessagesStateImpl &&
|
||||||
|
const DeepCollectionEquality()
|
||||||
|
.equals(other.windowMessages, windowMessages) &&
|
||||||
|
(identical(other.length, length) || other.length == length) &&
|
||||||
|
(identical(other.windowTail, windowTail) ||
|
||||||
|
other.windowTail == windowTail) &&
|
||||||
|
(identical(other.windowCount, windowCount) ||
|
||||||
|
other.windowCount == windowCount) &&
|
||||||
|
(identical(other.follow, follow) || other.follow == follow));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
const DeepCollectionEquality().hash(windowMessages),
|
||||||
|
length,
|
||||||
|
windowTail,
|
||||||
|
windowCount,
|
||||||
|
follow);
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$MessagesStateImplCopyWith<_$MessagesStateImpl> get copyWith =>
|
||||||
|
__$$MessagesStateImplCopyWithImpl<_$MessagesStateImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$MessagesStateImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _MessagesState implements MessagesState {
|
||||||
|
const factory _MessagesState(
|
||||||
|
{required final IList<MessageState> windowMessages,
|
||||||
|
required final int length,
|
||||||
|
required final int windowTail,
|
||||||
|
required final int windowCount,
|
||||||
|
required final bool follow}) = _$MessagesStateImpl;
|
||||||
|
|
||||||
|
factory _MessagesState.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$MessagesStateImpl.fromJson;
|
||||||
|
|
||||||
|
@override // List of messages in the window
|
||||||
|
IList<MessageState> get windowMessages;
|
||||||
|
@override // Total number of messages
|
||||||
|
int get length;
|
||||||
|
@override // One past the end of the last element
|
||||||
|
int get windowTail;
|
||||||
|
@override // The total number of elements to try to keep in 'messages'
|
||||||
|
int get windowCount;
|
||||||
|
@override // If we should have the tail following the array
|
||||||
|
bool get follow;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$MessagesStateImplCopyWith<_$MessagesStateImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
28
lib/chat/models/messages_state.g.dart
Normal file
28
lib/chat/models/messages_state.g.dart
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'messages_state.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$MessagesStateImpl _$$MessagesStateImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$MessagesStateImpl(
|
||||||
|
windowMessages: IList<MessageState>.fromJson(
|
||||||
|
json['window_messages'], (value) => MessageState.fromJson(value)),
|
||||||
|
length: (json['length'] as num).toInt(),
|
||||||
|
windowTail: (json['window_tail'] as num).toInt(),
|
||||||
|
windowCount: (json['window_count'] as num).toInt(),
|
||||||
|
follow: json['follow'] as bool,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$MessagesStateImplToJson(_$MessagesStateImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'window_messages': instance.windowMessages.toJson(
|
||||||
|
(value) => value.toJson(),
|
||||||
|
),
|
||||||
|
'length': instance.length,
|
||||||
|
'window_tail': instance.windowTail,
|
||||||
|
'window_count': instance.windowCount,
|
||||||
|
'follow': instance.follow,
|
||||||
|
};
|
@ -1 +1,2 @@
|
|||||||
export 'message_state.dart';
|
export 'message_state.dart';
|
||||||
|
export 'messages_state.dart';
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:async_tools/async_tools.dart';
|
import 'package:async_tools/async_tools.dart';
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
|
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
|
||||||
@ -166,8 +167,9 @@ class ChatComponent extends StatelessWidget {
|
|||||||
_messagesCubit.sendTextMessage(messageText: protoMessageText);
|
_messagesCubit.sendTextMessage(messageText: protoMessageText);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSendPressed(types.PartialText message) {
|
void _sendMessage(types.PartialText message) {
|
||||||
final text = message.text;
|
final text = message.text;
|
||||||
|
|
||||||
final replyId = (message.repliedMessage != null)
|
final replyId = (message.repliedMessage != null)
|
||||||
? base64UrlNoPadDecode(message.repliedMessage!.id)
|
? base64UrlNoPadDecode(message.repliedMessage!.id)
|
||||||
: null;
|
: null;
|
||||||
@ -200,6 +202,17 @@ class ChatComponent extends StatelessWidget {
|
|||||||
attachments: attachments ?? []);
|
attachments: attachments ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleSendPressed(types.PartialText message) {
|
||||||
|
final text = message.text;
|
||||||
|
|
||||||
|
if (text.startsWith('/')) {
|
||||||
|
_messagesCubit.runCommand(text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
// void _handleAttachmentPressed() async {
|
// void _handleAttachmentPressed() async {
|
||||||
// //
|
// //
|
||||||
// }
|
// }
|
||||||
@ -211,15 +224,15 @@ class ChatComponent extends StatelessWidget {
|
|||||||
final textTheme = Theme.of(context).textTheme;
|
final textTheme = Theme.of(context).textTheme;
|
||||||
final chatTheme = makeChatTheme(scale, textTheme);
|
final chatTheme = makeChatTheme(scale, textTheme);
|
||||||
|
|
||||||
final messages = _messagesState.asData?.value;
|
final messagesState = _messagesState.asData?.value;
|
||||||
if (messages == null) {
|
if (messagesState == null) {
|
||||||
return _messagesState.buildNotData();
|
return _messagesState.buildNotData();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert protobuf messages to chat messages
|
// Convert protobuf messages to chat messages
|
||||||
final chatMessages = <types.Message>[];
|
final chatMessages = <types.Message>[];
|
||||||
final tsSet = <String>{};
|
final tsSet = <String>{};
|
||||||
for (final message in messages) {
|
for (final message in messagesState.windowMessages) {
|
||||||
final chatMessage = messageStateToChatMessage(message);
|
final chatMessage = messageStateToChatMessage(message);
|
||||||
if (chatMessage == null) {
|
if (chatMessage == null) {
|
||||||
continue;
|
continue;
|
||||||
@ -228,12 +241,17 @@ class ChatComponent extends StatelessWidget {
|
|||||||
if (!tsSet.add(chatMessage.id)) {
|
if (!tsSet.add(chatMessage.id)) {
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print('duplicate id found: ${chatMessage.id}:\n'
|
print('duplicate id found: ${chatMessage.id}:\n'
|
||||||
'Messages:\n$messages\n'
|
'Messages:\n${messagesState.windowMessages}\n'
|
||||||
'ChatMessages:\n$chatMessages');
|
'ChatMessages:\n$chatMessages');
|
||||||
assert(false, 'should not have duplicate id');
|
assert(false, 'should not have duplicate id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final isLastPage =
|
||||||
|
(messagesState.windowTail - messagesState.windowMessages.length) <= 0;
|
||||||
|
final follow = messagesState.windowTail == 0 ||
|
||||||
|
messagesState.windowTail == messagesState.length; xxx finish calculating pagination and get scroll position here somehow
|
||||||
|
|
||||||
return DefaultTextStyle(
|
return DefaultTextStyle(
|
||||||
style: textTheme.bodySmall!,
|
style: textTheme.bodySmall!,
|
||||||
child: Align(
|
child: Align(
|
||||||
@ -272,9 +290,17 @@ class ChatComponent extends StatelessWidget {
|
|||||||
decoration: const BoxDecoration(),
|
decoration: const BoxDecoration(),
|
||||||
child: Chat(
|
child: Chat(
|
||||||
theme: chatTheme,
|
theme: chatTheme,
|
||||||
// emojiEnlargementBehavior:
|
|
||||||
// EmojiEnlargementBehavior.multi,
|
|
||||||
messages: chatMessages,
|
messages: chatMessages,
|
||||||
|
onEndReached: () async {
|
||||||
|
final tail = await _messagesCubit.setWindow(
|
||||||
|
tail: max(
|
||||||
|
0,
|
||||||
|
(messagesState.windowTail -
|
||||||
|
(messagesState.windowCount ~/ 2))),
|
||||||
|
count: messagesState.windowCount,
|
||||||
|
follow: follow);
|
||||||
|
},
|
||||||
|
isLastPage: isLastPage,
|
||||||
//onAttachmentPressed: _handleAttachmentPressed,
|
//onAttachmentPressed: _handleAttachmentPressed,
|
||||||
//onMessageTap: _handleMessageTap,
|
//onMessageTap: _handleMessageTap,
|
||||||
//onPreviewDataFetched: _handlePreviewDataFetched,
|
//onPreviewDataFetched: _handlePreviewDataFetched,
|
||||||
|
@ -609,6 +609,29 @@ ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) {
|
|||||||
final themeData = ThemeData.from(
|
final themeData = ThemeData.from(
|
||||||
colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true);
|
colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true);
|
||||||
return themeData.copyWith(
|
return themeData.copyWith(
|
||||||
|
scrollbarTheme: themeData.scrollbarTheme.copyWith(
|
||||||
|
thumbColor: WidgetStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(WidgetState.pressed)) {
|
||||||
|
return scaleScheme.primaryScale.border;
|
||||||
|
} else if (states.contains(WidgetState.hovered)) {
|
||||||
|
return scaleScheme.primaryScale.hoverBorder;
|
||||||
|
}
|
||||||
|
return scaleScheme.primaryScale.subtleBorder;
|
||||||
|
}), trackColor: WidgetStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(WidgetState.pressed)) {
|
||||||
|
return scaleScheme.primaryScale.activeElementBackground;
|
||||||
|
} else if (states.contains(WidgetState.hovered)) {
|
||||||
|
return scaleScheme.primaryScale.hoverElementBackground;
|
||||||
|
}
|
||||||
|
return scaleScheme.primaryScale.elementBackground;
|
||||||
|
}), trackBorderColor: WidgetStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(WidgetState.pressed)) {
|
||||||
|
return scaleScheme.primaryScale.subtleBorder;
|
||||||
|
} else if (states.contains(WidgetState.hovered)) {
|
||||||
|
return scaleScheme.primaryScale.subtleBorder;
|
||||||
|
}
|
||||||
|
return scaleScheme.primaryScale.subtleBorder;
|
||||||
|
})),
|
||||||
bottomSheetTheme: themeData.bottomSheetTheme.copyWith(
|
bottomSheetTheme: themeData.bottomSheetTheme.copyWith(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
modalElevation: 0,
|
modalElevation: 0,
|
||||||
|
18
lib/tools/misc.dart
Normal file
18
lib/tools/misc.dart
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
extension StringExt on String {
|
||||||
|
(String, String?) splitOnce(Pattern p) {
|
||||||
|
final pos = indexOf(p);
|
||||||
|
if (pos == -1) {
|
||||||
|
return (this, null);
|
||||||
|
}
|
||||||
|
final rest = substring(pos);
|
||||||
|
var offset = 0;
|
||||||
|
while (true) {
|
||||||
|
final match = p.matchAsPrefix(rest, offset);
|
||||||
|
if (match == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
offset = match.end;
|
||||||
|
}
|
||||||
|
return (substring(0, pos), rest.substring(offset));
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,9 @@ const Map<String, LogLevel> _blocChangeLogLevels = {
|
|||||||
'ConnectionStateCubit': LogLevel.off,
|
'ConnectionStateCubit': LogLevel.off,
|
||||||
'ActiveSingleContactChatBlocMapCubit': LogLevel.off,
|
'ActiveSingleContactChatBlocMapCubit': LogLevel.off,
|
||||||
'ActiveConversationsBlocMapCubit': LogLevel.off,
|
'ActiveConversationsBlocMapCubit': LogLevel.off,
|
||||||
'DHTShortArrayCubit<Message>': LogLevel.off,
|
|
||||||
'PersistentQueueCubit<Message>': LogLevel.off,
|
'PersistentQueueCubit<Message>': LogLevel.off,
|
||||||
|
'TableDBArrayProtobufCubit<ReconciledMessage>': LogLevel.off,
|
||||||
|
'DHTLogCubit<Message>': LogLevel.off,
|
||||||
'SingleContactMessagesCubit': LogLevel.off,
|
'SingleContactMessagesCubit': LogLevel.off,
|
||||||
};
|
};
|
||||||
const Map<String, LogLevel> _blocCreateCloseLogLevels = {};
|
const Map<String, LogLevel> _blocCreateCloseLogLevels = {};
|
||||||
|
@ -2,6 +2,7 @@ export 'animations.dart';
|
|||||||
export 'enter_password.dart';
|
export 'enter_password.dart';
|
||||||
export 'enter_pin.dart';
|
export 'enter_pin.dart';
|
||||||
export 'loggy.dart';
|
export 'loggy.dart';
|
||||||
|
export 'misc.dart';
|
||||||
export 'phono_byte.dart';
|
export 'phono_byte.dart';
|
||||||
export 'pop_control.dart';
|
export 'pop_control.dart';
|
||||||
export 'responsive.dart';
|
export 'responsive.dart';
|
||||||
|
@ -14,22 +14,25 @@ import '../../../veilid_support.dart';
|
|||||||
class TableDBArrayProtobufStateData<T extends GeneratedMessage>
|
class TableDBArrayProtobufStateData<T extends GeneratedMessage>
|
||||||
extends Equatable {
|
extends Equatable {
|
||||||
const TableDBArrayProtobufStateData(
|
const TableDBArrayProtobufStateData(
|
||||||
{required this.elements,
|
{required this.windowElements,
|
||||||
required this.tail,
|
required this.length,
|
||||||
required this.count,
|
required this.windowTail,
|
||||||
|
required this.windowCount,
|
||||||
required this.follow});
|
required this.follow});
|
||||||
// The view of the elements in the dhtlog
|
// The view of the elements in the dhtlog
|
||||||
// Span is from [tail-length, tail)
|
// Span is from [tail-length, tail)
|
||||||
final IList<T> elements;
|
final IList<T> windowElements;
|
||||||
|
// The length of the entire array
|
||||||
|
final int length;
|
||||||
// One past the end of the last element
|
// One past the end of the last element
|
||||||
final int tail;
|
final int windowTail;
|
||||||
// The total number of elements to try to keep in 'elements'
|
// The total number of elements to try to keep in 'elements'
|
||||||
final int count;
|
final int windowCount;
|
||||||
// If we should have the tail following the array
|
// If we should have the tail following the array
|
||||||
final bool follow;
|
final bool follow;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [elements, tail, count, follow];
|
List<Object?> get props => [windowElements, windowTail, windowCount, follow];
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef TableDBArrayProtobufState<T extends GeneratedMessage>
|
typedef TableDBArrayProtobufState<T extends GeneratedMessage>
|
||||||
@ -99,7 +102,10 @@ class TableDBArrayProtobufCubit<T extends GeneratedMessage>
|
|||||||
}
|
}
|
||||||
final elements = avElements.asData!.value;
|
final elements = avElements.asData!.value;
|
||||||
emit(AsyncValue.data(TableDBArrayProtobufStateData(
|
emit(AsyncValue.data(TableDBArrayProtobufStateData(
|
||||||
elements: elements, tail: _tail, count: _count, follow: _follow)));
|
windowElements: elements,
|
||||||
|
windowTail: _tail,
|
||||||
|
windowCount: _count,
|
||||||
|
follow: _follow)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<AsyncValue<IList<T>>> _loadElements(
|
Future<AsyncValue<IList<T>>> _loadElements(
|
||||||
|
Loading…
Reference in New Issue
Block a user