State machine work

This commit is contained in:
Christien Rioux 2025-05-23 11:30:26 -04:00
parent 61855521dc
commit be8014c97a
34 changed files with 703 additions and 505 deletions

View file

@ -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",

View file

@ -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);
}

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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;

View file

@ -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

View file

@ -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;
}

View file

@ -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(

View file

@ -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;
}
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.');
@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<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),
),
);
}

View file

@ -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);
}
////

View file

@ -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;

View file

@ -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,14 +129,11 @@ 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,
onValidationFailed: onValidationFailed,
inviteControlIsValid: inviteControlIsValid,
buildInviteControl: buildInviteControl);
}
}

View file

@ -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);
}
},
),

View file

@ -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(

View file

@ -23,7 +23,6 @@ class ContactItemWidget extends StatelessWidget {
_onDelete = onDelete;
@override
// ignore: prefer_expression_function_bodies
Widget build(
BuildContext context,
) {

View file

@ -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);
}
////////////////////////////////////////////////////////////////////////////

View file

@ -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> {
}
}
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;
bool _isModified = false;
var _isModified = false;
}

View file

@ -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);

View file

@ -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));
}
}

View file

@ -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();

View file

@ -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 = {};

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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)) {

View file

@ -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();

View file

@ -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';

View file

@ -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;
});

View file

@ -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',
);
}
}

View file

@ -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:

View file

@ -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

View file

@ -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"

View file

@ -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