This commit is contained in:
Christien Rioux 2023-07-22 23:29:10 -04:00
parent 08dab73757
commit c08878b35a
39 changed files with 771 additions and 218 deletions

98
assets/images/icon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 38 KiB

129
assets/images/splash.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 41 KiB

41
assets/images/title.svg Normal file
View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1003 187" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,0.352497,-60.1919,-339.48)">
<rect id="Artboard1" x="60.192" y="963.071" width="1002.92" height="528.331" style="fill:none;"/>
<g id="Artboard11" serif:id="Artboard1">
<g transform="matrix(12.4032,0,0,35.1866,98.2148,1359.83)">
<g transform="matrix(7.73995,0,0,7.73995,0,-8.88178e-16)">
<path d="M0.375,-0L0,-0.375L0,-1L0.25,-1L0.25,-0.5L0.5,-0.25L0.75,-0.5L0.75,-1L1,-1L1,-0.375L0.625,-0L0.375,-0Z" style="fill:rgb(242,89,201);fill-rule:nonzero;"/>
</g>
<g transform="matrix(7.73995,0,0,7.73995,8.51093,-8.88178e-16)">
<path d="M0,-0L0,-1L1,-1L1,-0.75L0.25,-0.75L0.25,-0.625L1,-0.625L1,-0.375L0.25,-0.375L0.25,-0.25L1,-0.25L1,-0L0,-0Z" style="fill:rgb(242,89,201);fill-rule:nonzero;"/>
</g>
<g transform="matrix(7.73995,0,0,7.73995,17.0219,-8.88178e-16)">
<rect x="0" y="-1" width="0.25" height="1" style="fill:rgb(242,89,201);fill-rule:nonzero;"/>
</g>
<g transform="matrix(7.73995,0,0,7.73995,19.7278,-8.88178e-16)">
<path d="M0,-0L0,-1L0.25,-1L0.25,-0.25L1,-0.25L1,-0L0,-0Z" style="fill:rgb(242,89,201);fill-rule:nonzero;"/>
</g>
<g transform="matrix(7.73995,0,0,7.73995,28.2387,-8.88178e-16)">
<rect x="0" y="-1" width="0.25" height="1" style="fill:rgb(242,89,201);fill-rule:nonzero;"/>
</g>
<g transform="matrix(7.73995,0,0,7.73995,30.9447,-8.88178e-16)">
<path d="M0.688,-0.25L0.75,-0.313L0.75,-0.688L0.688,-0.75L0.25,-0.75L0.25,-0.25L0.688,-0.25ZM0,-0L0,-1L0.875,-1L1,-0.875L1,-0.125L0.875,-0L0,-0Z" style="fill:rgb(242,89,201);fill-rule:nonzero;"/>
</g>
<g transform="matrix(7.73995,0,0,7.73995,39.4556,-8.88178e-16)">
<path d="M0,-0L0,-1L1,-1L1,-0.75L0.25,-0.75L0.25,-0.25L1,-0.25L1,-0L0,-0Z" style="fill:rgb(58,162,232);fill-rule:nonzero;"/>
</g>
<g transform="matrix(7.73995,0,0,7.73995,47.9665,-8.88178e-16)">
<path d="M0,-0L0,-1L0.25,-1L0.25,-0.625L0.75,-0.625L0.75,-1L1,-1L1,-0L0.75,-0L0.75,-0.375L0.25,-0.375L0.25,-0L0,-0Z" style="fill:rgb(58,162,232);fill-rule:nonzero;"/>
</g>
<g transform="matrix(7.73995,0,0,7.73995,56.4775,-8.88178e-16)">
<path d="M0.75,-0.625L0.75,-0.75L0.25,-0.75L0.25,-0.625L0.75,-0.625ZM0,-0L0,-1L1,-1L1,-0L0.75,-0L0.75,-0.375L0.25,-0.375L0.25,-0L0,-0Z" style="fill:rgb(58,162,232);fill-rule:nonzero;"/>
</g>
<g transform="matrix(7.73995,0,0,7.73995,64.9884,-8.88178e-16)">
<path d="M0.375,-0L0.375,-0.75L0,-0.75L0,-1L1,-1L1,-0.75L0.625,-0.75L0.625,-0L0.375,-0Z" style="fill:rgb(58,162,232);fill-rule:nonzero;"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 50; objectVersion = 51;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@ -121,7 +121,6 @@
66884EE8E4C818DD7F73E828 /* Pods-Runner.release.xcconfig */, 66884EE8E4C818DD7F73E828 /* Pods-Runner.release.xcconfig */,
DC6D2E60CBCA1AC9F82D654F /* Pods-Runner.profile.xcconfig */, DC6D2E60CBCA1AC9F82D654F /* Pods-Runner.profile.xcconfig */,
); );
name = Pods;
path = Pods; path = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -355,15 +354,16 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = XP5LBLT7M7;
DEVELOPMENT_TEAM = ZJPQSFX5MW;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = VeilidChat;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.vekoni.veilidchat.veilidchat; PRODUCT_BUNDLE_IDENTIFIER = com.veilid.veilidchat;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -484,15 +484,16 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = XP5LBLT7M7;
DEVELOPMENT_TEAM = ZJPQSFX5MW;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = VeilidChat;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.vekoni.veilidchat.veilidchat; PRODUCT_BUNDLE_IDENTIFIER = com.veilid.veilidchat;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -507,15 +508,16 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = XP5LBLT7M7;
DEVELOPMENT_TEAM = ZJPQSFX5MW;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = VeilidChat;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.vekoni.veilidchat.veilidchat; PRODUCT_BUNDLE_IDENTIFIER = com.veilid.veilidchat;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
@ -24,6 +26,8 @@
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>
@ -43,9 +47,5 @@
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@ -25,6 +25,7 @@ class VeilidChatApp extends ConsumerWidget {
return LocalizationProvider( return LocalizationProvider(
state: LocalizationProvider.of(context).state, state: LocalizationProvider.of(context).state,
child: MaterialApp.router( child: MaterialApp.router(
debugShowCheckedModeBanner: false,
routerConfig: router, routerConfig: router,
title: 'VeilidChat', title: 'VeilidChat',
theme: theme, theme: theme,

View File

@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:window_manager/window_manager.dart';
import 'package:user_profile_avatar/user_profile_avatar.dart';
import '../providers/local_accounts.dart';
import '../providers/logins.dart';
class AccountBubble extends ConsumerWidget {
const AccountBubble({super.key});
void _onReorder(WidgetRef ref, int oldIndex, int newIndex) {
final accounts = ref.read(localAccountsProvider.notifier);
accounts.reorderAccount(oldIndex, newIndex);
// xxx fix this so we can await this properly, use FutureBuilder or whatever
}
@override
Widget build(BuildContext context, WidgetRef ref) {
windowManager.setTitleBarStyle(TitleBarStyle.normal);
final accounts = ref.watch(localAccountsProvider);
final logins = ref.watch(loginsProvider);
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('Accounts'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.settings),
tooltip: 'Accessibility',
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content:
Text('Accessibility and language options coming soon')));
},
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Spacer(),
accounts.when(
error: (obj, err) => Text("error loading accounts: $err"),
loading: () => CircularProgressIndicator(),
data: (accountList) => ReorderableGridView.extent(
maxCrossAxisExtent: 128,
onReorder: (oldIndex, newIndex) =>
_onReorder(ref, oldIndex, newIndex),
children: accountList.map((account) {
return AccountBubble(account);
}),
)),
const Spacer(),
],
),
),
);
}
}

