From c22d6fcff87dbbbcdcb03149d14b7beb02bfb4b1 Mon Sep 17 00:00:00 2001
From: John Smith <jsmith@example.com>
Date: Sun, 8 Jan 2023 22:27:33 -0500
Subject: [PATCH] scaffolding

---
 ios/Flutter/Debug.xcconfig                    |   1 +
 ios/Flutter/Release.xcconfig                  |   1 +
 ios/Podfile                                   |  41 ++
 lib/app.dart                                  |  25 +
 lib/log/loggy.dart                            | 105 ++++
 lib/log/state_logger.dart                     |  21 +
 lib/main.dart                                 | 132 +----
 lib/pages/home.dart                           |  48 ++
 lib/theme/theme_service.dart                  |  57 ++
 lib/theme/themes.dart                         |  37 ++
 lib/veilid_support/veilid_log.dart            |  74 +++
 linux/flutter/generated_plugin_registrant.cc  |   4 +
 linux/flutter/generated_plugins.cmake         |   1 +
 macos/Flutter/Flutter-Debug.xcconfig          |   1 +
 macos/Flutter/Flutter-Release.xcconfig        |   1 +
 macos/Flutter/GeneratedPluginRegistrant.swift |   6 +
 macos/Podfile                                 |  40 ++
 pubspec.lock                                  | 533 +++++++++++++++++-
 pubspec.yaml                                  |  22 +-
 .../flutter/generated_plugin_registrant.cc    |   3 +
 windows/flutter/generated_plugins.cmake       |   1 +
 21 files changed, 1037 insertions(+), 117 deletions(-)
 create mode 100644 ios/Podfile
 create mode 100644 lib/app.dart
 create mode 100644 lib/log/loggy.dart
 create mode 100644 lib/log/state_logger.dart
 create mode 100644 lib/pages/home.dart
 create mode 100644 lib/theme/theme_service.dart
 create mode 100644 lib/theme/themes.dart
 create mode 100644 lib/veilid_support/veilid_log.dart
 create mode 100644 macos/Podfile

diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig
index 592ceee..ec97fc6 100644
--- a/ios/Flutter/Debug.xcconfig
+++ b/ios/Flutter/Debug.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
 #include "Generated.xcconfig"
diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig
index 592ceee..c4855bf 100644
--- a/ios/Flutter/Release.xcconfig
+++ b/ios/Flutter/Release.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
 #include "Generated.xcconfig"
