mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2024-10-01 06:55:46 -04:00
clean up context locators
This commit is contained in:
parent
751022e743
commit
2ccad50f9a
@ -1,5 +1,6 @@
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
@ -12,8 +13,12 @@ typedef AccountRecordsBlocMapState
|
||||
class AccountRecordsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
AsyncValue<AccountRecordState>, AccountRecordCubit>
|
||||
with StateMapFollower<UserLoginsState, TypedKey, UserLogin> {
|
||||
AccountRecordsBlocMapCubit(AccountRepository accountRepository)
|
||||
: _accountRepository = accountRepository;
|
||||
AccountRecordsBlocMapCubit(
|
||||
AccountRepository accountRepository, Locator locator)
|
||||
: _accountRepository = accountRepository {
|
||||
// Follow the user logins cubit
|
||||
follow(locator<UserLoginsCubit>());
|
||||
}
|
||||
|
||||
// Add account record cubit
|
||||
Future<void> _addAccountRecordCubit(
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'unlocked_account_info.dart';
|
||||
@ -10,7 +11,7 @@ enum AccountInfoStatus {
|
||||
}
|
||||
|
||||
@immutable
|
||||
class AccountInfo {
|
||||
class AccountInfo extends Equatable {
|
||||
const AccountInfo({
|
||||
required this.status,
|
||||
required this.active,
|
||||
@ -20,4 +21,7 @@ class AccountInfo {
|
||||
final AccountInfoStatus status;
|
||||
final bool active;
|
||||
final UnlockedAccountInfo? unlockedAccountInfo;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status, active, unlockedAccountInfo];
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
@ -7,12 +8,14 @@ import 'local_account/local_account.dart';
|
||||
import 'user_login/user_login.dart';
|
||||
|
||||
@immutable
|
||||
class UnlockedAccountInfo {
|
||||
class UnlockedAccountInfo extends Equatable {
|
||||
const UnlockedAccountInfo({
|
||||
required this.localAccount,
|
||||
required this.userLogin,
|
||||
});
|
||||
//
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Public Interface
|
||||
|
||||
TypedKey get superIdentityRecordKey => localAccount.superIdentity.recordKey;
|
||||
TypedKey get accountRecordKey =>
|
||||
@ -41,7 +44,12 @@ class UnlockedAccountInfo {
|
||||
return messagesCrypto;
|
||||
}
|
||||
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Fields
|
||||
|
||||
final LocalAccount localAccount;
|
||||
final UserLogin userLogin;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [localAccount, userLogin];
|
||||
}
|
||||
|
@ -131,9 +131,8 @@ class VeilidChatApp extends StatelessWidget {
|
||||
PreferencesCubit(PreferencesRepository.instance),
|
||||
),
|
||||
BlocProvider<AccountRecordsBlocMapCubit>(
|
||||
create: (context) =>
|
||||
AccountRecordsBlocMapCubit(AccountRepository.instance)
|
||||
..follow(context.read<UserLoginsCubit>())),
|
||||
create: (context) => AccountRecordsBlocMapCubit(
|
||||
AccountRepository.instance, context.read)),
|
||||
],
|
||||
child: BackgroundTicker(
|
||||
child: _buildShortcuts(
|
||||
@ -141,7 +140,7 @@ class VeilidChatApp extends StatelessWidget {
|
||||
builder: (context) => MaterialApp.router(
|
||||
debugShowCheckedModeBanner: false,
|
||||
routerConfig:
|
||||
context.watch<RouterCubit>().router(),
|
||||
context.read<RouterCubit>().router(),
|
||||
title: translate('app.title'),
|
||||
theme: theme,
|
||||
localizationsDelegates: [
|
||||
|
@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
|
||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
@ -44,23 +45,28 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
|
||||
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static ChatComponentCubit singleContact(
|
||||
{required UnlockedAccountInfo activeAccountInfo,
|
||||
required proto.Account accountRecordInfo,
|
||||
required ActiveConversationState activeConversationState,
|
||||
{required Locator locator,
|
||||
required ActiveConversationCubit activeConversationCubit,
|
||||
required SingleContactMessagesCubit messagesCubit}) {
|
||||
// Get account info
|
||||
final unlockedAccountInfo =
|
||||
locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
|
||||
final account = locator<AccountRecordCubit>().state.asData!.value;
|
||||
|
||||
// Make local 'User'
|
||||
final localUserIdentityKey = activeAccountInfo.identityTypedPublicKey;
|
||||
final localUserIdentityKey = unlockedAccountInfo.identityTypedPublicKey;
|
||||
final localUser = types.User(
|
||||
id: localUserIdentityKey.toString(),
|
||||
firstName: accountRecordInfo.profile.name,
|
||||
firstName: account.profile.name,
|
||||
metadata: {metadataKeyIdentityPublicKey: localUserIdentityKey});
|
||||
|
||||
// Make remote 'User's
|
||||
final remoteUsers = {
|
||||
activeConversationState.contact.identityPublicKey.toVeilid(): types.User(
|
||||
id: activeConversationState.contact.identityPublicKey
|
||||
.toVeilid()
|
||||
.toString(),
|
||||
firstName: activeConversationState.contact.editedProfile.name,
|
||||
firstName: activeConversationState.contact.displayName,
|
||||
metadata: {
|
||||
metadataKeyIdentityPublicKey:
|
||||
activeConversationState.contact.identityPublicKey.toVeilid()
|
||||
|
@ -4,6 +4,7 @@ import 'package:async_tools/async_tools.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
@ -50,13 +51,13 @@ typedef SingleContactMessagesState = AsyncValue<WindowState<MessageState>>;
|
||||
// Builds the reconciled chat record from the local and remote conversation keys
|
||||
class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
SingleContactMessagesCubit({
|
||||
required UnlockedAccountInfo activeAccountInfo,
|
||||
required Locator locator,
|
||||
required TypedKey remoteIdentityPublicKey,
|
||||
required TypedKey localConversationRecordKey,
|
||||
required TypedKey localMessagesRecordKey,
|
||||
required TypedKey remoteConversationRecordKey,
|
||||
required TypedKey remoteMessagesRecordKey,
|
||||
}) : _activeAccountInfo = activeAccountInfo,
|
||||
}) : _locator = locator,
|
||||
_remoteIdentityPublicKey = remoteIdentityPublicKey,
|
||||
_localConversationRecordKey = localConversationRecordKey,
|
||||
_localMessagesRecordKey = localMessagesRecordKey,
|
||||
@ -86,6 +87,9 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
|
||||
// Initialize everything
|
||||
Future<void> _init() async {
|
||||
_unlockedAccountInfo =
|
||||
_locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
|
||||
|
||||
_unsentMessagesQueue = PersistentQueue<proto.Message>(
|
||||
table: 'SingleContactUnsentMessages',
|
||||
key: _remoteConversationRecordKey.toString(),
|
||||
@ -111,15 +115,15 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
|
||||
// Make crypto
|
||||
Future<void> _initCrypto() async {
|
||||
_conversationCrypto = await _activeAccountInfo
|
||||
_conversationCrypto = await _unlockedAccountInfo
|
||||
.makeConversationCrypto(_remoteIdentityPublicKey);
|
||||
_senderMessageIntegrity = await MessageIntegrity.create(
|
||||
author: _activeAccountInfo.identityTypedPublicKey);
|
||||
author: _unlockedAccountInfo.identityTypedPublicKey);
|
||||
}
|
||||
|
||||
// Open local messages key
|
||||
Future<void> _initSentMessagesCubit() async {
|
||||
final writer = _activeAccountInfo.identityWriter;
|
||||
final writer = _unlockedAccountInfo.identityWriter;
|
||||
|
||||
_sentMessagesCubit = DHTLogCubit(
|
||||
open: () async => DHTLog.openWrite(_localMessagesRecordKey, writer,
|
||||
@ -149,7 +153,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
|
||||
Future<VeilidCrypto> _makeLocalMessagesCrypto() async =>
|
||||
VeilidCryptoPrivate.fromTypedKey(
|
||||
_activeAccountInfo.userLogin.identitySecret, 'tabledb');
|
||||
_unlockedAccountInfo.userLogin.identitySecret, 'tabledb');
|
||||
|
||||
// Open reconciled chat record key
|
||||
Future<void> _initReconciledMessagesCubit() async {
|
||||
@ -240,8 +244,10 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
return;
|
||||
}
|
||||
|
||||
_reconciliation.reconcileMessages(_activeAccountInfo.identityTypedPublicKey,
|
||||
sentMessages, _sentMessagesCubit!);
|
||||
_reconciliation.reconcileMessages(
|
||||
_unlockedAccountInfo.identityTypedPublicKey,
|
||||
sentMessages,
|
||||
_sentMessagesCubit!);
|
||||
|
||||
// Update the view
|
||||
_renderState();
|
||||
@ -278,7 +284,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
|
||||
// Now sign it
|
||||
await _senderMessageIntegrity.signMessage(
|
||||
message, _activeAccountInfo.identitySecretKey);
|
||||
message, _unlockedAccountInfo.identitySecretKey);
|
||||
}
|
||||
|
||||
// Async process to send messages in the background
|
||||
@ -331,7 +337,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
|
||||
for (final m in reconciledMessages.windowElements) {
|
||||
final isLocal = m.content.author.toVeilid() ==
|
||||
_activeAccountInfo.identityTypedPublicKey;
|
||||
_unlockedAccountInfo.identityTypedPublicKey;
|
||||
final reconciledTimestamp = Timestamp.fromInt64(m.reconciledTime);
|
||||
final sm =
|
||||
isLocal ? sentMessagesMap[m.content.authorUniqueIdString] : null;
|
||||
@ -369,7 +375,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
// Add common fields
|
||||
// id and signature will get set by _processMessageToSend
|
||||
message
|
||||
..author = _activeAccountInfo.identityTypedPublicKey.toProto()
|
||||
..author = _unlockedAccountInfo.identityTypedPublicKey.toProto()
|
||||
..timestamp = Veilid.instance.now().toInt64();
|
||||
|
||||
// Put in the queue
|
||||
@ -402,7 +408,8 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
final WaitSet<void> _initWait = WaitSet();
|
||||
final UnlockedAccountInfo _activeAccountInfo;
|
||||
final Locator _locator;
|
||||
late final UnlockedAccountInfo _unlockedAccountInfo;
|
||||
final TypedKey _remoteIdentityPublicKey;
|
||||
final TypedKey _localConversationRecordKey;
|
||||
final TypedKey _localMessagesRecordKey;
|
||||
|
@ -22,26 +22,15 @@ class ChatComponentWidget extends StatelessWidget {
|
||||
static Widget builder(
|
||||
{required TypedKey localConversationRecordKey, Key? key}) =>
|
||||
Builder(builder: (context) {
|
||||
// Get all watched dependendies
|
||||
final activeAccountInfo = context.watch<UnlockedAccountInfo>();
|
||||
final accountRecordInfo =
|
||||
context.watch<AccountRecordCubit>().state.asData?.value;
|
||||
if (accountRecordInfo == null) {
|
||||
return debugPage('should always have an account record here');
|
||||
}
|
||||
|
||||
final avconversation = context.select<ActiveConversationsBlocMapCubit,
|
||||
AsyncValue<ActiveConversationState>?>(
|
||||
(x) => x.state[localConversationRecordKey]);
|
||||
if (avconversation == null) {
|
||||
// Get the active conversation cubit
|
||||
final activeConversationCubit = context
|
||||
.select<ActiveConversationsBlocMapCubit, ActiveConversationCubit?>(
|
||||
(x) => x.tryOperate(localConversationRecordKey,
|
||||
closure: (cubit) => cubit));
|
||||
if (activeConversationCubit == null) {
|
||||
return waitingPage();
|
||||
}
|
||||
|
||||
final activeConversationState = avconversation.asData?.value;
|
||||
if (activeConversationState == null) {
|
||||
return avconversation.buildNotData();
|
||||
}
|
||||
|
||||
// Get the messages cubit
|
||||
final messagesCubit = context.select<
|
||||
ActiveSingleContactChatBlocMapCubit,
|
||||
@ -55,9 +44,8 @@ class ChatComponentWidget extends StatelessWidget {
|
||||
// Make chat component state
|
||||
return BlocProvider(
|
||||
create: (context) => ChatComponentCubit.singleContact(
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
accountRecordInfo: accountRecordInfo,
|
||||
activeConversationState: activeConversationState,
|
||||
locator: context.read,
|
||||
activeConversationCubit: activeConversationCubit,
|
||||
messagesCubit: messagesCubit,
|
||||
),
|
||||
child: ChatComponentWidget._(key: key));
|
||||
|
@ -3,9 +3,9 @@ import 'dart:async';
|
||||
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../chat/chat.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../tools/tools.dart';
|
||||
@ -19,21 +19,17 @@ typedef ChatListCubitState = DHTShortArrayBusyState<proto.Chat>;
|
||||
class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
|
||||
with StateMapFollowable<ChatListCubitState, TypedKey, proto.Chat> {
|
||||
ChatListCubit({
|
||||
required UnlockedAccountInfo unlockedAccountInfo,
|
||||
required proto.Account account,
|
||||
required this.activeChatCubit,
|
||||
}) : super(
|
||||
open: () => _open(unlockedAccountInfo, account),
|
||||
required Locator locator,
|
||||
required TypedKey accountRecordKey,
|
||||
required OwnedDHTRecordPointer chatListRecordPointer,
|
||||
}) : _locator = locator,
|
||||
super(
|
||||
open: () => _open(locator, accountRecordKey, chatListRecordPointer),
|
||||
decodeElement: proto.Chat.fromBuffer);
|
||||
|
||||
static Future<DHTShortArray> _open(
|
||||
UnlockedAccountInfo activeAccountInfo, proto.Account account) async {
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
final chatListRecordKey = account.chatList.toVeilid();
|
||||
|
||||
final dhtRecord = await DHTShortArray.openOwned(chatListRecordKey,
|
||||
static Future<DHTShortArray> _open(Locator locator, TypedKey accountRecordKey,
|
||||
OwnedDHTRecordPointer chatListRecordPointer) async {
|
||||
final dhtRecord = await DHTShortArray.openOwned(chatListRecordPointer,
|
||||
debugName: 'ChatListCubit::_open::ChatList', parent: accountRecordKey);
|
||||
|
||||
return dhtRecord;
|
||||
@ -41,11 +37,11 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
|
||||
|
||||
Future<proto.ChatSettings> getDefaultChatSettings(
|
||||
proto.Contact contact) async {
|
||||
final pronouns = contact.editedProfile.pronouns.isEmpty
|
||||
final pronouns = contact.profile.pronouns.isEmpty
|
||||
? ''
|
||||
: ' (${contact.editedProfile.pronouns})';
|
||||
: ' [${contact.profile.pronouns}])';
|
||||
return proto.ChatSettings()
|
||||
..title = '${contact.editedProfile.name}$pronouns'
|
||||
..title = '${contact.displayName}$pronouns'
|
||||
..description = ''
|
||||
..defaultExpiration = Int64.ZERO;
|
||||
}
|
||||
@ -99,6 +95,7 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
|
||||
final deletedItem =
|
||||
// Ensure followers get their changes before we return
|
||||
await syncFollowers(() => operateWrite((writer) async {
|
||||
final activeChatCubit = _locator<ActiveChatCubit>();
|
||||
if (activeChatCubit.state == localConversationRecordKey) {
|
||||
activeChatCubit.setActiveChat(null);
|
||||
}
|
||||
@ -142,5 +139,5 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
final ActiveChatCubit activeChatCubit;
|
||||
final Locator _locator;
|
||||
}
|
||||
|
@ -28,13 +28,31 @@ class ChatSingleContactItemWidget extends StatelessWidget {
|
||||
_contact.localConversationRecordKey.toVeilid();
|
||||
final selected = activeChatCubit.state == localConversationRecordKey;
|
||||
|
||||
late final String title;
|
||||
late final String subtitle;
|
||||
if (_contact.nickname.isNotEmpty) {
|
||||
title = _contact.nickname;
|
||||
if (_contact.profile.pronouns.isNotEmpty) {
|
||||
subtitle = '${_contact.profile.name} (${_contact.profile.pronouns})';
|
||||
} else {
|
||||
subtitle = _contact.profile.name;
|
||||
}
|
||||
} else {
|
||||
title = _contact.profile.name;
|
||||
if (_contact.profile.pronouns.isNotEmpty) {
|
||||
subtitle = '(${_contact.profile.pronouns})';
|
||||
} else {
|
||||
subtitle = '';
|
||||
}
|
||||
}
|
||||
|
||||
return SliderTile(
|
||||
key: ObjectKey(_contact),
|
||||
disabled: _disabled,
|
||||
selected: selected,
|
||||
tileScale: ScaleKind.secondary,
|
||||
title: _contact.editedProfile.name,
|
||||
subtitle: _contact.editedProfile.pronouns,
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
icon: Icons.chat,
|
||||
onTap: () {
|
||||
singleFuture(activeChatCubit, () async {
|
||||
|
@ -53,10 +53,13 @@ class ChatSingleContactListWidget extends StatelessWidget {
|
||||
if (contact == null) {
|
||||
return false;
|
||||
}
|
||||
return contact.editedProfile.name
|
||||
return contact.nickname
|
||||
.toLowerCase()
|
||||
.contains(lowerValue) ||
|
||||
contact.editedProfile.pronouns
|
||||
contact.profile.name
|
||||
.toLowerCase()
|
||||
.contains(lowerValue) ||
|
||||
contact.profile.pronouns
|
||||
.toLowerCase()
|
||||
.contains(lowerValue);
|
||||
}).toList();
|
||||
|
@ -4,6 +4,7 @@ import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
@ -36,22 +37,18 @@ class ContactInvitationListCubit
|
||||
StateMapFollowable<ContactInvitiationListState, TypedKey,
|
||||
proto.ContactInvitationRecord> {
|
||||
ContactInvitationListCubit({
|
||||
required UnlockedAccountInfo unlockedAccountInfo,
|
||||
required proto.Account account,
|
||||
}) : _activeAccountInfo = unlockedAccountInfo,
|
||||
_account = account,
|
||||
required Locator locator,
|
||||
required TypedKey accountRecordKey,
|
||||
required OwnedDHTRecordPointer contactInvitationListRecordPointer,
|
||||
}) : _locator = locator,
|
||||
_accountRecordKey = accountRecordKey,
|
||||
super(
|
||||
open: () => _open(unlockedAccountInfo, account),
|
||||
open: () =>
|
||||
_open(accountRecordKey, contactInvitationListRecordPointer),
|
||||
decodeElement: proto.ContactInvitationRecord.fromBuffer);
|
||||
|
||||
static Future<DHTShortArray> _open(
|
||||
UnlockedAccountInfo activeAccountInfo, proto.Account account) async {
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
final contactInvitationListRecordPointer =
|
||||
account.contactInvitationRecords.toVeilid();
|
||||
|
||||
static Future<DHTShortArray> _open(TypedKey accountRecordKey,
|
||||
OwnedDHTRecordPointer contactInvitationListRecordPointer) async {
|
||||
final dhtRecord = await DHTShortArray.openOwned(
|
||||
contactInvitationListRecordPointer,
|
||||
debugName: 'ContactInvitationListCubit::_open::ContactInvitationList',
|
||||
@ -71,8 +68,12 @@ class ContactInvitationListCubit
|
||||
final crcs = await pool.veilid.bestCryptoSystem();
|
||||
final contactRequestWriter = await crcs.generateKeyPair();
|
||||
|
||||
final idcs = await _activeAccountInfo.identityCryptoSystem;
|
||||
final identityWriter = _activeAccountInfo.identityWriter;
|
||||
final activeAccountInfo =
|
||||
_locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
|
||||
final profile = _locator<AccountRecordCubit>().state.asData!.value.profile;
|
||||
|
||||
final idcs = await activeAccountInfo.identityCryptoSystem;
|
||||
final identityWriter = activeAccountInfo.identityWriter;
|
||||
|
||||
// Encrypt the writer secret with the encryption key
|
||||
final encryptedSecret = await encryptionKeyType.encryptSecretToBytes(
|
||||
@ -90,7 +91,7 @@ class ContactInvitationListCubit
|
||||
await (await pool.createRecord(
|
||||
debugName: 'ContactInvitationListCubit::createInvitation::'
|
||||
'LocalConversation',
|
||||
parent: _activeAccountInfo.accountRecordKey,
|
||||
parent: _accountRecordKey,
|
||||
schema: DHTSchema.smpl(
|
||||
oCnt: 0,
|
||||
members: [DHTSchemaMember(mKey: identityWriter.key, mCnt: 1)])))
|
||||
@ -99,9 +100,9 @@ class ContactInvitationListCubit
|
||||
// Make ContactRequestPrivate and encrypt with the writer secret
|
||||
final crpriv = proto.ContactRequestPrivate()
|
||||
..writerKey = contactRequestWriter.key.toProto()
|
||||
..profile = _account.profile
|
||||
..profile = profile
|
||||
..superIdentityRecordKey =
|
||||
_activeAccountInfo.userLogin.superIdentityRecordKey.toProto()
|
||||
activeAccountInfo.userLogin.superIdentityRecordKey.toProto()
|
||||
..chatRecordKey = localConversation.key.toProto()
|
||||
..expiration = expiration?.toInt64() ?? Int64.ZERO;
|
||||
final crprivbytes = crpriv.writeToBuffer();
|
||||
@ -119,7 +120,7 @@ class ContactInvitationListCubit
|
||||
await (await pool.createRecord(
|
||||
debugName: 'ContactInvitationListCubit::createInvitation::'
|
||||
'ContactRequestInbox',
|
||||
parent: _activeAccountInfo.accountRecordKey,
|
||||
parent: _accountRecordKey,
|
||||
schema: DHTSchema.smpl(oCnt: 1, members: [
|
||||
DHTSchemaMember(mCnt: 1, mKey: contactRequestWriter.key)
|
||||
]),
|
||||
@ -172,8 +173,6 @@ class ContactInvitationListCubit
|
||||
{required bool accepted,
|
||||
required TypedKey contactRequestInboxRecordKey}) async {
|
||||
final pool = DHTRecordPool.instance;
|
||||
final accountRecordKey =
|
||||
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
// Remove ContactInvitationRecord from account's list
|
||||
final deletedItem = await operateWrite((writer) async {
|
||||
@ -198,7 +197,7 @@ class ContactInvitationListCubit
|
||||
await (await pool.openRecordOwned(contactRequestInbox,
|
||||
debugName: 'ContactInvitationListCubit::deleteInvitation::'
|
||||
'ContactRequestInbox',
|
||||
parent: accountRecordKey))
|
||||
parent: _accountRecordKey))
|
||||
.scope((contactRequestInbox) async {
|
||||
// Wipe out old invitation so it shows up as invalid
|
||||
await contactRequestInbox.tryWriteBytes(Uint8List(0));
|
||||
@ -248,7 +247,7 @@ class ContactInvitationListCubit
|
||||
await (await pool.openRecordRead(contactRequestInboxKey,
|
||||
debugName: 'ContactInvitationListCubit::validateInvitation::'
|
||||
'ContactRequestInbox',
|
||||
parent: _activeAccountInfo.accountRecordKey))
|
||||
parent: _accountRecordKey))
|
||||
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
|
||||
//
|
||||
final contactRequest = await contactRequestInbox
|
||||
@ -293,8 +292,7 @@ class ContactInvitationListCubit
|
||||
secret: writerSecret);
|
||||
|
||||
out = ValidContactInvitation(
|
||||
activeAccountInfo: _activeAccountInfo,
|
||||
account: _account,
|
||||
locator: _locator,
|
||||
contactRequestInboxKey: contactRequestInboxKey,
|
||||
contactRequestPrivate: contactRequestPrivate,
|
||||
contactSuperIdentity: contactSuperIdentity,
|
||||
@ -318,6 +316,6 @@ class ContactInvitationListCubit
|
||||
}
|
||||
|
||||
//
|
||||
final UnlockedAccountInfo _activeAccountInfo;
|
||||
final proto.Account _account;
|
||||
final Locator _locator;
|
||||
final TypedKey _accountRecordKey;
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
@ -7,27 +8,24 @@ import '../../proto/proto.dart' as proto;
|
||||
class ContactRequestInboxCubit
|
||||
extends DefaultDHTRecordCubit<proto.SignedContactResponse?> {
|
||||
ContactRequestInboxCubit(
|
||||
{required this.activeAccountInfo, required this.contactInvitationRecord})
|
||||
{required Locator locator, required this.contactInvitationRecord})
|
||||
: super(
|
||||
open: () => _open(
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
locator: locator,
|
||||
contactInvitationRecord: contactInvitationRecord),
|
||||
decodeState: (buf) => buf.isEmpty
|
||||
? null
|
||||
: proto.SignedContactResponse.fromBuffer(buf));
|
||||
|
||||
// ContactRequestInboxCubit.value(
|
||||
// {required super.record,
|
||||
// required this.activeAccountInfo,
|
||||
// required this.contactInvitationRecord})
|
||||
// : super.value(decodeState: proto.SignedContactResponse.fromBuffer);
|
||||
|
||||
static Future<DHTRecord> _open(
|
||||
{required UnlockedAccountInfo activeAccountInfo,
|
||||
{required Locator locator,
|
||||
required proto.ContactInvitationRecord contactInvitationRecord}) async {
|
||||
final pool = DHTRecordPool.instance;
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
final unlockedAccountInfo =
|
||||
locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
|
||||
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
|
||||
|
||||
final writerSecret = contactInvitationRecord.writerSecret.toVeilid();
|
||||
final recordKey =
|
||||
contactInvitationRecord.contactRequestInbox.recordKey.toVeilid();
|
||||
@ -42,6 +40,5 @@ class ContactRequestInboxCubit
|
||||
defaultSubkey: 1);
|
||||
}
|
||||
|
||||
final UnlockedAccountInfo activeAccountInfo;
|
||||
final proto.ContactInvitationRecord contactInvitationRecord;
|
||||
}
|
||||
|
@ -4,9 +4,9 @@ import 'package:async_tools/async_tools.dart';
|
||||
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
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';
|
||||
@ -25,24 +25,22 @@ class InvitationStatus extends Equatable {
|
||||
class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
|
||||
proto.SignedContactResponse?> {
|
||||
WaitingInvitationCubit(ContactRequestInboxCubit super.input,
|
||||
{required UnlockedAccountInfo activeAccountInfo,
|
||||
required proto.Account account,
|
||||
{required Locator locator,
|
||||
required proto.ContactInvitationRecord contactInvitationRecord})
|
||||
: super(
|
||||
transform: (signedContactResponse) => _transform(
|
||||
signedContactResponse,
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
account: account,
|
||||
locator: locator,
|
||||
contactInvitationRecord: contactInvitationRecord));
|
||||
|
||||
static Future<AsyncValue<InvitationStatus>> _transform(
|
||||
proto.SignedContactResponse? signedContactResponse,
|
||||
{required UnlockedAccountInfo activeAccountInfo,
|
||||
required proto.Account account,
|
||||
{required Locator locator,
|
||||
required proto.ContactInvitationRecord contactInvitationRecord}) async {
|
||||
if (signedContactResponse == null) {
|
||||
return const AsyncValue.loading();
|
||||
}
|
||||
|
||||
final contactResponseBytes =
|
||||
Uint8List.fromList(signedContactResponse.contactResponse);
|
||||
final contactResponse =
|
||||
@ -71,7 +69,7 @@ class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
|
||||
contactResponse.remoteConversationRecordKey.toVeilid();
|
||||
|
||||
final conversation = ConversationCubit(
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
locator: locator,
|
||||
remoteIdentityPublicKey:
|
||||
contactSuperIdentity.currentInstance.typedPublicKey,
|
||||
remoteConversationRecordKey: remoteConversationRecordKey);
|
||||
@ -99,15 +97,11 @@ class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
|
||||
contactInvitationRecord.localConversationRecordKey.toVeilid();
|
||||
return conversation.initLocalConversation(
|
||||
existingConversationRecordKey: localConversationRecordKey,
|
||||
profile: account.profile,
|
||||
// ignore: prefer_expression_function_bodies
|
||||
callback: (localConversation) async {
|
||||
return AsyncValue.data(InvitationStatus(
|
||||
acceptedContact: AcceptedContact(
|
||||
remoteProfile: remoteProfile,
|
||||
remoteIdentity: contactSuperIdentity,
|
||||
remoteConversationRecordKey: remoteConversationRecordKey,
|
||||
localConversationRecordKey: localConversationRecordKey)));
|
||||
});
|
||||
callback: (localConversation) async => AsyncValue.data(InvitationStatus(
|
||||
acceptedContact: AcceptedContact(
|
||||
remoteProfile: remoteProfile,
|
||||
remoteIdentity: contactSuperIdentity,
|
||||
remoteConversationRecordKey: remoteConversationRecordKey,
|
||||
localConversationRecordKey: localConversationRecordKey))));
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import 'cubits.dart';
|
||||
|
||||
@ -17,8 +17,12 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
with
|
||||
StateMapFollower<DHTShortArrayBusyState<proto.ContactInvitationRecord>,
|
||||
TypedKey, proto.ContactInvitationRecord> {
|
||||
WaitingInvitationsBlocMapCubit(
|
||||
{required this.unlockedAccountInfo, required this.account});
|
||||
WaitingInvitationsBlocMapCubit({
|
||||
required Locator locator,
|
||||
}) : _locator = locator {
|
||||
// Follow the contact invitation list cubit
|
||||
follow(locator<ContactInvitationListCubit>());
|
||||
}
|
||||
|
||||
Future<void> _addWaitingInvitation(
|
||||
{required proto.ContactInvitationRecord
|
||||
@ -27,10 +31,9 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
contactInvitationRecord.contactRequestInbox.recordKey.toVeilid(),
|
||||
WaitingInvitationCubit(
|
||||
ContactRequestInboxCubit(
|
||||
activeAccountInfo: unlockedAccountInfo,
|
||||
locator: _locator,
|
||||
contactInvitationRecord: contactInvitationRecord),
|
||||
activeAccountInfo: unlockedAccountInfo,
|
||||
account: account,
|
||||
locator: _locator,
|
||||
contactInvitationRecord: contactInvitationRecord)));
|
||||
|
||||
/// StateFollower /////////////////////////
|
||||
@ -43,6 +46,5 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
_addWaitingInvitation(contactInvitationRecord: value);
|
||||
|
||||
////
|
||||
final UnlockedAccountInfo unlockedAccountInfo;
|
||||
final proto.Account account;
|
||||
final Locator _locator;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
@ -13,14 +14,12 @@ import 'models.dart';
|
||||
class ValidContactInvitation {
|
||||
@internal
|
||||
ValidContactInvitation(
|
||||
{required UnlockedAccountInfo activeAccountInfo,
|
||||
required proto.Account account,
|
||||
{required Locator locator,
|
||||
required TypedKey contactRequestInboxKey,
|
||||
required proto.ContactRequestPrivate contactRequestPrivate,
|
||||
required SuperIdentity contactSuperIdentity,
|
||||
required KeyPair writer})
|
||||
: _activeAccountInfo = activeAccountInfo,
|
||||
_account = account,
|
||||
: _locator = locator,
|
||||
_contactRequestInboxKey = contactRequestInboxKey,
|
||||
_contactRequestPrivate = contactRequestPrivate,
|
||||
_contactSuperIdentity = contactSuperIdentity,
|
||||
@ -31,11 +30,15 @@ class ValidContactInvitation {
|
||||
Future<AcceptedContact?> accept() async {
|
||||
final pool = DHTRecordPool.instance;
|
||||
try {
|
||||
final unlockedAccountInfo =
|
||||
_locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
|
||||
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
|
||||
final identityPublicKey = unlockedAccountInfo.identityPublicKey;
|
||||
|
||||
// Ensure we don't delete this if we're trying to chat to self
|
||||
// The initiating side will delete the records in deleteInvitation()
|
||||
final isSelf = _contactSuperIdentity.currentInstance.publicKey ==
|
||||
_activeAccountInfo.identityPublicKey;
|
||||
final accountRecordKey = _activeAccountInfo.accountRecordKey;
|
||||
final isSelf =
|
||||
_contactSuperIdentity.currentInstance.publicKey == identityPublicKey;
|
||||
|
||||
return (await pool.openRecordWrite(_contactRequestInboxKey, _writer,
|
||||
debugName: 'ValidContactInvitation::accept::'
|
||||
@ -46,43 +49,42 @@ class ValidContactInvitation {
|
||||
// Create local conversation key for this
|
||||
// contact and send via contact response
|
||||
final conversation = ConversationCubit(
|
||||
activeAccountInfo: _activeAccountInfo,
|
||||
locator: _locator,
|
||||
remoteIdentityPublicKey:
|
||||
_contactSuperIdentity.currentInstance.typedPublicKey);
|
||||
return conversation.initLocalConversation(
|
||||
profile: _account.profile,
|
||||
callback: (localConversation) async {
|
||||
final contactResponse = proto.ContactResponse()
|
||||
..accept = true
|
||||
..remoteConversationRecordKey = localConversation.key.toProto()
|
||||
..superIdentityRecordKey =
|
||||
_activeAccountInfo.superIdentityRecordKey.toProto();
|
||||
final contactResponseBytes = contactResponse.writeToBuffer();
|
||||
final contactResponse = proto.ContactResponse()
|
||||
..accept = true
|
||||
..remoteConversationRecordKey = localConversation.key.toProto()
|
||||
..superIdentityRecordKey =
|
||||
unlockedAccountInfo.superIdentityRecordKey.toProto();
|
||||
final contactResponseBytes = contactResponse.writeToBuffer();
|
||||
|
||||
final cs = await pool.veilid
|
||||
.getCryptoSystem(_contactRequestInboxKey.kind);
|
||||
final cs =
|
||||
await pool.veilid.getCryptoSystem(_contactRequestInboxKey.kind);
|
||||
|
||||
final identitySignature = await cs.sign(
|
||||
_activeAccountInfo.identityWriter.key,
|
||||
_activeAccountInfo.identityWriter.secret,
|
||||
contactResponseBytes);
|
||||
final identitySignature = await cs.sign(
|
||||
unlockedAccountInfo.identityWriter.key,
|
||||
unlockedAccountInfo.identityWriter.secret,
|
||||
contactResponseBytes);
|
||||
|
||||
final signedContactResponse = proto.SignedContactResponse()
|
||||
..contactResponse = contactResponseBytes
|
||||
..identitySignature = identitySignature.toProto();
|
||||
final signedContactResponse = proto.SignedContactResponse()
|
||||
..contactResponse = contactResponseBytes
|
||||
..identitySignature = identitySignature.toProto();
|
||||
|
||||
// Write the acceptance to the inbox
|
||||
await contactRequestInbox
|
||||
.eventualWriteProtobuf(signedContactResponse, subkey: 1);
|
||||
// Write the acceptance to the inbox
|
||||
await contactRequestInbox.eventualWriteProtobuf(signedContactResponse,
|
||||
subkey: 1);
|
||||
|
||||
return AcceptedContact(
|
||||
remoteProfile: _contactRequestPrivate.profile,
|
||||
remoteIdentity: _contactSuperIdentity,
|
||||
remoteConversationRecordKey:
|
||||
_contactRequestPrivate.chatRecordKey.toVeilid(),
|
||||
localConversationRecordKey: localConversation.key,
|
||||
);
|
||||
});
|
||||
return AcceptedContact(
|
||||
remoteProfile: _contactRequestPrivate.profile,
|
||||
remoteIdentity: _contactSuperIdentity,
|
||||
remoteConversationRecordKey:
|
||||
_contactRequestPrivate.chatRecordKey.toVeilid(),
|
||||
localConversationRecordKey: localConversation.key,
|
||||
);
|
||||
});
|
||||
});
|
||||
} on Exception catch (e) {
|
||||
log.debug('exception: $e', e);
|
||||
@ -93,10 +95,14 @@ class ValidContactInvitation {
|
||||
Future<bool> reject() async {
|
||||
final pool = DHTRecordPool.instance;
|
||||
|
||||
final unlockedAccountInfo =
|
||||
_locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
|
||||
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
|
||||
final identityPublicKey = unlockedAccountInfo.identityPublicKey;
|
||||
|
||||
// Ensure we don't delete this if we're trying to chat to self
|
||||
final isSelf = _contactSuperIdentity.currentInstance.publicKey ==
|
||||
_activeAccountInfo.identityPublicKey;
|
||||
final accountRecordKey = _activeAccountInfo.accountRecordKey;
|
||||
final isSelf =
|
||||
_contactSuperIdentity.currentInstance.publicKey == identityPublicKey;
|
||||
|
||||
return (await pool.openRecordWrite(_contactRequestInboxKey, _writer,
|
||||
debugName: 'ValidContactInvitation::reject::'
|
||||
@ -109,12 +115,12 @@ class ValidContactInvitation {
|
||||
final contactResponse = proto.ContactResponse()
|
||||
..accept = false
|
||||
..superIdentityRecordKey =
|
||||
_activeAccountInfo.superIdentityRecordKey.toProto();
|
||||
unlockedAccountInfo.superIdentityRecordKey.toProto();
|
||||
final contactResponseBytes = contactResponse.writeToBuffer();
|
||||
|
||||
final identitySignature = await cs.sign(
|
||||
_activeAccountInfo.identityWriter.key,
|
||||
_activeAccountInfo.identityWriter.secret,
|
||||
unlockedAccountInfo.identityWriter.key,
|
||||
unlockedAccountInfo.identityWriter.secret,
|
||||
contactResponseBytes);
|
||||
|
||||
final signedContactResponse = proto.SignedContactResponse()
|
||||
@ -129,8 +135,7 @@ class ValidContactInvitation {
|
||||
}
|
||||
|
||||
//
|
||||
final UnlockedAccountInfo _activeAccountInfo;
|
||||
final proto.Account _account;
|
||||
final Locator _locator;
|
||||
final TypedKey _contactRequestInboxKey;
|
||||
final SuperIdentity _contactSuperIdentity;
|
||||
final KeyPair _writer;
|
||||
|
@ -3,8 +3,8 @@ import 'dart:async';
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
@ -15,13 +15,14 @@ import '../contact_invitation.dart';
|
||||
|
||||
class InvitationDialog extends StatefulWidget {
|
||||
const InvitationDialog(
|
||||
{required this.modalContext,
|
||||
{required Locator locator,
|
||||
required this.onValidationCancelled,
|
||||
required this.onValidationSuccess,
|
||||
required this.onValidationFailed,
|
||||
required this.inviteControlIsValid,
|
||||
required this.buildInviteControl,
|
||||
super.key});
|
||||
super.key})
|
||||
: _locator = locator;
|
||||
|
||||
final void Function() onValidationCancelled;
|
||||
final void Function() onValidationSuccess;
|
||||
@ -32,7 +33,7 @@ class InvitationDialog extends StatefulWidget {
|
||||
InvitationDialogState dialogState,
|
||||
Future<void> Function({required Uint8List inviteData})
|
||||
validateInviteData) buildInviteControl;
|
||||
final BuildContext modalContext;
|
||||
final Locator _locator;
|
||||
|
||||
@override
|
||||
InvitationDialogState createState() => InvitationDialogState();
|
||||
@ -54,8 +55,7 @@ class InvitationDialog extends StatefulWidget {
|
||||
InvitationDialogState dialogState,
|
||||
Future<void> Function({required Uint8List inviteData})
|
||||
validateInviteData)>.has(
|
||||
'buildInviteControl', buildInviteControl))
|
||||
..add(DiagnosticsProperty<BuildContext>('modalContext', modalContext));
|
||||
'buildInviteControl', buildInviteControl));
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,8 +74,8 @@ class InvitationDialogState extends State<InvitationDialog> {
|
||||
|
||||
Future<void> _onAccept() async {
|
||||
final navigator = Navigator.of(context);
|
||||
final activeAccountInfo = widget.modalContext.read<UnlockedAccountInfo>();
|
||||
final contactList = widget.modalContext.read<ContactListCubit>();
|
||||
final activeAccountInfo = widget._locator<UnlockedAccountInfo>();
|
||||
final contactList = widget._locator<ContactListCubit>();
|
||||
|
||||
setState(() {
|
||||
_isAccepting = true;
|
||||
@ -90,7 +90,7 @@ class InvitationDialogState extends State<InvitationDialog> {
|
||||
acceptedContact.remoteIdentity.currentInstance.publicKey;
|
||||
if (!isSelf) {
|
||||
await contactList.createContact(
|
||||
remoteProfile: acceptedContact.remoteProfile,
|
||||
profile: acceptedContact.remoteProfile,
|
||||
remoteSuperIdentity: acceptedContact.remoteIdentity,
|
||||
remoteConversationRecordKey:
|
||||
acceptedContact.remoteConversationRecordKey,
|
||||
@ -137,7 +137,7 @@ class InvitationDialogState extends State<InvitationDialog> {
|
||||
}) async {
|
||||
try {
|
||||
final contactInvitationListCubit =
|
||||
widget.modalContext.read<ContactInvitationListCubit>();
|
||||
widget._locator<ContactInvitationListCubit>();
|
||||
|
||||
setState(() {
|
||||
_isValidating = true;
|
||||
|
@ -3,33 +3,29 @@ import 'dart:convert';
|
||||
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
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 '../../conversation/cubits/conversation_cubit.dart';
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// Mutable state for per-account contacts
|
||||
|
||||
class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
||||
ContactListCubit({
|
||||
required UnlockedAccountInfo unlockedAccountInfo,
|
||||
required proto.Account account,
|
||||
}) : _activeAccountInfo = unlockedAccountInfo,
|
||||
required Locator locator,
|
||||
required TypedKey accountRecordKey,
|
||||
required OwnedDHTRecordPointer contactListRecordPointer,
|
||||
}) : _locator = locator,
|
||||
super(
|
||||
open: () => _open(unlockedAccountInfo, account),
|
||||
open: () => _open(accountRecordKey, contactListRecordPointer),
|
||||
decodeElement: proto.Contact.fromBuffer);
|
||||
|
||||
static Future<DHTShortArray> _open(
|
||||
UnlockedAccountInfo activeAccountInfo, proto.Account account) async {
|
||||
final accountRecordKey =
|
||||
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
final contactListRecordKey = account.contactList.toVeilid();
|
||||
|
||||
final dhtRecord = await DHTShortArray.openOwned(contactListRecordKey,
|
||||
static Future<DHTShortArray> _open(TypedKey accountRecordKey,
|
||||
OwnedDHTRecordPointer contactListRecordPointer) async {
|
||||
final dhtRecord = await DHTShortArray.openOwned(contactListRecordPointer,
|
||||
debugName: 'ContactListCubit::_open::ContactList',
|
||||
parent: accountRecordKey);
|
||||
|
||||
@ -52,15 +48,15 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
||||
if (remoteProfile == null) {
|
||||
return;
|
||||
}
|
||||
return updateContactRemoteProfile(
|
||||
return updateContactProfile(
|
||||
localConversationRecordKey: localConversationRecordKey,
|
||||
remoteProfile: remoteProfile);
|
||||
profile: remoteProfile);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> updateContactRemoteProfile({
|
||||
Future<void> updateContactProfile({
|
||||
required TypedKey localConversationRecordKey,
|
||||
required proto.Profile remoteProfile,
|
||||
required proto.Profile profile,
|
||||
}) async {
|
||||
// Update contact's remoteProfile
|
||||
await operateWriteEventual((writer) async {
|
||||
@ -69,36 +65,36 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
||||
if (c != null &&
|
||||
c.localConversationRecordKey.toVeilid() ==
|
||||
localConversationRecordKey) {
|
||||
if (c.remoteProfile == remoteProfile) {
|
||||
if (c.profile == profile) {
|
||||
// Unchanged
|
||||
break;
|
||||
}
|
||||
final newContact = c.deepCopy()..remoteProfile = remoteProfile;
|
||||
final newContact = c.deepCopy()..profile = profile;
|
||||
final updated = await writer.tryWriteItemProtobuf(
|
||||
proto.Contact.fromBuffer, pos, newContact);
|
||||
if (!updated) {
|
||||
throw DHTExceptionTryAgain();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> createContact({
|
||||
required proto.Profile remoteProfile,
|
||||
required proto.Profile profile,
|
||||
required SuperIdentity remoteSuperIdentity,
|
||||
required TypedKey remoteConversationRecordKey,
|
||||
required TypedKey localConversationRecordKey,
|
||||
required TypedKey remoteConversationRecordKey,
|
||||
}) async {
|
||||
// Create Contact
|
||||
final contact = proto.Contact()
|
||||
..editedProfile = remoteProfile
|
||||
..remoteProfile = remoteProfile
|
||||
..profile = profile
|
||||
..superIdentityJson = jsonEncode(remoteSuperIdentity.toJson())
|
||||
..identityPublicKey =
|
||||
remoteSuperIdentity.currentInstance.typedPublicKey.toProto()
|
||||
..remoteConversationRecordKey = remoteConversationRecordKey.toProto()
|
||||
..localConversationRecordKey = localConversationRecordKey.toProto()
|
||||
..remoteConversationRecordKey = remoteConversationRecordKey.toProto()
|
||||
..showAvailability = false;
|
||||
|
||||
// Add Contact to account's list
|
||||
@ -108,13 +104,8 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> deleteContact({required proto.Contact contact}) async {
|
||||
final remoteIdentityPublicKey = contact.identityPublicKey.toVeilid();
|
||||
final localConversationRecordKey =
|
||||
contact.localConversationRecordKey.toVeilid();
|
||||
final remoteConversationRecordKey =
|
||||
contact.remoteConversationRecordKey.toVeilid();
|
||||
|
||||
Future<void> deleteContact(
|
||||
{required TypedKey localConversationRecordKey}) async {
|
||||
// Remove Contact from account's list
|
||||
final deletedItem = await operateWrite((writer) async {
|
||||
for (var i = 0; i < writer.length; i++) {
|
||||
@ -122,8 +113,8 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
||||
if (item == null) {
|
||||
throw Exception('Failed to get contact');
|
||||
}
|
||||
if (item.localConversationRecordKey ==
|
||||
contact.localConversationRecordKey) {
|
||||
if (item.localConversationRecordKey.toVeilid() ==
|
||||
localConversationRecordKey) {
|
||||
await writer.remove(i);
|
||||
return item;
|
||||
}
|
||||
@ -135,10 +126,12 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
||||
try {
|
||||
// Make a conversation cubit to manipulate the conversation
|
||||
final conversationCubit = ConversationCubit(
|
||||
activeAccountInfo: _activeAccountInfo,
|
||||
remoteIdentityPublicKey: remoteIdentityPublicKey,
|
||||
localConversationRecordKey: localConversationRecordKey,
|
||||
remoteConversationRecordKey: remoteConversationRecordKey,
|
||||
locator: _locator,
|
||||
remoteIdentityPublicKey: deletedItem.identityPublicKey.toVeilid(),
|
||||
localConversationRecordKey:
|
||||
deletedItem.localConversationRecordKey.toVeilid(),
|
||||
remoteConversationRecordKey:
|
||||
deletedItem.remoteConversationRecordKey.toVeilid(),
|
||||
);
|
||||
|
||||
// Delete the local and remote conversation records
|
||||
@ -149,7 +142,7 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
|
||||
}
|
||||
}
|
||||
|
||||
final UnlockedAccountInfo _activeAccountInfo;
|
||||
final _contactProfileUpdateMap =
|
||||
SingleStateProcessorMap<TypedKey, proto.Profile?>();
|
||||
final Locator _locator;
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -11,18 +10,9 @@ import '../contacts.dart';
|
||||
|
||||
class ContactItemWidget extends StatelessWidget {
|
||||
const ContactItemWidget(
|
||||
{required this.contact, required this.disabled, super.key});
|
||||
|
||||
final proto.Contact contact;
|
||||
final bool disabled;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty<proto.Contact>('contact', contact))
|
||||
..add(DiagnosticsProperty<bool>('disabled', disabled));
|
||||
}
|
||||
{required proto.Contact contact, required bool disabled, super.key})
|
||||
: _disabled = disabled,
|
||||
_contact = contact;
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
@ -30,26 +20,44 @@ class ContactItemWidget extends StatelessWidget {
|
||||
BuildContext context,
|
||||
) {
|
||||
final localConversationRecordKey =
|
||||
contact.localConversationRecordKey.toVeilid();
|
||||
_contact.localConversationRecordKey.toVeilid();
|
||||
|
||||
const selected = false; // xxx: eventually when we have selectable contacts:
|
||||
// activeContactCubit.state == localConversationRecordKey;
|
||||
|
||||
final tileDisabled = disabled || context.watch<ContactListCubit>().isBusy;
|
||||
final tileDisabled = _disabled || context.watch<ContactListCubit>().isBusy;
|
||||
|
||||
late final String title;
|
||||
late final String subtitle;
|
||||
if (_contact.nickname.isNotEmpty) {
|
||||
title = _contact.nickname;
|
||||
if (_contact.profile.pronouns.isNotEmpty) {
|
||||
subtitle = '${_contact.profile.name} (${_contact.profile.pronouns})';
|
||||
} else {
|
||||
subtitle = _contact.profile.name;
|
||||
}
|
||||
} else {
|
||||
title = _contact.profile.name;
|
||||
if (_contact.profile.pronouns.isNotEmpty) {
|
||||
subtitle = '(${_contact.profile.pronouns})';
|
||||
} else {
|
||||
subtitle = '';
|
||||
}
|
||||
}
|
||||
|
||||
return SliderTile(
|
||||
key: ObjectKey(contact),
|
||||
key: ObjectKey(_contact),
|
||||
disabled: tileDisabled,
|
||||
selected: selected,
|
||||
tileScale: ScaleKind.primary,
|
||||
title: contact.editedProfile.name,
|
||||
subtitle: contact.editedProfile.pronouns,
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
icon: Icons.person,
|
||||
onTap: () async {
|
||||
// Start a chat
|
||||
final chatListCubit = context.read<ChatListCubit>();
|
||||
|
||||
await chatListCubit.getOrCreateChatSingleContact(contact: contact);
|
||||
await chatListCubit.getOrCreateChatSingleContact(contact: _contact);
|
||||
// Click over to chats
|
||||
if (context.mounted) {
|
||||
await MainPager.of(context)
|
||||
@ -71,9 +79,15 @@ class ContactItemWidget extends StatelessWidget {
|
||||
localConversationRecordKey: localConversationRecordKey);
|
||||
|
||||
// Delete the contact itself
|
||||
await contactListCubit.deleteContact(contact: contact);
|
||||
await contactListCubit.deleteContact(
|
||||
localConversationRecordKey: localConversationRecordKey);
|
||||
})
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
final proto.Contact _contact;
|
||||
final bool _disabled;
|
||||
}
|
||||
|
@ -45,10 +45,13 @@ class ContactListWidget extends StatelessWidget {
|
||||
final lowerValue = value.toLowerCase();
|
||||
return contactList
|
||||
.where((element) =>
|
||||
element.editedProfile.name
|
||||
element.nickname
|
||||
.toLowerCase()
|
||||
.contains(lowerValue) ||
|
||||
element.editedProfile.pronouns
|
||||
element.profile.name
|
||||
.toLowerCase()
|
||||
.contains(lowerValue) ||
|
||||
element.profile.pronouns
|
||||
.toLowerCase()
|
||||
.contains(lowerValue))
|
||||
.toList();
|
||||
|
@ -2,6 +2,7 @@ import 'package:async_tools/async_tools.dart';
|
||||
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
@ -44,12 +45,11 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
AsyncValue<ActiveConversationState>, ActiveConversationCubit>
|
||||
with StateMapFollower<ChatListCubitState, TypedKey, proto.Chat> {
|
||||
ActiveConversationsBlocMapCubit({
|
||||
required UnlockedAccountInfo unlockedAccountInfo,
|
||||
required ContactListCubit contactListCubit,
|
||||
required AccountRecordCubit accountRecordCubit,
|
||||
}) : _activeAccountInfo = unlockedAccountInfo,
|
||||
_contactListCubit = contactListCubit,
|
||||
_accountRecordCubit = accountRecordCubit;
|
||||
required Locator locator,
|
||||
}) : _locator = locator {
|
||||
// Follow the chat list cubit
|
||||
follow(locator<ChatListCubit>());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Public Interface
|
||||
@ -69,13 +69,20 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
// Conversation cubit the tracks the state between the local
|
||||
// and remote halves of a contact's relationship with this account
|
||||
final conversationCubit = ConversationCubit(
|
||||
activeAccountInfo: _activeAccountInfo,
|
||||
locator: _locator,
|
||||
remoteIdentityPublicKey: remoteIdentityPublicKey,
|
||||
localConversationRecordKey: localConversationRecordKey,
|
||||
remoteConversationRecordKey: remoteConversationRecordKey,
|
||||
)..watchAccountChanges(
|
||||
_accountRecordCubit.stream, _accountRecordCubit.state);
|
||||
_contactListCubit.followContactProfileChanges(
|
||||
);
|
||||
|
||||
// When our local account profile changes, send it to the conversation
|
||||
final accountRecordCubit = _locator<AccountRecordCubit>();
|
||||
conversationCubit.watchAccountChanges(
|
||||
accountRecordCubit.stream, accountRecordCubit.state);
|
||||
|
||||
// When remote conversation changes its profile,
|
||||
// update our local contact
|
||||
_locator<ContactListCubit>().followContactProfileChanges(
|
||||
localConversationRecordKey,
|
||||
conversationCubit.stream.map((x) => x.map(
|
||||
data: (d) => d.value.remoteConversation?.profile,
|
||||
@ -112,7 +119,7 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
|
||||
@override
|
||||
Future<void> updateState(TypedKey key, proto.Chat value) async {
|
||||
final contactList = _contactListCubit.state.state.asData?.value;
|
||||
final contactList = _locator<ContactListCubit>().state.state.asData?.value;
|
||||
if (contactList == null) {
|
||||
await addState(key, const AsyncValue.loading());
|
||||
return;
|
||||
@ -129,7 +136,5 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
|
||||
////
|
||||
|
||||
final UnlockedAccountInfo _activeAccountInfo;
|
||||
final ContactListCubit _contactListCubit;
|
||||
final AccountRecordCubit _accountRecordCubit;
|
||||
final Locator _locator;
|
||||
}
|
||||
|
@ -2,14 +2,14 @@ import 'dart:async';
|
||||
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../chat/chat.dart';
|
||||
import '../../chat_list/cubits/chat_list_cubit.dart';
|
||||
import '../../contacts/contacts.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import 'active_conversations_bloc_map_cubit.dart';
|
||||
import '../../chat_list/cubits/chat_list_cubit.dart';
|
||||
|
||||
// Map of localConversationRecordKey to MessagesCubit
|
||||
// Wraps a MessagesCubit to stream the latest messages to the state
|
||||
@ -19,13 +19,11 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
with
|
||||
StateMapFollower<ActiveConversationsBlocMapState, TypedKey,
|
||||
AsyncValue<ActiveConversationState>> {
|
||||
ActiveSingleContactChatBlocMapCubit(
|
||||
{required UnlockedAccountInfo unlockedAccountInfo,
|
||||
required ContactListCubit contactListCubit,
|
||||
required ChatListCubit chatListCubit})
|
||||
: _activeAccountInfo = unlockedAccountInfo,
|
||||
_contactListCubit = contactListCubit,
|
||||
_chatListCubit = chatListCubit;
|
||||
ActiveSingleContactChatBlocMapCubit({required Locator locator})
|
||||
: _locator = locator {
|
||||
// Follow the active conversations bloc map cubit
|
||||
follow(locator<ActiveConversationsBlocMapCubit>());
|
||||
}
|
||||
|
||||
Future<void> _addConversationMessages(
|
||||
{required proto.Contact contact,
|
||||
@ -35,7 +33,7 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
add(() => MapEntry(
|
||||
contact.localConversationRecordKey.toVeilid(),
|
||||
SingleContactMessagesCubit(
|
||||
activeAccountInfo: _activeAccountInfo,
|
||||
locator: _locator,
|
||||
remoteIdentityPublicKey: contact.identityPublicKey.toVeilid(),
|
||||
localConversationRecordKey:
|
||||
contact.localConversationRecordKey.toVeilid(),
|
||||
@ -54,7 +52,7 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
Future<void> updateState(
|
||||
TypedKey key, AsyncValue<ActiveConversationState> value) async {
|
||||
// Get the contact object for this single contact chat
|
||||
final contactList = _contactListCubit.state.state.asData?.value;
|
||||
final contactList = _locator<ContactListCubit>().state.state.asData?.value;
|
||||
if (contactList == null) {
|
||||
await addState(key, const AsyncValue.loading());
|
||||
return;
|
||||
@ -69,7 +67,7 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
final contact = contactList[contactIndex].value;
|
||||
|
||||
// Get the chat object for this single contact chat
|
||||
final chatList = _chatListCubit.state.state.asData?.value;
|
||||
final chatList = _locator<ChatListCubit>().state.state.asData?.value;
|
||||
if (chatList == null) {
|
||||
await addState(key, const AsyncValue.loading());
|
||||
return;
|
||||
@ -95,7 +93,5 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
|
||||
////
|
||||
|
||||
final UnlockedAccountInfo _activeAccountInfo;
|
||||
final ContactListCubit _contactListCubit;
|
||||
final ChatListCubit _chatListCubit;
|
||||
final Locator _locator;
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
@ -35,29 +36,31 @@ class ConversationState extends Equatable {
|
||||
/// 1-1 chats
|
||||
class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
||||
ConversationCubit(
|
||||
{required UnlockedAccountInfo activeAccountInfo,
|
||||
{required Locator locator,
|
||||
required TypedKey remoteIdentityPublicKey,
|
||||
TypedKey? localConversationRecordKey,
|
||||
TypedKey? remoteConversationRecordKey})
|
||||
: _unlockedAccountInfo = activeAccountInfo,
|
||||
: _locator = locator,
|
||||
_localConversationRecordKey = localConversationRecordKey,
|
||||
_remoteIdentityPublicKey = remoteIdentityPublicKey,
|
||||
_remoteConversationRecordKey = remoteConversationRecordKey,
|
||||
super(const AsyncValue.loading()) {
|
||||
final unlockedAccountInfo =
|
||||
_locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
|
||||
_accountRecordKey = unlockedAccountInfo.accountRecordKey;
|
||||
_identityWriter = unlockedAccountInfo.identityWriter;
|
||||
|
||||
if (_localConversationRecordKey != null) {
|
||||
_initWait.add(() async {
|
||||
await _setLocalConversation(() async {
|
||||
final accountRecordKey = _unlockedAccountInfo
|
||||
.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
// Open local record key if it is specified
|
||||
final pool = DHTRecordPool.instance;
|
||||
final crypto = await _cachedConversationCrypto();
|
||||
final writer = _unlockedAccountInfo.identityWriter;
|
||||
final writer = _identityWriter;
|
||||
final record = await pool.openRecordWrite(
|
||||
_localConversationRecordKey!, writer,
|
||||
debugName: 'ConversationCubit::LocalConversation',
|
||||
parent: accountRecordKey,
|
||||
parent: _accountRecordKey,
|
||||
crypto: crypto);
|
||||
|
||||
return record;
|
||||
@ -68,15 +71,12 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
||||
if (_remoteConversationRecordKey != null) {
|
||||
_initWait.add(() async {
|
||||
await _setRemoteConversation(() async {
|
||||
final accountRecordKey = _unlockedAccountInfo
|
||||
.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
// Open remote record key if it is specified
|
||||
final pool = DHTRecordPool.instance;
|
||||
final crypto = await _cachedConversationCrypto();
|
||||
final record = await pool.openRecordRead(_remoteConversationRecordKey,
|
||||
debugName: 'ConversationCubit::RemoteConversation',
|
||||
parent: accountRecordKey,
|
||||
parent: _accountRecordKey,
|
||||
crypto: crypto);
|
||||
return record;
|
||||
});
|
||||
@ -107,18 +107,19 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
||||
/// The callback allows for more initialization to occur and for
|
||||
/// cleanup to delete records upon failure of the callback
|
||||
Future<T> initLocalConversation<T>(
|
||||
{required proto.Profile profile,
|
||||
required FutureOr<T> Function(DHTRecord) callback,
|
||||
{required FutureOr<T> Function(DHTRecord) callback,
|
||||
TypedKey? existingConversationRecordKey}) async {
|
||||
assert(_localConversationRecordKey == null,
|
||||
'must not have a local conversation yet');
|
||||
|
||||
final pool = DHTRecordPool.instance;
|
||||
final accountRecordKey = _unlockedAccountInfo
|
||||
.userLogin.accountRecordInfo.accountRecord.recordKey;
|
||||
|
||||
final crypto = await _cachedConversationCrypto();
|
||||
final writer = _unlockedAccountInfo.identityWriter;
|
||||
final account = _locator<AccountRecordCubit>().state.asData!.value;
|
||||
final unlockedAccountInfo =
|
||||
_locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
|
||||
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
|
||||
final writer = unlockedAccountInfo.identityWriter;
|
||||
|
||||
// Open with SMPL scheme for identity writer
|
||||
late final DHTRecord localConversationRecord;
|
||||
@ -144,15 +145,13 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
||||
.deleteScope((localConversation) async {
|
||||
// Make messages log
|
||||
return _initLocalMessages(
|
||||
activeAccountInfo: _unlockedAccountInfo,
|
||||
remoteIdentityPublicKey: _remoteIdentityPublicKey,
|
||||
localConversationKey: localConversation.key,
|
||||
callback: (messages) async {
|
||||
// Create initial local conversation key contents
|
||||
final conversation = proto.Conversation()
|
||||
..profile = profile
|
||||
..profile = account.profile
|
||||
..superIdentityJson = jsonEncode(
|
||||
_unlockedAccountInfo.localAccount.superIdentity.toJson())
|
||||
unlockedAccountInfo.localAccount.superIdentity.toJson())
|
||||
..messages = messages.recordKey.toProto();
|
||||
|
||||
// Write initial conversation to record
|
||||
@ -340,14 +339,11 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
||||
|
||||
// Initialize local messages
|
||||
Future<T> _initLocalMessages<T>({
|
||||
required UnlockedAccountInfo activeAccountInfo,
|
||||
required TypedKey remoteIdentityPublicKey,
|
||||
required TypedKey localConversationKey,
|
||||
required FutureOr<T> Function(DHTLog) callback,
|
||||
}) async {
|
||||
final crypto =
|
||||
await activeAccountInfo.makeConversationCrypto(remoteIdentityPublicKey);
|
||||
final writer = activeAccountInfo.identityWriter;
|
||||
final crypto = await _cachedConversationCrypto();
|
||||
final writer = _identityWriter;
|
||||
|
||||
return (await DHTLog.create(
|
||||
debugName: 'ConversationCubit::initLocalMessages::LocalMessages',
|
||||
@ -362,17 +358,19 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
|
||||
if (conversationCrypto != null) {
|
||||
return conversationCrypto;
|
||||
}
|
||||
conversationCrypto = await _unlockedAccountInfo
|
||||
final unlockedAccountInfo =
|
||||
_locator<ActiveAccountInfoCubit>().state.unlockedAccountInfo!;
|
||||
conversationCrypto = await unlockedAccountInfo
|
||||
.makeConversationCrypto(_remoteIdentityPublicKey);
|
||||
|
||||
_conversationCrypto = conversationCrypto;
|
||||
return conversationCrypto;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Fields
|
||||
|
||||
final UnlockedAccountInfo _unlockedAccountInfo;
|
||||
final Locator _locator;
|
||||
late final TypedKey _accountRecordKey;
|
||||
late final KeyPair _identityWriter;
|
||||
final TypedKey _remoteIdentityPublicKey;
|
||||
TypedKey? _localConversationRecordKey;
|
||||
final TypedKey? _remoteConversationRecordKey;
|
||||
|
@ -1,3 +1,2 @@
|
||||
export 'home_account_ready_chat.dart';
|
||||
export 'home_account_ready_main.dart';
|
||||
export 'home_account_ready_shell.dart';
|
||||
|
@ -6,6 +6,7 @@ import 'package:flutter_zoom_drawer/flutter_zoom_drawer.dart';
|
||||
|
||||
import '../../../account_manager/account_manager.dart';
|
||||
import '../../../chat/chat.dart';
|
||||
import '../../../proto/proto.dart' as proto;
|
||||
import '../../../theme/theme.dart';
|
||||
import '../../../tools/tools.dart';
|
||||
import 'main_pager/main_pager.dart';
|
||||
@ -29,7 +30,8 @@ class _HomeAccountReadyMainState extends State<HomeAccountReadyMain> {
|
||||
}
|
||||
|
||||
Widget buildUserPanel() => Builder(builder: (context) {
|
||||
final account = context.watch<AccountRecordCubit>().state;
|
||||
final profile = context.select<AccountRecordCubit, proto.Profile>(
|
||||
(c) => c.state.asData!.value.profile);
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
||||
@ -50,9 +52,7 @@ class _HomeAccountReadyMainState extends State<HomeAccountReadyMain> {
|
||||
await ctrl.toggle?.call();
|
||||
//await GoRouterHelper(context).push('/settings');
|
||||
}).paddingLTRB(0, 0, 8, 0),
|
||||
asyncValueBuilder(account,
|
||||
(_, account) => ProfileWidget(profile: account.profile))
|
||||
.expanded(),
|
||||
ProfileWidget(profile: profile).expanded(),
|
||||
]).paddingAll(8),
|
||||
const MainPager().expanded()
|
||||
]);
|
||||
@ -72,8 +72,8 @@ class _HomeAccountReadyMainState extends State<HomeAccountReadyMain> {
|
||||
return const NoConversationWidget();
|
||||
}
|
||||
return ChatComponentWidget.builder(
|
||||
localConversationRecordKey: activeChatLocalConversationKey,
|
||||
);
|
||||
localConversationRecordKey: activeChatLocalConversationKey,
|
||||
key: ValueKey(activeChatLocalConversationKey));
|
||||
}
|
||||
|
||||
// ignore: prefer_expression_function_bodies
|
||||
|
@ -1,158 +0,0 @@
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../account_manager/account_manager.dart';
|
||||
import '../../../chat/chat.dart';
|
||||
import '../../../chat_list/chat_list.dart';
|
||||
import '../../../contact_invitation/contact_invitation.dart';
|
||||
import '../../../contacts/contacts.dart';
|
||||
import '../../../conversation/conversation.dart';
|
||||
import '../../../router/router.dart';
|
||||
import '../../../theme/theme.dart';
|
||||
|
||||
class HomeAccountReadyShell extends StatefulWidget {
|
||||
factory HomeAccountReadyShell(
|
||||
{required BuildContext context, required Widget child, Key? key}) {
|
||||
// These must exist in order for the account to
|
||||
// be considered 'ready' for this widget subtree
|
||||
final unlockedAccountInfo = context.watch<UnlockedAccountInfo>();
|
||||
final routerCubit = context.read<RouterCubit>();
|
||||
|
||||
return HomeAccountReadyShell._(
|
||||
unlockedAccountInfo: unlockedAccountInfo,
|
||||
routerCubit: routerCubit,
|
||||
key: key,
|
||||
child: child);
|
||||
}
|
||||
const HomeAccountReadyShell._(
|
||||
{required this.unlockedAccountInfo,
|
||||
required this.routerCubit,
|
||||
required this.child,
|
||||
super.key});
|
||||
|
||||
@override
|
||||
HomeAccountReadyShellState createState() => HomeAccountReadyShellState();
|
||||
|
||||
final Widget child;
|
||||
final UnlockedAccountInfo unlockedAccountInfo;
|
||||
final RouterCubit routerCubit;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty<UnlockedAccountInfo>(
|
||||
'unlockedAccountInfo', unlockedAccountInfo))
|
||||
..add(DiagnosticsProperty<RouterCubit>('routerCubit', routerCubit));
|
||||
}
|
||||
}
|
||||
|
||||
class HomeAccountReadyShellState extends State<HomeAccountReadyShell> {
|
||||
final SingleStateProcessor<WaitingInvitationsBlocMapState>
|
||||
_singleInvitationStatusProcessor = SingleStateProcessor();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
// Process all accepted or rejected invitations
|
||||
void _invitationStatusListener(
|
||||
BuildContext context, WaitingInvitationsBlocMapState state) {
|
||||
_singleInvitationStatusProcessor.updateState(state, (newState) async {
|
||||
final contactListCubit = context.read<ContactListCubit>();
|
||||
final contactInvitationListCubit =
|
||||
context.read<ContactInvitationListCubit>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Delete invitation and process the accepted or rejected contact
|
||||
final acceptedContact = invStatus.acceptedContact;
|
||||
if (acceptedContact != null) {
|
||||
await contactInvitationListCubit.deleteInvitation(
|
||||
accepted: true,
|
||||
contactRequestInboxRecordKey: contactRequestInboxRecordKey);
|
||||
|
||||
// Accept
|
||||
await contactListCubit.createContact(
|
||||
remoteProfile: acceptedContact.remoteProfile,
|
||||
remoteSuperIdentity: acceptedContact.remoteIdentity,
|
||||
remoteConversationRecordKey:
|
||||
acceptedContact.remoteConversationRecordKey,
|
||||
localConversationRecordKey:
|
||||
acceptedContact.localConversationRecordKey,
|
||||
);
|
||||
} else {
|
||||
// Reject
|
||||
await contactInvitationListCubit.deleteInvitation(
|
||||
accepted: false,
|
||||
contactRequestInboxRecordKey: contactRequestInboxRecordKey);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// XXX: Should probably eliminate this in favor
|
||||
// of streaming changes into other cubits. Too much rebuilding!
|
||||
// should not need to 'watch' all these cubits
|
||||
final account = context.watch<AccountRecordCubit>().state.asData?.value;
|
||||
if (account == null) {
|
||||
return waitingPage();
|
||||
}
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
// Contact Cubits
|
||||
BlocProvider(
|
||||
create: (context) => ContactInvitationListCubit(
|
||||
unlockedAccountInfo: widget.unlockedAccountInfo,
|
||||
account: account)),
|
||||
BlocProvider(
|
||||
create: (context) => ContactListCubit(
|
||||
unlockedAccountInfo: widget.unlockedAccountInfo,
|
||||
account: account)),
|
||||
BlocProvider(
|
||||
create: (context) => WaitingInvitationsBlocMapCubit(
|
||||
unlockedAccountInfo: widget.unlockedAccountInfo,
|
||||
account: account)
|
||||
..follow(context.read<ContactInvitationListCubit>())),
|
||||
// Chat Cubits
|
||||
BlocProvider(
|
||||
create: (context) => ActiveChatCubit(null,
|
||||
routerCubit: context.read<RouterCubit>())),
|
||||
BlocProvider(
|
||||
create: (context) => ChatListCubit(
|
||||
unlockedAccountInfo: widget.unlockedAccountInfo,
|
||||
activeChatCubit: context.read<ActiveChatCubit>(),
|
||||
account: account)),
|
||||
// Conversation Cubits
|
||||
BlocProvider(
|
||||
create: (context) => ActiveConversationsBlocMapCubit(
|
||||
unlockedAccountInfo: widget.unlockedAccountInfo,
|
||||
contactListCubit: context.read<ContactListCubit>(),
|
||||
accountRecordCubit: context.read<AccountRecordCubit>())
|
||||
..follow(context.read<ChatListCubit>())),
|
||||
BlocProvider(
|
||||
create: (context) => ActiveSingleContactChatBlocMapCubit(
|
||||
unlockedAccountInfo: widget.unlockedAccountInfo,
|
||||
contactListCubit: context.read<ContactListCubit>(),
|
||||
chatListCubit: context.read<ChatListCubit>())
|
||||
..follow(context.read<ActiveConversationsBlocMapCubit>())),
|
||||
],
|
||||
child: MultiBlocListener(listeners: [
|
||||
BlocListener<WaitingInvitationsBlocMapCubit,
|
||||
WaitingInvitationsBlocMapState>(
|
||||
listener: _invitationStatusListener,
|
||||
)
|
||||
], child: widget.child));
|
||||
}
|
||||
}
|
@ -1,11 +1,20 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_zoom_drawer/flutter_zoom_drawer.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../chat/chat.dart';
|
||||
import '../../chat_list/chat_list.dart';
|
||||
import '../../contact_invitation/contact_invitation.dart';
|
||||
import '../../contacts/contacts.dart';
|
||||
import '../../conversation/conversation.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../router/router.dart';
|
||||
import '../../theme/theme.dart';
|
||||
import 'drawer_menu/drawer_menu.dart';
|
||||
import 'home_account_invalid.dart';
|
||||
@ -33,25 +42,133 @@ class HomeShellState extends State<HomeShell> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget buildWithLogin(BuildContext context) {
|
||||
final accountInfo = context.watch<ActiveAccountInfoCubit>().state;
|
||||
final accountRecordsCubit = context.watch<AccountRecordsBlocMapCubit>();
|
||||
if (!accountInfo.active) {
|
||||
// Process all accepted or rejected invitations
|
||||
void _invitationStatusListener(
|
||||
BuildContext context, WaitingInvitationsBlocMapState state) {
|
||||
_singleInvitationStatusProcessor.updateState(state, (newState) async {
|
||||
final contactListCubit = context.read<ContactListCubit>();
|
||||
final contactInvitationListCubit =
|
||||
context.read<ContactInvitationListCubit>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Delete invitation and process the accepted or rejected contact
|
||||
final acceptedContact = invStatus.acceptedContact;
|
||||
if (acceptedContact != null) {
|
||||
await contactInvitationListCubit.deleteInvitation(
|
||||
accepted: true,
|
||||
contactRequestInboxRecordKey: contactRequestInboxRecordKey);
|
||||
|
||||
// Accept
|
||||
await contactListCubit.createContact(
|
||||
profile: acceptedContact.remoteProfile,
|
||||
remoteSuperIdentity: acceptedContact.remoteIdentity,
|
||||
remoteConversationRecordKey:
|
||||
acceptedContact.remoteConversationRecordKey,
|
||||
localConversationRecordKey:
|
||||
acceptedContact.localConversationRecordKey,
|
||||
);
|
||||
} else {
|
||||
// Reject
|
||||
await contactInvitationListCubit.deleteInvitation(
|
||||
accepted: false,
|
||||
contactRequestInboxRecordKey: contactRequestInboxRecordKey);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildActiveAccount(BuildContext context) {
|
||||
final accountRecordKey = context.select<ActiveAccountInfoCubit, TypedKey>(
|
||||
(c) => c.state.unlockedAccountInfo!.accountRecordKey);
|
||||
final contactListRecordPointer =
|
||||
context.select<AccountRecordCubit, OwnedDHTRecordPointer?>(
|
||||
(c) => c.state.asData?.value.contactList.toVeilid());
|
||||
final contactInvitationListRecordPointer =
|
||||
context.select<AccountRecordCubit, OwnedDHTRecordPointer?>(
|
||||
(c) => c.state.asData?.value.contactInvitationRecords.toVeilid());
|
||||
final chatListRecordPointer =
|
||||
context.select<AccountRecordCubit, OwnedDHTRecordPointer?>(
|
||||
(c) => c.state.asData?.value.chatList.toVeilid());
|
||||
|
||||
if (contactListRecordPointer == null ||
|
||||
contactInvitationListRecordPointer == null ||
|
||||
chatListRecordPointer == null) {
|
||||
return waitingPage();
|
||||
}
|
||||
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
// Contact Cubits
|
||||
BlocProvider(
|
||||
create: (context) => ContactInvitationListCubit(
|
||||
locator: context.read,
|
||||
accountRecordKey: accountRecordKey,
|
||||
contactInvitationListRecordPointer:
|
||||
contactInvitationListRecordPointer,
|
||||
)),
|
||||
BlocProvider(
|
||||
create: (context) => ContactListCubit(
|
||||
locator: context.read,
|
||||
accountRecordKey: accountRecordKey,
|
||||
contactListRecordPointer: contactListRecordPointer)),
|
||||
BlocProvider(
|
||||
create: (context) => WaitingInvitationsBlocMapCubit(
|
||||
locator: context.read,
|
||||
)),
|
||||
// Chat Cubits
|
||||
BlocProvider(
|
||||
create: (context) => ActiveChatCubit(null,
|
||||
routerCubit: context.read<RouterCubit>())),
|
||||
BlocProvider(
|
||||
create: (context) => ChatListCubit(
|
||||
locator: context.read,
|
||||
accountRecordKey: accountRecordKey,
|
||||
chatListRecordPointer: chatListRecordPointer)),
|
||||
// Conversation Cubits
|
||||
BlocProvider(
|
||||
create: (context) => ActiveConversationsBlocMapCubit(
|
||||
locator: context.read,
|
||||
)),
|
||||
BlocProvider(
|
||||
create: (context) => ActiveSingleContactChatBlocMapCubit(
|
||||
locator: context.read,
|
||||
)),
|
||||
],
|
||||
child: MultiBlocListener(listeners: [
|
||||
BlocListener<WaitingInvitationsBlocMapCubit,
|
||||
WaitingInvitationsBlocMapState>(
|
||||
listener: _invitationStatusListener,
|
||||
)
|
||||
], child: widget.child));
|
||||
}
|
||||
|
||||
Widget _buildWithLogin(BuildContext context) {
|
||||
// Get active account info status
|
||||
final (
|
||||
accountInfoStatus,
|
||||
accountInfoActive,
|
||||
superIdentityRecordKey
|
||||
) = context
|
||||
.select<ActiveAccountInfoCubit, (AccountInfoStatus, bool, TypedKey?)>(
|
||||
(c) => (
|
||||
c.state.status,
|
||||
c.state.active,
|
||||
c.state.unlockedAccountInfo?.superIdentityRecordKey
|
||||
));
|
||||
|
||||
if (!accountInfoActive) {
|
||||
// If no logged in user is active, show the loading panel
|
||||
return const HomeNoActive();
|
||||
}
|
||||
|
||||
final superIdentityRecordKey =
|
||||
accountInfo.unlockedAccountInfo?.superIdentityRecordKey;
|
||||
final activeCubit = superIdentityRecordKey == null
|
||||
? null
|
||||
: accountRecordsCubit.tryOperate(superIdentityRecordKey,
|
||||
closure: (c) => c);
|
||||
if (activeCubit == null) {
|
||||
return waitingPage();
|
||||
}
|
||||
|
||||
switch (accountInfo.status) {
|
||||
switch (accountInfoStatus) {
|
||||
case AccountInfoStatus.noAccount:
|
||||
return const HomeAccountMissing();
|
||||
case AccountInfoStatus.accountInvalid:
|
||||
@ -59,17 +176,21 @@ class HomeShellState extends State<HomeShell> {
|
||||
case AccountInfoStatus.accountLocked:
|
||||
return const HomeAccountLocked();
|
||||
case AccountInfoStatus.accountReady:
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<AccountRecordCubit>.value(value: activeCubit),
|
||||
],
|
||||
child: MultiProvider(providers: [
|
||||
Provider<UnlockedAccountInfo>.value(
|
||||
value: accountInfo.unlockedAccountInfo!,
|
||||
),
|
||||
Provider<ZoomDrawerController>.value(
|
||||
value: _zoomDrawerController),
|
||||
], child: widget.child));
|
||||
|
||||
// Get the current active account record cubit
|
||||
final activeAccountRecordCubit =
|
||||
context.select<AccountRecordsBlocMapCubit, AccountRecordCubit?>(
|
||||
(c) => superIdentityRecordKey == null
|
||||
? null
|
||||
: c.tryOperate(superIdentityRecordKey, closure: (x) => x));
|
||||
if (activeAccountRecordCubit == null) {
|
||||
return waitingPage();
|
||||
}
|
||||
|
||||
return MultiBlocProvider(providers: [
|
||||
BlocProvider<AccountRecordCubit>.value(
|
||||
value: activeAccountRecordCubit),
|
||||
], child: Builder(builder: _buildActiveAccount));
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,7 +217,9 @@ class HomeShellState extends State<HomeShell> {
|
||||
mainScreen: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: scale.primaryScale.activeElementBackground),
|
||||
child: buildWithLogin(context)),
|
||||
child: Provider<ZoomDrawerController>.value(
|
||||
value: _zoomDrawerController,
|
||||
child: Builder(builder: _buildWithLogin))),
|
||||
borderRadius: 24,
|
||||
showShadow: true,
|
||||
angle: 0,
|
||||
@ -112,5 +235,54 @@ class HomeShellState extends State<HomeShell> {
|
||||
)));
|
||||
}
|
||||
|
||||
final ZoomDrawerController _zoomDrawerController = ZoomDrawerController();
|
||||
final _zoomDrawerController = ZoomDrawerController();
|
||||
final _singleInvitationStatusProcessor =
|
||||
SingleStateProcessor<WaitingInvitationsBlocMapState>();
|
||||
}
|
||||
|
||||
// class HomeAccountReadyShell extends StatefulWidget {
|
||||
// factory HomeAccountReadyShell(
|
||||
// {required BuildContext context, required Widget child, Key? key}) {
|
||||
// // These must exist in order for the account to
|
||||
// // be considered 'ready' for this widget subtree
|
||||
// final unlockedAccountInfo = context.watch<UnlockedAccountInfo>();
|
||||
// final routerCubit = context.read<RouterCubit>();
|
||||
|
||||
// return HomeAccountReadyShell._(
|
||||
// unlockedAccountInfo: unlockedAccountInfo,
|
||||
// routerCubit: routerCubit,
|
||||
// key: key,
|
||||
// child: child);
|
||||
// }
|
||||
// const HomeAccountReadyShell._(
|
||||
// {required this.unlockedAccountInfo,
|
||||
// required this.routerCubit,
|
||||
// required this.child,
|
||||
// super.key});
|
||||
|
||||
// @override
|
||||
// HomeAccountReadyShellState createState() => HomeAccountReadyShellState();
|
||||
|
||||
// final Widget child;
|
||||
// final UnlockedAccountInfo unlockedAccountInfo;
|
||||
// final RouterCubit routerCubit;
|
||||
|
||||
// @override
|
||||
// void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
// super.debugFillProperties(properties);
|
||||
// properties
|
||||
// ..add(DiagnosticsProperty<UnlockedAccountInfo>(
|
||||
// 'unlockedAccountInfo', unlockedAccountInfo))
|
||||
// ..add(DiagnosticsProperty<RouterCubit>('routerCubit', routerCubit));
|
||||
// }
|
||||
// }
|
||||
|
||||
// class HomeAccountReadyShellState extends State<HomeAccountReadyShell> {
|
||||
// final SingleStateProcessor<WaitingInvitationsBlocMapState>
|
||||
// _singleInvitationStatusProcessor = SingleStateProcessor();
|
||||
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// }
|
||||
// }
|
||||
|
@ -29,3 +29,8 @@ extension MessageExt on proto.Message {
|
||||
static int compareTimestamp(proto.Message a, proto.Message b) =>
|
||||
a.timestamp.compareTo(b.timestamp);
|
||||
}
|
||||
|
||||
extension ContactExt on proto.Contact {
|
||||
String get displayName =>
|
||||
nickname.isNotEmpty ? '$nickname (${profile.name})' : profile.name;
|
||||
}
|
||||
|
@ -1606,13 +1606,14 @@ class Contact extends $pb.GeneratedMessage {
|
||||
factory Contact.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Contact', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create)
|
||||
..aOM<Profile>(1, _omitFieldNames ? '' : 'editedProfile', subBuilder: Profile.create)
|
||||
..aOM<Profile>(2, _omitFieldNames ? '' : 'remoteProfile', subBuilder: Profile.create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'nickname')
|
||||
..aOM<Profile>(2, _omitFieldNames ? '' : 'profile', subBuilder: Profile.create)
|
||||
..aOS(3, _omitFieldNames ? '' : 'superIdentityJson')
|
||||
..aOM<$0.TypedKey>(4, _omitFieldNames ? '' : 'identityPublicKey', subBuilder: $0.TypedKey.create)
|
||||
..aOM<$0.TypedKey>(5, _omitFieldNames ? '' : 'remoteConversationRecordKey', subBuilder: $0.TypedKey.create)
|
||||
..aOM<$0.TypedKey>(6, _omitFieldNames ? '' : 'localConversationRecordKey', subBuilder: $0.TypedKey.create)
|
||||
..aOB(7, _omitFieldNames ? '' : 'showAvailability')
|
||||
..aOS(8, _omitFieldNames ? '' : 'notes')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@ -1638,26 +1639,24 @@ class Contact extends $pb.GeneratedMessage {
|
||||
static Contact? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
Profile get editedProfile => $_getN(0);
|
||||
$core.String get nickname => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set editedProfile(Profile v) { setField(1, v); }
|
||||
set nickname($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasEditedProfile() => $_has(0);
|
||||
$core.bool hasNickname() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearEditedProfile() => clearField(1);
|
||||
@$pb.TagNumber(1)
|
||||
Profile ensureEditedProfile() => $_ensure(0);
|
||||
void clearNickname() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
Profile get remoteProfile => $_getN(1);
|
||||
Profile get profile => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set remoteProfile(Profile v) { setField(2, v); }
|
||||
set profile(Profile v) { setField(2, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasRemoteProfile() => $_has(1);
|
||||
$core.bool hasProfile() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearRemoteProfile() => clearField(2);
|
||||
void clearProfile() => clearField(2);
|
||||
@$pb.TagNumber(2)
|
||||
Profile ensureRemoteProfile() => $_ensure(1);
|
||||
Profile ensureProfile() => $_ensure(1);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.String get superIdentityJson => $_getSZ(2);
|
||||
@ -1709,6 +1708,15 @@ class Contact extends $pb.GeneratedMessage {
|
||||
$core.bool hasShowAvailability() => $_has(6);
|
||||
@$pb.TagNumber(7)
|
||||
void clearShowAvailability() => clearField(7);
|
||||
|
||||
@$pb.TagNumber(8)
|
||||
$core.String get notes => $_getSZ(7);
|
||||
@$pb.TagNumber(8)
|
||||
set notes($core.String v) { $_setString(7, v); }
|
||||
@$pb.TagNumber(8)
|
||||
$core.bool hasNotes() => $_has(7);
|
||||
@$pb.TagNumber(8)
|
||||
void clearNotes() => clearField(8);
|
||||
}
|
||||
|
||||
class ContactInvitation extends $pb.GeneratedMessage {
|
||||
|
@ -455,27 +455,27 @@ final $typed_data.Uint8List accountDescriptor = $convert.base64Decode(
|
||||
const Contact$json = {
|
||||
'1': 'Contact',
|
||||
'2': [
|
||||
{'1': 'edited_profile', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.Profile', '10': 'editedProfile'},
|
||||
{'1': 'remote_profile', '3': 2, '4': 1, '5': 11, '6': '.veilidchat.Profile', '10': 'remoteProfile'},
|
||||
{'1': 'nickname', '3': 1, '4': 1, '5': 9, '10': 'nickname'},
|
||||
{'1': 'profile', '3': 2, '4': 1, '5': 11, '6': '.veilidchat.Profile', '10': 'profile'},
|
||||
{'1': 'super_identity_json', '3': 3, '4': 1, '5': 9, '10': 'superIdentityJson'},
|
||||
{'1': 'identity_public_key', '3': 4, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'identityPublicKey'},
|
||||
{'1': 'remote_conversation_record_key', '3': 5, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'remoteConversationRecordKey'},
|
||||
{'1': 'local_conversation_record_key', '3': 6, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'localConversationRecordKey'},
|
||||
{'1': 'show_availability', '3': 7, '4': 1, '5': 8, '10': 'showAvailability'},
|
||||
{'1': 'notes', '3': 8, '4': 1, '5': 9, '10': 'notes'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Contact`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List contactDescriptor = $convert.base64Decode(
|
||||
'CgdDb250YWN0EjoKDmVkaXRlZF9wcm9maWxlGAEgASgLMhMudmVpbGlkY2hhdC5Qcm9maWxlUg'
|
||||
'1lZGl0ZWRQcm9maWxlEjoKDnJlbW90ZV9wcm9maWxlGAIgASgLMhMudmVpbGlkY2hhdC5Qcm9m'
|
||||
'aWxlUg1yZW1vdGVQcm9maWxlEi4KE3N1cGVyX2lkZW50aXR5X2pzb24YAyABKAlSEXN1cGVySW'
|
||||
'RlbnRpdHlKc29uEkAKE2lkZW50aXR5X3B1YmxpY19rZXkYBCABKAsyEC52ZWlsaWQuVHlwZWRL'
|
||||
'ZXlSEWlkZW50aXR5UHVibGljS2V5ElUKHnJlbW90ZV9jb252ZXJzYXRpb25fcmVjb3JkX2tleR'
|
||||
'gFIAEoCzIQLnZlaWxpZC5UeXBlZEtleVIbcmVtb3RlQ29udmVyc2F0aW9uUmVjb3JkS2V5ElMK'
|
||||
'HWxvY2FsX2NvbnZlcnNhdGlvbl9yZWNvcmRfa2V5GAYgASgLMhAudmVpbGlkLlR5cGVkS2V5Uh'
|
||||
'psb2NhbENvbnZlcnNhdGlvblJlY29yZEtleRIrChFzaG93X2F2YWlsYWJpbGl0eRgHIAEoCFIQ'
|
||||
'c2hvd0F2YWlsYWJpbGl0eQ==');
|
||||
'CgdDb250YWN0EhoKCG5pY2tuYW1lGAEgASgJUghuaWNrbmFtZRItCgdwcm9maWxlGAIgASgLMh'
|
||||
'MudmVpbGlkY2hhdC5Qcm9maWxlUgdwcm9maWxlEi4KE3N1cGVyX2lkZW50aXR5X2pzb24YAyAB'
|
||||
'KAlSEXN1cGVySWRlbnRpdHlKc29uEkAKE2lkZW50aXR5X3B1YmxpY19rZXkYBCABKAsyEC52ZW'
|
||||
'lsaWQuVHlwZWRLZXlSEWlkZW50aXR5UHVibGljS2V5ElUKHnJlbW90ZV9jb252ZXJzYXRpb25f'
|
||||
'cmVjb3JkX2tleRgFIAEoCzIQLnZlaWxpZC5UeXBlZEtleVIbcmVtb3RlQ29udmVyc2F0aW9uUm'
|
||||
'Vjb3JkS2V5ElMKHWxvY2FsX2NvbnZlcnNhdGlvbl9yZWNvcmRfa2V5GAYgASgLMhAudmVpbGlk'
|
||||
'LlR5cGVkS2V5Uhpsb2NhbENvbnZlcnNhdGlvblJlY29yZEtleRIrChFzaG93X2F2YWlsYWJpbG'
|
||||
'l0eRgHIAEoCFIQc2hvd0F2YWlsYWJpbGl0eRIUCgVub3RlcxgIIAEoCVIFbm90ZXM=');
|
||||
|
||||
@$core.Deprecated('Use contactInvitationDescriptor instead')
|
||||
const ContactInvitation$json = {
|
||||
|
@ -349,10 +349,10 @@ message Account {
|
||||
//
|
||||
// Stored in ContactList DHTList
|
||||
message Contact {
|
||||
// Friend's profile as locally edited
|
||||
Profile edited_profile = 1;
|
||||
// Friend's nickname
|
||||
string nickname = 1;
|
||||
// Copy of friend's profile from remote conversation
|
||||
Profile remote_profile = 2;
|
||||
Profile profile = 2;
|
||||
// Copy of friend's SuperIdentity in JSON from remote conversation
|
||||
string super_identity_json = 3;
|
||||
// Copy of friend's most recent identity public key from their identityMaster
|
||||
@ -363,6 +363,8 @@ message Contact {
|
||||
veilid.TypedKey local_conversation_record_key = 6;
|
||||
// Show availability to this contact
|
||||
bool show_availability = 7;
|
||||
// Notes about this friend
|
||||
string notes = 8;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -21,7 +21,6 @@ part 'router_cubit.g.dart';
|
||||
|
||||
final _rootNavKey = GlobalKey<NavigatorState>(debugLabel: 'rootNavKey');
|
||||
final _homeNavKey = GlobalKey<NavigatorState>(debugLabel: 'homeNavKey');
|
||||
final _activeNavKey = GlobalKey<NavigatorState>(debugLabel: 'activeNavKey');
|
||||
|
||||
@freezed
|
||||
class RouterState with _$RouterState {
|
||||
@ -69,20 +68,14 @@ class RouterCubit extends Cubit<RouterState> {
|
||||
navigatorKey: _homeNavKey,
|
||||
builder: (context, state, child) => HomeShell(child: child),
|
||||
routes: [
|
||||
ShellRoute(
|
||||
navigatorKey: _activeNavKey,
|
||||
builder: (context, state, child) =>
|
||||
HomeAccountReadyShell(context: context, child: child),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => const HomeAccountReadyMain(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/chat',
|
||||
builder: (context, state) => const HomeAccountReadyChat(),
|
||||
),
|
||||
]),
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => const HomeAccountReadyMain(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/chat',
|
||||
builder: (context, state) => const HomeAccountReadyChat(),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
|
Loading…
Reference in New Issue
Block a user