View File

@ -12,9 +12,9 @@ part 'user_login.g.dart';
class UserLogin with _$UserLogin { class UserLogin with _$UserLogin {
const factory UserLogin({ const factory UserLogin({
// Master record key for the user used to index the local accounts table // Master record key for the user used to index the local accounts table
required TypedKey accountMasterKey, required TypedKey accountMasterRecordKey,
// The identity secret as unlocked from the local accounts table // The identity secret as unlocked from the local accounts table
required TypedSecret secretKey, required TypedSecret identitySecret,
// The time this login was most recently used // The time this login was most recently used
required Timestamp lastActive, required Timestamp lastActive,
}) = _UserLogin; }) = _UserLogin;

View File

@ -21,9 +21,9 @@ UserLogin _$UserLoginFromJson(Map<String, dynamic> json) {
/// @nodoc /// @nodoc
mixin _$UserLogin { mixin _$UserLogin {
// Master record key for the user used to index the local accounts table // Master record key for the user used to index the local accounts table
Typed<FixedEncodedString43> get accountMasterKey => Typed<FixedEncodedString43> get accountMasterRecordKey =>
throw _privateConstructorUsedError; // The identity secret as unlocked from the local accounts table throw _privateConstructorUsedError; // The identity secret as unlocked from the local accounts table
Typed<FixedEncodedString43> get secretKey => Typed<FixedEncodedString43> get identitySecret =>
throw _privateConstructorUsedError; // The time this login was most recently used throw _privateConstructorUsedError; // The time this login was most recently used
Timestamp get lastActive => throw _privateConstructorUsedError; Timestamp get lastActive => throw _privateConstructorUsedError;
@ -39,8 +39,8 @@ abstract class $UserLoginCopyWith<$Res> {
_$UserLoginCopyWithImpl<$Res, UserLogin>; _$UserLoginCopyWithImpl<$Res, UserLogin>;
@useResult @useResult
$Res call( $Res call(
{Typed<FixedEncodedString43> accountMasterKey, {Typed<FixedEncodedString43> accountMasterRecordKey,
Typed<FixedEncodedString43> secretKey, Typed<FixedEncodedString43> identitySecret,
Timestamp lastActive}); Timestamp lastActive});
} }
@ -57,18 +57,18 @@ class _$UserLoginCopyWithImpl<$Res, $Val extends UserLogin>
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? accountMasterKey = null, Object? accountMasterRecordKey = null,
Object? secretKey = null, Object? identitySecret = null,
Object? lastActive = null, Object? lastActive = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
accountMasterKey: null == accountMasterKey accountMasterRecordKey: null == accountMasterRecordKey
? _value.accountMasterKey ? _value.accountMasterRecordKey
: accountMasterKey // ignore: cast_nullable_to_non_nullable : accountMasterRecordKey // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>, as Typed<FixedEncodedString43>,
secretKey: null == secretKey identitySecret: null == identitySecret
? _value.secretKey ? _value.identitySecret
: secretKey // ignore: cast_nullable_to_non_nullable : identitySecret // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>, as Typed<FixedEncodedString43>,
lastActive: null == lastActive lastActive: null == lastActive
? _value.lastActive ? _value.lastActive
@ -86,8 +86,8 @@ abstract class _$$_UserLoginCopyWith<$Res> implements $UserLoginCopyWith<$Res> {
@override @override
@useResult @useResult
$Res call( $Res call(
{Typed<FixedEncodedString43> accountMasterKey, {Typed<FixedEncodedString43> accountMasterRecordKey,
Typed<FixedEncodedString43> secretKey, Typed<FixedEncodedString43> identitySecret,
Timestamp lastActive}); Timestamp lastActive});
} }
@ -102,18 +102,18 @@ class __$$_UserLoginCopyWithImpl<$Res>
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
Object? accountMasterKey = null, Object? accountMasterRecordKey = null,
Object? secretKey = null, Object? identitySecret = null,
Object? lastActive = null, Object? lastActive = null,
}) { }) {
return _then(_$_UserLogin( return _then(_$_UserLogin(
accountMasterKey: null == accountMasterKey accountMasterRecordKey: null == accountMasterRecordKey
? _value.accountMasterKey ? _value.accountMasterRecordKey
: accountMasterKey // ignore: cast_nullable_to_non_nullable : accountMasterRecordKey // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>, as Typed<FixedEncodedString43>,
secretKey: null == secretKey identitySecret: null == identitySecret
? _value.secretKey ? _value.identitySecret
: secretKey // ignore: cast_nullable_to_non_nullable : identitySecret // ignore: cast_nullable_to_non_nullable
as Typed<FixedEncodedString43>, as Typed<FixedEncodedString43>,
lastActive: null == lastActive lastActive: null == lastActive
? _value.lastActive ? _value.lastActive
@ -127,8 +127,8 @@ class __$$_UserLoginCopyWithImpl<$Res>
@JsonSerializable() @JsonSerializable()
class _$_UserLogin implements _UserLogin { class _$_UserLogin implements _UserLogin {
const _$_UserLogin( const _$_UserLogin(
{required this.accountMasterKey, {required this.accountMasterRecordKey,
required this.secretKey, required this.identitySecret,
required this.lastActive}); required this.lastActive});
factory _$_UserLogin.fromJson(Map<String, dynamic> json) => factory _$_UserLogin.fromJson(Map<String, dynamic> json) =>
@ -136,17 +136,17 @@ class _$_UserLogin implements _UserLogin {
// Master record key for the user used to index the local accounts table // Master record key for the user used to index the local accounts table
@override @override
final Typed<FixedEncodedString43> accountMasterKey; final Typed<FixedEncodedString43> accountMasterRecordKey;
// The identity secret as unlocked from the local accounts table // The identity secret as unlocked from the local accounts table
@override @override
final Typed<FixedEncodedString43> secretKey; final Typed<FixedEncodedString43> identitySecret;
// The time this login was most recently used // The time this login was most recently used
@override @override
final Timestamp lastActive; final Timestamp lastActive;
@override @override
String toString() { String toString() {
return 'UserLogin(accountMasterKey: $accountMasterKey, secretKey: $secretKey, lastActive: $lastActive)'; return 'UserLogin(accountMasterRecordKey: $accountMasterRecordKey, identitySecret: $identitySecret, lastActive: $lastActive)';
} }
@override @override
@ -154,18 +154,18 @@ class _$_UserLogin implements _UserLogin {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$_UserLogin && other is _$_UserLogin &&
(identical(other.accountMasterKey, accountMasterKey) || (identical(other.accountMasterRecordKey, accountMasterRecordKey) ||
other.accountMasterKey == accountMasterKey) && other.accountMasterRecordKey == accountMasterRecordKey) &&
(identical(other.secretKey, secretKey) || (identical(other.identitySecret, identitySecret) ||
other.secretKey == secretKey) && other.identitySecret == identitySecret) &&
(identical(other.lastActive, lastActive) || (identical(other.lastActive, lastActive) ||
other.lastActive == lastActive)); other.lastActive == lastActive));
} }
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
int get hashCode => int get hashCode => Object.hash(
Object.hash(runtimeType, accountMasterKey, secretKey, lastActive); runtimeType, accountMasterRecordKey, identitySecret, lastActive);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@ -183,17 +183,17 @@ class _$_UserLogin implements _UserLogin {
abstract class _UserLogin implements UserLogin { abstract class _UserLogin implements UserLogin {
const factory _UserLogin( const factory _UserLogin(
{required final Typed<FixedEncodedString43> accountMasterKey, {required final Typed<FixedEncodedString43> accountMasterRecordKey,
required final Typed<FixedEncodedString43> secretKey, required final Typed<FixedEncodedString43> identitySecret,
required final Timestamp lastActive}) = _$_UserLogin; required final Timestamp lastActive}) = _$_UserLogin;
factory _UserLogin.fromJson(Map<String, dynamic> json) = factory _UserLogin.fromJson(Map<String, dynamic> json) =
_$_UserLogin.fromJson; _$_UserLogin.fromJson;
@override // Master record key for the user used to index the local accounts table @override // Master record key for the user used to index the local accounts table
Typed<FixedEncodedString43> get accountMasterKey; Typed<FixedEncodedString43> get accountMasterRecordKey;
@override // The identity secret as unlocked from the local accounts table @override // The identity secret as unlocked from the local accounts table
Typed<FixedEncodedString43> get secretKey; Typed<FixedEncodedString43> get identitySecret;
@override // The time this login was most recently used @override // The time this login was most recently used
Timestamp get lastActive; Timestamp get lastActive;
@override @override

View File

@ -7,16 +7,17 @@ part of 'user_login.dart';
// ************************************************************************** // **************************************************************************
_$_UserLogin _$$_UserLoginFromJson(Map<String, dynamic> json) => _$_UserLogin( _$_UserLogin _$$_UserLoginFromJson(Map<String, dynamic> json) => _$_UserLogin(
accountMasterKey: accountMasterRecordKey: Typed<FixedEncodedString43>.fromJson(
Typed<FixedEncodedString43>.fromJson(json['account_master_key']), json['account_master_record_key']),
secretKey: Typed<FixedEncodedString43>.fromJson(json['secret_key']), identitySecret:
Typed<FixedEncodedString43>.fromJson(json['identity_secret']),
lastActive: Timestamp.fromJson(json['last_active']), lastActive: Timestamp.fromJson(json['last_active']),
); );
Map<String, dynamic> _$$_UserLoginToJson(_$_UserLogin instance) => Map<String, dynamic> _$$_UserLoginToJson(_$_UserLogin instance) =>
<String, dynamic>{ <String, dynamic>{
'account_master_key': instance.accountMasterKey.toJson(), 'account_master_record_key': instance.accountMasterRecordKey.toJson(),
'secret_key': instance.secretKey.toJson(), 'identity_secret': instance.identitySecret.toJson(),
'last_active': instance.lastActive.toJson(), 'last_active': instance.lastActive.toJson(),
}; };

View File

@ -3,12 +3,14 @@ import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:window_manager/window_manager.dart';
import 'log/log.dart'; import 'log/log.dart';
import 'veilid_support/veilid_support.dart'; import 'veilid_support/veilid_support.dart';
import 'theming/theming.dart'; import 'theming/theming.dart';
import 'app.dart'; import 'app.dart';
import 'dart:io'; import 'dart:io';
import 'package:flutter_translate/flutter_translate.dart';
void main() async { void main() async {
// Disable all debugprints in release mode // Disable all debugprints in release mode
@ -29,13 +31,30 @@ void main() async {
final themeService = await ThemeService.instance; final themeService = await ThemeService.instance;
var initTheme = themeService.initial; var initTheme = themeService.initial;
// Start up Veilid and Veilid processor in the background // Manage window on desktop platforms
unawaited(initializeVeilid()); if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
await windowManager.ensureInitialized();
const windowOptions = WindowOptions(
size: Size(768, 1024),
center: true,
backgroundColor: Colors.transparent,
skipTaskbar: false,
titleBarStyle: TitleBarStyle.hidden,
);
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
await windowManager.focus();
});
}
// Make localization delegate // Make localization delegate
var delegate = await LocalizationDelegate.create( var delegate = await LocalizationDelegate.create(
fallbackLocale: 'en_US', supportedLocales: ['en_US']); fallbackLocale: 'en_US', supportedLocales: ['en_US']);
// Start up Veilid and Veilid processor in the background
unawaited(initializeVeilid());
// Run the app // Run the app
// Hot reloads will only restart this part, not Veilid // Hot reloads will only restart this part, not Veilid
runApp(ProviderScope( runApp(ProviderScope(

View File

@ -1,4 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:radix_colors/radix_colors.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:window_manager/window_manager.dart';
class IndexPage extends StatelessWidget { class IndexPage extends StatelessWidget {
const IndexPage({super.key}); const IndexPage({super.key});
@ -6,8 +9,34 @@ class IndexPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const Scaffold( windowManager.setTitleBarStyle(TitleBarStyle.hidden);
body: Center(child: Text("Index Page")), return Scaffold(
); body: DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[
RadixColors.dark.plum.step4,
RadixColors.dark.plum.step2,
])),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 300),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
flex: 2,
child: SvgPicture.asset(
"assets/images/icon.svg",
)),
Expanded(
flex: 1,
child: SvgPicture.asset(
"assets/images/title.svg",
))
]))),
));
} }
} }

View File

@ -1,30 +1,61 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../state/active_logins_state.dart'; import 'package:window_manager/window_manager.dart';
import 'package:reorderable_grid/reorderable_grid.dart';
import '../providers/local_accounts.dart';
import '../providers/logins.dart';
class LoginPage extends ConsumerWidget { class LoginPage extends ConsumerWidget {
const LoginPage({super.key}); const LoginPage({super.key});
static const path = '/login'; static const path = '/login';
void _onReorder(WidgetRef ref, int oldIndex, int newIndex) {
final accounts = ref.read(localAccountsProvider.notifier);
accounts.reorderAccount(oldIndex, newIndex);
// xxx fix this so we can await this properly, use FutureBuilder or whatever
}
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
windowManager.setTitleBarStyle(TitleBarStyle.normal);
final accounts = ref.watch(localAccountsProvider);
final logins = ref.watch(loginsProvider);
return Scaffold( return Scaffold(
appBar: null, appBar: AppBar(
centerTitle: true,
title: const Text('Accounts'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.settings),
tooltip: 'Accessibility',
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content:
Text('Accessibility and language options coming soon')));
},
),
],
),
body: Center( body: Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const Text("Login Page"), const Spacer(),
ElevatedButton( accounts.when(
onPressed: () async { error: (obj, err) => Text("error loading accounts: $err"),
await ref.watch(authNotifierProvider.notifier).login( loading: () => CircularProgressIndicator(),
"myEmail", data: (accountList) => ReorderableGridView.extent(
"myPassword", maxCrossAxisExtent: 128,
); onReorder: (oldIndex, newIndex) =>
}, _onReorder(ref, oldIndex, newIndex),
child: const Text("Login"), children: accountList.map((account) {
), return AccountBubble(account);
}),
)),
const Spacer(),
], ],
), ),
), ),