diff --git a/ios/Podfile b/ios/Podfile
new file mode 100644
index 0000000..88359b2
--- /dev/null
+++ b/ios/Podfile
@@ -0,0 +1,41 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '11.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+  'Debug' => :debug,
+  'Profile' => :release,
+  'Release' => :release,
+}
+
+def flutter_root
+  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+  unless File.exist?(generated_xcode_build_settings_path)
+    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+  end
+
+  File.foreach(generated_xcode_build_settings_path) do |line|
+    matches = line.match(/FLUTTER_ROOT\=(.*)/)
+    return matches[1].strip if matches
+  end
+  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+  use_frameworks!
+  use_modular_headers!
+
+  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+end
+
+post_install do |installer|
+  installer.pods_project.targets.each do |target|
+    flutter_additional_ios_build_settings(target)
+  end
+end
diff --git a/lib/app.dart b/lib/app.dart
new file mode 100644
index 0000000..40be51f
--- /dev/null
+++ b/lib/app.dart
@@ -0,0 +1,25 @@
+import 'package:flutter/material.dart';
+import 'package:animated_theme_switcher/animated_theme_switcher.dart';s
+
+class VeilidChatApp extends StatelessWidget {
+  const VeilidChatApp({
+    Key? key,
+    required this.theme,
+  }) : super(key: key);
+
+  final ThemeData theme;
+
+  @override
+  Widget build(BuildContext context) {
+    return ThemeProvider(
+      initTheme: theme,
+      builder: (_, theme) {
+        return MaterialApp(
+          title: 'VeilidChat',
+          theme: theme,
+          home: const MyHomePage(title: 'Flutter Demo Home Page'),
+        );
+      },
+    );
+  }
+}
diff --git a/lib/log/loggy.dart b/lib/log/loggy.dart
new file mode 100644
index 0000000..290a290
--- /dev/null
+++ b/lib/log/loggy.dart
@@ -0,0 +1,105 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/foundation.dart';
+import 'package:loggy/loggy.dart';
+import 'package:ansicolor/ansicolor.dart';
+
+// Loggy tools
+const LogLevel traceLevel = LogLevel('Trace', 1);
+
+String wrapWithLogColor(LogLevel? level, String text) {
+  if (level == null) {
+    return text;
+  }
+  final pen = AnsiPen();
+  ansiColorDisabled = false;
+  switch (level) {
+    case LogLevel.error:
+      pen
+        ..reset()
+        ..red(bold: true);
+      return pen(text);
+    case LogLevel.warning:
+      pen
+        ..reset()
+        ..yellow(bold: true);
+      return pen(text);
+    case LogLevel.info:
+      pen
+        ..reset()
+        ..white(bold: true);
+      return pen(text);
+    case LogLevel.debug:
+      pen
+        ..reset()
+        ..green(bold: true);
+      return pen(text);
+    case traceLevel:
+      pen
+        ..reset()
+        ..blue(bold: true);
+      return pen(text);
+  }
+  return text;
+}
+
+extension PrettyPrintLogRecord on LogRecord {
+  String pretty() {
+    final lstr =
+        wrapWithLogColor(level, '[${level.toString().substring(0, 1)}]');
+    return '$lstr $message';
+  }
+}
+
+class CallbackPrinter extends LoggyPrinter {
+  CallbackPrinter() : super();
+
+  void Function(LogRecord)? callback;
+
+  @override
+  void onLog(LogRecord record) {
+    debugPrint(record.pretty());
+    callback?.call(record);
+  }
+
+  void setCallback(Function(LogRecord)? cb) {
+    callback = cb;
+  }
+}
+
+var globalTerminalPrinter = CallbackPrinter();
+
+extension TraceLoggy on Loggy {
+  void trace(dynamic message, [Object? error, StackTrace? stackTrace]) =>
+      this.log(traceLevel, message, error, stackTrace);
+}
+
+LogOptions getLogOptions(LogLevel? level) {
+  return LogOptions(
+    level ?? LogLevel.all,
+    stackTraceLevel: LogLevel.error,
+  );
+}
+
+class RootLoggy implements LoggyType {
+  @override
+  Loggy<RootLoggy> get loggy => Loggy<RootLoggy>('');
+}
+
+Loggy get log => Loggy<RootLoggy>('');
+
+void initLoggy() {
+  Loggy.initLoggy(
+    logPrinter: globalTerminalPrinter,
+    logOptions: getLogOptions(null),
+  );
+
+  const isTrace = String.fromEnvironment("logTrace", defaultValue: "") != "";
+  LogLevel logLevel;
+  if (isTrace) {
+    logLevel = traceLevel;
+  } else {
+    logLevel = kDebugMode ? LogLevel.debug : LogLevel.info;
+  }
+
+  Loggy('').level = getLogOptions(logLevel);
+}
diff --git a/lib/log/state_logger.dart b/lib/log/state_logger.dart
new file mode 100644
index 0000000..045a983
--- /dev/null
+++ b/lib/log/state_logger.dart
@@ -0,0 +1,21 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'loggy.dart';
+
+class StateLogger extends ProviderObserver {
+  const StateLogger();
+  @override
+  void didUpdateProvider(
+    ProviderBase provider,
+    Object? previousValue,
+    Object? newValue,
+    ProviderContainer container,
+  ) {
+    log.debug('''{
+  provider: ${provider.name ?? provider.runtimeType},
+  oldValue: $previousValue,
+  newValue: $newValue
+}
+''');
+    super.didUpdateProvider(provider, previousValue, newValue, container);
+  }
+}
diff --git a/lib/main.dart b/lib/main.dart
index e016029..c8633ab 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,115 +1,23 @@
 import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'log/loggy.dart';
+import 'log/state_logger.dart';
+import 'veilid_support/veilid_log.dart';
+import 'theme/theme_service.dart';
+import 'app.dart';
 
