mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2024-10-01 06:55:46 -04:00
checkpoint
This commit is contained in:
parent
c516323e7d
commit
31f562119a
5
build.sh
5
build.sh
@ -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
|
||||
|
@ -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';
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
@ -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';
|
4
lib/account_manager/models/models.dart
Normal file
4
lib/account_manager/models/models.dart
Normal 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';
|
@ -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);
|
||||
|
@ -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';
|
||||
|
@ -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'),
|
||||
|
@ -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
235
lib/layout/home.dart
Normal 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
1
lib/layout/layout.dart
Normal file
@ -0,0 +1 @@
|
||||
|
@ -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,
|
||||
));
|
||||
}
|
||||
}
|
@ -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),
|
||||
)));
|
||||
}
|
||||
}
|
@ -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);
|
@ -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;
|
||||
}
|
@ -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()}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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';
|
||||
|
4
lib/theme/models/models.dart
Normal file
4
lib/theme/models/models.dart
Normal file
@ -0,0 +1,4 @@
|
||||
export 'scale_color.dart';
|
||||
export 'scale_scheme.dart';
|
||||
export 'theme_preference.dart';
|
||||
export 'radix_generator.dart';
|
@ -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';
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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';
|
||||
|
218
lib/tick.dart
218
lib/tick.dart
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
25
lib/tools/stream_wrapper_cubit.dart
Normal file
25
lib/tools/stream_wrapper_cubit.dart
Normal 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;
|
||||
}
|
@ -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';
|
||||
|
12
lib/veilid_processor/cubit/connection_state_cubit.dart
Normal file
12
lib/veilid_processor/cubit/connection_state_cubit.dart
Normal 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);
|
||||
}
|
1
lib/veilid_processor/models/models.dart
Normal file
1
lib/veilid_processor/models/models.dart
Normal file
@ -0,0 +1 @@
|
||||
export 'processor_connection_state.dart';
|
19
lib/veilid_processor/models/processor_connection_state.dart
Normal file
19
lib/veilid_processor/models/processor_connection_state.dart
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
132
lib/veilid_processor/repository/processor_repository.dart
Normal file
132
lib/veilid_processor/repository/processor_repository.dart
Normal 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;
|
||||
}
|
4
lib/veilid_processor/veilid_processor.dart
Normal file
4
lib/veilid_processor/veilid_processor.dart
Normal file
@ -0,0 +1,4 @@
|
||||
export 'cubit/connection_state_cubit.dart';
|
||||
export 'models/models.dart';
|
||||
export 'repository/processor_repository.dart';
|
||||
export 'views/views.dart';
|
@ -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);
|
88
lib/veilid_processor/views/signal_strength_meter.dart
Normal file
88
lib/veilid_processor/views/signal_strength_meter.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
2
lib/veilid_processor/views/views.dart
Normal file
2
lib/veilid_processor/views/views.dart
Normal file
@ -0,0 +1,2 @@
|
||||
export 'developer.dart';
|
||||
export 'signal_strength_meter.dart';
|
@ -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';
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
]);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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));
|
@ -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))
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user