View File

@ -10,6 +10,7 @@ import '../tools/tools.dart';
import '../veilid_support/veilid_support.dart'; import '../veilid_support/veilid_support.dart';
import '../entities/entities.dart'; import '../entities/entities.dart';
import '../entities/proto.dart' as proto; import '../entities/proto.dart' as proto;
import 'logins.dart';
part 'local_accounts.g.dart'; part 'local_accounts.g.dart';
@ -38,58 +39,15 @@ class LocalAccounts extends _$LocalAccounts
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
/// Mutators and Selectors /// Mutators and Selectors
/// Creates a new master identity and returns it with its secrets /// Reorder accounts
Future<IdentityMasterWithSecrets> newIdentityMaster() async { Future<void> reorderAccount(int oldIndex, int newIndex) async {
final crypto = await Veilid.instance.bestCryptoSystem(); final localAccounts = state.requireValue;
final dhtctx = (await Veilid.instance.routingContext()) var removedItem = Output<LocalAccount>();
.withPrivacy() final updated = localAccounts
.withSequencing(Sequencing.ensureOrdered); .removeAt(oldIndex, removedItem)
.insert(newIndex, removedItem.value!);
// IdentityMaster DHT record is public/unencrypted await store(updated);
return (await DHTRecord.create(dhtctx, state = AsyncValue.data(updated);
crypto: const DHTRecordCryptoPublic()))
.deleteScope((masterRec) async {
// Identity record is private
return (await DHTRecord.create(dhtctx)).deleteScope((identityRec) async {
// Make IdentityMaster
final masterRecordKey = masterRec.key();
final masterOwner = masterRec.ownerKeyPair()!;
final masterSigBuf = masterRecordKey.decode()
..addAll(masterOwner.key.decode());
final identityRecordKey = identityRec.key();
final identityOwner = identityRec.ownerKeyPair()!;
final identitySigBuf = identityRecordKey.decode()
..addAll(identityOwner.key.decode());
final identitySignature =
await crypto.signWithKeyPair(masterOwner, identitySigBuf);
final masterSignature =
await crypto.signWithKeyPair(identityOwner, masterSigBuf);
final identityMaster = IdentityMaster(
identityRecordKey: identityRecordKey,
identityPublicKey: identityOwner.key,
masterRecordKey: masterRecordKey,
masterPublicKey: masterOwner.key,
identitySignature: identitySignature,
masterSignature: masterSignature);
// Write identity master to master dht key
await masterRec.eventualWriteJson(identityMaster);
// Make empty identity
const identity = Identity(accountRecords: IMapConst({}));
// Write empty identity to identity dht key
await identityRec.eventualWriteJson(identity);
return IdentityMasterWithSecrets(
identityMaster: identityMaster,
masterSecret: masterOwner.secret,
identitySecret: identityOwner.secret);
});
});
} }
/// Creates a new account associated with master identity /// Creates a new account associated with master identity
@ -99,6 +57,7 @@ class LocalAccounts extends _$LocalAccounts
EncryptionKeyType encryptionKeyType, EncryptionKeyType encryptionKeyType,
String encryptionKey, String encryptionKey,
proto.Account account) async { proto.Account account) async {
final veilid = await eventualVeilid.future;
final localAccounts = state.requireValue; final localAccounts = state.requireValue;
// Encrypt identitySecret with key // Encrypt identitySecret with key
@ -111,8 +70,8 @@ class LocalAccounts extends _$LocalAccounts
identitySecretSaltBytes = Uint8List(0); identitySecretSaltBytes = Uint8List(0);
case EncryptionKeyType.pin: case EncryptionKeyType.pin:
case EncryptionKeyType.password: case EncryptionKeyType.password:
final cs = await Veilid.instance final cs =
.getCryptoSystem(identityMaster.identityRecordKey.kind); await veilid.getCryptoSystem(identityMaster.identityRecordKey.kind);
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey)); final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
final nonce = await cs.randomNonce(); final nonce = await cs.randomNonce();
identitySecretSaltBytes = nonce.decode(); identitySecretSaltBytes = nonce.decode();
@ -135,7 +94,7 @@ class LocalAccounts extends _$LocalAccounts
/////// Add account with profile to DHT /////// Add account with profile to DHT
// Create private routing context // Create private routing context
final dhtctx = (await Veilid.instance.routingContext()) final dhtctx = (await veilid.routingContext())
.withPrivacy() .withPrivacy()
.withSequencing(Sequencing.ensureOrdered); .withSequencing(Sequencing.ensureOrdered);
@ -171,6 +130,22 @@ class LocalAccounts extends _$LocalAccounts
return localAccount; return localAccount;
} }
/// Remove an account and wipe the messages for this account from this device
Future<bool> deleteAccount(TypedKey accountMasterRecordKey) async {
final logins = ref.read(loginsProvider.notifier);
await logins.logout(accountMasterRecordKey);
final localAccounts = state.requireValue;
final updated = localAccounts.removeWhere(
(la) => la.identityMaster.masterRecordKey == accountMasterRecordKey);
await store(updated);
state = AsyncValue.data(updated);
// xxx todo: wipe messages
return true;
}
/// Import an account from another VeilidChat instance /// Import an account from another VeilidChat instance
/// Recover an account with the master identity secret /// Recover an account with the master identity secret