-void main() {
-  runApp(const MyApp());
-}
-
-class MyApp extends StatelessWidget {
-  const MyApp({super.key});
-
-  // This widget is the root of your application.
-  @override
-  Widget build(BuildContext context) {
-    return MaterialApp(
-      title: 'Flutter Demo',
-      theme: ThemeData(
-        // This is the theme of your application.
-        //
-        // Try running your application with "flutter run". You'll see the
-        // application has a blue toolbar. Then, without quitting the app, try
-        // changing the primarySwatch below to Colors.green and then invoke
-        // "hot reload" (press "r" in the console where you ran "flutter run",
-        // or simply save your changes to "hot reload" in a Flutter IDE).
-        // Notice that the counter didn't reset back to zero; the application
-        // is not restarted.
-        primarySwatch: Colors.blue,
-      ),
-      home: const MyHomePage(title: 'Flutter Demo Home Page'),
-    );
-  }
-}
-
-class MyHomePage extends StatefulWidget {
-  const MyHomePage({super.key, required this.title});
-
-  // This widget is the home page of your application. It is stateful, meaning
-  // that it has a State object (defined below) that contains fields that affect
-  // how it looks.
-
-  // This class is the configuration for the state. It holds the values (in this
-  // case the title) provided by the parent (in this case the App widget) and
-  // used by the build method of the State. Fields in a Widget subclass are
-  // always marked "final".
-
-  final String title;
-
-  @override
-  State<MyHomePage> createState() => _MyHomePageState();
-}
-
-class _MyHomePageState extends State<MyHomePage> {
-  int _counter = 0;
-
-  void _incrementCounter() {
-    setState(() {
-      // This call to setState tells the Flutter framework that something has
-      // changed in this State, which causes it to rerun the build method below
-      // so that the display can reflect the updated values. If we changed
-      // _counter without calling setState(), then the build method would not be
-      // called again, and so nothing would appear to happen.
-      _counter++;
-    });
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    // This method is rerun every time setState is called, for instance as done
-    // by the _incrementCounter method above.
-    //
-    // The Flutter framework has been optimized to make rerunning build methods
-    // fast, so that you can just rebuild anything that needs updating rather
-    // than having to individually change instances of widgets.
-    return Scaffold(
-      appBar: AppBar(
-        // Here we take the value from the MyHomePage object that was created by
-        // the App.build method, and use it to set our appbar title.
-        title: Text(widget.title),
-      ),
-      body: Center(
-        // Center is a layout widget. It takes a single child and positions it
-        // in the middle of the parent.
-        child: Column(
-          // Column is also a layout widget. It takes a list of children and
-          // arranges them vertically. By default, it sizes itself to fit its
-          // children horizontally, and tries to be as tall as its parent.
-          //
-          // Invoke "debug painting" (press "p" in the console, choose the
-          // "Toggle Debug Paint" action from the Flutter Inspector in Android
-          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
-          // to see the wireframe for each widget.
-          //
-          // Column has various properties to control how it sizes itself and
-          // how it positions its children. Here we use mainAxisAlignment to
-          // center the children vertically; the main axis here is the vertical
-          // axis because Columns are vertical (the cross axis would be
-          // horizontal).
-          mainAxisAlignment: MainAxisAlignment.center,
-          children: <Widget>[
-            const Text(
-              'You have pushed the button this many times:',
-            ),
-            Text(
-              '$_counter',
-              style: Theme.of(context).textTheme.headline4,
-            ),
-          ],
-        ),
-      ),
-      floatingActionButton: FloatingActionButton(
-        onPressed: _incrementCounter,
-        tooltip: 'Increment',
-        child: const Icon(Icons.add),
-      ), // This trailing comma makes auto-formatting nicer for build methods.
-    );
-  }
+void main() async {
+  // Logs
+  initLoggy();
+  initVeilidLog();
+
+  // Run the app
+  WidgetsFlutterBinding.ensureInitialized();
+  final themeService = await ThemeService.instance;
+  var initTheme = themeService.initial;
+  runApp(
+    ProviderScope(
+        observers: [const StateLogger()],
+        child: VeilidChatApp(theme: initTheme)),
+  );
 }
diff --git a/lib/pages/home.dart b/lib/pages/home.dart
new file mode 100644
index 0000000..594b3de
--- /dev/null
+++ b/lib/pages/home.dart
@@ -0,0 +1,48 @@
+import 'package:flutter/material.dart';
+
+class MyHomePage extends StatefulWidget {
+  const MyHomePage({super.key, required this.title});
+
+  final String title;
+
+  @override
+  State<MyHomePage> createState() => _MyHomePageState();
+}
+
+class _MyHomePageState extends State<MyHomePage> {
+  int _counter = 0;
+
+  void _incrementCounter() {
+    setState(() {
+      _counter++;
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(widget.title),
+      ),
+      body: Center(
+        child: Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: <Widget>[
+            const Text(
+              'You have pushed the button this many times:',
+            ),
+            Text(
+              '$_counter',
+              style: Theme.of(context).textTheme.headline4,
+            ),
+          ],
+        ),
+      ),
+      floatingActionButton: FloatingActionButton(
+        onPressed: _incrementCounter,
+        tooltip: 'Increment',
+        child: const Icon(Icons.add),
+      ),
+    );
+  }
+}
diff --git a/lib/theme/theme_service.dart b/lib/theme/theme_service.dart
new file mode 100644
index 0000000..8ed215d
--- /dev/null
+++ b/lib/theme/theme_service.dart
@@ -0,0 +1,57 @@
+import 'package:flutter/material.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+import 'themes.dart';
+
+class ThemeService {
+  ThemeService._();
+  static late SharedPreferences prefs;
+  static ThemeService? _instance;
+
+  static Future<ThemeService> get instance async {
+    if (_instance == null) {
+      prefs = await SharedPreferences.getInstance();
+      _instance = ThemeService._();
+    }
+    return _instance!;
+  }
+
+  final allThemes = <String, ThemeData>{
+    'dark': darkTheme,
+    'light': lightTheme,
+    'pink': pinkTheme,
+    'darkBlue': darkBlueTheme,
+    'halloween': halloweenTheme,
+  };
+
+  String get previousThemeName {
+    String? themeName = prefs.getString('previousThemeName');
+    if (themeName == null) {
+      final isPlatformDark =
+          WidgetsBinding.instance.window.platformBrightness == Brightness.dark;
+      themeName = isPlatformDark ? 'light' : 'dark';
+    }
+    return themeName;
+  }
+
+  get initial {
+    String? themeName = prefs.getString('theme');
+    if (themeName == null) {
+      final isPlatformDark =
+          WidgetsBinding.instance.window.platformBrightness == Brightness.dark;
+      themeName = isPlatformDark ? 'dark' : 'light';
+    }
+    return allThemes[themeName];
+  }
+
+  save(String newThemeName) {
+    var currentThemeName = prefs.getString('theme');
+    if (currentThemeName != null) {
+      prefs.setString('previousThemeName', currentThemeName);
+    }
+    prefs.setString('theme', newThemeName);
+  }
+
+  ThemeData getByName(String name) {
+    return allThemes[name]!;
+  }
+}
diff --git a/lib/theme/themes.dart b/lib/theme/themes.dart
new file mode 100644
index 0000000..51eb712
--- /dev/null
+++ b/lib/theme/themes.dart
@@ -0,0 +1,37 @@
+import 'package:flutter/material.dart';
+
+ThemeData lightTheme = ThemeData.light();
+
+ThemeData darkTheme = ThemeData.dark();
+
+ThemeData pinkTheme = lightTheme.copyWith(
+    primaryColor: const Color(0xFFF49FB6),
+    scaffoldBackgroundColor: const Color(0xFFFAF8F0),
+    floatingActionButtonTheme: const FloatingActionButtonThemeData(
+      foregroundColor: Color(0xFF24737c),
+      backgroundColor: Color(0xFFA6E0DE),
+    ),
+    textTheme: const TextTheme(
+      bodyText1: TextStyle(
+        color: Colors.black87,
+      ),
+    ));
+
+ThemeData halloweenTheme = lightTheme.copyWith(
+  primaryColor: const Color(0xFF55705A),
+  scaffoldBackgroundColor: const Color(0xFFE48873),
+  floatingActionButtonTheme: const FloatingActionButtonThemeData(
+    foregroundColor: Color(0xFFea8e71),
+    backgroundColor: Color(0xFF2b2119),
+  ),
+);
+
+ThemeData darkBlueTheme = ThemeData.dark().copyWith(
+  primaryColor: const Color(0xFF1E1E2C),
+  scaffoldBackgroundColor: const Color(0xFF2D2D44),
+  textTheme: const TextTheme(
+    bodyText1: TextStyle(
+      color: Color(0xFF33E1Ed),
+    ),
+  ),
+);
\ No newline at end of file
diff --git a/lib/veilid_support/veilid_log.dart b/lib/veilid_support/veilid_log.dart
new file mode 100644
index 0000000..c5cfb5f
--- /dev/null
+++ b/lib/veilid_support/veilid_log.dart
@@ -0,0 +1,74 @@
+import 'package:flutter/foundation.dart';
+import 'package:veilid/veilid.dart';
+import 'package:loggy/loggy.dart';
+import '../log/loggy.dart';
+
+VeilidConfigLogLevel convertToVeilidConfigLogLevel(LogLevel? level) {
+  if (level == null) {
+    return VeilidConfigLogLevel.off;
+  }
+  switch (level) {
+    case LogLevel.error:
+      return VeilidConfigLogLevel.error;
+    case LogLevel.warning:
+      return VeilidConfigLogLevel.warn;
+    case LogLevel.info:
+      return VeilidConfigLogLevel.info;
+    case LogLevel.debug:
+      return VeilidConfigLogLevel.debug;
+    case traceLevel:
+      return VeilidConfigLogLevel.trace;
+  }
+  return VeilidConfigLogLevel.off;
+}
+
+void setVeilidLogLevel(LogLevel? level) {
+  Veilid.instance.changeLogLevel("all", convertToVeilidConfigLogLevel(level));
+}
+
+class VeilidLoggy implements LoggyType {
+  @override
+  Loggy<VeilidLoggy> get loggy => Loggy<VeilidLoggy>('Veilid');
+}
+
+Loggy get _veilidLoggy => Loggy<VeilidLoggy>('Veilid');
+
+Future<void> processLog(VeilidLog log) async {
+  StackTrace? stackTrace;
+  Object? error;
+  final backtrace = log.backtrace;
+  if (backtrace != null) {
+    stackTrace =
+        StackTrace.fromString("$backtrace\n${StackTrace.current.toString()}");
+    error = 'embedded stack trace for ${log.logLevel} ${log.message}';
+  }
+
+  switch (log.logLevel) {
+    case VeilidLogLevel.error:
+      _veilidLoggy.error(log.message, error, stackTrace);
+      break;
+    case VeilidLogLevel.warn:
+      _veilidLoggy.warning(log.message, error, stackTrace);
+      break;
+    case VeilidLogLevel.info:
+      _veilidLoggy.info(log.message, error, stackTrace);
+      break;
+    case VeilidLogLevel.debug:
+      _veilidLoggy.debug(log.message, error, stackTrace);
+      break;
+    case VeilidLogLevel.trace:
+      _veilidLoggy.trace(log.message, error, stackTrace);
+      break;
+  }
+}
+
+void initVeilidLog() {
+  const isTrace = String.fromEnvironment("logTrace", defaultValue: "") != "";
+  LogLevel logLevel;
+  if (isTrace) {
+    logLevel = traceLevel;
+  } else {
+    logLevel = kDebugMode ? LogLevel.debug : LogLevel.info;
+  }
+  setVeilidLogLevel(logLevel);
+}
diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc
index e71a16d..cebc32d 100644
--- a/linux/flutter/generated_plugin_registrant.cc
+++ b/linux/flutter/generated_plugin_registrant.cc
@@ -6,6 +6,10 @@
 
 #include "generated_plugin_registrant.h"
 
