checkpoint

This commit is contained in:
Christien Rioux 2024-01-04 22:29:43 -05:00
parent c516323e7d
commit 31f562119a
70 changed files with 1174 additions and 817 deletions

View File

@ -1,5 +1,10 @@
#!/bin/bash
set -e
pushd packages/veilid_support > /dev/null
./build.sh
popd > /dev/null
dart run build_runner build --delete-conflicting-outputs
pushd lib > /dev/null

View File

@ -2,7 +2,9 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../models/models.dart';
import '../../repository/account_repository/account_repository.dart';
part 'local_accounts_state.dart';

View File

@ -1,3 +1,14 @@
part of 'local_accounts_cubit.dart';
typedef LocalAccountsState = IList<LocalAccount>;
extension LocalAccountsStateExt on LocalAccountsState {
LocalAccount? fetchLocalAccount({required TypedKey accountMasterRecordKey}) {
final idx = indexWhere(
(e) => e.identityMaster.masterRecordKey == accountMasterRecordKey);
if (idx == -1) {
return null;
}
return this[idx];
}
}

View File

@ -2,7 +2,9 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../models/models.dart';
import '../../repository/account_repository/account_repository.dart';
part 'user_logins_state.dart';

View File

@ -1,3 +1,14 @@
part of 'user_logins_cubit.dart';
typedef UserLoginsState = IList<UserLogin>;
extension UserLoginsStateExt on UserLoginsState {
UserLogin? fetchUserLogin({required TypedKey accountMasterRecordKey}) {
final idx =
indexWhere((e) => e.accountMasterRecordKey == accountMasterRecordKey);
if (idx == -1) {
return null;
}
return this[idx];
}
}

View File

@ -9,7 +9,7 @@ import 'dart:typed_data';
import 'package:change_case/change_case.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../../../proto/proto.dart' as proto;
import '../../../proto/proto.dart' as proto;
enum EncryptionKeyType {
none,

View File

@ -3,7 +3,7 @@ import 'dart:typed_data';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:veilid_support/veilid_support.dart';
import 'encryption_key_type.dart';
import '../../models/encryption_key_type.dart';
part 'local_account.g.dart';
part 'local_account.freezed.dart';

View File

@ -0,0 +1,4 @@
export 'encryption_key_type.dart';
export 'local_account/local_account.dart';
export 'new_profile_spec.dart';
export 'user_login/user_login.dart';

View File

@ -2,16 +2,8 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../../../proto/proto.dart' as proto;
import '../../models/models.dart';
import 'active_logins.dart';
import 'encryption_key_type.dart';
import 'local_account.dart';
import 'new_profile_spec.dart';
import 'user_login.dart';
export 'active_logins.dart';
export 'encryption_key_type.dart';
export 'local_account.dart';
export 'user_login.dart';
const String veilidChatAccountKey = 'com.veilid.veilidchat';
@ -73,7 +65,7 @@ class AccountRepository {
return localAccounts[idx];
}
UserLogin? fetchLogin({required TypedKey accountMasterRecordKey}) {
UserLogin? fetchUserLogin({required TypedKey accountMasterRecordKey}) {
final userLogins = _activeLogins.requireValue.userLogins;
final idx = userLogins
.indexWhere((e) => e.accountMasterRecordKey == accountMasterRecordKey);

View File

@ -3,7 +3,7 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:veilid_support/veilid_support.dart';
import 'user_login.dart';
import '../../models/models.dart';
part 'active_logins.g.dart';
part 'active_logins.freezed.dart';

View File

@ -5,12 +5,12 @@ 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';
import 'package:go_router/go_router.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../../components/default_app_bar.dart';
import '../../../components/signal_strength_meter.dart';
import '../../../entities/entities.dart';
import '../../../layout/default_app_bar.dart';
import '../../../tools/tools.dart';
import '../../../veilid_processor/veilid_processor.dart';
import '../../account_manager.dart';
import '../../models/models.dart';
class NewAccountPage extends StatefulWidget {
const NewAccountPage({super.key});
@ -95,8 +95,7 @@ class NewAccountPageState extends State<NewAccountPage> {
@override
Widget build(BuildContext context) {
final displayModalHUD =
isInAsyncCall || !localAccounts.hasValue || !logins.hasValue;
final displayModalHUD = isInAsyncCall;
return Scaffold(
// resizeToAvoidBottomInset: false,
@ -116,7 +115,16 @@ class NewAccountPageState extends State<NewAccountPage> {
onSubmit: (formKey) async {
FocusScope.of(context).unfocus();
try {
await createAccount();
final name =
_formKey.currentState!.fields[formFieldName]!.value as String;
final pronouns = _formKey.currentState!.fields[formFieldPronouns]!
.value as String? ??
'';
final newProfileSpec =
NewProfileSpec(name: name, pronouns: pronouns);
await AccountRepository.instance
.createMasterIdentity(newProfileSpec);
} on Exception catch (e) {
if (context.mounted) {
await showErrorModal(context, translate('new_account_page.error'),

View File

@ -1,35 +1,29 @@
import 'dart:async';
import 'app.dart';
import 'local_account_manager/account_manager.dart';
import 'processor.dart';
import 'tools/tools.dart';
import '../packages/veilid_support/veilid_support.dart';
import 'package:flutter/foundation.dart';
import 'package:veilid_support/veilid_support.dart';
final Completer<Veilid> eventualVeilid = Completer<Veilid>();
final Processor processor = Processor();
import 'account_manager/account_manager.dart';
import 'app.dart';
import 'tools/tools.dart';
import 'veilid_processor/veilid_processor.dart';
final Completer<void> eventualInitialized = Completer<void>();
// Initialize Veilid
Future<void> initializeVeilid() async {
// Ensure this runs only once
if (eventualVeilid.isCompleted) {
return;
}
// Init Veilid
Veilid.instance
.initializeVeilidCore(getDefaultVeilidPlatformConfig(VeilidChatApp.name));
Veilid.instance.initializeVeilidCore(
getDefaultVeilidPlatformConfig(kIsWeb, VeilidChatApp.name));
// Veilid logging
initVeilidLog();
initVeilidLog(kDebugMode);
// DHT Record Pool
await DHTRecordPool.init();
// Startup Veilid
await processor.startup();
// Share the initialized veilid instance to the rest of the app
eventualVeilid.complete(Veilid.instance);
await ProcessorRepository.instance.startup();
}
// Initialize repositories
@ -38,9 +32,9 @@ Future<void> initializeRepositories() async {
}
Future<void> initializeVeilidChat() async {
log.info("Initializing Veilid");
log.info('Initializing Veilid');
await initializeVeilid();
log.info("Initializing Repositories");
log.info('Initializing Repositories');
await initializeRepositories();
eventualInitialized.complete();

235
lib/layout/home.dart Normal file
View File

@ -0,0 +1,235 @@
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:go_router/go_router.dart';
import '../account_manager/account_manager.dart';
import '../../proto/proto.dart' as proto;
import '../theme/theme.dart';
import '../tools/tools.dart';
import 'main_pager/main_pager.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
HomePageState createState() => HomePageState();
static Widget buildChatComponent() {
final contactList = ref.watch(fetchContactListProvider).asData?.value ??
const IListConst([]);
final activeChat = ref.watch(activeChatStateProvider);
if (activeChat == null) {
return const EmptyChatWidget();
}
final activeAccountInfo =
ref.watch(fetchActiveAccountProvider).asData?.value;
if (activeAccountInfo == null) {
return const EmptyChatWidget();
}
final activeChatContactIdx = contactList.indexWhere(
(c) =>
proto.TypedKeyProto.fromProto(c.remoteConversationRecordKey) ==
activeChat,
);
if (activeChatContactIdx == -1) {
ref.read(activeChatStateProvider.notifier).state = null;
return const EmptyChatWidget();
}
final activeChatContact = contactList[activeChatContactIdx];
return ChatComponent(
activeAccountInfo: activeAccountInfo,
activeChat: activeChat,
activeChatContact: activeChatContact);
}
}
class HomePageState extends State<HomePage> with TickerProviderStateMixin {
final _unfocusNode = FocusNode();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await changeWindowSetup(
TitleBarStyle.normal, OrientationCapability.normal);
});
}
@override
void dispose() {
_unfocusNode.dispose();
super.dispose();
}
// ignore: prefer_expression_function_bodies
Widget buildAccountList() {
return const Column(children: [
Center(child: Text('Small Profile')),
Center(child: Text('Contact invitations')),
Center(child: Text('Contacts'))
]);
}
Widget buildUnlockAccount(
BuildContext context,
IList<LocalAccount> localAccounts,
// ignore: prefer_expression_function_bodies
) {
return const Center(child: Text('unlock account'));
}
/// We have an active, unlocked, user login
Widget buildReadyAccount(
BuildContext context,
IList<LocalAccount> localAccounts,
TypedKey activeUserLogin,
proto.Account account) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
return Column(children: <Widget>[
Row(children: [
IconButton(
icon: const Icon(Icons.settings),
color: scale.secondaryScale.text,
constraints: const BoxConstraints.expand(height: 64, width: 64),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(scale.secondaryScale.border),
shape: MaterialStateProperty.all(const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16))))),
tooltip: translate('app_bar.settings_tooltip'),
onPressed: () async {
context.go('/home/settings');
}).paddingLTRB(0, 0, 8, 0),
ProfileWidget(
name: account.profile.name,
pronouns: account.profile.pronouns,
).expanded(),
]).paddingAll(8),
MainPager(
localAccounts: localAccounts,
activeUserLogin: activeUserLogin,
account: account)
.expanded()
]);
}
Widget buildUserPanel() => Builder(builder: (context) {
final activeUserLogin = context.watch<ActiveUserLoginCubit>().state;
final localAccounts = context.watch<LocalAccountsCubit>().state;
if (activeUserLogin == null) {
// If no logged in user is active, show the loading panel
return waitingPage(context);
}
final accountV = ref.watch(
fetchAccountProvider(accountMasterRecordKey: activeUserLogin));
if (!accountV.hasValue) {
return waitingPage(context);
}
final account = accountV.requireValue;
switch (account.status) {
case AccountInfoStatus.noAccount:
Future.delayed(0.ms, () async {
await showErrorModal(
context,
translate('home.missing_account_title'),
translate('home.missing_account_text'));
// Delete account
await ref
.read(localAccountsProvider.notifier)
.deleteLocalAccount(activeUserLogin);
// Switch to no active user login
await ref.read(loginsProvider.notifier).switchToAccount(null);
});
return waitingPage(context);
case AccountInfoStatus.accountInvalid:
Future.delayed(0.ms, () async {
await showErrorModal(
context,
translate('home.invalid_account_title'),
translate('home.invalid_account_text'));
// Delete account
await ref
.read(localAccountsProvider.notifier)
.deleteLocalAccount(activeUserLogin);
// Switch to no active user login
await ref.read(loginsProvider.notifier).switchToAccount(null);
});
return waitingPage(context);
case AccountInfoStatus.accountLocked:
// Show unlock widget
return buildUnlockAccount(context, localAccounts);
case AccountInfoStatus.accountReady:
return buildReadyAccount(
context,
localAccounts,
activeUserLogin,
account.account!,
);
}
});
Widget buildPhone() =>
Material(color: Colors.transparent, child: buildUserPanel());
Widget buildTabletLeftPane() =>
Material(color: Colors.transparent, child: buildUserPanel());
Widget buildTabletRightPane() => HomePage.buildChatComponent();
// ignore: prefer_expression_function_bodies
Widget buildTablet() => Builder(builder: (context) {
final w = MediaQuery.of(context).size.width;
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
final children = [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 300, maxWidth: 300),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: w / 2),
child: buildTabletLeftPane())),
SizedBox(
width: 2,
height: double.infinity,
child: ColoredBox(color: scale.primaryScale.hoverBorder)),
Expanded(child: buildTabletRightPane()),
];
return Row(
children: children,
);
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
return SafeArea(
child: GestureDetector(
onTap: () => FocusScope.of(context).requestFocus(_unfocusNode),
child: DecoratedBox(
decoration: BoxDecoration(
color: scale.primaryScale.activeElementBackground),
child: responsiveVisibility(
context: context,
phone: false,
)
? buildTablet()
: buildPhone(),
)));
}
}

1
lib/layout/layout.dart Normal file
View File

@ -0,0 +1 @@

View File

@ -1,66 +0,0 @@
import 'package:flutter/material.dart';
import 'package:signal_strength_indicator/signal_strength_indicator.dart';
import 'package:veilid_support/veilid_support.dart';
import '../providers/connection_state.dart';
import '../tools/tools.dart';
xxx move to feature level
class SignalStrengthMeterWidget extends Widget {
const SignalStrengthMeterWidget({super.key});
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
const iconSize = 16.0;
final connState = ref.watch(connectionStateProvider);
late final double value;
late final Color color;
late final Color inactiveColor;
switch (connState.attachment.state) {
case AttachmentState.detached:
return Icon(Icons.signal_cellular_nodata,
size: iconSize, color: scale.grayScale.text);
case AttachmentState.detaching:
return Icon(Icons.signal_cellular_off,
size: iconSize, color: scale.grayScale.text);
case AttachmentState.attaching:
value = 0;
color = scale.primaryScale.text;
case AttachmentState.attachedWeak:
value = 1;
color = scale.primaryScale.text;
case AttachmentState.attachedStrong:
value = 2;
color = scale.primaryScale.text;
case AttachmentState.attachedGood:
value = 3;
color = scale.primaryScale.text;
case AttachmentState.fullyAttached:
value = 4;
color = scale.primaryScale.text;
case AttachmentState.overAttached:
value = 4;
color = scale.secondaryScale.subtleText;
}
inactiveColor = scale.grayScale.subtleText;
return GestureDetector(
onLongPress: () async {
await context.push('/developer');
},
child: SignalStrengthIndicator.bars(
value: value,
activeColor: color,
inactiveColor: inactiveColor,
size: iconSize,
barCount: 4,
spacing: 1,
));
}
}

