refactor and cleanup in prep for profile changing

This commit is contained in:
Christien Rioux 2024-06-13 14:52:34 -04:00
parent 87bb1657c7
commit 56d65442f4
49 changed files with 967 additions and 655 deletions

View file

@ -27,6 +27,22 @@
"name": "Name", "name": "Name",
"pronouns": "Pronouns" "pronouns": "Pronouns"
}, },
"edit_account_page": {
"titlebar": "Edit Account",
"header": "Account Profile",
"update": "Update",
"instructions": "This information will be shared with the people you invite to connect with you on VeilidChat.",
"error": "Account modification error",
"name": "Name",
"pronouns": "Pronouns",
"remove_account": "Remove Account",
"delete_identity": "Delete Identity",
"remove_account_confirm": "Confirm Account Removal?",
"remove_account_description": "Remove account from this device only",
"delete_identity_description": "Delete identity and all messages completely",
"delete_identity_confirm_message": "This action is PERMANENT, and your identity will no longer be recoverable with the recovery key. This will not remove your messages you have sent from other people's devices.",
"confirm_are_you_sure": "Are you sure you want to do this?"
},
"show_recovery_key_page": { "show_recovery_key_page": {
"titlebar": "Save Recovery Key", "titlebar": "Save Recovery Key",
"instructions": "You must save this recovery key somewhere safe. This key is the ONLY way to recover your VeilidChat account in the event of a forgotton password or a lost, stolen, or compromised device.", "instructions": "You must save this recovery key somewhere safe. This key is the ONLY way to recover your VeilidChat account in the event of a forgotton password or a lost, stolen, or compromised device.",

View file

@ -3,13 +3,33 @@ import 'dart:async';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../proto/proto.dart' as proto; import '../../proto/proto.dart' as proto;
import '../account_manager.dart';
typedef AccountRecordState = proto.Account; typedef AccountRecordState = proto.Account;
class AccountRecordCubit extends DefaultDHTRecordCubit<AccountRecordState> { class AccountRecordCubit extends DefaultDHTRecordCubit<AccountRecordState> {
AccountRecordCubit({ AccountRecordCubit(
required super.open, {required AccountRepository accountRepository,
}) : super(decodeState: proto.Account.fromBuffer); required TypedKey superIdentityRecordKey})
: super(
decodeState: proto.Account.fromBuffer,
open: () => _open(accountRepository, superIdentityRecordKey));
static Future<DHTRecord> _open(AccountRepository accountRepository,
TypedKey superIdentityRecordKey) async {
final localAccount =
accountRepository.fetchLocalAccount(superIdentityRecordKey)!;
final userLogin = accountRepository.fetchUserLogin(superIdentityRecordKey)!;
// Record not yet open, do it
final pool = DHTRecordPool.instance;
final record = await pool.openRecordOwned(
userLogin.accountRecordInfo.accountRecord,
debugName: 'AccountRecordCubit::_open::AccountRecord',
parent: localAccount.superIdentity.currentInstance.recordKey);
return record;
}
@override @override
Future<void> close() async { Future<void> close() async {

View file

@ -15,11 +15,13 @@ class AccountRecordsBlocMapCubit extends BlocMapCubit<TypedKey,
: _accountRepository = accountRepository; : _accountRepository = accountRepository;
// Add account record cubit // Add account record cubit
Future<void> _addAccountRecordCubit({required UserLogin userLogin}) async => Future<void> _addAccountRecordCubit(
{required TypedKey superIdentityRecordKey}) async =>
add(() => MapEntry( add(() => MapEntry(
userLogin.superIdentityRecordKey, superIdentityRecordKey,
AccountRecordCubit( AccountRecordCubit(
open: () => _accountRepository.openAccountRecord(userLogin)))); accountRepository: _accountRepository,
superIdentityRecordKey: superIdentityRecordKey)));
/// StateFollower ///////////////////////// /// StateFollower /////////////////////////
@ -28,7 +30,8 @@ class AccountRecordsBlocMapCubit extends BlocMapCubit<TypedKey,
@override @override
Future<void> updateState(TypedKey key, UserLogin value) async { Future<void> updateState(TypedKey key, UserLogin value) async {
await _addAccountRecordCubit(userLogin: value); await _addAccountRecordCubit(
superIdentityRecordKey: value.superIdentityRecordKey);
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////

View file

@ -1,23 +1,23 @@
import 'dart:async'; import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:veilid_support/veilid_support.dart';
import '../models/models.dart';
import '../repository/account_repository.dart'; import '../repository/account_repository.dart';
class ActiveLocalAccountCubit extends Cubit<TypedKey?> { class ActiveAccountInfoCubit extends Cubit<AccountInfo> {
ActiveLocalAccountCubit(AccountRepository accountRepository) ActiveAccountInfoCubit(AccountRepository accountRepository)
: _accountRepository = accountRepository, : _accountRepository = accountRepository,
super(accountRepository.getActiveLocalAccount()) { super(accountRepository
.getAccountInfo(accountRepository.getActiveLocalAccount())) {
// Subscribe to streams // Subscribe to streams
_accountRepositorySubscription = _accountRepository.stream.listen((change) { _accountRepositorySubscription = _accountRepository.stream.listen((change) {
switch (change) { switch (change) {
case AccountRepositoryChange.activeLocalAccount: case AccountRepositoryChange.activeLocalAccount:
emit(_accountRepository.getActiveLocalAccount());
break;
// Ignore these
case AccountRepositoryChange.localAccounts: case AccountRepositoryChange.localAccounts:
case AccountRepositoryChange.userLogins: case AccountRepositoryChange.userLogins:
emit(accountRepository
.getAccountInfo(accountRepository.getActiveLocalAccount()));
break; break;
} }
}); });

View file

@ -1,5 +1,5 @@
export 'account_record_cubit.dart'; export 'account_record_cubit.dart';
export 'account_records_bloc_map_cubit.dart'; export 'account_records_bloc_map_cubit.dart';
export 'active_local_account_cubit.dart'; export 'active_account_info_cubit.dart';
export 'local_accounts_cubit.dart'; export 'local_accounts_cubit.dart';
export 'user_logins_cubit.dart'; export 'user_logins_cubit.dart';

View file

@ -1,6 +1,6 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'active_account_info.dart'; import 'unlocked_account_info.dart';
enum AccountInfoStatus { enum AccountInfoStatus {
noAccount, noAccount,
@ -14,10 +14,10 @@ class AccountInfo {
const AccountInfo({ const AccountInfo({
required this.status, required this.status,
required this.active, required this.active,
required this.activeAccountInfo, required this.unlockedAccountInfo,
}); });
final AccountInfoStatus status; final AccountInfoStatus status;
final bool active; final bool active;
final ActiveAccountInfo? activeAccountInfo; final UnlockedAccountInfo? unlockedAccountInfo;
} }

View file

@ -1,5 +1,5 @@
export 'account_info.dart'; export 'account_info.dart';
export 'active_account_info.dart'; export 'unlocked_account_info.dart';
export 'encryption_key_type.dart'; export 'encryption_key_type.dart';
export 'local_account/local_account.dart'; export 'local_account/local_account.dart';
export 'new_profile_spec.dart'; export 'new_profile_spec.dart';

View file

@ -7,8 +7,8 @@ import 'local_account/local_account.dart';
import 'user_login/user_login.dart'; import 'user_login/user_login.dart';
@immutable @immutable
class ActiveAccountInfo { class UnlockedAccountInfo {
const ActiveAccountInfo({ const UnlockedAccountInfo({
required this.localAccount, required this.localAccount,
required this.userLogin, required this.userLogin,
}); });

View file

@ -45,19 +45,6 @@ class AccountRepository {
valueToJson: (val) => val?.toJson(), valueToJson: (val) => val?.toJson(),
makeInitialValue: () => null); makeInitialValue: () => null);
//////////////////////////////////////////////////////////////
/// Fields
final TableDBValue<IList<LocalAccount>> _localAccounts;
final TableDBValue<IList<UserLogin>> _userLogins;
final TableDBValue<TypedKey?> _activeLocalAccount;
final StreamController<AccountRepositoryChange> _streamController;
//////////////////////////////////////////////////////////////
/// Singleton initialization
static AccountRepository instance = AccountRepository._();
Future<void> init() async { Future<void> init() async {
await _localAccounts.get(); await _localAccounts.get();
await _userLogins.get(); await _userLogins.get();
@ -71,12 +58,10 @@ class AccountRepository {
} }
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
/// Streams /// Public Interface
///
Stream<AccountRepositoryChange> get stream => _streamController.stream; Stream<AccountRepositoryChange> get stream => _streamController.stream;
//////////////////////////////////////////////////////////////
/// Selectors
IList<LocalAccount> getLocalAccounts() => _localAccounts.value; IList<LocalAccount> getLocalAccounts() => _localAccounts.value;
TypedKey? getActiveLocalAccount() => _activeLocalAccount.value; TypedKey? getActiveLocalAccount() => _activeLocalAccount.value;
IList<UserLogin> getUserLogins() => _userLogins.value; IList<UserLogin> getUserLogins() => _userLogins.value;
@ -116,7 +101,7 @@ class AccountRepository {
return const AccountInfo( return const AccountInfo(
status: AccountInfoStatus.noAccount, status: AccountInfoStatus.noAccount,
active: false, active: false,
activeAccountInfo: null); unlockedAccountInfo: null);
} }
superIdentityRecordKey = activeLocalAccount; superIdentityRecordKey = activeLocalAccount;
} }
@ -129,7 +114,7 @@ class AccountRepository {
return AccountInfo( return AccountInfo(
status: AccountInfoStatus.noAccount, status: AccountInfoStatus.noAccount,
active: active, active: active,
activeAccountInfo: null); unlockedAccountInfo: null);
} }
// See if we've logged into this account or if it is locked // See if we've logged into this account or if it is locked
@ -139,21 +124,18 @@ class AccountRepository {
return AccountInfo( return AccountInfo(
status: AccountInfoStatus.accountLocked, status: AccountInfoStatus.accountLocked,
active: active, active: active,
activeAccountInfo: null); unlockedAccountInfo: null);
} }
// Got account, decrypted and decoded // Got account, decrypted and decoded
return AccountInfo( return AccountInfo(
status: AccountInfoStatus.accountReady, status: AccountInfoStatus.accountReady,
active: active, active: active,
activeAccountInfo: unlockedAccountInfo:
ActiveAccountInfo(localAccount: localAccount, userLogin: userLogin), UnlockedAccountInfo(localAccount: localAccount, userLogin: userLogin),
); );
} }
//////////////////////////////////////////////////////////////
/// Mutators
/// Reorder accounts /// Reorder accounts
Future<void> reorderAccount(int oldIndex, int newIndex) async { Future<void> reorderAccount(int oldIndex, int newIndex) async {
final localAccounts = await _localAccounts.get(); final localAccounts = await _localAccounts.get();
@ -168,15 +150,14 @@ class AccountRepository {
/// Creates a new super identity, an identity instance, an account associated /// Creates a new super identity, an identity instance, an account associated
/// with the identity instance, stores the account in the identity key and /// with the identity instance, stores the account in the identity key and
/// then logs into that account with no password set at this time /// then logs into that account with no password set at this time
Future<SecretKey> createWithNewSuperIdentity( Future<SecretKey> createWithNewSuperIdentity(proto.Profile newProfile) async {
NewProfileSpec newProfileSpec) async {
log.debug('Creating super identity'); log.debug('Creating super identity');
final wsi = await WritableSuperIdentity.create(); final wsi = await WritableSuperIdentity.create();
try { try {
final localAccount = await _newLocalAccount( final localAccount = await _newLocalAccount(
superIdentity: wsi.superIdentity, superIdentity: wsi.superIdentity,
identitySecret: wsi.identitySecret, identitySecret: wsi.identitySecret,
newProfileSpec: newProfileSpec); newProfile: newProfile);
// Log in the new account by default with no pin // Log in the new account by default with no pin
final ok = await login( final ok = await login(
@ -190,85 +171,18 @@ class AccountRepository {
} }
} }
/// Creates a new Account associated with the current instance of the identity Future<void> editAccountProfile(
/// Adds a logged-out LocalAccount to track its existence on this device TypedKey superIdentityRecordKey, proto.Profile newProfile) async {
Future<LocalAccount> _newLocalAccount( log.debug('Editing profile for $superIdentityRecordKey');
{required SuperIdentity superIdentity,
required SecretKey identitySecret,
required NewProfileSpec newProfileSpec,
EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
String encryptionKey = ''}) async {
log.debug('Creating new local account');
final localAccounts = await _localAccounts.get(); final localAccounts = await _localAccounts.get();
// Add account with profile to DHT final newLocalAccounts = localAccounts.replaceFirstWhere(
await superIdentity.currentInstance.addAccount( (x) => x.superIdentity.recordKey == superIdentityRecordKey,
superRecordKey: superIdentity.recordKey, (localAccount) => localAccount!.copyWith(name: newProfile.name));
secretKey: identitySecret,
accountKey: veilidChatAccountKey,
createAccountCallback: (parent) async {
// Make empty contact list
log.debug('Creating contacts list');
final contactList = await (await DHTShortArray.create(
debugName: 'AccountRepository::_newLocalAccount::Contacts',
parent: parent))
.scope((r) async => r.recordPointer);
// Make empty contact invitation record list
log.debug('Creating contact invitation records list');
final contactInvitationRecords = await (await DHTShortArray.create(
debugName:
'AccountRepository::_newLocalAccount::ContactInvitations',
parent: parent))
.scope((r) async => r.recordPointer);
// Make empty chat record list
log.debug('Creating chat records list');
final chatRecords = await (await DHTShortArray.create(
debugName: 'AccountRepository::_newLocalAccount::Chats',
parent: parent))
.scope((r) async => r.recordPointer);
// Make account object
final account = proto.Account()
..profile = (proto.Profile()
..name = newProfileSpec.name
..pronouns = newProfileSpec.pronouns)
..contactList = contactList.toProto()
..contactInvitationRecords = contactInvitationRecords.toProto()
..chatList = chatRecords.toProto();
return account.writeToBuffer();
});
// Encrypt identitySecret with key
final identitySecretBytes = await encryptionKeyType.encryptSecretToBytes(
secret: identitySecret,
cryptoKind: superIdentity.currentInstance.recordKey.kind,
encryptionKey: encryptionKey,
);
// Create local account object
// Does not contain the account key or its secret
// as that is not to be persisted, and only pulled from the identity key
// and optionally decrypted with the unlock password
final localAccount = LocalAccount(
superIdentity: superIdentity,
identitySecretBytes: identitySecretBytes,
encryptionKeyType: encryptionKeyType,
biometricsEnabled: false,
hiddenAccount: false,
name: newProfileSpec.name,
);
// Add local account object to internal store
final newLocalAccounts = localAccounts.add(localAccount);
await _localAccounts.set(newLocalAccounts); await _localAccounts.set(newLocalAccounts);
_streamController.add(AccountRepositoryChange.localAccounts); _streamController.add(AccountRepositoryChange.localAccounts);
// Return local account object
return localAccount;
} }
/// Remove an account and wipe the messages for this account from this device /// Remove an account and wipe the messages for this account from this device
@ -310,6 +224,88 @@ class AccountRepository {
_streamController.add(AccountRepositoryChange.activeLocalAccount); _streamController.add(AccountRepositoryChange.activeLocalAccount);
} }
//////////////////////////////////////////////////////////////
/// Internal Implementation
/// Creates a new Account associated with the current instance of the identity
/// Adds a logged-out LocalAccount to track its existence on this device
Future<LocalAccount> _newLocalAccount(
{required SuperIdentity superIdentity,
required SecretKey identitySecret,
required proto.Profile newProfile,
EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
String encryptionKey = ''}) async {
log.debug('Creating new local account');
final localAccounts = await _localAccounts.get();
// Add account with profile to DHT
await superIdentity.currentInstance.addAccount(
superRecordKey: superIdentity.recordKey,
secretKey: identitySecret,
accountKey: veilidChatAccountKey,
createAccountCallback: (parent) async {
// Make empty contact list
log.debug('Creating contacts list');
final contactList = await (await DHTShortArray.create(
debugName: 'AccountRepository::_newLocalAccount::Contacts',
parent: parent))
.scope((r) async => r.recordPointer);
// Make empty contact invitation record list
log.debug('Creating contact invitation records list');
final contactInvitationRecords = await (await DHTShortArray.create(
debugName:
'AccountRepository::_newLocalAccount::ContactInvitations',
parent: parent))
.scope((r) async => r.recordPointer);
// Make empty chat record list
log.debug('Creating chat records list');
final chatRecords = await (await DHTShortArray.create(
debugName: 'AccountRepository::_newLocalAccount::Chats',
parent: parent))
.scope((r) async => r.recordPointer);
// Make account object
final account = proto.Account()
..profile = newProfile
..contactList = contactList.toProto()
..contactInvitationRecords = contactInvitationRecords.toProto()
..chatList = chatRecords.toProto();
return account.writeToBuffer();
});
// Encrypt identitySecret with key
final identitySecretBytes = await encryptionKeyType.encryptSecretToBytes(
secret: identitySecret,
cryptoKind: superIdentity.currentInstance.recordKey.kind,
encryptionKey: encryptionKey,
);
// Create local account object
// Does not contain the account key or its secret
// as that is not to be persisted, and only pulled from the identity key
// and optionally decrypted with the unlock password
final localAccount = LocalAccount(
superIdentity: superIdentity,
identitySecretBytes: identitySecretBytes,
encryptionKeyType: encryptionKeyType,
biometricsEnabled: false,
hiddenAccount: false,
name: newProfile.name,
);
// Add local account object to internal store
final newLocalAccounts = localAccounts.add(localAccount);
await _localAccounts.set(newLocalAccounts);
_streamController.add(AccountRepositoryChange.localAccounts);
// Return local account object
return localAccount;
}
Future<bool> _decryptedLogin( Future<bool> _decryptedLogin(
SuperIdentity superIdentity, SecretKey identitySecret) async { SuperIdentity superIdentity, SecretKey identitySecret) async {
// Verify identity secret works and return the valid cryptosystem // Verify identity secret works and return the valid cryptosystem
@ -402,16 +398,13 @@ class AccountRepository {
_streamController.add(AccountRepositoryChange.userLogins); _streamController.add(AccountRepositoryChange.userLogins);
} }
Future<DHTRecord> openAccountRecord(UserLogin userLogin) async { //////////////////////////////////////////////////////////////
final localAccount = fetchLocalAccount(userLogin.superIdentityRecordKey)!; /// Fields
// Record not yet open, do it static AccountRepository instance = AccountRepository._();
final pool = DHTRecordPool.instance;
final record = await pool.openRecordOwned(
userLogin.accountRecordInfo.accountRecord,
debugName: 'AccountRepository::openAccountRecord::AccountRecord',
parent: localAccount.superIdentity.currentInstance.recordKey);
return record; final TableDBValue<IList<LocalAccount>> _localAccounts;
} final TableDBValue<IList<UserLogin>> _userLogins;
final TableDBValue<TypedKey?> _activeLocalAccount;
final StreamController<AccountRepositoryChange> _streamController;
} }

View file

@ -0,0 +1,163 @@
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_form_builder/flutter_form_builder.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:go_router/go_router.dart';
import 'package:protobuf/protobuf.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../contacts/cubits/contact_list_cubit.dart';
import '../../conversation/conversation.dart';
import '../../layout/default_app_bar.dart';
import '../../proto/proto.dart' as proto;
import '../../theme/theme.dart';
import '../../tools/tools.dart';
import '../../veilid_processor/veilid_processor.dart';
import '../account_manager.dart';
import 'profile_edit_form.dart';
class EditAccountPage extends StatefulWidget {
const EditAccountPage(
{required this.superIdentityRecordKey,
required this.existingProfile,
super.key});
@override
State createState() => _EditAccountPageState();
final TypedKey superIdentityRecordKey;
final proto.Profile existingProfile;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<TypedKey>(
'superIdentityRecordKey', superIdentityRecordKey))
..add(DiagnosticsProperty<proto.Profile>(
'existingProfile', existingProfile));
}
}
class _EditAccountPageState extends State<EditAccountPage> {
final _formKey = GlobalKey<FormBuilderState>();
bool _isInAsyncCall = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await changeWindowSetup(
TitleBarStyle.normal, OrientationCapability.portraitOnly);
});
}
Widget _editAccountForm(BuildContext context,
{required Future<void> Function(GlobalKey<FormBuilderState>)
onSubmit}) =>
EditProfileForm(
header: translate('edit_account_page.header'),
instructions: translate('edit_account_page.instructions'),
submitText: translate('edit_account_page.update'),
submitDisabledText: translate('button.waiting_for_network'),
onSubmit: onSubmit);
@override
Widget build(BuildContext context) {
final displayModalHUD = _isInAsyncCall;
final accountRecordCubit = context.read<AccountRecordCubit>();
final activeConversationsBlocMapCubit =
context.read<ActiveConversationsBlocMapCubit>();
final contactListCubit = context.read<ContactListCubit>();
return Scaffold(
// resizeToAvoidBottomInset: false,
appBar: DefaultAppBar(
title: Text(translate('edit_account_page.titlebar')),
actions: [
const SignalStrengthMeterWidget(),
IconButton(
icon: const Icon(Icons.settings),
tooltip: translate('menu.settings_tooltip'),
onPressed: () async {
await GoRouterHelper(context).push('/settings');
})
]),
body: _editAccountForm(
context,
onSubmit: (formKey) async {
// dismiss the keyboard by unfocusing the textfield
FocusScope.of(context).unfocus();
try {
final name = _formKey.currentState!
.fields[EditProfileForm.formFieldName]!.value as String;
final pronouns = _formKey
.currentState!
.fields[EditProfileForm.formFieldPronouns]!
.value as String? ??
'';
final newProfile = widget.existingProfile.deepCopy()
..name = name
..pronouns = pronouns;
setState(() {
_isInAsyncCall = true;
});
try {
// Update account profile DHT record
final newValue = await accountRecordCubit.record
.tryWriteProtobuf(proto.Account.fromBuffer, newProfile);
if (newValue != null) {
if (context.mounted) {
await showErrorModal(
context,
translate('edit_account_page.error'),
'Failed to update profile online');
return;
}
}
// Update local account profile
await AccountRepository.instance.editAccountProfile(
widget.superIdentityRecordKey, newProfile);
// Update all conversations with new profile
final updates = <Future<void>>[];
for (final key in activeConversationsBlocMapCubit.state.keys) {
await activeConversationsBlocMapCubit.operateAsync(key,
closure: (cubit) async {
final newLocalConversation =
cubit.state.asData?.value.localConversation.deepCopy();
if (newLocalConversation != null) {
newLocalConversation.profile = newProfile;
updates.add(cubit.input.writeLocalConversation(
conversation: newLocalConversation));
}
});
}
// Wait for updates
await updates.wait;
// XXX: how to do this for non-chat contacts?
} finally {
if (mounted) {
setState(() {
_isInAsyncCall = false;
});
}
}
} on Exception catch (e) {
if (context.mounted) {
await showErrorModal(context,
translate('edit_account_page.error'), 'Exception: $e');
}
}
},
).paddingSymmetric(horizontal: 24, vertical: 8),
).withModalHUD(context, displayModalHUD);
}
}

View file

@ -1,30 +1,28 @@
import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_translate/flutter_translate.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../../layout/default_app_bar.dart'; import '../../layout/default_app_bar.dart';
import '../../proto/proto.dart' as proto;
import '../../theme/theme.dart'; import '../../theme/theme.dart';
import '../../tools/tools.dart'; import '../../tools/tools.dart';
import '../../veilid_processor/veilid_processor.dart'; import '../../veilid_processor/veilid_processor.dart';
import '../account_manager.dart'; import '../account_manager.dart';
import 'profile_edit_form.dart';
class NewAccountPage extends StatefulWidget { class NewAccountPage extends StatefulWidget {
const NewAccountPage({super.key}); const NewAccountPage({super.key});
@override @override
NewAccountPageState createState() => NewAccountPageState(); State createState() => _NewAccountPageState();
} }
class NewAccountPageState extends State<NewAccountPage> { class _NewAccountPageState extends State<NewAccountPage> {
final _formKey = GlobalKey<FormBuilderState>(); final _formKey = GlobalKey<FormBuilderState>();
late bool isInAsyncCall = false; bool _isInAsyncCall = false;
static const String formFieldName = 'name';
static const String formFieldPronouns = 'pronouns';
@override @override
void initState() { void initState() {
@ -47,70 +45,17 @@ class NewAccountPageState extends State<NewAccountPage> {
false; false;
final canSubmit = networkReady; final canSubmit = networkReady;
return FormBuilder( return EditProfileForm(
key: _formKey, header: translate('new_account_page.header'),
child: ListView( instructions: translate('new_account_page.instructions'),
children: [ submitText: translate('new_account_page.create'),
Text(translate('new_account_page.header')) submitDisabledText: translate('button.waiting_for_network'),
.textStyle(context.headlineSmall) onSubmit: !canSubmit ? null : onSubmit);
.paddingSymmetric(vertical: 16),
FormBuilderTextField(
autofocus: true,
name: formFieldName,
decoration:
InputDecoration(labelText: translate('account.form_name')),
maxLength: 64,
// The validator receives the text that the user has entered.
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(),
]),
textInputAction: TextInputAction.next,
),
FormBuilderTextField(
name: formFieldPronouns,
maxLength: 64,
decoration:
InputDecoration(labelText: translate('account.form_pronouns')),
textInputAction: TextInputAction.next,
),
Row(children: [
const Spacer(),
Text(translate('new_account_page.instructions'))
.toCenter()
.flexible(flex: 6),
const Spacer(),
]).paddingSymmetric(vertical: 4),
ElevatedButton(
onPressed: !canSubmit
? null
: () async {
if (_formKey.currentState?.saveAndValidate() ?? false) {
setState(() {
isInAsyncCall = true;
});
try {
await onSubmit(_formKey);
} finally {
if (mounted) {
setState(() {
isInAsyncCall = false;
});
}
}
}
},
child: Text(translate(!networkReady
? 'button.waiting_for_network'
: 'new_account_page.create')),
).paddingSymmetric(vertical: 4).alignAtCenterRight(),
],
),
);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final displayModalHUD = isInAsyncCall; final displayModalHUD = _isInAsyncCall;
return Scaffold( return Scaffold(
// resizeToAvoidBottomInset: false, // resizeToAvoidBottomInset: false,
@ -120,7 +65,7 @@ class NewAccountPageState extends State<NewAccountPage> {
const SignalStrengthMeterWidget(), const SignalStrengthMeterWidget(),
IconButton( IconButton(
icon: const Icon(Icons.settings), icon: const Icon(Icons.settings),
tooltip: translate('app_bar.settings_tooltip'), tooltip: translate('menu.settings_tooltip'),
onPressed: () async { onPressed: () async {
await GoRouterHelper(context).push('/settings'); await GoRouterHelper(context).push('/settings');
}) })
@ -132,19 +77,33 @@ class NewAccountPageState extends State<NewAccountPage> {
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
try { try {
final name = final name = _formKey.currentState!
_formKey.currentState!.fields[formFieldName]!.value as String; .fields[EditProfileForm.formFieldName]!.value as String;
final pronouns = _formKey.currentState!.fields[formFieldPronouns]! final pronouns = _formKey
.currentState!
.fields[EditProfileForm.formFieldPronouns]!
.value as String? ?? .value as String? ??
''; '';
final newProfileSpec = final newProfile = proto.Profile()
NewProfileSpec(name: name, pronouns: pronouns); ..name = name
..pronouns = pronouns;
setState(() {
_isInAsyncCall = true;
});
try {
final superSecret = await AccountRepository.instance final superSecret = await AccountRepository.instance
.createWithNewSuperIdentity(newProfileSpec); .createWithNewSuperIdentity(newProfile);
GoRouterHelper(context).pushReplacement(
GoRouterHelper(context).pushReplacement('/new_account/recovery_key', '/new_account/recovery_key',
extra: superSecret); extra: superSecret);
} finally {
if (mounted) {
setState(() {
_isInAsyncCall = false;
});
}
}
} on Exception catch (e) { } on Exception catch (e) {
if (context.mounted) { if (context.mounted) {
await showErrorModal(context, translate('new_account_page.error'), await showErrorModal(context, translate('new_account_page.error'),
@ -155,10 +114,4 @@ class NewAccountPageState extends State<NewAccountPage> {
).paddingSymmetric(horizontal: 24, vertical: 8), ).paddingSymmetric(horizontal: 24, vertical: 8),
).withModalHUD(context, displayModalHUD); ).withModalHUD(context, displayModalHUD);
} }
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<bool>('isInAsyncCall', isInAsyncCall));
}
} }

View file

@ -0,0 +1,106 @@
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
class EditProfileForm extends StatefulWidget {
const EditProfileForm({
required this.header,
required this.instructions,
required this.submitText,
required this.submitDisabledText,
super.key,
this.onSubmit,
});
@override
State createState() => _EditProfileFormState();
final String header;
final String instructions;
final Future<void> Function(GlobalKey<FormBuilderState>)? onSubmit;
final String submitText;
final String submitDisabledText;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(StringProperty('header', header))
..add(StringProperty('instructions', instructions))
..add(ObjectFlagProperty<
Future<void> Function(
GlobalKey<FormBuilderState> p1)?>.has('onSubmit', onSubmit))
..add(StringProperty('submitText', submitText))
..add(StringProperty('submitDisabledText', submitDisabledText));
}
static const String formFieldName = 'name';
static const String formFieldPronouns = 'pronouns';
}
class _EditProfileFormState extends State<EditProfileForm> {
final _formKey = GlobalKey<FormBuilderState>();
@override
void initState() {
super.initState();
}
Widget _editProfileForm(
BuildContext context,
) =>
FormBuilder(
key: _formKey,
child: ListView(
children: [
Text(widget.header)
.textStyle(context.headlineSmall)
.paddingSymmetric(vertical: 16),
FormBuilderTextField(
autofocus: true,
name: EditProfileForm.formFieldName,
decoration:
InputDecoration(labelText: translate('account.form_name')),
maxLength: 64,
// The validator receives the text that the user has entered.
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(),
]),
textInputAction: TextInputAction.next,
),
FormBuilderTextField(
name: EditProfileForm.formFieldPronouns,
maxLength: 64,
decoration: InputDecoration(
labelText: translate('account.form_pronouns')),
textInputAction: TextInputAction.next,
),
Row(children: [
const Spacer(),
Text(widget.instructions).toCenter().flexible(flex: 6),
const Spacer(),
]).paddingSymmetric(vertical: 4),
ElevatedButton(
onPressed: widget.onSubmit == null
? null
: () async {
if (_formKey.currentState?.saveAndValidate() ?? false) {
await widget.onSubmit!(_formKey);
}
},
child: Text((widget.onSubmit == null)
? widget.submitDisabledText
: widget.submitText),
).paddingSymmetric(vertical: 4).alignAtCenterRight(),
],
),
);
@override
Widget build(BuildContext context) => _editProfileForm(
context,
);
}

View file

@ -48,7 +48,7 @@ class ShowRecoveryKeyPageState extends State<ShowRecoveryKeyPage> {
const SignalStrengthMeterWidget(), const SignalStrengthMeterWidget(),
IconButton( IconButton(
icon: const Icon(Icons.settings), icon: const Icon(Icons.settings),
tooltip: translate('app_bar.settings_tooltip'), tooltip: translate('menu.settings_tooltip'),
onPressed: () async { onPressed: () async {
await GoRouterHelper(context).push('/settings'); await GoRouterHelper(context).push('/settings');
}) })

View file

@ -122,9 +122,9 @@ class VeilidChatApp extends StatelessWidget {
create: (context) => create: (context) =>
UserLoginsCubit(AccountRepository.instance), UserLoginsCubit(AccountRepository.instance),
), ),
BlocProvider<ActiveLocalAccountCubit>( BlocProvider<ActiveAccountInfoCubit>(
create: (context) => create: (context) =>
ActiveLocalAccountCubit(AccountRepository.instance), ActiveAccountInfoCubit(AccountRepository.instance),
), ),
BlocProvider<PreferencesCubit>( BlocProvider<PreferencesCubit>(
create: (context) => create: (context) =>

View file

@ -1,13 +1,19 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../router/router.dart';
// XXX: if we ever want to have more than one chat 'open', we should put the // XXX: if we ever want to have more than one chat 'open', we should put the
// operations and state for that here. // operations and state for that here.
class ActiveChatCubit extends Cubit<TypedKey?> { class ActiveChatCubit extends Cubit<TypedKey?> {
ActiveChatCubit(super.initialState); ActiveChatCubit(super.initialState, {required RouterCubit routerCubit})
: _routerCubit = routerCubit;
void setActiveChat(TypedKey? activeChatLocalConversationRecordKey) { void setActiveChat(TypedKey? activeChatLocalConversationRecordKey) {
emit(activeChatLocalConversationRecordKey); emit(activeChatLocalConversationRecordKey);
_routerCubit.setHasActiveChat(activeChatLocalConversationRecordKey != null);
} }
final RouterCubit _routerCubit;
} }

View file

@ -12,7 +12,7 @@ import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart'; import '../../account_manager/account_manager.dart';
import '../../chat_list/chat_list.dart'; import '../../conversation/conversation.dart';
import '../../proto/proto.dart' as proto; import '../../proto/proto.dart' as proto;
import '../models/chat_component_state.dart'; import '../models/chat_component_state.dart';
import '../models/message_state.dart'; import '../models/message_state.dart';
@ -44,7 +44,7 @@ class ChatComponentCubit extends Cubit<ChatComponentState> {
// ignore: prefer_constructors_over_static_methods // ignore: prefer_constructors_over_static_methods
static ChatComponentCubit singleContact( static ChatComponentCubit singleContact(
{required ActiveAccountInfo activeAccountInfo, {required UnlockedAccountInfo activeAccountInfo,
required proto.Account accountRecordInfo, required proto.Account accountRecordInfo,
required ActiveConversationState activeConversationState, required ActiveConversationState activeConversationState,
required SingleContactMessagesCubit messagesCubit}) { required SingleContactMessagesCubit messagesCubit}) {

View file

@ -50,7 +50,7 @@ typedef SingleContactMessagesState = AsyncValue<WindowState<MessageState>>;
// Builds the reconciled chat record from the local and remote conversation keys // Builds the reconciled chat record from the local and remote conversation keys
class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> { class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
SingleContactMessagesCubit({ SingleContactMessagesCubit({
required ActiveAccountInfo activeAccountInfo, required UnlockedAccountInfo activeAccountInfo,
required TypedKey remoteIdentityPublicKey, required TypedKey remoteIdentityPublicKey,
required TypedKey localConversationRecordKey, required TypedKey localConversationRecordKey,
required TypedKey localMessagesRecordKey, required TypedKey localMessagesRecordKey,
@ -402,7 +402,7 @@ class SingleContactMessagesCubit extends Cubit<SingleContactMessagesState> {
///////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////
final WaitSet<void> _initWait = WaitSet(); final WaitSet<void> _initWait = WaitSet();
final ActiveAccountInfo _activeAccountInfo; final UnlockedAccountInfo _activeAccountInfo;
final TypedKey _remoteIdentityPublicKey; final TypedKey _remoteIdentityPublicKey;
final TypedKey _localConversationRecordKey; final TypedKey _localConversationRecordKey;
final TypedKey _localMessagesRecordKey; final TypedKey _localMessagesRecordKey;

View file

@ -9,7 +9,7 @@ import 'package:flutter_chat_ui/flutter_chat_ui.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart'; import '../../account_manager/account_manager.dart';
import '../../chat_list/chat_list.dart'; import '../../conversation/conversation.dart';
import '../../theme/theme.dart'; import '../../theme/theme.dart';
import '../chat.dart'; import '../chat.dart';
@ -23,7 +23,7 @@ class ChatComponentWidget extends StatelessWidget {
{required TypedKey localConversationRecordKey, Key? key}) => {required TypedKey localConversationRecordKey, Key? key}) =>
Builder(builder: (context) { Builder(builder: (context) {
// Get all watched dependendies // Get all watched dependendies
final activeAccountInfo = context.watch<ActiveAccountInfo>(); final activeAccountInfo = context.watch<UnlockedAccountInfo>();
final accountRecordInfo = final accountRecordInfo =
context.watch<AccountRecordCubit>().state.asData?.value; context.watch<AccountRecordCubit>().state.asData?.value;
if (accountRecordInfo == null) { if (accountRecordInfo == null) {

View file

@ -19,15 +19,15 @@ typedef ChatListCubitState = DHTShortArrayBusyState<proto.Chat>;
class ChatListCubit extends DHTShortArrayCubit<proto.Chat> class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
with StateMapFollowable<ChatListCubitState, TypedKey, proto.Chat> { with StateMapFollowable<ChatListCubitState, TypedKey, proto.Chat> {
ChatListCubit({ ChatListCubit({
required ActiveAccountInfo activeAccountInfo, required UnlockedAccountInfo unlockedAccountInfo,
required proto.Account account, required proto.Account account,
required this.activeChatCubit, required this.activeChatCubit,
}) : super( }) : super(
open: () => _open(activeAccountInfo, account), open: () => _open(unlockedAccountInfo, account),
decodeElement: proto.Chat.fromBuffer); decodeElement: proto.Chat.fromBuffer);
static Future<DHTShortArray> _open( static Future<DHTShortArray> _open(
ActiveAccountInfo activeAccountInfo, proto.Account account) async { UnlockedAccountInfo activeAccountInfo, proto.Account account) async {
final accountRecordKey = final accountRecordKey =
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;

View file

@ -1,3 +1 @@
export 'active_single_contact_chat_bloc_map_cubit.dart';
export 'active_conversations_bloc_map_cubit.dart';
export 'chat_list_cubit.dart'; export 'chat_list_cubit.dart';

View file

@ -36,16 +36,16 @@ class ContactInvitationListCubit
StateMapFollowable<ContactInvitiationListState, TypedKey, StateMapFollowable<ContactInvitiationListState, TypedKey,
proto.ContactInvitationRecord> { proto.ContactInvitationRecord> {
ContactInvitationListCubit({ ContactInvitationListCubit({
required ActiveAccountInfo activeAccountInfo, required UnlockedAccountInfo unlockedAccountInfo,
required proto.Account account, required proto.Account account,
}) : _activeAccountInfo = activeAccountInfo, }) : _activeAccountInfo = unlockedAccountInfo,
_account = account, _account = account,
super( super(
open: () => _open(activeAccountInfo, account), open: () => _open(unlockedAccountInfo, account),
decodeElement: proto.ContactInvitationRecord.fromBuffer); decodeElement: proto.ContactInvitationRecord.fromBuffer);
static Future<DHTShortArray> _open( static Future<DHTShortArray> _open(
ActiveAccountInfo activeAccountInfo, proto.Account account) async { UnlockedAccountInfo activeAccountInfo, proto.Account account) async {
final accountRecordKey = final accountRecordKey =
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
@ -318,6 +318,6 @@ class ContactInvitationListCubit
} }
// //
final ActiveAccountInfo _activeAccountInfo; final UnlockedAccountInfo _activeAccountInfo;
final proto.Account _account; final proto.Account _account;
} }

View file

@ -23,7 +23,7 @@ class ContactRequestInboxCubit
// : super.value(decodeState: proto.SignedContactResponse.fromBuffer); // : super.value(decodeState: proto.SignedContactResponse.fromBuffer);
static Future<DHTRecord> _open( static Future<DHTRecord> _open(
{required ActiveAccountInfo activeAccountInfo, {required UnlockedAccountInfo activeAccountInfo,
required proto.ContactInvitationRecord contactInvitationRecord}) async { required proto.ContactInvitationRecord contactInvitationRecord}) async {
final pool = DHTRecordPool.instance; final pool = DHTRecordPool.instance;
final accountRecordKey = final accountRecordKey =
@ -42,6 +42,6 @@ class ContactRequestInboxCubit
defaultSubkey: 1); defaultSubkey: 1);
} }
final ActiveAccountInfo activeAccountInfo; final UnlockedAccountInfo activeAccountInfo;
final proto.ContactInvitationRecord contactInvitationRecord; final proto.ContactInvitationRecord contactInvitationRecord;
} }

View file

@ -7,7 +7,7 @@ import 'package:meta/meta.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart'; import '../../account_manager/account_manager.dart';
import '../../contacts/contacts.dart'; import '../../conversation/conversation.dart';
import '../../proto/proto.dart' as proto; import '../../proto/proto.dart' as proto;
import '../../tools/tools.dart'; import '../../tools/tools.dart';
import '../models/accepted_contact.dart'; import '../models/accepted_contact.dart';
@ -25,7 +25,7 @@ class InvitationStatus extends Equatable {
class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus, class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
proto.SignedContactResponse?> { proto.SignedContactResponse?> {
WaitingInvitationCubit(ContactRequestInboxCubit super.input, WaitingInvitationCubit(ContactRequestInboxCubit super.input,
{required ActiveAccountInfo activeAccountInfo, {required UnlockedAccountInfo activeAccountInfo,
required proto.Account account, required proto.Account account,
required proto.ContactInvitationRecord contactInvitationRecord}) required proto.ContactInvitationRecord contactInvitationRecord})
: super( : super(
@ -37,7 +37,7 @@ class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
static Future<AsyncValue<InvitationStatus>> _transform( static Future<AsyncValue<InvitationStatus>> _transform(
proto.SignedContactResponse? signedContactResponse, proto.SignedContactResponse? signedContactResponse,
{required ActiveAccountInfo activeAccountInfo, {required UnlockedAccountInfo activeAccountInfo,
required proto.Account account, required proto.Account account,
required proto.ContactInvitationRecord contactInvitationRecord}) async { required proto.ContactInvitationRecord contactInvitationRecord}) async {
if (signedContactResponse == null) { if (signedContactResponse == null) {

View file

@ -18,7 +18,7 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
StateMapFollower<DHTShortArrayBusyState<proto.ContactInvitationRecord>, StateMapFollower<DHTShortArrayBusyState<proto.ContactInvitationRecord>,
TypedKey, proto.ContactInvitationRecord> { TypedKey, proto.ContactInvitationRecord> {
WaitingInvitationsBlocMapCubit( WaitingInvitationsBlocMapCubit(
{required this.activeAccountInfo, required this.account}); {required this.unlockedAccountInfo, required this.account});
Future<void> _addWaitingInvitation( Future<void> _addWaitingInvitation(
{required proto.ContactInvitationRecord {required proto.ContactInvitationRecord
@ -27,9 +27,9 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
contactInvitationRecord.contactRequestInbox.recordKey.toVeilid(), contactInvitationRecord.contactRequestInbox.recordKey.toVeilid(),
WaitingInvitationCubit( WaitingInvitationCubit(
ContactRequestInboxCubit( ContactRequestInboxCubit(
activeAccountInfo: activeAccountInfo, activeAccountInfo: unlockedAccountInfo,
contactInvitationRecord: contactInvitationRecord), contactInvitationRecord: contactInvitationRecord),
activeAccountInfo: activeAccountInfo, activeAccountInfo: unlockedAccountInfo,
account: account, account: account,
contactInvitationRecord: contactInvitationRecord))); contactInvitationRecord: contactInvitationRecord)));
@ -43,6 +43,6 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
_addWaitingInvitation(contactInvitationRecord: value); _addWaitingInvitation(contactInvitationRecord: value);
//// ////
final ActiveAccountInfo activeAccountInfo; final UnlockedAccountInfo unlockedAccountInfo;
final proto.Account account; final proto.Account account;
} }

View file

@ -2,7 +2,7 @@ import 'package:meta/meta.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart'; import '../../account_manager/account_manager.dart';
import '../../contacts/contacts.dart'; import '../../conversation/conversation.dart';
import '../../proto/proto.dart' as proto; import '../../proto/proto.dart' as proto;
import '../../tools/tools.dart'; import '../../tools/tools.dart';
import 'models.dart'; import 'models.dart';
@ -13,7 +13,7 @@ import 'models.dart';
class ValidContactInvitation { class ValidContactInvitation {
@internal @internal
ValidContactInvitation( ValidContactInvitation(
{required ActiveAccountInfo activeAccountInfo, {required UnlockedAccountInfo activeAccountInfo,
required proto.Account account, required proto.Account account,
required TypedKey contactRequestInboxKey, required TypedKey contactRequestInboxKey,
required proto.ContactRequestPrivate contactRequestPrivate, required proto.ContactRequestPrivate contactRequestPrivate,
@ -129,7 +129,7 @@ class ValidContactInvitation {
} }
// //
final ActiveAccountInfo _activeAccountInfo; final UnlockedAccountInfo _activeAccountInfo;
final proto.Account _account; final proto.Account _account;
final TypedKey _contactRequestInboxKey; final TypedKey _contactRequestInboxKey;
final SuperIdentity _contactSuperIdentity; final SuperIdentity _contactSuperIdentity;

View file

@ -74,7 +74,7 @@ class InvitationDialogState extends State<InvitationDialog> {
Future<void> _onAccept() async { Future<void> _onAccept() async {
final navigator = Navigator.of(context); final navigator = Navigator.of(context);
final activeAccountInfo = widget.modalContext.read<ActiveAccountInfo>(); final activeAccountInfo = widget.modalContext.read<UnlockedAccountInfo>();
final contactList = widget.modalContext.read<ContactListCubit>(); final contactList = widget.modalContext.read<ContactListCubit>();
setState(() { setState(() {

View file

@ -13,15 +13,15 @@ import 'conversation_cubit.dart';
class ContactListCubit extends DHTShortArrayCubit<proto.Contact> { class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
ContactListCubit({ ContactListCubit({
required ActiveAccountInfo activeAccountInfo, required UnlockedAccountInfo unlockedAccountInfo,
required proto.Account account, required proto.Account account,
}) : _activeAccountInfo = activeAccountInfo, }) : _activeAccountInfo = unlockedAccountInfo,
super( super(
open: () => _open(activeAccountInfo, account), open: () => _open(unlockedAccountInfo, account),
decodeElement: proto.Contact.fromBuffer); decodeElement: proto.Contact.fromBuffer);
static Future<DHTShortArray> _open( static Future<DHTShortArray> _open(
ActiveAccountInfo activeAccountInfo, proto.Account account) async { UnlockedAccountInfo activeAccountInfo, proto.Account account) async {
final accountRecordKey = final accountRecordKey =
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
@ -99,5 +99,5 @@ class ContactListCubit extends DHTShortArrayCubit<proto.Contact> {
} }
} }
final ActiveAccountInfo _activeAccountInfo; final UnlockedAccountInfo _activeAccountInfo;
} }

View file

@ -1,2 +1 @@
export 'contact_list_cubit.dart'; export 'contact_list_cubit.dart';
export 'conversation_cubit.dart';

View file

@ -0,0 +1 @@
export 'cubits/cubits.dart';

View file

@ -6,6 +6,7 @@ import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart'; import '../../account_manager/account_manager.dart';
import '../../contacts/contacts.dart'; import '../../contacts/contacts.dart';
import '../../conversation/conversation.dart';
import '../../proto/proto.dart' as proto; import '../../proto/proto.dart' as proto;
import 'cubits.dart'; import 'cubits.dart';
@ -26,7 +27,9 @@ class ActiveConversationState extends Equatable {
} }
typedef ActiveConversationCubit = TransformerCubit< typedef ActiveConversationCubit = TransformerCubit<
AsyncValue<ActiveConversationState>, AsyncValue<ConversationState>>; AsyncValue<ActiveConversationState>,
AsyncValue<ConversationState>,
ConversationCubit>;
typedef ActiveConversationsBlocMapState typedef ActiveConversationsBlocMapState
= BlocMapState<TypedKey, AsyncValue<ActiveConversationState>>; = BlocMapState<TypedKey, AsyncValue<ActiveConversationState>>;
@ -41,11 +44,17 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
AsyncValue<ActiveConversationState>, ActiveConversationCubit> AsyncValue<ActiveConversationState>, ActiveConversationCubit>
with StateMapFollower<ChatListCubitState, TypedKey, proto.Chat> { with StateMapFollower<ChatListCubitState, TypedKey, proto.Chat> {
ActiveConversationsBlocMapCubit( ActiveConversationsBlocMapCubit(
{required ActiveAccountInfo activeAccountInfo, {required UnlockedAccountInfo unlockedAccountInfo,
required ContactListCubit contactListCubit}) required ContactListCubit contactListCubit})
: _activeAccountInfo = activeAccountInfo, : _activeAccountInfo = unlockedAccountInfo,
_contactListCubit = contactListCubit; _contactListCubit = contactListCubit;
////////////////////////////////////////////////////////////////////////////
// Public Interface
////////////////////////////////////////////////////////////////////////////
// Private Implementation
// Add an active conversation to be tracked for changes // Add an active conversation to be tracked for changes
Future<void> _addConversation({required proto.Contact contact}) async => Future<void> _addConversation({required proto.Contact contact}) async =>
add(() => MapEntry( add(() => MapEntry(
@ -97,6 +106,6 @@ class ActiveConversationsBlocMapCubit extends BlocMapCubit<TypedKey,
//// ////
final ActiveAccountInfo _activeAccountInfo; final UnlockedAccountInfo _activeAccountInfo;
final ContactListCubit _contactListCubit; final ContactListCubit _contactListCubit;
} }

View file

@ -20,10 +20,10 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
StateMapFollower<ActiveConversationsBlocMapState, TypedKey, StateMapFollower<ActiveConversationsBlocMapState, TypedKey,
AsyncValue<ActiveConversationState>> { AsyncValue<ActiveConversationState>> {
ActiveSingleContactChatBlocMapCubit( ActiveSingleContactChatBlocMapCubit(
{required ActiveAccountInfo activeAccountInfo, {required UnlockedAccountInfo unlockedAccountInfo,
required ContactListCubit contactListCubit, required ContactListCubit contactListCubit,
required ChatListCubit chatListCubit}) required ChatListCubit chatListCubit})
: _activeAccountInfo = activeAccountInfo, : _activeAccountInfo = unlockedAccountInfo,
_contactListCubit = contactListCubit, _contactListCubit = contactListCubit,
_chatListCubit = chatListCubit; _chatListCubit = chatListCubit;
@ -95,7 +95,7 @@ class ActiveSingleContactChatBlocMapCubit extends BlocMapCubit<TypedKey,
//// ////
final ActiveAccountInfo _activeAccountInfo; final UnlockedAccountInfo _activeAccountInfo;
final ContactListCubit _contactListCubit; final ContactListCubit _contactListCubit;
final ChatListCubit _chatListCubit; final ChatListCubit _chatListCubit;
} }

View file

@ -29,11 +29,11 @@ class ConversationState extends Equatable {
class ConversationCubit extends Cubit<AsyncValue<ConversationState>> { class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
ConversationCubit( ConversationCubit(
{required ActiveAccountInfo activeAccountInfo, {required UnlockedAccountInfo activeAccountInfo,
required TypedKey remoteIdentityPublicKey, required TypedKey remoteIdentityPublicKey,
TypedKey? localConversationRecordKey, TypedKey? localConversationRecordKey,
TypedKey? remoteConversationRecordKey}) TypedKey? remoteConversationRecordKey})
: _activeAccountInfo = activeAccountInfo, : _unlockedAccountInfo = activeAccountInfo,
_localConversationRecordKey = localConversationRecordKey, _localConversationRecordKey = localConversationRecordKey,
_remoteIdentityPublicKey = remoteIdentityPublicKey, _remoteIdentityPublicKey = remoteIdentityPublicKey,
_remoteConversationRecordKey = remoteConversationRecordKey, _remoteConversationRecordKey = remoteConversationRecordKey,
@ -41,13 +41,13 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
if (_localConversationRecordKey != null) { if (_localConversationRecordKey != null) {
_initWait.add(() async { _initWait.add(() async {
await _setLocalConversation(() async { await _setLocalConversation(() async {
final accountRecordKey = _activeAccountInfo final accountRecordKey = _unlockedAccountInfo
.userLogin.accountRecordInfo.accountRecord.recordKey; .userLogin.accountRecordInfo.accountRecord.recordKey;
// Open local record key if it is specified // Open local record key if it is specified
final pool = DHTRecordPool.instance; final pool = DHTRecordPool.instance;
final crypto = await _cachedConversationCrypto(); final crypto = await _cachedConversationCrypto();
final writer = _activeAccountInfo.identityWriter; final writer = _unlockedAccountInfo.identityWriter;
final record = await pool.openRecordWrite( final record = await pool.openRecordWrite(
_localConversationRecordKey!, writer, _localConversationRecordKey!, writer,
debugName: 'ConversationCubit::LocalConversation', debugName: 'ConversationCubit::LocalConversation',
@ -61,7 +61,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
if (_remoteConversationRecordKey != null) { if (_remoteConversationRecordKey != null) {
_initWait.add(() async { _initWait.add(() async {
await _setRemoteConversation(() async { await _setRemoteConversation(() async {
final accountRecordKey = _activeAccountInfo final accountRecordKey = _unlockedAccountInfo
.userLogin.accountRecordInfo.accountRecord.recordKey; .userLogin.accountRecordInfo.accountRecord.recordKey;
// Open remote record key if it is specified // Open remote record key if it is specified
@ -217,11 +217,11 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
'must not have a local conversation yet'); 'must not have a local conversation yet');
final pool = DHTRecordPool.instance; final pool = DHTRecordPool.instance;
final accountRecordKey = final accountRecordKey = _unlockedAccountInfo
_activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; .userLogin.accountRecordInfo.accountRecord.recordKey;
final crypto = await _cachedConversationCrypto(); final crypto = await _cachedConversationCrypto();
final writer = _activeAccountInfo.identityWriter; final writer = _unlockedAccountInfo.identityWriter;
// Open with SMPL scheme for identity writer // Open with SMPL scheme for identity writer
late final DHTRecord localConversationRecord; late final DHTRecord localConversationRecord;
@ -247,7 +247,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
.deleteScope((localConversation) async { .deleteScope((localConversation) async {
// Make messages log // Make messages log
return _initLocalMessages( return _initLocalMessages(
activeAccountInfo: _activeAccountInfo, activeAccountInfo: _unlockedAccountInfo,
remoteIdentityPublicKey: _remoteIdentityPublicKey, remoteIdentityPublicKey: _remoteIdentityPublicKey,
localConversationKey: localConversation.key, localConversationKey: localConversation.key,
callback: (messages) async { callback: (messages) async {
@ -255,7 +255,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
final conversation = proto.Conversation() final conversation = proto.Conversation()
..profile = profile ..profile = profile
..superIdentityJson = jsonEncode( ..superIdentityJson = jsonEncode(
_activeAccountInfo.localAccount.superIdentity.toJson()) _unlockedAccountInfo.localAccount.superIdentity.toJson())
..messages = messages.recordKey.toProto(); ..messages = messages.recordKey.toProto();
// Write initial conversation to record // Write initial conversation to record
@ -282,7 +282,7 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
// Initialize local messages // Initialize local messages
Future<T> _initLocalMessages<T>({ Future<T> _initLocalMessages<T>({
required ActiveAccountInfo activeAccountInfo, required UnlockedAccountInfo activeAccountInfo,
required TypedKey remoteIdentityPublicKey, required TypedKey remoteIdentityPublicKey,
required TypedKey localConversationKey, required TypedKey localConversationKey,
required FutureOr<T> Function(DHTLog) callback, required FutureOr<T> Function(DHTLog) callback,
@ -332,14 +332,14 @@ class ConversationCubit extends Cubit<AsyncValue<ConversationState>> {
if (conversationCrypto != null) { if (conversationCrypto != null) {
return conversationCrypto; return conversationCrypto;
} }
conversationCrypto = await _activeAccountInfo conversationCrypto = await _unlockedAccountInfo
.makeConversationCrypto(_remoteIdentityPublicKey); .makeConversationCrypto(_remoteIdentityPublicKey);
_conversationCrypto = conversationCrypto; _conversationCrypto = conversationCrypto;
return conversationCrypto; return conversationCrypto;
} }
final ActiveAccountInfo _activeAccountInfo; final UnlockedAccountInfo _unlockedAccountInfo;
final TypedKey _remoteIdentityPublicKey; final TypedKey _remoteIdentityPublicKey;
TypedKey? _localConversationRecordKey; TypedKey? _localConversationRecordKey;
final TypedKey? _remoteConversationRecordKey; final TypedKey? _remoteConversationRecordKey;

View file

@ -0,0 +1,3 @@
export 'active_conversations_bloc_map_cubit.dart';
export 'active_single_contact_chat_bloc_map_cubit.dart';
export 'conversation_cubit.dart';

View file

@ -1,3 +1,4 @@
import 'package:async_tools/async_tools.dart';
import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -9,6 +10,7 @@ import 'package:veilid_support/veilid_support.dart';
import '../../../account_manager/account_manager.dart'; import '../../../account_manager/account_manager.dart';
import '../../../theme/theme.dart'; import '../../../theme/theme.dart';
import '../../../tools/tools.dart'; import '../../../tools/tools.dart';
import '../../../veilid_processor/veilid_processor.dart';
import 'menu_item_widget.dart'; import 'menu_item_widget.dart';
class DrawerMenu extends StatefulWidget { class DrawerMenu extends StatefulWidget {
@ -29,8 +31,10 @@ class _DrawerMenuState extends State<DrawerMenu> {
super.dispose(); super.dispose();
} }
void _doLoginClick(TypedKey superIdentityRecordKey) { void _doSwitchClick(TypedKey superIdentityRecordKey) {
// singleFuture(this, () async {
await AccountRepository.instance.switchToAccount(superIdentityRecordKey);
});
} }
void _doEditClick(TypedKey superIdentityRecordKey) { void _doEditClick(TypedKey superIdentityRecordKey) {
@ -47,10 +51,12 @@ class _DrawerMenuState extends State<DrawerMenu> {
Widget _makeAccountWidget( Widget _makeAccountWidget(
{required String name, {required String name,
required bool selected,
required ScaleColor scale,
required bool loggedIn, required bool loggedIn,
required void Function() clickHandler}) { required void Function()? callback,
required void Function()? footerCallback}) {
final theme = Theme.of(context); final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!.tertiaryScale;
final abbrev = name.split(' ').map((s) => s.isEmpty ? '' : s[0]).join(); final abbrev = name.split(' ').map((s) => s.isEmpty ? '' : s[0]).join();
late final String shortname; late final String shortname;
if (abbrev.length >= 3) { if (abbrev.length >= 3) {
@ -65,30 +71,36 @@ class _DrawerMenuState extends State<DrawerMenu> {
foregroundColor: loggedIn ? scale.primaryText : scale.subtleText, foregroundColor: loggedIn ? scale.primaryText : scale.subtleText,
child: Text(shortname, style: theme.textTheme.titleLarge)); child: Text(shortname, style: theme.textTheme.titleLarge));
return MenuItemWidget( return AnimatedPadding(
padding: EdgeInsets.fromLTRB(selected ? 0 : 0, 0, selected ? 0 : 8, 0),
duration: const Duration(milliseconds: 50),
child: MenuItemWidget(
title: name, title: name,
headerWidget: avatar, headerWidget: avatar,
titleStyle: theme.textTheme.titleLarge!, titleStyle: theme.textTheme.titleLarge!,
foregroundColor: scale.primary, foregroundColor: scale.primary,
backgroundColor: scale.elementBackground, backgroundColor: selected
? scale.activeElementBackground
: scale.elementBackground,
backgroundHoverColor: scale.hoverElementBackground, backgroundHoverColor: scale.hoverElementBackground,
backgroundFocusColor: scale.activeElementBackground, backgroundFocusColor: scale.activeElementBackground,
borderColor: scale.border, borderColor: scale.border,
borderHoverColor: scale.hoverBorder, borderHoverColor: scale.hoverBorder,
borderFocusColor: scale.primary, borderFocusColor: scale.primary,
footerButtonIcon: loggedIn ? Icons.edit_outlined : Icons.login_outlined, callback: callback,
footerCallback: clickHandler, footerButtonIcon: loggedIn ? Icons.edit_outlined : null,
footerCallback: footerCallback,
footerButtonIconColor: scale.border, footerButtonIconColor: scale.border,
footerButtonIconHoverColor: scale.hoverElementBackground, footerButtonIconHoverColor: scale.hoverElementBackground,
footerButtonIconFocusColor: scale.activeElementBackground, footerButtonIconFocusColor: scale.activeElementBackground,
); ));
} }
Widget _getAccountList( Widget _getAccountList(
{required TypedKey? activeLocalAccount, {required TypedKey? activeLocalAccount,
required AccountRecordsBlocMapState accountRecords}) { required AccountRecordsBlocMapState accountRecords}) {
final theme = Theme.of(context); final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!; final scaleScheme = theme.extension<ScaleScheme>()!;
final accountRepo = AccountRepository.instance; final accountRepo = AccountRepository.instance;
final localAccounts = accountRepo.getLocalAccounts(); final localAccounts = accountRepo.getLocalAccounts();
@ -104,28 +116,38 @@ class _DrawerMenuState extends State<DrawerMenu> {
final acctRecord = accountRecords.get(superIdentityRecordKey); final acctRecord = accountRecords.get(superIdentityRecordKey);
if (acctRecord != null) { if (acctRecord != null) {
// Account is logged in // Account is logged in
final scale = theme.extension<ScaleScheme>()!.tertiaryScale;
final loggedInAccount = acctRecord.when( final loggedInAccount = acctRecord.when(
data: (value) => _makeAccountWidget( data: (value) => _makeAccountWidget(
name: value.profile.name, name: value.profile.name,
scale: scale,
selected: superIdentityRecordKey == activeLocalAccount,
loggedIn: true, loggedIn: true,
clickHandler: () { callback: () {
_doSwitchClick(superIdentityRecordKey);
},
footerCallback: () {
_doEditClick(superIdentityRecordKey); _doEditClick(superIdentityRecordKey);
}), }),
loading: () => _wrapInBox( loading: () => _wrapInBox(
child: buildProgressIndicator(), child: buildProgressIndicator(),
color: scale.grayScale.subtleBorder), color: scaleScheme.grayScale.subtleBorder),
error: (err, st) => _wrapInBox( error: (err, st) => _wrapInBox(
child: errorPage(err, st), color: scale.errorScale.subtleBorder), child: errorPage(err, st),
color: scaleScheme.errorScale.subtleBorder),
); );
loggedInAccounts.add(loggedInAccount); loggedInAccounts.add(loggedInAccount.paddingLTRB(0, 0, 0, 8));
} else { } else {
// Account is not logged in // Account is not logged in
final scale = theme.extension<ScaleScheme>()!.grayScale;
final loggedOutAccount = _makeAccountWidget( final loggedOutAccount = _makeAccountWidget(
name: la.name, name: la.name,
scale: scale,
selected: superIdentityRecordKey == activeLocalAccount,
loggedIn: false, loggedIn: false,
clickHandler: () { callback: () => {_doSwitchClick(superIdentityRecordKey)},
_doLoginClick(superIdentityRecordKey); footerCallback: null,
}); );
loggedOutAccounts.add(loggedOutAccount); loggedOutAccounts.add(loggedOutAccount);
} }
} }
@ -208,7 +230,7 @@ class _DrawerMenuState extends State<DrawerMenu> {
final scale = theme.extension<ScaleScheme>()!; final scale = theme.extension<ScaleScheme>()!;
//final textTheme = theme.textTheme; //final textTheme = theme.textTheme;
final accountRecords = context.watch<AccountRecordsBlocMapCubit>().state; final accountRecords = context.watch<AccountRecordsBlocMapCubit>().state;
final activeLocalAccount = context.watch<ActiveLocalAccountCubit>().state; final activeLocalAccount = context.watch<ActiveAccountInfoCubit>().state;
final gradient = LinearGradient( final gradient = LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
@ -249,13 +271,21 @@ class _DrawerMenuState extends State<DrawerMenu> {
])), ])),
const Spacer(), const Spacer(),
_getAccountList( _getAccountList(
activeLocalAccount: activeLocalAccount, activeLocalAccount:
activeLocalAccount.unlockedAccountInfo?.superIdentityRecordKey,
accountRecords: accountRecords), accountRecords: accountRecords),
_getBottomButtons(), _getBottomButtons(),
const Spacer(), const Spacer(),
Row(children: [
Text('Version $packageInfoVersion', Text('Version $packageInfoVersion',
style: theme.textTheme.labelMedium! style: theme.textTheme.labelMedium!
.copyWith(color: scale.tertiaryScale.hoverBorder)) .copyWith(color: scale.tertiaryScale.hoverBorder)),
const Spacer(),
SignalStrengthMeterWidget(
color: scale.tertiaryScale.hoverBorder,
inactiveColor: scale.tertiaryScale.border,
),
])
]).paddingAll(16), ]).paddingAll(16),
); );
} }

View file

@ -27,7 +27,7 @@ class MenuItemWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) => TextButton( Widget build(BuildContext context) => TextButton(
onPressed: () => callback, onPressed: callback,
style: TextButton.styleFrom(foregroundColor: foregroundColor).copyWith( style: TextButton.styleFrom(foregroundColor: foregroundColor).copyWith(
backgroundColor: WidgetStateProperty.resolveWith((states) { backgroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.hovered)) { if (states.contains(WidgetState.hovered)) {

View file

@ -44,7 +44,7 @@ class _HomeAccountReadyMainState extends State<HomeAccountReadyMain> {
WidgetStateProperty.all(scale.primaryScale.hoverBorder), WidgetStateProperty.all(scale.primaryScale.hoverBorder),
shape: WidgetStateProperty.all(const RoundedRectangleBorder( shape: WidgetStateProperty.all(const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16))))), borderRadius: BorderRadius.all(Radius.circular(16))))),
tooltip: translate('app_bar.settings_tooltip'), tooltip: translate('menu.settings_tooltip'),
onPressed: () async { onPressed: () async {
final ctrl = context.read<ZoomDrawerController>(); final ctrl = context.read<ZoomDrawerController>();
await ctrl.toggle?.call(); await ctrl.toggle?.call();

View file

@ -1,15 +1,14 @@
import 'package:async_tools/async_tools.dart'; import 'package:async_tools/async_tools.dart';
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../../account_manager/account_manager.dart'; import '../../../account_manager/account_manager.dart';
import '../../../chat/chat.dart'; import '../../../chat/chat.dart';
import '../../../chat_list/chat_list.dart'; import '../../../chat_list/chat_list.dart';
import '../../../contact_invitation/contact_invitation.dart'; import '../../../contact_invitation/contact_invitation.dart';
import '../../../contacts/contacts.dart'; import '../../../contacts/contacts.dart';
import '../../../conversation/conversation.dart';
import '../../../router/router.dart'; import '../../../router/router.dart';
import '../../../theme/theme.dart'; import '../../../theme/theme.dart';
@ -18,20 +17,17 @@ class HomeAccountReadyShell extends StatefulWidget {
{required BuildContext context, required Widget child, Key? key}) { {required BuildContext context, required Widget child, Key? key}) {
// These must exist in order for the account to // These must exist in order for the account to
// be considered 'ready' for this widget subtree // be considered 'ready' for this widget subtree
final activeLocalAccount = context.read<ActiveLocalAccountCubit>().state!; final unlockedAccountInfo = context.watch<UnlockedAccountInfo>();
final activeAccountInfo = context.read<ActiveAccountInfo>();
final routerCubit = context.read<RouterCubit>(); final routerCubit = context.read<RouterCubit>();
return HomeAccountReadyShell._( return HomeAccountReadyShell._(
activeLocalAccount: activeLocalAccount, unlockedAccountInfo: unlockedAccountInfo,
activeAccountInfo: activeAccountInfo,
routerCubit: routerCubit, routerCubit: routerCubit,
key: key, key: key,
child: child); child: child);
} }
const HomeAccountReadyShell._( const HomeAccountReadyShell._(
{required this.activeLocalAccount, {required this.unlockedAccountInfo,
required this.activeAccountInfo,
required this.routerCubit, required this.routerCubit,
required this.child, required this.child,
super.key}); super.key});
@ -40,18 +36,15 @@ class HomeAccountReadyShell extends StatefulWidget {
HomeAccountReadyShellState createState() => HomeAccountReadyShellState(); HomeAccountReadyShellState createState() => HomeAccountReadyShellState();
final Widget child; final Widget child;
final TypedKey activeLocalAccount; final UnlockedAccountInfo unlockedAccountInfo;
final ActiveAccountInfo activeAccountInfo;
final RouterCubit routerCubit; final RouterCubit routerCubit;
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
properties properties
..add(DiagnosticsProperty<TypedKey>( ..add(DiagnosticsProperty<UnlockedAccountInfo>(
'activeLocalAccount', activeLocalAccount)) 'unlockedAccountInfo', unlockedAccountInfo))
..add(DiagnosticsProperty<ActiveAccountInfo>(
'activeAccountInfo', activeAccountInfo))
..add(DiagnosticsProperty<RouterCubit>('routerCubit', routerCubit)); ..add(DiagnosticsProperty<RouterCubit>('routerCubit', routerCubit));
} }
} }
@ -115,39 +108,41 @@ class HomeAccountReadyShellState extends State<HomeAccountReadyShell> {
} }
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
// Contact Cubits
BlocProvider( BlocProvider(
create: (context) => ContactInvitationListCubit( create: (context) => ContactInvitationListCubit(
activeAccountInfo: widget.activeAccountInfo, unlockedAccountInfo: widget.unlockedAccountInfo,
account: account)), account: account)),
BlocProvider( BlocProvider(
create: (context) => ContactListCubit( create: (context) => ContactListCubit(
activeAccountInfo: widget.activeAccountInfo, unlockedAccountInfo: widget.unlockedAccountInfo,
account: account)), account: account)),
BlocProvider(
create: (context) => ActiveChatCubit(null)
..withStateListen((event) {
widget.routerCubit.setHasActiveChat(event != null);
})),
BlocProvider(
create: (context) => ChatListCubit(
activeAccountInfo: widget.activeAccountInfo,
activeChatCubit: context.read<ActiveChatCubit>(),
account: account)),
BlocProvider(
create: (context) => ActiveConversationsBlocMapCubit(
activeAccountInfo: widget.activeAccountInfo,
contactListCubit: context.read<ContactListCubit>())
..follow(context.read<ChatListCubit>())),
BlocProvider(
create: (context) => ActiveSingleContactChatBlocMapCubit(
activeAccountInfo: widget.activeAccountInfo,
contactListCubit: context.read<ContactListCubit>(),
chatListCubit: context.read<ChatListCubit>())
..follow(context.read<ActiveConversationsBlocMapCubit>())),
BlocProvider( BlocProvider(
create: (context) => WaitingInvitationsBlocMapCubit( create: (context) => WaitingInvitationsBlocMapCubit(
activeAccountInfo: widget.activeAccountInfo, account: account) unlockedAccountInfo: widget.unlockedAccountInfo,
..follow(context.read<ContactInvitationListCubit>())) account: account)
..follow(context.watch<ContactInvitationListCubit>())),
// Chat Cubits
BlocProvider(
create: (context) => ActiveChatCubit(null,
routerCubit: context.watch<RouterCubit>())),
BlocProvider(
create: (context) => ChatListCubit(
unlockedAccountInfo: widget.unlockedAccountInfo,
activeChatCubit: context.watch<ActiveChatCubit>(),
account: account)),
// Conversation Cubits
BlocProvider(
create: (context) => ActiveConversationsBlocMapCubit(
unlockedAccountInfo: widget.unlockedAccountInfo,
contactListCubit: context.watch<ContactListCubit>())
..follow(context.watch<ChatListCubit>())),
BlocProvider(
create: (context) => ActiveSingleContactChatBlocMapCubit(
unlockedAccountInfo: widget.unlockedAccountInfo,
contactListCubit: context.watch<ContactListCubit>(),
chatListCubit: context.watch<ChatListCubit>())
..follow(context.watch<ActiveConversationsBlocMapCubit>())),
], ],
child: MultiBlocListener(listeners: [ child: MultiBlocListener(listeners: [
BlocListener<WaitingInvitationsBlocMapCubit, BlocListener<WaitingInvitationsBlocMapCubit,

View file

@ -1,8 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_zoom_drawer/flutter_zoom_drawer.dart'; import 'package:flutter_zoom_drawer/flutter_zoom_drawer.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -35,17 +33,19 @@ class HomeShellState extends State<HomeShell> {
} }
Widget buildWithLogin(BuildContext context) { Widget buildWithLogin(BuildContext context) {
final activeLocalAccount = context.watch<ActiveLocalAccountCubit>().state; final accountInfo = context.watch<ActiveAccountInfoCubit>().state;
final accountRecordsCubit = context.watch<AccountRecordsBlocMapCubit>(); final accountRecordsCubit = context.watch<AccountRecordsBlocMapCubit>();
if (activeLocalAccount == null) { if (!accountInfo.active) {
// If no logged in user is active, show the loading panel // If no logged in user is active, show the loading panel
return const HomeNoActive(); return const HomeNoActive();
} }
final accountInfo = final superIdentityRecordKey =
AccountRepository.instance.getAccountInfo(activeLocalAccount); accountInfo.unlockedAccountInfo?.superIdentityRecordKey;
final activeCubit = final activeCubit = superIdentityRecordKey == null
accountRecordsCubit.tryOperate(activeLocalAccount, closure: (c) => c); ? null
: accountRecordsCubit.tryOperate(superIdentityRecordKey,
closure: (c) => c);
if (activeCubit == null) { if (activeCubit == null) {
return waitingPage(); return waitingPage();
} }
@ -59,8 +59,8 @@ class HomeShellState extends State<HomeShell> {
return const HomeAccountLocked(); return const HomeAccountLocked();
case AccountInfoStatus.accountReady: case AccountInfoStatus.accountReady:
return MultiProvider(providers: [ return MultiProvider(providers: [
Provider<ActiveAccountInfo>.value( Provider<UnlockedAccountInfo>.value(
value: accountInfo.activeAccountInfo!, value: accountInfo.unlockedAccountInfo!,
), ),
Provider<AccountRecordCubit>.value(value: activeCubit), Provider<AccountRecordCubit>.value(value: activeCubit),
Provider<ZoomDrawerController>.value(value: _zoomDrawerController), Provider<ZoomDrawerController>.value(value: _zoomDrawerController),
@ -101,6 +101,7 @@ class HomeShellState extends State<HomeShell> {
// duration: const Duration(milliseconds: 250), // duration: const Duration(milliseconds: 250),
// reverseDuration: const Duration(milliseconds: 250), // reverseDuration: const Duration(milliseconds: 250),
menuScreenTapClose: true, menuScreenTapClose: true,
mainScreenTapClose: true,
mainScreenScale: .25, mainScreenScale: .25,
slideWidth: min(360, MediaQuery.of(context).size.width * 0.9), slideWidth: min(360, MediaQuery.of(context).size.width * 0.9),
))); )));

View file

@ -20,6 +20,177 @@ import 'veilidchat.pbenum.dart';
export 'veilidchat.pbenum.dart'; export 'veilidchat.pbenum.dart';
class DHTDataReference extends $pb.GeneratedMessage {
factory DHTDataReference() => create();
DHTDataReference._() : super();
factory DHTDataReference.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory DHTDataReference.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'DHTDataReference', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create)
..aOM<$0.TypedKey>(1, _omitFieldNames ? '' : 'dhtData', subBuilder: $0.TypedKey.create)
..aOM<$0.TypedKey>(2, _omitFieldNames ? '' : 'hash', subBuilder: $0.TypedKey.create)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
DHTDataReference clone() => DHTDataReference()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
DHTDataReference copyWith(void Function(DHTDataReference) updates) => super.copyWith((message) => updates(message as DHTDataReference)) as DHTDataReference;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static DHTDataReference create() => DHTDataReference._();
DHTDataReference createEmptyInstance() => create();
static $pb.PbList<DHTDataReference> createRepeated() => $pb.PbList<DHTDataReference>();
@$core.pragma('dart2js:noInline')
static DHTDataReference getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DHTDataReference>(create);
static DHTDataReference? _defaultInstance;
@$pb.TagNumber(1)
$0.TypedKey get dhtData => $_getN(0);
@$pb.TagNumber(1)
set dhtData($0.TypedKey v) { setField(1, v); }
@$pb.TagNumber(1)
$core.bool hasDhtData() => $_has(0);
@$pb.TagNumber(1)
void clearDhtData() => clearField(1);
@$pb.TagNumber(1)
$0.TypedKey ensureDhtData() => $_ensure(0);
@$pb.TagNumber(2)
$0.TypedKey get hash => $_getN(1);
@$pb.TagNumber(2)
set hash($0.TypedKey v) { setField(2, v); }
@$pb.TagNumber(2)
$core.bool hasHash() => $_has(1);
@$pb.TagNumber(2)
void clearHash() => clearField(2);
@$pb.TagNumber(2)
$0.TypedKey ensureHash() => $_ensure(1);
}
class BlockStoreDataReference extends $pb.GeneratedMessage {
factory BlockStoreDataReference() => create();
BlockStoreDataReference._() : super();
factory BlockStoreDataReference.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory BlockStoreDataReference.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'BlockStoreDataReference', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create)
..aOM<$0.TypedKey>(1, _omitFieldNames ? '' : 'block', subBuilder: $0.TypedKey.create)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
BlockStoreDataReference clone() => BlockStoreDataReference()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
BlockStoreDataReference copyWith(void Function(BlockStoreDataReference) updates) => super.copyWith((message) => updates(message as BlockStoreDataReference)) as BlockStoreDataReference;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static BlockStoreDataReference create() => BlockStoreDataReference._();
BlockStoreDataReference createEmptyInstance() => create();
static $pb.PbList<BlockStoreDataReference> createRepeated() => $pb.PbList<BlockStoreDataReference>();
@$core.pragma('dart2js:noInline')
static BlockStoreDataReference getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<BlockStoreDataReference>(create);
static BlockStoreDataReference? _defaultInstance;
@$pb.TagNumber(1)
$0.TypedKey get block => $_getN(0);
@$pb.TagNumber(1)
set block($0.TypedKey v) { setField(1, v); }
@$pb.TagNumber(1)
$core.bool hasBlock() => $_has(0);
@$pb.TagNumber(1)
void clearBlock() => clearField(1);
@$pb.TagNumber(1)
$0.TypedKey ensureBlock() => $_ensure(0);
}
enum DataReference_Kind {
dhtData,
blockStoreData,
notSet
}
class DataReference extends $pb.GeneratedMessage {
factory DataReference() => create();
DataReference._() : super();
factory DataReference.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory DataReference.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static const $core.Map<$core.int, DataReference_Kind> _DataReference_KindByTag = {
1 : DataReference_Kind.dhtData,
2 : DataReference_Kind.blockStoreData,
0 : DataReference_Kind.notSet
};
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'DataReference', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create)
..oo(0, [1, 2])
..aOM<DHTDataReference>(1, _omitFieldNames ? '' : 'dhtData', subBuilder: DHTDataReference.create)
..aOM<BlockStoreDataReference>(2, _omitFieldNames ? '' : 'blockStoreData', subBuilder: BlockStoreDataReference.create)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
DataReference clone() => DataReference()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
DataReference copyWith(void Function(DataReference) updates) => super.copyWith((message) => updates(message as DataReference)) as DataReference;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static DataReference create() => DataReference._();
DataReference createEmptyInstance() => create();
static $pb.PbList<DataReference> createRepeated() => $pb.PbList<DataReference>();
@$core.pragma('dart2js:noInline')
static DataReference getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DataReference>(create);
static DataReference? _defaultInstance;
DataReference_Kind whichKind() => _DataReference_KindByTag[$_whichOneof(0)]!;
void clearKind() => clearField($_whichOneof(0));
@$pb.TagNumber(1)
DHTDataReference get dhtData => $_getN(0);
@$pb.TagNumber(1)
set dhtData(DHTDataReference v) { setField(1, v); }
@$pb.TagNumber(1)
$core.bool hasDhtData() => $_has(0);
@$pb.TagNumber(1)
void clearDhtData() => clearField(1);
@$pb.TagNumber(1)
DHTDataReference ensureDhtData() => $_ensure(0);
@$pb.TagNumber(2)
BlockStoreDataReference get blockStoreData => $_getN(1);
@$pb.TagNumber(2)
set blockStoreData(BlockStoreDataReference v) { setField(2, v); }
@$pb.TagNumber(2)
$core.bool hasBlockStoreData() => $_has(1);
@$pb.TagNumber(2)
void clearBlockStoreData() => clearField(2);
@$pb.TagNumber(2)
BlockStoreDataReference ensureBlockStoreData() => $_ensure(1);
}
enum Attachment_Kind { enum Attachment_Kind {
media, media,
notSet notSet
@ -98,7 +269,7 @@ class AttachmentMedia extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'AttachmentMedia', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create) static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'AttachmentMedia', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'mime') ..aOS(1, _omitFieldNames ? '' : 'mime')
..aOS(2, _omitFieldNames ? '' : 'name') ..aOS(2, _omitFieldNames ? '' : 'name')
..aOM<$1.DataReference>(3, _omitFieldNames ? '' : 'content', subBuilder: $1.DataReference.create) ..aOM<DataReference>(3, _omitFieldNames ? '' : 'content', subBuilder: DataReference.create)
..hasRequiredFields = false ..hasRequiredFields = false
; ;
@ -142,15 +313,15 @@ class AttachmentMedia extends $pb.GeneratedMessage {
void clearName() => clearField(2); void clearName() => clearField(2);
@$pb.TagNumber(3) @$pb.TagNumber(3)
$1.DataReference get content => $_getN(2); DataReference get content => $_getN(2);
@$pb.TagNumber(3) @$pb.TagNumber(3)
set content($1.DataReference v) { setField(3, v); } set content(DataReference v) { setField(3, v); }
@$pb.TagNumber(3) @$pb.TagNumber(3)
$core.bool hasContent() => $_has(2); $core.bool hasContent() => $_has(2);
@$pb.TagNumber(3) @$pb.TagNumber(3)
void clearContent() => clearField(3); void clearContent() => clearField(3);
@$pb.TagNumber(3) @$pb.TagNumber(3)
$1.DataReference ensureContent() => $_ensure(2); DataReference ensureContent() => $_ensure(2);
} }
class Permissions extends $pb.GeneratedMessage { class Permissions extends $pb.GeneratedMessage {
@ -276,7 +447,7 @@ class ChatSettings extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ChatSettings', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create) static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ChatSettings', package: const $pb.PackageName(_omitMessageNames ? '' : 'veilidchat'), createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'title') ..aOS(1, _omitFieldNames ? '' : 'title')
..aOS(2, _omitFieldNames ? '' : 'description') ..aOS(2, _omitFieldNames ? '' : 'description')
..aOM<$1.DataReference>(3, _omitFieldNames ? '' : 'icon', subBuilder: $1.DataReference.create) ..aOM<DataReference>(3, _omitFieldNames ? '' : 'icon', subBuilder: DataReference.create)
..a<$fixnum.Int64>(4, _omitFieldNames ? '' : 'defaultExpiration', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO) ..a<$fixnum.Int64>(4, _omitFieldNames ? '' : 'defaultExpiration', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO)
..hasRequiredFields = false ..hasRequiredFields = false
; ;
@ -321,15 +492,15 @@ class ChatSettings extends $pb.GeneratedMessage {
void clearDescription() => clearField(2); void clearDescription() => clearField(2);
@$pb.TagNumber(3) @$pb.TagNumber(3)
$1.DataReference get icon => $_getN(2); DataReference get icon => $_getN(2);
@$pb.TagNumber(3) @$pb.TagNumber(3)
set icon($1.DataReference v) { setField(3, v); } set icon(DataReference v) { setField(3, v); }
@$pb.TagNumber(3) @$pb.TagNumber(3)
$core.bool hasIcon() => $_has(2); $core.bool hasIcon() => $_has(2);
@$pb.TagNumber(3) @$pb.TagNumber(3)
void clearIcon() => clearField(3); void clearIcon() => clearField(3);
@$pb.TagNumber(3) @$pb.TagNumber(3)
$1.DataReference ensureIcon() => $_ensure(2); DataReference ensureIcon() => $_ensure(2);
@$pb.TagNumber(4) @$pb.TagNumber(4)
$fixnum.Int64 get defaultExpiration => $_getI64(3); $fixnum.Int64 get defaultExpiration => $_getI64(3);
@ -1224,7 +1395,7 @@ class Profile extends $pb.GeneratedMessage {
..aOS(3, _omitFieldNames ? '' : 'about') ..aOS(3, _omitFieldNames ? '' : 'about')
..aOS(4, _omitFieldNames ? '' : 'status') ..aOS(4, _omitFieldNames ? '' : 'status')
..e<Availability>(5, _omitFieldNames ? '' : 'availability', $pb.PbFieldType.OE, defaultOrMaker: Availability.AVAILABILITY_UNSPECIFIED, valueOf: Availability.valueOf, enumValues: Availability.values) ..e<Availability>(5, _omitFieldNames ? '' : 'availability', $pb.PbFieldType.OE, defaultOrMaker: Availability.AVAILABILITY_UNSPECIFIED, valueOf: Availability.valueOf, enumValues: Availability.values)
..aOM<$0.TypedKey>(6, _omitFieldNames ? '' : 'avatar', subBuilder: $0.TypedKey.create) ..aOM<DataReference>(6, _omitFieldNames ? '' : 'avatar', subBuilder: DataReference.create)
..hasRequiredFields = false ..hasRequiredFields = false
; ;
@ -1295,15 +1466,15 @@ class Profile extends $pb.GeneratedMessage {
void clearAvailability() => clearField(5); void clearAvailability() => clearField(5);
@$pb.TagNumber(6) @$pb.TagNumber(6)
$0.TypedKey get avatar => $_getN(5); DataReference get avatar => $_getN(5);
@$pb.TagNumber(6) @$pb.TagNumber(6)
set avatar($0.TypedKey v) { setField(6, v); } set avatar(DataReference v) { setField(6, v); }
@$pb.TagNumber(6) @$pb.TagNumber(6)
$core.bool hasAvatar() => $_has(5); $core.bool hasAvatar() => $_has(5);
@$pb.TagNumber(6) @$pb.TagNumber(6)
void clearAvatar() => clearField(6); void clearAvatar() => clearField(6);
@$pb.TagNumber(6) @$pb.TagNumber(6)
$0.TypedKey ensureAvatar() => $_ensure(5); DataReference ensureAvatar() => $_ensure(5);
} }
class Account extends $pb.GeneratedMessage { class Account extends $pb.GeneratedMessage {

View file

@ -65,6 +65,51 @@ final $typed_data.Uint8List scopeDescriptor = $convert.base64Decode(
'CgVTY29wZRIMCghXQVRDSEVSUxAAEg0KCU1PREVSQVRFRBABEgsKB1RBTEtFUlMQAhIOCgpNT0' 'CgVTY29wZRIMCghXQVRDSEVSUxAAEg0KCU1PREVSQVRFRBABEgsKB1RBTEtFUlMQAhIOCgpNT0'
'RFUkFUT1JTEAMSCgoGQURNSU5TEAQ='); 'RFUkFUT1JTEAMSCgoGQURNSU5TEAQ=');
@$core.Deprecated('Use dHTDataReferenceDescriptor instead')
const DHTDataReference$json = {
'1': 'DHTDataReference',
'2': [
{'1': 'dht_data', '3': 1, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'dhtData'},
{'1': 'hash', '3': 2, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'hash'},
],
};
/// Descriptor for `DHTDataReference`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List dHTDataReferenceDescriptor = $convert.base64Decode(
'ChBESFREYXRhUmVmZXJlbmNlEisKCGRodF9kYXRhGAEgASgLMhAudmVpbGlkLlR5cGVkS2V5Ug'
'dkaHREYXRhEiQKBGhhc2gYAiABKAsyEC52ZWlsaWQuVHlwZWRLZXlSBGhhc2g=');
@$core.Deprecated('Use blockStoreDataReferenceDescriptor instead')
const BlockStoreDataReference$json = {
'1': 'BlockStoreDataReference',
'2': [
{'1': 'block', '3': 1, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'block'},
],
};
/// Descriptor for `BlockStoreDataReference`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List blockStoreDataReferenceDescriptor = $convert.base64Decode(
'ChdCbG9ja1N0b3JlRGF0YVJlZmVyZW5jZRImCgVibG9jaxgBIAEoCzIQLnZlaWxpZC5UeXBlZE'
'tleVIFYmxvY2s=');
@$core.Deprecated('Use dataReferenceDescriptor instead')
const DataReference$json = {
'1': 'DataReference',
'2': [
{'1': 'dht_data', '3': 1, '4': 1, '5': 11, '6': '.veilidchat.DHTDataReference', '9': 0, '10': 'dhtData'},
{'1': 'block_store_data', '3': 2, '4': 1, '5': 11, '6': '.veilidchat.BlockStoreDataReference', '9': 0, '10': 'blockStoreData'},
],
'8': [
{'1': 'kind'},
],
};
/// Descriptor for `DataReference`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List dataReferenceDescriptor = $convert.base64Decode(
'Cg1EYXRhUmVmZXJlbmNlEjkKCGRodF9kYXRhGAEgASgLMhwudmVpbGlkY2hhdC5ESFREYXRhUm'
'VmZXJlbmNlSABSB2RodERhdGESTwoQYmxvY2tfc3RvcmVfZGF0YRgCIAEoCzIjLnZlaWxpZGNo'
'YXQuQmxvY2tTdG9yZURhdGFSZWZlcmVuY2VIAFIOYmxvY2tTdG9yZURhdGFCBgoEa2luZA==');
@$core.Deprecated('Use attachmentDescriptor instead') @$core.Deprecated('Use attachmentDescriptor instead')
const Attachment$json = { const Attachment$json = {
'1': 'Attachment', '1': 'Attachment',
@ -89,14 +134,14 @@ const AttachmentMedia$json = {
'2': [ '2': [
{'1': 'mime', '3': 1, '4': 1, '5': 9, '10': 'mime'}, {'1': 'mime', '3': 1, '4': 1, '5': 9, '10': 'mime'},
{'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'}, {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
{'1': 'content', '3': 3, '4': 1, '5': 11, '6': '.dht.DataReference', '10': 'content'}, {'1': 'content', '3': 3, '4': 1, '5': 11, '6': '.veilidchat.DataReference', '10': 'content'},
], ],
}; };
/// Descriptor for `AttachmentMedia`. Decode as a `google.protobuf.DescriptorProto`. /// Descriptor for `AttachmentMedia`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List attachmentMediaDescriptor = $convert.base64Decode( final $typed_data.Uint8List attachmentMediaDescriptor = $convert.base64Decode(
'Cg9BdHRhY2htZW50TWVkaWESEgoEbWltZRgBIAEoCVIEbWltZRISCgRuYW1lGAIgASgJUgRuYW' 'Cg9BdHRhY2htZW50TWVkaWESEgoEbWltZRgBIAEoCVIEbWltZRISCgRuYW1lGAIgASgJUgRuYW'
'1lEiwKB2NvbnRlbnQYAyABKAsyEi5kaHQuRGF0YVJlZmVyZW5jZVIHY29udGVudA=='); '1lEjMKB2NvbnRlbnQYAyABKAsyGS52ZWlsaWRjaGF0LkRhdGFSZWZlcmVuY2VSB2NvbnRlbnQ=');
@$core.Deprecated('Use permissionsDescriptor instead') @$core.Deprecated('Use permissionsDescriptor instead')
const Permissions$json = { const Permissions$json = {
@ -140,7 +185,7 @@ const ChatSettings$json = {
'2': [ '2': [
{'1': 'title', '3': 1, '4': 1, '5': 9, '10': 'title'}, {'1': 'title', '3': 1, '4': 1, '5': 9, '10': 'title'},
{'1': 'description', '3': 2, '4': 1, '5': 9, '10': 'description'}, {'1': 'description', '3': 2, '4': 1, '5': 9, '10': 'description'},
{'1': 'icon', '3': 3, '4': 1, '5': 11, '6': '.dht.DataReference', '9': 0, '10': 'icon', '17': true}, {'1': 'icon', '3': 3, '4': 1, '5': 11, '6': '.veilidchat.DataReference', '9': 0, '10': 'icon', '17': true},
{'1': 'default_expiration', '3': 4, '4': 1, '5': 4, '10': 'defaultExpiration'}, {'1': 'default_expiration', '3': 4, '4': 1, '5': 4, '10': 'defaultExpiration'},
], ],
'8': [ '8': [
@ -151,9 +196,9 @@ const ChatSettings$json = {
/// Descriptor for `ChatSettings`. Decode as a `google.protobuf.DescriptorProto`. /// Descriptor for `ChatSettings`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List chatSettingsDescriptor = $convert.base64Decode( final $typed_data.Uint8List chatSettingsDescriptor = $convert.base64Decode(
'CgxDaGF0U2V0dGluZ3MSFAoFdGl0bGUYASABKAlSBXRpdGxlEiAKC2Rlc2NyaXB0aW9uGAIgAS' 'CgxDaGF0U2V0dGluZ3MSFAoFdGl0bGUYASABKAlSBXRpdGxlEiAKC2Rlc2NyaXB0aW9uGAIgAS'
'gJUgtkZXNjcmlwdGlvbhIrCgRpY29uGAMgASgLMhIuZGh0LkRhdGFSZWZlcmVuY2VIAFIEaWNv' 'gJUgtkZXNjcmlwdGlvbhIyCgRpY29uGAMgASgLMhkudmVpbGlkY2hhdC5EYXRhUmVmZXJlbmNl'
'bogBARItChJkZWZhdWx0X2V4cGlyYXRpb24YBCABKARSEWRlZmF1bHRFeHBpcmF0aW9uQgcKBV' 'SABSBGljb26IAQESLQoSZGVmYXVsdF9leHBpcmF0aW9uGAQgASgEUhFkZWZhdWx0RXhwaXJhdG'
'9pY29u'); 'lvbkIHCgVfaWNvbg==');
@$core.Deprecated('Use messageDescriptor instead') @$core.Deprecated('Use messageDescriptor instead')
const Message$json = { const Message$json = {
@ -365,7 +410,7 @@ const Profile$json = {
{'1': 'about', '3': 3, '4': 1, '5': 9, '10': 'about'}, {'1': 'about', '3': 3, '4': 1, '5': 9, '10': 'about'},
{'1': 'status', '3': 4, '4': 1, '5': 9, '10': 'status'}, {'1': 'status', '3': 4, '4': 1, '5': 9, '10': 'status'},
{'1': 'availability', '3': 5, '4': 1, '5': 14, '6': '.veilidchat.Availability', '10': 'availability'}, {'1': 'availability', '3': 5, '4': 1, '5': 14, '6': '.veilidchat.Availability', '10': 'availability'},
{'1': 'avatar', '3': 6, '4': 1, '5': 11, '6': '.veilid.TypedKey', '9': 0, '10': 'avatar', '17': true}, {'1': 'avatar', '3': 6, '4': 1, '5': 11, '6': '.veilidchat.DataReference', '9': 0, '10': 'avatar', '17': true},
], ],
'8': [ '8': [
{'1': '_avatar'}, {'1': '_avatar'},
@ -376,9 +421,9 @@ const Profile$json = {
final $typed_data.Uint8List profileDescriptor = $convert.base64Decode( final $typed_data.Uint8List profileDescriptor = $convert.base64Decode(
'CgdQcm9maWxlEhIKBG5hbWUYASABKAlSBG5hbWUSGgoIcHJvbm91bnMYAiABKAlSCHByb25vdW' 'CgdQcm9maWxlEhIKBG5hbWUYASABKAlSBG5hbWUSGgoIcHJvbm91bnMYAiABKAlSCHByb25vdW'
'5zEhQKBWFib3V0GAMgASgJUgVhYm91dBIWCgZzdGF0dXMYBCABKAlSBnN0YXR1cxI8CgxhdmFp' '5zEhQKBWFib3V0GAMgASgJUgVhYm91dBIWCgZzdGF0dXMYBCABKAlSBnN0YXR1cxI8CgxhdmFp'
'bGFiaWxpdHkYBSABKA4yGC52ZWlsaWRjaGF0LkF2YWlsYWJpbGl0eVIMYXZhaWxhYmlsaXR5Ei' 'bGFiaWxpdHkYBSABKA4yGC52ZWlsaWRjaGF0LkF2YWlsYWJpbGl0eVIMYXZhaWxhYmlsaXR5Ej'
'0KBmF2YXRhchgGIAEoCzIQLnZlaWxpZC5UeXBlZEtleUgAUgZhdmF0YXKIAQFCCQoHX2F2YXRh' 'YKBmF2YXRhchgGIAEoCzIZLnZlaWxpZGNoYXQuRGF0YVJlZmVyZW5jZUgAUgZhdmF0YXKIAQFC'
'cg=='); 'CQoHX2F2YXRhcg==');
@$core.Deprecated('Use accountDescriptor instead') @$core.Deprecated('Use accountDescriptor instead')
const Account$json = { const Account$json = {

View file

@ -47,6 +47,31 @@ enum Scope {
ADMINS = 4; ADMINS = 4;
} }
////////////////////////////////////////////////////////////////////////////////////
// Data
////////////////////////////////////////////////////////////////////////////////////
// Reference to data on the DHT
message DHTDataReference {
veilid.TypedKey dht_data = 1;
veilid.TypedKey hash = 2;
}
// Reference to data on the BlockStore
message BlockStoreDataReference {
veilid.TypedKey block = 1;
}
// DataReference
// Pointer to data somewhere in Veilid
// Abstraction over DHTData and BlockStore
message DataReference {
oneof kind {
DHTDataReference dht_data = 1;
BlockStoreDataReference block_store_data = 2;
}
}
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
// Attachments // Attachments
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
@ -67,10 +92,9 @@ message AttachmentMedia {
// Title or filename // Title or filename
string name = 2; string name = 2;
// Pointer to the data content // Pointer to the data content
dht.DataReference content = 3; DataReference content = 3;
} }
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
// Chat room controls // Chat room controls
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
@ -106,7 +130,7 @@ message ChatSettings {
// Description for the chat // Description for the chat
string description = 2; string description = 2;
// Icon for the chat // Icon for the chat
optional dht.DataReference icon = 3; optional DataReference icon = 3;
// Default message expiration duration (in us) // Default message expiration duration (in us)
uint64 default_expiration = 4; uint64 default_expiration = 4;
} }
@ -285,8 +309,8 @@ message Profile {
string status = 4; string status = 4;
// Availability // Availability
Availability availability = 5; Availability availability = 5;
// Avatar DHTData // Avatar
optional veilid.TypedKey avatar = 6; optional DataReference avatar = 6;
} }
// A record of an individual account // A record of an individual account

View file

@ -1,5 +1,6 @@
import 'package:async_tools/async_tools.dart'; import 'package:async_tools/async_tools.dart';
import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@ -11,7 +12,7 @@ import '../../theme/theme.dart';
import '../cubit/connection_state_cubit.dart'; import '../cubit/connection_state_cubit.dart';
class SignalStrengthMeterWidget extends StatelessWidget { class SignalStrengthMeterWidget extends StatelessWidget {
const SignalStrengthMeterWidget({super.key}); const SignalStrengthMeterWidget({super.key, this.color, this.inactiveColor});
@override @override
// ignore: prefer_expression_function_bodies // ignore: prefer_expression_function_bodies
@ -33,32 +34,35 @@ class SignalStrengthMeterWidget extends StatelessWidget {
switch (connectionState.attachment.state) { switch (connectionState.attachment.state) {
case AttachmentState.detached: case AttachmentState.detached:
iconWidget = Icon(Icons.signal_cellular_nodata, iconWidget = Icon(Icons.signal_cellular_nodata,
size: iconSize, color: scale.primaryScale.primaryText); size: iconSize,
color: this.color ?? scale.primaryScale.primaryText);
return; return;
case AttachmentState.detaching: case AttachmentState.detaching:
iconWidget = Icon(Icons.signal_cellular_off, iconWidget = Icon(Icons.signal_cellular_off,
size: iconSize, color: scale.primaryScale.primaryText); size: iconSize,
color: this.color ?? scale.primaryScale.primaryText);
return; return;
case AttachmentState.attaching: case AttachmentState.attaching:
value = 0; value = 0;
color = scale.primaryScale.primaryText; color = this.color ?? scale.primaryScale.primaryText;
case AttachmentState.attachedWeak: case AttachmentState.attachedWeak:
value = 1; value = 1;
color = scale.primaryScale.primaryText; color = this.color ?? scale.primaryScale.primaryText;
case AttachmentState.attachedStrong: case AttachmentState.attachedStrong:
value = 2; value = 2;
color = scale.primaryScale.primaryText; color = this.color ?? scale.primaryScale.primaryText;
case AttachmentState.attachedGood: case AttachmentState.attachedGood:
value = 3; value = 3;
color = scale.primaryScale.primaryText; color = this.color ?? scale.primaryScale.primaryText;
case AttachmentState.fullyAttached: case AttachmentState.fullyAttached:
value = 4; value = 4;
color = scale.primaryScale.primaryText; color = this.color ?? scale.primaryScale.primaryText;
case AttachmentState.overAttached: case AttachmentState.overAttached:
value = 4; value = 4;
color = scale.primaryScale.primaryText; color = this.color ?? scale.primaryScale.primaryText;
} }
inactiveColor = scale.primaryScale.primaryText; inactiveColor =
this.inactiveColor ?? scale.primaryScale.primaryText;
iconWidget = SignalStrengthIndicator.bars( iconWidget = SignalStrengthIndicator.bars(
value: value, value: value,
@ -86,4 +90,16 @@ class SignalStrengthMeterWidget extends StatelessWidget {
child: iconWidget); child: iconWidget);
}); });
} }
////////////////////////////////////////////////////////////////////////////
final Color? color;
final Color? inactiveColor;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(ColorProperty('color', color))
..add(ColorProperty('inactiveColor', inactiveColor));
}
} }

View file

@ -23,7 +23,6 @@ message DHTData {
uint32 size = 4; uint32 size = 4;
} }
// DHTLog - represents a ring buffer of many elements with append/truncate semantics // DHTLog - represents a ring buffer of many elements with append/truncate semantics
// Header in subkey 0 of first key follows this structure // Header in subkey 0 of first key follows this structure
message DHTLog { message DHTLog {
@ -62,27 +61,6 @@ message DHTShortArray {
// calculated through iteration // calculated through iteration
} }
// Reference to data on the DHT
message DHTDataReference {
veilid.TypedKey dht_data = 1;
veilid.TypedKey hash = 2;
}
// Reference to data on the BlockStore
message BlockStoreDataReference {
veilid.TypedKey block = 1;
}
// DataReference
// Pointer to data somewhere in Veilid
// Abstraction over DHTData and BlockStore
message DataReference {
oneof kind {
DHTDataReference dht_data = 1;
BlockStoreDataReference block_store_data = 2;
}
}
// A pointer to an child DHT record // A pointer to an child DHT record
message OwnedDHTRecordPointer { message OwnedDHTRecordPointer {
// DHT Record key // DHT Record key

View file

@ -195,177 +195,6 @@ class DHTShortArray extends $pb.GeneratedMessage {
$core.List<$core.int> get seqs => $_getList(2); $core.List<$core.int> get seqs => $_getList(2);
} }
class DHTDataReference extends $pb.GeneratedMessage {
factory DHTDataReference() => create();
DHTDataReference._() : super();
factory DHTDataReference.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory DHTDataReference.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'DHTDataReference', package: const $pb.PackageName(_omitMessageNames ? '' : 'dht'), createEmptyInstance: create)
..aOM<$0.TypedKey>(1, _omitFieldNames ? '' : 'dhtData', subBuilder: $0.TypedKey.create)
..aOM<$0.TypedKey>(2, _omitFieldNames ? '' : 'hash', subBuilder: $0.TypedKey.create)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
DHTDataReference clone() => DHTDataReference()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
DHTDataReference copyWith(void Function(DHTDataReference) updates) => super.copyWith((message) => updates(message as DHTDataReference)) as DHTDataReference;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static DHTDataReference create() => DHTDataReference._();
DHTDataReference createEmptyInstance() => create();
static $pb.PbList<DHTDataReference> createRepeated() => $pb.PbList<DHTDataReference>();
@$core.pragma('dart2js:noInline')
static DHTDataReference getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DHTDataReference>(create);
static DHTDataReference? _defaultInstance;
@$pb.TagNumber(1)
$0.TypedKey get dhtData => $_getN(0);
@$pb.TagNumber(1)
set dhtData($0.TypedKey v) { setField(1, v); }
@$pb.TagNumber(1)
$core.bool hasDhtData() => $_has(0);
@$pb.TagNumber(1)
void clearDhtData() => clearField(1);
@$pb.TagNumber(1)
$0.TypedKey ensureDhtData() => $_ensure(0);
@$pb.TagNumber(2)
$0.TypedKey get hash => $_getN(1);
@$pb.TagNumber(2)
set hash($0.TypedKey v) { setField(2, v); }
@$pb.TagNumber(2)
$core.bool hasHash() => $_has(1);
@$pb.TagNumber(2)
void clearHash() => clearField(2);
@$pb.TagNumber(2)
$0.TypedKey ensureHash() => $_ensure(1);
}
class BlockStoreDataReference extends $pb.GeneratedMessage {
factory BlockStoreDataReference() => create();
BlockStoreDataReference._() : super();
factory BlockStoreDataReference.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory BlockStoreDataReference.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'BlockStoreDataReference', package: const $pb.PackageName(_omitMessageNames ? '' : 'dht'), createEmptyInstance: create)
..aOM<$0.TypedKey>(1, _omitFieldNames ? '' : 'block', subBuilder: $0.TypedKey.create)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
BlockStoreDataReference clone() => BlockStoreDataReference()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
BlockStoreDataReference copyWith(void Function(BlockStoreDataReference) updates) => super.copyWith((message) => updates(message as BlockStoreDataReference)) as BlockStoreDataReference;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static BlockStoreDataReference create() => BlockStoreDataReference._();
BlockStoreDataReference createEmptyInstance() => create();
static $pb.PbList<BlockStoreDataReference> createRepeated() => $pb.PbList<BlockStoreDataReference>();
@$core.pragma('dart2js:noInline')
static BlockStoreDataReference getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<BlockStoreDataReference>(create);
static BlockStoreDataReference? _defaultInstance;
@$pb.TagNumber(1)
$0.TypedKey get block => $_getN(0);
@$pb.TagNumber(1)
set block($0.TypedKey v) { setField(1, v); }
@$pb.TagNumber(1)
$core.bool hasBlock() => $_has(0);
@$pb.TagNumber(1)
void clearBlock() => clearField(1);
@$pb.TagNumber(1)
$0.TypedKey ensureBlock() => $_ensure(0);
}
enum DataReference_Kind {
dhtData,
blockStoreData,
notSet
}
class DataReference extends $pb.GeneratedMessage {
factory DataReference() => create();
DataReference._() : super();
factory DataReference.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory DataReference.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static const $core.Map<$core.int, DataReference_Kind> _DataReference_KindByTag = {
1 : DataReference_Kind.dhtData,
2 : DataReference_Kind.blockStoreData,
0 : DataReference_Kind.notSet
};
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'DataReference', package: const $pb.PackageName(_omitMessageNames ? '' : 'dht'), createEmptyInstance: create)
..oo(0, [1, 2])
..aOM<DHTDataReference>(1, _omitFieldNames ? '' : 'dhtData', subBuilder: DHTDataReference.create)
..aOM<BlockStoreDataReference>(2, _omitFieldNames ? '' : 'blockStoreData', subBuilder: BlockStoreDataReference.create)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
DataReference clone() => DataReference()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
DataReference copyWith(void Function(DataReference) updates) => super.copyWith((message) => updates(message as DataReference)) as DataReference;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static DataReference create() => DataReference._();
DataReference createEmptyInstance() => create();
static $pb.PbList<DataReference> createRepeated() => $pb.PbList<DataReference>();
@$core.pragma('dart2js:noInline')
static DataReference getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<DataReference>(create);
static DataReference? _defaultInstance;
DataReference_Kind whichKind() => _DataReference_KindByTag[$_whichOneof(0)]!;
void clearKind() => clearField($_whichOneof(0));
@$pb.TagNumber(1)
DHTDataReference get dhtData => $_getN(0);
@$pb.TagNumber(1)
set dhtData(DHTDataReference v) { setField(1, v); }
@$pb.TagNumber(1)
$core.bool hasDhtData() => $_has(0);
@$pb.TagNumber(1)
void clearDhtData() => clearField(1);
@$pb.TagNumber(1)
DHTDataReference ensureDhtData() => $_ensure(0);
@$pb.TagNumber(2)
BlockStoreDataReference get blockStoreData => $_getN(1);
@$pb.TagNumber(2)
set blockStoreData(BlockStoreDataReference v) { setField(2, v); }
@$pb.TagNumber(2)
$core.bool hasBlockStoreData() => $_has(1);
@$pb.TagNumber(2)
void clearBlockStoreData() => clearField(2);
@$pb.TagNumber(2)
BlockStoreDataReference ensureBlockStoreData() => $_ensure(1);
}
class OwnedDHTRecordPointer extends $pb.GeneratedMessage { class OwnedDHTRecordPointer extends $pb.GeneratedMessage {
factory OwnedDHTRecordPointer() => create(); factory OwnedDHTRecordPointer() => create();
OwnedDHTRecordPointer._() : super(); OwnedDHTRecordPointer._() : super();

View file

@ -60,51 +60,6 @@ final $typed_data.Uint8List dHTShortArrayDescriptor = $convert.base64Decode(
'Cg1ESFRTaG9ydEFycmF5EiQKBGtleXMYASADKAsyEC52ZWlsaWQuVHlwZWRLZXlSBGtleXMSFA' 'Cg1ESFRTaG9ydEFycmF5EiQKBGtleXMYASADKAsyEC52ZWlsaWQuVHlwZWRLZXlSBGtleXMSFA'
'oFaW5kZXgYAiABKAxSBWluZGV4EhIKBHNlcXMYAyADKA1SBHNlcXM='); 'oFaW5kZXgYAiABKAxSBWluZGV4EhIKBHNlcXMYAyADKA1SBHNlcXM=');
@$core.Deprecated('Use dHTDataReferenceDescriptor instead')
const DHTDataReference$json = {
'1': 'DHTDataReference',
'2': [
{'1': 'dht_data', '3': 1, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'dhtData'},
{'1': 'hash', '3': 2, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'hash'},
],
};
/// Descriptor for `DHTDataReference`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List dHTDataReferenceDescriptor = $convert.base64Decode(
'ChBESFREYXRhUmVmZXJlbmNlEisKCGRodF9kYXRhGAEgASgLMhAudmVpbGlkLlR5cGVkS2V5Ug'
'dkaHREYXRhEiQKBGhhc2gYAiABKAsyEC52ZWlsaWQuVHlwZWRLZXlSBGhhc2g=');
@$core.Deprecated('Use blockStoreDataReferenceDescriptor instead')
const BlockStoreDataReference$json = {
'1': 'BlockStoreDataReference',
'2': [
{'1': 'block', '3': 1, '4': 1, '5': 11, '6': '.veilid.TypedKey', '10': 'block'},
],
};
/// Descriptor for `BlockStoreDataReference`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List blockStoreDataReferenceDescriptor = $convert.base64Decode(
'ChdCbG9ja1N0b3JlRGF0YVJlZmVyZW5jZRImCgVibG9jaxgBIAEoCzIQLnZlaWxpZC5UeXBlZE'
'tleVIFYmxvY2s=');
@$core.Deprecated('Use dataReferenceDescriptor instead')
const DataReference$json = {
'1': 'DataReference',
'2': [
{'1': 'dht_data', '3': 1, '4': 1, '5': 11, '6': '.dht.DHTDataReference', '9': 0, '10': 'dhtData'},
{'1': 'block_store_data', '3': 2, '4': 1, '5': 11, '6': '.dht.BlockStoreDataReference', '9': 0, '10': 'blockStoreData'},
],
'8': [
{'1': 'kind'},
],
};
/// Descriptor for `DataReference`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List dataReferenceDescriptor = $convert.base64Decode(
'Cg1EYXRhUmVmZXJlbmNlEjIKCGRodF9kYXRhGAEgASgLMhUuZGh0LkRIVERhdGFSZWZlcmVuY2'
'VIAFIHZGh0RGF0YRJIChBibG9ja19zdG9yZV9kYXRhGAIgASgLMhwuZGh0LkJsb2NrU3RvcmVE'
'YXRhUmVmZXJlbmNlSABSDmJsb2NrU3RvcmVEYXRhQgYKBGtpbmQ=');
@$core.Deprecated('Use ownedDHTRecordPointerDescriptor instead') @$core.Deprecated('Use ownedDHTRecordPointerDescriptor instead')
const OwnedDHTRecordPointer$json = { const OwnedDHTRecordPointer$json = {
'1': 'OwnedDHTRecordPointer', '1': 'OwnedDHTRecordPointer',

View file

@ -52,11 +52,10 @@ packages:
bloc_advanced_tools: bloc_advanced_tools:
dependency: "direct main" dependency: "direct main"
description: description:
name: bloc_advanced_tools path: "../../../bloc_advanced_tools"
sha256: "0cf9b3a73a67addfe22ec3f97a1ac240f6ad53870d6b21a980260f390d7901cd" relative: true
url: "https://pub.dev" source: path
source: hosted version: "0.1.3"
version: "0.1.2"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:

View file

@ -9,7 +9,7 @@ environment:
dependencies: dependencies:
async_tools: ^0.1.2 async_tools: ^0.1.2
bloc: ^8.1.4 bloc: ^8.1.4
bloc_advanced_tools: ^0.1.2 bloc_advanced_tools: ^0.1.3
charcode: ^1.3.1 charcode: ^1.3.1
collection: ^1.18.0 collection: ^1.18.0
equatable: ^2.0.5 equatable: ^2.0.5
@ -24,11 +24,11 @@ dependencies:
# veilid: ^0.0.1 # veilid: ^0.0.1
path: ../../../veilid/veilid-flutter path: ../../../veilid/veilid-flutter
# dependency_overrides: dependency_overrides:
# async_tools: # async_tools:
# path: ../../../dart_async_tools # path: ../../../dart_async_tools
# bloc_advanced_tools: bloc_advanced_tools:
# path: ../../../bloc_advanced_tools path: ../../../bloc_advanced_tools
dev_dependencies: dev_dependencies:
build_runner: ^2.4.10 build_runner: ^2.4.10

View file

@ -100,11 +100,10 @@ packages:
bloc_advanced_tools: bloc_advanced_tools:
dependency: "direct main" dependency: "direct main"
description: description:
name: bloc_advanced_tools path: "../bloc_advanced_tools"
sha256: "0cf9b3a73a67addfe22ec3f97a1ac240f6ad53870d6b21a980260f390d7901cd" relative: true
url: "https://pub.dev" source: path
source: hosted version: "0.1.3"
version: "0.1.2"
blurry_modal_progress_hud: blurry_modal_progress_hud:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -16,7 +16,7 @@ dependencies:
badges: ^3.1.2 badges: ^3.1.2
basic_utils: ^5.7.0 basic_utils: ^5.7.0
bloc: ^8.1.4 bloc: ^8.1.4
bloc_advanced_tools: ^0.1.2 bloc_advanced_tools: ^0.1.3
blurry_modal_progress_hud: ^1.1.1 blurry_modal_progress_hud: ^1.1.1
change_case: ^2.1.0 change_case: ^2.1.0
charcode: ^1.3.1 charcode: ^1.3.1
@ -93,11 +93,11 @@ dependencies:
xterm: ^4.0.0 xterm: ^4.0.0
zxing2: ^0.2.3 zxing2: ^0.2.3
# dependency_overrides: dependency_overrides:
# async_tools: # async_tools:
# path: ../dart_async_tools # path: ../dart_async_tools
# bloc_advanced_tools: bloc_advanced_tools:
# path: ../bloc_advanced_tools path: ../bloc_advanced_tools
# flutter_chat_ui: # flutter_chat_ui:
# path: ../flutter_chat_ui # path: ../flutter_chat_ui