+#include <veilid/veilid_plugin.h>
 
 void fl_register_plugins(FlPluginRegistry* registry) {
+  g_autoptr(FlPluginRegistrar) veilid_registrar =
+      fl_plugin_registry_get_registrar_for_plugin(registry, "VeilidPlugin");
+  veilid_plugin_register_with_registrar(veilid_registrar);
 }
diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake
index 2e1de87..003d7b5 100644
--- a/linux/flutter/generated_plugins.cmake
+++ b/linux/flutter/generated_plugins.cmake
@@ -3,6 +3,7 @@
 #
 
 list(APPEND FLUTTER_PLUGIN_LIST
+  veilid
 )
 
 list(APPEND FLUTTER_FFI_PLUGIN_LIST
diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig
index c2efd0b..4b81f9b 100644
--- a/macos/Flutter/Flutter-Debug.xcconfig
+++ b/macos/Flutter/Flutter-Debug.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
 #include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig
index c2efd0b..5caa9d1 100644
--- a/macos/Flutter/Flutter-Release.xcconfig
+++ b/macos/Flutter/Flutter-Release.xcconfig
@@ -1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
 #include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index cccf817..b43fb2d 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,6 +5,12 @@
 import FlutterMacOS
 import Foundation
 
+import path_provider_macos
+import shared_preferences_macos
+import veilid
 
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+  PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
+  SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
+  VeilidPlugin.register(with: registry.registrar(forPlugin: "VeilidPlugin"))
 }