View File

@ -6,7 +6,7 @@ part of 'local_accounts.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$localAccountsHash() => r'41f70b78e71c8b3faa2cd1f2a96084fbb373324c'; String _$localAccountsHash() => r'694236fa91156c00b3f7d6985fbc55b8871646ab';
/// See also [LocalAccounts]. /// See also [LocalAccounts].
@ProviderFor(LocalAccounts) @ProviderFor(LocalAccounts)

View File

@ -4,10 +4,10 @@ import 'dart:typed_data';
import 'package:veilid/veilid.dart'; import 'package:veilid/veilid.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:veilidchat/providers/repositories.dart';
import '../veilid_support/veilid_support.dart'; import '../veilid_support/veilid_support.dart';
import '../entities/entities.dart'; import '../entities/entities.dart';
import 'local_accounts.dart';
part 'logins.g.dart'; part 'logins.g.dart';
@ -34,19 +34,20 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
/// Mutators and Selectors /// Mutators and Selectors
Future<void> setActiveUserLogin(TypedKey accountMasterKey) async { Future<void> switchToAccount(TypedKey? accountMasterRecordKey) async {
final current = state.requireValue; final current = state.requireValue;
for (final userLogin in current.userLogins) { if (accountMasterRecordKey != null) {
if (userLogin.accountMasterRecordKey == accountMasterKey) { // Assert the specified record key can be found, will throw if not
state = AsyncValue.data( final _ = current.userLogins.firstWhere(
current.copyWith(activeUserLogin: accountMasterKey)); (ul) => ul.accountMasterRecordKey == accountMasterRecordKey);
return;
}
} }
throw Exception("User not found"); final updated = current.copyWith(activeUserLogin: accountMasterRecordKey);
await store(updated);
state = AsyncValue.data(updated);
} }
Future<bool> loginWithNone(TypedKey accountMasterRecordKey) async { Future<bool> loginWithNone(TypedKey accountMasterRecordKey) async {
final veilid = await eventualVeilid.future;
final localAccounts = ref.read(localAccountsProvider).requireValue; final localAccounts = ref.read(localAccountsProvider).requireValue;
// Get account, throws if not found // Get account, throws if not found
@ -64,7 +65,7 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
SecretKey.fromBytes(localAccount.identitySecretKeyBytes); SecretKey.fromBytes(localAccount.identitySecretKeyBytes);
// Validate this secret with the identity public key // Validate this secret with the identity public key
final cs = await Veilid.instance final cs = await veilid
.getCryptoSystem(localAccount.identityMaster.identityRecordKey.kind); .getCryptoSystem(localAccount.identityMaster.identityRecordKey.kind);
final keyOk = await cs.validateKeyPair( final keyOk = await cs.validateKeyPair(
localAccount.identityMaster.identityPublicKey, identitySecret); localAccount.identityMaster.identityPublicKey, identitySecret);
@ -74,15 +75,15 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
// Add to user logins and select it // Add to user logins and select it
final current = state.requireValue; final current = state.requireValue;
final now = Veilid.instance.now(); final now = veilid.now();
final updated = current.copyWith( final updated = current.copyWith(
userLogins: current.userLogins.replaceFirstWhere( userLogins: current.userLogins.replaceFirstWhere(
(ul) => ul.accountMasterRecordKey == accountMasterRecordKey, (ul) => ul.accountMasterRecordKey == accountMasterRecordKey,
(ul) => ul != null (ul) => ul != null
? ul.copyWith(lastActive: now) ? ul.copyWith(lastActive: now)
: UserLogin( : UserLogin(
accountMasterKey: accountMasterRecordKey, accountMasterRecordKey: accountMasterRecordKey,
secretKey: identitySecret:
TypedSecret(kind: cs.kind(), value: identitySecret), TypedSecret(kind: cs.kind(), value: identitySecret),
lastActive: now), lastActive: now),
addIfNotFound: true), addIfNotFound: true),
@ -95,6 +96,7 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
Future<bool> loginWithPasswordOrPin( Future<bool> loginWithPasswordOrPin(
TypedKey accountMasterRecordKey, String encryptionKey) async { TypedKey accountMasterRecordKey, String encryptionKey) async {
final veilid = await eventualVeilid.future;
final localAccounts = ref.read(localAccountsProvider).requireValue; final localAccounts = ref.read(localAccountsProvider).requireValue;
// Get account, throws if not found // Get account, throws if not found
@ -108,7 +110,7 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
localAccount.encryptionKeyType != EncryptionKeyType.pin) { localAccount.encryptionKeyType != EncryptionKeyType.pin) {
throw Exception("Wrong authentication type"); throw Exception("Wrong authentication type");
} }
final cs = await Veilid.instance final cs = await veilid
.getCryptoSystem(localAccount.identityMaster.identityRecordKey.kind); .getCryptoSystem(localAccount.identityMaster.identityRecordKey.kind);
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey)); final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
final eksalt = localAccount.identitySecretSaltBytes; final eksalt = localAccount.identitySecretSaltBytes;
@ -126,15 +128,15 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
// Add to user logins and select it // Add to user logins and select it
final current = state.requireValue; final current = state.requireValue;
final now = Veilid.instance.now(); final now = veilid.now();
final updated = current.copyWith( final updated = current.copyWith(
userLogins: current.userLogins.replaceFirstWhere( userLogins: current.userLogins.replaceFirstWhere(
(ul) => ul.accountMasterRecordKey == accountMasterRecordKey, (ul) => ul.accountMasterRecordKey == accountMasterRecordKey,
(ul) => ul != null (ul) => ul != null
? ul.copyWith(lastActive: now) ? ul.copyWith(lastActive: now)
: UserLogin( : UserLogin(
accountMasterKey: accountMasterRecordKey, accountMasterRecordKey: accountMasterRecordKey,
secretKey: identitySecret:
TypedSecret(kind: cs.kind(), value: identitySecret), TypedSecret(kind: cs.kind(), value: identitySecret),
lastActive: now), lastActive: now),
addIfNotFound: true), addIfNotFound: true),
@ -145,25 +147,18 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
return true; return true;
} }
Future<void> logout() async { Future<void> logout(TypedKey? accountMasterRecordKey) async {
final current = state.requireValue; final current = state.requireValue;
if (current.activeUserLogin == null) { final logoutUser = accountMasterRecordKey ?? current.activeUserLogin;
if (logoutUser == null) {
return; return;
} }
final updated = current.copyWith( final updated = current.copyWith(
activeUserLogin: null, activeUserLogin: current.activeUserLogin == logoutUser
userLogins: current.userLogins.removeWhere( ? null
(ul) => ul.accountMasterRecordKey == current.activeUserLogin)); : current.activeUserLogin,
await store(updated); userLogins: current.userLogins
state = AsyncValue.data(updated); .removeWhere((ul) => ul.accountMasterRecordKey == logoutUser));
}
Future<void> switchToAccount(TypedKey accountMasterRecordKey) async {
final current = state.requireValue;
final userLogin = current.userLogins.firstWhere(
(ul) => ul.accountMasterRecordKey == accountMasterRecordKey);
final updated =
current.copyWith(activeUserLogin: userLogin.accountMasterRecordKey);
await store(updated); await store(updated);
state = AsyncValue.data(updated); state = AsyncValue.data(updated);
} }

View File

@ -6,7 +6,7 @@ part of 'logins.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$loginsHash() => r'379514b8b6623d59cb89b4f128be5953d5ea62a6'; String _$loginsHash() => r'cf7f1a2343340a4dc4ebfa4009e846d0a39c0167';
/// See also [Logins]. /// See also [Logins].
@ProviderFor(Logins) @ProviderFor(Logins)

View File

@ -3,56 +3,42 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../pages/pages.dart'; import '../pages/pages.dart';
import '../state/active_logins_state.dart'; import '../providers/logins.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> class RouterNotifier extends AutoDisposeAsyncNotifier<void>
implements Listenable { implements Listenable {
/// GoRouter listener
VoidCallback? routerListener; VoidCallback? routerListener;
bool isAuth = false; // Useful for our global redirect function
/// Router state for redirect
bool hasActiveUserLogin = false;
/// AsyncNotifier build
@override @override
Future<void> build() async { Future<void> build() async {
// One could watch more providers and write logic accordingly hasActiveUserLogin = await ref.watch(
loginsProvider.selectAsync((data) => data.activeUserLogin != null),
isAuth = await ref.watch(
authNotifierProvider.selectAsync((data) => data != null),
); );
// When this notifier's state changes, inform GoRouter
ref.listenSelf((_, __) { ref.listenSelf((_, __) {
// One could write more conditional logic for when to call redirection
if (state.isLoading) return; if (state.isLoading) return;
routerListener?.call(); routerListener?.call();
}); });
} }
/// Redirects the user when our authentication changes /// Redirects when our state changes
String? redirect(BuildContext context, GoRouterState state) { String? redirect(BuildContext context, GoRouterState state) {
if (this.state.isLoading || this.state.hasError) return null; if (this.state.isLoading || this.state.hasError) return null;
final isIndex = state.location == IndexPage.path; switch (state.location) {
if (isIndex) { case IndexPage.path:
return isAuth ? HomePage.path : LoginPage.path; return hasActiveUserLogin ? HomePage.path : LoginPage.path;
case LoginPage.path:
return hasActiveUserLogin ? HomePage.path : null;
default:
return hasActiveUserLogin ? null : LoginPage.path;
} }
final isLoggingIn = state.location == LoginPage.path;
if (isLoggingIn) {
return isAuth ? HomePage.path : null;
}
return isAuth ? null : IndexPage.path;
} }
/// Our application routes /// Our application routes
@ -103,6 +89,9 @@ class RouterNotifier extends AutoDisposeAsyncNotifier<void>
), ),
]; ];
///////////////////////////////////////////////////////////////////////////
/// Listenable
/// Adds [GoRouter]'s listener as specified by its [Listenable]. /// Adds [GoRouter]'s listener as specified by its [Listenable].
/// [GoRouteInformationProvider] uses this method on creation to handle its /// [GoRouteInformationProvider] uses this method on creation to handle its
/// internal [ChangeNotifier]. /// internal [ChangeNotifier].