View File

@ -1,269 +0,0 @@
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:go_router/go_router.dart';
import '../../proto/proto.dart' as proto;
import '../../components/chat_component.dart';
import '../../components/empty_chat_widget.dart';
import '../../components/profile_widget.dart';
import '../../entities/local_account.dart';
import '../providers/account.dart';
import '../providers/chat.dart';
import '../providers/contact.dart';
import '../../local_accounts/local_accounts.dart';
import '../providers/logins.dart';
import '../providers/window_control.dart';
import '../../tools/tools.dart';
import '../../../packages/veilid_support/veilid_support.dart';
import 'main_pager/main_pager.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
HomePageState createState() => HomePageState();
static Widget buildChatComponent(BuildContext context, WidgetRef ref) {
final contactList = ref.watch(fetchContactListProvider).asData?.value ??
const IListConst([]);
final activeChat = ref.watch(activeChatStateProvider);
if (activeChat == null) {
return const EmptyChatWidget();
}
final activeAccountInfo =
ref.watch(fetchActiveAccountProvider).asData?.value;
if (activeAccountInfo == null) {
return const EmptyChatWidget();
}
final activeChatContactIdx = contactList.indexWhere(
(c) =>
proto.TypedKeyProto.fromProto(c.remoteConversationRecordKey) ==
activeChat,
);
if (activeChatContactIdx == -1) {
ref.read(activeChatStateProvider.notifier).state = null;
return const EmptyChatWidget();
}
final activeChatContact = contactList[activeChatContactIdx];
return ChatComponent(
activeAccountInfo: activeAccountInfo,
activeChat: activeChat,
activeChatContact: activeChatContact);
}
}
class HomePageState extends ConsumerState<HomePage>
with TickerProviderStateMixin {
final _unfocusNode = FocusNode();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
setState(() {});
await ref.read(windowControlProvider.notifier).changeWindowSetup(
TitleBarStyle.normal, OrientationCapability.normal);
});
}
@override
void dispose() {
_unfocusNode.dispose();
super.dispose();
}
// ignore: prefer_expression_function_bodies
Widget buildAccountList() {
return const Column(children: [
Center(child: Text('Small Profile')),
Center(child: Text('Contact invitations')),
Center(child: Text('Contacts'))
]);
}
Widget buildUnlockAccount(
BuildContext context,
IList<LocalAccount> localAccounts,
// ignore: prefer_expression_function_bodies
) {
return const Center(child: Text('unlock account'));
}
/// We have an active, unlocked, user login
Widget buildReadyAccount(
BuildContext context,
IList<LocalAccount> localAccounts,
TypedKey activeUserLogin,
proto.Account account) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
return Column(children: <Widget>[
Row(children: [
IconButton(
icon: const Icon(Icons.settings),
color: scale.secondaryScale.text,
constraints: const BoxConstraints.expand(height: 64, width: 64),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(scale.secondaryScale.border),
shape: MaterialStateProperty.all(const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16))))),
tooltip: translate('app_bar.settings_tooltip'),
onPressed: () async {
context.go('/home/settings');
}).paddingLTRB(0, 0, 8, 0),
ProfileWidget(
name: account.profile.name,
pronouns: account.profile.pronouns,
).expanded(),
]).paddingAll(8),
MainPager(
localAccounts: localAccounts,
activeUserLogin: activeUserLogin,
account: account)
.expanded()
]);
}
Widget buildUserPanel() {
final localAccountsV = ref.watch(localAccountsProvider);
final loginsV = ref.watch(loginsProvider);
if (!localAccountsV.hasValue || !loginsV.hasValue) {
return waitingPage(context);
}
final localAccounts = localAccountsV.requireValue;
final logins = loginsV.requireValue;
final activeUserLogin = logins.activeUserLogin;
if (activeUserLogin == null) {
// If no logged in user is active, show the list of account
return buildAccountList();
}
final accountV = ref
.watch(fetchAccountProvider(accountMasterRecordKey: activeUserLogin));
if (!accountV.hasValue) {
return waitingPage(context);
}
final account = accountV.requireValue;
switch (account.status) {
case AccountInfoStatus.noAccount:
Future.delayed(0.ms, () async {
await showErrorModal(context, translate('home.missing_account_title'),
translate('home.missing_account_text'));
// Delete account
await ref
.read(localAccountsProvider.notifier)
.deleteLocalAccount(activeUserLogin);
// Switch to no active user login
await ref.read(loginsProvider.notifier).switchToAccount(null);
});
return waitingPage(context);
case AccountInfoStatus.accountInvalid:
Future.delayed(0.ms, () async {
await showErrorModal(context, translate('home.invalid_account_title'),
translate('home.invalid_account_text'));
// Delete account
await ref
.read(localAccountsProvider.notifier)
.deleteLocalAccount(activeUserLogin);
// Switch to no active user login
await ref.read(loginsProvider.notifier).switchToAccount(null);
});
return waitingPage(context);
case AccountInfoStatus.accountLocked:
// Show unlock widget
return buildUnlockAccount(context, localAccounts);
case AccountInfoStatus.accountReady:
return buildReadyAccount(
context,
localAccounts,
activeUserLogin,
account.account!,
);
}
}
// ignore: prefer_expression_function_bodies
Widget buildPhone(BuildContext context) {
return Material(color: Colors.transparent, child: buildUserPanel());
}
// ignore: prefer_expression_function_bodies
Widget buildTabletLeftPane(BuildContext context) {
//
return Material(color: Colors.transparent, child: buildUserPanel());
}
// ignore: prefer_expression_function_bodies
Widget buildTabletRightPane(BuildContext context) {
//
return HomePage.buildChatComponent(context, ref);
}
// ignore: prefer_expression_function_bodies
Widget buildTablet(BuildContext context) {
final w = MediaQuery.of(context).size.width;
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
final children = [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 300, maxWidth: 300),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: w / 2),
child: buildTabletLeftPane(context))),
SizedBox(
width: 2,
height: double.infinity,
child: ColoredBox(color: scale.primaryScale.hoverBorder)),
Expanded(child: buildTabletRightPane(context)),
];
return Row(
children: children,
);
// final theme = MultiSplitViewTheme(
// data: isDesktop
// ? MultiSplitViewThemeData(
// dividerThickness: 1,
// dividerPainter: DividerPainters.grooved2(thickness: 1))
// : MultiSplitViewThemeData(
// dividerThickness: 3,
// dividerPainter: DividerPainters.grooved2(thickness: 1)),
// child: multiSplitView);
}
@override
Widget build(BuildContext context) {
ref.watch(windowControlProvider);
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
return SafeArea(
child: GestureDetector(
onTap: () => FocusScope.of(context).requestFocus(_unfocusNode),
child: DecoratedBox(
decoration: BoxDecoration(
color: scale.primaryScale.activeElementBackground),
child: responsiveVisibility(
context: context,
phone: false,
)
? buildTablet(context)
: buildPhone(context),
)));
}
}