diff --git a/macos/Podfile b/macos/Podfile
new file mode 100644
index 0000000..dade8df
--- /dev/null
+++ b/macos/Podfile
@@ -0,0 +1,40 @@
+platform :osx, '10.11'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+  'Debug' => :debug,
+  'Profile' => :release,
+  'Release' => :release,
+}
+
+def flutter_root
+  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
+  unless File.exist?(generated_xcode_build_settings_path)
+    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
+  end
+
+  File.foreach(generated_xcode_build_settings_path) do |line|
+    matches = line.match(/FLUTTER_ROOT\=(.*)/)
+    return matches[1].strip if matches
+  end
+  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_macos_podfile_setup
+
+target 'Runner' do
+  use_frameworks!
+  use_modular_headers!
+
+  flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
+end
+
+post_install do |installer|
+  installer.pods_project.targets.each do |target|
+    flutter_additional_macos_build_settings(target)
+  end
+end
diff --git a/pubspec.lock b/pubspec.lock
index 0f3a736..271555a 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1,6 +1,41 @@
 # Generated by pub
 # See https://dart.dev/tools/pub/glossary#lockfile
 packages:
+  _fe_analyzer_shared:
+    dependency: transitive
+    description:
+      name: _fe_analyzer_shared
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "50.0.0"
+  analyzer:
+    dependency: transitive
+    description:
+      name: analyzer
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "5.2.0"
+  animated_theme_switcher:
+    dependency: "direct main"
+    description:
+      name: animated_theme_switcher
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.7"
+  ansicolor:
+    dependency: "direct main"
+    description:
+      name: ansicolor
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.1"
+  args:
+    dependency: transitive
+    description:
+      name: args
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.3.1"
   async:
     dependency: transitive
     description:
@@ -15,6 +50,69 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.1.0"
+  build:
+    dependency: transitive
+    description:
+      name: build
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.3.1"
+  build_config:
+    dependency: transitive
+    description:
+      name: build_config
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.1"
+  build_daemon:
+    dependency: transitive
+    description:
+      name: build_daemon
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.1.0"
+  build_resolvers:
+    dependency: transitive
+    description:
+      name: build_resolvers
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
+  build_runner:
+    dependency: "direct dev"
+    description:
+      name: build_runner
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.3.3"
+  build_runner_core:
+    dependency: transitive
+    description:
+      name: build_runner_core
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "7.2.7"
+  built_collection:
+    dependency: transitive
+    description:
+      name: built_collection
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "5.1.1"
+  built_value:
+    dependency: transitive
+    description:
+      name: built_value
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "8.4.2"
+  change_case:
+    dependency: transitive
+    description:
+      name: change_case
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.0"
   characters:
     dependency: transitive
     description:
@@ -22,6 +120,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.2.1"
+  checked_yaml:
+    dependency: transitive
+    description:
+      name: checked_yaml
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.2"
   clock:
     dependency: transitive
     description:
@@ -29,6 +134,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.1.1"
+  code_builder:
+    dependency: transitive
+    description:
+      name: code_builder
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "4.4.0"
   collection:
     dependency: transitive
     description:
@@ -36,6 +148,20 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.16.0"
+  convert:
+    dependency: transitive
+    description:
+      name: convert
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.1.1"
+  crypto:
+    dependency: transitive
+    description:
+      name: crypto
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.2"
   cupertino_icons:
     dependency: "direct main"
     description:
@@ -43,6 +169,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.0.5"
+  dart_style:
+    dependency: transitive
+    description:
+      name: dart_style
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.2.4"
   fake_async:
     dependency: transitive
     description:
@@ -50,11 +183,39 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.3.1"
+  ffi:
+    dependency: transitive
+    description:
+      name: ffi
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.1"
+  file:
+    dependency: transitive
+    description:
+      name: file
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "6.1.4"
+  fixnum:
+    dependency: transitive
+    description:
+      name: fixnum
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.1"
   flutter:
     dependency: "direct main"
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_hooks:
+    dependency: "direct main"
+    description:
+      name: flutter_hooks
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.18.5+1"
   flutter_lints:
     dependency: "direct dev"
     description:
@@ -62,11 +223,86 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.0.1"
+  flutter_riverpod:
+    dependency: "direct main"
+    description:
+      name: flutter_riverpod
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.3"
   flutter_test:
     dependency: "direct dev"
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_web_plugins:
+    dependency: transitive
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  frontend_server_client:
+    dependency: transitive
+    description:
+      name: frontend_server_client
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.2.0"
+  glob:
+    dependency: transitive
+    description:
+      name: glob
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.1"
+  graphs:
+    dependency: transitive
+    description:
+      name: graphs
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.2.0"
+  hooks_riverpod:
+    dependency: "direct main"
+    description:
+      name: hooks_riverpod
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.3"
+  http_multi_server:
+    dependency: transitive
+    description:
+      name: http_multi_server
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.2.1"
+  http_parser:
+    dependency: transitive
+    description:
+      name: http_parser
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "4.0.2"
+  io:
+    dependency: transitive
+    description:
+      name: io
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.3"
+  js:
+    dependency: transitive
+    description:
+      name: js
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.6.4"
+  json_annotation:
+    dependency: transitive
+    description:
+      name: json_annotation
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "4.7.0"
   lints:
     dependency: transitive
     description:
@@ -74,6 +310,20 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.0.1"
+  logging:
+    dependency: transitive
+    description:
+      name: logging
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.0"
+  loggy:
+    dependency: "direct main"
+    description:
+      name: loggy
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.3"
   matcher:
     dependency: transitive
     description:
@@ -95,18 +345,221 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.8.0"
-  path:
+  mime:
     dependency: transitive
+    description:
+      name: mime
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.3"
+  package_config:
+    dependency: transitive
+    description:
+      name: package_config
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
+  path:
+    dependency: "direct main"
     description:
       name: path
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.8.2"
+  path_provider:
+    dependency: "direct main"
+    description:
+      name: path_provider
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.11"
+  path_provider_android:
+    dependency: transitive
+    description:
+      name: path_provider_android
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.22"
+  path_provider_ios:
+    dependency: transitive
+    description:
+      name: path_provider_ios
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.11"
+  path_provider_linux:
+    dependency: transitive
+    description:
+      name: path_provider_linux
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.7"
+  path_provider_macos:
+    dependency: transitive
+    description:
+      name: path_provider_macos
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.6"
+  path_provider_platform_interface:
+    dependency: transitive
+    description:
+      name: path_provider_platform_interface
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.5"
+  path_provider_windows:
+    dependency: transitive
+    description:
+      name: path_provider_windows
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.3"
+  platform:
+    dependency: transitive
+    description:
+      name: platform
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.1.0"
+  plugin_platform_interface:
+    dependency: transitive
+    description:
+      name: plugin_platform_interface
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.3"
+  pool:
+    dependency: transitive
+    description:
+      name: pool
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.5.1"
+  process:
+    dependency: transitive
+    description:
+      name: process
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "4.2.4"
+  pub_semver:
+    dependency: transitive
+    description:
+      name: pub_semver
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.3"
+  pubspec_parse:
+    dependency: transitive
+    description:
+      name: pubspec_parse
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.2.1"
+  riverpod:
+    dependency: transitive
+    description:
+      name: riverpod
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.3"
+  riverpod_annotation:
+    dependency: "direct main"
+    description:
+      name: riverpod_annotation
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.1"
+  riverpod_generator:
+    dependency: "direct dev"
+    description:
+      name: riverpod_generator
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.1"
+  shared_preferences:
+    dependency: "direct main"
+    description:
+      name: shared_preferences
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.15"
+  shared_preferences_android:
+    dependency: transitive
+    description:
+      name: shared_preferences_android
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.14"
+  shared_preferences_ios:
+    dependency: transitive
+    description:
+      name: shared_preferences_ios
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.1"
+  shared_preferences_linux:
+    dependency: transitive
+    description:
+      name: shared_preferences_linux
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.2"
+  shared_preferences_macos:
+    dependency: transitive
+    description:
+      name: shared_preferences_macos
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.5"
+  shared_preferences_platform_interface:
+    dependency: transitive
+    description:
+      name: shared_preferences_platform_interface
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
+  shared_preferences_web:
+    dependency: transitive
+    description:
+      name: shared_preferences_web
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.4"
+  shared_preferences_windows:
+    dependency: transitive
+    description:
+      name: shared_preferences_windows
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.2"
+  shelf:
+    dependency: transitive
+    description:
+      name: shelf
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.4.0"
+  shelf_web_socket:
+    dependency: transitive
+    description:
+      name: shelf_web_socket
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.3"
   sky_engine:
     dependency: transitive
     description: flutter
     source: sdk
     version: "0.0.99"
