mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2024-12-28 00:59:25 -05:00
lint work
This commit is contained in:
parent
9e4008214d
commit
6e8725f569
@ -1,5 +1,7 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
include: package:lint_hard/all.yaml
|
||||
analyzer:
|
||||
errors:
|
||||
invalid_annotation_target: ignore
|
||||
linter:
|
||||
rules:
|
||||
- unawaited_futures
|
31
lib/app.dart
31
lib/app.dart
@ -1,16 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:animated_theme_switcher/animated_theme_switcher.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'router/router.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
|
||||
import 'router/router.dart';
|
||||
|
||||
class VeilidChatApp extends ConsumerWidget {
|
||||
const VeilidChatApp({
|
||||
Key? key,
|
||||
required this.theme,
|
||||
}) : super(key: key);
|
||||
required this.theme, super.key,
|
||||
});
|
||||
|
||||
final ThemeData theme;
|
||||
|
||||
@ -18,17 +18,16 @@ class VeilidChatApp extends ConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final router = ref.watch(routerProvider);
|
||||
|
||||
var localizationDelegate = LocalizedApp.of(context).delegate;
|
||||
final localizationDelegate = LocalizedApp.of(context).delegate;
|
||||
|
||||
return ThemeProvider(
|
||||
initTheme: theme,
|
||||
builder: (_, theme) {
|
||||
return LocalizationProvider(
|
||||
builder: (_, theme) => LocalizationProvider(
|
||||
state: LocalizationProvider.of(context).state,
|
||||
child: MaterialApp.router(
|
||||
debugShowCheckedModeBanner: false,
|
||||
routerConfig: router,
|
||||
title: translate("app.title"),
|
||||
title: translate('app.title'),
|
||||
theme: theme,
|
||||
localizationsDelegates: [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
@ -38,8 +37,12 @@ class VeilidChatApp extends ConsumerWidget {
|
||||
],
|
||||
supportedLocales: localizationDelegate.supportedLocales,
|
||||
locale: localizationDelegate.currentLocale,
|
||||
));
|
||||
},
|
||||
)),
|
||||
);
|
||||
}
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<ThemeData>('theme', theme));
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,17 @@
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:circular_profile_avatar/circular_profile_avatar.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:circular_profile_avatar/circular_profile_avatar.dart';
|
||||
import 'package:badges/badges.dart';
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
|
||||
import '../entities/local_account.dart';
|
||||
import '../providers/logins.dart';
|
||||
|
||||
class AccountBubble extends ConsumerWidget {
|
||||
const AccountBubble({required this.account, super.key});
|
||||
final LocalAccount account;
|
||||
|
||||
const AccountBubble({super.key, required this.account});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
windowManager.setTitleBarStyle(TitleBarStyle.normal);
|
||||
@ -24,11 +22,17 @@ class AccountBubble extends ConsumerWidget {
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: CircularProfileAvatar("",
|
||||
child: CircularProfileAvatar('',
|
||||
child: Container(color: Theme.of(context).disabledColor))),
|
||||
Expanded(flex: 1, child: Text("Placeholder"))
|
||||
const Expanded(child: Text('Placeholder'))
|
||||
]));
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<LocalAccount>('account', account));
|
||||
}
|
||||
}
|
||||
|
||||
class AddAccountBubble extends ConsumerWidget {
|
||||
@ -40,12 +44,12 @@ class AddAccountBubble extends ConsumerWidget {
|
||||
final logins = ref.watch(loginsProvider);
|
||||
|
||||
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
CircularProfileAvatar("",
|
||||
CircularProfileAvatar('',
|
||||
borderWidth: 4,
|
||||
borderColor: Theme.of(context).unselectedWidgetColor,
|
||||
child: Container(
|
||||
color: Colors.blue, child: const Icon(Icons.add, size: 50))),
|
||||
const Text("Add Account").paddingLTRB(0, 4, 0, 0)
|
||||
const Text('Add Account').paddingLTRB(0, 4, 0, 0)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -5,15 +5,13 @@ class Chat extends ConsumerWidget {
|
||||
const Chat({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text("Chat")),
|
||||
body: Center(
|
||||
Widget build(BuildContext context, WidgetRef ref) => Scaffold(
|
||||
appBar: AppBar(title: const Text('Chat')),
|
||||
body: const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Text("Home Page"),
|
||||
Text('Home Page'),
|
||||
// ElevatedButton(
|
||||
// onPressed: () {
|
||||
// ref.watch(authNotifierProvider.notifier).logout();
|
||||
@ -24,5 +22,4 @@ class Chat extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -5,15 +5,12 @@ class ChatIndex extends ConsumerWidget {
|
||||
const ChatIndex({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: null,
|
||||
Widget build(BuildContext context, WidgetRef ref) => const Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Text("Contacts Page"),
|
||||
Text('Contacts Page'),
|
||||
// ElevatedButton(
|
||||
// onPressed: () async {
|
||||
// ref.watch(authNotifierProvider.notifier).login(
|
||||
@ -27,5 +24,4 @@ class ChatIndex extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
class DefaultAppBar extends AppBar {
|
||||
DefaultAppBar(BuildContext context,
|
||||
{super.key, required super.title, Widget? leading, List<Widget>? actions})
|
||||
{required super.title, super.key, Widget? leading, List<Widget>? actions})
|
||||
: super(
|
||||
leading: leading ??
|
||||
Container(
|
||||
@ -12,7 +12,7 @@ class DefaultAppBar extends AppBar {
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withAlpha(32),
|
||||
shape: BoxShape.circle),
|
||||
child: SvgPicture.asset("assets/images/vlogo.svg",
|
||||
child: SvgPicture.asset('assets/images/vlogo.svg',
|
||||
height: 48)),
|
||||
actions: (actions ?? <Widget>[])
|
||||
..add(
|
||||
|
@ -71,11 +71,7 @@ class IdentityMaster with _$IdentityMaster {
|
||||
}
|
||||
|
||||
extension IdentityMasterExtension on IdentityMaster {
|
||||
KeyPair identityWriter(SecretKey secret) {
|
||||
return KeyPair(key: identityPublicKey, secret: secret);
|
||||
}
|
||||
KeyPair identityWriter(SecretKey secret) => KeyPair(key: identityPublicKey, secret: secret);
|
||||
|
||||
KeyPair masterWriter(SecretKey secret) {
|
||||
return KeyPair(key: masterPublicKey, secret: secret);
|
||||
}
|
||||
KeyPair masterWriter(SecretKey secret) => KeyPair(key: masterPublicKey, secret: secret);
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ _$_Identity _$$_IdentityFromJson(Map<String, dynamic> json) => _$_Identity(
|
||||
json['account_records'] as Map<String, dynamic>,
|
||||
(value) => value as String,
|
||||
(value) => ISet<AccountRecordInfo>.fromJson(
|
||||
value, (value) => AccountRecordInfo.fromJson(value))),
|
||||
value, AccountRecordInfo.fromJson)),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$_IdentityToJson(_$_Identity instance) =>
|
||||
|
@ -1,10 +1,11 @@
|
||||
export 'proto/veilidchat.pb.dart';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:veilid/veilid.dart';
|
||||
|
||||
import 'proto/veilidchat.pb.dart' as proto;
|
||||
|
||||
export 'proto/veilidchat.pb.dart';
|
||||
|
||||
/// CryptoKey protobuf marshaling
|
||||
///
|
||||
extension CryptoKeyProto on CryptoKey {
|
||||
@ -120,7 +121,5 @@ extension TypedKeyProto on TypedKey {
|
||||
return out;
|
||||
}
|
||||
|
||||
static TypedKey fromProto(proto.TypedKey p) {
|
||||
return TypedKey(kind: p.kind, value: CryptoKeyProto.fromProto(p.value));
|
||||
}
|
||||
static TypedKey fromProto(proto.TypedKey p) => TypedKey(kind: p.kind, value: CryptoKeyProto.fromProto(p.value));
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:veilid/veilid.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
|
||||
part 'user_login.freezed.dart';
|
||||
part 'user_login.g.dart';
|
||||
|
@ -24,7 +24,7 @@ Map<String, dynamic> _$$_UserLoginToJson(_$_UserLogin instance) =>
|
||||
_$_ActiveLogins _$$_ActiveLoginsFromJson(Map<String, dynamic> json) =>
|
||||
_$_ActiveLogins(
|
||||
userLogins: IList<UserLogin>.fromJson(
|
||||
json['user_logins'], (value) => UserLogin.fromJson(value)),
|
||||
json['user_logins'], UserLogin.fromJson),
|
||||
activeUserLogin: json['active_user_login'] == null
|
||||
? null
|
||||
: Typed<FixedEncodedString43>.fromJson(json['active_user_login']),
|
||||
|
@ -1,9 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:ansicolor/ansicolor.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:loggy/loggy.dart';
|
||||
import 'package:ansicolor/ansicolor.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
|
||||
// Loggy tools
|
||||
const LogLevel traceLevel = LogLevel('Trace', 1);
|
||||
@ -73,19 +72,17 @@ class CallbackPrinter extends LoggyPrinter {
|
||||
}
|
||||
}
|
||||
|
||||
var globalTerminalPrinter = CallbackPrinter();
|
||||
CallbackPrinter 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(
|
||||
LogOptions getLogOptions(LogLevel? level) => LogOptions(
|
||||
level ?? LogLevel.all,
|
||||
stackTraceLevel: LogLevel.error,
|
||||
);
|
||||
}
|
||||
|
||||
class RootLoggy implements LoggyType {
|
||||
@override
|
||||
@ -100,7 +97,7 @@ void initLoggy() {
|
||||
logOptions: getLogOptions(null),
|
||||
);
|
||||
|
||||
const isTrace = String.fromEnvironment("logTrace", defaultValue: "") != "";
|
||||
const isTrace = String.fromEnvironment('logTrace') != '';
|
||||
LogLevel logLevel;
|
||||
if (isTrace) {
|
||||
logLevel = traceLevel;
|
||||
|
@ -1,22 +1,21 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import 'log/log.dart';
|
||||
import 'veilid_support/veilid_support.dart';
|
||||
import 'theming/theming.dart';
|
||||
import 'app.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import '../tools/desktop_control.dart';
|
||||
import 'app.dart';
|
||||
import 'log/log.dart';
|
||||
import 'theming/theming.dart';
|
||||
import 'veilid_support/veilid_support.dart';
|
||||
|
||||
void main() async {
|
||||
// Disable all debugprints in release mode
|
||||
if (kReleaseMode) {
|
||||
debugPrint = (String? message, {int? wrapWidth}) {};
|
||||
debugPrint = (message, {wrapWidth}) {};
|
||||
}
|
||||
|
||||
// Print our PID for debugging
|
||||
@ -30,13 +29,13 @@ void main() async {
|
||||
// Prepare theme
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final themeService = await ThemeService.instance;
|
||||
var initTheme = themeService.initial;
|
||||
final initTheme = themeService.initial;
|
||||
|
||||
// Manage window on desktop platforms
|
||||
await setupDesktopWindow();
|
||||
|
||||
// Make localization delegate
|
||||
var delegate = await LocalizationDelegate.create(
|
||||
final delegate = await LocalizationDelegate.create(
|
||||
fallbackLocale: 'en_US', supportedLocales: ['en_US']);
|
||||
|
||||
// Start up Veilid and Veilid processor in the background
|
||||
|
@ -6,15 +6,12 @@ class ContactsPage extends ConsumerWidget {
|
||||
static const path = '/contacts';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: null,
|
||||
Widget build(BuildContext context, WidgetRef ref) => const Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Text("Contacts Page"),
|
||||
Text('Contacts Page'),
|
||||
// ElevatedButton(
|
||||
// onPressed: () async {
|
||||
// ref.watch(authNotifierProvider.notifier).login(
|
||||
@ -28,5 +25,4 @@ class ContactsPage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,15 +6,13 @@ class HomePage extends ConsumerWidget {
|
||||
static const path = '/home';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text("VeilidChat")),
|
||||
body: Center(
|
||||
Widget build(BuildContext context, WidgetRef ref) => Scaffold(
|
||||
appBar: AppBar(title: const Text('VeilidChat')),
|
||||
body: const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Text("Home Page"),
|
||||
Text('Home Page'),
|
||||
// ElevatedButton(
|
||||
// onPressed: () {
|
||||
// ref.watch(authNotifierProvider.notifier).logout();
|
||||
@ -25,5 +23,4 @@ class HomePage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:radix_colors/radix_colors.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:radix_colors/radix_colors.dart';
|
||||
|
||||
import '../tools/desktop_control.dart';
|
||||
|
||||
@ -30,12 +30,11 @@ class IndexPage extends StatelessWidget {
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: SvgPicture.asset(
|
||||
"assets/images/icon.svg",
|
||||
'assets/images/icon.svg',
|
||||
)),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: SvgPicture.asset(
|
||||
"assets/images/title.svg",
|
||||
'assets/images/title.svg',
|
||||
))
|
||||
]))),
|
||||
));
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:reorderable_grid/reorderable_grid.dart';
|
||||
|
||||
import '../components/account_bubble.dart';
|
||||
import '../providers/local_accounts.dart';
|
||||
@ -41,10 +40,9 @@ class LoginPage extends ConsumerWidget {
|
||||
),
|
||||
body: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(height: 100, color: Color.fromARGB(255, 255, 0, 0)),
|
||||
Spacer(),
|
||||
Container(height: 100, color: const Color.fromARGB(255, 255, 0, 0)),
|
||||
const Spacer(),
|
||||
// accounts.when(
|
||||
// error: (obj, err) => Text("error loading accounts: $err"),
|
||||
// loading: () => CircularProgressIndicator(),
|
||||
@ -58,9 +56,9 @@ class LoginPage extends ConsumerWidget {
|
||||
// account: account);
|
||||
// }).toList(),
|
||||
// )),
|
||||
AddAccountBubble(key: ValueKey("+")),
|
||||
Spacer(),
|
||||
Container(height: 100, color: Color.fromARGB(255, 0, 255, 0)),
|
||||
const AddAccountBubble(key: ValueKey('+')),
|
||||
const Spacer(),
|
||||
Container(height: 100, color: const Color.fromARGB(255, 0, 255, 0)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:quickalert/quickalert.dart';
|
||||
|
||||
@ -18,16 +18,14 @@ class NewAccountPage extends ConsumerStatefulWidget {
|
||||
static const path = '/new_account';
|
||||
|
||||
@override
|
||||
NewAccountPageState createState() {
|
||||
return NewAccountPageState();
|
||||
}
|
||||
NewAccountPageState createState() => NewAccountPageState();
|
||||
}
|
||||
|
||||
class NewAccountPageState extends ConsumerState<NewAccountPage> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
late bool isInAsyncCall = false;
|
||||
static const String formFieldName = "name";
|
||||
static const String formFieldTitle = "title";
|
||||
static const String formFieldName = 'name';
|
||||
static const String formFieldTitle = 'title';
|
||||
|
||||
Future<void> createAccount() async {
|
||||
final imws = await newIdentityMaster();
|
||||
@ -56,19 +54,18 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
|
||||
}
|
||||
|
||||
Widget _newAccountForm(BuildContext context,
|
||||
{required Future<void> Function(GlobalKey<FormBuilderState>) onSubmit}) {
|
||||
return FormBuilder(
|
||||
{required Future<void> Function(GlobalKey<FormBuilderState>) onSubmit}) => FormBuilder(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
Text(translate("new_account_page.header"))
|
||||
Text(translate('new_account_page.header'))
|
||||
.textStyle(context.headlineSmall)
|
||||
.paddingSymmetric(vertical: 16),
|
||||
FormBuilderTextField(
|
||||
autofocus: true,
|
||||
name: formFieldName,
|
||||
decoration:
|
||||
InputDecoration(hintText: translate("account.form_name")),
|
||||
InputDecoration(hintText: translate('account.form_name')),
|
||||
maxLength: 64,
|
||||
// The validator receives the text that the user has entered.
|
||||
validator: FormBuilderValidators.compose([
|
||||
@ -79,11 +76,11 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
|
||||
name: formFieldTitle,
|
||||
maxLength: 64,
|
||||
decoration:
|
||||
InputDecoration(hintText: translate("account.form_title")),
|
||||
InputDecoration(hintText: translate('account.form_title')),
|
||||
),
|
||||
Row(children: [
|
||||
const Spacer(),
|
||||
Text(translate("new_account_page.instructions"))
|
||||
Text(translate('new_account_page.instructions'))
|
||||
.toCenter()
|
||||
.flexible(flex: 6),
|
||||
const Spacer(),
|
||||
@ -108,7 +105,6 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -121,7 +117,7 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
|
||||
return Scaffold(
|
||||
// resizeToAvoidBottomInset: false,
|
||||
appBar: DefaultAppBar(context,
|
||||
title: Text(translate("new_account_page.titlebar"))),
|
||||
title: Text(translate('new_account_page.titlebar'))),
|
||||
body: _newAccountForm(
|
||||
context,
|
||||
onSubmit: (formKey) async {
|
||||
@ -130,11 +126,11 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
|
||||
try {
|
||||
await createAccount();
|
||||
} catch (e) {
|
||||
QuickAlert.show(
|
||||
await QuickAlert.show(
|
||||
context: context,
|
||||
type: QuickAlertType.error,
|
||||
title: translate("new_account_page.error"),
|
||||
text: 'Exception: ${e.toString()}',
|
||||
title: translate('new_account_page.error'),
|
||||
text: 'Exception: $e',
|
||||
//backgroundColor: Colors.black,
|
||||
//titleColor: Colors.white,
|
||||
//textColor: Colors.white,
|
||||
@ -144,4 +140,9 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
|
||||
).paddingSymmetric(horizontal: 24, vertical: 8),
|
||||
).withModalHUD(context, displayModalHUD);
|
||||
}
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<bool>('isInAsyncCall', isInAsyncCall));
|
||||
}
|
||||
}
|
||||
|
@ -6,15 +6,12 @@ class LoginPage extends ConsumerWidget {
|
||||
static const path = '/settings';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: null,
|
||||
Widget build(BuildContext context, WidgetRef ref) => const Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Text("Settings Page"),
|
||||
Text('Settings Page'),
|
||||
// ElevatedButton(
|
||||
// onPressed: () async {
|
||||
// ref.watch(authNotifierProvider.notifier).login(
|
||||
@ -28,5 +25,4 @@ class LoginPage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'package:riverpod/src/stream_provider.dart';
|
||||
|
||||
import '../tools/tools.dart';
|
||||
|
||||
enum ConnectionState {
|
||||
@ -13,4 +15,4 @@ enum ConnectionState {
|
||||
|
||||
ExternalStreamState<ConnectionState> globalConnectionState =
|
||||
ExternalStreamState<ConnectionState>(ConnectionState.detached);
|
||||
var globalConnectionStateProvider = globalConnectionState.provider();
|
||||
AutoDisposeStreamProvider<ConnectionState> globalConnectionStateProvider = globalConnectionState.provider();
|
||||
|
@ -3,13 +3,13 @@ import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:veilid/veilid.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:veilid/veilid.dart';
|
||||
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import '../entities/entities.dart';
|
||||
import '../entities/proto.dart' as proto;
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import 'logins.dart';
|
||||
|
||||
part 'local_accounts.g.dart';
|
||||
@ -21,9 +21,9 @@ class LocalAccounts extends _$LocalAccounts
|
||||
//////////////////////////////////////////////////////////////
|
||||
/// AsyncTableDBBacked
|
||||
@override
|
||||
String tableName() => "local_account_manager";
|
||||
String tableName() => 'local_account_manager';
|
||||
@override
|
||||
String tableKeyName() => "local_accounts";
|
||||
String tableKeyName() => 'local_accounts';
|
||||
@override
|
||||
IList<LocalAccount> valueFromJson(Object? obj) => obj != null
|
||||
? IList<LocalAccount>.fromJson(
|
||||
@ -35,9 +35,7 @@ class LocalAccounts extends _$LocalAccounts
|
||||
|
||||
/// Get all local account information
|
||||
@override
|
||||
FutureOr<IList<LocalAccount>> build() async {
|
||||
return await load();
|
||||
}
|
||||
FutureOr<IList<LocalAccount>> build() async => await load();
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
/// Mutators and Selectors
|
||||
@ -45,7 +43,7 @@ class LocalAccounts extends _$LocalAccounts
|
||||
/// Reorder accounts
|
||||
Future<void> reorderAccount(int oldIndex, int newIndex) async {
|
||||
final localAccounts = state.requireValue;
|
||||
var removedItem = Output<LocalAccount>();
|
||||
final removedItem = Output<LocalAccount>();
|
||||
final updated = localAccounts
|
||||
.removeAt(oldIndex, removedItem)
|
||||
.insert(newIndex, removedItem.value!);
|
||||
@ -57,9 +55,8 @@ class LocalAccounts extends _$LocalAccounts
|
||||
Future<LocalAccount> newAccount(
|
||||
{required IdentityMaster identityMaster,
|
||||
required SecretKey identitySecret,
|
||||
EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
|
||||
String encryptionKey = "",
|
||||
required proto.Account account}) async {
|
||||
required proto.Account account, EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
|
||||
String encryptionKey = ''}) async {
|
||||
final veilid = await eventualVeilid.future;
|
||||
final localAccounts = state.requireValue;
|
||||
|
||||
@ -78,7 +75,7 @@ class LocalAccounts extends _$LocalAccounts
|
||||
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
|
||||
final nonce = await cs.randomNonce();
|
||||
identitySecretSaltBytes = nonce.decode();
|
||||
SharedSecret sharedSecret =
|
||||
final sharedSecret =
|
||||
await cs.deriveSharedSecret(ekbytes, identitySecretSaltBytes);
|
||||
identitySecretBytes =
|
||||
await cs.cryptNoAuth(identitySecret.decode(), nonce, sharedSecret);
|
||||
@ -102,11 +99,11 @@ class LocalAccounts extends _$LocalAccounts
|
||||
.withSequencing(Sequencing.ensureOrdered);
|
||||
|
||||
// Open identity key for writing
|
||||
(await DHTRecord.openWrite(dhtctx, identityMaster.identityRecordKey,
|
||||
await (await DHTRecord.openWrite(dhtctx, identityMaster.identityRecordKey,
|
||||
identityMaster.identityWriter(identitySecret)))
|
||||
.scope((identityRec) async {
|
||||
// Create new account to insert into identity
|
||||
(await DHTRecord.create(dhtctx)).deleteScope((accountRec) async {
|
||||
await (await DHTRecord.create(dhtctx)).deleteScope((accountRec) async {
|
||||
// Write account key
|
||||
await accountRec.eventualWriteProtobuf(account);
|
||||
|
||||
@ -117,7 +114,7 @@ class LocalAccounts extends _$LocalAccounts
|
||||
await identityRec.eventualUpdateJson(Identity.fromJson,
|
||||
(oldIdentity) async {
|
||||
final accountRecords = IMapOfSets.from(oldIdentity.accountRecords)
|
||||
.add("com.veilid.veilidchat", newAccountRecordInfo)
|
||||
.add('com.veilid.veilidchat', newAccountRecordInfo)
|
||||
.asIMap();
|
||||
return oldIdentity.copyWith(accountRecords: accountRecords);
|
||||
});
|
||||
|
@ -2,11 +2,11 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:veilid/veilid.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:veilid/veilid.dart';
|
||||
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import '../entities/entities.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import 'local_accounts.dart';
|
||||
|
||||
part 'logins.g.dart';
|
||||
@ -17,9 +17,9 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
|
||||
//////////////////////////////////////////////////////////////
|
||||
/// AsyncTableDBBacked
|
||||
@override
|
||||
String tableName() => "local_account_manager";
|
||||
String tableName() => 'local_account_manager';
|
||||
@override
|
||||
String tableKeyName() => "active_logins";
|
||||
String tableKeyName() => 'active_logins';
|
||||
@override
|
||||
ActiveLogins valueFromJson(Object? obj) => obj != null
|
||||
? ActiveLogins.fromJson(obj as Map<String, dynamic>)
|
||||
@ -29,9 +29,7 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
|
||||
|
||||
/// Get all local account information
|
||||
@override
|
||||
FutureOr<ActiveLogins> build() async {
|
||||
return await load();
|
||||
}
|
||||
FutureOr<ActiveLogins> build() async => await load();
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
/// Mutators and Selectors
|
||||
@ -60,7 +58,7 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
|
||||
|
||||
// Derive key from password
|
||||
if (localAccount.encryptionKeyType != EncryptionKeyType.none) {
|
||||
throw Exception("Wrong authentication type");
|
||||
throw Exception('Wrong authentication type');
|
||||
}
|
||||
|
||||
final identitySecret =
|
||||
@ -72,7 +70,7 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
|
||||
final keyOk = await cs.validateKeyPair(
|
||||
localAccount.identityMaster.identityPublicKey, identitySecret);
|
||||
if (!keyOk) {
|
||||
throw Exception("Identity is corrupted");
|
||||
throw Exception('Identity is corrupted');
|
||||
}
|
||||
|
||||
// Add to user logins and select it
|
||||
@ -110,14 +108,14 @@ class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
|
||||
// Derive key from password
|
||||
if (localAccount.encryptionKeyType != EncryptionKeyType.password ||
|
||||
localAccount.encryptionKeyType != EncryptionKeyType.pin) {
|
||||
throw Exception("Wrong authentication type");
|
||||
throw Exception('Wrong authentication type');
|
||||
}
|
||||
final cs = await veilid
|
||||
.getCryptoSystem(localAccount.identityMaster.identityRecordKey.kind);
|
||||
final ekbytes = Uint8List.fromList(utf8.encode(encryptionKey));
|
||||
final eksalt = localAccount.identitySecretSaltBytes;
|
||||
final nonce = Nonce.fromBytes(eksalt);
|
||||
SharedSecret sharedSecret = await cs.deriveSharedSecret(ekbytes, eksalt);
|
||||
final sharedSecret = await cs.deriveSharedSecret(ekbytes, eksalt);
|
||||
final identitySecret = SecretKey.fromBytes(await cs.cryptNoAuth(
|
||||
localAccount.identitySecretKeyBytes, nonce, sharedSecret));
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
export 'local_accounts.dart';
|
||||
export 'connection_state.dart';
|
||||
export 'local_accounts.dart';
|
||||
export 'logins.dart';
|
||||
|
@ -21,7 +21,7 @@ class ThemeService {
|
||||
};
|
||||
|
||||
String get previousThemeName {
|
||||
String? themeName = prefs.getString('previousThemeName');
|
||||
var themeName = prefs.getString('previousThemeName');
|
||||
if (themeName == null) {
|
||||
final isPlatformDark =
|
||||
WidgetsBinding.instance.platformDispatcher.platformBrightness ==
|
||||
@ -31,8 +31,8 @@ class ThemeService {
|
||||
return themeName;
|
||||
}
|
||||
|
||||
get initial {
|
||||
String? themeName = prefs.getString('theme');
|
||||
ThemeData? get initial {
|
||||
var themeName = prefs.getString('theme');
|
||||
if (themeName == null) {
|
||||
final isPlatformDark =
|
||||
WidgetsBinding.instance.platformDispatcher.platformBrightness ==
|
||||
@ -43,14 +43,12 @@ class ThemeService {
|
||||
}
|
||||
|
||||
save(String newThemeName) {
|
||||
var currentThemeName = prefs.getString('theme');
|
||||
final currentThemeName = prefs.getString('theme');
|
||||
if (currentThemeName != null) {
|
||||
prefs.setString('previousThemeName', currentThemeName);
|
||||
}
|
||||
prefs.setString('theme', newThemeName);
|
||||
}
|
||||
|
||||
ThemeData getByName(String name) {
|
||||
return allThemes[name]!;
|
||||
}
|
||||
ThemeData getByName(String name) => allThemes[name]!;
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
export 'light.dart';
|
||||
export 'dark.dart';
|
||||
export 'light.dart';
|
||||
|
@ -1,7 +1,8 @@
|
||||
import 'dart:io';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
Future<void> setupDesktopWindow() async {
|
||||
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
|
||||
@ -15,7 +16,7 @@ Future<void> setupDesktopWindow() async {
|
||||
skipTaskbar: false,
|
||||
titleBarStyle: TitleBarStyle.hidden,
|
||||
);
|
||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
await windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
await windowManager.show();
|
||||
await windowManager.focus();
|
||||
});
|
||||
|
@ -4,18 +4,17 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
// Caches a state value which can be changed from anywhere
|
||||
// Creates a provider interface that notices when the value changes
|
||||
class ExternalStreamState<T> {
|
||||
T currentState;
|
||||
StreamController<T> streamController;
|
||||
ExternalStreamState(T initialState)
|
||||
: currentState = initialState,
|
||||
streamController = StreamController<T>.broadcast();
|
||||
T currentState;
|
||||
StreamController<T> streamController;
|
||||
void add(T newState) {
|
||||
currentState = newState;
|
||||
streamController.add(newState);
|
||||
}
|
||||
|
||||
AutoDisposeStreamProvider<T> provider() {
|
||||
return AutoDisposeStreamProvider<T>((ref) async* {
|
||||
AutoDisposeStreamProvider<T> provider() => AutoDisposeStreamProvider<T>((ref) async* {
|
||||
if (await streamController.stream.isEmpty) {
|
||||
yield currentState;
|
||||
}
|
||||
@ -23,5 +22,4 @@ class ExternalStreamState<T> {
|
||||
yield value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +1,22 @@
|
||||
import 'dart:typed_data';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
T jsonDecodeBytes<T>(
|
||||
T Function(Map<String, dynamic>) fromJson, Uint8List data) {
|
||||
return fromJson(jsonDecode(utf8.decode(data)));
|
||||
}
|
||||
T Function(Map<String, dynamic>) fromJson, Uint8List data) => fromJson(jsonDecode(utf8.decode(data)));
|
||||
|
||||
Uint8List jsonEncodeBytes(Object? object,
|
||||
{Object? Function(Object?)? toEncodable}) {
|
||||
return Uint8List.fromList(
|
||||
{Object? Function(Object?)? toEncodable}) => Uint8List.fromList(
|
||||
utf8.encode(jsonEncode(object, toEncodable: toEncodable)));
|
||||
}
|
||||
|
||||
Future<Uint8List> jsonUpdateBytes<T>(T Function(Map<String, dynamic>) fromJson,
|
||||
Uint8List oldBytes, Future<T> Function(T) update) async {
|
||||
T oldObj = fromJson(jsonDecode(utf8.decode(oldBytes)));
|
||||
T newObj = await update(oldObj);
|
||||
final oldObj = fromJson(jsonDecode(utf8.decode(oldBytes)));
|
||||
final newObj = await update(oldObj);
|
||||
return jsonEncodeBytes(newObj);
|
||||
}
|
||||
|
||||
Future<Uint8List> Function(Uint8List) jsonUpdate<T>(
|
||||
T Function(Map<String, dynamic>) fromJson, Future<T> Function(T) update) {
|
||||
return (Uint8List oldBytes) {
|
||||
return jsonUpdateBytes(fromJson, oldBytes, update);
|
||||
};
|
||||
}
|
||||
T Function(Map<String, dynamic>) fromJson, Future<T> Function(T) update) => (oldBytes) => jsonUpdateBytes(fromJson, oldBytes, update);
|
||||
|
||||
T Function(Object?) genericFromJson<T>(
|
||||
T Function(Map<String, dynamic>) fromJsonMap) {
|
||||
return (Object? json) {
|
||||
return fromJsonMap(json as Map<String, dynamic>);
|
||||
};
|
||||
}
|
||||
T Function(Map<String, dynamic>) fromJsonMap) => (json) => fromJsonMap(json as Map<String, dynamic>);
|
||||
|
@ -265,9 +265,9 @@ const _byteToPhono = [
|
||||
Map<String, int> _phonoToByte = _buildPhonoToByte();
|
||||
|
||||
Map<String, int> _buildPhonoToByte() {
|
||||
Map<String, int> phonoToByte = {};
|
||||
for (int b = 0; b < 256; b++) {
|
||||
String ph = _byteToPhono[b];
|
||||
final phonoToByte = <String, int>{};
|
||||
for (var b = 0; b < 256; b++) {
|
||||
final ph = _byteToPhono[b];
|
||||
phonoToByte[ph] = b;
|
||||
}
|
||||
return phonoToByte;
|
||||
@ -278,10 +278,10 @@ String prettyPhonoString(String s,
|
||||
assert(wordsPerLine >= 1);
|
||||
assert(phonoPerWord >= 1);
|
||||
final cs = canonicalPhonoString(s).toUpperCase();
|
||||
String out = "";
|
||||
int words = 0;
|
||||
int phonos = 0;
|
||||
for (int i = 0; i < cs.length; i += 3) {
|
||||
var out = '';
|
||||
var words = 0;
|
||||
var phonos = 0;
|
||||
for (var i = 0; i < cs.length; i += 3) {
|
||||
if (i != 0) {
|
||||
phonos += 1;
|
||||
if (phonos == phonoPerWord) {
|
||||
@ -289,9 +289,9 @@ String prettyPhonoString(String s,
|
||||
words += 1;
|
||||
if (words == wordsPerLine) {
|
||||
words = 0;
|
||||
out += "\n";
|
||||
out += '\n';
|
||||
} else {
|
||||
out += " ";
|
||||
out += ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -301,22 +301,22 @@ String prettyPhonoString(String s,
|
||||
}
|
||||
|
||||
String canonicalPhonoString(String s) {
|
||||
Uint8List bytes = Uint8List.fromList(utf8.encode(s.toLowerCase()));
|
||||
String cs = "";
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
int ch = bytes[i];
|
||||
final bytes = Uint8List.fromList(utf8.encode(s.toLowerCase()));
|
||||
var cs = '';
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
final ch = bytes[i];
|
||||
if (ch >= $a && ch <= $z) {
|
||||
cs += String.fromCharCode(ch);
|
||||
}
|
||||
}
|
||||
if (cs.length % 3 != 0) {
|
||||
throw const FormatException(
|
||||
"phonobyte string length should be a multiple of 3");
|
||||
'phonobyte string length should be a multiple of 3');
|
||||
}
|
||||
for (int i = 0; i < cs.length; i += 3) {
|
||||
String ph = cs.substring(i, i + 3);
|
||||
for (var i = 0; i < cs.length; i += 3) {
|
||||
final ph = cs.substring(i, i + 3);
|
||||
if (!_phonoToByte.containsKey(ph)) {
|
||||
throw const FormatException("phonobyte string contains invalid sequence");
|
||||
throw const FormatException('phonobyte string contains invalid sequence');
|
||||
}
|
||||
}
|
||||
return cs;
|
||||
@ -325,17 +325,17 @@ String canonicalPhonoString(String s) {
|
||||
Uint8List decodePhono(String s) {
|
||||
final cs = canonicalPhonoString(s);
|
||||
final out = Uint8List(cs.length ~/ 3);
|
||||
for (int i = 0; i < cs.length; i += 3) {
|
||||
String ph = cs.substring(i, i + 3);
|
||||
int b = _phonoToByte[ph]!;
|
||||
for (var i = 0; i < cs.length; i += 3) {
|
||||
final ph = cs.substring(i, i + 3);
|
||||
final b = _phonoToByte[ph]!;
|
||||
out[i] = b;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
String encodePhono(Uint8List b) {
|
||||
String out = "";
|
||||
for (int i = 0; i < b.length; i++) {
|
||||
var out = '';
|
||||
for (var i = 0; i < b.length; i++) {
|
||||
out += _byteToPhono[b[i]];
|
||||
}
|
||||
return out;
|
||||
|
@ -1,19 +1,16 @@
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
Future<Uint8List> protobufUpdateBytes<T extends GeneratedMessage>(
|
||||
T Function(List<int>) fromBuffer,
|
||||
Uint8List oldBytes,
|
||||
Future<T> Function(T) update) async {
|
||||
T oldObj = fromBuffer(oldBytes);
|
||||
T newObj = await update(oldObj);
|
||||
final oldObj = fromBuffer(oldBytes);
|
||||
final newObj = await update(oldObj);
|
||||
return Uint8List.fromList(newObj.writeToBuffer());
|
||||
}
|
||||
|
||||
Future<Uint8List> Function(Uint8List)
|
||||
protobufUpdate<T extends GeneratedMessage>(
|
||||
T Function(List<int>) fromBuffer, Future<T> Function(T) update) {
|
||||
return (Uint8List oldBytes) {
|
||||
return protobufUpdateBytes(fromBuffer, oldBytes, update);
|
||||
};
|
||||
}
|
||||
T Function(List<int>) fromBuffer, Future<T> Function(T) update) => (oldBytes) => protobufUpdateBytes(fromBuffer, oldBytes, update);
|
||||
|
@ -1,6 +1,6 @@
|
||||
export 'desktop_control.dart';
|
||||
export 'external_stream_state.dart';
|
||||
export 'json_tools.dart';
|
||||
export 'phono_byte.dart';
|
||||
export 'protobuf_tools.dart';
|
||||
export 'widget_helpers.dart';
|
||||
export 'desktop_control.dart';
|
||||
|
@ -1,27 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
|
||||
extension BorderExt on Widget {
|
||||
Container debugBorder() {
|
||||
return Container(
|
||||
Container debugBorder() => DecoratedBox(
|
||||
decoration: BoxDecoration(border: Border.all(color: Colors.redAccent)),
|
||||
child: this);
|
||||
}
|
||||
}
|
||||
|
||||
extension ModalProgressExt on Widget {
|
||||
BlurryModalProgressHUD withModalHUD(BuildContext context, bool isLoading) {
|
||||
return BlurryModalProgressHUD(
|
||||
BlurryModalProgressHUD withModalHUD(BuildContext context, bool isLoading) => BlurryModalProgressHUD(
|
||||
inAsyncCall: isLoading,
|
||||
blurEffectIntensity: 4,
|
||||
progressIndicator: SpinKitFoldingCube(
|
||||
color: Theme.of(context).highlightColor,
|
||||
size: 90.0,
|
||||
size: 90,
|
||||
),
|
||||
dismissible: false,
|
||||
opacity: 0.3,
|
||||
color: Theme.of(context).shadowColor,
|
||||
child: this);
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import 'package:veilid/veilid.dart';
|
||||
|
||||
Future<VeilidConfig> getVeilidChatConfig() async {
|
||||
VeilidConfig config = await getDefaultVeilidConfig("VeilidChat");
|
||||
if (const String.fromEnvironment("DELETE_TABLE_STORE") == "1") {
|
||||
var config = await getDefaultVeilidConfig('VeilidChat');
|
||||
if (const String.fromEnvironment('DELETE_TABLE_STORE') == '1') {
|
||||
config =
|
||||
config.copyWith(tableStore: config.tableStore.copyWith(delete: true));
|
||||
}
|
||||
if (const String.fromEnvironment("DELETE_PROTECTED_STORE") == "1") {
|
||||
if (const String.fromEnvironment('DELETE_PROTECTED_STORE') == '1') {
|
||||
config = config.copyWith(
|
||||
protectedStore: config.protectedStore.copyWith(delete: true));
|
||||
}
|
||||
if (const String.fromEnvironment("DELETE_BLOCK_STORE") == "1") {
|
||||
if (const String.fromEnvironment('DELETE_BLOCK_STORE') == '1') {
|
||||
config =
|
||||
config.copyWith(blockStore: config.blockStore.copyWith(delete: true));
|
||||
}
|
||||
|
@ -3,10 +3,22 @@ import 'dart:typed_data';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'package:veilid/veilid.dart';
|
||||
|
||||
import 'veilid_support.dart';
|
||||
import '../tools/tools.dart';
|
||||
import 'veilid_support.dart';
|
||||
|
||||
class DHTRecord {
|
||||
|
||||
DHTRecord(
|
||||
{required VeilidRoutingContext dhtctx,
|
||||
required DHTRecordDescriptor recordDescriptor,
|
||||
int defaultSubkey = 0,
|
||||
KeyPair? writer,
|
||||
DHTRecordCrypto crypto = const DHTRecordCryptoPublic()})
|
||||
: _dhtctx = dhtctx,
|
||||
_recordDescriptor = recordDescriptor,
|
||||
_defaultSubkey = defaultSubkey,
|
||||
_writer = writer,
|
||||
_crypto = crypto;
|
||||
final VeilidRoutingContext _dhtctx;
|
||||
final DHTRecordDescriptor _recordDescriptor;
|
||||
final int _defaultSubkey;
|
||||
@ -17,7 +29,7 @@ class DHTRecord {
|
||||
{DHTSchema schema = const DHTSchema.dflt(oCnt: 1),
|
||||
int defaultSubkey = 0,
|
||||
DHTRecordCrypto? crypto}) async {
|
||||
DHTRecordDescriptor recordDescriptor = await dhtctx.createDHTRecord(schema);
|
||||
final recordDescriptor = await dhtctx.createDHTRecord(schema);
|
||||
|
||||
final rec = DHTRecord(
|
||||
dhtctx: dhtctx,
|
||||
@ -34,13 +46,12 @@ class DHTRecord {
|
||||
static Future<DHTRecord> openRead(
|
||||
VeilidRoutingContext dhtctx, TypedKey recordKey,
|
||||
{int defaultSubkey = 0, DHTRecordCrypto? crypto}) async {
|
||||
DHTRecordDescriptor recordDescriptor =
|
||||
final recordDescriptor =
|
||||
await dhtctx.openDHTRecord(recordKey, null);
|
||||
final rec = DHTRecord(
|
||||
dhtctx: dhtctx,
|
||||
recordDescriptor: recordDescriptor,
|
||||
defaultSubkey: defaultSubkey,
|
||||
writer: null,
|
||||
crypto: crypto ?? const DHTRecordCryptoPublic());
|
||||
|
||||
return rec;
|
||||
@ -53,7 +64,7 @@ class DHTRecord {
|
||||
int defaultSubkey = 0,
|
||||
DHTRecordCrypto? crypto,
|
||||
}) async {
|
||||
DHTRecordDescriptor recordDescriptor =
|
||||
final recordDescriptor =
|
||||
await dhtctx.openDHTRecord(recordKey, writer);
|
||||
final rec = DHTRecord(
|
||||
dhtctx: dhtctx,
|
||||
@ -66,35 +77,15 @@ class DHTRecord {
|
||||
return rec;
|
||||
}
|
||||
|
||||
DHTRecord(
|
||||
{required VeilidRoutingContext dhtctx,
|
||||
required DHTRecordDescriptor recordDescriptor,
|
||||
int defaultSubkey = 0,
|
||||
KeyPair? writer,
|
||||
DHTRecordCrypto crypto = const DHTRecordCryptoPublic()})
|
||||
: _dhtctx = dhtctx,
|
||||
_recordDescriptor = recordDescriptor,
|
||||
_defaultSubkey = defaultSubkey,
|
||||
_writer = writer,
|
||||
_crypto = crypto;
|
||||
|
||||
int subkeyOrDefault(int subkey) => (subkey == -1) ? _defaultSubkey : subkey;
|
||||
|
||||
TypedKey key() {
|
||||
return _recordDescriptor.key;
|
||||
}
|
||||
TypedKey key() => _recordDescriptor.key;
|
||||
|
||||
PublicKey owner() {
|
||||
return _recordDescriptor.owner;
|
||||
}
|
||||
PublicKey owner() => _recordDescriptor.owner;
|
||||
|
||||
KeyPair? ownerKeyPair() {
|
||||
return _recordDescriptor.ownerKeyPair();
|
||||
}
|
||||
KeyPair? ownerKeyPair() => _recordDescriptor.ownerKeyPair();
|
||||
|
||||
KeyPair? writer() {
|
||||
return _writer;
|
||||
}
|
||||
KeyPair? writer() => _writer;
|
||||
|
||||
void setCrypto(DHTRecordCrypto crypto) {
|
||||
_crypto = crypto;
|
||||
@ -112,24 +103,24 @@ class DHTRecord {
|
||||
try {
|
||||
return await scopeFunction(this);
|
||||
} finally {
|
||||
close();
|
||||
await close();
|
||||
}
|
||||
}
|
||||
|
||||
Future<T> deleteScope<T>(Future<T> Function(DHTRecord) scopeFunction) async {
|
||||
try {
|
||||
final out = await scopeFunction(this);
|
||||
close();
|
||||
await close();
|
||||
return out;
|
||||
} catch (_) {
|
||||
delete();
|
||||
await delete();
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Uint8List?> get({int subkey = -1, bool forceRefresh = false}) async {
|
||||
subkey = subkeyOrDefault(subkey);
|
||||
ValueData? valueData =
|
||||
final valueData =
|
||||
await _dhtctx.getDHTValue(_recordDescriptor.key, subkey, false);
|
||||
if (valueData == null) {
|
||||
return null;
|
||||
@ -165,12 +156,12 @@ class DHTRecord {
|
||||
{int subkey = -1}) async {
|
||||
subkey = subkeyOrDefault(subkey);
|
||||
// Get existing identity key
|
||||
ValueData? valueData =
|
||||
var valueData =
|
||||
await _dhtctx.getDHTValue(_recordDescriptor.key, subkey, false);
|
||||
do {
|
||||
// Ensure it exists already
|
||||
if (valueData == null) {
|
||||
throw const FormatException("value does not exist");
|
||||
throw const FormatException('value does not exist');
|
||||
}
|
||||
|
||||
// Update the data
|
||||
@ -186,25 +177,17 @@ class DHTRecord {
|
||||
} while (valueData != null);
|
||||
}
|
||||
|
||||
Future<void> eventualWriteJson<T>(T newValue, {int subkey = -1}) {
|
||||
return eventualWriteBytes(jsonEncodeBytes(newValue), subkey: subkey);
|
||||
}
|
||||
Future<void> eventualWriteJson<T>(T newValue, {int subkey = -1}) => eventualWriteBytes(jsonEncodeBytes(newValue), subkey: subkey);
|
||||
|
||||
Future<void> eventualWriteProtobuf<T extends GeneratedMessage>(T newValue,
|
||||
{int subkey = -1}) {
|
||||
return eventualWriteBytes(newValue.writeToBuffer(), subkey: subkey);
|
||||
}
|
||||
{int subkey = -1}) => eventualWriteBytes(newValue.writeToBuffer(), subkey: subkey);
|
||||
|
||||
Future<void> eventualUpdateJson<T>(
|
||||
T Function(Map<String, dynamic>) fromJson, Future<T> Function(T) update,
|
||||
{int subkey = -1}) {
|
||||
return eventualUpdateBytes(jsonUpdate(fromJson, update), subkey: subkey);
|
||||
}
|
||||
{int subkey = -1}) => eventualUpdateBytes(jsonUpdate(fromJson, update), subkey: subkey);
|
||||
|
||||
Future<void> eventualUpdateProtobuf<T extends GeneratedMessage>(
|
||||
T Function(List<int>) fromBuffer, Future<T> Function(T) update,
|
||||
{int subkey = -1}) {
|
||||
return eventualUpdateBytes(protobufUpdate(fromBuffer, update),
|
||||
{int subkey = -1}) => eventualUpdateBytes(protobufUpdate(fromBuffer, update),
|
||||
subkey: subkey);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:veilid/veilid.dart';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'veilid_init.dart';
|
||||
|
||||
@ -13,13 +13,13 @@ abstract class DHTRecordCrypto {
|
||||
////////////////////////////////////
|
||||
/// Private DHT Record: Encrypted for a specific symmetric key
|
||||
class DHTRecordCryptoPrivate implements DHTRecordCrypto {
|
||||
final VeilidCryptoSystem _cryptoSystem;
|
||||
final SharedSecret _secretKey;
|
||||
|
||||
DHTRecordCryptoPrivate._(
|
||||
VeilidCryptoSystem cryptoSystem, SharedSecret secretKey)
|
||||
: _cryptoSystem = cryptoSystem,
|
||||
_secretKey = secretKey;
|
||||
final VeilidCryptoSystem _cryptoSystem;
|
||||
final SharedSecret _secretKey;
|
||||
|
||||
static Future<DHTRecordCryptoPrivate> fromTypedKeyPair(
|
||||
TypedKeyPair typedKeyPair) async {
|
||||
@ -41,7 +41,7 @@ class DHTRecordCryptoPrivate implements DHTRecordCrypto {
|
||||
// generate nonce
|
||||
final nonce = await _cryptoSystem.randomNonce();
|
||||
// crypt and append nonce
|
||||
var b = BytesBuilder();
|
||||
final b = BytesBuilder();
|
||||
b.add(await _cryptoSystem.cryptNoAuth(data, nonce, _secretKey));
|
||||
b.add(nonce.decode());
|
||||
return b.toBytes();
|
||||
@ -51,7 +51,7 @@ class DHTRecordCryptoPrivate implements DHTRecordCrypto {
|
||||
FutureOr<Uint8List> decrypt(Uint8List data, int subkey) async {
|
||||
// split off nonce from end
|
||||
if (data.length <= Nonce.decodedLength()) {
|
||||
throw const FormatException("not enough data to decrypt");
|
||||
throw const FormatException('not enough data to decrypt');
|
||||
}
|
||||
final nonce =
|
||||
Nonce.fromBytes(data.sublist(data.length - Nonce.decodedLength()));
|
||||
@ -67,12 +67,8 @@ class DHTRecordCryptoPublic implements DHTRecordCrypto {
|
||||
const DHTRecordCryptoPublic();
|
||||
|
||||
@override
|
||||
FutureOr<Uint8List> encrypt(Uint8List data, int subkey) {
|
||||
return data;
|
||||
}
|
||||
FutureOr<Uint8List> encrypt(Uint8List data, int subkey) => data;
|
||||
|
||||
@override
|
||||
FutureOr<Uint8List> decrypt(Uint8List data, int subkey) {
|
||||
return data;
|
||||
}
|
||||
FutureOr<Uint8List> decrypt(Uint8List data, int subkey) => data;
|
||||
}
|
||||
|
@ -9,13 +9,13 @@ import 'veilid_support.dart';
|
||||
// Identity Master with secrets
|
||||
// Not freezed because we never persist this class in its entirety
|
||||
class IdentityMasterWithSecrets {
|
||||
IdentityMaster identityMaster;
|
||||
SecretKey masterSecret;
|
||||
SecretKey identitySecret;
|
||||
IdentityMasterWithSecrets(
|
||||
{required this.identityMaster,
|
||||
required this.masterSecret,
|
||||
required this.identitySecret});
|
||||
IdentityMaster identityMaster;
|
||||
SecretKey masterSecret;
|
||||
SecretKey identitySecret;
|
||||
|
||||
Future<void> delete() async {
|
||||
final veilid = await eventualVeilid.future;
|
||||
|
@ -1,18 +1,20 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:veilid/veilid.dart';
|
||||
import 'config.dart';
|
||||
import 'veilid_log.dart';
|
||||
|
||||
import '../log/log.dart';
|
||||
import '../providers/providers.dart';
|
||||
import 'config.dart';
|
||||
import 'veilid_log.dart';
|
||||
|
||||
class Processor {
|
||||
String _veilidVersion = "";
|
||||
|
||||
Processor();
|
||||
String _veilidVersion = '';
|
||||
bool _startedUp = false;
|
||||
Stream<VeilidUpdate>? _updateStream;
|
||||
Future<void>? _updateProcessor;
|
||||
|
||||
Processor();
|
||||
|
||||
Future<void> startup() async {
|
||||
if (_startedUp) {
|
||||
return;
|
||||
@ -24,7 +26,7 @@ class Processor {
|
||||
_veilidVersion = 'Failed to get veilid version.';
|
||||
}
|
||||
|
||||
log.info("Veilid version: $_veilidVersion");
|
||||
log.info('Veilid version: $_veilidVersion');
|
||||
|
||||
// In case of hot restart shut down first
|
||||
try {
|
||||
@ -33,7 +35,7 @@ class Processor {
|
||||
//
|
||||
}
|
||||
|
||||
var updateStream =
|
||||
final updateStream =
|
||||
await Veilid.instance.startupVeilidCore(await getVeilidChatConfig());
|
||||
_updateStream = updateStream;
|
||||
_updateProcessor = processUpdates();
|
||||
@ -113,7 +115,7 @@ class Processor {
|
||||
}
|
||||
|
||||
Future<void> processUpdates() async {
|
||||
var stream = _updateStream;
|
||||
final stream = _updateStream;
|
||||
if (stream != null) {
|
||||
await for (final update in stream) {
|
||||
if (update is VeilidLog) {
|
||||
@ -125,11 +127,11 @@ class Processor {
|
||||
} else if (update is VeilidUpdateNetwork) {
|
||||
await processUpdateNetwork(update);
|
||||
} else if (update is VeilidAppMessage) {
|
||||
log.info("AppMessage: ${update.toJson()}");
|
||||
log.info('AppMessage: ${update.toJson()}');
|
||||
} else if (update is VeilidAppCall) {
|
||||
log.info("AppCall: ${update.toJson()}");
|
||||
log.info('AppCall: ${update.toJson()}');
|
||||
} else {
|
||||
log.trace("Update: ${update.toJson()}");
|
||||
log.trace('Update: ${update.toJson()}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ Future<T> tableScope<T>(
|
||||
String name, Future<T> Function(VeilidTableDB tdb) callback,
|
||||
{int columnCount = 1}) async {
|
||||
final veilid = await eventualVeilid.future;
|
||||
VeilidTableDB tableDB = await veilid.openTableDB(name, columnCount);
|
||||
final tableDB = await veilid.openTableDB(name, columnCount);
|
||||
try {
|
||||
return await callback(tableDB);
|
||||
} finally {
|
||||
@ -17,7 +17,7 @@ Future<T> transactionScope<T>(
|
||||
VeilidTableDB tdb,
|
||||
Future<T> Function(VeilidTableDBTransaction tdbt) callback,
|
||||
) async {
|
||||
VeilidTableDBTransaction tdbt = tdb.transact();
|
||||
final tdbt = tdb.transact();
|
||||
try {
|
||||
final ret = await callback(tdbt);
|
||||
if (!tdbt.isDone()) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:veilid/veilid.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'processor.dart';
|
||||
import 'veilid_log.dart';
|
||||
|
||||
@ -23,7 +23,7 @@ Future<String> getVeilidVersion() async {
|
||||
// Call only once.
|
||||
void _initVeilid() {
|
||||
if (kIsWeb) {
|
||||
var platformConfig = const VeilidWASMConfig(
|
||||
const platformConfig = VeilidWASMConfig(
|
||||
logging: VeilidWASMConfigLogging(
|
||||
performance: VeilidWASMConfigLoggingPerformance(
|
||||
enabled: true,
|
||||
@ -34,7 +34,7 @@ void _initVeilid() {
|
||||
enabled: true, level: VeilidConfigLogLevel.info)));
|
||||
Veilid.instance.initializeVeilidCore(platformConfig.toJson());
|
||||
} else {
|
||||
var platformConfig = const VeilidFFIConfig(
|
||||
const platformConfig = VeilidFFIConfig(
|
||||
logging: VeilidFFIConfigLogging(
|
||||
terminal: VeilidFFIConfigLoggingTerminal(
|
||||
enabled: false,
|
||||
@ -43,8 +43,8 @@ void _initVeilid() {
|
||||
otlp: VeilidFFIConfigLoggingOtlp(
|
||||
enabled: false,
|
||||
level: VeilidConfigLogLevel.trace,
|
||||
grpcEndpoint: "192.168.1.40:4317",
|
||||
serviceName: "VeilidChat"),
|
||||
grpcEndpoint: '192.168.1.40:4317',
|
||||
serviceName: 'VeilidChat'),
|
||||
api: VeilidFFIConfigLoggingApi(
|
||||
enabled: true, level: VeilidConfigLogLevel.info)));
|
||||
Veilid.instance.initializeVeilidCore(platformConfig.toJson());
|
||||
@ -75,6 +75,4 @@ Future<void> initializeVeilid() async {
|
||||
|
||||
// Expose the Veilid instance as a FutureProvider
|
||||
@riverpod
|
||||
FutureOr<Veilid> veilidInstance(VeilidInstanceRef ref) async {
|
||||
return await eventualVeilid.future;
|
||||
}
|
||||
FutureOr<Veilid> veilidInstance(VeilidInstanceRef ref) async => await eventualVeilid.future;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:veilid/veilid.dart';
|
||||
import 'package:loggy/loggy.dart';
|
||||
import 'package:veilid/veilid.dart';
|
||||
|
||||
import '../log/loggy.dart';
|
||||
|
||||
VeilidConfigLogLevel convertToVeilidConfigLogLevel(LogLevel? level) {
|
||||
@ -23,7 +24,7 @@ VeilidConfigLogLevel convertToVeilidConfigLogLevel(LogLevel? level) {
|
||||
}
|
||||
|
||||
void setVeilidLogLevel(LogLevel? level) {
|
||||
Veilid.instance.changeLogLevel("all", convertToVeilidConfigLogLevel(level));
|
||||
Veilid.instance.changeLogLevel('all', convertToVeilidConfigLogLevel(level));
|
||||
}
|
||||
|
||||
class VeilidLoggy implements LoggyType {
|
||||
@ -39,7 +40,7 @@ Future<void> processLog(VeilidLog log) async {
|
||||
final backtrace = log.backtrace;
|
||||
if (backtrace != null) {
|
||||
stackTrace =
|
||||
StackTrace.fromString("$backtrace\n${StackTrace.current.toString()}");
|
||||
StackTrace.fromString('$backtrace\n${StackTrace.current}');
|
||||
error = 'embedded stack trace for ${log.logLevel} ${log.message}';
|
||||
}
|
||||
|
||||
@ -63,7 +64,7 @@ Future<void> processLog(VeilidLog log) async {
|
||||
}
|
||||
|
||||
void initVeilidLog() {
|
||||
const isTrace = String.fromEnvironment("logTrace", defaultValue: "") != "";
|
||||
const isTrace = String.fromEnvironment('logTrace') != '';
|
||||
LogLevel logLevel;
|
||||
if (isTrace) {
|
||||
logLevel = traceLevel;
|
||||
|
@ -1,8 +1,8 @@
|
||||
export 'config.dart';
|
||||
export 'processor.dart';
|
||||
export 'veilid_log.dart';
|
||||
export 'veilid_init.dart';
|
||||
export 'dht_record.dart';
|
||||
export 'dht_record_crypto.dart';
|
||||
export 'table_db.dart';
|
||||
export 'identity_master.dart';
|
||||
export 'processor.dart';
|
||||
export 'table_db.dart';
|
||||
export 'veilid_init.dart';
|
||||
export 'veilid_log.dart';
|
||||
|
18
pubspec.lock
18
pubspec.lock
@ -414,14 +414,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.1"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -613,14 +605,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.7.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
lint_hard:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: lints
|
||||
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
|
||||
name: lint_hard
|
||||
sha256: "44d15ec309b1a8e1aff99069df9dcb1597f49d5f588f32811ca28fb7b38c32fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "4.0.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -54,12 +54,12 @@ dependencies:
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^2.0.1
|
||||
build_runner: ^2.4.6
|
||||
freezed: ^2.3.5
|
||||
json_serializable: ^6.7.1
|
||||
riverpod_generator: ^2.2.3
|
||||
flutter_launcher_icons: "^0.13.1"
|
||||
lint_hard: ^4.0.0
|
||||
|
||||
flutter_launcher_icons:
|
||||
image_path: "assets/launcher/icon.png"
|
||||
|
Loading…
Reference in New Issue
Block a user