mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2024-10-01 06:55:46 -04:00
message length limit
This commit is contained in:
parent
9dfb8c3f71
commit
94988718e8
@ -36,18 +36,19 @@
|
|||||||
"name": "Name",
|
"name": "Name",
|
||||||
"pronouns": "Pronouns",
|
"pronouns": "Pronouns",
|
||||||
"remove_account": "Remove Account",
|
"remove_account": "Remove Account",
|
||||||
"delete_identity": "Delete Identity",
|
"destroy_account": "Destroy Account",
|
||||||
"remove_account_confirm": "Confirm Account Removal",
|
"remove_account_confirm": "Confirm Account Removal",
|
||||||
"remove_account_description": "Remove account from this device only",
|
"remove_account_description": "Remove account from this device only",
|
||||||
"remove_account_confirm_message": " • Your account will be removed from this device ONLY\n • Your identity will remain recoverable with the recovery key\n • Your messages and contacts will remain available on other devices\n",
|
"remove_account_confirm_message": " • Your account will be removed from this device ONLY\n • Your identity will remain recoverable with the recovery key\n • Your messages and contacts will remain available on other devices\n",
|
||||||
"delete_identity_description": "Delete identity from all devices everywhere",
|
"destroy_account_confirm": "Confirm Account Destruction",
|
||||||
"delete_identity_confirm_message": "This action is PERMANENT, and your identity will no longer be recoverable with the recovery key. Restoring from backups will not recover your account!",
|
"destroy_account_description": "Destroy account, removing it completely from all devices everywhere",
|
||||||
"delete_identity_confirm_message_details": "You will lose access to:\n • Your entire message history\n • Your contacts\n • This will not remove your messages you have sent from other people's devices\n",
|
"destroy_account_confirm_message": "This action is PERMANENT, and your VeilidChat account will no longer be recoverable with the recovery key. Restoring from backups will not recover your account!",
|
||||||
|
"destroy_account_confirm_message_details": "You will lose access to:\n • Your entire message history\n • Your contacts\n • This will not remove your messages you have sent from other people's devices\n",
|
||||||
"confirm_are_you_sure": "Are you sure you want to do this?",
|
"confirm_are_you_sure": "Are you sure you want to do this?",
|
||||||
"failed_to_remove": "Failed to remove account.\n\nTry again when you have a more stable network connection.",
|
"failed_to_remove": "Failed to remove account.\n\nTry again when you have a more stable network connection.",
|
||||||
"failed_to_delete": "Failed to delete identity.\n\nTry again when you have a more stable network connection.",
|
"failed_to_destroy": "Failed to destroy account.\n\nTry again when you have a more stable network connection.",
|
||||||
"account_removed": "Account removed successfully",
|
"account_removed": "Account removed successfully",
|
||||||
"identity_deleted": "Identity deleted successfully"
|
"account_destroyed": "Account destroyed successfully"
|
||||||
},
|
},
|
||||||
"show_recovery_key_page": {
|
"show_recovery_key_page": {
|
||||||
"titlebar": "Save Recovery Key",
|
"titlebar": "Save Recovery Key",
|
||||||
@ -103,7 +104,8 @@
|
|||||||
},
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"start_a_conversation": "Start A Conversation",
|
"start_a_conversation": "Start A Conversation",
|
||||||
"say_something": "Say Something"
|
"say_something": "Say Something",
|
||||||
|
"message_too_long": "Message too long"
|
||||||
},
|
},
|
||||||
"create_invitation_dialog": {
|
"create_invitation_dialog": {
|
||||||
"title": "Create Contact Invitation",
|
"title": "Create Contact Invitation",
|
||||||
|
@ -7,7 +7,7 @@ import '../../../proto/proto.dart' as proto;
|
|||||||
import '../../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
import '../models/models.dart';
|
import '../models/models.dart';
|
||||||
|
|
||||||
const String veilidChatAccountKey = 'com.veilid.veilidchat';
|
const String veilidChatApplicationId = 'com.veilid.veilidchat';
|
||||||
|
|
||||||
enum AccountRepositoryChange { localAccounts, userLogins, activeLocalAccount }
|
enum AccountRepositoryChange { localAccounts, userLogins, activeLocalAccount }
|
||||||
|
|
||||||
@ -194,6 +194,33 @@ class AccountRepository {
|
|||||||
/// Recover an account with the master identity secret
|
/// Recover an account with the master identity secret
|
||||||
|
|
||||||
/// Delete an account from all devices
|
/// Delete an account from all devices
|
||||||
|
Future<bool> destroyAccount(TypedKey superIdentityRecordKey,
|
||||||
|
OwnedDHTRecordPointer accountRecord) async {
|
||||||
|
// Get which local account we want to fetch the profile for
|
||||||
|
final localAccount = fetchLocalAccount(superIdentityRecordKey);
|
||||||
|
if (localAccount == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if we've logged into this account or if it is locked
|
||||||
|
final userLogin = fetchUserLogin(superIdentityRecordKey);
|
||||||
|
if (userLogin == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final success = await localAccount.superIdentity.currentInstance
|
||||||
|
.removeAccount(
|
||||||
|
superRecordKey: localAccount.superIdentity.recordKey,
|
||||||
|
secretKey: userLogin.identitySecret.value,
|
||||||
|
applicationId: veilidChatApplicationId,
|
||||||
|
removeAccountCallback: (accountRecordInfos) async =>
|
||||||
|
accountRecordInfos.singleOrNull);
|
||||||
|
if (!success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return deleteLocalAccount(superIdentityRecordKey, accountRecord);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> switchToAccount(TypedKey? superIdentityRecordKey) async {
|
Future<void> switchToAccount(TypedKey? superIdentityRecordKey) async {
|
||||||
final activeLocalAccount = await _activeLocalAccount.get();
|
final activeLocalAccount = await _activeLocalAccount.get();
|
||||||
@ -231,7 +258,7 @@ class AccountRepository {
|
|||||||
await superIdentity.currentInstance.addAccount(
|
await superIdentity.currentInstance.addAccount(
|
||||||
superRecordKey: superIdentity.recordKey,
|
superRecordKey: superIdentity.recordKey,
|
||||||
secretKey: identitySecret,
|
secretKey: identitySecret,
|
||||||
accountKey: veilidChatAccountKey,
|
applicationId: veilidChatApplicationId,
|
||||||
createAccountCallback: (parent) async {
|
createAccountCallback: (parent) async {
|
||||||
// Make empty contact list
|
// Make empty contact list
|
||||||
log.debug('Creating contacts list');
|
log.debug('Creating contacts list');
|
||||||
@ -305,7 +332,7 @@ class AccountRepository {
|
|||||||
.readAccount(
|
.readAccount(
|
||||||
superRecordKey: superIdentity.recordKey,
|
superRecordKey: superIdentity.recordKey,
|
||||||
secretKey: identitySecret,
|
secretKey: identitySecret,
|
||||||
accountKey: veilidChatAccountKey);
|
applicationId: veilidChatApplicationId);
|
||||||
if (accountRecordInfoList.length > 1) {
|
if (accountRecordInfoList.length > 1) {
|
||||||
throw IdentityException.limitExceeded;
|
throw IdentityException.limitExceeded;
|
||||||
} else if (accountRecordInfoList.isEmpty) {
|
} else if (accountRecordInfoList.isEmpty) {
|
||||||
|
@ -134,8 +134,70 @@ class _EditAccountPageState extends State<EditAccountPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onDeleteIdentity() async {
|
Future<void> _onDestroyAccount() async {
|
||||||
//
|
final confirmed = await StyledDialog.show<bool>(
|
||||||
|
context: context,
|
||||||
|
title: translate('edit_account_page.destroy_account_confirm'),
|
||||||
|
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
|
Text(translate('edit_account_page.destroy_account_confirm_message'))
|
||||||
|
.paddingLTRB(24, 24, 24, 0),
|
||||||
|
Text(translate(
|
||||||
|
'edit_account_page.destroy_account_confirm_message_details'))
|
||||||
|
.paddingLTRB(24, 24, 24, 0),
|
||||||
|
Text(translate('edit_account_page.confirm_are_you_sure'))
|
||||||
|
.paddingAll(8),
|
||||||
|
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(false);
|
||||||
|
},
|
||||||
|
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
|
const Icon(Icons.cancel, size: 16).paddingLTRB(0, 0, 4, 0),
|
||||||
|
Text(translate('button.no_cancel')).paddingLTRB(0, 0, 4, 0)
|
||||||
|
])),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
},
|
||||||
|
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
|
const Icon(Icons.check, size: 16).paddingLTRB(0, 0, 4, 0),
|
||||||
|
Text(translate('button.yes_proceed')).paddingLTRB(0, 0, 4, 0)
|
||||||
|
]))
|
||||||
|
]).paddingAll(24)
|
||||||
|
]));
|
||||||
|
if (confirmed != null && confirmed && mounted) {
|
||||||
|
// dismiss the keyboard by unfocusing the textfield
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
|
||||||
|
try {
|
||||||
|
setState(() {
|
||||||
|
_isInAsyncCall = true;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
final success = await AccountRepository.instance.destroyAccount(
|
||||||
|
widget.superIdentityRecordKey, widget.accountRecord);
|
||||||
|
if (success && mounted) {
|
||||||
|
showInfoToast(
|
||||||
|
context, translate('edit_account_page.account_destroyed'));
|
||||||
|
GoRouterHelper(context).pop();
|
||||||
|
} else if (mounted) {
|
||||||
|
showErrorToast(
|
||||||
|
context, translate('edit_account_page.failed_to_destroy'));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isInAsyncCall = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} on Exception catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
await showErrorModal(
|
||||||
|
context, translate('new_account_page.error'), 'Exception: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSubmit(GlobalKey<FormBuilderState> formKey) async {
|
Future<void> _onSubmit(GlobalKey<FormBuilderState> formKey) async {
|
||||||
@ -234,13 +296,13 @@ class _EditAccountPageState extends State<EditAccountPage> {
|
|||||||
Text(translate('edit_account_page.remove_account'))
|
Text(translate('edit_account_page.remove_account'))
|
||||||
.paddingLTRB(0, 0, 4, 0)
|
.paddingLTRB(0, 0, 4, 0)
|
||||||
])).paddingLTRB(0, 8, 0, 24),
|
])).paddingLTRB(0, 8, 0, 24),
|
||||||
Text(translate('edit_account_page.delete_identity_description')),
|
Text(translate('edit_account_page.destroy_account_description')),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: _onDeleteIdentity,
|
onPressed: _onDestroyAccount,
|
||||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||||
const Icon(Icons.person_off, size: 16)
|
const Icon(Icons.person_off, size: 16)
|
||||||
.paddingLTRB(0, 0, 4, 0),
|
.paddingLTRB(0, 0, 4, 0),
|
||||||
Text(translate('edit_account_page.delete_identity'))
|
Text(translate('edit_account_page.destroy_account'))
|
||||||
.paddingLTRB(0, 0, 4, 0)
|
.paddingLTRB(0, 0, 4, 0)
|
||||||
])).paddingLTRB(0, 8, 0, 24)
|
])).paddingLTRB(0, 8, 0, 24)
|
||||||
]).paddingSymmetric(horizontal: 24, vertical: 8))
|
]).paddingSymmetric(horizontal: 24, vertical: 8))
|
||||||
|
@ -42,6 +42,7 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
|
|||||||
super(ChatComponentState(
|
super(ChatComponentState(
|
||||||
chatKey: GlobalKey<ChatState>(),
|
chatKey: GlobalKey<ChatState>(),
|
||||||
scrollController: AutoScrollController(),
|
scrollController: AutoScrollController(),
|
||||||
|
textEditingController: InputTextFieldController(),
|
||||||
localUser: null,
|
localUser: null,
|
||||||
remoteUsers: const IMap.empty(),
|
remoteUsers: const IMap.empty(),
|
||||||
historicalRemoteUsers: const IMap.empty(),
|
historicalRemoteUsers: const IMap.empty(),
|
||||||
|
@ -97,11 +97,13 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
// Initialize everything
|
// Initialize everything
|
||||||
Future<void> _init() async {
|
Future<void> _init() async {
|
||||||
_unsentMessagesQueue = PersistentQueue<proto.Message>(
|
_unsentMessagesQueue = PersistentQueue<proto.Message>(
|
||||||
table: 'SingleContactUnsentMessages',
|
table: 'SingleContactUnsentMessages',
|
||||||
key: _remoteConversationRecordKey.toString(),
|
key: _remoteConversationRecordKey.toString(),
|
||||||
fromBuffer: proto.Message.fromBuffer,
|
fromBuffer: proto.Message.fromBuffer,
|
||||||
closure: _processUnsentMessages,
|
closure: _processUnsentMessages,
|
||||||
);
|
onError: (e, sp) {
|
||||||
|
log.error('Exception while processing unsent messages: $e\n$sp\n');
|
||||||
|
});
|
||||||
|
|
||||||
// Make crypto
|
// Make crypto
|
||||||
await _initCrypto();
|
await _initCrypto();
|
||||||
@ -297,16 +299,23 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
proto.Message? previousMessage;
|
proto.Message? previousMessage;
|
||||||
final processedMessages = messages.toList();
|
final processedMessages = messages.toList();
|
||||||
for (final message in processedMessages) {
|
for (final message in processedMessages) {
|
||||||
await _processMessageToSend(message, previousMessage);
|
try {
|
||||||
previousMessage = message;
|
await _processMessageToSend(message, previousMessage);
|
||||||
|
previousMessage = message;
|
||||||
|
} on Exception catch (e) {
|
||||||
|
log.error('Exception processing unsent message: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// _sendingMessages = messages;
|
// _sendingMessages = messages;
|
||||||
|
|
||||||
// _renderState();
|
// _renderState();
|
||||||
|
try {
|
||||||
await _sentMessagesCubit!.operateAppendEventual((writer) =>
|
await _sentMessagesCubit!.operateAppendEventual((writer) =>
|
||||||
writer.addAll(messages.map((m) => m.writeToBuffer()).toList()));
|
writer.addAll(messages.map((m) => m.writeToBuffer()).toList()));
|
||||||
|
} on Exception catch (e) {
|
||||||
|
log.error('Exception appending unsent messages: $e');
|
||||||
|
}
|
||||||
|
|
||||||
// _sendingMessages = const IList.empty();
|
// _sendingMessages = const IList.empty();
|
||||||
}
|
}
|
||||||
@ -403,6 +412,10 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||||||
..author = _accountInfo.identityTypedPublicKey.toProto()
|
..author = _accountInfo.identityTypedPublicKey.toProto()
|
||||||
..timestamp = Veilid.instance.now().toInt64();
|
..timestamp = Veilid.instance.now().toInt64();
|
||||||
|
|
||||||
|
if ((message.writeToBuffer().lengthInBytes + 256) > 4096) {
|
||||||
|
throw const FormatException('message is too long');
|
||||||
|
}
|
||||||
|
|
||||||
// Put in the queue
|
// Put in the queue
|
||||||
_unsentMessagesQueue.addSync(message);
|
_unsentMessagesQueue.addSync(message);
|
||||||
|
|
||||||
|
@ -2,7 +2,8 @@ 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/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_chat_types/flutter_chat_types.dart' show Message, User;
|
import 'package:flutter_chat_types/flutter_chat_types.dart' show Message, User;
|
||||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart' show ChatState;
|
import 'package:flutter_chat_ui/flutter_chat_ui.dart'
|
||||||
|
show ChatState, InputTextFieldController;
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||||
import 'package:veilid_support/veilid_support.dart';
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
@ -19,6 +20,8 @@ class ChatComponentState with _$ChatComponentState {
|
|||||||
required GlobalKey<ChatState> chatKey,
|
required GlobalKey<ChatState> chatKey,
|
||||||
// ScrollController for the chat
|
// ScrollController for the chat
|
||||||
required AutoScrollController scrollController,
|
required AutoScrollController scrollController,
|
||||||
|
// TextEditingController for the chat
|
||||||
|
required InputTextFieldController textEditingController,
|
||||||
// Local user
|
// Local user
|
||||||
required User? localUser,
|
required User? localUser,
|
||||||
// Active remote users
|
// Active remote users
|
||||||
|
@ -20,6 +20,8 @@ mixin _$ChatComponentState {
|
|||||||
GlobalKey<ChatState> get chatKey =>
|
GlobalKey<ChatState> get chatKey =>
|
||||||
throw _privateConstructorUsedError; // ScrollController for the chat
|
throw _privateConstructorUsedError; // ScrollController for the chat
|
||||||
AutoScrollController get scrollController =>
|
AutoScrollController get scrollController =>
|
||||||
|
throw _privateConstructorUsedError; // TextEditingController for the chat
|
||||||
|
InputTextFieldController get textEditingController =>
|
||||||
throw _privateConstructorUsedError; // Local user
|
throw _privateConstructorUsedError; // Local user
|
||||||
User? get localUser =>
|
User? get localUser =>
|
||||||
throw _privateConstructorUsedError; // Active remote users
|
throw _privateConstructorUsedError; // Active remote users
|
||||||
@ -47,6 +49,7 @@ abstract class $ChatComponentStateCopyWith<$Res> {
|
|||||||
$Res call(
|
$Res call(
|
||||||
{GlobalKey<ChatState> chatKey,
|
{GlobalKey<ChatState> chatKey,
|
||||||
AutoScrollController scrollController,
|
AutoScrollController scrollController,
|
||||||
|
InputTextFieldController textEditingController,
|
||||||
User? localUser,
|
User? localUser,
|
||||||
IMap<Typed<FixedEncodedString43>, User> remoteUsers,
|
IMap<Typed<FixedEncodedString43>, User> remoteUsers,
|
||||||
IMap<Typed<FixedEncodedString43>, User> historicalRemoteUsers,
|
IMap<Typed<FixedEncodedString43>, User> historicalRemoteUsers,
|
||||||
@ -72,6 +75,7 @@ class _$ChatComponentStateCopyWithImpl<$Res, $Val extends ChatComponentState>
|
|||||||
$Res call({
|
$Res call({
|
||||||
Object? chatKey = null,
|
Object? chatKey = null,
|
||||||
Object? scrollController = null,
|
Object? scrollController = null,
|
||||||
|
Object? textEditingController = null,
|
||||||
Object? localUser = freezed,
|
Object? localUser = freezed,
|
||||||
Object? remoteUsers = null,
|
Object? remoteUsers = null,
|
||||||
Object? historicalRemoteUsers = null,
|
Object? historicalRemoteUsers = null,
|
||||||
@ -88,6 +92,10 @@ class _$ChatComponentStateCopyWithImpl<$Res, $Val extends ChatComponentState>
|
|||||||
? _value.scrollController
|
? _value.scrollController
|
||||||
: scrollController // ignore: cast_nullable_to_non_nullable
|
: scrollController // ignore: cast_nullable_to_non_nullable
|
||||||
as AutoScrollController,
|
as AutoScrollController,
|
||||||
|
textEditingController: null == textEditingController
|
||||||
|
? _value.textEditingController
|
||||||
|
: textEditingController // ignore: cast_nullable_to_non_nullable
|
||||||
|
as InputTextFieldController,
|
||||||
localUser: freezed == localUser
|
localUser: freezed == localUser
|
||||||
? _value.localUser
|
? _value.localUser
|
||||||
: localUser // ignore: cast_nullable_to_non_nullable
|
: localUser // ignore: cast_nullable_to_non_nullable
|
||||||
@ -136,6 +144,7 @@ abstract class _$$ChatComponentStateImplCopyWith<$Res>
|
|||||||
$Res call(
|
$Res call(
|
||||||
{GlobalKey<ChatState> chatKey,
|
{GlobalKey<ChatState> chatKey,
|
||||||
AutoScrollController scrollController,
|
AutoScrollController scrollController,
|
||||||
|
InputTextFieldController textEditingController,
|
||||||
User? localUser,
|
User? localUser,
|
||||||
IMap<Typed<FixedEncodedString43>, User> remoteUsers,
|
IMap<Typed<FixedEncodedString43>, User> remoteUsers,
|
||||||
IMap<Typed<FixedEncodedString43>, User> historicalRemoteUsers,
|
IMap<Typed<FixedEncodedString43>, User> historicalRemoteUsers,
|
||||||
@ -160,6 +169,7 @@ class __$$ChatComponentStateImplCopyWithImpl<$Res>
|
|||||||
$Res call({
|
$Res call({
|
||||||
Object? chatKey = null,
|
Object? chatKey = null,
|
||||||
Object? scrollController = null,
|
Object? scrollController = null,
|
||||||
|
Object? textEditingController = null,
|
||||||
Object? localUser = freezed,
|
Object? localUser = freezed,
|
||||||
Object? remoteUsers = null,
|
Object? remoteUsers = null,
|
||||||
Object? historicalRemoteUsers = null,
|
Object? historicalRemoteUsers = null,
|
||||||
@ -176,6 +186,10 @@ class __$$ChatComponentStateImplCopyWithImpl<$Res>
|
|||||||
? _value.scrollController
|
? _value.scrollController
|
||||||
: scrollController // ignore: cast_nullable_to_non_nullable
|
: scrollController // ignore: cast_nullable_to_non_nullable
|
||||||
as AutoScrollController,
|
as AutoScrollController,
|
||||||
|
textEditingController: null == textEditingController
|
||||||
|
? _value.textEditingController
|
||||||
|
: textEditingController // ignore: cast_nullable_to_non_nullable
|
||||||
|
as InputTextFieldController,
|
||||||
localUser: freezed == localUser
|
localUser: freezed == localUser
|
||||||
? _value.localUser
|
? _value.localUser
|
||||||
: localUser // ignore: cast_nullable_to_non_nullable
|
: localUser // ignore: cast_nullable_to_non_nullable
|
||||||
@ -210,6 +224,7 @@ class _$ChatComponentStateImpl implements _ChatComponentState {
|
|||||||
const _$ChatComponentStateImpl(
|
const _$ChatComponentStateImpl(
|
||||||
{required this.chatKey,
|
{required this.chatKey,
|
||||||
required this.scrollController,
|
required this.scrollController,
|
||||||
|
required this.textEditingController,
|
||||||
required this.localUser,
|
required this.localUser,
|
||||||
required this.remoteUsers,
|
required this.remoteUsers,
|
||||||
required this.historicalRemoteUsers,
|
required this.historicalRemoteUsers,
|
||||||
@ -223,6 +238,9 @@ class _$ChatComponentStateImpl implements _ChatComponentState {
|
|||||||
// ScrollController for the chat
|
// ScrollController for the chat
|
||||||
@override
|
@override
|
||||||
final AutoScrollController scrollController;
|
final AutoScrollController scrollController;
|
||||||
|
// TextEditingController for the chat
|
||||||
|
@override
|
||||||
|
final InputTextFieldController textEditingController;
|
||||||
// Local user
|
// Local user
|
||||||
@override
|
@override
|
||||||
final User? localUser;
|
final User? localUser;
|
||||||
@ -244,7 +262,7 @@ class _$ChatComponentStateImpl implements _ChatComponentState {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'ChatComponentState(chatKey: $chatKey, scrollController: $scrollController, localUser: $localUser, remoteUsers: $remoteUsers, historicalRemoteUsers: $historicalRemoteUsers, unknownUsers: $unknownUsers, messageWindow: $messageWindow, title: $title)';
|
return 'ChatComponentState(chatKey: $chatKey, scrollController: $scrollController, textEditingController: $textEditingController, localUser: $localUser, remoteUsers: $remoteUsers, historicalRemoteUsers: $historicalRemoteUsers, unknownUsers: $unknownUsers, messageWindow: $messageWindow, title: $title)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -255,6 +273,8 @@ class _$ChatComponentStateImpl implements _ChatComponentState {
|
|||||||
(identical(other.chatKey, chatKey) || other.chatKey == chatKey) &&
|
(identical(other.chatKey, chatKey) || other.chatKey == chatKey) &&
|
||||||
(identical(other.scrollController, scrollController) ||
|
(identical(other.scrollController, scrollController) ||
|
||||||
other.scrollController == scrollController) &&
|
other.scrollController == scrollController) &&
|
||||||
|
(identical(other.textEditingController, textEditingController) ||
|
||||||
|
other.textEditingController == textEditingController) &&
|
||||||
(identical(other.localUser, localUser) ||
|
(identical(other.localUser, localUser) ||
|
||||||
other.localUser == localUser) &&
|
other.localUser == localUser) &&
|
||||||
(identical(other.remoteUsers, remoteUsers) ||
|
(identical(other.remoteUsers, remoteUsers) ||
|
||||||
@ -273,6 +293,7 @@ class _$ChatComponentStateImpl implements _ChatComponentState {
|
|||||||
runtimeType,
|
runtimeType,
|
||||||
chatKey,
|
chatKey,
|
||||||
scrollController,
|
scrollController,
|
||||||
|
textEditingController,
|
||||||
localUser,
|
localUser,
|
||||||
remoteUsers,
|
remoteUsers,
|
||||||
historicalRemoteUsers,
|
historicalRemoteUsers,
|
||||||
@ -292,6 +313,7 @@ abstract class _ChatComponentState implements ChatComponentState {
|
|||||||
const factory _ChatComponentState(
|
const factory _ChatComponentState(
|
||||||
{required final GlobalKey<ChatState> chatKey,
|
{required final GlobalKey<ChatState> chatKey,
|
||||||
required final AutoScrollController scrollController,
|
required final AutoScrollController scrollController,
|
||||||
|
required final InputTextFieldController textEditingController,
|
||||||
required final User? localUser,
|
required final User? localUser,
|
||||||
required final IMap<Typed<FixedEncodedString43>, User> remoteUsers,
|
required final IMap<Typed<FixedEncodedString43>, User> remoteUsers,
|
||||||
required final IMap<Typed<FixedEncodedString43>, User>
|
required final IMap<Typed<FixedEncodedString43>, User>
|
||||||
@ -304,6 +326,8 @@ abstract class _ChatComponentState implements ChatComponentState {
|
|||||||
GlobalKey<ChatState> get chatKey;
|
GlobalKey<ChatState> get chatKey;
|
||||||
@override // ScrollController for the chat
|
@override // ScrollController for the chat
|
||||||
AutoScrollController get scrollController;
|
AutoScrollController get scrollController;
|
||||||
|
@override // TextEditingController for the chat
|
||||||
|
InputTextFieldController get textEditingController;
|
||||||
@override // Local user
|
@override // Local user
|
||||||
User? get localUser;
|
User? get localUser;
|
||||||
@override // Active remote users
|
@override // Active remote users
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:async_tools/async_tools.dart';
|
import 'package:async_tools/async_tools.dart';
|
||||||
@ -6,6 +7,7 @@ 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;
|
||||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
||||||
|
import 'package:flutter_translate/flutter_translate.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';
|
||||||
@ -154,6 +156,14 @@ class ChatComponentWidget extends StatelessWidget {
|
|||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
final textTheme = Theme.of(context).textTheme;
|
final textTheme = Theme.of(context).textTheme;
|
||||||
final chatTheme = makeChatTheme(scale, textTheme);
|
final chatTheme = makeChatTheme(scale, textTheme);
|
||||||
|
final errorChatTheme = (ChatThemeEditor(chatTheme)
|
||||||
|
..inputTextColor = scale.errorScale.primary
|
||||||
|
..sendButtonIcon = Image.asset(
|
||||||
|
'assets/icon-send.png',
|
||||||
|
color: scale.errorScale.primary,
|
||||||
|
package: 'flutter_chat_ui',
|
||||||
|
))
|
||||||
|
.commit();
|
||||||
|
|
||||||
// Get the enclosing chat component cubit that contains our state
|
// Get the enclosing chat component cubit that contains our state
|
||||||
// (created by ChatComponentWidget.builder())
|
// (created by ChatComponentWidget.builder())
|
||||||
@ -216,80 +226,125 @@ class ChatComponentWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: DecoratedBox(
|
child: DecoratedBox(
|
||||||
decoration: const BoxDecoration(),
|
decoration: const BoxDecoration(),
|
||||||
child: NotificationListener<ScrollNotification>(
|
child: NotificationListener<ScrollNotification>(
|
||||||
onNotification: (notification) {
|
onNotification: (notification) {
|
||||||
if (chatComponentCubit.scrollOffset != 0) {
|
if (chatComponentCubit.scrollOffset != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isFirstPage &&
|
||||||
|
notification.metrics.pixels <=
|
||||||
|
((notification.metrics.maxScrollExtent -
|
||||||
|
notification.metrics
|
||||||
|
.minScrollExtent) *
|
||||||
|
(1.0 - onEndReachedThreshold) +
|
||||||
|
notification
|
||||||
|
.metrics.minScrollExtent)) {
|
||||||
|
//
|
||||||
|
final scrollOffset = (notification
|
||||||
|
.metrics.maxScrollExtent -
|
||||||
|
notification.metrics.minScrollExtent) *
|
||||||
|
(1.0 - onEndReachedThreshold);
|
||||||
|
|
||||||
|
chatComponentCubit.scrollOffset = scrollOffset;
|
||||||
|
|
||||||
|
//
|
||||||
|
singleFuture(chatComponentState.chatKey,
|
||||||
|
() async {
|
||||||
|
await _handlePageForward(chatComponentCubit,
|
||||||
|
messageWindow, notification);
|
||||||
|
});
|
||||||
|
} else if (!isLastPage &&
|
||||||
|
notification.metrics.pixels >=
|
||||||
|
((notification.metrics.maxScrollExtent -
|
||||||
|
notification.metrics
|
||||||
|
.minScrollExtent) *
|
||||||
|
onEndReachedThreshold +
|
||||||
|
notification
|
||||||
|
.metrics.minScrollExtent)) {
|
||||||
|
//
|
||||||
|
final scrollOffset = -(notification
|
||||||
|
.metrics.maxScrollExtent -
|
||||||
|
notification.metrics.minScrollExtent) *
|
||||||
|
(1.0 - onEndReachedThreshold);
|
||||||
|
|
||||||
|
chatComponentCubit.scrollOffset = scrollOffset;
|
||||||
|
//
|
||||||
|
singleFuture(chatComponentState.chatKey,
|
||||||
|
() async {
|
||||||
|
await _handlePageBackward(chatComponentCubit,
|
||||||
|
messageWindow, notification);
|
||||||
|
});
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
},
|
||||||
|
child: ValueListenableBuilder(
|
||||||
|
valueListenable:
|
||||||
|
chatComponentState.textEditingController,
|
||||||
|
builder: (context, textEditingValue, __) {
|
||||||
|
final messageIsValid = utf8
|
||||||
|
.encode(textEditingValue.text)
|
||||||
|
.lengthInBytes <
|
||||||
|
2048;
|
||||||
|
|
||||||
if (!isFirstPage &&
|
return Chat(
|
||||||
notification.metrics.pixels <=
|
key: chatComponentState.chatKey,
|
||||||
((notification.metrics.maxScrollExtent -
|
theme: messageIsValid
|
||||||
notification
|
? chatTheme
|
||||||
.metrics.minScrollExtent) *
|
: errorChatTheme,
|
||||||
(1.0 - onEndReachedThreshold) +
|
messages: messageWindow.window.toList(),
|
||||||
notification.metrics.minScrollExtent)) {
|
scrollToBottomOnSend: isFirstPage,
|
||||||
//
|
scrollController:
|
||||||
final scrollOffset = (notification
|
chatComponentState.scrollController,
|
||||||
.metrics.maxScrollExtent -
|
inputOptions: InputOptions(
|
||||||
notification.metrics.minScrollExtent) *
|
inputClearMode: messageIsValid
|
||||||
(1.0 - onEndReachedThreshold);
|
? InputClearMode.always
|
||||||
|
: InputClearMode.never,
|
||||||
chatComponentCubit.scrollOffset = scrollOffset;
|
textEditingController:
|
||||||
|
chatComponentState
|
||||||
//
|
.textEditingController),
|
||||||
singleFuture(chatComponentState.chatKey,
|
// isLastPage: isLastPage,
|
||||||
() async {
|
// onEndReached: () async {
|
||||||
await _handlePageForward(chatComponentCubit,
|
// await _handlePageBackward(
|
||||||
messageWindow, notification);
|
// chatComponentCubit, messageWindow);
|
||||||
});
|
// },
|
||||||
} else if (!isLastPage &&
|
//onEndReachedThreshold: onEndReachedThreshold,
|
||||||
notification.metrics.pixels >=
|
//onAttachmentPressed: _handleAttachmentPressed,
|
||||||
((notification.metrics.maxScrollExtent -
|
//onMessageTap: _handleMessageTap,
|
||||||
notification
|
//onPreviewDataFetched: _handlePreviewDataFetched,
|
||||||
.metrics.minScrollExtent) *
|
onSendPressed: (pt) {
|
||||||
onEndReachedThreshold +
|
try {
|
||||||
notification.metrics.minScrollExtent)) {
|
if (!messageIsValid) {
|
||||||
//
|
showErrorToast(
|
||||||
final scrollOffset = -(notification
|
context,
|
||||||
.metrics.maxScrollExtent -
|
translate(
|
||||||
notification.metrics.minScrollExtent) *
|
'chat.message_too_long'));
|
||||||
(1.0 - onEndReachedThreshold);
|
return;
|
||||||
|
}
|
||||||
chatComponentCubit.scrollOffset = scrollOffset;
|
_handleSendPressed(
|
||||||
//
|
chatComponentCubit, pt);
|
||||||
singleFuture(chatComponentState.chatKey,
|
} on FormatException {
|
||||||
() async {
|
showErrorToast(
|
||||||
await _handlePageBackward(chatComponentCubit,
|
context,
|
||||||
messageWindow, notification);
|
translate(
|
||||||
});
|
'chat.message_too_long'));
|
||||||
}
|
}
|
||||||
return false;
|
},
|
||||||
},
|
listBottomWidget: messageIsValid
|
||||||
child: Chat(
|
? null
|
||||||
key: chatComponentState.chatKey,
|
: Text(
|
||||||
theme: chatTheme,
|
translate(
|
||||||
messages: messageWindow.window.toList(),
|
'chat.message_too_long'),
|
||||||
scrollToBottomOnSend: isFirstPage,
|
style: TextStyle(
|
||||||
scrollController:
|
color: scale
|
||||||
chatComponentState.scrollController,
|
.errorScale.primary))
|
||||||
// isLastPage: isLastPage,
|
.toCenter(),
|
||||||
// onEndReached: () async {
|
//showUserAvatars: false,
|
||||||
// await _handlePageBackward(
|
//showUserNames: true,
|
||||||
// chatComponentCubit, messageWindow);
|
user: localUser,
|
||||||
// },
|
emptyState: const EmptyChatWidget());
|
||||||
//onEndReachedThreshold: onEndReachedThreshold,
|
}))),
|
||||||
//onAttachmentPressed: _handleAttachmentPressed,
|
|
||||||
//onMessageTap: _handleMessageTap,
|
|
||||||
//onPreviewDataFetched: _handlePreviewDataFetched,
|
|
||||||
onSendPressed: (pt) =>
|
|
||||||
_handleSendPressed(chatComponentCubit, pt),
|
|
||||||
//showUserAvatars: false,
|
|
||||||
//showUserNames: true,
|
|
||||||
user: localUser,
|
|
||||||
emptyState: const EmptyChatWidget())),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
||||||
|
|
||||||
@ -52,3 +54,398 @@ ChatTheme makeChatTheme(ScaleScheme scale, TextTheme textTheme) =>
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 64,
|
fontSize: 64,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
class EditedChatTheme extends ChatTheme {
|
||||||
|
const EditedChatTheme({
|
||||||
|
required super.attachmentButtonIcon,
|
||||||
|
required super.attachmentButtonMargin,
|
||||||
|
required super.backgroundColor,
|
||||||
|
super.bubbleMargin,
|
||||||
|
required super.dateDividerMargin,
|
||||||
|
required super.dateDividerTextStyle,
|
||||||
|
required super.deliveredIcon,
|
||||||
|
required super.documentIcon,
|
||||||
|
required super.emptyChatPlaceholderTextStyle,
|
||||||
|
required super.errorColor,
|
||||||
|
required super.errorIcon,
|
||||||
|
required super.inputBackgroundColor,
|
||||||
|
required super.inputSurfaceTintColor,
|
||||||
|
required super.inputElevation,
|
||||||
|
required super.inputBorderRadius,
|
||||||
|
super.inputContainerDecoration,
|
||||||
|
required super.inputMargin,
|
||||||
|
required super.inputPadding,
|
||||||
|
required super.inputTextColor,
|
||||||
|
super.inputTextCursorColor,
|
||||||
|
required super.inputTextDecoration,
|
||||||
|
required super.inputTextStyle,
|
||||||
|
required super.messageBorderRadius,
|
||||||
|
required super.messageInsetsHorizontal,
|
||||||
|
required super.messageInsetsVertical,
|
||||||
|
required super.messageMaxWidth,
|
||||||
|
required super.primaryColor,
|
||||||
|
required super.receivedEmojiMessageTextStyle,
|
||||||
|
super.receivedMessageBodyBoldTextStyle,
|
||||||
|
super.receivedMessageBodyCodeTextStyle,
|
||||||
|
super.receivedMessageBodyLinkTextStyle,
|
||||||
|
required super.receivedMessageBodyTextStyle,
|
||||||
|
required super.receivedMessageCaptionTextStyle,
|
||||||
|
required super.receivedMessageDocumentIconColor,
|
||||||
|
required super.receivedMessageLinkDescriptionTextStyle,
|
||||||
|
required super.receivedMessageLinkTitleTextStyle,
|
||||||
|
required super.secondaryColor,
|
||||||
|
required super.seenIcon,
|
||||||
|
required super.sendButtonIcon,
|
||||||
|
required super.sendButtonMargin,
|
||||||
|
required super.sendingIcon,
|
||||||
|
required super.sentEmojiMessageTextStyle,
|
||||||
|
super.sentMessageBodyBoldTextStyle,
|
||||||
|
super.sentMessageBodyCodeTextStyle,
|
||||||
|
super.sentMessageBodyLinkTextStyle,
|
||||||
|
required super.sentMessageBodyTextStyle,
|
||||||
|
required super.sentMessageCaptionTextStyle,
|
||||||
|
required super.sentMessageDocumentIconColor,
|
||||||
|
required super.sentMessageLinkDescriptionTextStyle,
|
||||||
|
required super.sentMessageLinkTitleTextStyle,
|
||||||
|
required super.statusIconPadding,
|
||||||
|
required super.systemMessageTheme,
|
||||||
|
required super.typingIndicatorTheme,
|
||||||
|
required super.unreadHeaderTheme,
|
||||||
|
required super.userAvatarImageBackgroundColor,
|
||||||
|
required super.userAvatarNameColors,
|
||||||
|
required super.userAvatarTextStyle,
|
||||||
|
required super.userNameTextStyle,
|
||||||
|
super.highlightMessageColor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChatThemeEditor {
|
||||||
|
ChatThemeEditor(ChatTheme base)
|
||||||
|
: attachmentButtonIcon = base.attachmentButtonIcon,
|
||||||
|
attachmentButtonMargin = base.attachmentButtonMargin,
|
||||||
|
backgroundColor = base.backgroundColor,
|
||||||
|
bubbleMargin = base.bubbleMargin,
|
||||||
|
dateDividerMargin = base.dateDividerMargin,
|
||||||
|
dateDividerTextStyle = base.dateDividerTextStyle,
|
||||||
|
deliveredIcon = base.deliveredIcon,
|
||||||
|
documentIcon = base.documentIcon,
|
||||||
|
emptyChatPlaceholderTextStyle = base.emptyChatPlaceholderTextStyle,
|
||||||
|
errorColor = base.errorColor,
|
||||||
|
errorIcon = base.errorIcon,
|
||||||
|
inputBackgroundColor = base.inputBackgroundColor,
|
||||||
|
inputSurfaceTintColor = base.inputSurfaceTintColor,
|
||||||
|
inputElevation = base.inputElevation,
|
||||||
|
inputBorderRadius = base.inputBorderRadius,
|
||||||
|
inputContainerDecoration = base.inputContainerDecoration,
|
||||||
|
inputMargin = base.inputMargin,
|
||||||
|
inputPadding = base.inputPadding,
|
||||||
|
inputTextColor = base.inputTextColor,
|
||||||
|
inputTextCursorColor = base.inputTextCursorColor,
|
||||||
|
inputTextDecoration = base.inputTextDecoration,
|
||||||
|
inputTextStyle = base.inputTextStyle,
|
||||||
|
messageBorderRadius = base.messageBorderRadius,
|
||||||
|
messageInsetsHorizontal = base.messageInsetsHorizontal,
|
||||||
|
messageInsetsVertical = base.messageInsetsVertical,
|
||||||
|
messageMaxWidth = base.messageMaxWidth,
|
||||||
|
primaryColor = base.primaryColor,
|
||||||
|
receivedEmojiMessageTextStyle = base.receivedEmojiMessageTextStyle,
|
||||||
|
receivedMessageBodyBoldTextStyle =
|
||||||
|
base.receivedMessageBodyBoldTextStyle,
|
||||||
|
receivedMessageBodyCodeTextStyle =
|
||||||
|
base.receivedMessageBodyCodeTextStyle,
|
||||||
|
receivedMessageBodyLinkTextStyle =
|
||||||
|
base.receivedMessageBodyLinkTextStyle,
|
||||||
|
receivedMessageBodyTextStyle = base.receivedMessageBodyTextStyle,
|
||||||
|
receivedMessageCaptionTextStyle = base.receivedMessageCaptionTextStyle,
|
||||||
|
receivedMessageDocumentIconColor =
|
||||||
|
base.receivedMessageDocumentIconColor,
|
||||||
|
receivedMessageLinkDescriptionTextStyle =
|
||||||
|
base.receivedMessageLinkDescriptionTextStyle,
|
||||||
|
receivedMessageLinkTitleTextStyle =
|
||||||
|
base.receivedMessageLinkTitleTextStyle,
|
||||||
|
secondaryColor = base.secondaryColor,
|
||||||
|
seenIcon = base.seenIcon,
|
||||||
|
sendButtonIcon = base.sendButtonIcon,
|
||||||
|
sendButtonMargin = base.sendButtonMargin,
|
||||||
|
sendingIcon = base.sendingIcon,
|
||||||
|
sentEmojiMessageTextStyle = base.sentEmojiMessageTextStyle,
|
||||||
|
sentMessageBodyBoldTextStyle = base.sentMessageBodyBoldTextStyle,
|
||||||
|
sentMessageBodyCodeTextStyle = base.sentMessageBodyCodeTextStyle,
|
||||||
|
sentMessageBodyLinkTextStyle = base.sentMessageBodyLinkTextStyle,
|
||||||
|
sentMessageBodyTextStyle = base.sentMessageBodyTextStyle,
|
||||||
|
sentMessageCaptionTextStyle = base.sentMessageCaptionTextStyle,
|
||||||
|
sentMessageDocumentIconColor = base.sentMessageDocumentIconColor,
|
||||||
|
sentMessageLinkDescriptionTextStyle =
|
||||||
|
base.sentMessageLinkDescriptionTextStyle,
|
||||||
|
sentMessageLinkTitleTextStyle = base.sentMessageLinkTitleTextStyle,
|
||||||
|
statusIconPadding = base.statusIconPadding,
|
||||||
|
systemMessageTheme = base.systemMessageTheme,
|
||||||
|
typingIndicatorTheme = base.typingIndicatorTheme,
|
||||||
|
unreadHeaderTheme = base.unreadHeaderTheme,
|
||||||
|
userAvatarImageBackgroundColor = base.userAvatarImageBackgroundColor,
|
||||||
|
userAvatarNameColors = base.userAvatarNameColors,
|
||||||
|
userAvatarTextStyle = base.userAvatarTextStyle,
|
||||||
|
userNameTextStyle = base.userNameTextStyle,
|
||||||
|
highlightMessageColor = base.highlightMessageColor;
|
||||||
|
|
||||||
|
EditedChatTheme commit() => EditedChatTheme(
|
||||||
|
attachmentButtonIcon: attachmentButtonIcon,
|
||||||
|
attachmentButtonMargin: attachmentButtonMargin,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
bubbleMargin: bubbleMargin,
|
||||||
|
dateDividerMargin: dateDividerMargin,
|
||||||
|
dateDividerTextStyle: dateDividerTextStyle,
|
||||||
|
deliveredIcon: deliveredIcon,
|
||||||
|
documentIcon: documentIcon,
|
||||||
|
emptyChatPlaceholderTextStyle: emptyChatPlaceholderTextStyle,
|
||||||
|
errorColor: errorColor,
|
||||||
|
errorIcon: errorIcon,
|
||||||
|
inputBackgroundColor: inputBackgroundColor,
|
||||||
|
inputSurfaceTintColor: inputSurfaceTintColor,
|
||||||
|
inputElevation: inputElevation,
|
||||||
|
inputBorderRadius: inputBorderRadius,
|
||||||
|
inputContainerDecoration: inputContainerDecoration,
|
||||||
|
inputMargin: inputMargin,
|
||||||
|
inputPadding: inputPadding,
|
||||||
|
inputTextColor: inputTextColor,
|
||||||
|
inputTextCursorColor: inputTextCursorColor,
|
||||||
|
inputTextDecoration: inputTextDecoration,
|
||||||
|
inputTextStyle: inputTextStyle,
|
||||||
|
messageBorderRadius: messageBorderRadius,
|
||||||
|
messageInsetsHorizontal: messageInsetsHorizontal,
|
||||||
|
messageInsetsVertical: messageInsetsVertical,
|
||||||
|
messageMaxWidth: messageMaxWidth,
|
||||||
|
primaryColor: primaryColor,
|
||||||
|
receivedEmojiMessageTextStyle: receivedEmojiMessageTextStyle,
|
||||||
|
receivedMessageBodyBoldTextStyle: receivedMessageBodyBoldTextStyle,
|
||||||
|
receivedMessageBodyCodeTextStyle: receivedMessageBodyCodeTextStyle,
|
||||||
|
receivedMessageBodyLinkTextStyle: receivedMessageBodyLinkTextStyle,
|
||||||
|
receivedMessageBodyTextStyle: receivedMessageBodyTextStyle,
|
||||||
|
receivedMessageCaptionTextStyle: receivedMessageCaptionTextStyle,
|
||||||
|
receivedMessageDocumentIconColor: receivedMessageDocumentIconColor,
|
||||||
|
receivedMessageLinkDescriptionTextStyle:
|
||||||
|
receivedMessageLinkDescriptionTextStyle,
|
||||||
|
receivedMessageLinkTitleTextStyle: receivedMessageLinkTitleTextStyle,
|
||||||
|
secondaryColor: secondaryColor,
|
||||||
|
seenIcon: seenIcon,
|
||||||
|
sendButtonIcon: sendButtonIcon,
|
||||||
|
sendButtonMargin: sendButtonMargin,
|
||||||
|
sendingIcon: sendingIcon,
|
||||||
|
sentEmojiMessageTextStyle: sentEmojiMessageTextStyle,
|
||||||
|
sentMessageBodyBoldTextStyle: sentMessageBodyBoldTextStyle,
|
||||||
|
sentMessageBodyCodeTextStyle: sentMessageBodyCodeTextStyle,
|
||||||
|
sentMessageBodyLinkTextStyle: sentMessageBodyLinkTextStyle,
|
||||||
|
sentMessageBodyTextStyle: sentMessageBodyTextStyle,
|
||||||
|
sentMessageCaptionTextStyle: sentMessageCaptionTextStyle,
|
||||||
|
sentMessageDocumentIconColor: sentMessageDocumentIconColor,
|
||||||
|
sentMessageLinkDescriptionTextStyle:
|
||||||
|
sentMessageLinkDescriptionTextStyle,
|
||||||
|
sentMessageLinkTitleTextStyle: sentMessageLinkTitleTextStyle,
|
||||||
|
statusIconPadding: statusIconPadding,
|
||||||
|
systemMessageTheme: systemMessageTheme,
|
||||||
|
typingIndicatorTheme: typingIndicatorTheme,
|
||||||
|
unreadHeaderTheme: unreadHeaderTheme,
|
||||||
|
userAvatarImageBackgroundColor: userAvatarImageBackgroundColor,
|
||||||
|
userAvatarNameColors: userAvatarNameColors,
|
||||||
|
userAvatarTextStyle: userAvatarTextStyle,
|
||||||
|
userNameTextStyle: userNameTextStyle,
|
||||||
|
highlightMessageColor: highlightMessageColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Icon for select attachment button.
|
||||||
|
Widget? attachmentButtonIcon;
|
||||||
|
|
||||||
|
/// Margin of attachment button.
|
||||||
|
EdgeInsets? attachmentButtonMargin;
|
||||||
|
|
||||||
|
/// Used as a background color of a chat widget.
|
||||||
|
Color backgroundColor;
|
||||||
|
|
||||||
|
// Margin around the message bubble.
|
||||||
|
EdgeInsetsGeometry? bubbleMargin;
|
||||||
|
|
||||||
|
/// Margin around date dividers.
|
||||||
|
EdgeInsets dateDividerMargin;
|
||||||
|
|
||||||
|
/// Text style of the date dividers.
|
||||||
|
TextStyle dateDividerTextStyle;
|
||||||
|
|
||||||
|
/// Icon for message's `delivered` status. For the best look use size of 16.
|
||||||
|
Widget? deliveredIcon;
|
||||||
|
|
||||||
|
/// Icon inside file message.
|
||||||
|
Widget? documentIcon;
|
||||||
|
|
||||||
|
/// Text style of the empty chat placeholder.
|
||||||
|
TextStyle emptyChatPlaceholderTextStyle;
|
||||||
|
|
||||||
|
/// Color to indicate something bad happened (usually - shades of red).
|
||||||
|
Color errorColor;
|
||||||
|
|
||||||
|
/// Icon for message's `error` status. For the best look use size of 16.
|
||||||
|
Widget? errorIcon;
|
||||||
|
|
||||||
|
/// Color of the bottom bar where text field is.
|
||||||
|
Color inputBackgroundColor;
|
||||||
|
|
||||||
|
/// Surface Tint Color of the bottom bar where text field is.
|
||||||
|
Color inputSurfaceTintColor;
|
||||||
|
|
||||||
|
double inputElevation;
|
||||||
|
|
||||||
|
/// Top border radius of the bottom bar where text field is.
|
||||||
|
BorderRadius inputBorderRadius;
|
||||||
|
|
||||||
|
/// Decoration of the container wrapping the text field.
|
||||||
|
Decoration? inputContainerDecoration;
|
||||||
|
|
||||||
|
/// Outer insets of the bottom bar where text field is.
|
||||||
|
EdgeInsets inputMargin;
|
||||||
|
|
||||||
|
/// Inner insets of the bottom bar where text field is.
|
||||||
|
EdgeInsets inputPadding;
|
||||||
|
|
||||||
|
/// Color of the text field's text and attachment/send buttons.
|
||||||
|
Color inputTextColor;
|
||||||
|
|
||||||
|
/// Color of the text field's cursor.
|
||||||
|
Color? inputTextCursorColor;
|
||||||
|
|
||||||
|
/// Decoration of the input text field.
|
||||||
|
InputDecoration inputTextDecoration;
|
||||||
|
|
||||||
|
/// Text style of the message input. To change the color use [inputTextColor].
|
||||||
|
TextStyle inputTextStyle;
|
||||||
|
|
||||||
|
/// Border radius of message container.
|
||||||
|
double messageBorderRadius;
|
||||||
|
|
||||||
|
/// Horizontal message bubble insets.
|
||||||
|
double messageInsetsHorizontal;
|
||||||
|
|
||||||
|
/// Vertical message bubble insets.
|
||||||
|
double messageInsetsVertical;
|
||||||
|
|
||||||
|
/// Message bubble max width. set to [double.infinity] adaptive screen.
|
||||||
|
double messageMaxWidth;
|
||||||
|
|
||||||
|
/// Primary color of the chat used as a background of sent messages
|
||||||
|
/// and statuses.
|
||||||
|
Color primaryColor;
|
||||||
|
|
||||||
|
/// Text style used for displaying emojis on text messages.
|
||||||
|
TextStyle receivedEmojiMessageTextStyle;
|
||||||
|
|
||||||
|
/// Body text style used for displaying bold text on received text messages.
|
||||||
|
/// Default to a bold version of [receivedMessageBodyTextStyle].
|
||||||
|
TextStyle? receivedMessageBodyBoldTextStyle;
|
||||||
|
|
||||||
|
/// Body text style used for displaying code text on received text messages.
|
||||||
|
/// Defaults to a mono version of [receivedMessageBodyTextStyle].
|
||||||
|
TextStyle? receivedMessageBodyCodeTextStyle;
|
||||||
|
|
||||||
|
/// Text style used for displaying link text on received text messages.
|
||||||
|
/// Defaults to [receivedMessageBodyTextStyle].
|
||||||
|
TextStyle? receivedMessageBodyLinkTextStyle;
|
||||||
|
|
||||||
|
/// Body text style used for displaying text on different types
|
||||||
|
/// of received messages.
|
||||||
|
TextStyle receivedMessageBodyTextStyle;
|
||||||
|
|
||||||
|
/// Caption text style used for displaying secondary info (e.g. file size) on
|
||||||
|
/// different types of received messages.
|
||||||
|
TextStyle receivedMessageCaptionTextStyle;
|
||||||
|
|
||||||
|
/// Color of the document icon on received messages. Has no effect when
|
||||||
|
/// [documentIcon] is used.
|
||||||
|
Color receivedMessageDocumentIconColor;
|
||||||
|
|
||||||
|
/// Text style used for displaying link description on received messages.
|
||||||
|
TextStyle receivedMessageLinkDescriptionTextStyle;
|
||||||
|
|
||||||
|
/// Text style used for displaying link title on received messages.
|
||||||
|
TextStyle receivedMessageLinkTitleTextStyle;
|
||||||
|
|
||||||
|
/// Secondary color, used as a background of received messages.
|
||||||
|
Color secondaryColor;
|
||||||
|
|
||||||
|
/// Icon for message's `seen` status. For the best look use size of 16.
|
||||||
|
Widget? seenIcon;
|
||||||
|
|
||||||
|
/// Icon for send button.
|
||||||
|
Widget? sendButtonIcon;
|
||||||
|
|
||||||
|
/// Margin of send button.
|
||||||
|
EdgeInsets? sendButtonMargin;
|
||||||
|
|
||||||
|
/// Icon for message's `sending` status. For the best look use size of 10.
|
||||||
|
Widget? sendingIcon;
|
||||||
|
|
||||||
|
/// Text style used for displaying emojis on text messages.
|
||||||
|
TextStyle sentEmojiMessageTextStyle;
|
||||||
|
|
||||||
|
/// Body text style used for displaying bold text on sent text messages.
|
||||||
|
/// Defaults to a bold version of [sentMessageBodyTextStyle].
|
||||||
|
TextStyle? sentMessageBodyBoldTextStyle;
|
||||||
|
|
||||||
|
/// Body text style used for displaying code text on sent text messages.
|
||||||
|
/// Defaults to a mono version of [sentMessageBodyTextStyle].
|
||||||
|
TextStyle? sentMessageBodyCodeTextStyle;
|
||||||
|
|
||||||
|
/// Text style used for displaying link text on sent text messages.
|
||||||
|
/// Defaults to [sentMessageBodyTextStyle].
|
||||||
|
TextStyle? sentMessageBodyLinkTextStyle;
|
||||||
|
|
||||||
|
/// Body text style used for displaying text on different types
|
||||||
|
/// of sent messages.
|
||||||
|
TextStyle sentMessageBodyTextStyle;
|
||||||
|
|
||||||
|
/// Caption text style used for displaying secondary info (e.g. file size) on
|
||||||
|
/// different types of sent messages.
|
||||||
|
TextStyle sentMessageCaptionTextStyle;
|
||||||
|
|
||||||
|
/// Color of the document icon on sent messages. Has no effect when
|
||||||
|
/// [documentIcon] is used.
|
||||||
|
Color sentMessageDocumentIconColor;
|
||||||
|
|
||||||
|
/// Text style used for displaying link description on sent messages.
|
||||||
|
TextStyle sentMessageLinkDescriptionTextStyle;
|
||||||
|
|
||||||
|
/// Text style used for displaying link title on sent messages.
|
||||||
|
TextStyle sentMessageLinkTitleTextStyle;
|
||||||
|
|
||||||
|
/// Padding around status icons.
|
||||||
|
EdgeInsets statusIconPadding;
|
||||||
|
|
||||||
|
/// Theme for the system message. Will not have an effect if a custom builder
|
||||||
|
/// is provided.
|
||||||
|
SystemMessageTheme systemMessageTheme;
|
||||||
|
|
||||||
|
/// Theme for typing indicator. See [TypingIndicator].
|
||||||
|
TypingIndicatorTheme typingIndicatorTheme;
|
||||||
|
|
||||||
|
/// Theme for the unread header.
|
||||||
|
UnreadHeaderTheme unreadHeaderTheme;
|
||||||
|
|
||||||
|
/// Color used as a background for user avatar if an image is provided.
|
||||||
|
/// Visible if the image has some transparent parts.
|
||||||
|
Color userAvatarImageBackgroundColor;
|
||||||
|
|
||||||
|
/// Colors used as backgrounds for user avatars with no image and so,
|
||||||
|
/// corresponding user names.
|
||||||
|
/// Calculated based on a user ID, so unique across the whole app.
|
||||||
|
List<Color> userAvatarNameColors;
|
||||||
|
|
||||||
|
/// Text style used for displaying initials on user avatar if no
|
||||||
|
/// image is provided.
|
||||||
|
TextStyle userAvatarTextStyle;
|
||||||
|
|
||||||
|
/// User names text style. Color will be overwritten
|
||||||
|
/// with [userAvatarNameColors].
|
||||||
|
TextStyle userNameTextStyle;
|
||||||
|
|
||||||
|
/// Color used as background of message row on highligth.
|
||||||
|
Color? highlightMessageColor;
|
||||||
|
}
|
||||||
|
@ -3,7 +3,8 @@ enum IdentityException implements Exception {
|
|||||||
readError('identity could not be read'),
|
readError('identity could not be read'),
|
||||||
noAccount('no account record info'),
|
noAccount('no account record info'),
|
||||||
limitExceeded('too many items for the limit'),
|
limitExceeded('too many items for the limit'),
|
||||||
invalid('identity is corrupted or secret is invalid');
|
invalid('identity is corrupted or secret is invalid'),
|
||||||
|
cancelled('account operation cancelled');
|
||||||
|
|
||||||
const IdentityException(this.message);
|
const IdentityException(this.message);
|
||||||
final String message;
|
final String message;
|
||||||
|
@ -68,12 +68,12 @@ class IdentityInstance with _$IdentityInstance {
|
|||||||
return cs;
|
return cs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the account record info for a specific accountKey from the identity
|
/// Read the account record info for a specific applicationId from the
|
||||||
/// instance record using the identity instance secret key to decrypt
|
/// identity instance record using the identity instance secret key to decrypt
|
||||||
Future<List<AccountRecordInfo>> readAccount(
|
Future<List<AccountRecordInfo>> readAccount(
|
||||||
{required TypedKey superRecordKey,
|
{required TypedKey superRecordKey,
|
||||||
required SecretKey secretKey,
|
required SecretKey secretKey,
|
||||||
required String accountKey}) async {
|
required String applicationId}) async {
|
||||||
// Read the identity key to get the account keys
|
// Read the identity key to get the account keys
|
||||||
final pool = DHTRecordPool.instance;
|
final pool = DHTRecordPool.instance;
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ class IdentityInstance with _$IdentityInstance {
|
|||||||
throw IdentityException.readError;
|
throw IdentityException.readError;
|
||||||
}
|
}
|
||||||
final accountRecords = IMapOfSets.from(identity.accountRecords);
|
final accountRecords = IMapOfSets.from(identity.accountRecords);
|
||||||
final vcAccounts = accountRecords.get(accountKey);
|
final vcAccounts = accountRecords.get(applicationId);
|
||||||
|
|
||||||
accountRecordInfo = vcAccounts.toList();
|
accountRecordInfo = vcAccounts.toList();
|
||||||
});
|
});
|
||||||
@ -104,7 +104,7 @@ class IdentityInstance with _$IdentityInstance {
|
|||||||
Future<AccountRecordInfo> addAccount({
|
Future<AccountRecordInfo> addAccount({
|
||||||
required TypedKey superRecordKey,
|
required TypedKey superRecordKey,
|
||||||
required SecretKey secretKey,
|
required SecretKey secretKey,
|
||||||
required String accountKey,
|
required String applicationId,
|
||||||
required Future<Uint8List> Function(TypedKey parent) createAccountCallback,
|
required Future<Uint8List> Function(TypedKey parent) createAccountCallback,
|
||||||
int maxAccounts = 1,
|
int maxAccounts = 1,
|
||||||
}) async {
|
}) async {
|
||||||
@ -143,11 +143,12 @@ class IdentityInstance with _$IdentityInstance {
|
|||||||
}
|
}
|
||||||
final oldAccountRecords = IMapOfSets.from(oldIdentity.accountRecords);
|
final oldAccountRecords = IMapOfSets.from(oldIdentity.accountRecords);
|
||||||
|
|
||||||
if (oldAccountRecords.get(accountKey).length >= maxAccounts) {
|
if (oldAccountRecords.get(applicationId).length >= maxAccounts) {
|
||||||
throw IdentityException.limitExceeded;
|
throw IdentityException.limitExceeded;
|
||||||
}
|
}
|
||||||
final accountRecords =
|
final accountRecords = oldAccountRecords
|
||||||
oldAccountRecords.add(accountKey, newAccountRecordInfo).asIMap();
|
.add(applicationId, newAccountRecordInfo)
|
||||||
|
.asIMap();
|
||||||
return oldIdentity.copyWith(accountRecords: accountRecords);
|
return oldIdentity.copyWith(accountRecords: accountRecords);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -156,6 +157,61 @@ class IdentityInstance with _$IdentityInstance {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes an Account associated with super identity from the identity
|
||||||
|
/// instance record. 'removeAccountCallback' returns the account to be
|
||||||
|
/// removed from the list passed to it.
|
||||||
|
Future<bool> removeAccount({
|
||||||
|
required TypedKey superRecordKey,
|
||||||
|
required SecretKey secretKey,
|
||||||
|
required String applicationId,
|
||||||
|
required Future<AccountRecordInfo?> Function(
|
||||||
|
List<AccountRecordInfo> accountRecordInfos)
|
||||||
|
removeAccountCallback,
|
||||||
|
}) async {
|
||||||
|
final pool = DHTRecordPool.instance;
|
||||||
|
|
||||||
|
/////// Add account with profile to DHT
|
||||||
|
|
||||||
|
// Open identity key for writing
|
||||||
|
veilidLoggy.debug('Opening identity record');
|
||||||
|
return (await pool.openRecordWrite(recordKey, writer(secretKey),
|
||||||
|
debugName: 'IdentityInstance::addAccount::IdentityRecord',
|
||||||
|
parent: superRecordKey))
|
||||||
|
.scope((identityRec) async {
|
||||||
|
try {
|
||||||
|
// Update identity key to remove account
|
||||||
|
veilidLoggy.debug('Updating identity to remove account');
|
||||||
|
await identityRec.eventualUpdateJson(Identity.fromJson,
|
||||||
|
(oldIdentity) async {
|
||||||
|
if (oldIdentity == null) {
|
||||||
|
throw IdentityException.readError;
|
||||||
|
}
|
||||||
|
final oldAccountRecords = IMapOfSets.from(oldIdentity.accountRecords);
|
||||||
|
|
||||||
|
// Get list of accounts associated with the application
|
||||||
|
final vcAccounts = oldAccountRecords.get(applicationId);
|
||||||
|
final accountRecordInfos = vcAccounts.toList();
|
||||||
|
|
||||||
|
// Call the callback to return what account to remove
|
||||||
|
final toRemove = await removeAccountCallback(accountRecordInfos);
|
||||||
|
if (toRemove == null) {
|
||||||
|
throw IdentityException.cancelled;
|
||||||
|
}
|
||||||
|
final newAccountRecords =
|
||||||
|
oldAccountRecords.remove(applicationId, toRemove).asIMap();
|
||||||
|
|
||||||
|
return oldIdentity.copyWith(accountRecords: newAccountRecords);
|
||||||
|
});
|
||||||
|
} on IdentityException catch (e) {
|
||||||
|
if (e == IdentityException.cancelled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
// Internal implementation
|
// Internal implementation
|
||||||
|
|
||||||
|
@ -15,12 +15,14 @@ class PersistentQueue<T extends GeneratedMessage>
|
|||||||
required String key,
|
required String key,
|
||||||
required T Function(Uint8List) fromBuffer,
|
required T Function(Uint8List) fromBuffer,
|
||||||
required Future<void> Function(IList<T>) closure,
|
required Future<void> Function(IList<T>) closure,
|
||||||
bool deleteOnClose = true})
|
bool deleteOnClose = true,
|
||||||
|
void Function(Object, StackTrace)? onError})
|
||||||
: _table = table,
|
: _table = table,
|
||||||
_key = key,
|
_key = key,
|
||||||
_fromBuffer = fromBuffer,
|
_fromBuffer = fromBuffer,
|
||||||
_closure = closure,
|
_closure = closure,
|
||||||
_deleteOnClose = deleteOnClose {
|
_deleteOnClose = deleteOnClose,
|
||||||
|
_onError = onError {
|
||||||
_initWait.add(_init);
|
_initWait.add(_init);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,9 +63,17 @@ class PersistentQueue<T extends GeneratedMessage>
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Load the queue if we have one
|
// Load the queue if we have one
|
||||||
await _queueMutex.protect(() async {
|
try {
|
||||||
_queue = await load() ?? await store(IList<T>.empty());
|
await _queueMutex.protect(() async {
|
||||||
});
|
_queue = await load() ?? await store(IList<T>.empty());
|
||||||
|
});
|
||||||
|
} on Exception catch (e, st) {
|
||||||
|
if (_onError != null) {
|
||||||
|
_onError(e, st);
|
||||||
|
} else {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateQueueInner(IList<T> newQueue) async {
|
Future<void> _updateQueueInner(IList<T> newQueue) async {
|
||||||
@ -132,24 +142,32 @@ class PersistentQueue<T extends GeneratedMessage>
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
Future<void> _process() async {
|
Future<void> _process() async {
|
||||||
// Take a copy of the current queue
|
try {
|
||||||
// (doesn't need queue mutex because this is a sync operation)
|
// Take a copy of the current queue
|
||||||
final toProcess = _queue;
|
// (doesn't need queue mutex because this is a sync operation)
|
||||||
final processCount = toProcess.length;
|
final toProcess = _queue;
|
||||||
if (processCount == 0) {
|
final processCount = toProcess.length;
|
||||||
return;
|
if (processCount == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the processing closure
|
||||||
|
await _closure(toProcess);
|
||||||
|
|
||||||
|
// If there was no exception, remove the processed items
|
||||||
|
await _queueMutex.protect(() async {
|
||||||
|
// Get the queue from the state again as items could
|
||||||
|
// have been added during processing
|
||||||
|
final newQueue = _queue.skip(processCount).toIList();
|
||||||
|
await _updateQueueInner(newQueue);
|
||||||
|
});
|
||||||
|
} on Exception catch (e, sp) {
|
||||||
|
if (_onError != null) {
|
||||||
|
_onError(e, sp);
|
||||||
|
} else {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the processing closure
|
|
||||||
await _closure(toProcess);
|
|
||||||
|
|
||||||
// If there was no exception, remove the processed items
|
|
||||||
await _queueMutex.protect(() async {
|
|
||||||
// Get the queue from the state again as items could
|
|
||||||
// have been added during processing
|
|
||||||
final newQueue = _queue.skip(processCount).toIList();
|
|
||||||
await _updateQueueInner(newQueue);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IList<T> get queue => _queue;
|
IList<T> get queue => _queue;
|
||||||
@ -190,4 +208,5 @@ class PersistentQueue<T extends GeneratedMessage>
|
|||||||
final StreamController<Iterable<T>> _syncAddController = StreamController();
|
final StreamController<Iterable<T>> _syncAddController = StreamController();
|
||||||
final StreamController<void> _queueReady = StreamController();
|
final StreamController<void> _queueReady = StreamController();
|
||||||
final Future<void> Function(IList<T>) _closure;
|
final Future<void> Function(IList<T>) _closure;
|
||||||
|
final void Function(Object, StackTrace)? _onError;
|
||||||
}
|
}
|
||||||
|
@ -483,10 +483,10 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: "6712d897e25041de38fb53ec06dc7a12cc6bff7d"
|
resolved-ref: "0d8ac2fcafe24eba1adff9290a9ccd41f7718480"
|
||||||
url: "https://gitlab.com/veilid/flutter-chat-ui.git"
|
url: "https://gitlab.com/veilid/flutter-chat-ui.git"
|
||||||
source: git
|
source: git
|
||||||
version: "1.6.13"
|
version: "1.6.14"
|
||||||
flutter_form_builder:
|
flutter_form_builder:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
Loading…
Reference in New Issue
Block a user