+  source_gen:
+    dependency: transitive
+    description:
+      name: source_gen
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.2.6"
   source_span:
     dependency: transitive
     description:
@@ -121,6 +574,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.10.0"
+  state_notifier:
+    dependency: transitive
+    description:
+      name: state_notifier
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.7.2+1"
   stream_channel:
     dependency: transitive
     description:
@@ -128,6 +588,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.1.0"
+  stream_transform:
+    dependency: transitive
+    description:
+      name: stream_transform
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
   string_scanner:
     dependency: transitive
     description:
@@ -149,6 +616,27 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "0.4.12"
+  timing:
+    dependency: transitive
+    description:
+      name: timing
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.0"
+  typed_data:
+    dependency: transitive
+    description:
+      name: typed_data
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.3.1"
+  uuid:
+    dependency: "direct main"
+    description:
+      name: uuid
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.7"
   vector_math:
     dependency: transitive
     description:
@@ -156,5 +644,48 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.1.2"
+  veilid:
+    dependency: "direct main"
+    description:
+      path: "../veilid/veilid-flutter"
+      relative: true
+    source: path
+    version: "0.0.1"
+  watcher:
+    dependency: transitive
+    description:
+      name: watcher
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.2"
+  web_socket_channel:
+    dependency: transitive
+    description:
+      name: web_socket_channel
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.2.0"
+  win32:
+    dependency: transitive
+    description:
+      name: win32
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.1.3"
+  xdg_directories:
+    dependency: transitive
+    description:
+      name: xdg_directories
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.2.0+2"
+  yaml:
+    dependency: transitive
+    description:
+      name: yaml
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.1.1"
 sdks:
   dart: ">=2.18.6 <3.0.0"
+  flutter: ">=3.0.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index a77c579..13639a6 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -21,6 +21,7 @@ version: 1.0.0+1
 
 environment:
   sdk: '>=2.18.6 <3.0.0'
+  flutter: ">=3.0.0"
 
 # Dependencies specify other packages that your package needs in order to work.
 # To automatically upgrade your package dependencies to the latest versions
@@ -31,15 +32,28 @@ environment:
 dependencies:
   flutter:
     sdk: flutter
-
-
-  # The following adds the Cupertino Icons font to your application.
-  # Use with the CupertinoIcons class for iOS style icons.
+  flutter_hooks: ^0.18.0
+  hooks_riverpod: ^2.1.3
+  flutter_riverpod: ^2.1.3
+  riverpod_annotation: ^1.1.1
   cupertino_icons: ^1.0.2
+  ansicolor: ^2.0.1
+  loggy: ^2.0.3
+  uuid: ^3.0.7
+  path: ^1.8.2
+  path_provider: ^2.0.11
+  veilid:
+    #   veilid: ^0.0.1
+    path: ../veilid/veilid-flutter
+  animated_theme_switcher: ^2.0.7
+  shared_preferences: ^2.0.15
 
 dev_dependencies:
   flutter_test:
     sdk: flutter
+  build_runner:
+  riverpod_generator: ^1.1.1
+
 
   # The "flutter_lints" package below contains a set of recommended lints to
   # encourage good coding practices. The lint set provided by the package is
diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc
index 8b6d468..72dbdef 100644
--- a/windows/flutter/generated_plugin_registrant.cc
+++ b/windows/flutter/generated_plugin_registrant.cc
@@ -6,6 +6,9 @@
 
 #include "generated_plugin_registrant.h"
 
+#include <veilid/veilid_plugin.h>
 
 void RegisterPlugins(flutter::PluginRegistry* registry) {
+  VeilidPluginRegisterWithRegistrar(
+      registry->GetRegistrarForPlugin("VeilidPlugin"));
 }
diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake
index b93c4c3..658ec85 100644
--- a/windows/flutter/generated_plugins.cmake
+++ b/windows/flutter/generated_plugins.cmake
@@ -3,6 +3,7 @@
 #
 
 list(APPEND FLUTTER_PLUGIN_LIST
+  veilid
 )
 
 list(APPEND FLUTTER_FFI_PLUGIN_LIST