diff --git a/assets/i18n/en.json b/assets/i18n/en.json index f0b84a0..53bf461 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -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", diff --git a/lib/account_manager/cubits/per_account_collection_bloc_map_cubit.dart b/lib/account_manager/cubits/per_account_collection_bloc_map_cubit.dart index ded50e4..e63b53e 100644 --- a/lib/account_manager/cubits/per_account_collection_bloc_map_cubit.dart +++ b/lib/account_manager/cubits/per_account_collection_bloc_map_cubit.dart @@ -22,11 +22,11 @@ class PerAccountCollectionBlocMapCubit extends BlocMapCubit _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 removeFromState(TypedKey key) => remove(key); + void removeFromState(TypedKey key) => remove(key); @override - Future 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 { @useResult $Res call( {AccountInfo accountInfo, - AsyncValue? avAccountRecordState, + AsyncValue? 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?, + as AsyncValue?, 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? avAccountRecordState; + final AsyncValue? avAccountRecordState; @override final AccountInfoCubit? accountInfoCubit; @override @@ -326,7 +326,7 @@ abstract mixin class _$PerAccountCollectionStateCopyWith<$Res> @useResult $Res call( {AccountInfo accountInfo, - AsyncValue? avAccountRecordState, + AsyncValue? 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?, + as AsyncValue?, accountInfoCubit: freezed == accountInfoCubit ? _self.accountInfoCubit : accountInfoCubit // ignore: cast_nullable_to_non_nullable diff --git a/lib/account_manager/models/user_login/user_login.freezed.dart b/lib/account_manager/models/user_login/user_login.freezed.dart index c406812..914afb8 100644 --- a/lib/account_manager/models/user_login/user_login.freezed.dart +++ b/lib/account_manager/models/user_login/user_login.freezed.dart @@ -67,8 +67,8 @@ abstract mixin class $UserLoginCopyWith<$Res> { _$UserLoginCopyWithImpl; @useResult $Res call( - {Typed superIdentityRecordKey, - Typed 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, + as TypedKey, identitySecret: null == identitySecret - ? _self.identitySecret! + ? _self.identitySecret : identitySecret // ignore: cast_nullable_to_non_nullable - as Typed, + 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 superIdentityRecordKey; + final TypedKey superIdentityRecordKey; // The identity secret as unlocked from the local accounts table @override - final Typed 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 superIdentityRecordKey, - Typed 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, + as TypedKey, identitySecret: null == identitySecret ? _self.identitySecret : identitySecret // ignore: cast_nullable_to_non_nullable - as Typed, + as TypedSecret, accountRecordInfo: null == accountRecordInfo ? _self.accountRecordInfo : accountRecordInfo // ignore: cast_nullable_to_non_nullable diff --git a/lib/account_manager/views/edit_account_page.dart b/lib/account_manager/views/edit_account_page.dart index 9af4719..bb14968 100644 --- a/lib/account_manager/views/edit_account_page.dart +++ b/lib/account_manager/views/edit_account_page.dart @@ -214,9 +214,9 @@ class _EditAccountPageState extends WindowSetupState { // Look up account cubit for this specific account final perAccountCollectionBlocMapCubit = context.read(); - final accountRecordCubit = await perAccountCollectionBlocMapCubit - .operate(widget.superIdentityRecordKey, - closure: (c) async => c.accountRecordCubit); + final accountRecordCubit = perAccountCollectionBlocMapCubit + .entry(widget.superIdentityRecordKey) + ?.accountRecordCubit; if (accountRecordCubit == null) { return false; } diff --git a/lib/chat/cubits/single_contact_messages_cubit.dart b/lib/chat/cubits/single_contact_messages_cubit.dart index 0e20229..878858b 100644 --- a/lib/chat/cubits/single_contact_messages_cubit.dart +++ b/lib/chat/cubits/single_contact_messages_cubit.dart @@ -176,12 +176,11 @@ class SingleContactMessagesCubit extends Cubit { _remoteIdentityPublicKey, rcvdMessagesDHTLog); } - Future 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; diff --git a/lib/chat/models/chat_component_state.freezed.dart b/lib/chat/models/chat_component_state.freezed.dart index dd5e68e..bbecc7a 100644 --- a/lib/chat/models/chat_component_state.freezed.dart +++ b/lib/chat/models/chat_component_state.freezed.dart @@ -67,9 +67,9 @@ abstract mixin class $ChatComponentStateCopyWith<$Res> { @useResult $Res call( {User? localUser, - IMap remoteUsers, - IMap historicalRemoteUsers, - IMap unknownUsers, + IMap remoteUsers, + IMap historicalRemoteUsers, + IMap unknownUsers, AsyncValue> 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, + as IMap, historicalRemoteUsers: null == historicalRemoteUsers - ? _self.historicalRemoteUsers! + ? _self.historicalRemoteUsers : historicalRemoteUsers // ignore: cast_nullable_to_non_nullable - as IMap, + as IMap, unknownUsers: null == unknownUsers - ? _self.unknownUsers! + ? _self.unknownUsers : unknownUsers // ignore: cast_nullable_to_non_nullable - as IMap, + as IMap, 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 remoteUsers; + final IMap remoteUsers; // Historical remote users @override - final IMap historicalRemoteUsers; + final IMap historicalRemoteUsers; // Unknown users @override - final IMap unknownUsers; + final IMap unknownUsers; // Messages state @override final AsyncValue> messageWindow; @@ -227,9 +227,9 @@ abstract mixin class _$ChatComponentStateCopyWith<$Res> @useResult $Res call( {User? localUser, - IMap remoteUsers, - IMap historicalRemoteUsers, - IMap unknownUsers, + IMap remoteUsers, + IMap historicalRemoteUsers, + IMap unknownUsers, AsyncValue> messageWindow, String title}); @@ -267,15 +267,15 @@ class __$ChatComponentStateCopyWithImpl<$Res> remoteUsers: null == remoteUsers ? _self.remoteUsers : remoteUsers // ignore: cast_nullable_to_non_nullable - as IMap, + as IMap, historicalRemoteUsers: null == historicalRemoteUsers ? _self.historicalRemoteUsers : historicalRemoteUsers // ignore: cast_nullable_to_non_nullable - as IMap, + as IMap, unknownUsers: null == unknownUsers ? _self.unknownUsers : unknownUsers // ignore: cast_nullable_to_non_nullable - as IMap, + as IMap, messageWindow: null == messageWindow ? _self.messageWindow : messageWindow // ignore: cast_nullable_to_non_nullable diff --git a/lib/chat/views/chat_component_widget.dart b/lib/chat/views/chat_component_widget.dart index 5ecf6e9..7d90d89 100644 --- a/lib/chat/views/chat_component_widget.dart +++ b/lib/chat/views/chat_component_widget.dart @@ -54,10 +54,9 @@ class ChatComponentWidget extends StatefulWidget { final contactListCubit = context.watch(); // Get the active conversation cubit - final activeConversationCubit = context - .select( - (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( - (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 { _textEditingController = TextEditingController(); _scrollController = ScrollController(); _chatStateProcessor = SingleStateProcessor(); + _focusNode = FocusNode(); - final _chatComponentCubit = context.read(); - _chatStateProcessor.follow(_chatComponentCubit.stream, - _chatComponentCubit.state, _updateChatState); + final chatComponentCubit = context.read(); + _chatStateProcessor.follow( + chatComponentCubit.stream, chatComponentCubit.state, _updateChatState); super.initState(); } @@ -118,6 +117,7 @@ class _ChatComponentWidgetState extends State { void dispose() { unawaited(_chatStateProcessor.close()); + _focusNode.dispose(); _chatController.dispose(); _scrollController.dispose(); _textEditingController.dispose(); @@ -281,6 +281,7 @@ class _ChatComponentWidgetState extends State { // Composer builder composerBuilder: (ctx) => VcComposerWidget( autofocus: true, + focusNode: _focusNode, textInputAction: isAnyMobile ? TextInputAction.newline : TextInputAction.send, @@ -399,6 +400,8 @@ class _ChatComponentWidgetState extends State { } void _handleSendPressed(ChatComponentCubit chatComponentCubit, String text) { + _focusNode.requestFocus(); + if (text.startsWith('/')) { chatComponentCubit.runCommand(text); return; @@ -491,4 +494,5 @@ class _ChatComponentWidgetState extends State { late final TextEditingController _textEditingController; late final ScrollController _scrollController; late final SingleStateProcessor _chatStateProcessor; + late final FocusNode _focusNode; } diff --git a/lib/contact_invitation/cubits/contact_request_inbox_cubit.dart b/lib/contact_invitation/cubits/contact_request_inbox_cubit.dart index 714201b..198ae85 100644 --- a/lib/contact_invitation/cubits/contact_request_inbox_cubit.dart +++ b/lib/contact_invitation/cubits/contact_request_inbox_cubit.dart @@ -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; + class ContactRequestInboxCubit extends DefaultDHTRecordCubit { ContactRequestInboxCubit( diff --git a/lib/contact_invitation/cubits/waiting_invitation_cubit.dart b/lib/contact_invitation/cubits/waiting_invitation_cubit.dart index 47addc2..b712546 100644 --- a/lib/contact_invitation/cubits/waiting_invitation_cubit.dart +++ b/lib/contact_invitation/cubits/waiting_invitation_cubit.dart @@ -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({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 + implements WaitingInvitationState { + const WaitingInvitationStateInvalidSignature({required this.global}); + + @override + final WaitingInvitationStateGlobal global; +} + +/// State of WaitingInvitationCubit: +/// Failed to initialize +class WaitingInvitationStateInitFailed + with StateMachineEndState + 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 + 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,94 +74,160 @@ class InvitationStatus extends Equatable { List get props => [acceptedContact]; } -class WaitingInvitationCubit extends AsyncTransformerCubit { - WaitingInvitationCubit( - ContactRequestInboxCubit super.input, { +/// 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 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 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); + + // Verify + final idcs = await contactSuperIdentity.currentInstance.cryptoSystem; + final signature = signedContactResponse.identitySignature.toVeilid(); + if (!await idcs.verify(contactSuperIdentity.currentInstance.publicKey, + contactResponseBytes, signature)) { + // Could not verify signature of contact response + return WaitingInvitationStateInvalidSignature( + global: global, + ); + } + + // Check for rejection + if (!contactResponse.accept) { + // Rejection + return WaitingInvitationStateInvitationStatus( + global: global, + status: const InvitationStatus(acceptedContact: null), + ); + } + + // Pull profile from remote conversation key + final remoteConversationRecordKey = + contactResponse.remoteConversationRecordKey.toVeilid(); + + return WaitingInvitationStateWaitForConversation( + () => ConversationCubit( + accountInfo: global.accountInfo, + remoteIdentityPublicKey: + contactSuperIdentity.currentInstance.typedPublicKey, + remoteConversationRecordKey: remoteConversationRecordKey), + global: global, + remoteConversationRecordKey: remoteConversationRecordKey, + contactSuperIdentity: contactSuperIdentity, + ); + }); + + @override + final WaitingInvitationStateGlobal global; +} + +/// State of WaitingInvitationCubit: +/// Wait for the conversation cubit to initialize so we can return the +/// accepted invitation +class WaitingInvitationStateWaitForConversation extends AsyncCubitReactorState< + WaitingInvitationState, + AsyncValue, + 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 = 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))); + }); + + @override + final WaitingInvitationStateGlobal global; +} + +/// Invitation state processor for sent invitations +class WaitingInvitationCubit extends StateMachineCubit { + WaitingInvitationCubit({ + required ContactRequestInboxCubit Function() initialStateCreate, required AccountInfo accountInfo, required AccountRecordCubit accountRecordCubit, required proto.ContactInvitationRecord contactInvitationRecord, }) : super( - transform: (signedContactResponse) => _transform( - signedContactResponse, + WaitingInvitationStateWaitForContactResponse( + initialStateCreate, + global: WaitingInvitationStateGlobal( accountInfo: accountInfo, accountRecordCubit: accountRecordCubit, - contactInvitationRecord: contactInvitationRecord)); - - static Future> _transform( - proto.SignedContactResponse? signedContactResponse, - {required AccountInfo accountInfo, - required AccountRecordCubit accountRecordCubit, - required proto.ContactInvitationRecord contactInvitationRecord}) async { - if (signedContactResponse == null) { - return const AsyncValue.loading(); - } - - 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; - final signature = signedContactResponse.identitySignature.toVeilid(); - if (!await idcs.verify(contactSuperIdentity.currentInstance.publicKey, - contactResponseBytes, signature)) { - // Could not verify signature of contact response - return AsyncValue.error('Invalid signature on contact response.'); - } - - // Check for rejection - if (!contactResponse.accept) { - // Rejection - return const AsyncValue.data(InvitationStatus(acceptedContact: null)); - } - - // Pull profile from remote conversation key - final remoteConversationRecordKey = - contactResponse.remoteConversationRecordKey.toVeilid(); - - final conversation = ConversationCubit( - accountInfo: accountInfo, - remoteIdentityPublicKey: - contactSuperIdentity.currentInstance.typedPublicKey, - remoteConversationRecordKey: remoteConversationRecordKey); - - // 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; - } - log.info('Remote conversation could not be read. Waiting...'); - await Future.delayed(const Duration(seconds: 1)); - retryCount--; - } while (retryCount > 0); - if (remoteConversation == null) { - return AsyncValue.error('Invitation accept timed out.'); - } - - // 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( - acceptedContact: AcceptedContact( - remoteProfile: remoteProfile, - remoteIdentity: contactSuperIdentity, - remoteConversationRecordKey: remoteConversationRecordKey, - localConversationRecordKey: localConversationRecordKey)))); - } + contactInvitationRecord: contactInvitationRecord), + ), + ); } diff --git a/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart b/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart index 197ba8c..f125e71 100644 --- a/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart +++ b/lib/contact_invitation/cubits/waiting_invitations_bloc_map_cubit.dart @@ -10,13 +10,13 @@ import '../../proto/proto.dart' as proto; import 'cubits.dart'; typedef WaitingInvitationsBlocMapState - = BlocMapState>; + = BlocMapState; // 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, WaitingInvitationCubit> + WaitingInvitationState, WaitingInvitationCubit> with StateMapFollower, TypedKey, proto.ContactInvitationRecord> { @@ -45,13 +45,12 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit _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,44 +62,73 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit removeFromState(TypedKey key) => remove(key); + void removeFromState(TypedKey key) => remove(key); @override - Future 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); } //// diff --git a/lib/contact_invitation/models/valid_contact_invitation.dart b/lib/contact_invitation/models/valid_contact_invitation.dart index f19e951..c39692c 100644 --- a/lib/contact_invitation/models/valid_contact_invitation.dart +++ b/lib/contact_invitation/models/valid_contact_invitation.dart @@ -47,36 +47,35 @@ class ValidContactInvitation { accountInfo: _accountInfo, remoteIdentityPublicKey: _contactSuperIdentity.currentInstance.typedPublicKey); - return conversation.initLocalConversation( - profile: profile, - callback: (localConversation) async { - final contactResponse = proto.ContactResponse() - ..accept = true - ..remoteConversationRecordKey = localConversation.key.toProto() - ..superIdentityRecordKey = - _accountInfo.superIdentityRecordKey.toProto(); - final contactResponseBytes = contactResponse.writeToBuffer(); + final localConversationRecordKey = + await conversation.initLocalConversation(profile: profile); - final cs = await _accountInfo.identityCryptoSystem; - final identitySignature = await cs.signWithKeyPair( - _accountInfo.identityWriter, contactResponseBytes); + final contactResponse = proto.ContactResponse() + ..accept = true + ..remoteConversationRecordKey = localConversationRecordKey.toProto() + ..superIdentityRecordKey = + _accountInfo.superIdentityRecordKey.toProto(); + final contactResponseBytes = contactResponse.writeToBuffer(); - final signedContactResponse = proto.SignedContactResponse() - ..contactResponse = contactResponseBytes - ..identitySignature = identitySignature.toProto(); + final cs = await _accountInfo.identityCryptoSystem; + final identitySignature = await cs.signWithKeyPair( + _accountInfo.identityWriter, contactResponseBytes); - // Write the acceptance to the inbox - await contactRequestInbox - .eventualWriteProtobuf(signedContactResponse, subkey: 1); + final signedContactResponse = proto.SignedContactResponse() + ..contactResponse = contactResponseBytes + ..identitySignature = identitySignature.toProto(); - return AcceptedContact( - remoteProfile: _contactRequestPrivate.profile, - remoteIdentity: _contactSuperIdentity, - remoteConversationRecordKey: - _contactRequestPrivate.chatRecordKey.toVeilid(), - localConversationRecordKey: localConversation.key, - ); - }); + // Write the acceptance to the inbox + await contactRequestInbox.eventualWriteProtobuf(signedContactResponse, + subkey: 1); + + return AcceptedContact( + remoteProfile: _contactRequestPrivate.profile, + remoteIdentity: _contactSuperIdentity, + remoteConversationRecordKey: + _contactRequestPrivate.chatRecordKey.toVeilid(), + localConversationRecordKey: localConversationRecordKey, + ); }); } on Exception catch (e) { log.debug('exception: $e', e); diff --git a/lib/contact_invitation/views/paste_invitation_dialog.dart b/lib/contact_invitation/views/paste_invitation_dialog.dart index 9cd9efc..b014fc2 100644 --- a/lib/contact_invitation/views/paste_invitation_dialog.dart +++ b/lib/contact_invitation/views/paste_invitation_dialog.dart @@ -112,8 +112,8 @@ class PasteInvitationDialogState extends State { 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,14 +129,11 @@ class PasteInvitationDialogState extends State { } @override - // ignore: prefer_expression_function_bodies - Widget build(BuildContext context) { - return InvitationDialog( - locator: widget._locator, - onValidationCancelled: onValidationCancelled, - onValidationSuccess: onValidationSuccess, - onValidationFailed: onValidationFailed, - inviteControlIsValid: inviteControlIsValid, - buildInviteControl: buildInviteControl); - } + Widget build(BuildContext context) => InvitationDialog( + locator: widget._locator, + onValidationCancelled: onValidationCancelled, + onValidationSuccess: onValidationSuccess, + onValidationFailed: onValidationFailed, + inviteControlIsValid: inviteControlIsValid, + buildInviteControl: buildInviteControl); } diff --git a/lib/contact_invitation/views/scan_invitation_dialog.dart b/lib/contact_invitation/views/scan_invitation_dialog.dart index fa8bba9..8fbdf5c 100644 --- a/lib/contact_invitation/views/scan_invitation_dialog.dart +++ b/lib/contact_invitation/views/scan_invitation_dialog.dart @@ -169,7 +169,7 @@ class ScanInvitationDialogState extends State { 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 { 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); } }, ), diff --git a/lib/contacts/views/contact_details_widget.dart b/lib/contacts/views/contact_details_widget.dart index 7b5416e..707dc1e 100644 --- a/lib/contacts/views/contact_details_widget.dart +++ b/lib/contacts/views/contact_details_widget.dart @@ -27,8 +27,7 @@ class ContactDetailsWidget extends StatefulWidget { final void Function(bool)? onModifiedState; } -class _ContactDetailsWidgetState extends State - with SingleTickerProviderStateMixin { +class _ContactDetailsWidgetState extends State { @override Widget build(BuildContext context) => SingleChildScrollView( child: EditContactForm( diff --git a/lib/contacts/views/contact_item_widget.dart b/lib/contacts/views/contact_item_widget.dart index 4614f27..e206570 100644 --- a/lib/contacts/views/contact_item_widget.dart +++ b/lib/contacts/views/contact_item_widget.dart @@ -23,7 +23,6 @@ class ContactItemWidget extends StatelessWidget { _onDelete = onDelete; @override - // ignore: prefer_expression_function_bodies Widget build( BuildContext context, ) { diff --git a/lib/contacts/views/contacts_browser.dart b/lib/contacts/views/contacts_browser.dart index 74cd0b5..84a2601 100644 --- a/lib/contacts/views/contacts_browser.dart +++ b/lib/contacts/views/contacts_browser.dart @@ -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 createState() => _ContactsBrowserState(); final Future Function(proto.Contact? contact) onContactSelected; + final Future Function(proto.Contact contact) onContactDeleted; final Future Function(proto.Contact contact) onStartChat; final TypedKey? selectedContactRecordKey; @@ -66,7 +67,10 @@ class ContactsBrowser extends StatefulWidget { 'onContactSelected', onContactSelected)) ..add( ObjectFlagProperty Function(proto.Contact contact)>.has( - 'onStartChat', onStartChat)); + 'onStartChat', onStartChat)) + ..add( + ObjectFlagProperty Function(proto.Contact contact)>.has( + 'onContactDeleted', onContactDeleted)); } } @@ -89,8 +93,6 @@ class _ContactsBrowserState extends State 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 return StarMenu( items: inviteMenuItems, - onItemTapped: (_index, controller) { + onItemTapped: (_, controller) { controller.closeMenu!(); }, controller: _invitationMenuController, @@ -162,16 +164,13 @@ class _ContactsBrowserState extends State Widget build(BuildContext context) { final theme = Theme.of(context); final scale = theme.extension()!; - //final scaleConfig = theme.extension()!; final cilState = context.watch().state; - //final cilBusy = cilState.busy; final contactInvitationRecordList = cilState.state.asData?.value.map((x) => x.value).toIList() ?? const IListConst([]); final ciState = context.watch().state; - //final ciBusy = ciState.busy; final contactList = ciState.state.asData?.value.map((x) => x.value).toIList(); @@ -201,8 +200,8 @@ class _ContactsBrowserState extends State 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 ]); } - Future _onSelectContact(proto.Contact contact) async { + Future onContactSelected(proto.Contact contact) async { await widget.onContactSelected(contact); } @@ -269,20 +268,8 @@ class _ContactsBrowserState extends State await widget.onStartChat(contact); } - Future _onDeleteContact(proto.Contact contact) async { - final localConversationRecordKey = - contact.localConversationRecordKey.toVeilid(); - - final contactListCubit = context.read(); - final chatListCubit = context.read(); - - // Delete the contact itself - await contactListCubit.deleteContact( - localConversationRecordKey: localConversationRecordKey); - - // Remove any chats for this contact - await chatListCubit.deleteChat( - localConversationRecordKey: localConversationRecordKey); + Future _onContactDeleted(proto.Contact contact) async { + await widget.onContactDeleted(contact); } //////////////////////////////////////////////////////////////////////////// diff --git a/lib/contacts/views/contacts_page.dart b/lib/contacts/views/contacts_page.dart index 0f1731d..26a6f0d 100644 --- a/lib/contacts/views/contacts_page.dart +++ b/lib/contacts/views/contacts_page.dart @@ -103,6 +103,7 @@ class _ContactsPageState extends State { .toVeilid(), onContactSelected: _onContactSelected, onStartChat: _onChatStarted, + onContactDeleted: _onContactDeleted, ).paddingLTRB(4, 0, 4, 8)))), if (enableRight && enableLeft) Container( @@ -157,6 +158,40 @@ class _ContactsPageState extends State { } } + Future _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(); + final chatListCubit = context.read(); + + // 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; - bool _isModified = false; + var _isModified = false; } diff --git a/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart b/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart index 08a249f..3c00eba 100644 --- a/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart +++ b/lib/conversation/cubits/active_conversations_bloc_map_cubit.dart @@ -74,11 +74,11 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit _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 removeFromState(TypedKey key) => remove(key); + void removeFromState(TypedKey key) => remove(key); @override - Future 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 _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 removeFromState(TypedKey key) => remove(key); + void removeFromState(TypedKey key) => remove(key); @override - Future updateState( - TypedKey key, - AsyncValue? oldValue, - AsyncValue newValue) async { + void updateState(TypedKey key, AsyncValue? oldValue, + AsyncValue newValue) { final newState = _mapStateValue(newValue); if (oldValue != null) { final oldState = _mapStateValue(oldValue); @@ -102,14 +97,14 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit 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> { 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> { 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> { }); } - 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> { /// 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 initLocalConversation( + /// Returns the local conversation record key that was initialized + Future initLocalConversation( {required proto.Profile profile, - required FutureOr 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> { 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> { 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 refresh() async { - await _initWait(); + // Future 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> accountStream, @@ -226,12 +220,6 @@ class ConversationCubit extends Cubit> { _incrementalState = ConversationState( localConversation: conv, remoteConversation: _incrementalState.remoteConversation); - // return loading still if state isn't complete - if (_localConversationRecordKey != null && - _incrementalState.localConversation == null) { - return const AsyncValue.loading(); - } - // local state is complete, all remote state is emitted incrementally return AsyncValue.data(_incrementalState); }, loading: AsyncValue.loading, @@ -246,12 +234,6 @@ class ConversationCubit extends Cubit> { _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.loading(); - } - // local state is complete, all remote state is emitted incrementally return AsyncValue.data(_incrementalState); }, loading: AsyncValue.loading, @@ -263,9 +245,12 @@ class ConversationCubit extends Cubit> { // Open local converation key Future _setLocalConversation(Future 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> { // Open remote converation key Future _setRemoteConversation(Future 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> { final AccountInfo _accountInfo; late final KeyPair _identityWriter; final TypedKey _remoteIdentityPublicKey; - TypedKey? _localConversationRecordKey; - final TypedKey? _remoteConversationRecordKey; DefaultDHTRecordCubit? _localConversationCubit; DefaultDHTRecordCubit? _remoteConversationCubit; StreamSubscription>? _localSubscription; StreamSubscription>? _remoteSubscription; StreamSubscription>? _accountSubscription; - ConversationState _incrementalState = const ConversationState( + var _incrementalState = const ConversationState( localConversation: null, remoteConversation: null); VeilidCrypto? _conversationCrypto; final WaitSet _initWait = WaitSet(); diff --git a/lib/tools/state_logger.dart b/lib/tools/state_logger.dart index a133346..8782662 100644 --- a/lib/tools/state_logger.dart +++ b/lib/tools/state_logger.dart @@ -17,6 +17,7 @@ const Map _blocChangeLogLevels = { 'PreferencesCubit': LogLevel.debug, 'ConversationCubit': LogLevel.debug, 'DefaultDHTRecordCubit': LogLevel.debug, + 'WaitingInvitationCubit': LogLevel.debug, }; const Map _blocCreateCloseLogLevels = {}; diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 2d40a21..beb7d0e 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -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 diff --git a/packages/veilid_support/example/pubspec.lock b/packages/veilid_support/example/pubspec.lock index c40540e..9af9773 100644 --- a/packages/veilid_support/example/pubspec.lock +++ b/packages/veilid_support/example/pubspec.lock @@ -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: diff --git a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart index 93cdcc4..6323692 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_log/dht_log_spine.dart @@ -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 diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/default_dht_record_cubit.dart b/packages/veilid_support/lib/dht_support/src/dht_record/default_dht_record_cubit.dart index e5fb513..3d396d2 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/default_dht_record_cubit.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/default_dht_record_cubit.dart @@ -6,14 +6,14 @@ import '../../../veilid_support.dart'; class DefaultDHTRecordCubit extends DHTRecordCubit { DefaultDHTRecordCubit({ required super.open, - required T Function(List data) decodeState, + required T Function(Uint8List data) decodeState, }) : super( initialStateFunction: _makeInitialStateFunction(decodeState), stateFunction: _makeStateFunction(decodeState), watchFunction: _makeWatchFunction()); static InitialStateFunction _makeInitialStateFunction( - T Function(List data) decodeState) => + T Function(Uint8List data) decodeState) => (record) async { final initialData = await record.get(); if (initialData == null) { @@ -23,7 +23,7 @@ class DefaultDHTRecordCubit extends DHTRecordCubit { }; static StateFunction _makeStateFunction( - T Function(List data) decodeState) => + T Function(Uint8List data) decodeState) => (record, subkeys, updatedata) async { final defaultSubkey = record.subkeyOrDefault(-1); if (subkeys.containsSubkey(defaultSubkey)) { diff --git a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_cubit.dart b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_cubit.dart index cab5a77..eb56f7c 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_cubit.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record/dht_record_cubit.dart @@ -87,6 +87,10 @@ abstract class DHTRecordCubit extends Cubit> { await super.close(); } + Future ready() async { + await initWait(); + } + Future refresh(List subkeys) async { await initWait(); @@ -111,8 +115,6 @@ abstract class DHTRecordCubit extends Cubit> { } } - // DHTRecord get record => _record; - @protected final WaitSet initWait = WaitSet(); diff --git a/packages/veilid_support/lib/identity_support/identity_support.dart b/packages/veilid_support/lib/identity_support/identity_support.dart index 463be9a..68723bf 100644 --- a/packages/veilid_support/lib/identity_support/identity_support.dart +++ b/packages/veilid_support/lib/identity_support/identity_support.dart @@ -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'; diff --git a/packages/veilid_support/lib/identity_support/super_identity.dart b/packages/veilid_support/lib/identity_support/super_identity.dart index 5dc0e90..c8fd59d 100644 --- a/packages/veilid_support/lib/identity_support/super_identity.dart +++ b/packages/veilid_support/lib/identity_support/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 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 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; }); diff --git a/packages/veilid_support/lib/identity_support/super_identity_cubit.dart b/packages/veilid_support/lib/identity_support/super_identity_cubit.dart new file mode 100644 index 0000000..9de55ad --- /dev/null +++ b/packages/veilid_support/lib/identity_support/super_identity_cubit.dart @@ -0,0 +1,21 @@ +import 'package:async_tools/async_tools.dart'; + +import '../veilid_support.dart'; + +typedef SuperIdentityState = AsyncValue; + +class SuperIdentityCubit extends DefaultDHTRecordCubit { + SuperIdentityCubit({required TypedKey superRecordKey}) + : super( + open: () => _open(superRecordKey: superRecordKey), + decodeState: (buf) => jsonDecodeBytes(SuperIdentity.fromJson, buf)); + + static Future _open({required TypedKey superRecordKey}) async { + final pool = DHTRecordPool.instance; + + return pool.openRecordRead( + superRecordKey, + debugName: 'SuperIdentityCubit::_open::SuperIdentityRecord', + ); + } +} diff --git a/packages/veilid_support/pubspec.lock b/packages/veilid_support/pubspec.lock index d86402b..b4c7eef 100644 --- a/packages/veilid_support/pubspec.lock +++ b/packages/veilid_support/pubspec.lock @@ -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: diff --git a/packages/veilid_support/pubspec.yaml b/packages/veilid_support/pubspec.yaml index 65ba78d..8864fa6 100644 --- a/packages/veilid_support/pubspec.yaml +++ b/packages/veilid_support/pubspec.yaml @@ -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 diff --git a/pubspec.lock b/pubspec.lock index cdec931..aa97499 100644 --- a/pubspec.lock +++ b/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" diff --git a/pubspec.yaml b/pubspec.yaml index 73ec83a..519714b 100644 --- a/pubspec.yaml +++ b/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,17 +108,18 @@ dependencies: xterm: ^4.0.0 zxing2: ^0.2.3 - # dependency_overrides: - # async_tools: - # path: ../dart_async_tools - # bloc_advanced_tools: - # path: ../bloc_advanced_tools - # searchable_listview: - # path: ../Searchable-Listview - # flutter_chat_core: - # path: ../flutter_chat_ui/packages/flutter_chat_core - # flutter_chat_ui: - # path: ../flutter_chat_ui/packages/flutter_chat_ui +dependency_overrides: + intl: ^0.20.2 # Until flutter_translate updates intl +# async_tools: +# path: ../dart_async_tools +# bloc_advanced_tools: +# path: ../bloc_advanced_tools +# searchable_listview: +# path: ../Searchable-Listview +# flutter_chat_core: +# path: ../flutter_chat_ui/packages/flutter_chat_core +# flutter_chat_ui: +# path: ../flutter_chat_ui/packages/flutter_chat_ui dev_dependencies: build_runner: ^2.4.15