mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2024-10-01 06:55:46 -04: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:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../tools/tools.dart';
|
||||
import '../models/models.dart';
|
||||
import 'reconciliation/reconciliation.dart';
|
||||
|
||||
@ -42,7 +44,7 @@ class RenderStateElement {
|
||||
bool sentOffline;
|
||||
}
|
||||
|
||||
typedef SingleContactMessagesState = AsyncValue<IList<MessageState>>;
|
||||
typedef SingleContactMessagesState = AsyncValue<MessagesState>;
|
||||
|
||||
// Cubit that processes single-contact chats
|
||||
// Builds the reconciled chat record from the local and remote conversation keys
|
||||
@ -60,6 +62,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
_localMessagesRecordKey = localMessagesRecordKey,
|
||||
_remoteConversationRecordKey = remoteConversationRecordKey,
|
||||
_remoteMessagesRecordKey = remoteMessagesRecordKey,
|
||||
_commandController = StreamController(),
|
||||
super(const AsyncValue.loading()) {
|
||||
// Async Init
|
||||
_initWait.add(_init);
|
||||
@ -69,6 +72,8 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
Future<void> close() async {
|
||||
await _initWait();
|
||||
|
||||
await _commandController.close();
|
||||
await _commandRunnerFut;
|
||||
await _unsentMessagesQueue.close();
|
||||
await _sentSubscription?.cancel();
|
||||
await _rcvdSubscription?.cancel();
|
||||
@ -99,6 +104,9 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
|
||||
// Remote messages key
|
||||
await _initRcvdMessagesCubit();
|
||||
|
||||
// Command execution background process
|
||||
_commandRunnerFut = Future.delayed(Duration.zero, _commandRunner);
|
||||
}
|
||||
|
||||
// Make crypto
|
||||
@ -191,6 +199,34 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
_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
|
||||
|
||||
@ -220,9 +256,6 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
|
||||
_reconciliation.reconcileMessages(
|
||||
_remoteIdentityPublicKey, rcvdMessages, _rcvdMessagesCubit!);
|
||||
|
||||
// Update the view
|
||||
_renderState();
|
||||
}
|
||||
|
||||
// Called when the reconciled messages window gets a change
|
||||
@ -296,7 +329,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
|
||||
final renderedElements = <RenderStateElement>[];
|
||||
|
||||
for (final m in reconciledMessages.elements) {
|
||||
for (final m in reconciledMessages.windowElements) {
|
||||
final isLocal = m.content.author.toVeilid() ==
|
||||
_activeAccountInfo.localAccount.identityMaster
|
||||
.identityPublicTypedKey();
|
||||
@ -316,7 +349,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
}
|
||||
|
||||
// Render the state
|
||||
final renderedState = renderedElements
|
||||
final messages = renderedElements
|
||||
.map((x) => MessageState(
|
||||
content: x.message,
|
||||
sentTimestamp: Timestamp.fromInt64(x.message.timestamp),
|
||||
@ -325,7 +358,12 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
.toIList();
|
||||
|
||||
// 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}) {
|
||||
@ -344,6 +382,12 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
_renderState();
|
||||
}
|
||||
|
||||
Future<void> _commandRunner() async {
|
||||
await for (final command in _commandController.stream) {
|
||||
await command();
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Static utility functions
|
||||
|
||||
@ -383,4 +427,6 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
StreamSubscription<DHTLogBusyState<proto.Message>>? _rcvdSubscription;
|
||||
StreamSubscription<TableDBArrayProtobufBusyState<proto.ReconciledMessage>>?
|
||||
_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 'messages_state.dart';
|
||||
|
@ -1,8 +1,9 @@
|
||||
import 'dart:typed_data';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
|
||||
@ -166,8 +167,9 @@ class ChatComponent extends StatelessWidget {
|
||||
_messagesCubit.sendTextMessage(messageText: protoMessageText);
|
||||
}
|
||||
|
||||
void _handleSendPressed(types.PartialText message) {
|
||||
void _sendMessage(types.PartialText message) {
|
||||
final text = message.text;
|
||||
|
||||
final replyId = (message.repliedMessage != null)
|
||||
? base64UrlNoPadDecode(message.repliedMessage!.id)
|
||||
: null;
|
||||
@ -200,6 +202,17 @@ class ChatComponent extends StatelessWidget {
|
||||
attachments: attachments ?? []);
|
||||
}
|
||||
|
||||
void _handleSendPressed(types.PartialText message) {
|
||||
final text = message.text;
|
||||
|
||||
if (text.startsWith('/')) {
|
||||
_messagesCubit.runCommand(text);
|
||||
return;
|
||||
}
|
||||
|
||||
_sendMessage(message);
|
||||
}
|
||||
|
||||
// void _handleAttachmentPressed() async {
|
||||
// //
|
||||
// }
|
||||
@ -211,15 +224,15 @@ class ChatComponent extends StatelessWidget {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
final chatTheme = makeChatTheme(scale, textTheme);
|
||||
|
||||
final messages = _messagesState.asData?.value;
|
||||
if (messages == null) {
|
||||
final messagesState = _messagesState.asData?.value;
|
||||
if (messagesState == null) {
|
||||
return _messagesState.buildNotData();
|
||||
}
|
||||
|
||||
// Convert protobuf messages to chat messages
|
||||
final chatMessages = <types.Message>[];
|
||||
final tsSet = <String>{};
|
||||
for (final message in messages) {
|
||||
for (final message in messagesState.windowMessages) {
|
||||
final chatMessage = messageStateToChatMessage(message);
|
||||
if (chatMessage == null) {
|
||||
continue;
|
||||
@ -228,12 +241,17 @@ class ChatComponent extends StatelessWidget {
|
||||
if (!tsSet.add(chatMessage.id)) {
|
||||
// ignore: avoid_print
|
||||
print('duplicate id found: ${chatMessage.id}:\n'
|
||||
'Messages:\n$messages\n'
|
||||
'Messages:\n${messagesState.windowMessages}\n'
|
||||
'ChatMessages:\n$chatMessages');
|
||||
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(
|
||||
style: textTheme.bodySmall!,
|
||||
child: Align(
|
||||
@ -272,9 +290,17 @@ class ChatComponent extends StatelessWidget {
|
||||
decoration: const BoxDecoration(),
|
||||
child: Chat(
|
||||
theme: chatTheme,
|
||||
// emojiEnlargementBehavior:
|
||||
// EmojiEnlargementBehavior.multi,
|
||||
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,
|
||||
//onMessageTap: _handleMessageTap,
|
||||
//onPreviewDataFetched: _handlePreviewDataFetched,
|
||||
|
@ -609,6 +609,29 @@ ThemeData radixGenerator(Brightness brightness, RadixThemeColor themeColor) {
|
||||
final themeData = ThemeData.from(
|
||||
colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true);
|
||||
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(
|
||||
elevation: 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,
|
||||
'ActiveSingleContactChatBlocMapCubit': LogLevel.off,
|
||||
'ActiveConversationsBlocMapCubit': LogLevel.off,
|
||||
'DHTShortArrayCubit<Message>': LogLevel.off,
|
||||
'PersistentQueueCubit<Message>': LogLevel.off,
|
||||
'TableDBArrayProtobufCubit<ReconciledMessage>': LogLevel.off,
|
||||
'DHTLogCubit<Message>': LogLevel.off,
|
||||
'SingleContactMessagesCubit': LogLevel.off,
|
||||
};
|
||||
const Map<String, LogLevel> _blocCreateCloseLogLevels = {};
|
||||
|
@ -2,6 +2,7 @@ export 'animations.dart';
|
||||
export 'enter_password.dart';
|
||||
export 'enter_pin.dart';
|
||||
export 'loggy.dart';
|
||||
export 'misc.dart';
|
||||
export 'phono_byte.dart';
|
||||
export 'pop_control.dart';
|
||||
export 'responsive.dart';
|
||||
|
@ -14,22 +14,25 @@ import '../../../veilid_support.dart';
|
||||
class TableDBArrayProtobufStateData<T extends GeneratedMessage>
|
||||
extends Equatable {
|
||||
const TableDBArrayProtobufStateData(
|
||||
{required this.elements,
|
||||
required this.tail,
|
||||
required this.count,
|
||||
{required this.windowElements,
|
||||
required this.length,
|
||||
required this.windowTail,
|
||||
required this.windowCount,
|
||||
required this.follow});
|
||||
// The view of the elements in the dhtlog
|
||||
// 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
|
||||
final int tail;
|
||||
final int windowTail;
|
||||
// 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
|
||||
final bool follow;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [elements, tail, count, follow];
|
||||
List<Object?> get props => [windowElements, windowTail, windowCount, follow];
|
||||
}
|
||||
|
||||
typedef TableDBArrayProtobufState<T extends GeneratedMessage>
|
||||
@ -99,7 +102,10 @@ class TableDBArrayProtobufCubit<T extends GeneratedMessage>
|
||||
}
|
||||
final elements = avElements.asData!.value;
|
||||
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(
|
||||
|
Loading…
Reference in New Issue
Block a user