View File

@ -1,29 +0,0 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../../packages/veilid_support/veilid_support.dart';
part 'connection_state.freezed.dart';
@freezed
class ConnectionState with _$ConnectionState {
const factory ConnectionState({
required VeilidStateAttachment attachment,
}) = _ConnectionState;
const ConnectionState._();
bool get isAttached => !(attachment.state == AttachmentState.detached ||
attachment.state == AttachmentState.detaching ||
attachment.state == AttachmentState.attaching);
bool get isPublicInternetReady => attachment.publicInternetReady;
}
final connectionState = StateController<ConnectionState>(const ConnectionState(
attachment: VeilidStateAttachment(
state: AttachmentState.detached,
publicInternetReady: false,
localNetworkReady: false)));
final connectionStateProvider =
StateNotifierProvider<StateController<ConnectionState>, ConnectionState>(
(ref) => connectionState);

View File

@ -1,138 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'connection_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
/// @nodoc
mixin _$ConnectionState {
VeilidStateAttachment get attachment => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ConnectionStateCopyWith<ConnectionState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ConnectionStateCopyWith<$Res> {
factory $ConnectionStateCopyWith(
ConnectionState value, $Res Function(ConnectionState) then) =
_$ConnectionStateCopyWithImpl<$Res, ConnectionState>;
@useResult
$Res call({VeilidStateAttachment attachment});
}
/// @nodoc
class _$ConnectionStateCopyWithImpl<$Res, $Val extends ConnectionState>
implements $ConnectionStateCopyWith<$Res> {
_$ConnectionStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? attachment = freezed,
}) {
return _then(_value.copyWith(
attachment: freezed == attachment
? _value.attachment
: attachment // ignore: cast_nullable_to_non_nullable
as VeilidStateAttachment,
) as $Val);
}
}
/// @nodoc
abstract class _$$ConnectionStateImplCopyWith<$Res>
implements $ConnectionStateCopyWith<$Res> {
factory _$$ConnectionStateImplCopyWith(_$ConnectionStateImpl value,
$Res Function(_$ConnectionStateImpl) then) =
__$$ConnectionStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({VeilidStateAttachment attachment});
}
/// @nodoc
class __$$ConnectionStateImplCopyWithImpl<$Res>
extends _$ConnectionStateCopyWithImpl<$Res, _$ConnectionStateImpl>
implements _$$ConnectionStateImplCopyWith<$Res> {
__$$ConnectionStateImplCopyWithImpl(
_$ConnectionStateImpl _value, $Res Function(_$ConnectionStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? attachment = freezed,
}) {
return _then(_$ConnectionStateImpl(
attachment: freezed == attachment
? _value.attachment
: attachment // ignore: cast_nullable_to_non_nullable
as VeilidStateAttachment,
));
}
}
/// @nodoc
class _$ConnectionStateImpl extends _ConnectionState {
const _$ConnectionStateImpl({required this.attachment}) : super._();
@override
final VeilidStateAttachment attachment;
@override
String toString() {
return 'ConnectionState(attachment: $attachment)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ConnectionStateImpl &&
const DeepCollectionEquality()
.equals(other.attachment, attachment));
}
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(attachment));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ConnectionStateImplCopyWith<_$ConnectionStateImpl> get copyWith =>
__$$ConnectionStateImplCopyWithImpl<_$ConnectionStateImpl>(
this, _$identity);
}
abstract class _ConnectionState extends ConnectionState {
const factory _ConnectionState(
{required final VeilidStateAttachment attachment}) =
_$ConnectionStateImpl;
const _ConnectionState._() : super._();
@override
VeilidStateAttachment get attachment;
@override
@JsonKey(ignore: true)
_$$ConnectionStateImplCopyWith<_$ConnectionStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -1,103 +0,0 @@
import 'dart:async';
import 'package:veilid/veilid.dart';
import 'app.dart';
import 'old_to_refactor/providers/connection_state.dart';
import 'tools/tools.dart';
import '../packages/veilid_support/src/config.dart';
import '../packages/veilid_support/src/veilid_log.dart';
class Processor {
Processor();
String _veilidVersion = '';
bool _startedUp = false;
Stream<VeilidUpdate>? _updateStream;
Future<void>? _updateProcessor;
Future<void> startup() async {
if (_startedUp) {
return;
}
try {
_veilidVersion = Veilid.instance.veilidVersionString();
} on Exception {
_veilidVersion = 'Failed to get veilid version.';
}
log.info('Veilid version: $_veilidVersion');
// HACK: In case of hot restart shut down first
try {
await Veilid.instance.shutdownVeilidCore();
} on Exception {
// Do nothing on failure here
}
final updateStream = await Veilid.instance
.startupVeilidCore(await getVeilidConfig(VeilidChatApp.name));
_updateStream = updateStream;
_updateProcessor = processUpdates();
_startedUp = true;
await Veilid.instance.attach();
}
Future<void> shutdown() async {
if (!_startedUp) {
return;
}
await Veilid.instance.shutdownVeilidCore();
if (_updateProcessor != null) {
await _updateProcessor;
}
_updateProcessor = null;
_updateStream = null;
_startedUp = false;
}
Future<void> processUpdateAttachment(
VeilidUpdateAttachment updateAttachment) async {
//loggy.info("Attachment: ${updateAttachment.json}");
// // Set connection meter and ui state for connection state
connectionState.state = ConnectionState(
attachment: VeilidStateAttachment(
state: updateAttachment.state,
publicInternetReady: updateAttachment.publicInternetReady,
localNetworkReady: updateAttachment.localNetworkReady));
}
Future<void> processUpdateConfig(VeilidUpdateConfig updateConfig) async {
//loggy.info("Config: ${updateConfig.json}");
}
Future<void> processUpdateNetwork(VeilidUpdateNetwork updateNetwork) async {
//loggy.info("Network: ${updateNetwork.json}");
}
Future<void> processUpdates() async {
final stream = _updateStream;
if (stream != null) {
await for (final update in stream) {
if (update is VeilidLog) {
await processLog(update);
} else if (update is VeilidUpdateAttachment) {
await processUpdateAttachment(update);
} else if (update is VeilidUpdateConfig) {
await processUpdateConfig(update);
} else if (update is VeilidUpdateNetwork) {
await processUpdateNetwork(update);
} else if (update is VeilidAppMessage) {
log.info('AppMessage: ${update.toJson()}');
} else if (update is VeilidAppCall) {
log.info('AppCall: ${update.toJson()}');
} else {
log.trace('Update: ${update.toJson()}');
}
}
}
}
}

View File

@ -5,15 +5,14 @@ import 'package:flutter/widgets.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:go_router/go_router.dart';
import '../../../account_manager/account_manager.dart';
import '../../init.dart';
import '../../local_account_manager/respository/account_repository/account_repository.dart';
import '../../old_to_refactor/pages/chat_only.dart';
import '../../old_to_refactor/pages/developer.dart';
import '../../old_to_refactor/pages/home.dart';
import '../../old_to_refactor/pages/index.dart';
import '../../account_manager/view/new_account_page/new_account_page.dart';
import '../../old_to_refactor/pages/settings.dart';
import '../../tools/tools.dart';
import '../../veilid_processor/views/developer.dart';
part 'router_cubit.freezed.dart';
part 'router_cubit.g.dart';

View File

@ -0,0 +1,4 @@
export 'scale_color.dart';
export 'scale_scheme.dart';
export 'theme_preference.dart';
export 'radix_generator.dart';

View File

@ -5,8 +5,7 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'radix_generator.dart';
import 'theme_preference.dart';
import '../models/models.dart';
////////////////////////////////////////////////////////////////////////

View File

@ -1,3 +1,2 @@
export 'scale_scheme.dart';
export 'theme_preference.dart';
export 'theme_repository.dart';
export 'models/models.dart';
export 'repository/theme_repository.dart';

View File

@ -1,18 +1,11 @@
// XXX Eliminate this when we have ValueChanged
import 'dart:async';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:veilid_support/veilid_support.dart';
import 'init.dart';
import 'old_to_refactor/providers/account.dart';
import 'old_to_refactor/providers/chat.dart';
import 'old_to_refactor/providers/connection_state.dart';
import 'old_to_refactor/providers/contact.dart';
import 'old_to_refactor/providers/contact_invite.dart';
import 'old_to_refactor/providers/conversation.dart';
import 'proto/proto.dart' as proto;
import 'veilid_processor/veilid_processor.dart';
const int ticksPerContactInvitationCheck = 5;
const int ticksPerNewMessageCheck = 5;
@ -35,6 +28,9 @@ class BackgroundTicker extends StatefulWidget {
class BackgroundTickerState extends State<BackgroundTicker> {
Timer? _tickTimer;
bool _inTick = false;
bool _inDoContactInvitationCheck = false;
bool _inDoNewMessageCheck = false;
int _contactInvitationCheckTick = 0;
int _newMessageCheckTick = 0;
@ -65,32 +61,38 @@ class BackgroundTickerState extends State<BackgroundTicker> {
}
Future<void> _onTick() async {
// Don't tick until veilid is started and attached
if (!eventualVeilid.isCompleted) {
// Don't tick until we are initialized
if (!eventualInitialized.isCompleted) {
return;
}
if (!connectionState.state.isAttached) {
if (!ProcessorRepository
.instance.processorConnectionState.isPublicInternetReady) {
return;
}
_inTick = true;
try {
final unord = <Future<void>>[];
// Tick DHT record pool
if (!DHTRecordPool.instance.inTick) {
unawaited(DHTRecordPool.instance.tick());
}
// Check extant contact invitations once every N seconds
_contactInvitationCheckTick += 1;
if (_contactInvitationCheckTick >= ticksPerContactInvitationCheck) {
_contactInvitationCheckTick = 0;
unord.add(_doContactInvitationCheck());
if (!_inDoContactInvitationCheck) {
unawaited(_doContactInvitationCheck());
}
}
// Check new messages once every N seconds
_newMessageCheckTick += 1;
if (_newMessageCheckTick >= ticksPerNewMessageCheck) {
_newMessageCheckTick = 0;
unord.add(_doNewMessageCheck());
}
if (unord.isNotEmpty) {
await Future.wait(unord);
if (!_inDoNewMessageCheck) {
unawaited(_doNewMessageCheck());
}
}
} finally {
_inTick = false;
@ -98,96 +100,118 @@ class BackgroundTickerState extends State<BackgroundTicker> {
}
Future<void> _doContactInvitationCheck() async {
if (!connectionState.state.isPublicInternetReady) {
return;
}
final contactInvitationRecords =
await ref.read(fetchContactInvitationRecordsProvider.future);
if (contactInvitationRecords == null) {
return;
}
final activeAccountInfo = await ref.read(fetchActiveAccountProvider.future);
if (activeAccountInfo == null) {
if (_inDoContactInvitationCheck) {
return;
}
_inDoContactInvitationCheck = true;
final allChecks = <Future<void>>[];
for (final contactInvitationRecord in contactInvitationRecords) {
allChecks.add(() async {
final acceptReject = await checkAcceptRejectContact(
activeAccountInfo: activeAccountInfo,
contactInvitationRecord: contactInvitationRecord);
if (acceptReject != null) {
final acceptedContact = acceptReject.acceptedContact;
if (acceptedContact != null) {
// Accept
await createContact(
activeAccountInfo: activeAccountInfo,
profile: acceptedContact.profile,
remoteIdentity: acceptedContact.remoteIdentity,
remoteConversationRecordKey:
acceptedContact.remoteConversationRecordKey,
localConversationRecordKey:
acceptedContact.localConversationRecordKey,
);
ref
..invalidate(fetchContactInvitationRecordsProvider)
..invalidate(fetchContactListProvider);
} else {
// Reject
ref.invalidate(fetchContactInvitationRecordsProvider);
}
}
}());
if (!ProcessorRepository
.instance.processorConnectionState.isPublicInternetReady) {
return;
}
// final contactInvitationRecords =
// await ref.read(fetchContactInvitationRecordsProvider.future);
// if (contactInvitationRecords == null) {
// return;
// }
try {
// final activeAccountInfo =
// await ref.read(fetchActiveAccountProvider.future);
// if (activeAccountInfo == null) {
// return;
// }
// final allChecks = <Future<void>>[];
// for (final contactInvitationRecord in contactInvitationRecords) {
// allChecks.add(() async {
// final acceptReject = await checkAcceptRejectContact(
// activeAccountInfo: activeAccountInfo,
// contactInvitationRecord: contactInvitationRecord);
// if (acceptReject != null) {
// final acceptedContact = acceptReject.acceptedContact;
// if (acceptedContact != null) {
// // Accept
// await createContact(
// activeAccountInfo: activeAccountInfo,
// profile: acceptedContact.profile,
// remoteIdentity: acceptedContact.remoteIdentity,
// remoteConversationRecordKey:
// acceptedContact.remoteConversationRecordKey,
// localConversationRecordKey:
// acceptedContact.localConversationRecordKey,
// );
// ref
// ..invalidate(fetchContactInvitationRecordsProvider)
// ..invalidate(fetchContactListProvider);
// } else {
// // Reject
// ref.invalidate(fetchContactInvitationRecordsProvider);
// }
// }
// }());
// }
// await Future.wait(allChecks);
} finally {
_inDoContactInvitationCheck = true;
}
await Future.wait(allChecks);
}
Future<void> _doNewMessageCheck() async {
if (!connectionState.state.isPublicInternetReady) {
return;
}
final activeChat = ref.read(activeChatStateProvider);
if (activeChat == null) {
return;
}
final activeAccountInfo = await ref.read(fetchActiveAccountProvider.future);
if (activeAccountInfo == null) {
if (_inDoNewMessageCheck) {
return;
}
_inDoNewMessageCheck = true;
final contactList = ref.read(fetchContactListProvider).asData?.value ??
const IListConst([]);
final activeChatContactIdx = contactList.indexWhere(
(c) =>
proto.TypedKeyProto.fromProto(c.remoteConversationRecordKey) ==
activeChat,
);
if (activeChatContactIdx == -1) {
return;
}
final activeChatContact = contactList[activeChatContactIdx];
final remoteIdentityPublicKey =
proto.TypedKeyProto.fromProto(activeChatContact.identityPublicKey);
final remoteConversationRecordKey = proto.TypedKeyProto.fromProto(
activeChatContact.remoteConversationRecordKey);
final localConversationRecordKey = proto.TypedKeyProto.fromProto(
activeChatContact.localConversationRecordKey);
final newMessages = await getRemoteConversationMessages(
activeAccountInfo: activeAccountInfo,
remoteIdentityPublicKey: remoteIdentityPublicKey,
remoteConversationRecordKey: remoteConversationRecordKey);
if (newMessages != null && newMessages.isNotEmpty) {
final changed = await mergeLocalConversationMessages(
activeAccountInfo: activeAccountInfo,
localConversationRecordKey: localConversationRecordKey,
remoteIdentityPublicKey: remoteIdentityPublicKey,
newMessages: newMessages);
if (changed) {
ref.invalidate(activeConversationMessagesProvider);
try {
if (!ProcessorRepository
.instance.processorConnectionState.isPublicInternetReady) {
return;
}
// final activeChat = ref.read(activeChatStateProvider);
// if (activeChat == null) {
// return;
// }
// final activeAccountInfo =
// await ref.read(fetchActiveAccountProvider.future);
// if (activeAccountInfo == null) {
// return;
// }
// final contactList = ref.read(fetchContactListProvider).asData?.value ??
// const IListConst([]);
// final activeChatContactIdx = contactList.indexWhere(
// (c) =>
// proto.TypedKeyProto.fromProto(c.remoteConversationRecordKey) ==
// activeChat,
// );
// if (activeChatContactIdx == -1) {
// return;
// }
// final activeChatContact = contactList[activeChatContactIdx];
// final remoteIdentityPublicKey =
// proto.TypedKeyProto.fromProto(activeChatContact.identityPublicKey);
// final remoteConversationRecordKey = proto.TypedKeyProto.fromProto(
// activeChatContact.remoteConversationRecordKey);
// final localConversationRecordKey = proto.TypedKeyProto.fromProto(
// activeChatContact.localConversationRecordKey);
// final newMessages = await getRemoteConversationMessages(
// activeAccountInfo: activeAccountInfo,
// remoteIdentityPublicKey: remoteIdentityPublicKey,
// remoteConversationRecordKey: remoteConversationRecordKey);
// if (newMessages != null && newMessages.isNotEmpty) {
// final changed = await mergeLocalConversationMessages(
// activeAccountInfo: activeAccountInfo,
// localConversationRecordKey: localConversationRecordKey,
// remoteIdentityPublicKey: remoteIdentityPublicKey,
// newMessages: newMessages);
// if (changed) {
// ref.invalidate(activeConversationMessagesProvider);
// }
// }
} finally {
_inDoNewMessageCheck = false;
}
}
}

View File

@ -6,9 +6,9 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:intl/intl.dart';
import 'package:loggy/loggy.dart';
import 'package:veilid_support/veilid_support.dart';
import '../old_to_refactor/pages/developer.dart';
import '../../packages/veilid_support/veilid_support.dart';
import '../veilid_processor/views/developer.dart';
import 'state_logger.dart';
String wrapWithLogColor(LogLevel? level, String text) {

View File

@ -0,0 +1,25 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:veilid_support/veilid_support.dart';
abstract class StreamWrapperCubit<State> extends Cubit<AsyncValue<State>> {
StreamWrapperCubit(Stream<State> stream, {State? defaultState})
: super(defaultState != null
? AsyncValue.data(defaultState)
: const AsyncValue.loading()) {
_subscription = stream.listen((event) => emit(AsyncValue.data(event)),
// ignore: avoid_types_on_closure_parameters
onError: (Object error, StackTrace stackTrace) {
emit(AsyncValue.error(error, stackTrace));
});
@override
Future<void> close() async {
await _subscription.cancel();
await super.close();
}
}
late final StreamSubscription<State> _subscription;
}

View File

@ -1,10 +1,9 @@
export 'animations.dart';
export 'async_table_db_backed_cubit.dart';
export 'async_value.dart';
export 'loggy.dart';
export 'phono_byte.dart';
export 'responsive.dart';
export 'scanner_error_widget.dart';
export 'state_logger.dart';
export 'stream_wrapper_cubit.dart';
export 'widget_helpers.dart';
export 'window_control.dart';

View File

@ -0,0 +1,12 @@
import '../../tools/tools.dart';
import '../models/models.dart';
import '../repository/processor_repository.dart';
export '../models/processor_connection_state.dart';
class ConnectionStateCubit
extends StreamWrapperCubit<ProcessorConnectionState> {
ConnectionStateCubit(ProcessorRepository processorRepository)
: super(processorRepository.streamProcessorConnectionState(),
defaultState: processorRepository.processorConnectionState);
}

View File

@ -0,0 +1 @@
export 'processor_connection_state.dart';

View File

@ -0,0 +1,19 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:veilid_support/veilid_support.dart';
part 'processor_connection_state.freezed.dart';
@freezed
class ProcessorConnectionState with _$ProcessorConnectionState {
const factory ProcessorConnectionState({
required VeilidStateAttachment attachment,
required VeilidStateNetwork network,
}) = _ProcessorConnectionState;
const ProcessorConnectionState._();
bool get isAttached => !(attachment.state == AttachmentState.detached ||
attachment.state == AttachmentState.detaching ||
attachment.state == AttachmentState.attaching);
bool get isPublicInternetReady => attachment.publicInternetReady;
}

View File

@ -0,0 +1,184 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'processor_connection_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
/// @nodoc
mixin _$ProcessorConnectionState {
VeilidStateAttachment get attachment => throw _privateConstructorUsedError;
VeilidStateNetwork get network => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ProcessorConnectionStateCopyWith<ProcessorConnectionState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProcessorConnectionStateCopyWith<$Res> {
factory $ProcessorConnectionStateCopyWith(ProcessorConnectionState value,
$Res Function(ProcessorConnectionState) then) =
_$ProcessorConnectionStateCopyWithImpl<$Res, ProcessorConnectionState>;
@useResult
$Res call({VeilidStateAttachment attachment, VeilidStateNetwork network});
$VeilidStateAttachmentCopyWith<$Res> get attachment;
$VeilidStateNetworkCopyWith<$Res> get network;
}
/// @nodoc
class _$ProcessorConnectionStateCopyWithImpl<$Res,
$Val extends ProcessorConnectionState>
implements $ProcessorConnectionStateCopyWith<$Res> {
_$ProcessorConnectionStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? attachment = null,
Object? network = null,
}) {
return _then(_value.copyWith(
attachment: null == attachment
? _value.attachment
: attachment // ignore: cast_nullable_to_non_nullable
as VeilidStateAttachment,
network: null == network
? _value.network
: network // ignore: cast_nullable_to_non_nullable
as VeilidStateNetwork,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$VeilidStateAttachmentCopyWith<$Res> get attachment {
return $VeilidStateAttachmentCopyWith<$Res>(_value.attachment, (value) {
return _then(_value.copyWith(attachment: value) as $Val);
});
}
@override
@pragma('vm:prefer-inline')
$VeilidStateNetworkCopyWith<$Res> get network {
return $VeilidStateNetworkCopyWith<$Res>(_value.network, (value) {
return _then(_value.copyWith(network: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$ProcessorConnectionStateImplCopyWith<$Res>
implements $ProcessorConnectionStateCopyWith<$Res> {
factory _$$ProcessorConnectionStateImplCopyWith(
_$ProcessorConnectionStateImpl value,
$Res Function(_$ProcessorConnectionStateImpl) then) =
__$$ProcessorConnectionStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({VeilidStateAttachment attachment, VeilidStateNetwork network});
@override
$VeilidStateAttachmentCopyWith<$Res> get attachment;
@override
$VeilidStateNetworkCopyWith<$Res> get network;
}
/// @nodoc
class __$$ProcessorConnectionStateImplCopyWithImpl<$Res>
extends _$ProcessorConnectionStateCopyWithImpl<$Res,
_$ProcessorConnectionStateImpl>
implements _$$ProcessorConnectionStateImplCopyWith<$Res> {
__$$ProcessorConnectionStateImplCopyWithImpl(
_$ProcessorConnectionStateImpl _value,
$Res Function(_$ProcessorConnectionStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? attachment = null,
Object? network = null,
}) {
return _then(_$ProcessorConnectionStateImpl(
attachment: null == attachment
? _value.attachment
: attachment // ignore: cast_nullable_to_non_nullable
as VeilidStateAttachment,
network: null == network
? _value.network
: network // ignore: cast_nullable_to_non_nullable
as VeilidStateNetwork,
));
}
}
/// @nodoc
class _$ProcessorConnectionStateImpl extends _ProcessorConnectionState {
const _$ProcessorConnectionStateImpl(
{required this.attachment, required this.network})
: super._();
@override
final VeilidStateAttachment attachment;
@override
final VeilidStateNetwork network;
@override
String toString() {
return 'ProcessorConnectionState(attachment: $attachment, network: $network)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProcessorConnectionStateImpl &&
(identical(other.attachment, attachment) ||
other.attachment == attachment) &&
(identical(other.network, network) || other.network == network));
}
@override
int get hashCode => Object.hash(runtimeType, attachment, network);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ProcessorConnectionStateImplCopyWith<_$ProcessorConnectionStateImpl>
get copyWith => __$$ProcessorConnectionStateImplCopyWithImpl<
_$ProcessorConnectionStateImpl>(this, _$identity);
}
abstract class _ProcessorConnectionState extends ProcessorConnectionState {
const factory _ProcessorConnectionState(
{required final VeilidStateAttachment attachment,
required final VeilidStateNetwork network}) =
_$ProcessorConnectionStateImpl;
const _ProcessorConnectionState._() : super._();
@override
VeilidStateAttachment get attachment;
@override
VeilidStateNetwork get network;
@override
@JsonKey(ignore: true)
_$$ProcessorConnectionStateImplCopyWith<_$ProcessorConnectionStateImpl>
get copyWith => throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,132 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../app.dart';
import '../../tools/tools.dart';
import '../models/models.dart';
class ProcessorRepository {
ProcessorRepository._()
: startedUp = false,
_controllerConnectionState = StreamController.broadcast(sync: true),
processorConnectionState = ProcessorConnectionState(
attachment: const VeilidStateAttachment(
state: AttachmentState.detached,
publicInternetReady: false,
localNetworkReady: false),
network: VeilidStateNetwork(
started: false,
bpsDown: BigInt.zero,
bpsUp: BigInt.zero,
peers: []));
//////////////////////////////////////////////////////////////
/// Singleton initialization
static ProcessorRepository instance = ProcessorRepository._();
Future<void> startup() async {
if (startedUp) {
return;
}
var veilidVersion = '';
try {
veilidVersion = Veilid.instance.veilidVersionString();
} on Exception {
veilidVersion = 'Failed to get veilid version.';
}
log.info('Veilid version: $veilidVersion');
// HACK: In case of hot restart shut down first
try {
await Veilid.instance.shutdownVeilidCore();
} on Exception {
// Do nothing on failure here
}
final updateStream = await Veilid.instance
.startupVeilidCore(await getVeilidConfig(kIsWeb, VeilidChatApp.name));
_updateSubscription = updateStream.listen((update) {
if (update is VeilidLog) {
processLog(update);
} else if (update is VeilidUpdateAttachment) {
processUpdateAttachment(update);
} else if (update is VeilidUpdateConfig) {
processUpdateConfig(update);
} else if (update is VeilidUpdateNetwork) {
processUpdateNetwork(update);
} else if (update is VeilidAppMessage) {
processAppMessage(update);
} else if (update is VeilidAppCall) {
log.info('AppCall: ${update.toJson()}');
} else if (update is VeilidUpdateValueChange) {
processUpdateValueChange(update);
} else {
log.trace('Update: ${update.toJson()}');
}
});
startedUp = true;
await Veilid.instance.attach();
}
Future<void> shutdown() async {
if (!startedUp) {
return;
}
await Veilid.instance.shutdownVeilidCore();
await _updateSubscription?.cancel();
_updateSubscription = null;
startedUp = false;
}
Stream<ProcessorConnectionState> streamProcessorConnectionState() =>
_controllerConnectionState.stream;
void processUpdateAttachment(VeilidUpdateAttachment updateAttachment) {
// Set connection meter and ui state for connection state
processorConnectionState = processorConnectionState.copyWith(
attachment: VeilidStateAttachment(
state: updateAttachment.state,
publicInternetReady: updateAttachment.publicInternetReady,
localNetworkReady: updateAttachment.localNetworkReady));
}
void processUpdateConfig(VeilidUpdateConfig updateConfig) {
log.debug('VeilidUpdateConfig: ${updateConfig.toJson()}');
}
void processUpdateNetwork(VeilidUpdateNetwork updateNetwork) {
// Set connection meter and ui state for connection state
processorConnectionState = processorConnectionState.copyWith(
network: VeilidStateNetwork(
started: updateNetwork.started,
bpsDown: updateNetwork.bpsDown,
bpsUp: updateNetwork.bpsUp,
peers: updateNetwork.peers));
_controllerConnectionState.add(processorConnectionState);
}
void processAppMessage(VeilidAppMessage appMessage) {
log.debug('VeilidAppMessage: ${appMessage.toJson()}');
}
void processUpdateValueChange(VeilidUpdateValueChange updateValueChange) {
// Send value updates to DHTRecordPool
DHTRecordPool.instance.processUpdateValueChange(updateValueChange);
}
////////////////////////////////////////////
StreamSubscription<VeilidUpdate>? _updateSubscription;
final StreamController<ProcessorConnectionState> _controllerConnectionState;
bool startedUp;
ProcessorConnectionState processorConnectionState;
}

View File

@ -0,0 +1,4 @@
export 'cubit/connection_state_cubit.dart';
export 'models/models.dart';
export 'repository/processor_repository.dart';
export 'views/views.dart';

View File

@ -6,15 +6,15 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:go_router/go_router.dart';
import 'package:loggy/loggy.dart';
import 'package:quickalert/quickalert.dart';
import 'package:veilid_support/veilid_support.dart';
import 'package:xterm/xterm.dart';
import '../../theme/theme.dart';
import '../../tools/tools.dart';
import '../../../packages/veilid_support/veilid_support.dart';
final globalDebugTerminal = Terminal(
maxLines: 50000,
@ -32,7 +32,7 @@ class DeveloperPage extends StatefulWidget {
DeveloperPageState createState() => DeveloperPageState();
}
class DeveloperPageState extends ConsumerState<DeveloperPage> {
class DeveloperPageState extends State<DeveloperPage> {
final _terminalController = TerminalController();
final _debugCommandController = TextEditingController();
final _logLevelController = DropdownController(duration: 250.ms);

View File

@ -0,0 +1,88 @@
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:quickalert/quickalert.dart';
import 'package:signal_strength_indicator/signal_strength_indicator.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../theme/theme.dart';
import '../cubit/connection_state_cubit.dart';
class SignalStrengthMeterWidget extends StatelessWidget {
const SignalStrengthMeterWidget({super.key});
@override
// ignore: prefer_expression_function_bodies
Widget build(BuildContext context) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
const iconSize = 16.0;
return BlocBuilder<ConnectionStateCubit,
AsyncValue<ProcessorConnectionState>>(builder: (context, state) {
late final Widget iconWidget;
state.when(
data: (connectionState) {
late final double value;
late final Color color;
late final Color inactiveColor;
switch (connectionState.attachment.state) {
case AttachmentState.detached:
iconWidget = Icon(Icons.signal_cellular_nodata,
size: iconSize, color: scale.grayScale.text);
return;
case AttachmentState.detaching:
iconWidget = Icon(Icons.signal_cellular_off,
size: iconSize, color: scale.grayScale.text);
return;
case AttachmentState.attaching:
value = 0;
color = scale.primaryScale.text;
case AttachmentState.attachedWeak:
value = 1;
color = scale.primaryScale.text;
case AttachmentState.attachedStrong:
value = 2;
color = scale.primaryScale.text;
case AttachmentState.attachedGood:
value = 3;
color = scale.primaryScale.text;
case AttachmentState.fullyAttached:
value = 4;
color = scale.primaryScale.text;
case AttachmentState.overAttached:
value = 4;
color = scale.secondaryScale.subtleText;
}
inactiveColor = scale.grayScale.subtleText;
iconWidget = SignalStrengthIndicator.bars(
value: value,
activeColor: color,
inactiveColor: inactiveColor,
size: iconSize,
barCount: 4,
spacing: 1);
},
loading: () => {iconWidget = const Icon(Icons.warning)},
error: (e, st) => {
iconWidget = const Icon(Icons.error).onTap(
() async => QuickAlert.show(
type: QuickAlertType.error,
context: context,
title: 'Error',
text: 'Error: {e}\n StackTrace: {st}'),
)
});
return GestureDetector(
onLongPress: () async {
await GoRouterHelper(context).push('/developer');
},
child: iconWidget);
});
}
}

View File

@ -0,0 +1,2 @@
export 'developer.dart';
export 'signal_strength_meter.dart';

View File

@ -4,5 +4,6 @@ library dht_support;
export 'src/dht_record.dart';
export 'src/dht_record_crypto.dart';
export 'src/dht_record_cubit.dart';
export 'src/dht_record_pool.dart';
export 'src/dht_short_array.dart';

View File

@ -3,7 +3,7 @@ import 'dart:typed_data';
import 'package:protobuf/protobuf.dart';
import '../../../../veilid_support.dart';
import '../../../veilid_support.dart';
class DHTRecord {
DHTRecord(
@ -28,6 +28,7 @@ class DHTRecord {
final DHTRecordCrypto _crypto;
bool _open;
bool _valid;
StreamSubscription<VeilidUpdateValueChange>? _watchSubscription;
int subkeyOrDefault(int subkey) => (subkey == -1) ? _defaultSubkey : subkey;
@ -47,9 +48,8 @@ class DHTRecord {
if (!_open) {
return;
}
final pool = await DHTRecordPool.instance();
await _routingContext.closeDHTRecord(_recordDescriptor.key);
pool.recordClosed(_recordDescriptor.key);
await DHTRecordPool.instance.recordClosed(_recordDescriptor.key);
_open = false;
}
@ -60,8 +60,7 @@ class DHTRecord {
if (_open) {
await close();
}
final pool = await DHTRecordPool.instance();
await pool.deleteDeep(key);
await DHTRecordPool.instance.deleteDeep(key);
_valid = false;
}
@ -253,4 +252,20 @@ class DHTRecord {
T Function(List<int>) fromBuffer, Future<T> Function(T) update,
{int subkey = -1}) =>
eventualUpdateBytes(protobufUpdate(fromBuffer, update), subkey: subkey);
Future<void> watch(
Future<void> Function(VeilidUpdateValueChange update) onUpdate,
{List<ValueSubkeyRange>? subkeys,
Timestamp? expiration,
int? count}) async {
// register watch with pool
_watchSubscription = await DHTRecordPool.instance.recordWatch(
_recordDescriptor.key, onUpdate,
subkeys: subkeys, expiration: expiration, count: count);
}
Future<void> cancelWatch() async {
// register watch with pool
await _watchSubscription?.cancel();
}
}

View File

@ -0,0 +1,53 @@
import 'package:bloc/bloc.dart';
import '../../veilid_support.dart';
class DhtRecordCubit<T> extends Cubit<AsyncValue<T>> {
DhtRecordCubit({
required DHTRecord record,
required Future<T?> Function(DHTRecord, VeilidUpdateValueChange)
stateFunction,
List<ValueSubkeyRange> watchSubkeys = const [],
}) : _record = record,
super(const AsyncValue.loading()) {
Future.delayed(Duration.zero, () async {
await record.watch((update) async {
try {
final newState = await stateFunction(record, update);
if (newState != null) {
emit(AsyncValue.data(newState));
}
} on Exception catch (e) {
emit(AsyncValue.error(e));
}
}, subkeys: watchSubkeys);
});
}
@override
Future<void> close() async {
await _record.cancelWatch();
await super.close();
}
DHTRecord _record;
}
class SingleDHTRecordCubit<T> extends DhtRecordCubit<T> {
SingleDHTRecordCubit(
{required super.record,
required T? Function(List<int> data) decodeState,
int singleSubkey = 0})
: super(
stateFunction: (record, update) async {
//
if (update.subkeys.isNotEmpty) {
final newState = decodeState(update.valueData.data);
return newState;
}
return null;
},
watchSubkeys: [
ValueSubkeyRange(low: singleSubkey, high: singleSubkey)
]);
}

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:mutex/mutex.dart';
@ -35,24 +37,55 @@ class OwnedDHTRecordPointer with _$OwnedDHTRecordPointer {
_$OwnedDHTRecordPointerFromJson(json as Map<String, dynamic>);
}
/// Watch state
class _WatchState {
_WatchState(
{required this.subkeys, required this.expiration, required this.count});
List<ValueSubkeyRange>? subkeys;
Timestamp? expiration;
int? count;
Timestamp? realExpiration;
}
/// Opened DHTRecord state
class _OpenedDHTRecord {
_OpenedDHTRecord(this.routingContext)
: mutex = Mutex(),
needsWatchStateUpdate = false,
inWatchStateUpdate = false;
Future<void> close() async {
await watchController?.close();
}
Mutex mutex;
StreamController<VeilidUpdateValueChange>? watchController;
bool needsWatchStateUpdate;
bool inWatchStateUpdate;
_WatchState? watchState;
VeilidRoutingContext routingContext;
}
class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
DHTRecordPool._(Veilid veilid, VeilidRoutingContext routingContext)
: _state = DHTRecordPoolAllocations(
childrenByParent: IMap(),
parentByChild: IMap(),
rootRecords: ISet()),
_opened = <TypedKey, Mutex>{},
_opened = <TypedKey, _OpenedDHTRecord>{},
_routingContext = routingContext,
_veilid = veilid;
// Persistent DHT record list
DHTRecordPoolAllocations _state;
// Which DHT records are currently open
final Map<TypedKey, Mutex> _opened;
final Map<TypedKey, _OpenedDHTRecord> _opened;
// Default routing context to use for new keys
final VeilidRoutingContext _routingContext;
// Convenience accessor
final Veilid _veilid;
// If tick is already running or not
bool inTick = false;
static DHTRecordPool? _singleton;
@ -71,37 +104,71 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
Object? valueToJson(DHTRecordPoolAllocations val) => val.toJson();
//////////////////////////////////////////////////////////////
static Mutex instanceSetupMutex = Mutex();
// ignore: prefer_expression_function_bodies
static Future<DHTRecordPool> instance() async {
return instanceSetupMutex.protect(() async {
if (_singleton == null) {
final routingContext = await Veilid.instance.routingContext();
final globalPool = DHTRecordPool._(Veilid.instance, routingContext);
globalPool._state = await globalPool.load();
_singleton = globalPool;
}
return _singleton!;
});
static DHTRecordPool get instance => _singleton!;
static Future<void> init() async {
final routingContext = await Veilid.instance.routingContext();
final globalPool = DHTRecordPool._(Veilid.instance, routingContext);
globalPool._state = await globalPool.load();
_singleton = globalPool;
}
Veilid get veilid => _veilid;
Future<void> _recordOpened(TypedKey key) async {
Future<void> _recordOpened(
TypedKey key, VeilidRoutingContext routingContext) async {
// no race because dart is single threaded until async breaks
final m = _opened[key] ?? Mutex();
_opened[key] = m;
await m.acquire();
_opened[key] = m;
final odr = _opened[key] ?? _OpenedDHTRecord(routingContext);
_opened[key] = odr;
await odr.mutex.acquire();
}
void recordClosed(TypedKey key) {
final m = _opened.remove(key);
if (m == null) {
Future<StreamSubscription<VeilidUpdateValueChange>> recordWatch(
TypedKey key, Future<void> Function(VeilidUpdateValueChange) onUpdate,
{required List<ValueSubkeyRange>? subkeys,
required Timestamp? expiration,
required int? count}) async {
final odr = _opened[key];
if (odr == null) {
throw StateError("can't watch unopened record");
}
// Set up watch requirements
odr
..watchState =
_WatchState(subkeys: subkeys, expiration: expiration, count: count)
..needsWatchStateUpdate = true
..watchController ??=
StreamController<VeilidUpdateValueChange>.broadcast(onCancel: () {
// Request watch state change for cancel
odr
..watchState = null
..needsWatchStateUpdate = true;
// If there are no more listeners then we can get rid of the controller
if (!(odr.watchController?.hasListener ?? true)) {
odr.watchController = null;
}
});
return odr.watchController!.stream.listen(
(update) {
Future.delayed(Duration.zero, () => onUpdate(update));
},
cancelOnError: true,
onError: (e) async {
await odr.watchController!.close();
odr.watchController = null;
});
}
Future<void> recordClosed(TypedKey key) async {
final odr = _opened.remove(key);
if (odr == null) {
throw StateError('record already closed');
}
m.release();
await odr.close();
odr.mutex.release();
}
Future<void> deleteDeep(TypedKey parent) async {
@ -112,7 +179,8 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
final nextDep = currentDeps.removeLast();
// Ensure we get the exclusive lock on this record
await _recordOpened(nextDep);
// Can use default routing context here because we are only deleting
await _recordOpened(nextDep, _routingContext);
// Remove this child from its parent
await _removeDependency(nextDep);
@ -127,7 +195,7 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
final allFutures = <Future<void>>[];
for (final dep in allDeps) {
allFutures.add(_routingContext.deleteDHTRecord(dep));
recordClosed(dep);
await recordClosed(dep);
}
await Future.wait(allFutures);
}
@ -220,7 +288,7 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
recordDescriptor.ownerTypedKeyPair()!));
await _addDependency(parent, rec.key);
await _recordOpened(rec.key);
await _recordOpened(rec.key, dhtctx);
return rec;
}
@ -231,7 +299,9 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
TypedKey? parent,
int defaultSubkey = 0,
DHTRecordCrypto? crypto}) async {
await _recordOpened(recordKey);
final dhtctx = routingContext ?? _routingContext;
await _recordOpened(recordKey, dhtctx);
late final DHTRecord rec;
try {
@ -240,7 +310,6 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
_validateParent(parent, recordKey);
// Open from the veilid api
final dhtctx = routingContext ?? _routingContext;
final recordDescriptor = await dhtctx.openDHTRecord(recordKey, null);
rec = DHTRecord(
routingContext: dhtctx,
@ -251,7 +320,7 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
// Register the dependency
await _addDependency(parent, rec.key);
} on Exception catch (_) {
recordClosed(recordKey);
await recordClosed(recordKey);
rethrow;
}
@ -267,7 +336,9 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
int defaultSubkey = 0,
DHTRecordCrypto? crypto,
}) async {
await _recordOpened(recordKey);
final dhtctx = routingContext ?? _routingContext;
await _recordOpened(recordKey, dhtctx);
late final DHTRecord rec;
try {
@ -276,7 +347,6 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
_validateParent(parent, recordKey);
// Open from the veilid api
final dhtctx = routingContext ?? _routingContext;
final recordDescriptor = await dhtctx.openDHTRecord(recordKey, writer);
rec = DHTRecord(
routingContext: dhtctx,
@ -290,7 +360,7 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
// Register the dependency if specified
await _addDependency(parent, rec.key);
} on Exception catch (_) {
recordClosed(recordKey);
await recordClosed(recordKey);
rethrow;
}
@ -324,4 +394,69 @@ class DHTRecordPool with TableDBBacked<DHTRecordPoolAllocations> {
final childJson = child.toJson();
return _state.parentByChild[childJson];
}
/// Handle the DHT record updates coming from Veilid
void processUpdateValueChange(VeilidUpdateValueChange updateValueChange) {
if (updateValueChange.subkeys.isNotEmpty) {}
}
/// Ticker to check watch state change requests
Future<void> tick() async {
if (inTick) {
return;
}
inTick = true;
try {
// See if any opened records need watch state changes
final unord = List<Future<void>>.empty(growable: true);
for (final kv in _opened.entries) {
// Check if already updating
if (kv.value.inWatchStateUpdate) {
continue;
}
if (kv.value.needsWatchStateUpdate) {
kv.value.inWatchStateUpdate = true;
final ws = kv.value.watchState;
if (ws == null) {
unord.add(() async {
// Record needs watch cancel
try {
final done =
await kv.value.routingContext.cancelDHTWatch(kv.key);
assert(done,
'should always be done when cancelling whole subkey range');
kv.value.needsWatchStateUpdate = false;
} on VeilidAPIException {
// Failed to cancel DHT watch, try again next tick
}
kv.value.inWatchStateUpdate = false;
}());
} else {
unord.add(() async {
// Record needs new watch
try {
final realExpiration = await kv.value.routingContext
.watchDHTValues(kv.key,
subkeys: ws.subkeys,
count: ws.count,
expiration: ws.expiration);
kv.value.needsWatchStateUpdate = false;
ws.realExpiration = realExpiration;
} on VeilidAPIException {
// Failed to cancel DHT watch, try again next tick
}
kv.value.inWatchStateUpdate = false;
}());
}
}
}
await unord.wait;
} finally {
inTick = false;
}
}
}

View File

@ -69,7 +69,7 @@ class DHTShortArray {
DHTRecordCrypto? crypto,
KeyPair? smplWriter}) async {
assert(stride <= maxElements, 'stride too long');
final pool = await DHTRecordPool.instance();
final pool = DHTRecordPool.instance;
late final DHTRecord dhtRecord;
if (smplWriter != null) {
@ -111,9 +111,7 @@ class DHTShortArray {
{VeilidRoutingContext? routingContext,
TypedKey? parent,
DHTRecordCrypto? crypto}) async {
final pool = await DHTRecordPool.instance();
final dhtRecord = await pool.openRead(headRecordKey,
final dhtRecord = await DHTRecordPool.instance.openRead(headRecordKey,
parent: parent, routingContext: routingContext, crypto: crypto);
try {
final dhtShortArray = DHTShortArray._(headRecord: dhtRecord);
@ -132,8 +130,8 @@ class DHTShortArray {
TypedKey? parent,
DHTRecordCrypto? crypto,
}) async {
final pool = await DHTRecordPool.instance();
final dhtRecord = await pool.openWrite(headRecordKey, writer,
final dhtRecord = await DHTRecordPool.instance.openWrite(
headRecordKey, writer,
parent: parent, routingContext: routingContext, crypto: crypto);
try {
final dhtShortArray = DHTShortArray._(headRecord: dhtRecord);
@ -214,17 +212,15 @@ class DHTShortArray {
/// Open a linked record for reading or writing, same as the head record
Future<DHTRecord> _openLinkedRecord(TypedKey recordKey) async {
final pool = await DHTRecordPool.instance();
final writer = _headRecord.writer;
return (writer != null)
? await pool.openWrite(
? await DHTRecordPool.instance.openWrite(
recordKey,
writer,
parent: _headRecord.key,
routingContext: _headRecord.routingContext,
)
: await pool.openRead(
: await DHTRecordPool.instance.openRead(
recordKey,
parent: _headRecord.key,
routingContext: _headRecord.routingContext,

View File

@ -2,9 +2,7 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import '../../tools/tools.dart';
import '../init.dart';
import '../../packages/veilid_support/veilid_support.dart';
import '../veilid_support.dart';
abstract class AsyncTableDBBackedCubit<State> extends Cubit<AsyncValue<State>>
with TableDBBacked<State> {
@ -14,7 +12,6 @@ abstract class AsyncTableDBBackedCubit<State> extends Cubit<AsyncValue<State>>
Future<void> _build() async {
try {
await eventualVeilid.future;
emit(AsyncValue.data(await load()));
} on Exception catch (e, stackTrace) {
emit(AsyncValue.error(e, stackTrace));

View File

@ -78,7 +78,7 @@ class IdentityMaster with _$IdentityMaster {
extension IdentityMasterExtension on IdentityMaster {
/// Deletes a master identity and the identity record under it
Future<void> delete() async {
final pool = await DHTRecordPool.instance();
final pool = DHTRecordPool.instance;
await (await pool.openRead(masterRecordKey)).delete();
}
@ -95,7 +95,7 @@ extension IdentityMasterExtension on IdentityMaster {
{required SharedSecret identitySecret,
required String accountKey}) async {
// Read the identity key to get the account keys
final pool = await DHTRecordPool.instance();
final pool = DHTRecordPool.instance;
final identityRecordCrypto = await DHTRecordCryptoPrivate.fromSecret(
identityRecordKey.kind, identitySecret);
@ -129,7 +129,7 @@ extension IdentityMasterExtension on IdentityMaster {
required String accountKey,
required Future<T> Function(TypedKey parent) createAccountCallback,
}) async {
final pool = await DHTRecordPool.instance();
final pool = DHTRecordPool.instance;
/////// Add account with profile to DHT
@ -186,7 +186,7 @@ class IdentityMasterWithSecrets {
/// Creates a new master identity and returns it with its secrets
static Future<IdentityMasterWithSecrets> create() async {
final pool = await DHTRecordPool.instance();
final pool = DHTRecordPool.instance;
// IdentityMaster DHT record is public/unencrypted
return (await pool.create(crypto: const DHTRecordCryptoPublic()))
@ -245,7 +245,7 @@ class IdentityMasterWithSecrets {
/// Opens an existing master identity and validates it
Future<IdentityMaster> openIdentityMaster(
{required TypedKey identityMasterRecordKey}) async {
final pool = await DHTRecordPool.instance();
final pool = DHTRecordPool.instance;
// IdentityMaster DHT record is public/unencrypted
return (await pool.openRead(identityMasterRecordKey))

View File

@ -39,7 +39,7 @@ class VeilidLoggy implements LoggyType {
Loggy get _veilidLoggy => Loggy<VeilidLoggy>('Veilid');
Future<void> processLog(VeilidLog log) async {
void processLog(VeilidLog log) {
StackTrace? stackTrace;
Object? error;
final backtrace = log.backtrace;

View File

@ -6,6 +6,7 @@ library veilid_support;
export 'package:veilid/veilid.dart';
export 'dht_support/dht_support.dart';
export 'src/async_value.dart';
export 'src/config.dart';
export 'src/identity.dart';
export 'src/json_tools.dart';

View File

@ -33,6 +33,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.11.0"
bloc:
dependency: "direct main"
description:
name: bloc
sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49"
url: "https://pub.dev"
source: hosted
version: "8.1.2"
boolean_selector:
dependency: transitive
description:

View File

@ -5,9 +5,9 @@ version: 1.0.2+0
environment:
sdk: '>=3.0.5 <4.0.0'
flutter: ">=3.10.0"
dependencies:
bloc: ^8.1.2
fast_immutable_collections: ^9.1.5
freezed_annotation: ^2.2.0
json_annotation: ^4.8.1
@ -20,7 +20,7 @@ dependencies:
dev_dependencies:
build_runner: ^2.4.6
test: ^1.25.0
freezed: ^2.3.5
json_serializable: ^6.7.1
lint_hard: ^4.0.0
test: ^1.25.0