View File

@ -24,7 +24,8 @@ class ThemeService {
String? themeName = prefs.getString('previousThemeName'); String? themeName = prefs.getString('previousThemeName');
if (themeName == null) { if (themeName == null) {
final isPlatformDark = final isPlatformDark =
WidgetsBinding.instance.window.platformBrightness == Brightness.dark; WidgetsBinding.instance.platformDispatcher.platformBrightness ==
Brightness.dark;
themeName = isPlatformDark ? 'light' : 'dark'; themeName = isPlatformDark ? 'light' : 'dark';
} }
return themeName; return themeName;
@ -34,7 +35,8 @@ class ThemeService {
String? themeName = prefs.getString('theme'); String? themeName = prefs.getString('theme');
if (themeName == null) { if (themeName == null) {
final isPlatformDark = final isPlatformDark =
WidgetsBinding.instance.window.platformBrightness == Brightness.dark; WidgetsBinding.instance.platformDispatcher.platformBrightness ==
Brightness.dark;
themeName = isPlatformDark ? 'dark' : 'light'; themeName = isPlatformDark ? 'dark' : 'light';
} }
return allThemes[themeName]; return allThemes[themeName];

View File

@ -3,6 +3,8 @@ import 'dart:async';
import 'package:veilid/veilid.dart'; import 'package:veilid/veilid.dart';
import 'dart:typed_data'; import 'dart:typed_data';
import 'veilid_init.dart';
abstract class DHTRecordCrypto { abstract class DHTRecordCrypto {
FutureOr<Uint8List> encrypt(Uint8List data, int subkey); FutureOr<Uint8List> encrypt(Uint8List data, int subkey);
FutureOr<Uint8List> decrypt(Uint8List data, int subkey); FutureOr<Uint8List> decrypt(Uint8List data, int subkey);
@ -21,15 +23,16 @@ class DHTRecordCryptoPrivate implements DHTRecordCrypto {
static Future<DHTRecordCryptoPrivate> fromTypedKeyPair( static Future<DHTRecordCryptoPrivate> fromTypedKeyPair(
TypedKeyPair typedKeyPair) async { TypedKeyPair typedKeyPair) async {
final cryptoSystem = final veilid = await eventualVeilid.future;
await Veilid.instance.getCryptoSystem(typedKeyPair.kind); final cryptoSystem = await veilid.getCryptoSystem(typedKeyPair.kind);
final secretKey = typedKeyPair.secret; final secretKey = typedKeyPair.secret;
return DHTRecordCryptoPrivate._(cryptoSystem, secretKey); return DHTRecordCryptoPrivate._(cryptoSystem, secretKey);
} }
static Future<DHTRecordCryptoPrivate> fromSecret( static Future<DHTRecordCryptoPrivate> fromSecret(
CryptoKind kind, SharedSecret secretKey) async { CryptoKind kind, SharedSecret secretKey) async {
final cryptoSystem = await Veilid.instance.getCryptoSystem(kind); final veilid = await eventualVeilid.future;
final cryptoSystem = await veilid.getCryptoSystem(kind);
return DHTRecordCryptoPrivate._(cryptoSystem, secretKey); return DHTRecordCryptoPrivate._(cryptoSystem, secretKey);
} }

View File

@ -6,8 +6,9 @@ import 'veilid_support.dart';
/// Creates a new master identity and returns it with its secrets /// Creates a new master identity and returns it with its secrets
Future<IdentityMasterWithSecrets> newIdentityMaster() async { Future<IdentityMasterWithSecrets> newIdentityMaster() async {
final crypto = await Veilid.instance.bestCryptoSystem(); final veilid = await eventualVeilid.future;
final dhtctx = (await Veilid.instance.routingContext()) final crypto = await veilid.bestCryptoSystem();
final dhtctx = (await veilid.routingContext())
.withPrivacy() .withPrivacy()
.withSequencing(Sequencing.ensureOrdered); .withSequencing(Sequencing.ensureOrdered);

View File

@ -24,6 +24,8 @@ class Processor {
_veilidVersion = 'Failed to get veilid version.'; _veilidVersion = 'Failed to get veilid version.';
} }
log.info("Veilid version: $_veilidVersion");
// In case of hot restart shut down first // In case of hot restart shut down first
try { try {
await Veilid.instance.shutdownVeilidCore(); await Veilid.instance.shutdownVeilidCore();

View File

@ -1,9 +1,11 @@
import 'package:veilid/veilid.dart'; import 'package:veilid/veilid.dart';
import 'veilid_init.dart';
Future<T> tableScope<T>( Future<T> tableScope<T>(
String name, Future<T> Function(VeilidTableDB tdb) callback, String name, Future<T> Function(VeilidTableDB tdb) callback,
{int columnCount = 1}) async { {int columnCount = 1}) async {
VeilidTableDB tableDB = await Veilid.instance.openTableDB(name, columnCount); final veilid = await eventualVeilid.future;
VeilidTableDB tableDB = await veilid.openTableDB(name, columnCount);
try { try {
return await callback(tableDB); return await callback(tableDB);
} finally { } finally {

View File

@ -1,8 +1,14 @@
import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:veilid/veilid.dart'; import 'package:veilid/veilid.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'processor.dart'; import 'processor.dart';
import 'veilid_log.dart'; import 'veilid_log.dart';
part 'veilid_init.g.dart';
Future<String> getVeilidVersion() async { Future<String> getVeilidVersion() async {
String veilidVersion; String veilidVersion;
try { try {
@ -45,11 +51,12 @@ void _initVeilid() {
} }
} }
bool initialized = false; Completer<Veilid> eventualVeilid = Completer<Veilid>();
Processor processor = Processor(); Processor processor = Processor();
Future<void> initializeVeilid() async { Future<void> initializeVeilid() async {
if (initialized) { // Ensure this runs only once
if (eventualVeilid.isCompleted) {
return; return;
} }
@ -62,5 +69,12 @@ Future<void> initializeVeilid() async {
// Startup Veilid // Startup Veilid
await processor.startup(); await processor.startup();
initialized = true; // Share the initialized veilid instance to the rest of the app
eventualVeilid.complete(Veilid.instance);
}
// Expose the Veilid instance as a FutureProvider
@riverpod
FutureOr<Veilid> veilidInstance(VeilidInstanceRef ref) async {
return await eventualVeilid.future;
} }

View File

@ -0,0 +1,24 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'veilid_init.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$veilidInstanceHash() => r'6086fc1e7a83e7af81ee05ee84954507d38cb748';
/// See also [veilidInstance].
@ProviderFor(veilidInstance)
final veilidInstanceProvider = AutoDisposeFutureProvider<Veilid>.internal(
veilidInstance,
name: r'veilidInstanceProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$veilidInstanceHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef VeilidInstanceRef = AutoDisposeFutureProviderRef<Veilid>;
// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions

View File

@ -1,7 +1,7 @@
export 'config.dart'; export 'config.dart';
export 'processor.dart'; export 'processor.dart';
export 'veilid_log.dart'; export 'veilid_log.dart';
export 'init.dart'; export 'veilid_init.dart';
export 'dht_record.dart'; export 'dht_record.dart';
export 'dht_record_crypto.dart'; export 'dht_record_crypto.dart';
export 'table_db.dart'; export 'table_db.dart';

View File

@ -6,10 +6,18 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <screen_retriever/screen_retriever_plugin.h>
#include <veilid/veilid_plugin.h> #include <veilid/veilid_plugin.h>
#include <window_manager/window_manager_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) screen_retriever_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
screen_retriever_plugin_register_with_registrar(screen_retriever_registrar);
g_autoptr(FlPluginRegistrar) veilid_registrar = g_autoptr(FlPluginRegistrar) veilid_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "VeilidPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "VeilidPlugin");
veilid_plugin_register_with_registrar(veilid_registrar); veilid_plugin_register_with_registrar(veilid_registrar);
g_autoptr(FlPluginRegistrar) window_manager_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin");
window_manager_plugin_register_with_registrar(window_manager_registrar);
} }

View File

@ -3,7 +3,9 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
screen_retriever
veilid veilid
window_manager
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@ -6,11 +6,15 @@ import FlutterMacOS
import Foundation import Foundation
import path_provider_foundation import path_provider_foundation
import screen_retriever
import shared_preferences_foundation import shared_preferences_foundation
import veilid import veilid
import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
VeilidPlugin.register(with: registry.registrar(forPlugin: "VeilidPlugin")) VeilidPlugin.register(with: registry.registrar(forPlugin: "VeilidPlugin"))
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
} }

View File

@ -3,33 +3,45 @@ PODS:
- path_provider_foundation (0.0.1): - path_provider_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- screen_retriever (0.0.1):
- FlutterMacOS
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- veilid (0.0.1): - veilid (0.0.1):
- FlutterMacOS - FlutterMacOS
- window_manager (0.2.0):
- FlutterMacOS
DEPENDENCIES: DEPENDENCIES:
- FlutterMacOS (from `Flutter/ephemeral`) - FlutterMacOS (from `Flutter/ephemeral`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- veilid (from `Flutter/ephemeral/.symlinks/plugins/veilid/macos`) - veilid (from `Flutter/ephemeral/.symlinks/plugins/veilid/macos`)
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
EXTERNAL SOURCES: EXTERNAL SOURCES:
FlutterMacOS: FlutterMacOS:
:path: Flutter/ephemeral :path: Flutter/ephemeral
path_provider_foundation: path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
screen_retriever:
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos
shared_preferences_foundation: shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
veilid: veilid:
:path: Flutter/ephemeral/.symlinks/plugins/veilid/macos :path: Flutter/ephemeral/.symlinks/plugins/veilid/macos
window_manager:
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
SPEC CHECKSUMS: SPEC CHECKSUMS:
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
veilid: a54f57b7bcf0e4e072fe99272d76ca126b2026d0 veilid: a54f57b7bcf0e4e072fe99272d76ca126b2026d0
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
PODFILE CHECKSUM: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9 PODFILE CHECKSUM: ff0a9a3ce75ee73f200ca7e2f47745698c917ef9

View File

@ -425,9 +425,13 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = XP5LBLT7M7;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = VeilidChat;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
@ -438,6 +442,7 @@
/usr/lib/swift, /usr/lib/swift,
"../../veilid/target/lipo-darwin", "../../veilid/target/lipo-darwin",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.veilid.veilidchat;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
@ -557,9 +562,13 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = XP5LBLT7M7;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = VeilidChat;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
@ -570,6 +579,7 @@
/usr/lib/swift, /usr/lib/swift,
"../../veilid/target/lipo-darwin", "../../veilid/target/lipo-darwin",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.veilid.veilidchat;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -583,9 +593,13 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = XP5LBLT7M7;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = VeilidChat;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
@ -596,6 +610,7 @@
/usr/lib/swift, /usr/lib/swift,
"../../veilid/target/lipo-darwin", "../../veilid/target/lipo-darwin",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.veilid.veilidchat;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };

View File

@ -339,6 +339,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.6" version: "2.3.6"
flutter_svg:
dependency: "direct main"
description:
name: flutter_svg
sha256: "8c5d68a82add3ca76d792f058b186a0599414f279f00ece4830b9b231b570338"
url: "https://pub.dev"
source: hosted
version: "2.0.7"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -549,6 +557,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.3" version: "1.8.3"
path_parsing:
dependency: transitive
description:
name: path_parsing
sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
url: "https://pub.dev"
source: hosted
version: "1.0.1"
path_provider: path_provider:
dependency: "direct main" dependency: "direct main"
description: description:
@ -597,6 +613,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.7" version: "2.1.7"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
url: "https://pub.dev"
source: hosted
version: "5.4.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -653,6 +677,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
reorderable_grid:
dependency: "direct main"
description:
name: reorderable_grid
sha256: e482ecb05c0a8c45a383d19b39cdc5c3c02f16339c6699c81d5978e602409fff
url: "https://pub.dev"
source: hosted
version: "1.0.7"
riverpod: riverpod:
dependency: transitive dependency: transitive
description: description:
@ -693,6 +725,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.27.7" version: "0.27.7"
screen_retriever:
dependency: transitive
description:
name: screen_retriever
sha256: "4931f226ca158123ccd765325e9fbf360bfed0af9b460a10f960f9bb13d58323"
url: "https://pub.dev"
source: hosted
version: "0.1.6"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
@ -890,6 +930,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.7" version: "3.0.7"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: "670f6e07aca990b4a2bcdc08a784193c4ccdd1932620244c3a86bb72a0eac67f"
url: "https://pub.dev"
source: hosted
version: "1.1.7"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256: "7451721781d967db9933b63f5733b1c4533022c0ba373a01bdd79d1a5457f69f"
url: "https://pub.dev"
source: hosted
version: "1.1.7"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "80a13c613c8bde758b1464a1755a7b3a8f2b6cec61fbf0f5a53c94c30f03ba2e"
url: "https://pub.dev"
source: hosted
version: "1.1.7"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -904,7 +968,7 @@ packages:
path: "../veilid/veilid-flutter" path: "../veilid/veilid-flutter"
relative: true relative: true
source: path source: path
version: "0.1.4" version: "0.1.6"
watcher: watcher:
dependency: transitive dependency: transitive
description: description:
@ -929,6 +993,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.5" version: "5.0.5"
window_manager:
dependency: "direct main"
description:
name: window_manager
sha256: "9eef00e393e7f9308309ce9a8b2398c9ee3ca78b50c96e8b4f9873945693ac88"
url: "https://pub.dev"
source: hosted
version: "0.3.5"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -937,6 +1009,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.1" version: "1.0.1"
xml:
dependency: transitive
description:
name: xml
sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:

View File

@ -39,6 +39,10 @@ dependencies:
fixnum: ^1.1.0 fixnum: ^1.1.0
protobuf: ^3.0.0 protobuf: ^3.0.0
charcode: ^1.3.1 charcode: ^1.3.1
window_manager: ^0.3.5
flutter_svg: ^2.0.7
reorderable_grid: ^1.0.7
user_profile_avatar: ^0.1.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -50,14 +54,13 @@ dev_dependencies:
riverpod_generator: ^2.2.3 riverpod_generator: ^2.2.3
flutter: flutter:
uses-material-design: true uses-material-design: true
assets:
# To add assets to your application, add an assets section, like this: - assets/i18n/en.json
# assets: - assets/images/splash.svg
# - images/a_dot_burr.jpeg - assets/images/icon.svg
# - images/a_dot_ham.jpeg - assets/images/title.svg
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware # https://flutter.dev/assets-and-images/#resolution-aware

View File

@ -6,9 +6,15 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <screen_retriever/screen_retriever_plugin.h>
#include <veilid/veilid_plugin.h> #include <veilid/veilid_plugin.h>
#include <window_manager/window_manager_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
ScreenRetrieverPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
VeilidPluginRegisterWithRegistrar( VeilidPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("VeilidPlugin")); registry->GetRegistrarForPlugin("VeilidPlugin"));
WindowManagerPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("WindowManagerPlugin"));
} }

View File

@ -3,7 +3,9 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
screen_retriever
veilid veilid
window_manager
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST