mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-03-11 01:49:17 -04:00
checkpoint
This commit is contained in:
parent
c22d6fcff8
commit
8c22bf8cc0
14
lib/app.dart
14
lib/app.dart
@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:animated_theme_switcher/animated_theme_switcher.dart';s
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:animated_theme_switcher/animated_theme_switcher.dart';
|
||||||
|
import 'router/router.dart';
|
||||||
|
|
||||||
class VeilidChatApp extends StatelessWidget {
|
class VeilidChatApp extends ConsumerWidget {
|
||||||
const VeilidChatApp({
|
const VeilidChatApp({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.theme,
|
required this.theme,
|
||||||
@ -10,14 +12,16 @@ class VeilidChatApp extends StatelessWidget {
|
|||||||
final ThemeData theme;
|
final ThemeData theme;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final router = ref.watch(routerProvider);
|
||||||
|
|
||||||
return ThemeProvider(
|
return ThemeProvider(
|
||||||
initTheme: theme,
|
initTheme: theme,
|
||||||
builder: (_, theme) {
|
builder: (_, theme) {
|
||||||
return MaterialApp(
|
return MaterialApp.router(
|
||||||
|
routerConfig: router,
|
||||||
title: 'VeilidChat',
|
title: 'VeilidChat',
|
||||||
theme: theme,
|
theme: theme,
|
||||||
home: const MyHomePage(title: 'Flutter Demo Home Page'),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
96
lib/entities/contact.dart
Normal file
96
lib/entities/contact.dart
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
|
||||||
|
class Contact {
|
||||||
|
String name;
|
||||||
|
String publicKey;
|
||||||
|
bool available;
|
||||||
|
|
||||||
|
Contact(this.name, this.publicKey) : available = false;
|
||||||
|
|
||||||
|
Contact.fromJson(Map<String, dynamic> json)
|
||||||
|
: name = json['name'],
|
||||||
|
publicKey = json['public_key'],
|
||||||
|
available = json['available'];
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'name': name,
|
||||||
|
'public_key': publicKey,
|
||||||
|
'available': available,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Synchronize the app state contact list with the internal one
|
||||||
|
// void sortAndStoreAppStateContactList(List<dynamic> ffasContactList) {
|
||||||
|
// ffasContactList.sortBy((element) => element["name"] as String);
|
||||||
|
// FFAppState().update(() {
|
||||||
|
// FFAppState().ContactList = ffasContactList;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Called when a new contact is added or an existing one is edited
|
||||||
|
// Future<String> vcsUpdateContact(
|
||||||
|
// String id,
|
||||||
|
// String name,
|
||||||
|
// String publicKey,
|
||||||
|
// ) async {
|
||||||
|
// var api = Veilid.instance;
|
||||||
|
// try {
|
||||||
|
// // if we are adding a new contact, make its id
|
||||||
|
// var newContact = false;
|
||||||
|
// if (id.isEmpty) {
|
||||||
|
// id = const Uuid().v4();
|
||||||
|
// newContact = true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Trim name
|
||||||
|
// name = name.trim();
|
||||||
|
|
||||||
|
// // Validate name and public key
|
||||||
|
// if (name.length > 127) {
|
||||||
|
// return "Name is too long.";
|
||||||
|
// }
|
||||||
|
// if (name.isEmpty) {
|
||||||
|
// return "Name can not be empty";
|
||||||
|
// }
|
||||||
|
// if (!isValidDHTKey(publicKey)) {
|
||||||
|
// return "Public key is not valid";
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // update entry in internal contacts table
|
||||||
|
// var contactsDb = await api.openTableDB("contacts", 1);
|
||||||
|
// var contact = Contact(name, publicKey);
|
||||||
|
// await contactsDb.storeStringJson(0, id, contact);
|
||||||
|
|
||||||
|
// // update app state
|
||||||
|
// var contactJson = contact.toJson();
|
||||||
|
// contactJson['id'] = id;
|
||||||
|
|
||||||
|
// var ffasContactList = FFAppState().ContactList;
|
||||||
|
// if (newContact) {
|
||||||
|
// // Add new contact
|
||||||
|
// ffasContactList.add(contactJson);
|
||||||
|
// } else {
|
||||||
|
// // Update existing contact
|
||||||
|
// ffasContactList.forEachIndexedWhile((i, e) {
|
||||||
|
// if (e['id'] == id) {
|
||||||
|
// ffasContactList[i] = contactJson;
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// return true;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Sort the contact list
|
||||||
|
// sortAndStoreAppStateContactList(ffasContactList);
|
||||||
|
// } catch (e) {
|
||||||
|
// return e.toString();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return "";
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Called when a contact is to be removed
|
||||||
|
// Future<void> vcsDeleteContact(String id) async {
|
||||||
|
// //
|
||||||
|
// }
|
@ -17,7 +17,7 @@ void main() async {
|
|||||||
var initTheme = themeService.initial;
|
var initTheme = themeService.initial;
|
||||||
runApp(
|
runApp(
|
||||||
ProviderScope(
|
ProviderScope(
|
||||||
observers: [const StateLogger()],
|
observers: const [StateLogger()],
|
||||||
child: VeilidChatApp(theme: initTheme)),
|
child: VeilidChatApp(theme: initTheme)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
29
lib/pages/chat.dart
Normal file
29
lib/pages/chat.dart
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
class ChatPage extends ConsumerWidget {
|
||||||
|
const ChatPage({super.key});
|
||||||
|
static const path = '/chat';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text("Chat")),
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Text("Home Page"),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
ref.watch(authNotifierProvider.notifier).logout();
|
||||||
|
},
|
||||||
|
child: const Text("Logout"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
32
lib/pages/contacts.dart
Normal file
32
lib/pages/contacts.dart
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
class ContactsPage extends ConsumerWidget {
|
||||||
|
const ContactsPage({super.key});
|
||||||
|
static const path = '/contacts';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: null,
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Text("Contacts Page"),
|
||||||
|
// ElevatedButton(
|
||||||
|
// onPressed: () async {
|
||||||
|
// ref.watch(authNotifierProvider.notifier).login(
|
||||||
|
// "myEmail",
|
||||||
|
// "myPassword",
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// child: const Text("Login"),
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
0
lib/pages/edit_account.dart
Normal file
0
lib/pages/edit_account.dart
Normal file
32
lib/pages/edit_contact.dart
Normal file
32
lib/pages/edit_contact.dart
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
class ContactsPage extends ConsumerWidget {
|
||||||
|
const ContactsPage({super.key});
|
||||||
|
static const path = '/contacts';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: null,
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Text("Contacts Page"),
|
||||||
|
// ElevatedButton(
|
||||||
|
// onPressed: () async {
|
||||||
|
// ref.watch(authNotifierProvider.notifier).login(
|
||||||
|
// "myEmail",
|
||||||
|
// "myPassword",
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// child: const Text("Login"),
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,48 +1,29 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
class MyHomePage extends StatefulWidget {
|
class HomePage extends ConsumerWidget {
|
||||||
const MyHomePage({super.key, required this.title});
|
const HomePage({super.key});
|
||||||
|
static const path = '/home';
|
||||||
final String title;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MyHomePage> createState() => _MyHomePageState();
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
}
|
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
|
||||||
int _counter = 0;
|
|
||||||
|
|
||||||
void _incrementCounter() {
|
|
||||||
setState(() {
|
|
||||||
_counter++;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(title: const Text("VeilidChat")),
|
||||||
title: Text(widget.title),
|
|
||||||
),
|
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
const Text(
|
children: [
|
||||||
'You have pushed the button this many times:',
|
const Text("Home Page"),
|
||||||
),
|
ElevatedButton(
|
||||||
Text(
|
onPressed: () {
|
||||||
'$_counter',
|
ref.watch(authNotifierProvider.notifier).logout();
|
||||||
style: Theme.of(context).textTheme.headline4,
|
},
|
||||||
|
child: const Text("Logout"),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
|
||||||
onPressed: _incrementCounter,
|
|
||||||
tooltip: 'Increment',
|
|
||||||
child: const Icon(Icons.add),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
lib/pages/index.dart
Normal file
13
lib/pages/index.dart
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class IndexPage extends StatelessWidget {
|
||||||
|
const IndexPage({super.key});
|
||||||
|
static const path = '/';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Center(child: Text("Index Page")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
32
lib/pages/login.dart
Normal file
32
lib/pages/login.dart
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
class LoginPage extends ConsumerWidget {
|
||||||
|
const LoginPage({super.key});
|
||||||
|
static const path = '/login';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: null,
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Text("Login Page"),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
ref.watch(authNotifierProvider.notifier).login(
|
||||||
|
"myEmail",
|
||||||
|
"myPassword",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text("Login"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
0
lib/pages/new_account.dart
Normal file
0
lib/pages/new_account.dart
Normal file
2
lib/pages/pages.dart
Normal file
2
lib/pages/pages.dart
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export 'home.dart';
|
||||||
|
export 'splash.dart';
|
0
lib/pages/settings.dart
Normal file
0
lib/pages/settings.dart
Normal file
21
lib/router/router.dart
Normal file
21
lib/router/router.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
import 'router_notifier.dart';
|
||||||
|
|
||||||
|
final _key = GlobalKey<NavigatorState>(debugLabel: 'routerKey');
|
||||||
|
|
||||||
|
/// This simple provider caches our GoRouter.
|
||||||
|
final routerProvider = Provider.autoDispose<GoRouter>((ref) {
|
||||||
|
final notifier = ref.watch(routerNotifierProvider.notifier);
|
||||||
|
|
||||||
|
return GoRouter(
|
||||||
|
navigatorKey: _key,
|
||||||
|
refreshListenable: notifier,
|
||||||
|
debugLogDiagnostics: true,
|
||||||
|
initialLocation: SplashPage.path,
|
||||||
|
routes: notifier.routes,
|
||||||
|
redirect: notifier.redirect,
|
||||||
|
);
|
||||||
|
});
|
146
lib/router/router_notifier.dart
Normal file
146
lib/router/router_notifier.dart
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
import '../entities/user_role.dart';
|
||||||
|
import '../pages/pages.dart';
|
||||||
|
import '../state/auth.dart';
|
||||||
|
import '../state/permissions.dart';
|
||||||
|
|
||||||
|
/// This notifier is meant to implement the [Listenable] our [GoRouter] needs.
|
||||||
|
///
|
||||||
|
/// We aim to trigger redirects whenever's needed.
|
||||||
|
/// This is done by calling our (only) listener everytime we want to notify stuff.
|
||||||
|
/// This allows to centralize global redirecting logic in this class.
|
||||||
|
/// In this simple case, we just listen to auth changes.
|
||||||
|
///
|
||||||
|
/// SIDE NOTE.
|
||||||
|
/// This might look overcomplicated at a first glance;
|
||||||
|
/// Instead, this method aims to follow some good some good practices:
|
||||||
|
/// 1. It doesn't require us to pipe down any `ref` parameter
|
||||||
|
/// 2. It works as a complete replacement for [ChangeNotifier] (it's a [Listenable] implementation)
|
||||||
|
/// 3. It allows for listening to multiple providers if needed (we do have a [Ref] now!)
|
||||||
|
class RouterNotifier extends AutoDisposeAsyncNotifier<void>
|
||||||
|
implements Listenable {
|
||||||
|
VoidCallback? routerListener;
|
||||||
|
bool isAuth = false; // Useful for our global redirect functio
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> build() async {
|
||||||
|
// One could watch more providers and write logic accordingly
|
||||||
|
|
||||||
|
isAuth = await ref.watch(
|
||||||
|
authNotifierProvider.selectAsync((data) => data != null),
|
||||||
|
);
|
||||||
|
|
||||||
|
ref.listenSelf((_, __) {
|
||||||
|
// One could write more conditional logic for when to call redirection
|
||||||
|
if (state.isLoading) return;
|
||||||
|
routerListener?.call();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Redirects the user when our authentication changes
|
||||||
|
String? redirect(BuildContext context, GoRouterState state) {
|
||||||
|
if (this.state.isLoading || this.state.hasError) return null;
|
||||||
|
|
||||||
|
final isSplash = state.location == IndexPage.path;
|
||||||
|
|
||||||
|
if (isSplash) {
|
||||||
|
return isAuth ? HomePage.path : LoginPage.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
final isLoggingIn = state.location == LoginPage.path;
|
||||||
|
if (isLoggingIn) return isAuth ? HomePage.path : null;
|
||||||
|
|
||||||
|
return isAuth ? null : IndexPage.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Our application routes. Obtained through code generation
|
||||||
|
List<GoRoute> get routes => [
|
||||||
|
GoRoute(
|
||||||
|
path: IndexPage.path,
|
||||||
|
builder: (context, state) => const IndexPage(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: HomePage.path,
|
||||||
|
builder: (context, state) => const HomePage(),
|
||||||
|
redirect: (context, state) async {
|
||||||
|
if (state.location == HomePage.path) return null;
|
||||||
|
|
||||||
|
final roleListener = ProviderScope.containerOf(context).listen(
|
||||||
|
permissionsProvider.select((value) => value.valueOrNull),
|
||||||
|
(previous, next) {},
|
||||||
|
);
|
||||||
|
|
||||||
|
final userRole = roleListener.read();
|
||||||
|
final redirectTo = userRole?.redirectBasedOn(state.location);
|
||||||
|
|
||||||
|
roleListener.close();
|
||||||
|
return redirectTo;
|
||||||
|
},
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: AdminPage.path,
|
||||||
|
builder: (context, state) => const AdminPage(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: UserPage.path,
|
||||||
|
builder: (context, state) => const UserPage(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: GuestPage.path,
|
||||||
|
builder: (context, state) => const GuestPage(),
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
GoRoute(
|
||||||
|
path: LoginPage.path,
|
||||||
|
builder: (context, state) => const LoginPage(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Adds [GoRouter]'s listener as specified by its [Listenable].
|
||||||
|
/// [GoRouteInformationProvider] uses this method on creation to handle its
|
||||||
|
/// internal [ChangeNotifier].
|
||||||
|
/// Check out the internal implementation of [GoRouter] and
|
||||||
|
/// [GoRouteInformationProvider] to see this in action.
|
||||||
|
@override
|
||||||
|
void addListener(VoidCallback listener) {
|
||||||
|
routerListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes [GoRouter]'s listener as specified by its [Listenable].
|
||||||
|
/// [GoRouteInformationProvider] uses this method when disposing,
|
||||||
|
/// so that it removes its callback when destroyed.
|
||||||
|
/// Check out the internal implementation of [GoRouter] and
|
||||||
|
/// [GoRouteInformationProvider] to see this in action.
|
||||||
|
@override
|
||||||
|
void removeListener(VoidCallback listener) {
|
||||||
|
routerListener = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final routerNotifierProvider =
|
||||||
|
AutoDisposeAsyncNotifierProvider<RouterNotifier, void>(() {
|
||||||
|
return RouterNotifier();
|
||||||
|
});
|
||||||
|
|
||||||
|
/// A simple extension to determine wherever should we redirect our users
|
||||||
|
extension RedirecttionBasedOnRole on UserRole {
|
||||||
|
/// Redirects the users based on [this] and its current [location]
|
||||||
|
String? redirectBasedOn(String location) {
|
||||||
|
switch (this) {
|
||||||
|
case UserRole.admin:
|
||||||
|
return null;
|
||||||
|
case UserRole.verifiedUser:
|
||||||
|
case UserRole.unverifiedUser:
|
||||||
|
if (location == AdminPage.path) return HomePage.path;
|
||||||
|
return null;
|
||||||
|
case UserRole.guest:
|
||||||
|
case UserRole.none:
|
||||||
|
if (location != HomePage.path) return HomePage.path;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
0
lib/state/connection_state.dart
Normal file
0
lib/state/connection_state.dart
Normal file
6
lib/veilid_support/config.dart
Normal file
6
lib/veilid_support/config.dart
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import 'package:veilid/veilid.dart';
|
||||||
|
|
||||||
|
Future<VeilidConfig> getVeilidChatConfig() async {
|
||||||
|
VeilidConfig config = await getDefaultVeilidConfig("VeilidChat");
|
||||||
|
return config;
|
||||||
|
}
|
63
lib/veilid_support/init.dart
Normal file
63
lib/veilid_support/init.dart
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import 'package:veilid/veilid.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'processor.dart';
|
||||||
|
|
||||||
|
Future<String> getVeilidVersion() async {
|
||||||
|
String veilidVersion;
|
||||||
|
try {
|
||||||
|
veilidVersion = Veilid.instance.veilidVersionString();
|
||||||
|
} on Exception {
|
||||||
|
veilidVersion = 'Failed to get veilid version.';
|
||||||
|
}
|
||||||
|
return veilidVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Veilid
|
||||||
|
// Call only once.
|
||||||
|
void _init() {
|
||||||
|
if (kIsWeb) {
|
||||||
|
var platformConfig = VeilidWASMConfig(
|
||||||
|
logging: VeilidWASMConfigLogging(
|
||||||
|
performance: VeilidWASMConfigLoggingPerformance(
|
||||||
|
enabled: true,
|
||||||
|
level: VeilidConfigLogLevel.debug,
|
||||||
|
logsInTimings: true,
|
||||||
|
logsInConsole: false),
|
||||||
|
api: VeilidWASMConfigLoggingApi(
|
||||||
|
enabled: true, level: VeilidConfigLogLevel.info)));
|
||||||
|
Veilid.instance.initializeVeilidCore(platformConfig.json);
|
||||||
|
} else {
|
||||||
|
var platformConfig = VeilidFFIConfig(
|
||||||
|
logging: VeilidFFIConfigLogging(
|
||||||
|
terminal: VeilidFFIConfigLoggingTerminal(
|
||||||
|
enabled: false,
|
||||||
|
level: VeilidConfigLogLevel.debug,
|
||||||
|
),
|
||||||
|
otlp: VeilidFFIConfigLoggingOtlp(
|
||||||
|
enabled: false,
|
||||||
|
level: VeilidConfigLogLevel.trace,
|
||||||
|
grpcEndpoint: "localhost:4317",
|
||||||
|
serviceName: "VeilidChat"),
|
||||||
|
api: VeilidFFIConfigLoggingApi(
|
||||||
|
enabled: true, level: VeilidConfigLogLevel.info)));
|
||||||
|
Veilid.instance.initializeVeilidCore(platformConfig.json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from FlutterFlow stub initialize() function upon Main page load
|
||||||
|
bool initialized = false;
|
||||||
|
Processor processor = Processor();
|
||||||
|
|
||||||
|
Future<void> initializeVeilid() async {
|
||||||
|
if (initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init Veilid
|
||||||
|
_init();
|
||||||
|
|
||||||
|
// Startup Veilid
|
||||||
|
await processor.startup();
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
}
|
136
lib/veilid_support/processor.dart
Normal file
136
lib/veilid_support/processor.dart
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:veilid/veilid.dart';
|
||||||
|
import 'config.dart';
|
||||||
|
import 'veilid_log.dart';
|
||||||
|
import '../log/loggy.dart';
|
||||||
|
|
||||||
|
class Processor {
|
||||||
|
String _veilidVersion = "";
|
||||||
|
bool _startedUp = false;
|
||||||
|
Stream<VeilidUpdate>? _updateStream;
|
||||||
|
Future<void>? _updateProcessor;
|
||||||
|
|
||||||
|
Processor();
|
||||||
|
|
||||||
|
Future<void> startup() async {
|
||||||
|
if (_startedUp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_veilidVersion = Veilid.instance.veilidVersionString();
|
||||||
|
} on Exception {
|
||||||
|
_veilidVersion = 'Failed to get veilid version.';
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case of hot restart shut down first
|
||||||
|
try {
|
||||||
|
await Veilid.instance.shutdownVeilidCore();
|
||||||
|
} on Exception {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateStream =
|
||||||
|
await Veilid.instance.startupVeilidCore(await getVeilidChatConfig());
|
||||||
|
_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
|
||||||
|
var connectionState = "";
|
||||||
|
var checkPublicInternet = false;
|
||||||
|
switch (updateAttachment.state.state) {
|
||||||
|
case AttachmentState.detached:
|
||||||
|
connectionState = "detached";
|
||||||
|
break;
|
||||||
|
case AttachmentState.detaching:
|
||||||
|
connectionState = "detaching";
|
||||||
|
break;
|
||||||
|
case AttachmentState.attaching:
|
||||||
|
connectionState = "attaching";
|
||||||
|
break;
|
||||||
|
case AttachmentState.attachedWeak:
|
||||||
|
checkPublicInternet = true;
|
||||||
|
connectionState = "weak";
|
||||||
|
break;
|
||||||
|
case AttachmentState.attachedGood:
|
||||||
|
checkPublicInternet = true;
|
||||||
|
connectionState = "good";
|
||||||
|
break;
|
||||||
|
case AttachmentState.attachedStrong:
|
||||||
|
checkPublicInternet = true;
|
||||||
|
connectionState = "strong";
|
||||||
|
break;
|
||||||
|
case AttachmentState.fullyAttached:
|
||||||
|
checkPublicInternet = true;
|
||||||
|
connectionState = "full";
|
||||||
|
break;
|
||||||
|
case AttachmentState.overAttached:
|
||||||
|
checkPublicInternet = true;
|
||||||
|
connectionState = "over";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (checkPublicInternet) {
|
||||||
|
if (!updateAttachment.state.publicInternetReady) {
|
||||||
|
connectionState = "attaching";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FFAppState().update(() {
|
||||||
|
FFAppState().ConnectionState = connectionState;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> processUpdateConfig(VeilidUpdateConfig updateConfig) async {
|
||||||
|
//loggy.info("Config: ${updateConfig.json}");
|
||||||
|
// xxx: store in flutterflow local state? do we need this for anything?
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> processUpdateNetwork(VeilidUpdateNetwork updateNetwork) async {
|
||||||
|
//loggy.info("Network: ${updateNetwork.json}");
|
||||||
|
// xxx: store in flutterflow local state? do we need this for anything?
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> processUpdates() async {
|
||||||
|
var 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.json}");
|
||||||
|
} else if (update is VeilidAppCall) {
|
||||||
|
log.info("AppCall: ${update.json}");
|
||||||
|
} else {
|
||||||
|
log.trace("Update: ${update.json}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
lib/veilid_support/tools.dart
Normal file
16
lib/veilid_support/tools.dart
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import 'package:veilid/base64url_no_pad.dart';
|
||||||
|
|
||||||
|
bool isValidDHTKey(String key) {
|
||||||
|
if (key.length != 43) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var dec = base64UrlNoPadDecode(key);
|
||||||
|
if (dec.length != 32) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
0
lib/veilid_support/veilid_support.dart
Normal file
0
lib/veilid_support/veilid_support.dart
Normal file
@ -254,6 +254,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
|
go_router:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: go_router
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.1"
|
||||||
graphs:
|
graphs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -688,4 +695,4 @@ packages:
|
|||||||
version: "3.1.1"
|
version: "3.1.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.18.6 <3.0.0"
|
dart: ">=2.18.6 <3.0.0"
|
||||||
flutter: ">=3.0.0"
|
flutter: ">=3.3.0"
|
||||||
|
@ -47,6 +47,7 @@ dependencies:
|
|||||||
path: ../veilid/veilid-flutter
|
path: ../veilid/veilid-flutter
|
||||||
animated_theme_switcher: ^2.0.7
|
animated_theme_switcher: ^2.0.7
|
||||||
shared_preferences: ^2.0.15
|
shared_preferences: ^2.0.15
|
||||||
|
go_router: ^6.0.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@ -5,26 +5,26 @@
|
|||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
// tree, read text, and verify that the values of widget properties are correct.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
// import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
// import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'package:veilidchat/main.dart';
|
// import 'package:veilidchat/main.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
// testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
// Build our app and trigger a frame.
|
// // Build our app and trigger a frame.
|
||||||
await tester.pumpWidget(const MyApp());
|
// await tester.pumpWidget(const MyApp());
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
// // Verify that our counter starts at 0.
|
||||||
expect(find.text('0'), findsOneWidget);
|
// expect(find.text('0'), findsOneWidget);
|
||||||
expect(find.text('1'), findsNothing);
|
// expect(find.text('1'), findsNothing);
|
||||||
|
|
||||||
// Tap the '+' icon and trigger a frame.
|
// // Tap the '+' icon and trigger a frame.
|
||||||
await tester.tap(find.byIcon(Icons.add));
|
// await tester.tap(find.byIcon(Icons.add));
|
||||||
await tester.pump();
|
// await tester.pump();
|
||||||
|
|
||||||
// Verify that our counter has incremented.
|
// // Verify that our counter has incremented.
|
||||||
expect(find.text('0'), findsNothing);
|
// expect(find.text('0'), findsNothing);
|
||||||
expect(find.text('1'), findsOneWidget);
|
// expect(find.text('1'), findsOneWidget);
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user