mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-07-22 22:20:43 -04:00
State machine work
This commit is contained in:
parent
61855521dc
commit
be8014c97a
34 changed files with 703 additions and 505 deletions
|
@ -213,7 +213,9 @@
|
|||
},
|
||||
"waiting_invitation": {
|
||||
"accepted": "Contact invitation accepted from {name}",
|
||||
"rejected": "Contact invitation was rejected"
|
||||
"rejected": "Contact invitation was rejected",
|
||||
"invalid": "Contact invitation was not valid",
|
||||
"init_failed": "Contact initialization failed"
|
||||
},
|
||||
"paste_invitation_dialog": {
|
||||
"title": "Paste Contact Invite",
|
||||
|
|
|
@ -22,11 +22,11 @@ class PerAccountCollectionBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||
}
|
||||
|
||||
// Add account record cubit
|
||||
Future<void> _addPerAccountCollectionCubit(
|
||||
{required TypedKey superIdentityRecordKey}) async =>
|
||||
void _addPerAccountCollectionCubit(
|
||||
{required TypedKey superIdentityRecordKey}) =>
|
||||
add(
|
||||
superIdentityRecordKey,
|
||||
() async => PerAccountCollectionCubit(
|
||||
() => PerAccountCollectionCubit(
|
||||
locator: _locator,
|
||||
accountInfoCubit: AccountInfoCubit(
|
||||
accountRepository: _accountRepository,
|
||||
|
@ -35,11 +35,11 @@ class PerAccountCollectionBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||
/// StateFollower /////////////////////////
|
||||
|
||||
@override
|
||||
Future<void> removeFromState(TypedKey key) => remove(key);
|
||||
void removeFromState(TypedKey key) => remove(key);
|
||||
|
||||
@override
|
||||
Future<void> updateState(
|
||||
TypedKey key, LocalAccount? oldValue, LocalAccount newValue) async {
|
||||
void updateState(
|
||||
TypedKey key, LocalAccount? oldValue, LocalAccount newValue) {
|
||||
// Don't replace unless this is a totally different account
|
||||
// The sub-cubit's subscription will update our state later
|
||||
if (oldValue != null) {
|
||||
|
@ -53,7 +53,7 @@ class PerAccountCollectionBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||
// sub-cubit
|
||||
return;
|
||||
}
|
||||
await _addPerAccountCollectionCubit(
|
||||
_addPerAccountCollectionCubit(
|
||||
superIdentityRecordKey: newValue.superIdentity.recordKey);
|
||||
}
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ abstract mixin class $PerAccountCollectionStateCopyWith<$Res> {
|
|||
@useResult
|
||||
$Res call(
|
||||
{AccountInfo accountInfo,
|
||||
AsyncValue<Account>? avAccountRecordState,
|
||||
AsyncValue<AccountRecordState>? avAccountRecordState,
|
||||
AccountInfoCubit? accountInfoCubit,
|
||||
AccountRecordCubit? accountRecordCubit,
|
||||
ContactInvitationListCubit? contactInvitationListCubit,
|
||||
|
@ -147,9 +147,9 @@ class _$PerAccountCollectionStateCopyWithImpl<$Res>
|
|||
: accountInfo // ignore: cast_nullable_to_non_nullable
|
||||
as AccountInfo,
|
||||
avAccountRecordState: freezed == avAccountRecordState
|
||||
? _self.avAccountRecordState!
|
||||
? _self.avAccountRecordState
|
||||
: avAccountRecordState // ignore: cast_nullable_to_non_nullable
|
||||
as AsyncValue<Account>?,
|
||||
as AsyncValue<AccountRecordState>?,
|
||||
accountInfoCubit: freezed == accountInfoCubit
|
||||
? _self.accountInfoCubit
|
||||
: accountInfoCubit // ignore: cast_nullable_to_non_nullable
|
||||
|
@ -227,7 +227,7 @@ class _PerAccountCollectionState extends PerAccountCollectionState {
|
|||
@override
|
||||
final AccountInfo accountInfo;
|
||||
@override
|
||||
final AsyncValue<Account>? avAccountRecordState;
|
||||
final AsyncValue<AccountRecordState>? avAccountRecordState;
|
||||
@override
|
||||
final AccountInfoCubit? accountInfoCubit;
|
||||
@override
|
||||
|
@ -326,7 +326,7 @@ abstract mixin class _$PerAccountCollectionStateCopyWith<$Res>
|
|||
@useResult
|
||||
$Res call(
|
||||
{AccountInfo accountInfo,
|
||||
AsyncValue<Account>? avAccountRecordState,
|
||||
AsyncValue<AccountRecordState>? avAccountRecordState,
|
||||
AccountInfoCubit? accountInfoCubit,
|
||||
AccountRecordCubit? accountRecordCubit,
|
||||
ContactInvitationListCubit? contactInvitationListCubit,
|
||||
|
@ -375,7 +375,7 @@ class __$PerAccountCollectionStateCopyWithImpl<$Res>
|
|||
avAccountRecordState: freezed == avAccountRecordState
|
||||
? _self.avAccountRecordState
|
||||
: avAccountRecordState // ignore: cast_nullable_to_non_nullable
|
||||
as AsyncValue<Account>?,
|
||||
as AsyncValue<AccountRecordState>?,
|
||||
accountInfoCubit: freezed == accountInfoCubit
|
||||
? _self.accountInfoCubit
|
||||
: accountInfoCubit // ignore: cast_nullable_to_non_nullable
|
||||
|
|
|
@ -67,8 +67,8 @@ abstract mixin class $UserLoginCopyWith<$Res> {
|
|||
_$UserLoginCopyWithImpl;
|
||||
@useResult
|
||||
$Res call(
|
||||
{Typed<FixedEncodedString43> superIdentityRecordKey,
|
||||
Typed<FixedEncodedString43> identitySecret,
|
||||
{TypedKey superIdentityRecordKey,
|
||||
TypedSecret identitySecret,
|
||||
AccountRecordInfo accountRecordInfo,
|
||||
Timestamp lastActive});
|
||||
|
||||
|
@ -94,13 +94,13 @@ class _$UserLoginCopyWithImpl<$Res> implements $UserLoginCopyWith<$Res> {
|
|||
}) {
|
||||
return _then(_self.copyWith(
|
||||
superIdentityRecordKey: null == superIdentityRecordKey
|
||||
? _self.superIdentityRecordKey!
|
||||
? _self.superIdentityRecordKey
|
||||
: superIdentityRecordKey // ignore: cast_nullable_to_non_nullable
|
||||
as Typed<FixedEncodedString43>,
|
||||
as TypedKey,
|
||||
identitySecret: null == identitySecret
|
||||
? _self.identitySecret!
|
||||
? _self.identitySecret
|
||||
: identitySecret // ignore: cast_nullable_to_non_nullable
|
||||
as Typed<FixedEncodedString43>,
|
||||
as TypedSecret,
|
||||
accountRecordInfo: null == accountRecordInfo
|
||||
? _self.accountRecordInfo
|
||||
: accountRecordInfo // ignore: cast_nullable_to_non_nullable
|
||||
|
@ -136,10 +136,10 @@ class _UserLogin implements UserLogin {
|
|||
// SuperIdentity record key for the user
|
||||
// used to index the local accounts table
|
||||
@override
|
||||
final Typed<FixedEncodedString43> superIdentityRecordKey;
|
||||
final TypedKey superIdentityRecordKey;
|
||||
// The identity secret as unlocked from the local accounts table
|
||||
@override
|
||||
final Typed<FixedEncodedString43> identitySecret;
|
||||
final TypedSecret identitySecret;
|
||||
// The account record key, owner key and secret pulled from the identity
|
||||
@override
|
||||
final AccountRecordInfo accountRecordInfo;
|
||||
|
@ -197,8 +197,8 @@ abstract mixin class _$UserLoginCopyWith<$Res>
|
|||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{Typed<FixedEncodedString43> superIdentityRecordKey,
|
||||
Typed<FixedEncodedString43> identitySecret,
|
||||
{TypedKey superIdentityRecordKey,
|
||||
TypedSecret identitySecret,
|
||||
AccountRecordInfo accountRecordInfo,
|
||||
Timestamp lastActive});
|
||||
|
||||
|
@ -227,11 +227,11 @@ class __$UserLoginCopyWithImpl<$Res> implements _$UserLoginCopyWith<$Res> {
|
|||
superIdentityRecordKey: null == superIdentityRecordKey
|
||||
? _self.superIdentityRecordKey
|
||||
: superIdentityRecordKey // ignore: cast_nullable_to_non_nullable
|
||||
as Typed<FixedEncodedString43>,
|
||||
as TypedKey,
|
||||
identitySecret: null == identitySecret
|
||||
? _self.identitySecret
|
||||
: identitySecret // ignore: cast_nullable_to_non_nullable
|
||||
as Typed<FixedEncodedString43>,
|
||||
as TypedSecret,
|
||||
accountRecordInfo: null == accountRecordInfo
|
||||
? _self.accountRecordInfo
|
||||
: accountRecordInfo // ignore: cast_nullable_to_non_nullable
|
||||
|
|
|
@ -214,9 +214,9 @@ class _EditAccountPageState extends WindowSetupState<EditAccountPage> {
|
|||
// Look up account cubit for this specific account
|
||||
final perAccountCollectionBlocMapCubit =
|
||||
context.read<PerAccountCollectionBlocMapCubit>();
|
||||
final accountRecordCubit = await perAccountCollectionBlocMapCubit
|
||||
.operate(widget.superIdentityRecordKey,
|
||||
closure: (c) async => c.accountRecordCubit);
|
||||
final accountRecordCubit = perAccountCollectionBlocMapCubit
|
||||
.entry(widget.superIdentityRecordKey)
|
||||
?.accountRecordCubit;
|
||||
if (accountRecordCubit == null) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -176,12 +176,11 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
|||
_remoteIdentityPublicKey, rcvdMessagesDHTLog);
|
||||
}
|
||||
|
||||
Future<void> updateRemoteMessagesRecordKey(
|
||||
TypedKey? remoteMessagesRecordKey) async {
|
||||
await _initWait();
|
||||
|
||||
void updateRemoteMessagesRecordKey(TypedKey? remoteMessagesRecordKey) {
|
||||
_sspRemoteConversationRecordKey.updateState(remoteMessagesRecordKey,
|
||||
(remoteMessagesRecordKey) async {
|
||||
await _initWait();
|
||||
|
||||
// Don't bother if nothing is changing
|
||||
if (_remoteMessagesRecordKey == remoteMessagesRecordKey) {
|
||||
return;
|
||||
|
|
|
@ -67,9 +67,9 @@ abstract mixin class $ChatComponentStateCopyWith<$Res> {
|
|||
@useResult
|
||||
$Res call(
|
||||
{User? localUser,
|
||||
IMap<String, User> remoteUsers,
|
||||
IMap<String, User> historicalRemoteUsers,
|
||||
IMap<String, User> unknownUsers,
|
||||
IMap<UserID, User> remoteUsers,
|
||||
IMap<UserID, User> historicalRemoteUsers,
|
||||
IMap<UserID, User> unknownUsers,
|
||||
AsyncValue<WindowState<Message>> messageWindow,
|
||||
String title});
|
||||
|
||||
|
@ -103,17 +103,17 @@ class _$ChatComponentStateCopyWithImpl<$Res>
|
|||
: localUser // ignore: cast_nullable_to_non_nullable
|
||||
as User?,
|
||||
remoteUsers: null == remoteUsers
|
||||
? _self.remoteUsers!
|
||||
? _self.remoteUsers
|
||||
: remoteUsers // ignore: cast_nullable_to_non_nullable
|
||||
as IMap<String, User>,
|
||||
as IMap<UserID, User>,
|
||||
historicalRemoteUsers: null == historicalRemoteUsers
|
||||
? _self.historicalRemoteUsers!
|
||||
? _self.historicalRemoteUsers
|
||||
: historicalRemoteUsers // ignore: cast_nullable_to_non_nullable
|
||||
as IMap<String, User>,
|
||||
as IMap<UserID, User>,
|
||||
unknownUsers: null == unknownUsers
|
||||
? _self.unknownUsers!
|
||||
? _self.unknownUsers
|
||||
: unknownUsers // ignore: cast_nullable_to_non_nullable
|
||||
as IMap<String, User>,
|
||||
as IMap<UserID, User>,
|
||||
messageWindow: null == messageWindow
|
||||
? _self.messageWindow
|
||||
: messageWindow // ignore: cast_nullable_to_non_nullable
|
||||
|
@ -167,13 +167,13 @@ class _ChatComponentState implements ChatComponentState {
|
|||
final User? localUser;
|
||||
// Active remote users
|
||||
@override
|
||||
final IMap<String, User> remoteUsers;
|
||||
final IMap<UserID, User> remoteUsers;
|
||||
// Historical remote users
|
||||
@override
|
||||
final IMap<String, User> historicalRemoteUsers;
|
||||
final IMap<UserID, User> historicalRemoteUsers;
|
||||
// Unknown users
|
||||
@override
|
||||
final IMap<String, User> unknownUsers;
|
||||
final IMap<UserID, User> unknownUsers;
|
||||
// Messages state
|
||||
@override
|
||||
final AsyncValue<WindowState<Message>> messageWindow;
|
||||
|
@ -227,9 +227,9 @@ abstract mixin class _$ChatComponentStateCopyWith<$Res>
|
|||
@useResult
|
||||
$Res call(
|
||||
{User? localUser,
|
||||
IMap<String, User> remoteUsers,
|
||||
IMap<String, User> historicalRemoteUsers,
|
||||
IMap<String, User> unknownUsers,
|
||||
IMap<UserID, User> remoteUsers,
|
||||
IMap<UserID, User> historicalRemoteUsers,
|
||||
IMap<UserID, User> unknownUsers,
|
||||
AsyncValue<WindowState<Message>> messageWindow,
|
||||
String title});
|
||||
|
||||
|
@ -267,15 +267,15 @@ class __$ChatComponentStateCopyWithImpl<$Res>
|
|||
remoteUsers: null == remoteUsers
|
||||
? _self.remoteUsers
|
||||
: remoteUsers // ignore: cast_nullable_to_non_nullable
|
||||
as IMap<String, User>,
|
||||
as IMap<UserID, User>,
|
||||
historicalRemoteUsers: null == historicalRemoteUsers
|
||||
? _self.historicalRemoteUsers
|
||||
: historicalRemoteUsers // ignore: cast_nullable_to_non_nullable
|
||||
as IMap<String, User>,
|
||||
as IMap<UserID, User>,
|
||||
unknownUsers: null == unknownUsers
|
||||
? _self.unknownUsers
|
||||
: unknownUsers // ignore: cast_nullable_to_non_nullable
|
||||
as IMap<String, User>,
|
||||
as IMap<UserID, User>,
|
||||
messageWindow: null == messageWindow
|
||||
? _self.messageWindow
|
||||
: messageWindow // ignore: cast_nullable_to_non_nullable
|
||||
|
|
|
@ -54,10 +54,9 @@ class ChatComponentWidget extends StatefulWidget {
|
|||
final contactListCubit = context.watch<ContactListCubit>();
|
||||
|
||||
// Get the active conversation cubit
|
||||
final activeConversationCubit = context
|
||||
.select<ActiveConversationsBlocMapCubit, ActiveConversationCubit?>(
|
||||
(x) => x.tryOperateSync(localConversationRecordKey,
|
||||
closure: (cubit) => cubit));
|
||||
final activeConversationCubit = context.select<
|
||||
ActiveConversationsBlocMapCubit,
|
||||
ActiveConversationCubit?>((x) => x.entry(localConversationRecordKey));
|
||||
if (activeConversationCubit == null) {
|
||||
return waitingPage(onCancel: onCancel);
|
||||
}
|
||||
|
@ -65,8 +64,7 @@ class ChatComponentWidget extends StatefulWidget {
|
|||
// Get the messages cubit
|
||||
final messagesCubit = context.select<ActiveSingleContactChatBlocMapCubit,
|
||||
SingleContactMessagesCubit?>(
|
||||
(x) => x.tryOperateSync(localConversationRecordKey,
|
||||
closure: (cubit) => cubit));
|
||||
(x) => x.entry(localConversationRecordKey));
|
||||
if (messagesCubit == null) {
|
||||
return waitingPage(onCancel: onCancel);
|
||||
}
|
||||
|
@ -106,10 +104,11 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
|
|||
_textEditingController = TextEditingController();
|
||||
_scrollController = ScrollController();
|
||||
_chatStateProcessor = SingleStateProcessor<ChatComponentState>();
|
||||
_focusNode = FocusNode();
|
||||
|
||||
final _chatComponentCubit = context.read<ChatComponentCubit>();
|
||||
_chatStateProcessor.follow(_chatComponentCubit.stream,
|
||||
_chatComponentCubit.state, _updateChatState);
|
||||
final chatComponentCubit = context.read<ChatComponentCubit>();
|
||||
_chatStateProcessor.follow(
|
||||
chatComponentCubit.stream, chatComponentCubit.state, _updateChatState);
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
@ -118,6 +117,7 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
|
|||
void dispose() {
|
||||
unawaited(_chatStateProcessor.close());
|
||||
|
||||
_focusNode.dispose();
|
||||
_chatController.dispose();
|
||||
_scrollController.dispose();
|
||||
_textEditingController.dispose();
|
||||
|
@ -281,6 +281,7 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
|
|||
// Composer builder
|
||||
composerBuilder: (ctx) => VcComposerWidget(
|
||||
autofocus: true,
|
||||
focusNode: _focusNode,
|
||||
textInputAction: isAnyMobile
|
||||
? TextInputAction.newline
|
||||
: TextInputAction.send,
|
||||
|
@ -399,6 +400,8 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
|
|||
}
|
||||
|
||||
void _handleSendPressed(ChatComponentCubit chatComponentCubit, String text) {
|
||||
_focusNode.requestFocus();
|
||||
|
||||
if (text.startsWith('/')) {
|
||||
chatComponentCubit.runCommand(text);
|
||||
return;
|
||||
|
@ -491,4 +494,5 @@ class _ChatComponentWidgetState extends State<ChatComponentWidget> {
|
|||
late final TextEditingController _textEditingController;
|
||||
late final ScrollController _scrollController;
|
||||
late final SingleStateProcessor<ChatComponentState> _chatStateProcessor;
|
||||
late final FocusNode _focusNode;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
|
||||
// Watch subkey #1 of the ContactRequest record for accept/reject
|
||||
typedef ContactRequestInboxState = AsyncValue<proto.SignedContactResponse?>;
|
||||
|
||||
class ContactRequestInboxCubit
|
||||
extends DefaultDHTRecordCubit<proto.SignedContactResponse?> {
|
||||
ContactRequestInboxCubit(
|
||||
|
|
|
@ -9,10 +9,62 @@ import 'package:veilid_support/veilid_support.dart';
|
|||
import '../../account_manager/account_manager.dart';
|
||||
import '../../conversation/conversation.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../tools/tools.dart';
|
||||
import '../models/accepted_contact.dart';
|
||||
import 'contact_request_inbox_cubit.dart';
|
||||
|
||||
/// State of WaitingInvitationCubit
|
||||
sealed class WaitingInvitationState
|
||||
implements StateMachineState<WaitingInvitationState> {
|
||||
WaitingInvitationState({required this.global});
|
||||
final WaitingInvitationStateGlobal global;
|
||||
}
|
||||
|
||||
class WaitingInvitationStateGlobal {
|
||||
WaitingInvitationStateGlobal(
|
||||
{required this.accountInfo,
|
||||
required this.accountRecordCubit,
|
||||
required this.contactInvitationRecord});
|
||||
final AccountInfo accountInfo;
|
||||
final AccountRecordCubit accountRecordCubit;
|
||||
final proto.ContactInvitationRecord contactInvitationRecord;
|
||||
}
|
||||
|
||||
/// State of WaitingInvitationCubit:
|
||||
/// Signature was invalid
|
||||
class WaitingInvitationStateInvalidSignature
|
||||
with StateMachineEndState<WaitingInvitationState>
|
||||
implements WaitingInvitationState {
|
||||
const WaitingInvitationStateInvalidSignature({required this.global});
|
||||
|
||||
@override
|
||||
final WaitingInvitationStateGlobal global;
|
||||
}
|
||||
|
||||
/// State of WaitingInvitationCubit:
|
||||
/// Failed to initialize
|
||||
class WaitingInvitationStateInitFailed
|
||||
with StateMachineEndState<WaitingInvitationState>
|
||||
implements WaitingInvitationState {
|
||||
const WaitingInvitationStateInitFailed(
|
||||
{required this.global, required this.exception});
|
||||
|
||||
@override
|
||||
final WaitingInvitationStateGlobal global;
|
||||
final Exception exception;
|
||||
}
|
||||
|
||||
/// State of WaitingInvitationCubit:
|
||||
/// Finished normally with an invitation status
|
||||
class WaitingInvitationStateInvitationStatus
|
||||
with StateMachineEndState<WaitingInvitationState>
|
||||
implements WaitingInvitationState {
|
||||
const WaitingInvitationStateInvitationStatus(
|
||||
{required this.global, required this.status});
|
||||
@override
|
||||
final WaitingInvitationStateGlobal global;
|
||||
final InvitationStatus status;
|
||||
}
|
||||
|
||||
@immutable
|
||||
class InvitationStatus extends Equatable {
|
||||
const InvitationStatus({required this.acceptedContact});
|
||||
|
@ -22,39 +74,55 @@ class InvitationStatus extends Equatable {
|
|||
List<Object?> get props => [acceptedContact];
|
||||
}
|
||||
|
||||
class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
|
||||
proto.SignedContactResponse?> {
|
||||
WaitingInvitationCubit(
|
||||
ContactRequestInboxCubit super.input, {
|
||||
required AccountInfo accountInfo,
|
||||
required AccountRecordCubit accountRecordCubit,
|
||||
required proto.ContactInvitationRecord contactInvitationRecord,
|
||||
}) : super(
|
||||
transform: (signedContactResponse) => _transform(
|
||||
signedContactResponse,
|
||||
accountInfo: accountInfo,
|
||||
accountRecordCubit: accountRecordCubit,
|
||||
contactInvitationRecord: contactInvitationRecord));
|
||||
|
||||
static Future<AsyncValue<InvitationStatus>> _transform(
|
||||
proto.SignedContactResponse? signedContactResponse,
|
||||
{required AccountInfo accountInfo,
|
||||
required AccountRecordCubit accountRecordCubit,
|
||||
required proto.ContactInvitationRecord contactInvitationRecord}) async {
|
||||
/// State of WaitingInvitationCubit:
|
||||
/// Waiting for the invited contact to accept/reject the invitation
|
||||
class WaitingInvitationStateWaitForContactResponse
|
||||
extends AsyncCubitReactorState<
|
||||
WaitingInvitationState,
|
||||
ContactRequestInboxState,
|
||||
ContactRequestInboxCubit> implements WaitingInvitationState {
|
||||
WaitingInvitationStateWaitForContactResponse(super.create,
|
||||
{required this.global})
|
||||
: super(onState: (ctx) async {
|
||||
final signedContactResponse = ctx.state.asData?.value;
|
||||
if (signedContactResponse == null) {
|
||||
return const AsyncValue.loading();
|
||||
return null;
|
||||
}
|
||||
|
||||
final contactResponse = proto.ContactResponse.fromBuffer(
|
||||
signedContactResponse.contactResponse);
|
||||
final contactSuperRecordKey =
|
||||
contactResponse.superIdentityRecordKey.toVeilid();
|
||||
|
||||
// Fetch the remote contact's account superidentity
|
||||
return WaitingInvitationStateWaitForContactSuperIdentity(
|
||||
() => SuperIdentityCubit(superRecordKey: contactSuperRecordKey),
|
||||
global: global,
|
||||
signedContactResponse: signedContactResponse);
|
||||
});
|
||||
|
||||
@override
|
||||
final WaitingInvitationStateGlobal global;
|
||||
}
|
||||
|
||||
/// State of WaitingInvitationCubit:
|
||||
/// Once an accept/reject happens, get the SuperIdentity of the recipient
|
||||
class WaitingInvitationStateWaitForContactSuperIdentity
|
||||
extends AsyncCubitReactorState<WaitingInvitationState, SuperIdentityState,
|
||||
SuperIdentityCubit> implements WaitingInvitationState {
|
||||
WaitingInvitationStateWaitForContactSuperIdentity(super.create,
|
||||
{required this.global,
|
||||
required proto.SignedContactResponse signedContactResponse})
|
||||
: super(onState: (ctx) async {
|
||||
final contactSuperIdentity = ctx.state.asData?.value;
|
||||
if (contactSuperIdentity == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final contactResponseBytes =
|
||||
Uint8List.fromList(signedContactResponse.contactResponse);
|
||||
final contactResponse =
|
||||
proto.ContactResponse.fromBuffer(contactResponseBytes);
|
||||
final contactIdentityMasterRecordKey =
|
||||
contactResponse.superIdentityRecordKey.toVeilid();
|
||||
|
||||
// Fetch the remote contact's account master
|
||||
final contactSuperIdentity = await SuperIdentity.open(
|
||||
superRecordKey: contactIdentityMasterRecordKey);
|
||||
|
||||
// Verify
|
||||
final idcs = await contactSuperIdentity.currentInstance.cryptoSystem;
|
||||
|
@ -62,54 +130,104 @@ class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
|
|||
if (!await idcs.verify(contactSuperIdentity.currentInstance.publicKey,
|
||||
contactResponseBytes, signature)) {
|
||||
// Could not verify signature of contact response
|
||||
return AsyncValue.error('Invalid signature on contact response.');
|
||||
return WaitingInvitationStateInvalidSignature(
|
||||
global: global,
|
||||
);
|
||||
}
|
||||
|
||||
// Check for rejection
|
||||
if (!contactResponse.accept) {
|
||||
// Rejection
|
||||
return const AsyncValue.data(InvitationStatus(acceptedContact: null));
|
||||
return WaitingInvitationStateInvitationStatus(
|
||||
global: global,
|
||||
status: const InvitationStatus(acceptedContact: null),
|
||||
);
|
||||
}
|
||||
|
||||
// Pull profile from remote conversation key
|
||||
final remoteConversationRecordKey =
|
||||
contactResponse.remoteConversationRecordKey.toVeilid();
|
||||
|
||||
final conversation = ConversationCubit(
|
||||
accountInfo: accountInfo,
|
||||
return WaitingInvitationStateWaitForConversation(
|
||||
() => ConversationCubit(
|
||||
accountInfo: global.accountInfo,
|
||||
remoteIdentityPublicKey:
|
||||
contactSuperIdentity.currentInstance.typedPublicKey,
|
||||
remoteConversationRecordKey: remoteConversationRecordKey);
|
||||
remoteConversationRecordKey: remoteConversationRecordKey),
|
||||
global: global,
|
||||
remoteConversationRecordKey: remoteConversationRecordKey,
|
||||
contactSuperIdentity: contactSuperIdentity,
|
||||
);
|
||||
});
|
||||
|
||||
// wait for remote conversation for up to 20 seconds
|
||||
proto.Conversation? remoteConversation;
|
||||
var retryCount = 20;
|
||||
do {
|
||||
await conversation.refresh();
|
||||
remoteConversation = conversation.state.asData?.value.remoteConversation;
|
||||
if (remoteConversation != null) {
|
||||
break;
|
||||
@override
|
||||
final WaitingInvitationStateGlobal global;
|
||||
}
|
||||
log.info('Remote conversation could not be read. Waiting...');
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
retryCount--;
|
||||
} while (retryCount > 0);
|
||||
if (remoteConversation == null) {
|
||||
return AsyncValue.error('Invitation accept timed out.');
|
||||
|
||||
/// State of WaitingInvitationCubit:
|
||||
/// Wait for the conversation cubit to initialize so we can return the
|
||||
/// accepted invitation
|
||||
class WaitingInvitationStateWaitForConversation extends AsyncCubitReactorState<
|
||||
WaitingInvitationState,
|
||||
AsyncValue<ConversationState>,
|
||||
ConversationCubit> implements WaitingInvitationState {
|
||||
WaitingInvitationStateWaitForConversation(super.create,
|
||||
{required this.global,
|
||||
required TypedKey remoteConversationRecordKey,
|
||||
required SuperIdentity contactSuperIdentity})
|
||||
: super(onState: (ctx) async {
|
||||
final remoteConversation = ctx.state.asData?.value.remoteConversation;
|
||||
final localConversation = ctx.state.asData?.value.localConversation;
|
||||
if (remoteConversation == null || localConversation != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Stop reacting to the conversation cubit
|
||||
ctx.stop();
|
||||
|
||||
// Complete the local conversation now that we have the remote profile
|
||||
final remoteProfile = remoteConversation.profile;
|
||||
final localConversationRecordKey =
|
||||
contactInvitationRecord.localConversationRecordKey.toVeilid();
|
||||
return conversation.initLocalConversation(
|
||||
profile: accountRecordCubit.state.asData!.value.profile,
|
||||
existingConversationRecordKey: localConversationRecordKey,
|
||||
callback: (localConversation) async => AsyncValue.data(InvitationStatus(
|
||||
final localConversationRecordKey = global
|
||||
.contactInvitationRecord.localConversationRecordKey
|
||||
.toVeilid();
|
||||
|
||||
try {
|
||||
await ctx.cubit.initLocalConversation(
|
||||
profile: global.accountRecordCubit.state.asData!.value.profile,
|
||||
existingConversationRecordKey: localConversationRecordKey);
|
||||
} on Exception catch (e) {
|
||||
return WaitingInvitationStateInitFailed(
|
||||
global: global, exception: e);
|
||||
}
|
||||
|
||||
return WaitingInvitationStateInvitationStatus(
|
||||
global: global,
|
||||
status: InvitationStatus(
|
||||
acceptedContact: AcceptedContact(
|
||||
remoteProfile: remoteProfile,
|
||||
remoteIdentity: contactSuperIdentity,
|
||||
remoteConversationRecordKey: remoteConversationRecordKey,
|
||||
localConversationRecordKey: localConversationRecordKey))));
|
||||
localConversationRecordKey: localConversationRecordKey)));
|
||||
});
|
||||
|
||||
@override
|
||||
final WaitingInvitationStateGlobal global;
|
||||
}
|
||||
|
||||
/// Invitation state processor for sent invitations
|
||||
class WaitingInvitationCubit extends StateMachineCubit<WaitingInvitationState> {
|
||||
WaitingInvitationCubit({
|
||||
required ContactRequestInboxCubit Function() initialStateCreate,
|
||||
required AccountInfo accountInfo,
|
||||
required AccountRecordCubit accountRecordCubit,
|
||||
required proto.ContactInvitationRecord contactInvitationRecord,
|
||||
}) : super(
|
||||
WaitingInvitationStateWaitForContactResponse(
|
||||
initialStateCreate,
|
||||
global: WaitingInvitationStateGlobal(
|
||||
accountInfo: accountInfo,
|
||||
accountRecordCubit: accountRecordCubit,
|
||||
contactInvitationRecord: contactInvitationRecord),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,13 +10,13 @@ import '../../proto/proto.dart' as proto;
|
|||
import 'cubits.dart';
|
||||
|
||||
typedef WaitingInvitationsBlocMapState
|
||||
= BlocMapState<TypedKey, AsyncValue<InvitationStatus>>;
|
||||
= BlocMapState<TypedKey, WaitingInvitationState>;
|
||||
|
||||
// Map of contactRequestInboxRecordKey to WaitingInvitationCubit
|
||||
// Wraps a contact invitation cubit to watch for accept/reject
|
||||
// Automatically follows the state of a ContactInvitationListCubit.
|
||||
class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
AsyncValue<InvitationStatus>, WaitingInvitationCubit>
|
||||
WaitingInvitationState, WaitingInvitationCubit>
|
||||
with
|
||||
StateMapFollower<DHTShortArrayCubitState<proto.ContactInvitationRecord>,
|
||||
TypedKey, proto.ContactInvitationRecord> {
|
||||
|
@ -45,13 +45,12 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||
await super.close();
|
||||
}
|
||||
|
||||
Future<void> _addWaitingInvitation(
|
||||
{required proto.ContactInvitationRecord
|
||||
contactInvitationRecord}) async =>
|
||||
void _addWaitingInvitation(
|
||||
{required proto.ContactInvitationRecord contactInvitationRecord}) =>
|
||||
add(
|
||||
contactInvitationRecord.contactRequestInbox.recordKey.toVeilid(),
|
||||
() async => WaitingInvitationCubit(
|
||||
ContactRequestInboxCubit(
|
||||
() => WaitingInvitationCubit(
|
||||
initialStateCreate: () => ContactRequestInboxCubit(
|
||||
accountInfo: _accountInfo,
|
||||
contactInvitationRecord: contactInvitationRecord),
|
||||
accountInfo: _accountInfo,
|
||||
|
@ -63,11 +62,30 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||
WaitingInvitationsBlocMapState newState) async {
|
||||
for (final entry in newState.entries) {
|
||||
final contactRequestInboxRecordKey = entry.key;
|
||||
final invStatus = entry.value.asData?.value;
|
||||
// Skip invitations that have not yet been accepted or rejected
|
||||
if (invStatus == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (entry.value) {
|
||||
case WaitingInvitationStateInvalidSignature():
|
||||
// Signature was invalid, display an error and treat like a rejection
|
||||
await _contactInvitationListCubit.deleteInvitation(
|
||||
accepted: false,
|
||||
contactRequestInboxRecordKey: contactRequestInboxRecordKey);
|
||||
|
||||
// Notify about error state
|
||||
_notificationsCubit.error(
|
||||
text: translate('waiting_invitation.invalid'));
|
||||
case final WaitingInvitationStateInitFailed st:
|
||||
// Initialization error, display an error and treat like a rejection
|
||||
await _contactInvitationListCubit.deleteInvitation(
|
||||
accepted: false,
|
||||
contactRequestInboxRecordKey: contactRequestInboxRecordKey);
|
||||
|
||||
// Notify about error state
|
||||
_notificationsCubit.error(
|
||||
text: '${translate('waiting_invitation.init_failed')}\n'
|
||||
'${st.exception}');
|
||||
|
||||
case final WaitingInvitationStateInvitationStatus st:
|
||||
final invStatus = st.status;
|
||||
|
||||
// Delete invitation and process the accepted or rejected contact
|
||||
final acceptedContact = invStatus.acceptedContact;
|
||||
|
@ -102,20 +120,28 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||
'waiting_invitation.rejected',
|
||||
));
|
||||
}
|
||||
case WaitingInvitationStateWaitForContactResponse():
|
||||
// Do nothing, still waiting for contact response
|
||||
break;
|
||||
case WaitingInvitationStateWaitForContactSuperIdentity():
|
||||
// Do nothing, still waiting for contact SuperIdentity
|
||||
break;
|
||||
case WaitingInvitationStateWaitForConversation():
|
||||
// Do nothing, still waiting for conversation
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// StateFollower /////////////////////////
|
||||
|
||||
@override
|
||||
Future<void> removeFromState(TypedKey key) => remove(key);
|
||||
void removeFromState(TypedKey key) => remove(key);
|
||||
|
||||
@override
|
||||
Future<void> updateState(
|
||||
TypedKey key,
|
||||
proto.ContactInvitationRecord? oldValue,
|
||||
proto.ContactInvitationRecord newValue) async {
|
||||
await _addWaitingInvitation(contactInvitationRecord: newValue);
|
||||
void updateState(TypedKey key, proto.ContactInvitationRecord? oldValue,
|
||||
proto.ContactInvitationRecord newValue) {
|
||||
_addWaitingInvitation(contactInvitationRecord: newValue);
|
||||
}
|
||||
|
||||
////
|
||||
|
|
|
@ -47,12 +47,12 @@ class ValidContactInvitation {
|
|||
accountInfo: _accountInfo,
|
||||
remoteIdentityPublicKey:
|
||||
_contactSuperIdentity.currentInstance.typedPublicKey);
|
||||
return conversation.initLocalConversation(
|
||||
profile: profile,
|
||||
callback: (localConversation) async {
|
||||
final localConversationRecordKey =
|
||||
await conversation.initLocalConversation(profile: profile);
|
||||
|
||||
final contactResponse = proto.ContactResponse()
|
||||
..accept = true
|
||||
..remoteConversationRecordKey = localConversation.key.toProto()
|
||||
..remoteConversationRecordKey = localConversationRecordKey.toProto()
|
||||
..superIdentityRecordKey =
|
||||
_accountInfo.superIdentityRecordKey.toProto();
|
||||
final contactResponseBytes = contactResponse.writeToBuffer();
|
||||
|
@ -66,18 +66,17 @@ class ValidContactInvitation {
|
|||
..identitySignature = identitySignature.toProto();
|
||||
|
||||
// Write the acceptance to the inbox
|
||||
await contactRequestInbox
|
||||
.eventualWriteProtobuf(signedContactResponse, subkey: 1);
|
||||
await contactRequestInbox.eventualWriteProtobuf(signedContactResponse,
|
||||
subkey: 1);
|
||||
|
||||
return AcceptedContact(
|
||||
remoteProfile: _contactRequestPrivate.profile,
|
||||
remoteIdentity: _contactSuperIdentity,
|
||||
remoteConversationRecordKey:
|
||||
_contactRequestPrivate.chatRecordKey.toVeilid(),
|
||||
localConversationRecordKey: localConversation.key,
|
||||
localConversationRecordKey: localConversationRecordKey,
|
||||
);
|
||||
});
|
||||
});
|
||||
} on Exception catch (e) {
|
||||
log.debug('exception: $e', e);
|
||||
return null;
|
||||
|
|
|
@ -112,8 +112,8 @@ class PasteInvitationDialogState extends State<PasteInvitationDialog> {
|
|||
constraints: const BoxConstraints(maxHeight: 200),
|
||||
child: TextField(
|
||||
enabled: !dialogState.isValidating,
|
||||
onChanged: (text) async =>
|
||||
_onPasteChanged(text, validateInviteData),
|
||||
autofocus: true,
|
||||
onChanged: (text) => _onPasteChanged(text, validateInviteData),
|
||||
style: monoStyle,
|
||||
keyboardType: TextInputType.multiline,
|
||||
maxLines: null,
|
||||
|
@ -129,9 +129,7 @@ class PasteInvitationDialogState extends State<PasteInvitationDialog> {
|
|||
}
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return InvitationDialog(
|
||||
Widget build(BuildContext context) => InvitationDialog(
|
||||
locator: widget._locator,
|
||||
onValidationCancelled: onValidationCancelled,
|
||||
onValidationSuccess: onValidationSuccess,
|
||||
|
@ -139,4 +137,3 @@ class PasteInvitationDialogState extends State<PasteInvitationDialog> {
|
|||
inviteControlIsValid: inviteControlIsValid,
|
||||
buildInviteControl: buildInviteControl);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -169,7 +169,7 @@ class ScanInvitationDialogState extends State<ScanInvitationDialog> {
|
|||
fit: BoxFit.contain,
|
||||
scanWindow: scanWindow,
|
||||
controller: cameraController,
|
||||
errorBuilder: (context, error, child) =>
|
||||
errorBuilder: (context, error) =>
|
||||
ScannerErrorWidget(error: error),
|
||||
onDetect: (c) {
|
||||
final barcode = c.barcodes.firstOrNull;
|
||||
|
@ -242,6 +242,10 @@ class ScanInvitationDialogState extends State<ScanInvitationDialog> {
|
|||
return const Icon(Icons.camera_front);
|
||||
case CameraFacing.back:
|
||||
return const Icon(Icons.camera_rear);
|
||||
case CameraFacing.external:
|
||||
return const Icon(Icons.camera_alt);
|
||||
case CameraFacing.unknown:
|
||||
return const Icon(Icons.question_mark);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
|
@ -27,8 +27,7 @@ class ContactDetailsWidget extends StatefulWidget {
|
|||
final void Function(bool)? onModifiedState;
|
||||
}
|
||||
|
||||
class _ContactDetailsWidgetState extends State<ContactDetailsWidget>
|
||||
with SingleTickerProviderStateMixin {
|
||||
class _ContactDetailsWidgetState extends State<ContactDetailsWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) => SingleChildScrollView(
|
||||
child: EditContactForm(
|
||||
|
|
|
@ -23,7 +23,6 @@ class ContactItemWidget extends StatelessWidget {
|
|||
_onDelete = onDelete;
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
) {
|
||||
|
|
|
@ -8,7 +8,6 @@ import 'package:searchable_listview/searchable_listview.dart';
|
|||
import 'package:star_menu/star_menu.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../chat_list/chat_list.dart';
|
||||
import '../../contact_invitation/contact_invitation.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../theme/theme.dart';
|
||||
|
@ -45,6 +44,7 @@ class ContactsBrowserElement {
|
|||
class ContactsBrowser extends StatefulWidget {
|
||||
const ContactsBrowser(
|
||||
{required this.onContactSelected,
|
||||
required this.onContactDeleted,
|
||||
required this.onStartChat,
|
||||
this.selectedContactRecordKey,
|
||||
super.key});
|
||||
|
@ -52,6 +52,7 @@ class ContactsBrowser extends StatefulWidget {
|
|||
State<ContactsBrowser> createState() => _ContactsBrowserState();
|
||||
|
||||
final Future<void> Function(proto.Contact? contact) onContactSelected;
|
||||
final Future<void> Function(proto.Contact contact) onContactDeleted;
|
||||
final Future<void> Function(proto.Contact contact) onStartChat;
|
||||
final TypedKey? selectedContactRecordKey;
|
||||
|
||||
|
@ -66,7 +67,10 @@ class ContactsBrowser extends StatefulWidget {
|
|||
'onContactSelected', onContactSelected))
|
||||
..add(
|
||||
ObjectFlagProperty<Future<void> Function(proto.Contact contact)>.has(
|
||||
'onStartChat', onStartChat));
|
||||
'onStartChat', onStartChat))
|
||||
..add(
|
||||
ObjectFlagProperty<Future<void> Function(proto.Contact contact)>.has(
|
||||
'onContactDeleted', onContactDeleted));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,8 +93,6 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||
final menuParams = StarMenuParameters(
|
||||
shape: MenuShape.linear,
|
||||
centerOffset: const Offset(0, 64),
|
||||
// backgroundParams:
|
||||
// BackgroundParams(backgroundColor: theme.shadowColor.withAlpha(128)),
|
||||
boundaryBackground: BoundaryBackground(
|
||||
color: menuBackgroundColor,
|
||||
decoration: ShapeDecoration(
|
||||
|
@ -145,7 +147,7 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||
|
||||
return StarMenu(
|
||||
items: inviteMenuItems,
|
||||
onItemTapped: (_index, controller) {
|
||||
onItemTapped: (_, controller) {
|
||||
controller.closeMenu!();
|
||||
},
|
||||
controller: _invitationMenuController,
|
||||
|
@ -162,16 +164,13 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
//final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||
|
||||
final cilState = context.watch<ContactInvitationListCubit>().state;
|
||||
//final cilBusy = cilState.busy;
|
||||
final contactInvitationRecordList =
|
||||
cilState.state.asData?.value.map((x) => x.value).toIList() ??
|
||||
const IListConst([]);
|
||||
|
||||
final ciState = context.watch<ContactListCubit>().state;
|
||||
//final ciBusy = ciState.busy;
|
||||
final contactList =
|
||||
ciState.state.asData?.value.map((x) => x.value).toIList();
|
||||
|
||||
|
@ -201,8 +200,8 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||
contact.localConversationRecordKey.toVeilid(),
|
||||
disabled: false,
|
||||
onDoubleTap: _onStartChat,
|
||||
onTap: _onSelectContact,
|
||||
onDelete: _onDeleteContact)
|
||||
onTap: onContactSelected,
|
||||
onDelete: _onContactDeleted)
|
||||
.paddingLTRB(0, 4, 0, 0);
|
||||
case ContactsBrowserElementKind.invitation:
|
||||
final invitation = element.invitation!;
|
||||
|
@ -261,7 +260,7 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||
]);
|
||||
}
|
||||
|
||||
Future<void> _onSelectContact(proto.Contact contact) async {
|
||||
Future<void> onContactSelected(proto.Contact contact) async {
|
||||
await widget.onContactSelected(contact);
|
||||
}
|
||||
|
||||
|
@ -269,20 +268,8 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
|||
await widget.onStartChat(contact);
|
||||
}
|
||||
|
||||
Future<void> _onDeleteContact(proto.Contact contact) async {
|
||||
final localConversationRecordKey =
|
||||
contact.localConversationRecordKey.toVeilid();
|
||||
|
||||
final contactListCubit = context.read<ContactListCubit>();
|
||||
final chatListCubit = context.read<ChatListCubit>();
|
||||
|
||||
// Delete the contact itself
|
||||
await contactListCubit.deleteContact(
|
||||
localConversationRecordKey: localConversationRecordKey);
|
||||
|
||||
// Remove any chats for this contact
|
||||
await chatListCubit.deleteChat(
|
||||
localConversationRecordKey: localConversationRecordKey);
|
||||
Future<void> _onContactDeleted(proto.Contact contact) async {
|
||||
await widget.onContactDeleted(contact);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -103,6 +103,7 @@ class _ContactsPageState extends State<ContactsPage> {
|
|||
.toVeilid(),
|
||||
onContactSelected: _onContactSelected,
|
||||
onStartChat: _onChatStarted,
|
||||
onContactDeleted: _onContactDeleted,
|
||||
).paddingLTRB(4, 0, 4, 8)))),
|
||||
if (enableRight && enableLeft)
|
||||
Container(
|
||||
|
@ -157,6 +158,40 @@ class _ContactsPageState extends State<ContactsPage> {
|
|||
}
|
||||
}
|
||||
|
||||
proto.Contact? _selectedContact;
|
||||
bool _isModified = false;
|
||||
Future<bool> _onContactDeleted(proto.Contact contact) async {
|
||||
if (contact == _selectedContact && _isModified) {
|
||||
final ok = await showConfirmModal(
|
||||
context: context,
|
||||
title: translate('confirmation.discard_changes'),
|
||||
text: translate('confirmation.are_you_sure_discard'));
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
_selectedContact = null;
|
||||
_isModified = false;
|
||||
});
|
||||
|
||||
if (mounted) {
|
||||
final localConversationRecordKey =
|
||||
contact.localConversationRecordKey.toVeilid();
|
||||
|
||||
final contactListCubit = context.read<ContactListCubit>();
|
||||
final chatListCubit = context.read<ChatListCubit>();
|
||||
|
||||
// Delete the contact itself
|
||||
await contactListCubit.deleteContact(
|
||||
localConversationRecordKey: localConversationRecordKey);
|
||||
|
||||
// Remove any chats for this contact
|
||||
await chatListCubit.deleteChat(
|
||||
localConversationRecordKey: localConversationRecordKey);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
proto.Contact? _selectedContact;
|
||||
var _isModified = false;
|
||||
}
|
||||
|
|
|
@ -74,11 +74,11 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||
// Private Implementation
|
||||
|
||||
// Add an active conversation to be tracked for changes
|
||||
Future<void> _addDirectConversation(
|
||||
void _addDirectConversation(
|
||||
{required TypedKey remoteIdentityPublicKey,
|
||||
required TypedKey localConversationRecordKey,
|
||||
required TypedKey remoteConversationRecordKey}) async =>
|
||||
add(localConversationRecordKey, () async {
|
||||
required TypedKey remoteConversationRecordKey}) =>
|
||||
add(localConversationRecordKey, () {
|
||||
// Conversation cubit the tracks the state between the local
|
||||
// and remote halves of a contact's relationship with this account
|
||||
final conversationCubit = ConversationCubit(
|
||||
|
@ -129,11 +129,10 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||
/// StateFollower /////////////////////////
|
||||
|
||||
@override
|
||||
Future<void> removeFromState(TypedKey key) => remove(key);
|
||||
void removeFromState(TypedKey key) => remove(key);
|
||||
|
||||
@override
|
||||
Future<void> updateState(
|
||||
TypedKey key, proto.Chat? oldValue, proto.Chat newValue) async {
|
||||
void updateState(TypedKey key, proto.Chat? oldValue, proto.Chat newValue) {
|
||||
switch (newValue.whichKind()) {
|
||||
case proto.Chat_Kind.notSet:
|
||||
throw StateError('unknown chat kind');
|
||||
|
@ -161,7 +160,7 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||
}
|
||||
}
|
||||
|
||||
await _addDirectConversation(
|
||||
_addDirectConversation(
|
||||
remoteIdentityPublicKey: remoteIdentityPublicKey,
|
||||
localConversationRecordKey: localConversationRecordKey,
|
||||
remoteConversationRecordKey: remoteConversationRecordKey);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
@ -53,12 +51,11 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||
follow(activeConversationsBlocMapCubit);
|
||||
}
|
||||
|
||||
Future<void> _addConversationMessages(_SingleContactChatState state) async {
|
||||
// xxx could use atomic update() function
|
||||
await update(state.localConversationRecordKey,
|
||||
onUpdate: (cubit) async =>
|
||||
void _addConversationMessages(_SingleContactChatState state) {
|
||||
update(state.localConversationRecordKey,
|
||||
onUpdate: (cubit) =>
|
||||
cubit.updateRemoteMessagesRecordKey(state.remoteMessagesRecordKey),
|
||||
onCreate: () async => SingleContactMessagesCubit(
|
||||
onCreate: () => SingleContactMessagesCubit(
|
||||
accountInfo: _accountInfo,
|
||||
remoteIdentityPublicKey: state.remoteIdentityPublicKey,
|
||||
localConversationRecordKey: state.localConversationRecordKey,
|
||||
|
@ -87,13 +84,11 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||
/// StateFollower /////////////////////////
|
||||
|
||||
@override
|
||||
Future<void> removeFromState(TypedKey key) => remove(key);
|
||||
void removeFromState(TypedKey key) => remove(key);
|
||||
|
||||
@override
|
||||
Future<void> updateState(
|
||||
TypedKey key,
|
||||
AsyncValue<ActiveConversationState>? oldValue,
|
||||
AsyncValue<ActiveConversationState> newValue) async {
|
||||
void updateState(TypedKey key, AsyncValue<ActiveConversationState>? oldValue,
|
||||
AsyncValue<ActiveConversationState> newValue) {
|
||||
final newState = _mapStateValue(newValue);
|
||||
if (oldValue != null) {
|
||||
final oldState = _mapStateValue(oldValue);
|
||||
|
@ -102,14 +97,14 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
|
|||
}
|
||||
}
|
||||
if (newState != null) {
|
||||
await _addConversationMessages(newState);
|
||||
_addConversationMessages(newState);
|
||||
} else if (newValue.isLoading) {
|
||||
await addState(key, const AsyncValue.loading());
|
||||
addState(key, const AsyncValue.loading());
|
||||
} else {
|
||||
final (error, stackTrace) =
|
||||
(newValue.asError!.error, newValue.asError!.stackTrace);
|
||||
addError(error, stackTrace);
|
||||
await addState(key, AsyncValue.error(error, stackTrace));
|
||||
addState(key, AsyncValue.error(error, stackTrace));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,12 @@ class ConversationState extends Equatable {
|
|||
|
||||
@override
|
||||
List<Object?> get props => [localConversation, remoteConversation];
|
||||
|
||||
@override
|
||||
String toString() => 'ConversationState('
|
||||
'localConversation: ${DynamicDebug.toDebug(localConversation)}, '
|
||||
'remoteConversation: ${DynamicDebug.toDebug(remoteConversation)}'
|
||||
')';
|
||||
}
|
||||
|
||||
/// Represents the control channel between two contacts
|
||||
|
@ -39,13 +45,11 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||
TypedKey? localConversationRecordKey,
|
||||
TypedKey? remoteConversationRecordKey})
|
||||
: _accountInfo = accountInfo,
|
||||
_localConversationRecordKey = localConversationRecordKey,
|
||||
_remoteIdentityPublicKey = remoteIdentityPublicKey,
|
||||
_remoteConversationRecordKey = remoteConversationRecordKey,
|
||||
super(const AsyncValue.loading()) {
|
||||
_identityWriter = _accountInfo.identityWriter;
|
||||
|
||||
if (_localConversationRecordKey != null) {
|
||||
if (localConversationRecordKey != null) {
|
||||
_initWait.add((_) async {
|
||||
await _setLocalConversation(() async {
|
||||
// Open local record key if it is specified
|
||||
|
@ -54,7 +58,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||
final writer = _identityWriter;
|
||||
|
||||
final record = await pool.openRecordWrite(
|
||||
_localConversationRecordKey!, writer,
|
||||
localConversationRecordKey, writer,
|
||||
debugName: 'ConversationCubit::LocalConversation',
|
||||
parent: accountInfo.accountRecordKey,
|
||||
crypto: crypto);
|
||||
|
@ -64,17 +68,17 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||
});
|
||||
}
|
||||
|
||||
if (_remoteConversationRecordKey != null) {
|
||||
if (remoteConversationRecordKey != null) {
|
||||
_initWait.add((cancel) async {
|
||||
await _setRemoteConversation(() async {
|
||||
// Open remote record key if it is specified
|
||||
final pool = DHTRecordPool.instance;
|
||||
final crypto = await _cachedConversationCrypto();
|
||||
|
||||
final record = await pool.openRecordRead(_remoteConversationRecordKey,
|
||||
final record = await pool.openRecordRead(remoteConversationRecordKey,
|
||||
debugName: 'ConversationCubit::RemoteConversation',
|
||||
parent:
|
||||
await pool.getParentRecordKey(_remoteConversationRecordKey) ??
|
||||
await pool.getParentRecordKey(remoteConversationRecordKey) ??
|
||||
accountInfo.accountRecordKey,
|
||||
crypto: crypto);
|
||||
|
||||
|
@ -104,13 +108,11 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||
/// incomplete 'existingConversationRecord' that we need to fill
|
||||
/// in now that we have the remote identity key
|
||||
/// The ConversationCubit must not already have a local conversation
|
||||
/// The callback allows for more initialization to occur and for
|
||||
/// cleanup to delete records upon failure of the callback
|
||||
Future<T> initLocalConversation<T>(
|
||||
/// Returns the local conversation record key that was initialized
|
||||
Future<TypedKey> initLocalConversation(
|
||||
{required proto.Profile profile,
|
||||
required FutureOr<T> Function(DHTRecord) callback,
|
||||
TypedKey? existingConversationRecordKey}) async {
|
||||
assert(_localConversationRecordKey == null,
|
||||
assert(_localConversationCubit == null,
|
||||
'must not have a local conversation yet');
|
||||
|
||||
final pool = DHTRecordPool.instance;
|
||||
|
@ -138,11 +140,8 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||
schema: DHTSchema.smpl(
|
||||
oCnt: 0, members: [DHTSchemaMember(mKey: writer.key, mCnt: 1)]));
|
||||
}
|
||||
final out = localConversationRecord
|
||||
// ignore: prefer_expression_function_bodies
|
||||
.deleteScope((localConversation) async {
|
||||
// Make messages log
|
||||
return _initLocalMessages(
|
||||
await localConversationRecord.deleteScope((localConversation) async {
|
||||
await _initLocalMessages(
|
||||
localConversationKey: localConversation.key,
|
||||
callback: (messages) async {
|
||||
// Create initial local conversation key contents
|
||||
|
@ -158,36 +157,31 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||
if (update != null) {
|
||||
throw Exception('Failed to write local conversation');
|
||||
}
|
||||
final out = await callback(localConversation);
|
||||
|
||||
// Upon success emit the local conversation record to the state
|
||||
_updateLocalConversationState(AsyncValue.data(conversation));
|
||||
|
||||
return out;
|
||||
// If success, save the new local conversation
|
||||
// record key in this object
|
||||
localConversation.ref();
|
||||
await _setLocalConversation(() async => localConversation);
|
||||
});
|
||||
});
|
||||
|
||||
// If success, save the new local conversation record key in this object
|
||||
_localConversationRecordKey = localConversationRecord.key;
|
||||
await _setLocalConversation(() async => localConversationRecord);
|
||||
|
||||
return out;
|
||||
return localConversationRecord.key;
|
||||
}
|
||||
|
||||
/// Force refresh of conversation keys
|
||||
Future<void> refresh() async {
|
||||
await _initWait();
|
||||
// Future<void> refresh() async {
|
||||
// await _initWait();
|
||||
|
||||
final lcc = _localConversationCubit;
|
||||
final rcc = _remoteConversationCubit;
|
||||
// final lcc = _localConversationCubit;
|
||||
// final rcc = _remoteConversationCubit;
|
||||
|
||||
if (lcc != null) {
|
||||
await lcc.refreshDefault();
|
||||
}
|
||||
if (rcc != null) {
|
||||
await rcc.refreshDefault();
|
||||
}
|
||||
}
|
||||
// if (lcc != null) {
|
||||
// await lcc.refreshDefault();
|
||||
// }
|
||||
// if (rcc != null) {
|
||||
// await rcc.refreshDefault();
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Watch for account record changes and update the conversation
|
||||
void watchAccountChanges(Stream<AsyncValue<proto.Account>> accountStream,
|
||||
|
@ -226,12 +220,6 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||
_incrementalState = ConversationState(
|
||||
localConversation: conv,
|
||||
remoteConversation: _incrementalState.remoteConversation);
|
||||
// return loading still if state isn't complete
|
||||
if (_localConversationRecordKey != null &&
|
||||
_incrementalState.localConversation == null) {
|
||||
return const AsyncValue<ConversationState>.loading();
|
||||
}
|
||||
// local state is complete, all remote state is emitted incrementally
|
||||
return AsyncValue.data(_incrementalState);
|
||||
},
|
||||
loading: AsyncValue<ConversationState>.loading,
|
||||
|
@ -246,12 +234,6 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||
_incrementalState = ConversationState(
|
||||
localConversation: _incrementalState.localConversation,
|
||||
remoteConversation: conv);
|
||||
// return loading still if the local state isn't complete
|
||||
if (_localConversationRecordKey != null &&
|
||||
_incrementalState.localConversation == null) {
|
||||
return const AsyncValue<ConversationState>.loading();
|
||||
}
|
||||
// local state is complete, all remote state is emitted incrementally
|
||||
return AsyncValue.data(_incrementalState);
|
||||
},
|
||||
loading: AsyncValue<ConversationState>.loading,
|
||||
|
@ -263,9 +245,12 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||
// Open local converation key
|
||||
Future<void> _setLocalConversation(Future<DHTRecord> Function() open) async {
|
||||
assert(_localConversationCubit == null,
|
||||
'shoud not set local conversation twice');
|
||||
'should not set local conversation twice');
|
||||
_localConversationCubit = DefaultDHTRecordCubit(
|
||||
open: open, decodeState: proto.Conversation.fromBuffer);
|
||||
|
||||
await _localConversationCubit!.ready();
|
||||
|
||||
_localSubscription =
|
||||
_localConversationCubit!.stream.listen(_updateLocalConversationState);
|
||||
_updateLocalConversationState(_localConversationCubit!.state);
|
||||
|
@ -274,9 +259,12 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||
// Open remote converation key
|
||||
Future<void> _setRemoteConversation(Future<DHTRecord> Function() open) async {
|
||||
assert(_remoteConversationCubit == null,
|
||||
'shoud not set remote conversation twice');
|
||||
'should not set remote conversation twice');
|
||||
_remoteConversationCubit = DefaultDHTRecordCubit(
|
||||
open: open, decodeState: proto.Conversation.fromBuffer);
|
||||
|
||||
await _remoteConversationCubit!.ready();
|
||||
|
||||
_remoteSubscription =
|
||||
_remoteConversationCubit!.stream.listen(_updateRemoteConversationState);
|
||||
_updateRemoteConversationState(_remoteConversationCubit!.state);
|
||||
|
@ -316,14 +304,12 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
|||
final AccountInfo _accountInfo;
|
||||
late final KeyPair _identityWriter;
|
||||
final TypedKey _remoteIdentityPublicKey;
|
||||
TypedKey? _localConversationRecordKey;
|
||||
final TypedKey? _remoteConversationRecordKey;
|
||||
DefaultDHTRecordCubit<proto.Conversation>? _localConversationCubit;
|
||||
DefaultDHTRecordCubit<proto.Conversation>? _remoteConversationCubit;
|
||||
StreamSubscription<AsyncValue<proto.Conversation>>? _localSubscription;
|
||||
StreamSubscription<AsyncValue<proto.Conversation>>? _remoteSubscription;
|
||||
StreamSubscription<AsyncValue<proto.Account>>? _accountSubscription;
|
||||
ConversationState _incrementalState = const ConversationState(
|
||||
var _incrementalState = const ConversationState(
|
||||
localConversation: null, remoteConversation: null);
|
||||
VeilidCrypto? _conversationCrypto;
|
||||
final WaitSet<void, void> _initWait = WaitSet();
|
||||
|
|
|
@ -17,6 +17,7 @@ const Map<String, LogLevel> _blocChangeLogLevels = {
|
|||
'PreferencesCubit': LogLevel.debug,
|
||||
'ConversationCubit': LogLevel.debug,
|
||||
'DefaultDHTRecordCubit<Conversation>': LogLevel.debug,
|
||||
'WaitingInvitationCubit': LogLevel.debug,
|
||||
};
|
||||
|
||||
const Map<String, LogLevel> _blocCreateCloseLogLevels = {};
|
||||
|
|
|
@ -2,7 +2,8 @@ PODS:
|
|||
- file_saver (0.0.1):
|
||||
- FlutterMacOS
|
||||
- FlutterMacOS (1.0.0)
|
||||
- mobile_scanner (6.0.2):
|
||||
- mobile_scanner (7.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- package_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
|
@ -33,7 +34,7 @@ PODS:
|
|||
DEPENDENCIES:
|
||||
- file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos`)
|
||||
- mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin`)
|
||||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||
- pasteboard (from `Flutter/ephemeral/.symlinks/plugins/pasteboard/macos`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
|
@ -52,7 +53,7 @@ EXTERNAL SOURCES:
|
|||
FlutterMacOS:
|
||||
:path: Flutter/ephemeral
|
||||
mobile_scanner:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin
|
||||
package_info_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
|
||||
pasteboard:
|
||||
|
@ -79,7 +80,7 @@ EXTERNAL SOURCES:
|
|||
SPEC CHECKSUMS:
|
||||
file_saver: e35bd97de451dde55ff8c38862ed7ad0f3418d0f
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
mobile_scanner: 0e365ed56cad24f28c0fd858ca04edefb40dfac3
|
||||
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
|
||||
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||
pasteboard: 278d8100149f940fb795d6b3a74f0720c890ecb7
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
|
|
|
@ -5,18 +5,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57
|
||||
sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "80.0.0"
|
||||
version: "82.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e"
|
||||
sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.3.0"
|
||||
version: "7.4.5"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -29,10 +29,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.12.0"
|
||||
version: "2.13.0"
|
||||
async_tools:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -53,10 +53,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: bloc_advanced_tools
|
||||
sha256: "7c7f294b425552c2d4831b01ad0d3e1f33f2bdf9acfb7b639caa072781d228cf"
|
||||
sha256: dfb142569814952af8d93e7fe045972d847e29382471687db59913e253202f6e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.10"
|
||||
version: "0.1.12"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -89,6 +89,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
cli_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_config
|
||||
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -117,10 +125,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: coverage
|
||||
sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43
|
||||
sha256: "802bd084fb82e55df091ec8ad1553a7331b61c08251eef19a508b6f3f3a9858d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
version: "1.13.1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -149,18 +157,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
version: "1.3.3"
|
||||
fast_immutable_collections:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fast_immutable_collections
|
||||
sha256: "95a69b9380483dff49ae2c12c9eb92e2b4e1aeff481a33c2a20883471771598a"
|
||||
sha256: d1aa3d7788fab06cce7f303f4969c7a16a10c865e1bd2478291a8ebcbee084e5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.3"
|
||||
version: "11.0.4"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -299,10 +307,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
||||
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.8"
|
||||
version: "10.0.9"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -323,10 +331,10 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: lint_hard
|
||||
sha256: ffe7058cb49e021d244d67e650a63380445b56643c2849c6929e938246b99058
|
||||
sha256: "2073d4e83ac4e3f2b87cc615fff41abb5c2c5618e117edcd3d71f40f2186f4d5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
version: "6.1.1"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -411,10 +419,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12"
|
||||
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.16"
|
||||
version: "2.2.17"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -483,10 +491,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: protobuf
|
||||
sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
|
||||
sha256: "579fe5557eae58e3adca2e999e38f02441d8aa908703854a9e0a0f47fa857731"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "4.1.0"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -658,7 +666,7 @@ packages:
|
|||
path: "../../../../veilid/veilid-flutter"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.4.4"
|
||||
version: "0.4.6"
|
||||
veilid_support:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -677,10 +685,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
|
||||
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.3.1"
|
||||
version: "15.0.0"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -701,26 +709,26 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: web_socket
|
||||
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
|
||||
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.6"
|
||||
version: "1.0.1"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5"
|
||||
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
version: "3.0.3"
|
||||
webdriver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webdriver
|
||||
sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8"
|
||||
sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.4"
|
||||
version: "3.1.0"
|
||||
webkit_inspection_protocol:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -613,7 +613,7 @@ class _DHTLogSpine {
|
|||
// xxx: Don't watch for local changes because this class already handles
|
||||
// xxx: notifying listeners and knows when it makes local changes
|
||||
_subscription ??=
|
||||
await _spineRecord.listen(localChanges: true, _onSpineChanged);
|
||||
await _spineRecord.listen(localChanges: false, _onSpineChanged);
|
||||
await _spineRecord.watch(subkeys: [ValueSubkeyRange.single(0)]);
|
||||
} on Exception {
|
||||
// If anything fails, try to cancel the watches
|
||||
|
|
|
@ -6,14 +6,14 @@ import '../../../veilid_support.dart';
|
|||
class DefaultDHTRecordCubit<T> extends DHTRecordCubit<T> {
|
||||
DefaultDHTRecordCubit({
|
||||
required super.open,
|
||||
required T Function(List<int> data) decodeState,
|
||||
required T Function(Uint8List data) decodeState,
|
||||
}) : super(
|
||||
initialStateFunction: _makeInitialStateFunction(decodeState),
|
||||
stateFunction: _makeStateFunction(decodeState),
|
||||
watchFunction: _makeWatchFunction());
|
||||
|
||||
static InitialStateFunction<T> _makeInitialStateFunction<T>(
|
||||
T Function(List<int> data) decodeState) =>
|
||||
T Function(Uint8List data) decodeState) =>
|
||||
(record) async {
|
||||
final initialData = await record.get();
|
||||
if (initialData == null) {
|
||||
|
@ -23,7 +23,7 @@ class DefaultDHTRecordCubit<T> extends DHTRecordCubit<T> {
|
|||
};
|
||||
|
||||
static StateFunction<T> _makeStateFunction<T>(
|
||||
T Function(List<int> data) decodeState) =>
|
||||
T Function(Uint8List data) decodeState) =>
|
||||
(record, subkeys, updatedata) async {
|
||||
final defaultSubkey = record.subkeyOrDefault(-1);
|
||||
if (subkeys.containsSubkey(defaultSubkey)) {
|
||||
|
|
|
@ -87,6 +87,10 @@ abstract class DHTRecordCubit<T> extends Cubit<AsyncValue<T>> {
|
|||
await super.close();
|
||||
}
|
||||
|
||||
Future<void> ready() async {
|
||||
await initWait();
|
||||
}
|
||||
|
||||
Future<void> refresh(List<ValueSubkeyRange> subkeys) async {
|
||||
await initWait();
|
||||
|
||||
|
@ -111,8 +115,6 @@ abstract class DHTRecordCubit<T> extends Cubit<AsyncValue<T>> {
|
|||
}
|
||||
}
|
||||
|
||||
// DHTRecord get record => _record;
|
||||
|
||||
@protected
|
||||
final WaitSet<void, bool> initWait = WaitSet();
|
||||
|
||||
|
|
|
@ -3,4 +3,5 @@ export 'exceptions.dart';
|
|||
export 'identity.dart';
|
||||
export 'identity_instance.dart';
|
||||
export 'super_identity.dart';
|
||||
export 'super_identity_cubit.dart';
|
||||
export 'writable_super_identity.dart';
|
||||
|
|
|
@ -64,7 +64,40 @@ sealed class SuperIdentity with _$SuperIdentity {
|
|||
|
||||
const SuperIdentity._();
|
||||
|
||||
/// Opens an existing super identity and validates it
|
||||
/// Ensure a SuperIdentity is valid
|
||||
Future<void> validate({required TypedKey superRecordKey}) async {
|
||||
// Validate current IdentityInstance
|
||||
if (!await currentInstance.validateIdentityInstance(
|
||||
superRecordKey: superRecordKey, superPublicKey: publicKey)) {
|
||||
// Invalid current IdentityInstance signature(s)
|
||||
throw IdentityException.invalid;
|
||||
}
|
||||
|
||||
// Validate deprecated IdentityInstances
|
||||
for (final deprecatedInstance in deprecatedInstances) {
|
||||
if (!await deprecatedInstance.validateIdentityInstance(
|
||||
superRecordKey: superRecordKey, superPublicKey: publicKey)) {
|
||||
// Invalid deprecated IdentityInstance signature(s)
|
||||
throw IdentityException.invalid;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate SuperIdentity
|
||||
final deprecatedInstancesSignatures =
|
||||
deprecatedInstances.map((x) => x.signature).toList();
|
||||
if (!await _validateSuperIdentitySignature(
|
||||
recordKey: recordKey,
|
||||
currentInstanceSignature: currentInstance.signature,
|
||||
deprecatedInstancesSignatures: deprecatedInstancesSignatures,
|
||||
deprecatedSuperRecordKeys: deprecatedSuperRecordKeys,
|
||||
publicKey: publicKey,
|
||||
signature: signature)) {
|
||||
// Invalid SuperIdentity signature
|
||||
throw IdentityException.invalid;
|
||||
}
|
||||
}
|
||||
|
||||
/// Opens an existing super identity, validates it, and returns it
|
||||
static Future<SuperIdentity> open({required TypedKey superRecordKey}) async {
|
||||
final pool = DHTRecordPool.instance;
|
||||
|
||||
|
@ -75,37 +108,7 @@ sealed class SuperIdentity with _$SuperIdentity {
|
|||
final superIdentity = (await superRec.getJson(SuperIdentity.fromJson,
|
||||
refreshMode: DHTRecordRefreshMode.network))!;
|
||||
|
||||
// Validate current IdentityInstance
|
||||
if (!await superIdentity.currentInstance.validateIdentityInstance(
|
||||
superRecordKey: superRecordKey,
|
||||
superPublicKey: superIdentity.publicKey)) {
|
||||
// Invalid current IdentityInstance signature(s)
|
||||
throw IdentityException.invalid;
|
||||
}
|
||||
|
||||
// Validate deprecated IdentityInstances
|
||||
for (final deprecatedInstance in superIdentity.deprecatedInstances) {
|
||||
if (!await deprecatedInstance.validateIdentityInstance(
|
||||
superRecordKey: superRecordKey,
|
||||
superPublicKey: superIdentity.publicKey)) {
|
||||
// Invalid deprecated IdentityInstance signature(s)
|
||||
throw IdentityException.invalid;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate SuperIdentity
|
||||
final deprecatedInstancesSignatures =
|
||||
superIdentity.deprecatedInstances.map((x) => x.signature).toList();
|
||||
if (!await _validateSuperIdentitySignature(
|
||||
recordKey: superIdentity.recordKey,
|
||||
currentInstanceSignature: superIdentity.currentInstance.signature,
|
||||
deprecatedInstancesSignatures: deprecatedInstancesSignatures,
|
||||
deprecatedSuperRecordKeys: superIdentity.deprecatedSuperRecordKeys,
|
||||
publicKey: superIdentity.publicKey,
|
||||
signature: superIdentity.signature)) {
|
||||
// Invalid SuperIdentity signature
|
||||
throw IdentityException.invalid;
|
||||
}
|
||||
await superIdentity.validate(superRecordKey: superRecordKey);
|
||||
|
||||
return superIdentity;
|
||||
});
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import 'package:async_tools/async_tools.dart';
|
||||
|
||||
import '../veilid_support.dart';
|
||||
|
||||
typedef SuperIdentityState = AsyncValue<SuperIdentity>;
|
||||
|
||||
class SuperIdentityCubit extends DefaultDHTRecordCubit<SuperIdentity> {
|
||||
SuperIdentityCubit({required TypedKey superRecordKey})
|
||||
: super(
|
||||
open: () => _open(superRecordKey: superRecordKey),
|
||||
decodeState: (buf) => jsonDecodeBytes(SuperIdentity.fromJson, buf));
|
||||
|
||||
static Future<DHTRecord> _open({required TypedKey superRecordKey}) async {
|
||||
final pool = DHTRecordPool.instance;
|
||||
|
||||
return pool.openRecordRead(
|
||||
superRecordKey,
|
||||
debugName: 'SuperIdentityCubit::_open::SuperIdentityRecord',
|
||||
);
|
||||
}
|
||||
}
|
|
@ -5,18 +5,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57
|
||||
sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "80.0.0"
|
||||
version: "82.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e"
|
||||
sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.3.0"
|
||||
version: "7.4.5"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -53,10 +53,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: bloc_advanced_tools
|
||||
sha256: "7c7f294b425552c2d4831b01ad0d3e1f33f2bdf9acfb7b639caa072781d228cf"
|
||||
sha256: dfb142569814952af8d93e7fe045972d847e29382471687db59913e253202f6e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.10"
|
||||
version: "0.1.12"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -161,6 +161,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
cli_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_config
|
||||
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -189,10 +197,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: coverage
|
||||
sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43
|
||||
sha256: "802bd084fb82e55df091ec8ad1553a7331b61c08251eef19a508b6f3f3a9858d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
version: "1.13.1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -205,10 +213,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac"
|
||||
sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.1.0"
|
||||
equatable:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -221,10 +229,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: fast_immutable_collections
|
||||
sha256: "95a69b9380483dff49ae2c12c9eb92e2b4e1aeff481a33c2a20883471771598a"
|
||||
sha256: d1aa3d7788fab06cce7f303f4969c7a16a10c865e1bd2478291a8ebcbee084e5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.3"
|
||||
version: "11.0.4"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -263,10 +271,10 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: freezed
|
||||
sha256: "7ed2ddaa47524976d5f2aa91432a79da36a76969edd84170777ac5bea82d797c"
|
||||
sha256: "6022db4c7bfa626841b2a10f34dd1e1b68e8f8f9650db6112dcdeeca45ca793c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.4"
|
||||
version: "3.0.6"
|
||||
freezed_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -311,10 +319,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
|
||||
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "1.4.0"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -367,18 +375,18 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: json_serializable
|
||||
sha256: "81f04dee10969f89f604e1249382d46b97a1ccad53872875369622b5bfc9e58a"
|
||||
sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.9.4"
|
||||
version: "6.9.5"
|
||||
lint_hard:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: lint_hard
|
||||
sha256: ffe7058cb49e021d244d67e650a63380445b56643c2849c6929e938246b99058
|
||||
sha256: "2073d4e83ac4e3f2b87cc615fff41abb5c2c5618e117edcd3d71f40f2186f4d5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
version: "6.1.1"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -463,10 +471,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12"
|
||||
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.16"
|
||||
version: "2.2.17"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -527,10 +535,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: protobuf
|
||||
sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
|
||||
sha256: "579fe5557eae58e3adca2e999e38f02441d8aa908703854a9e0a0f47fa857731"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "4.1.0"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -684,26 +692,26 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: test
|
||||
sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e"
|
||||
sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.25.15"
|
||||
version: "1.26.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
version: "0.7.6"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa"
|
||||
sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.8"
|
||||
version: "0.6.11"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -734,15 +742,15 @@ packages:
|
|||
path: "../../../veilid/veilid-flutter"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.4.4"
|
||||
version: "0.4.6"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||
sha256: "6f82e9ee8e7339f5d8b699317f6f3afc17c80a68ebef1bc0d6f52a678c14b1e6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.0"
|
||||
version: "15.0.1"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -763,18 +771,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: web_socket
|
||||
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
|
||||
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.6"
|
||||
version: "1.0.1"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5"
|
||||
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
version: "3.0.3"
|
||||
webkit_inspection_protocol:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -9,7 +9,7 @@ environment:
|
|||
dependencies:
|
||||
async_tools: ^0.1.9
|
||||
bloc: ^9.0.0
|
||||
bloc_advanced_tools: ^0.1.10
|
||||
bloc_advanced_tools: ^0.1.12
|
||||
charcode: ^1.4.0
|
||||
collection: ^1.19.1
|
||||
convert: ^3.1.2
|
||||
|
@ -23,7 +23,7 @@ dependencies:
|
|||
|
||||
path: ^1.9.1
|
||||
path_provider: ^2.1.5
|
||||
protobuf: ^3.1.0
|
||||
protobuf: ^4.1.0
|
||||
veilid:
|
||||
# veilid: ^0.0.1
|
||||
path: ../../../veilid/veilid-flutter
|
||||
|
|
74
pubspec.lock
74
pubspec.lock
|
@ -157,10 +157,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: bloc_advanced_tools
|
||||
sha256: "7c7f294b425552c2d4831b01ad0d3e1f33f2bdf9acfb7b639caa072781d228cf"
|
||||
sha256: dfb142569814952af8d93e7fe045972d847e29382471687db59913e253202f6e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.10"
|
||||
version: "0.1.12"
|
||||
blurry_modal_progress_hud:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -277,10 +277,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: camera_android_camerax
|
||||
sha256: ea7e40bd63afb8f55058e48ec529ce96562be9d08393f79631a06f781161fd0d
|
||||
sha256: "9fb44e73e0fea3647a904dc26d38db24055e5b74fc68fd2b6d3abfa1bd20f536"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.16"
|
||||
version: "0.6.17"
|
||||
camera_avfoundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -437,10 +437,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac"
|
||||
sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.1.0"
|
||||
diffutil_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -477,10 +477,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: expansion_tile_group
|
||||
sha256: "3be10b81d6d99d1213fe76a285993be0ea6092565ac100152deb6cdf9f5521dc"
|
||||
sha256: "894c5088d94dda5d1ddde50463881935ff41b15850fe674605b9003d09716c8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.3.0"
|
||||
fast_immutable_collections:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -554,18 +554,18 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_chat_core
|
||||
sha256: "529959634622e9df3b96a4a3764ecc61ec6f0dfa3258a52c139ae10a56ccad80"
|
||||
sha256: "7875785bc4aa0b1dce56a76d2a8bd65841c130a3deb2c527878ebfdf8c54f971"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.3.0"
|
||||
flutter_chat_ui:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_chat_ui
|
||||
sha256: c63df9cd05fe86a3588b4e47f184fbb9e9c3b86153b8a97f3a789e6edb03d28e
|
||||
sha256: "012aa0d9cc2898b8f89b48f66adb106de9547e466ba21ad54ccef25515f68dcc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.3.0"
|
||||
flutter_form_builder:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -631,10 +631,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_sticky_header
|
||||
sha256: "7f76d24d119424ca0c95c146b8627a457e8de8169b0d584f766c2c545db8f8be"
|
||||
sha256: fb4fda6164ef3e5fc7ab73aba34aad253c17b7c6ecf738fa26f1a905b7d2d1e2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
version: "0.8.0"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -724,10 +724,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3
|
||||
sha256: "0b1e06223bee260dee31a171fb1153e306907563a0b0225e8c1733211911429a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.8.1"
|
||||
version: "15.1.2"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -788,10 +788,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: idb_shim
|
||||
sha256: d3dae2085f2dcc9d05b851331fddb66d57d3447ff800de9676b396795436e135
|
||||
sha256: "40e872276d79a1a97cc2c1ea0ecf046b8e34d788f16a8ea8f0da3e9b337d42da"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.5+1"
|
||||
version: "2.6.6+1"
|
||||
image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -812,10 +812,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
version: "0.20.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -916,10 +916,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: mobile_scanner
|
||||
sha256: f536c5b8cadcf73d764bdce09c94744f06aa832264730f8971b21a60c5666826
|
||||
sha256: "72f06a071aa8b14acea3ab43ea7949eefe4a2469731ae210e006ba330a033a8c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.10"
|
||||
version: "7.0.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -964,10 +964,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: pasteboard
|
||||
sha256: "7bf733f3a00c7188ec1f2c6f0612854248b302cf91ef3611a2b7bb141c0f9d55"
|
||||
sha256: "9ff73ada33f79a59ff91f6c01881fd4ed0a0031cfc4ae2d86c0384471525fca1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
version: "0.4.0"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1132,10 +1132,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: protobuf
|
||||
sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
|
||||
sha256: "579fe5557eae58e3adca2e999e38f02441d8aa908703854a9e0a0f47fa857731"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "4.1.0"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1317,18 +1317,18 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da
|
||||
sha256: b2961506569e28948d75ec346c28775bb111986bb69dc6a20754a457e3d97fa0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.1.4"
|
||||
version: "11.0.0"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b
|
||||
sha256: "1032d392bc5d2095a77447a805aa3f804d2ae6a4d5eef5e6ebb3bd94c1bc19ef"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.2"
|
||||
version: "6.0.0"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1603,10 +1603,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "6c7653816b1c938e121b69ff63a33c9dc68102b65a5fb0a5c0f9786256ed33e6"
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.5"
|
||||
version: "0.7.6"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1731,10 +1731,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: value_layout_builder
|
||||
sha256: c02511ea91ca5c643b514a33a38fa52536f74aa939ec367d02938b5ede6807fa
|
||||
sha256: ab4b7d98bac8cefeb9713154d43ee0477490183f5aa23bb4ffa5103d9bbf6275
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.0"
|
||||
version: "0.5.0"
|
||||
vector_graphics:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1755,10 +1755,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad"
|
||||
sha256: "557a315b7d2a6dbb0aaaff84d857967ce6bdc96a63dc6ee2a57ce5a6ee5d3331"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.16"
|
||||
version: "1.1.17"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1887,4 +1887,4 @@ packages:
|
|||
version: "1.1.2"
|
||||
sdks:
|
||||
dart: ">=3.7.0 <4.0.0"
|
||||
flutter: ">=3.29.0"
|
||||
flutter: ">=3.32.0"
|
||||
|
|
19
pubspec.yaml
19
pubspec.yaml
|
@ -5,7 +5,7 @@ version: 0.4.7+20
|
|||
|
||||
environment:
|
||||
sdk: ">=3.2.0 <4.0.0"
|
||||
flutter: ">=3.22.1"
|
||||
flutter: ">=3.32.0"
|
||||
|
||||
dependencies:
|
||||
accordion: ^2.6.0
|
||||
|
@ -21,7 +21,7 @@ dependencies:
|
|||
badges: ^3.1.2
|
||||
basic_utils: ^5.8.2
|
||||
bloc: ^9.0.0
|
||||
bloc_advanced_tools: ^0.1.10
|
||||
bloc_advanced_tools: ^0.1.12
|
||||
blurry_modal_progress_hud: ^1.1.1
|
||||
change_case: ^2.2.0
|
||||
charcode: ^1.4.0
|
||||
|
@ -46,29 +46,29 @@ dependencies:
|
|||
flutter_native_splash: ^2.4.5
|
||||
flutter_slidable: ^4.0.0
|
||||
flutter_spinkit: ^5.2.1
|
||||
flutter_sticky_header: ^0.7.0
|
||||
flutter_sticky_header: ^0.8.0
|
||||
flutter_svg: ^2.0.17
|
||||
flutter_translate: ^4.1.0
|
||||
flutter_zoom_drawer: ^3.2.0
|
||||
form_builder_validators: ^11.1.2
|
||||
freezed_annotation: ^3.0.0
|
||||
go_router: ^14.8.1
|
||||
go_router: ^15.1.2
|
||||
image: ^4.5.3
|
||||
intl: ^0.19.0
|
||||
json_annotation: ^4.9.0
|
||||
keyboard_avoider: ^0.2.0
|
||||
loggy: ^2.0.3
|
||||
meta: ^1.16.0
|
||||
mobile_scanner: ^6.0.7
|
||||
mobile_scanner: ^7.0.0
|
||||
package_info_plus: ^8.3.0
|
||||
pasteboard: ^0.3.0
|
||||
pasteboard: ^0.4.0
|
||||
path: ^1.9.1
|
||||
path_provider: ^2.1.5
|
||||
pdf: ^3.11.3
|
||||
pinput: ^5.0.1
|
||||
preload_page_view: ^0.2.0
|
||||
printing: ^5.14.2
|
||||
protobuf: ^3.1.0
|
||||
protobuf: ^4.1.0
|
||||
provider: ^6.1.2
|
||||
qr_code_dart_scan: ^0.10.0
|
||||
qr_flutter: ^4.1.0
|
||||
|
@ -81,7 +81,7 @@ dependencies:
|
|||
git:
|
||||
url: https://gitlab.com/veilid/Searchable-Listview.git
|
||||
ref: main
|
||||
share_plus: ^10.1.4
|
||||
share_plus: ^11.0.0
|
||||
shared_preferences: ^2.5.2
|
||||
signal_strength_indicator: ^0.4.1
|
||||
sliver_expandable: ^1.1.2
|
||||
|
@ -108,7 +108,8 @@ dependencies:
|
|||
xterm: ^4.0.0
|
||||
zxing2: ^0.2.3
|
||||
|
||||
# dependency_overrides:
|
||||
dependency_overrides:
|
||||
intl: ^0.20.2 # Until flutter_translate updates intl
|
||||
# async_tools:
|
||||
# path: ../dart_async_tools
|
||||
# bloc_advanced_tools:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue