mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2024-10-01 06:55:46 -04:00
break everything
This commit is contained in:
parent
e898074387
commit
29210c89d2
47
lib/app.dart
47
lib/app.dart
@ -1,45 +1,50 @@
|
||||
import 'package:animated_theme_switcher/animated_theme_switcher.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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';
|
||||
import 'tick.dart';
|
||||
|
||||
class VeilidChatApp extends ConsumerWidget {
|
||||
class VeilidChatApp extends StatelessWidget {
|
||||
const VeilidChatApp({
|
||||
required this.theme,
|
||||
required this.themeData,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final ThemeData theme;
|
||||
final ThemeData themeData;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final router = ref.watch(routerProvider);
|
||||
Widget build(BuildContext context) {
|
||||
final localizationDelegate = LocalizedApp.of(context).delegate;
|
||||
|
||||
return ThemeProvider(
|
||||
initTheme: theme,
|
||||
initTheme: themeData,
|
||||
builder: (_, theme) => LocalizationProvider(
|
||||
state: LocalizationProvider.of(context).state,
|
||||
child: BackgroundTicker(
|
||||
builder: (context) => MaterialApp.router(
|
||||
debugShowCheckedModeBanner: false,
|
||||
routerConfig: router,
|
||||
title: translate('app.title'),
|
||||
theme: theme,
|
||||
localizationsDelegates: [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
FormBuilderLocalizations.delegate,
|
||||
localizationDelegate
|
||||
],
|
||||
supportedLocales: localizationDelegate.supportedLocales,
|
||||
locale: localizationDelegate.currentLocale,
|
||||
builder: (context) => BlocProvider(
|
||||
create: (context) => RouterCubit(),
|
||||
child: MaterialApp.router(
|
||||
debugShowCheckedModeBanner: false,
|
||||
routerConfig: router(
|
||||
routerCubit:
|
||||
BlocProvider.of<RouterCubit>(context)),
|
||||
title: translate('app.title'),
|
||||
theme: theme,
|
||||
localizationsDelegates: [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
FormBuilderLocalizations.delegate,
|
||||
localizationDelegate
|
||||
],
|
||||
supportedLocales:
|
||||
localizationDelegate.supportedLocales,
|
||||
locale: localizationDelegate.currentLocale,
|
||||
),
|
||||
)),
|
||||
));
|
||||
}
|
||||
@ -47,6 +52,6 @@ class VeilidChatApp extends ConsumerWidget {
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<ThemeData>('theme', theme));
|
||||
properties.add(DiagnosticsProperty<ThemeData>('themeData', themeData));
|
||||
}
|
||||
}
|
||||
|
@ -1,76 +0,0 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:change_case/change_case.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../proto/proto.dart' as proto;
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
|
||||
part 'local_account.freezed.dart';
|
||||
part 'local_account.g.dart';
|
||||
|
||||
// Local account identitySecretKey is potentially encrypted with a key
|
||||
// using the following mechanisms
|
||||
// * None : no key, bytes are unencrypted
|
||||
// * Pin : Code is a numeric pin (4-256 numeric digits) hashed with Argon2
|
||||
// * Password: Code is a UTF-8 string that is hashed with Argon2
|
||||
enum EncryptionKeyType {
|
||||
none,
|
||||
pin,
|
||||
password;
|
||||
|
||||
factory EncryptionKeyType.fromJson(dynamic j) =>
|
||||
EncryptionKeyType.values.byName((j as String).toCamelCase());
|
||||
|
||||
factory EncryptionKeyType.fromProto(proto.EncryptionKeyType p) {
|
||||
// ignore: exhaustive_cases
|
||||
switch (p) {
|
||||
case proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_NONE:
|
||||
return EncryptionKeyType.none;
|
||||
case proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_PIN:
|
||||
return EncryptionKeyType.pin;
|
||||
case proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_PASSWORD:
|
||||
return EncryptionKeyType.password;
|
||||
}
|
||||
throw StateError('unknown EncryptionKeyType enum value');
|
||||
}
|
||||
String toJson() => name.toPascalCase();
|
||||
proto.EncryptionKeyType toProto() => switch (this) {
|
||||
EncryptionKeyType.none =>
|
||||
proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_NONE,
|
||||
EncryptionKeyType.pin =>
|
||||
proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_PIN,
|
||||
EncryptionKeyType.password =>
|
||||
proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_PASSWORD,
|
||||
};
|
||||
}
|
||||
|
||||
// Local Accounts are stored in a table locally and not backed by a DHT key
|
||||
// and represents the accounts that have been added/imported
|
||||
// on the current device.
|
||||
// Stores a copy of the IdentityMaster associated with the account
|
||||
// and the identitySecretKey optionally encrypted by an unlock code
|
||||
// This is the root of the account information tree for VeilidChat
|
||||
//
|
||||
@freezed
|
||||
class LocalAccount with _$LocalAccount {
|
||||
const factory LocalAccount({
|
||||
// The master key record for the account, containing the identityPublicKey
|
||||
required IdentityMaster identityMaster,
|
||||
// The encrypted identity secret that goes with
|
||||
// the identityPublicKey with appended salt
|
||||
@Uint8ListJsonConverter() required Uint8List identitySecretBytes,
|
||||
// The kind of encryption input used on the account
|
||||
required EncryptionKeyType encryptionKeyType,
|
||||
// If account is not hidden, password can be retrieved via
|
||||
required bool biometricsEnabled,
|
||||
// Keep account hidden unless account password is entered
|
||||
// (tries all hidden accounts with auth method (no biometrics))
|
||||
required bool hiddenAccount,
|
||||
// Display name for account until it is unlocked
|
||||
required String name,
|
||||
}) = _LocalAccount;
|
||||
|
||||
factory LocalAccount.fromJson(dynamic json) =>
|
||||
_$LocalAccountFromJson(json as Map<String, dynamic>);
|
||||
}
|
47
lib/init.dart
Normal file
47
lib/init.dart
Normal file
@ -0,0 +1,47 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'local_account_manager/local_account_manager.dart';
|
||||
import 'processor.dart';
|
||||
import 'tools/tools.dart';
|
||||
import 'veilid_support/veilid_support.dart';
|
||||
|
||||
const String appName = 'VeilidChat';
|
||||
|
||||
final Completer<Veilid> eventualVeilid = Completer<Veilid>();
|
||||
final Processor processor = Processor();
|
||||
|
||||
final Completer<void> eventualInitialized = Completer<void>();
|
||||
|
||||
// Initialize Veilid
|
||||
Future<void> initializeVeilid() async {
|
||||
// Ensure this runs only once
|
||||
if (eventualVeilid.isCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Init Veilid
|
||||
Veilid.instance.initializeVeilidCore(getDefaultVeilidPlatformConfig(appName));
|
||||
|
||||
// Veilid logging
|
||||
initVeilidLog();
|
||||
|
||||
// Startup Veilid
|
||||
await processor.startup();
|
||||
|
||||
// Share the initialized veilid instance to the rest of the app
|
||||
eventualVeilid.complete(Veilid.instance);
|
||||
}
|
||||
|
||||
// Initialize repositories
|
||||
Future<void> initializeRepositories() async {
|
||||
await AccountRepository.instance;
|
||||
}
|
||||
|
||||
Future<void> initializeVeilidChat() async {
|
||||
log.info("Initializing Veilid");
|
||||
await initializeVeilid();
|
||||
log.info("Initializing Repositories");
|
||||
await initializeRepositories();
|
||||
|
||||
eventualInitialized.complete();
|
||||
}
|
@ -0,0 +1,285 @@
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
|
||||
import '../../../proto/proto.dart' as proto;
|
||||
import '../../tools/tools.dart';
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
import 'active_logins.dart';
|
||||
import 'encryption_key_type.dart';
|
||||
import 'local_account.dart';
|
||||
import 'user_login.dart';
|
||||
|
||||
export 'active_logins.dart';
|
||||
export 'encryption_key_type.dart';
|
||||
export 'local_account.dart';
|
||||
export 'user_login.dart';
|
||||
|
||||
const String veilidChatAccountKey = 'com.veilid.veilidchat';
|
||||
|
||||
enum AccountRepositoryChange { localAccounts, userLogins, activeUserLogin }
|
||||
|
||||
class AccountRepository {
|
||||
AccountRepository._()
|
||||
: _localAccounts = _initLocalAccounts(),
|
||||
_activeLogins = _initActiveLogins();
|
||||
|
||||
static TableDBValue<IList<LocalAccount>> _initLocalAccounts() => TableDBValue(
|
||||
tableName: 'local_account_manager',
|
||||
tableKeyName: 'local_accounts',
|
||||
valueFromJson: (obj) => obj != null
|
||||
? IList<LocalAccount>.fromJson(
|
||||
obj, genericFromJson(LocalAccount.fromJson))
|
||||
: IList<LocalAccount>(),
|
||||
valueToJson: (val) => val.toJson((la) => la.toJson()));
|
||||
|
||||
static TableDBValue<ActiveLogins> _initActiveLogins() => TableDBValue(
|
||||
tableName: 'local_account_manager',
|
||||
tableKeyName: 'active_logins',
|
||||
valueFromJson: (obj) => obj != null
|
||||
? ActiveLogins.fromJson(obj as Map<String, dynamic>)
|
||||
: ActiveLogins.empty(),
|
||||
valueToJson: (val) => val.toJson());
|
||||
|
||||
final TableDBValue<IList<LocalAccount>> _localAccounts;
|
||||
final TableDBValue<ActiveLogins> _activeLogins;
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
/// Singleton initialization
|
||||
|
||||
static AccountRepository? _instance;
|
||||
static Future<AccountRepository> get instance async {
|
||||
if (_instance == null) {
|
||||
final accountRepository = AccountRepository._();
|
||||
await accountRepository.init();
|
||||
_instance = accountRepository;
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
Future<void> init() async {
|
||||
await _localAccounts.load();
|
||||
await _activeLogins.load();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
/// Streams
|
||||
|
||||
Stream<AccountRepositoryChange> changes() async* {}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
/// Selectors
|
||||
IList<LocalAccount> getLocalAccounts() => _localAccounts.requireValue;
|
||||
IList<UserLogin> getUserLogins() => _activeLogins.requireValue.userLogins;
|
||||
TypedKey? getActiveUserLogin() => _activeLogins.requireValue.activeUserLogin;
|
||||
|
||||
LocalAccount? fetchLocalAccount({required TypedKey accountMasterRecordKey}) {
|
||||
final localAccounts = _localAccounts.requireValue;
|
||||
final idx = localAccounts.indexWhere(
|
||||
(e) => e.identityMaster.masterRecordKey == accountMasterRecordKey);
|
||||
if (idx == -1) {
|
||||
return null;
|
||||
}
|
||||
return localAccounts[idx];
|
||||
}
|
||||
|
||||
UserLogin? fetchLogin({required TypedKey accountMasterRecordKey}) {
|
||||
final userLogins = _activeLogins.requireValue.userLogins;
|
||||
final idx = userLogins
|
||||
.indexWhere((e) => e.accountMasterRecordKey == accountMasterRecordKey);
|
||||
if (idx == -1) {
|
||||
return null;
|
||||
}
|
||||
return userLogins[idx];
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
/// Mutators
|
||||
|
||||
/// Reorder accounts
|
||||
Future<void> reorderAccount(int oldIndex, int newIndex) async {
|
||||
final localAccounts = await _localAccounts.get();
|
||||
final removedItem = Output<LocalAccount>();
|
||||
final updated = localAccounts
|
||||
.removeAt(oldIndex, removedItem)
|
||||
.insert(newIndex, removedItem.value!);
|
||||
await _localAccounts.set(updated);
|
||||
}
|
||||
|
||||
/// Creates a new Account associated with master identity
|
||||
/// Adds a logged-out LocalAccount to track its existence on this device
|
||||
Future<LocalAccount> newLocalAccount(
|
||||
{required IdentityMaster identityMaster,
|
||||
required SecretKey identitySecret,
|
||||
required String name,
|
||||
required String pronouns,
|
||||
EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
|
||||
String encryptionKey = ''}) async {
|
||||
final localAccounts = await _localAccounts.get();
|
||||
|
||||
// Add account with profile to DHT
|
||||
await identityMaster.addAccountToIdentity(
|
||||
identitySecret: identitySecret,
|
||||
accountKey: veilidChatAccountKey,
|
||||
createAccountCallback: (parent) async {
|
||||
// Make empty contact list
|
||||
final contactList = await (await DHTShortArray.create(parent: parent))
|
||||
.scope((r) async => r.record.ownedDHTRecordPointer);
|
||||
|
||||
// Make empty contact invitation record list
|
||||
final contactInvitationRecords =
|
||||
await (await DHTShortArray.create(parent: parent))
|
||||
.scope((r) async => r.record.ownedDHTRecordPointer);
|
||||
|
||||
// Make empty chat record list
|
||||
final chatRecords = await (await DHTShortArray.create(parent: parent))
|
||||
.scope((r) async => r.record.ownedDHTRecordPointer);
|
||||
|
||||
// Make account object
|
||||
final account = proto.Account()
|
||||
..profile = (proto.Profile()
|
||||
..name = name
|
||||
..pronouns = pronouns)
|
||||
..contactList = contactList.toProto()
|
||||
..contactInvitationRecords = contactInvitationRecords.toProto()
|
||||
..chatList = chatRecords.toProto();
|
||||
return account;
|
||||
});
|
||||
|
||||
// Encrypt identitySecret with key
|
||||
final identitySecretBytes = await encryptSecretToBytes(
|
||||
secret: identitySecret,
|
||||
cryptoKind: identityMaster.identityRecordKey.kind,
|
||||
encryptionKey: encryptionKey,
|
||||
encryptionKeyType: encryptionKeyType);
|
||||
|
||||
// Create local account object
|
||||
// Does not contain the account key or its secret
|
||||
// as that is not to be persisted, and only pulled from the identity key
|
||||
// and optionally decrypted with the unlock password
|
||||
final localAccount = LocalAccount(
|
||||
identityMaster: identityMaster,
|
||||
identitySecretBytes: identitySecretBytes,
|
||||
encryptionKeyType: encryptionKeyType,
|
||||
biometricsEnabled: false,
|
||||
hiddenAccount: false,
|
||||
name: name,
|
||||
);
|
||||
|
||||
// Add local account object to internal store
|
||||
final newLocalAccounts = localAccounts.add(localAccount);
|
||||
|
||||
await _localAccounts.set(newLocalAccounts);
|
||||
|
||||
// Return local account object
|
||||
return localAccount;
|
||||
}
|
||||
|
||||
/// Remove an account and wipe the messages for this account from this device
|
||||
Future<bool> deleteLocalAccount(TypedKey accountMasterRecordKey) async {
|
||||
await logout(accountMasterRecordKey);
|
||||
|
||||
final localAccounts = await _localAccounts.get();
|
||||
final newLocalAccounts = localAccounts.removeWhere(
|
||||
(la) => la.identityMaster.masterRecordKey == accountMasterRecordKey);
|
||||
|
||||
await _localAccounts.set(newLocalAccounts);
|
||||
|
||||
// TO DO: wipe messages
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Import an account from another VeilidChat instance
|
||||
|
||||
/// Recover an account with the master identity secret
|
||||
|
||||
/// Delete an account from all devices
|
||||
|
||||
Future<void> switchToAccount(TypedKey? accountMasterRecordKey) async {
|
||||
final activeLogins = await _activeLogins.get();
|
||||
|
||||
if (accountMasterRecordKey != null) {
|
||||
// Assert the specified record key can be found, will throw if not
|
||||
final _ = activeLogins.userLogins.firstWhere(
|
||||
(ul) => ul.accountMasterRecordKey == accountMasterRecordKey);
|
||||
}
|
||||
final newActiveLogins =
|
||||
activeLogins.copyWith(activeUserLogin: accountMasterRecordKey);
|
||||
await _activeLogins.set(newActiveLogins);
|
||||
}
|
||||
|
||||
Future<bool> _decryptedLogin(
|
||||
IdentityMaster identityMaster, SecretKey identitySecret) async {
|
||||
final cs = await Veilid.instance
|
||||
.getCryptoSystem(identityMaster.identityRecordKey.kind);
|
||||
final keyOk = await cs.validateKeyPair(
|
||||
identityMaster.identityPublicKey, identitySecret);
|
||||
if (!keyOk) {
|
||||
throw Exception('Identity is corrupted');
|
||||
}
|
||||
|
||||
// Read the identity key to get the account keys
|
||||
final accountRecordInfo = await identityMaster.readAccountFromIdentity(
|
||||
identitySecret: identitySecret, accountKey: veilidChatAccountKey);
|
||||
|
||||
// Add to user logins and select it
|
||||
final activeLogins = await _activeLogins.get();
|
||||
final now = Veilid.instance.now();
|
||||
final newActiveLogins = activeLogins.copyWith(
|
||||
userLogins: activeLogins.userLogins.replaceFirstWhere(
|
||||
(ul) => ul.accountMasterRecordKey == identityMaster.masterRecordKey,
|
||||
(ul) => ul != null
|
||||
? ul.copyWith(lastActive: now)
|
||||
: UserLogin(
|
||||
accountMasterRecordKey: identityMaster.masterRecordKey,
|
||||
identitySecret:
|
||||
TypedSecret(kind: cs.kind(), value: identitySecret),
|
||||
accountRecordInfo: accountRecordInfo,
|
||||
lastActive: now),
|
||||
addIfNotFound: true),
|
||||
activeUserLogin: identityMaster.masterRecordKey);
|
||||
await _activeLogins.set(newActiveLogins);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> login(TypedKey accountMasterRecordKey,
|
||||
EncryptionKeyType encryptionKeyType, String encryptionKey) async {
|
||||
final localAccounts = await _localAccounts.get();
|
||||
|
||||
// Get account, throws if not found
|
||||
final localAccount = localAccounts.firstWhere(
|
||||
(la) => la.identityMaster.masterRecordKey == accountMasterRecordKey);
|
||||
|
||||
// Log in with this local account
|
||||
|
||||
// Derive key from password
|
||||
if (localAccount.encryptionKeyType != encryptionKeyType) {
|
||||
throw Exception('Wrong authentication type');
|
||||
}
|
||||
|
||||
final identitySecret = await decryptSecretFromBytes(
|
||||
secretBytes: localAccount.identitySecretBytes,
|
||||
cryptoKind: localAccount.identityMaster.identityRecordKey.kind,
|
||||
encryptionKeyType: localAccount.encryptionKeyType,
|
||||
encryptionKey: encryptionKey,
|
||||
);
|
||||
|
||||
// Validate this secret with the identity public key and log in
|
||||
return _decryptedLogin(localAccount.identityMaster, identitySecret);
|
||||
}
|
||||
|
||||
Future<void> logout(TypedKey? accountMasterRecordKey) async {
|
||||
final activeLogins = await _activeLogins.get();
|
||||
final logoutUser = accountMasterRecordKey ?? activeLogins.activeUserLogin;
|
||||
if (logoutUser == null) {
|
||||
return;
|
||||
}
|
||||
final newActiveLogins = activeLogins.copyWith(
|
||||
activeUserLogin: activeLogins.activeUserLogin == logoutUser
|
||||
? null
|
||||
: activeLogins.activeUserLogin,
|
||||
userLogins: activeLogins.userLogins
|
||||
.removeWhere((ul) => ul.accountMasterRecordKey == logoutUser));
|
||||
await _activeLogins.set(newActiveLogins);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
// Represents a set of user logins and the currently selected account
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
import 'user_login.dart';
|
||||
|
||||
part 'active_logins.g.dart';
|
||||
part 'active_logins.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class ActiveLogins with _$ActiveLogins {
|
||||
const factory ActiveLogins({
|
||||
// The list of current logged in accounts
|
||||
required IList<UserLogin> userLogins,
|
||||
// The current selected account indexed by master record key
|
||||
TypedKey? activeUserLogin,
|
||||
}) = _ActiveLogins;
|
||||
|
||||
factory ActiveLogins.empty() =>
|
||||
const ActiveLogins(userLogins: IListConst([]));
|
||||
|
||||
factory ActiveLogins.fromJson(dynamic json) =>
|
||||
_ActiveLogins.fromJson(json as Map<String, dynamic>);
|
||||
}
|
@ -0,0 +1,181 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'active_logins.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
|
||||
ActiveLogins _$ActiveLoginsFromJson(Map<String, dynamic> json) {
|
||||
return _ActiveLogins.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ActiveLogins {
|
||||
// The list of current logged in accounts
|
||||
IList<UserLogin> get userLogins =>
|
||||
throw _privateConstructorUsedError; // The current selected account indexed by master record key
|
||||
Typed<FixedEncodedString43>? get activeUserLogin =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$ActiveLoginsCopyWith<ActiveLogins> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ActiveLoginsCopyWith<$Res> {
|
||||
factory $ActiveLoginsCopyWith(
|
||||
ActiveLogins value, $Res Function(ActiveLogins) then) =
|
||||
_$ActiveLoginsCopyWithImpl<$Res, ActiveLogins>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{IList<UserLogin> userLogins,
|
||||
Typed<FixedEncodedString43>? activeUserLogin});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ActiveLoginsCopyWithImpl<$Res, $Val extends ActiveLogins>
|
||||
implements $ActiveLoginsCopyWith<$Res> {
|
||||
_$ActiveLoginsCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? userLogins = null,
|
||||
Object? activeUserLogin = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
userLogins: null == userLogins
|
||||
? _value.userLogins
|
||||
: userLogins // ignore: cast_nullable_to_non_nullable
|
||||
as IList<UserLogin>,
|
||||
activeUserLogin: freezed == activeUserLogin
|
||||
? _value.activeUserLogin
|
||||
: activeUserLogin // ignore: cast_nullable_to_non_nullable
|
||||
as Typed<FixedEncodedString43>?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ActiveLoginsImplCopyWith<$Res>
|
||||
implements $ActiveLoginsCopyWith<$Res> {
|
||||
factory _$$ActiveLoginsImplCopyWith(
|
||||
_$ActiveLoginsImpl value, $Res Function(_$ActiveLoginsImpl) then) =
|
||||
__$$ActiveLoginsImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{IList<UserLogin> userLogins,
|
||||
Typed<FixedEncodedString43>? activeUserLogin});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ActiveLoginsImplCopyWithImpl<$Res>
|
||||
extends _$ActiveLoginsCopyWithImpl<$Res, _$ActiveLoginsImpl>
|
||||
implements _$$ActiveLoginsImplCopyWith<$Res> {
|
||||
__$$ActiveLoginsImplCopyWithImpl(
|
||||
_$ActiveLoginsImpl _value, $Res Function(_$ActiveLoginsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? userLogins = null,
|
||||
Object? activeUserLogin = freezed,
|
||||
}) {
|
||||
return _then(_$ActiveLoginsImpl(
|
||||
userLogins: null == userLogins
|
||||
? _value.userLogins
|
||||
: userLogins // ignore: cast_nullable_to_non_nullable
|
||||
as IList<UserLogin>,
|
||||
activeUserLogin: freezed == activeUserLogin
|
||||
? _value.activeUserLogin
|
||||
: activeUserLogin // ignore: cast_nullable_to_non_nullable
|
||||
as Typed<FixedEncodedString43>?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ActiveLoginsImpl implements _ActiveLogins {
|
||||
const _$ActiveLoginsImpl({required this.userLogins, this.activeUserLogin});
|
||||
|
||||
factory _$ActiveLoginsImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ActiveLoginsImplFromJson(json);
|
||||
|
||||
// The list of current logged in accounts
|
||||
@override
|
||||
final IList<UserLogin> userLogins;
|
||||
// The current selected account indexed by master record key
|
||||
@override
|
||||
final Typed<FixedEncodedString43>? activeUserLogin;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ActiveLogins(userLogins: $userLogins, activeUserLogin: $activeUserLogin)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ActiveLoginsImpl &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.userLogins, userLogins) &&
|
||||
(identical(other.activeUserLogin, activeUserLogin) ||
|
||||
other.activeUserLogin == activeUserLogin));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,
|
||||
const DeepCollectionEquality().hash(userLogins), activeUserLogin);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ActiveLoginsImplCopyWith<_$ActiveLoginsImpl> get copyWith =>
|
||||
__$$ActiveLoginsImplCopyWithImpl<_$ActiveLoginsImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ActiveLoginsImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ActiveLogins implements ActiveLogins {
|
||||
const factory _ActiveLogins(
|
||||
{required final IList<UserLogin> userLogins,
|
||||
final Typed<FixedEncodedString43>? activeUserLogin}) = _$ActiveLoginsImpl;
|
||||
|
||||
factory _ActiveLogins.fromJson(Map<String, dynamic> json) =
|
||||
_$ActiveLoginsImpl.fromJson;
|
||||
|
||||
@override // The list of current logged in accounts
|
||||
IList<UserLogin> get userLogins;
|
||||
@override // The current selected account indexed by master record key
|
||||
Typed<FixedEncodedString43>? get activeUserLogin;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ActiveLoginsImplCopyWith<_$ActiveLoginsImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'active_logins.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$ActiveLoginsImpl _$$ActiveLoginsImplFromJson(Map<String, dynamic> json) =>
|
||||
_$ActiveLoginsImpl(
|
||||
userLogins: IList<UserLogin>.fromJson(
|
||||
json['user_logins'], (value) => UserLogin.fromJson(value)),
|
||||
activeUserLogin: json['active_user_login'] == null
|
||||
? null
|
||||
: Typed<FixedEncodedString43>.fromJson(json['active_user_login']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ActiveLoginsImplToJson(_$ActiveLoginsImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'user_logins': instance.userLogins.toJson(
|
||||
(value) => value.toJson(),
|
||||
),
|
||||
'active_user_login': instance.activeUserLogin?.toJson(),
|
||||
};
|
@ -0,0 +1,40 @@
|
||||
// Local account identitySecretKey is potentially encrypted with a key
|
||||
// using the following mechanisms
|
||||
// * None : no key, bytes are unencrypted
|
||||
// * Pin : Code is a numeric pin (4-256 numeric digits) hashed with Argon2
|
||||
// * Password: Code is a UTF-8 string that is hashed with Argon2
|
||||
|
||||
import 'package:change_case/change_case.dart';
|
||||
|
||||
import '../../../proto/proto.dart' as proto;
|
||||
|
||||
enum EncryptionKeyType {
|
||||
none,
|
||||
pin,
|
||||
password;
|
||||
|
||||
factory EncryptionKeyType.fromJson(dynamic j) =>
|
||||
EncryptionKeyType.values.byName((j as String).toCamelCase());
|
||||
|
||||
factory EncryptionKeyType.fromProto(proto.EncryptionKeyType p) {
|
||||
// ignore: exhaustive_cases
|
||||
switch (p) {
|
||||
case proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_NONE:
|
||||
return EncryptionKeyType.none;
|
||||
case proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_PIN:
|
||||
return EncryptionKeyType.pin;
|
||||
case proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_PASSWORD:
|
||||
return EncryptionKeyType.password;
|
||||
}
|
||||
throw StateError('unknown EncryptionKeyType enum value');
|
||||
}
|
||||
String toJson() => name.toPascalCase();
|
||||
proto.EncryptionKeyType toProto() => switch (this) {
|
||||
EncryptionKeyType.none =>
|
||||
proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_NONE,
|
||||
EncryptionKeyType.pin =>
|
||||
proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_PIN,
|
||||
EncryptionKeyType.password =>
|
||||
proto.EncryptionKeyType.ENCRYPTION_KEY_TYPE_PASSWORD,
|
||||
};
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
import 'encryption_key_type.dart';
|
||||
|
||||
part 'local_account.g.dart';
|
||||
part 'local_account.freezed.dart';
|
||||
|
||||
// Local Accounts are stored in a table locally and not backed by a DHT key
|
||||
// and represents the accounts that have been added/imported
|
||||
// on the current device.
|
||||
// Stores a copy of the IdentityMaster associated with the account
|
||||
// and the identitySecretKey optionally encrypted by an unlock code
|
||||
// This is the root of the account information tree for VeilidChat
|
||||
//
|
||||
@freezed
|
||||
class LocalAccount with _$LocalAccount {
|
||||
const factory LocalAccount({
|
||||
// The master key record for the account, containing the identityPublicKey
|
||||
required IdentityMaster identityMaster,
|
||||
// The encrypted identity secret that goes with
|
||||
// the identityPublicKey with appended salt
|
||||
@Uint8ListJsonConverter() required Uint8List identitySecretBytes,
|
||||
// The kind of encryption input used on the account
|
||||
required EncryptionKeyType encryptionKeyType,
|
||||
// If account is not hidden, password can be retrieved via
|
||||
required bool biometricsEnabled,
|
||||
// Keep account hidden unless account password is entered
|
||||
// (tries all hidden accounts with auth method (no biometrics))
|
||||
required bool hiddenAccount,
|
||||
// Display name for account until it is unlocked
|
||||
required String name,
|
||||
}) = _LocalAccount;
|
||||
|
||||
factory LocalAccount.fromJson(dynamic json) =>
|
||||
_$LocalAccountFromJson(json as Map<String, dynamic>);
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
|
||||
part 'user_login.freezed.dart';
|
||||
part 'user_login.g.dart';
|
||||
@ -26,21 +25,3 @@ class UserLogin with _$UserLogin {
|
||||
factory UserLogin.fromJson(dynamic json) =>
|
||||
_$UserLoginFromJson(json as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
// Represents a set of user logins
|
||||
// and the currently selected account
|
||||
@freezed
|
||||
class ActiveLogins with _$ActiveLogins {
|
||||
const factory ActiveLogins({
|
||||
// The list of current logged in accounts
|
||||
required IList<UserLogin> userLogins,
|
||||
// The current selected account indexed by master record key
|
||||
TypedKey? activeUserLogin,
|
||||
}) = _ActiveLogins;
|
||||
|
||||
factory ActiveLogins.empty() =>
|
||||
const ActiveLogins(userLogins: IListConst([]));
|
||||
|
||||
factory ActiveLogins.fromJson(dynamic json) =>
|
||||
_$ActiveLoginsFromJson(json as Map<String, dynamic>);
|
||||
}
|
@ -238,169 +238,3 @@ abstract class _UserLogin implements UserLogin {
|
||||
_$$UserLoginImplCopyWith<_$UserLoginImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
ActiveLogins _$ActiveLoginsFromJson(Map<String, dynamic> json) {
|
||||
return _ActiveLogins.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ActiveLogins {
|
||||
// The list of current logged in accounts
|
||||
IList<UserLogin> get userLogins =>
|
||||
throw _privateConstructorUsedError; // The current selected account indexed by master record key
|
||||
Typed<FixedEncodedString43>? get activeUserLogin =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$ActiveLoginsCopyWith<ActiveLogins> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ActiveLoginsCopyWith<$Res> {
|
||||
factory $ActiveLoginsCopyWith(
|
||||
ActiveLogins value, $Res Function(ActiveLogins) then) =
|
||||
_$ActiveLoginsCopyWithImpl<$Res, ActiveLogins>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{IList<UserLogin> userLogins,
|
||||
Typed<FixedEncodedString43>? activeUserLogin});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ActiveLoginsCopyWithImpl<$Res, $Val extends ActiveLogins>
|
||||
implements $ActiveLoginsCopyWith<$Res> {
|
||||
_$ActiveLoginsCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? userLogins = null,
|
||||
Object? activeUserLogin = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
userLogins: null == userLogins
|
||||
? _value.userLogins
|
||||
: userLogins // ignore: cast_nullable_to_non_nullable
|
||||
as IList<UserLogin>,
|
||||
activeUserLogin: freezed == activeUserLogin
|
||||
? _value.activeUserLogin
|
||||
: activeUserLogin // ignore: cast_nullable_to_non_nullable
|
||||
as Typed<FixedEncodedString43>?,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ActiveLoginsImplCopyWith<$Res>
|
||||
implements $ActiveLoginsCopyWith<$Res> {
|
||||
factory _$$ActiveLoginsImplCopyWith(
|
||||
_$ActiveLoginsImpl value, $Res Function(_$ActiveLoginsImpl) then) =
|
||||
__$$ActiveLoginsImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{IList<UserLogin> userLogins,
|
||||
Typed<FixedEncodedString43>? activeUserLogin});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ActiveLoginsImplCopyWithImpl<$Res>
|
||||
extends _$ActiveLoginsCopyWithImpl<$Res, _$ActiveLoginsImpl>
|
||||
implements _$$ActiveLoginsImplCopyWith<$Res> {
|
||||
__$$ActiveLoginsImplCopyWithImpl(
|
||||
_$ActiveLoginsImpl _value, $Res Function(_$ActiveLoginsImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? userLogins = null,
|
||||
Object? activeUserLogin = freezed,
|
||||
}) {
|
||||
return _then(_$ActiveLoginsImpl(
|
||||
userLogins: null == userLogins
|
||||
? _value.userLogins
|
||||
: userLogins // ignore: cast_nullable_to_non_nullable
|
||||
as IList<UserLogin>,
|
||||
activeUserLogin: freezed == activeUserLogin
|
||||
? _value.activeUserLogin
|
||||
: activeUserLogin // ignore: cast_nullable_to_non_nullable
|
||||
as Typed<FixedEncodedString43>?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ActiveLoginsImpl implements _ActiveLogins {
|
||||
const _$ActiveLoginsImpl({required this.userLogins, this.activeUserLogin});
|
||||
|
||||
factory _$ActiveLoginsImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ActiveLoginsImplFromJson(json);
|
||||
|
||||
// The list of current logged in accounts
|
||||
@override
|
||||
final IList<UserLogin> userLogins;
|
||||
// The current selected account indexed by master record key
|
||||
@override
|
||||
final Typed<FixedEncodedString43>? activeUserLogin;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ActiveLogins(userLogins: $userLogins, activeUserLogin: $activeUserLogin)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ActiveLoginsImpl &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.userLogins, userLogins) &&
|
||||
(identical(other.activeUserLogin, activeUserLogin) ||
|
||||
other.activeUserLogin == activeUserLogin));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,
|
||||
const DeepCollectionEquality().hash(userLogins), activeUserLogin);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ActiveLoginsImplCopyWith<_$ActiveLoginsImpl> get copyWith =>
|
||||
__$$ActiveLoginsImplCopyWithImpl<_$ActiveLoginsImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ActiveLoginsImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ActiveLogins implements ActiveLogins {
|
||||
const factory _ActiveLogins(
|
||||
{required final IList<UserLogin> userLogins,
|
||||
final Typed<FixedEncodedString43>? activeUserLogin}) = _$ActiveLoginsImpl;
|
||||
|
||||
factory _ActiveLogins.fromJson(Map<String, dynamic> json) =
|
||||
_$ActiveLoginsImpl.fromJson;
|
||||
|
||||
@override // The list of current logged in accounts
|
||||
IList<UserLogin> get userLogins;
|
||||
@override // The current selected account indexed by master record key
|
||||
Typed<FixedEncodedString43>? get activeUserLogin;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ActiveLoginsImplCopyWith<_$ActiveLoginsImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
@ -24,20 +24,3 @@ Map<String, dynamic> _$$UserLoginImplToJson(_$UserLoginImpl instance) =>
|
||||
'account_record_info': instance.accountRecordInfo.toJson(),
|
||||
'last_active': instance.lastActive.toJson(),
|
||||
};
|
||||
|
||||
_$ActiveLoginsImpl _$$ActiveLoginsImplFromJson(Map<String, dynamic> json) =>
|
||||
_$ActiveLoginsImpl(
|
||||
userLogins: IList<UserLogin>.fromJson(
|
||||
json['user_logins'], (value) => UserLogin.fromJson(value)),
|
||||
activeUserLogin: json['active_user_login'] == null
|
||||
? null
|
||||
: Typed<FixedEncodedString43>.fromJson(json['active_user_login']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ActiveLoginsImplToJson(_$ActiveLoginsImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'user_logins': instance.userLogins.toJson(
|
||||
(value) => value.toJson(),
|
||||
),
|
||||
'active_user_login': instance.activeUserLogin?.toJson(),
|
||||
};
|
@ -0,0 +1,42 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
import '../account_repository/account_repository.dart';
|
||||
|
||||
part 'active_user_login_state.dart';
|
||||
|
||||
class ActiveUserLoginCubit extends Cubit<ActiveUserLoginState> {
|
||||
ActiveUserLoginCubit({required AccountRepository accountRepository})
|
||||
: _accountRepository = accountRepository,
|
||||
super(null) {
|
||||
// Subscribe to streams
|
||||
_initAccountRepositorySubscription();
|
||||
}
|
||||
|
||||
void _initAccountRepositorySubscription() {
|
||||
_accountRepositorySubscription =
|
||||
_accountRepository.changes().listen((change) {
|
||||
switch (change) {
|
||||
case AccountRepositoryChange.activeUserLogin:
|
||||
emit(_accountRepository.getActiveUserLogin());
|
||||
break;
|
||||
// Ignore these
|
||||
case AccountRepositoryChange.localAccounts:
|
||||
case AccountRepositoryChange.userLogins:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await super.close();
|
||||
await _accountRepositorySubscription.cancel();
|
||||
}
|
||||
|
||||
final AccountRepository _accountRepository;
|
||||
late final StreamSubscription<AccountRepositoryChange>
|
||||
_accountRepositorySubscription;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
part of 'active_user_login_cubit.dart';
|
||||
|
||||
typedef ActiveUserLoginState = TypedKey?;
|
3
lib/local_account_manager/local_account_manager.dart
Normal file
3
lib/local_account_manager/local_account_manager.dart
Normal file
@ -0,0 +1,3 @@
|
||||
export 'account_repository/account_repository.dart';
|
||||
export 'local_accounts_cubit/local_accounts_cubit.dart';
|
||||
export 'user_logins_cubit/user_logins_cubit.dart';
|
@ -0,0 +1,42 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
|
||||
import '../account_repository/account_repository.dart';
|
||||
|
||||
part 'local_accounts_state.dart';
|
||||
|
||||
class LocalAccountsCubit extends Cubit<LocalAccountsState> {
|
||||
LocalAccountsCubit({required AccountRepository accountRepository})
|
||||
: _accountRepository = accountRepository,
|
||||
super(LocalAccountsState()) {
|
||||
// Subscribe to streams
|
||||
_initAccountRepositorySubscription();
|
||||
}
|
||||
|
||||
void _initAccountRepositorySubscription() {
|
||||
_accountRepositorySubscription =
|
||||
_accountRepository.changes().listen((change) {
|
||||
switch (change) {
|
||||
case AccountRepositoryChange.localAccounts:
|
||||
emit(_accountRepository.getLocalAccounts());
|
||||
break;
|
||||
// Ignore these
|
||||
case AccountRepositoryChange.userLogins:
|
||||
case AccountRepositoryChange.activeUserLogin:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await super.close();
|
||||
await _accountRepositorySubscription.cancel();
|
||||
}
|
||||
|
||||
final AccountRepository _accountRepository;
|
||||
late final StreamSubscription<AccountRepositoryChange>
|
||||
_accountRepositorySubscription;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
part of 'local_accounts_cubit.dart';
|
||||
|
||||
typedef LocalAccountsState = IList<LocalAccount>;
|
@ -0,0 +1,42 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
|
||||
import '../account_repository/account_repository.dart';
|
||||
|
||||
part 'user_logins_state.dart';
|
||||
|
||||
class UserLoginsCubit extends Cubit<UserLoginsState> {
|
||||
UserLoginsCubit({required AccountRepository accountRepository})
|
||||
: _accountRepository = accountRepository,
|
||||
super(UserLoginsState()) {
|
||||
// Subscribe to streams
|
||||
_initAccountRepositorySubscription();
|
||||
}
|
||||
|
||||
void _initAccountRepositorySubscription() {
|
||||
_accountRepositorySubscription =
|
||||
_accountRepository.changes().listen((change) {
|
||||
switch (change) {
|
||||
case AccountRepositoryChange.userLogins:
|
||||
emit(_accountRepository.getUserLogins());
|
||||
break;
|
||||
// Ignore these
|
||||
case AccountRepositoryChange.localAccounts:
|
||||
case AccountRepositoryChange.activeUserLogin:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await super.close();
|
||||
await _accountRepositorySubscription.cancel();
|
||||
}
|
||||
|
||||
final AccountRepository _accountRepository;
|
||||
late final StreamSubscription<AccountRepositoryChange>
|
||||
_accountRepositorySubscription;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
part of 'user_logins_cubit.dart';
|
||||
|
||||
typedef UserLoginsState = IList<UserLogin>;
|
@ -4,14 +4,16 @@ import 'dart:io';
|
||||
import 'package:ansicolor/ansicolor.dart';
|
||||
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 'package:intl/date_symbol_data_local.dart';
|
||||
|
||||
import 'app.dart';
|
||||
import 'providers/window_control.dart';
|
||||
import 'old_to_refactor/providers/window_control.dart';
|
||||
import 'theme/theme.dart';
|
||||
import 'tools/tools.dart';
|
||||
import 'veilid_init.dart';
|
||||
import 'init.dart';
|
||||
|
||||
const String appName = "VeilidChat";
|
||||
|
||||
void main() async {
|
||||
// Disable all debugprints in release mode
|
||||
@ -34,8 +36,8 @@ void main() async {
|
||||
|
||||
// Prepare theme
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final themeService = await ThemeService.instance;
|
||||
final initTheme = themeService.initial;
|
||||
final themeRepository = await ThemeRepository.instance;
|
||||
final themeData = themeRepository.themeData();
|
||||
|
||||
// Manage window on desktop platforms
|
||||
await WindowControl.initialize();
|
||||
@ -50,9 +52,7 @@ void main() async {
|
||||
|
||||
// Run the app
|
||||
// Hot reloads will only restart this part, not Veilid
|
||||
runApp(ProviderScope(
|
||||
observers: const [StateLogger()],
|
||||
child: LocalizedApp(delegate, VeilidChatApp(theme: initTheme))));
|
||||
runApp(LocalizedApp(delegate, VeilidChatApp(themeData: themeData)));
|
||||
}, (error, stackTrace) {
|
||||
log.error('Dart Runtime: {$error}\n{$stackTrace}');
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ import 'package:flutter_translate/flutter_translate.dart';
|
||||
import '../proto/proto.dart' as proto;
|
||||
import '../providers/account.dart';
|
||||
import '../providers/chat.dart';
|
||||
import '../tools/theme_service.dart';
|
||||
import '../theme/theme.dart';
|
||||
|
||||
class ChatSingleContactItemWidget extends ConsumerWidget {
|
||||
const ChatSingleContactItemWidget({required this.contact, super.key});
|
@ -9,7 +9,7 @@ import '../pages/main_pager/main_pager.dart';
|
||||
import '../providers/account.dart';
|
||||
import '../providers/chat.dart';
|
||||
import '../providers/contact.dart';
|
||||
import '../tools/theme_service.dart';
|
||||
import '../theme/theme.dart';
|
||||
|
||||
class ContactItemWidget extends ConsumerWidget {
|
||||
const ContactItemWidget({required this.contact, super.key});
|
@ -4,19 +4,6 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'preferences.freezed.dart';
|
||||
part 'preferences.g.dart';
|
||||
|
||||
// Theme supports light and dark mode, optionally selected by the
|
||||
// operating system
|
||||
enum BrightnessPreference {
|
||||
system,
|
||||
light,
|
||||
dark;
|
||||
|
||||
factory BrightnessPreference.fromJson(dynamic j) =>
|
||||
BrightnessPreference.values.byName((j as String).toCamelCase());
|
||||
|
||||
String toJson() => name.toPascalCase();
|
||||
}
|
||||
|
||||
// Lock preference changes how frequently the messenger locks its
|
||||
// interface and requires the identitySecretKey to be entered (pin/password/etc)
|
||||
@freezed
|
||||
@ -31,28 +18,6 @@ class LockPreference with _$LockPreference {
|
||||
_$LockPreferenceFromJson(json as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
// Theme supports multiple color variants based on 'Radix'
|
||||
enum ColorPreference {
|
||||
// Radix Colors
|
||||
scarlet,
|
||||
babydoll,
|
||||
vapor,
|
||||
gold,
|
||||
garden,
|
||||
forest,
|
||||
arctic,
|
||||
lapis,
|
||||
eggplant,
|
||||
lime,
|
||||
grim,
|
||||
// Accessible Colors
|
||||
contrast;
|
||||
|
||||
factory ColorPreference.fromJson(dynamic j) =>
|
||||
ColorPreference.values.byName((j as String).toCamelCase());
|
||||
String toJson() => name.toPascalCase();
|
||||
}
|
||||
|
||||
// Theme supports multiple translations
|
||||
enum LanguagePreference {
|
||||
englishUS;
|
||||
@ -62,18 +27,6 @@ enum LanguagePreference {
|
||||
String toJson() => name.toPascalCase();
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ThemePreferences with _$ThemePreferences {
|
||||
const factory ThemePreferences({
|
||||
required BrightnessPreference brightnessPreference,
|
||||
required ColorPreference colorPreference,
|
||||
required double displayScale,
|
||||
}) = _ThemePreferences;
|
||||
|
||||
factory ThemePreferences.fromJson(dynamic json) =>
|
||||
_$ThemePreferencesFromJson(json as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
// Preferences are stored in a table locally and globally affect all
|
||||
// accounts imported/added and the app in general
|
||||
@freezed
|
@ -199,192 +199,6 @@ abstract class _LockPreference implements LockPreference {
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
ThemePreferences _$ThemePreferencesFromJson(Map<String, dynamic> json) {
|
||||
return _ThemePreferences.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ThemePreferences {
|
||||
BrightnessPreference get brightnessPreference =>
|
||||
throw _privateConstructorUsedError;
|
||||
ColorPreference get colorPreference => throw _privateConstructorUsedError;
|
||||
double get displayScale => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$ThemePreferencesCopyWith<ThemePreferences> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ThemePreferencesCopyWith<$Res> {
|
||||
factory $ThemePreferencesCopyWith(
|
||||
ThemePreferences value, $Res Function(ThemePreferences) then) =
|
||||
_$ThemePreferencesCopyWithImpl<$Res, ThemePreferences>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{BrightnessPreference brightnessPreference,
|
||||
ColorPreference colorPreference,
|
||||
double displayScale});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ThemePreferencesCopyWithImpl<$Res, $Val extends ThemePreferences>
|
||||
implements $ThemePreferencesCopyWith<$Res> {
|
||||
_$ThemePreferencesCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? brightnessPreference = null,
|
||||
Object? colorPreference = null,
|
||||
Object? displayScale = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
brightnessPreference: null == brightnessPreference
|
||||
? _value.brightnessPreference
|
||||
: brightnessPreference // ignore: cast_nullable_to_non_nullable
|
||||
as BrightnessPreference,
|
||||
colorPreference: null == colorPreference
|
||||
? _value.colorPreference
|
||||
: colorPreference // ignore: cast_nullable_to_non_nullable
|
||||
as ColorPreference,
|
||||
displayScale: null == displayScale
|
||||
? _value.displayScale
|
||||
: displayScale // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ThemePreferencesImplCopyWith<$Res>
|
||||
implements $ThemePreferencesCopyWith<$Res> {
|
||||
factory _$$ThemePreferencesImplCopyWith(_$ThemePreferencesImpl value,
|
||||
$Res Function(_$ThemePreferencesImpl) then) =
|
||||
__$$ThemePreferencesImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{BrightnessPreference brightnessPreference,
|
||||
ColorPreference colorPreference,
|
||||
double displayScale});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ThemePreferencesImplCopyWithImpl<$Res>
|
||||
extends _$ThemePreferencesCopyWithImpl<$Res, _$ThemePreferencesImpl>
|
||||
implements _$$ThemePreferencesImplCopyWith<$Res> {
|
||||
__$$ThemePreferencesImplCopyWithImpl(_$ThemePreferencesImpl _value,
|
||||
$Res Function(_$ThemePreferencesImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? brightnessPreference = null,
|
||||
Object? colorPreference = null,
|
||||
Object? displayScale = null,
|
||||
}) {
|
||||
return _then(_$ThemePreferencesImpl(
|
||||
brightnessPreference: null == brightnessPreference
|
||||
? _value.brightnessPreference
|
||||
: brightnessPreference // ignore: cast_nullable_to_non_nullable
|
||||
as BrightnessPreference,
|
||||
colorPreference: null == colorPreference
|
||||
? _value.colorPreference
|
||||
: colorPreference // ignore: cast_nullable_to_non_nullable
|
||||
as ColorPreference,
|
||||
displayScale: null == displayScale
|
||||
? _value.displayScale
|
||||
: displayScale // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ThemePreferencesImpl implements _ThemePreferences {
|
||||
const _$ThemePreferencesImpl(
|
||||
{required this.brightnessPreference,
|
||||
required this.colorPreference,
|
||||
required this.displayScale});
|
||||
|
||||
factory _$ThemePreferencesImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ThemePreferencesImplFromJson(json);
|
||||
|
||||
@override
|
||||
final BrightnessPreference brightnessPreference;
|
||||
@override
|
||||
final ColorPreference colorPreference;
|
||||
@override
|
||||
final double displayScale;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ThemePreferences(brightnessPreference: $brightnessPreference, colorPreference: $colorPreference, displayScale: $displayScale)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ThemePreferencesImpl &&
|
||||
(identical(other.brightnessPreference, brightnessPreference) ||
|
||||
other.brightnessPreference == brightnessPreference) &&
|
||||
(identical(other.colorPreference, colorPreference) ||
|
||||
other.colorPreference == colorPreference) &&
|
||||
(identical(other.displayScale, displayScale) ||
|
||||
other.displayScale == displayScale));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType, brightnessPreference, colorPreference, displayScale);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ThemePreferencesImplCopyWith<_$ThemePreferencesImpl> get copyWith =>
|
||||
__$$ThemePreferencesImplCopyWithImpl<_$ThemePreferencesImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ThemePreferencesImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ThemePreferences implements ThemePreferences {
|
||||
const factory _ThemePreferences(
|
||||
{required final BrightnessPreference brightnessPreference,
|
||||
required final ColorPreference colorPreference,
|
||||
required final double displayScale}) = _$ThemePreferencesImpl;
|
||||
|
||||
factory _ThemePreferences.fromJson(Map<String, dynamic> json) =
|
||||
_$ThemePreferencesImpl.fromJson;
|
||||
|
||||
@override
|
||||
BrightnessPreference get brightnessPreference;
|
||||
@override
|
||||
ColorPreference get colorPreference;
|
||||
@override
|
||||
double get displayScale;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ThemePreferencesImplCopyWith<_$ThemePreferencesImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
Preferences _$PreferencesFromJson(Map<String, dynamic> json) {
|
||||
return _Preferences.fromJson(json);
|
||||
}
|
||||
@ -412,7 +226,6 @@ abstract class $PreferencesCopyWith<$Res> {
|
||||
LanguagePreference language,
|
||||
LockPreference locking});
|
||||
|
||||
$ThemePreferencesCopyWith<$Res> get themePreferences;
|
||||
$LockPreferenceCopyWith<$Res> get locking;
|
||||
}
|
||||
|
||||
@ -429,12 +242,12 @@ class _$PreferencesCopyWithImpl<$Res, $Val extends Preferences>
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? themePreferences = null,
|
||||
Object? themePreferences = freezed,
|
||||
Object? language = null,
|
||||
Object? locking = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
themePreferences: null == themePreferences
|
||||
themePreferences: freezed == themePreferences
|
||||
? _value.themePreferences
|
||||
: themePreferences // ignore: cast_nullable_to_non_nullable
|
||||
as ThemePreferences,
|
||||
@ -449,14 +262,6 @@ class _$PreferencesCopyWithImpl<$Res, $Val extends Preferences>
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ThemePreferencesCopyWith<$Res> get themePreferences {
|
||||
return $ThemePreferencesCopyWith<$Res>(_value.themePreferences, (value) {
|
||||
return _then(_value.copyWith(themePreferences: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$LockPreferenceCopyWith<$Res> get locking {
|
||||
@ -479,8 +284,6 @@ abstract class _$$PreferencesImplCopyWith<$Res>
|
||||
LanguagePreference language,
|
||||
LockPreference locking});
|
||||
|
||||
@override
|
||||
$ThemePreferencesCopyWith<$Res> get themePreferences;
|
||||
@override
|
||||
$LockPreferenceCopyWith<$Res> get locking;
|
||||
}
|
||||
@ -496,12 +299,12 @@ class __$$PreferencesImplCopyWithImpl<$Res>
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? themePreferences = null,
|
||||
Object? themePreferences = freezed,
|
||||
Object? language = null,
|
||||
Object? locking = null,
|
||||
}) {
|
||||
return _then(_$PreferencesImpl(
|
||||
themePreferences: null == themePreferences
|
||||
themePreferences: freezed == themePreferences
|
||||
? _value.themePreferences
|
||||
: themePreferences // ignore: cast_nullable_to_non_nullable
|
||||
as ThemePreferences,
|
||||
@ -545,8 +348,8 @@ class _$PreferencesImpl implements _Preferences {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$PreferencesImpl &&
|
||||
(identical(other.themePreferences, themePreferences) ||
|
||||
other.themePreferences == themePreferences) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.themePreferences, themePreferences) &&
|
||||
(identical(other.language, language) ||
|
||||
other.language == language) &&
|
||||
(identical(other.locking, locking) || other.locking == locking));
|
||||
@ -554,8 +357,8 @@ class _$PreferencesImpl implements _Preferences {
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, themePreferences, language, locking);
|
||||
int get hashCode => Object.hash(runtimeType,
|
||||
const DeepCollectionEquality().hash(themePreferences), language, locking);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
@ -4,7 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../providers/window_control.dart';
|
||||
import 'home.dart';
|
||||
|
||||
class ChatOnlyPage extends ConsumerStatefulWidget {
|
||||
class ChatOnlyPage extends StatefulWidget {
|
||||
const ChatOnlyPage({super.key});
|
||||
|
||||
@override
|
@ -13,8 +13,8 @@ import 'package:loggy/loggy.dart';
|
||||
import 'package:quickalert/quickalert.dart';
|
||||
import 'package:xterm/xterm.dart';
|
||||
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import '../../tools/tools.dart';
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
|
||||
final globalDebugTerminal = Terminal(
|
||||
maxLines: 50000,
|
||||
@ -25,7 +25,7 @@ const kDefaultTerminalStyle = TerminalStyle(
|
||||
// height: 1.2,
|
||||
fontFamily: 'Source Code Pro');
|
||||
|
||||
class DeveloperPage extends ConsumerStatefulWidget {
|
||||
class DeveloperPage extends StatefulWidget {
|
||||
const DeveloperPage({super.key});
|
||||
|
||||
@override
|
@ -6,22 +6,22 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../proto/proto.dart' as proto;
|
||||
import '../components/chat_component.dart';
|
||||
import '../components/empty_chat_widget.dart';
|
||||
import '../components/profile_widget.dart';
|
||||
import '../entities/local_account.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../components/chat_component.dart';
|
||||
import '../../components/empty_chat_widget.dart';
|
||||
import '../../components/profile_widget.dart';
|
||||
import '../../entities/local_account.dart';
|
||||
import '../providers/account.dart';
|
||||
import '../providers/chat.dart';
|
||||
import '../providers/contact.dart';
|
||||
import '../providers/local_accounts.dart';
|
||||
import '../../local_accounts/local_accounts.dart';
|
||||
import '../providers/logins.dart';
|
||||
import '../providers/window_control.dart';
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import '../../tools/tools.dart';
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
import 'main_pager/main_pager.dart';
|
||||
|
||||
class HomePage extends ConsumerStatefulWidget {
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
@ -1,17 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:radix_colors/radix_colors.dart';
|
||||
|
||||
import '../providers/window_control.dart';
|
||||
|
||||
class IndexPage extends ConsumerWidget {
|
||||
class IndexPage extends StatelessWidget {
|
||||
const IndexPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
ref.watch(windowControlProvider);
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final textTheme = theme.textTheme;
|
||||
final monoTextStyle = textTheme.labelSmall!
|
@ -7,15 +7,15 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import '../../components/contact_invitation_list_widget.dart';
|
||||
import '../../components/contact_list_widget.dart';
|
||||
import '../../entities/local_account.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../../components/contact_invitation_list_widget.dart';
|
||||
import '../../../components/contact_list_widget.dart';
|
||||
import '../../../entities/local_account.dart';
|
||||
import '../../../proto/proto.dart' as proto;
|
||||
import '../../providers/contact.dart';
|
||||
import '../../providers/contact_invite.dart';
|
||||
import '../../tools/theme_service.dart';
|
||||
import '../../tools/tools.dart';
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
import '../../../theme/theme.dart';
|
||||
import '../../../tools/tools.dart';
|
||||
import '../../../veilid_support/veilid_support.dart';
|
||||
|
||||
class AccountPage extends ConsumerStatefulWidget {
|
||||
const AccountPage({
|
@ -3,17 +3,17 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../components/chat_single_contact_list_widget.dart';
|
||||
import '../../components/empty_chat_list_widget.dart';
|
||||
import '../../entities/local_account.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../../components/chat_single_contact_list_widget.dart';
|
||||
import '../../../components/empty_chat_list_widget.dart';
|
||||
import '../../../entities/local_account.dart';
|
||||
import '../../../proto/proto.dart' as proto;
|
||||
import '../../providers/account.dart';
|
||||
import '../../providers/chat.dart';
|
||||
import '../../providers/contact.dart';
|
||||
import '../../providers/local_accounts.dart';
|
||||
import '../../../local_accounts/local_accounts.dart';
|
||||
import '../../providers/logins.dart';
|
||||
import '../../tools/tools.dart';
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
import '../../../tools/tools.dart';
|
||||
import '../../../veilid_support/veilid_support.dart';
|
||||
|
||||
class ChatsPage extends ConsumerStatefulWidget {
|
||||
const ChatsPage({super.key});
|
@ -13,14 +13,14 @@ import 'package:preload_page_view/preload_page_view.dart';
|
||||
import 'package:stylish_bottom_bar/model/bar_items.dart';
|
||||
import 'package:stylish_bottom_bar/stylish_bottom_bar.dart';
|
||||
|
||||
import '../../components/bottom_sheet_action_button.dart';
|
||||
import '../../components/paste_invite_dialog.dart';
|
||||
import '../../components/scan_invite_dialog.dart';
|
||||
import '../../components/send_invite_dialog.dart';
|
||||
import '../../entities/local_account.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../tools/tools.dart';
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
import '../../../components/bottom_sheet_action_button.dart';
|
||||
import '../../../components/paste_invite_dialog.dart';
|
||||
import '../../../components/scan_invite_dialog.dart';
|
||||
import '../../../components/send_invite_dialog.dart';
|
||||
import '../../../entities/local_account.dart';
|
||||
import '../../../proto/proto.dart' as proto;
|
||||
import '../../../tools/tools.dart';
|
||||
import '../../../veilid_support/veilid_support.dart';
|
||||
import 'account.dart';
|
||||
import 'chats.dart';
|
||||
|
@ -7,16 +7,16 @@ import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../components/default_app_bar.dart';
|
||||
import '../components/signal_strength_meter.dart';
|
||||
import '../entities/entities.dart';
|
||||
import '../providers/local_accounts.dart';
|
||||
import '../../components/default_app_bar.dart';
|
||||
import '../../components/signal_strength_meter.dart';
|
||||
import '../../entities/entities.dart';
|
||||
import '../../local_accounts/local_accounts.dart';
|
||||
import '../providers/logins.dart';
|
||||
import '../providers/window_control.dart';
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import '../../tools/tools.dart';
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
|
||||
class NewAccountPage extends ConsumerStatefulWidget {
|
||||
class NewAccountPage extends StatefulWidget {
|
||||
const NewAccountPage({super.key});
|
||||
|
||||
@override
|
@ -6,13 +6,13 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import '../components/default_app_bar.dart';
|
||||
import '../components/signal_strength_meter.dart';
|
||||
import '../entities/preferences.dart';
|
||||
import '../../components/default_app_bar.dart';
|
||||
import '../../components/signal_strength_meter.dart';
|
||||
import '../../entities/preferences.dart';
|
||||
import '../providers/window_control.dart';
|
||||
import '../tools/tools.dart';
|
||||
import '../../tools/tools.dart';
|
||||
|
||||
class SettingsPage extends ConsumerStatefulWidget {
|
||||
class SettingsPage extends StatefulWidget {
|
||||
const SettingsPage({super.key});
|
||||
|
||||
@override
|
@ -1,11 +1,11 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import '../entities/local_account.dart';
|
||||
import '../entities/user_login.dart';
|
||||
import '../proto/proto.dart' as proto;
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import 'local_accounts.dart';
|
||||
import '../../entities/local_account.dart';
|
||||
import '../../entities/user_login.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
import '../../local_accounts/local_accounts.dart';
|
||||
import 'logins.dart';
|
||||
|
||||
part 'account.g.dart';
|
@ -2,9 +2,9 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import '../proto/proto.dart' as proto;
|
||||
import '../../proto/proto.dart' as proto;
|
||||
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
import 'account.dart';
|
||||
|
||||
part 'chat.g.dart';
|
@ -1,7 +1,7 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
|
||||
part 'connection_state.freezed.dart';
|
||||
|
@ -3,10 +3,10 @@ import 'dart:convert';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import '../proto/proto.dart' as proto;
|
||||
import '../../proto/proto.dart' as proto;
|
||||
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import '../tools/tools.dart';
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
import '../../tools/tools.dart';
|
||||
import 'account.dart';
|
||||
import 'chat.dart';
|
||||
|
@ -4,10 +4,10 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
|
||||
import '../entities/entities.dart';
|
||||
import '../proto/proto.dart' as proto;
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import '../../entities/entities.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../tools/tools.dart';
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
import 'account.dart';
|
||||
|
||||
part 'contact_invitation_list_manager.g.dart';
|
@ -4,10 +4,10 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import '../entities/local_account.dart';
|
||||
import '../proto/proto.dart' as proto;
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import '../../entities/local_account.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../tools/tools.dart';
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
import 'account.dart';
|
||||
import 'conversation.dart';
|
||||
|
@ -7,11 +7,11 @@ import 'dart:convert';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import '../proto/proto.dart' as proto;
|
||||
import '../../proto/proto.dart' as proto;
|
||||
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_init.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import '../../tools/tools.dart';
|
||||
import '../../veilid_init.dart';
|
||||
import '../../veilid_support/veilid_support.dart';
|
||||
import 'account.dart';
|
||||
import 'chat.dart';
|
||||
import 'contact.dart';
|
@ -5,7 +5,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import '../tools/responsive.dart';
|
||||
import '../../tools/responsive.dart';
|
||||
|
||||
export 'package:window_manager/window_manager.dart' show TitleBarStyle;
|
||||
|
@ -2,7 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:veilid/veilid.dart';
|
||||
|
||||
import 'providers/connection_state.dart';
|
||||
import 'old_to_refactor/providers/connection_state.dart';
|
||||
import 'tools/tools.dart';
|
||||
import 'veilid_support/src/config.dart';
|
||||
import 'veilid_support/src/veilid_log.dart';
|
||||
|
@ -1,200 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'account.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$fetchAccountInfoHash() => r'3d2e3b3ddce5158d03bceaf82cdb35bae000280c';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an account from the identity key and if it is logged in and we
|
||||
/// have its secret available, return the account record contents
|
||||
///
|
||||
/// Copied from [fetchAccountInfo].
|
||||
@ProviderFor(fetchAccountInfo)
|
||||
const fetchAccountInfoProvider = FetchAccountInfoFamily();
|
||||
|
||||
/// Get an account from the identity key and if it is logged in and we
|
||||
/// have its secret available, return the account record contents
|
||||
///
|
||||
/// Copied from [fetchAccountInfo].
|
||||
class FetchAccountInfoFamily extends Family<AsyncValue<AccountInfo>> {
|
||||
/// Get an account from the identity key and if it is logged in and we
|
||||
/// have its secret available, return the account record contents
|
||||
///
|
||||
/// Copied from [fetchAccountInfo].
|
||||
const FetchAccountInfoFamily();
|
||||
|
||||
/// Get an account from the identity key and if it is logged in and we
|
||||
/// have its secret available, return the account record contents
|
||||
///
|
||||
/// Copied from [fetchAccountInfo].
|
||||
FetchAccountInfoProvider call({
|
||||
required Typed<FixedEncodedString43> accountMasterRecordKey,
|
||||
}) {
|
||||
return FetchAccountInfoProvider(
|
||||
accountMasterRecordKey: accountMasterRecordKey,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
FetchAccountInfoProvider getProviderOverride(
|
||||
covariant FetchAccountInfoProvider provider,
|
||||
) {
|
||||
return call(
|
||||
accountMasterRecordKey: provider.accountMasterRecordKey,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'fetchAccountInfoProvider';
|
||||
}
|
||||
|
||||
/// Get an account from the identity key and if it is logged in and we
|
||||
/// have its secret available, return the account record contents
|
||||
///
|
||||
/// Copied from [fetchAccountInfo].
|
||||
class FetchAccountInfoProvider extends AutoDisposeFutureProvider<AccountInfo> {
|
||||
/// Get an account from the identity key and if it is logged in and we
|
||||
/// have its secret available, return the account record contents
|
||||
///
|
||||
/// Copied from [fetchAccountInfo].
|
||||
FetchAccountInfoProvider({
|
||||
required Typed<FixedEncodedString43> accountMasterRecordKey,
|
||||
}) : this._internal(
|
||||
(ref) => fetchAccountInfo(
|
||||
ref as FetchAccountInfoRef,
|
||||
accountMasterRecordKey: accountMasterRecordKey,
|
||||
),
|
||||
from: fetchAccountInfoProvider,
|
||||
name: r'fetchAccountInfoProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$fetchAccountInfoHash,
|
||||
dependencies: FetchAccountInfoFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
FetchAccountInfoFamily._allTransitiveDependencies,
|
||||
accountMasterRecordKey: accountMasterRecordKey,
|
||||
);
|
||||
|
||||
FetchAccountInfoProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.accountMasterRecordKey,
|
||||
}) : super.internal();
|
||||
|
||||
final Typed<FixedEncodedString43> accountMasterRecordKey;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<AccountInfo> Function(FetchAccountInfoRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: FetchAccountInfoProvider._internal(
|
||||
(ref) => create(ref as FetchAccountInfoRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
accountMasterRecordKey: accountMasterRecordKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<AccountInfo> createElement() {
|
||||
return _FetchAccountInfoProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is FetchAccountInfoProvider &&
|
||||
other.accountMasterRecordKey == accountMasterRecordKey;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, accountMasterRecordKey.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin FetchAccountInfoRef on AutoDisposeFutureProviderRef<AccountInfo> {
|
||||
/// The parameter `accountMasterRecordKey` of this provider.
|
||||
Typed<FixedEncodedString43> get accountMasterRecordKey;
|
||||
}
|
||||
|
||||
class _FetchAccountInfoProviderElement
|
||||
extends AutoDisposeFutureProviderElement<AccountInfo>
|
||||
with FetchAccountInfoRef {
|
||||
_FetchAccountInfoProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
Typed<FixedEncodedString43> get accountMasterRecordKey =>
|
||||
(origin as FetchAccountInfoProvider).accountMasterRecordKey;
|
||||
}
|
||||
|
||||
String _$fetchActiveAccountInfoHash() =>
|
||||
r'85276ff85b0e82c8d3c6313250954f5b578697d1';
|
||||
|
||||
/// Get the active account info
|
||||
///
|
||||
/// Copied from [fetchActiveAccountInfo].
|
||||
@ProviderFor(fetchActiveAccountInfo)
|
||||
final fetchActiveAccountInfoProvider =
|
||||
AutoDisposeFutureProvider<ActiveAccountInfo?>.internal(
|
||||
fetchActiveAccountInfo,
|
||||
name: r'fetchActiveAccountInfoProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$fetchActiveAccountInfoHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef FetchActiveAccountInfoRef
|
||||
= AutoDisposeFutureProviderRef<ActiveAccountInfo?>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
@ -1,28 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'chat.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$fetchChatListHash() => r'0c166082625799862128dff09d9286f64785ba6c';
|
||||
|
||||
/// Get the active account contact list
|
||||
///
|
||||
/// Copied from [fetchChatList].
|
||||
@ProviderFor(fetchChatList)
|
||||
final fetchChatListProvider =
|
||||
AutoDisposeFutureProvider<IList<proto.Chat>?>.internal(
|
||||
fetchChatList,
|
||||
name: r'fetchChatListProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$fetchChatListHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef FetchChatListRef = AutoDisposeFutureProviderRef<IList<proto.Chat>?>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
@ -1,29 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'contact.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$fetchContactListHash() => r'03e5b90435c331be87495d999a62a97af5b74d9e';
|
||||
|
||||
/// Get the active account contact list
|
||||
///
|
||||
/// Copied from [fetchContactList].
|
||||
@ProviderFor(fetchContactList)
|
||||
final fetchContactListProvider =
|
||||
AutoDisposeFutureProvider<IList<proto.Contact>?>.internal(
|
||||
fetchContactList,
|
||||
name: r'fetchContactListProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$fetchContactListHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef FetchContactListRef
|
||||
= AutoDisposeFutureProviderRef<IList<proto.Contact>?>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
@ -1,202 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'contact_invitation_list_manager.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$contactInvitationListManagerHash() =>
|
||||
r'8dda8e5005f0c0c921e3e8b7ce06e54bb5682085';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$ContactInvitationListManager
|
||||
extends BuildlessAutoDisposeAsyncNotifier<
|
||||
IList<proto.ContactInvitationRecord>> {
|
||||
late final ActiveAccountInfo activeAccountInfo;
|
||||
|
||||
FutureOr<IList<proto.ContactInvitationRecord>> build(
|
||||
ActiveAccountInfo activeAccountInfo,
|
||||
);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////
|
||||
///
|
||||
/// Copied from [ContactInvitationListManager].
|
||||
@ProviderFor(ContactInvitationListManager)
|
||||
const contactInvitationListManagerProvider =
|
||||
ContactInvitationListManagerFamily();
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////
|
||||
///
|
||||
/// Copied from [ContactInvitationListManager].
|
||||
class ContactInvitationListManagerFamily
|
||||
extends Family<AsyncValue<IList<proto.ContactInvitationRecord>>> {
|
||||
//////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////
|
||||
///
|
||||
/// Copied from [ContactInvitationListManager].
|
||||
const ContactInvitationListManagerFamily();
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////
|
||||
///
|
||||
/// Copied from [ContactInvitationListManager].
|
||||
ContactInvitationListManagerProvider call(
|
||||
ActiveAccountInfo activeAccountInfo,
|
||||
) {
|
||||
return ContactInvitationListManagerProvider(
|
||||
activeAccountInfo,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ContactInvitationListManagerProvider getProviderOverride(
|
||||
covariant ContactInvitationListManagerProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.activeAccountInfo,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'contactInvitationListManagerProvider';
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////
|
||||
///
|
||||
/// Copied from [ContactInvitationListManager].
|
||||
class ContactInvitationListManagerProvider
|
||||
extends AutoDisposeAsyncNotifierProviderImpl<ContactInvitationListManager,
|
||||
IList<proto.ContactInvitationRecord>> {
|
||||
//////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////
|
||||
///
|
||||
/// Copied from [ContactInvitationListManager].
|
||||
ContactInvitationListManagerProvider(
|
||||
ActiveAccountInfo activeAccountInfo,
|
||||
) : this._internal(
|
||||
() => ContactInvitationListManager()
|
||||
..activeAccountInfo = activeAccountInfo,
|
||||
from: contactInvitationListManagerProvider,
|
||||
name: r'contactInvitationListManagerProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$contactInvitationListManagerHash,
|
||||
dependencies: ContactInvitationListManagerFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
ContactInvitationListManagerFamily._allTransitiveDependencies,
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
);
|
||||
|
||||
ContactInvitationListManagerProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.activeAccountInfo,
|
||||
}) : super.internal();
|
||||
|
||||
final ActiveAccountInfo activeAccountInfo;
|
||||
|
||||
@override
|
||||
FutureOr<IList<proto.ContactInvitationRecord>> runNotifierBuild(
|
||||
covariant ContactInvitationListManager notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
activeAccountInfo,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(ContactInvitationListManager Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: ContactInvitationListManagerProvider._internal(
|
||||
() => create()..activeAccountInfo = activeAccountInfo,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
activeAccountInfo: activeAccountInfo,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeAsyncNotifierProviderElement<ContactInvitationListManager,
|
||||
IList<proto.ContactInvitationRecord>> createElement() {
|
||||
return _ContactInvitationListManagerProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ContactInvitationListManagerProvider &&
|
||||
other.activeAccountInfo == activeAccountInfo;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, activeAccountInfo.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin ContactInvitationListManagerRef on AutoDisposeAsyncNotifierProviderRef<
|
||||
IList<proto.ContactInvitationRecord>> {
|
||||
/// The parameter `activeAccountInfo` of this provider.
|
||||
ActiveAccountInfo get activeAccountInfo;
|
||||
}
|
||||
|
||||
class _ContactInvitationListManagerProviderElement
|
||||
extends AutoDisposeAsyncNotifierProviderElement<
|
||||
ContactInvitationListManager, IList<proto.ContactInvitationRecord>>
|
||||
with ContactInvitationListManagerRef {
|
||||
_ContactInvitationListManagerProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
ActiveAccountInfo get activeAccountInfo =>
|
||||
(origin as ContactInvitationListManagerProvider).activeAccountInfo;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
@ -1,30 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'contact_invite.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$fetchContactInvitationRecordsHash() =>
|
||||
r'ff0b2c68d42cb106602982b1fb56a7bd8183c04a';
|
||||
|
||||
/// Get the active account contact invitation list
|
||||
///
|
||||
/// Copied from [fetchContactInvitationRecords].
|
||||
@ProviderFor(fetchContactInvitationRecords)
|
||||
final fetchContactInvitationRecordsProvider =
|
||||
AutoDisposeFutureProvider<IList<proto.ContactInvitationRecord>?>.internal(
|
||||
fetchContactInvitationRecords,
|
||||
name: r'fetchContactInvitationRecordsProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$fetchContactInvitationRecordsHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef FetchContactInvitationRecordsRef
|
||||
= AutoDisposeFutureProviderRef<IList<proto.ContactInvitationRecord>?>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
@ -1,28 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'conversation.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$activeConversationMessagesHash() =>
|
||||
r'5579a9386f2046b156720ae799a0e77aca119b09';
|
||||
|
||||
/// See also [ActiveConversationMessages].
|
||||
@ProviderFor(ActiveConversationMessages)
|
||||
final activeConversationMessagesProvider = AutoDisposeAsyncNotifierProvider<
|
||||
ActiveConversationMessages, IList<proto.Message>?>.internal(
|
||||
ActiveConversationMessages.new,
|
||||
name: r'activeConversationMessagesProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$activeConversationMessagesHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$ActiveConversationMessages
|
||||
= AutoDisposeAsyncNotifier<IList<proto.Message>?>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
@ -1,167 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import '../entities/entities.dart';
|
||||
import '../proto/proto.dart' as proto;
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_init.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import 'logins.dart';
|
||||
|
||||
part 'local_accounts.g.dart';
|
||||
|
||||
const String veilidChatAccountKey = 'com.veilid.veilidchat';
|
||||
|
||||
// Local accounts table
|
||||
@riverpod
|
||||
class LocalAccounts extends _$LocalAccounts
|
||||
with AsyncTableDBBacked<IList<LocalAccount>> {
|
||||
//////////////////////////////////////////////////////////////
|
||||
/// AsyncTableDBBacked
|
||||
@override
|
||||
String tableName() => 'local_account_manager';
|
||||
@override
|
||||
String tableKeyName() => 'local_accounts';
|
||||
@override
|
||||
IList<LocalAccount> valueFromJson(Object? obj) => obj != null
|
||||
? IList<LocalAccount>.fromJson(
|
||||
obj, genericFromJson(LocalAccount.fromJson))
|
||||
: IList<LocalAccount>();
|
||||
@override
|
||||
Object? valueToJson(IList<LocalAccount> val) =>
|
||||
val.toJson((la) => la.toJson());
|
||||
|
||||
/// Get all local account information
|
||||
@override
|
||||
FutureOr<IList<LocalAccount>> build() async {
|
||||
try {
|
||||
await eventualVeilid.future;
|
||||
return await load();
|
||||
} on Exception catch (e) {
|
||||
log.error('Failed to load LocalAccounts table: $e', e);
|
||||
return const IListConst([]);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
/// Mutators and Selectors
|
||||
|
||||
/// Reorder accounts
|
||||
Future<void> reorderAccount(int oldIndex, int newIndex) async {
|
||||
final localAccounts = state.requireValue;
|
||||
final removedItem = Output<LocalAccount>();
|
||||
final updated = localAccounts
|
||||
.removeAt(oldIndex, removedItem)
|
||||
.insert(newIndex, removedItem.value!);
|
||||
await store(updated);
|
||||
state = AsyncValue.data(updated);
|
||||
}
|
||||
|
||||
/// Creates a new Account associated with master identity
|
||||
/// Adds a logged-out LocalAccount to track its existence on this device
|
||||
Future<LocalAccount> newLocalAccount(
|
||||
{required IdentityMaster identityMaster,
|
||||
required SecretKey identitySecret,
|
||||
required String name,
|
||||
required String pronouns,
|
||||
EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
|
||||
String encryptionKey = ''}) async {
|
||||
final localAccounts = state.requireValue;
|
||||
|
||||
// Add account with profile to DHT
|
||||
await identityMaster.addAccountToIdentity(
|
||||
identitySecret: identitySecret,
|
||||
accountKey: veilidChatAccountKey,
|
||||
createAccountCallback: (parent) async {
|
||||
// Make empty contact list
|
||||
final contactList = await (await DHTShortArray.create(parent: parent))
|
||||
.scope((r) async => r.record.ownedDHTRecordPointer);
|
||||
|
||||
// Make empty contact invitation record list
|
||||
final contactInvitationRecords =
|
||||
await (await DHTShortArray.create(parent: parent))
|
||||
.scope((r) async => r.record.ownedDHTRecordPointer);
|
||||
|
||||
// Make empty chat record list
|
||||
final chatRecords = await (await DHTShortArray.create(parent: parent))
|
||||
.scope((r) async => r.record.ownedDHTRecordPointer);
|
||||
|
||||
// Make account object
|
||||
final account = proto.Account()
|
||||
..profile = (proto.Profile()
|
||||
..name = name
|
||||
..pronouns = pronouns)
|
||||
..contactList = contactList.toProto()
|
||||
..contactInvitationRecords = contactInvitationRecords.toProto()
|
||||
..chatList = chatRecords.toProto();
|
||||
return account;
|
||||
});
|
||||
|
||||
// Encrypt identitySecret with key
|
||||
final identitySecretBytes = await encryptSecretToBytes(
|
||||
secret: identitySecret,
|
||||
cryptoKind: identityMaster.identityRecordKey.kind,
|
||||
encryptionKey: encryptionKey,
|
||||
encryptionKeyType: encryptionKeyType);
|
||||
|
||||
// Create local account object
|
||||
// Does not contain the account key or its secret
|
||||
// as that is not to be persisted, and only pulled from the identity key
|
||||
// and optionally decrypted with the unlock password
|
||||
final localAccount = LocalAccount(
|
||||
identityMaster: identityMaster,
|
||||
identitySecretBytes: identitySecretBytes,
|
||||
encryptionKeyType: encryptionKeyType,
|
||||
biometricsEnabled: false,
|
||||
hiddenAccount: false,
|
||||
name: name,
|
||||
);
|
||||
|
||||
// Add local account object to internal store
|
||||
final newLocalAccounts = localAccounts.add(localAccount);
|
||||
await store(newLocalAccounts);
|
||||
state = AsyncValue.data(newLocalAccounts);
|
||||
|
||||
// Return local account object
|
||||
return localAccount;
|
||||
}
|
||||
|
||||
/// Remove an account and wipe the messages for this account from this device
|
||||
Future<bool> deleteLocalAccount(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);
|
||||
|
||||
// TO DO: wipe messages
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Import an account from another VeilidChat instance
|
||||
|
||||
/// Recover an account with the master identity secret
|
||||
|
||||
/// Delete an account from all devices
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<LocalAccount?> fetchLocalAccount(FetchLocalAccountRef ref,
|
||||
{required TypedKey accountMasterRecordKey}) async {
|
||||
final localAccounts = await ref.watch(localAccountsProvider.future);
|
||||
try {
|
||||
return localAccounts.firstWhere(
|
||||
(e) => e.identityMaster.masterRecordKey == accountMasterRecordKey);
|
||||
} on Exception catch (e) {
|
||||
if (e is StateError) {
|
||||
return null;
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'local_accounts.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$fetchLocalAccountHash() => r'e9f8ea0dd15031cc8145532e9cac73ab7f0f81be';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [fetchLocalAccount].
|
||||
@ProviderFor(fetchLocalAccount)
|
||||
const fetchLocalAccountProvider = FetchLocalAccountFamily();
|
||||
|
||||
/// See also [fetchLocalAccount].
|
||||
class FetchLocalAccountFamily extends Family<AsyncValue<LocalAccount?>> {
|
||||
/// See also [fetchLocalAccount].
|
||||
const FetchLocalAccountFamily();
|
||||
|
||||
/// See also [fetchLocalAccount].
|
||||
FetchLocalAccountProvider call({
|
||||
required Typed<FixedEncodedString43> accountMasterRecordKey,
|
||||
}) {
|
||||
return FetchLocalAccountProvider(
|
||||
accountMasterRecordKey: accountMasterRecordKey,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
FetchLocalAccountProvider getProviderOverride(
|
||||
covariant FetchLocalAccountProvider provider,
|
||||
) {
|
||||
return call(
|
||||
accountMasterRecordKey: provider.accountMasterRecordKey,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'fetchLocalAccountProvider';
|
||||
}
|
||||
|
||||
/// See also [fetchLocalAccount].
|
||||
class FetchLocalAccountProvider
|
||||
extends AutoDisposeFutureProvider<LocalAccount?> {
|
||||
/// See also [fetchLocalAccount].
|
||||
FetchLocalAccountProvider({
|
||||
required Typed<FixedEncodedString43> accountMasterRecordKey,
|
||||
}) : this._internal(
|
||||
(ref) => fetchLocalAccount(
|
||||
ref as FetchLocalAccountRef,
|
||||
accountMasterRecordKey: accountMasterRecordKey,
|
||||
),
|
||||
from: fetchLocalAccountProvider,
|
||||
name: r'fetchLocalAccountProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$fetchLocalAccountHash,
|
||||
dependencies: FetchLocalAccountFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
FetchLocalAccountFamily._allTransitiveDependencies,
|
||||
accountMasterRecordKey: accountMasterRecordKey,
|
||||
);
|
||||
|
||||
FetchLocalAccountProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.accountMasterRecordKey,
|
||||
}) : super.internal();
|
||||
|
||||
final Typed<FixedEncodedString43> accountMasterRecordKey;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<LocalAccount?> Function(FetchLocalAccountRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: FetchLocalAccountProvider._internal(
|
||||
(ref) => create(ref as FetchLocalAccountRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
accountMasterRecordKey: accountMasterRecordKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<LocalAccount?> createElement() {
|
||||
return _FetchLocalAccountProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is FetchLocalAccountProvider &&
|
||||
other.accountMasterRecordKey == accountMasterRecordKey;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, accountMasterRecordKey.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin FetchLocalAccountRef on AutoDisposeFutureProviderRef<LocalAccount?> {
|
||||
/// The parameter `accountMasterRecordKey` of this provider.
|
||||
Typed<FixedEncodedString43> get accountMasterRecordKey;
|
||||
}
|
||||
|
||||
class _FetchLocalAccountProviderElement
|
||||
extends AutoDisposeFutureProviderElement<LocalAccount?>
|
||||
with FetchLocalAccountRef {
|
||||
_FetchLocalAccountProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
Typed<FixedEncodedString43> get accountMasterRecordKey =>
|
||||
(origin as FetchLocalAccountProvider).accountMasterRecordKey;
|
||||
}
|
||||
|
||||
String _$localAccountsHash() => r'f19ec560b585d353219be82bc383b2c091660c53';
|
||||
|
||||
/// See also [LocalAccounts].
|
||||
@ProviderFor(LocalAccounts)
|
||||
final localAccountsProvider = AutoDisposeAsyncNotifierProvider<LocalAccounts,
|
||||
IList<LocalAccount>>.internal(
|
||||
LocalAccounts.new,
|
||||
name: r'localAccountsProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$localAccountsHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$LocalAccounts = AutoDisposeAsyncNotifier<IList<LocalAccount>>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
@ -1,150 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import '../entities/entities.dart';
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_init.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import 'local_accounts.dart';
|
||||
|
||||
part 'logins.g.dart';
|
||||
|
||||
// Local account manager
|
||||
@riverpod
|
||||
class Logins extends _$Logins with AsyncTableDBBacked<ActiveLogins> {
|
||||
//////////////////////////////////////////////////////////////
|
||||
/// AsyncTableDBBacked
|
||||
@override
|
||||
String tableName() => 'local_account_manager';
|
||||
@override
|
||||
String tableKeyName() => 'active_logins';
|
||||
@override
|
||||
ActiveLogins valueFromJson(Object? obj) => obj != null
|
||||
? ActiveLogins.fromJson(obj as Map<String, dynamic>)
|
||||
: ActiveLogins.empty();
|
||||
@override
|
||||
Object? valueToJson(ActiveLogins val) => val.toJson();
|
||||
|
||||
/// Get all local account information
|
||||
@override
|
||||
FutureOr<ActiveLogins> build() async {
|
||||
try {
|
||||
await eventualVeilid.future;
|
||||
return await load();
|
||||
} on Exception catch (e) {
|
||||
log.error('Failed to load ActiveLogins table: $e', e);
|
||||
return const ActiveLogins(userLogins: IListConst([]));
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
/// Mutators and Selectors
|
||||
|
||||
Future<void> switchToAccount(TypedKey? accountMasterRecordKey) async {
|
||||
final current = state.requireValue;
|
||||
if (accountMasterRecordKey != null) {
|
||||
// Assert the specified record key can be found, will throw if not
|
||||
final _ = current.userLogins.firstWhere(
|
||||
(ul) => ul.accountMasterRecordKey == accountMasterRecordKey);
|
||||
}
|
||||
final updated = current.copyWith(activeUserLogin: accountMasterRecordKey);
|
||||
await store(updated);
|
||||
state = AsyncValue.data(updated);
|
||||
}
|
||||
|
||||
Future<bool> _decryptedLogin(
|
||||
IdentityMaster identityMaster, SecretKey identitySecret) async {
|
||||
final veilid = await eventualVeilid.future;
|
||||
final cs =
|
||||
await veilid.getCryptoSystem(identityMaster.identityRecordKey.kind);
|
||||
final keyOk = await cs.validateKeyPair(
|
||||
identityMaster.identityPublicKey, identitySecret);
|
||||
if (!keyOk) {
|
||||
throw Exception('Identity is corrupted');
|
||||
}
|
||||
|
||||
// Read the identity key to get the account keys
|
||||
final accountRecordInfo = await identityMaster.readAccountFromIdentity(
|
||||
identitySecret: identitySecret, accountKey: veilidChatAccountKey);
|
||||
|
||||
// Add to user logins and select it
|
||||
final current = state.requireValue;
|
||||
final now = veilid.now();
|
||||
final updated = current.copyWith(
|
||||
userLogins: current.userLogins.replaceFirstWhere(
|
||||
(ul) => ul.accountMasterRecordKey == identityMaster.masterRecordKey,
|
||||
(ul) => ul != null
|
||||
? ul.copyWith(lastActive: now)
|
||||
: UserLogin(
|
||||
accountMasterRecordKey: identityMaster.masterRecordKey,
|
||||
identitySecret:
|
||||
TypedSecret(kind: cs.kind(), value: identitySecret),
|
||||
accountRecordInfo: accountRecordInfo,
|
||||
lastActive: now),
|
||||
addIfNotFound: true),
|
||||
activeUserLogin: identityMaster.masterRecordKey);
|
||||
await store(updated);
|
||||
state = AsyncValue.data(updated);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> login(TypedKey accountMasterRecordKey,
|
||||
EncryptionKeyType encryptionKeyType, String encryptionKey) async {
|
||||
final localAccounts = ref.read(localAccountsProvider).requireValue;
|
||||
|
||||
// Get account, throws if not found
|
||||
final localAccount = localAccounts.firstWhere(
|
||||
(la) => la.identityMaster.masterRecordKey == accountMasterRecordKey);
|
||||
|
||||
// Log in with this local account
|
||||
|
||||
// Derive key from password
|
||||
if (localAccount.encryptionKeyType != encryptionKeyType) {
|
||||
throw Exception('Wrong authentication type');
|
||||
}
|
||||
|
||||
final identitySecret = await decryptSecretFromBytes(
|
||||
secretBytes: localAccount.identitySecretBytes,
|
||||
cryptoKind: localAccount.identityMaster.identityRecordKey.kind,
|
||||
encryptionKeyType: localAccount.encryptionKeyType,
|
||||
encryptionKey: encryptionKey,
|
||||
);
|
||||
|
||||
// Validate this secret with the identity public key and log in
|
||||
return _decryptedLogin(localAccount.identityMaster, identitySecret);
|
||||
}
|
||||
|
||||
Future<void> logout(TypedKey? accountMasterRecordKey) async {
|
||||
final current = state.requireValue;
|
||||
final logoutUser = accountMasterRecordKey ?? current.activeUserLogin;
|
||||
if (logoutUser == null) {
|
||||
return;
|
||||
}
|
||||
final updated = current.copyWith(
|
||||
activeUserLogin: current.activeUserLogin == logoutUser
|
||||
? null
|
||||
: current.activeUserLogin,
|
||||
userLogins: current.userLogins
|
||||
.removeWhere((ul) => ul.accountMasterRecordKey == logoutUser));
|
||||
await store(updated);
|
||||
state = AsyncValue.data(updated);
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<UserLogin?> fetchLogin(FetchLoginRef ref,
|
||||
{required TypedKey accountMasterRecordKey}) async {
|
||||
final activeLogins = await ref.watch(loginsProvider.future);
|
||||
try {
|
||||
return activeLogins.userLogins
|
||||
.firstWhere((e) => e.accountMasterRecordKey == accountMasterRecordKey);
|
||||
} on Exception catch (e) {
|
||||
if (e is StateError) {
|
||||
return null;
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
@ -1,176 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'logins.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$fetchLoginHash() => r'cfe13f5152f1275e6eccc698142abfd98170d9b9';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [fetchLogin].
|
||||
@ProviderFor(fetchLogin)
|
||||
const fetchLoginProvider = FetchLoginFamily();
|
||||
|
||||
/// See also [fetchLogin].
|
||||
class FetchLoginFamily extends Family<AsyncValue<UserLogin?>> {
|
||||
/// See also [fetchLogin].
|
||||
const FetchLoginFamily();
|
||||
|
||||
/// See also [fetchLogin].
|
||||
FetchLoginProvider call({
|
||||
required Typed<FixedEncodedString43> accountMasterRecordKey,
|
||||
}) {
|
||||
return FetchLoginProvider(
|
||||
accountMasterRecordKey: accountMasterRecordKey,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
FetchLoginProvider getProviderOverride(
|
||||
covariant FetchLoginProvider provider,
|
||||
) {
|
||||
return call(
|
||||
accountMasterRecordKey: provider.accountMasterRecordKey,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'fetchLoginProvider';
|
||||
}
|
||||
|
||||
/// See also [fetchLogin].
|
||||
class FetchLoginProvider extends AutoDisposeFutureProvider<UserLogin?> {
|
||||
/// See also [fetchLogin].
|
||||
FetchLoginProvider({
|
||||
required Typed<FixedEncodedString43> accountMasterRecordKey,
|
||||
}) : this._internal(
|
||||
(ref) => fetchLogin(
|
||||
ref as FetchLoginRef,
|
||||
accountMasterRecordKey: accountMasterRecordKey,
|
||||
),
|
||||
from: fetchLoginProvider,
|
||||
name: r'fetchLoginProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$fetchLoginHash,
|
||||
dependencies: FetchLoginFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
FetchLoginFamily._allTransitiveDependencies,
|
||||
accountMasterRecordKey: accountMasterRecordKey,
|
||||
);
|
||||
|
||||
FetchLoginProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.accountMasterRecordKey,
|
||||
}) : super.internal();
|
||||
|
||||
final Typed<FixedEncodedString43> accountMasterRecordKey;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<UserLogin?> Function(FetchLoginRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: FetchLoginProvider._internal(
|
||||
(ref) => create(ref as FetchLoginRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
accountMasterRecordKey: accountMasterRecordKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<UserLogin?> createElement() {
|
||||
return _FetchLoginProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is FetchLoginProvider &&
|
||||
other.accountMasterRecordKey == accountMasterRecordKey;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, accountMasterRecordKey.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin FetchLoginRef on AutoDisposeFutureProviderRef<UserLogin?> {
|
||||
/// The parameter `accountMasterRecordKey` of this provider.
|
||||
Typed<FixedEncodedString43> get accountMasterRecordKey;
|
||||
}
|
||||
|
||||
class _FetchLoginProviderElement
|
||||
extends AutoDisposeFutureProviderElement<UserLogin?> with FetchLoginRef {
|
||||
_FetchLoginProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
Typed<FixedEncodedString43> get accountMasterRecordKey =>
|
||||
(origin as FetchLoginProvider).accountMasterRecordKey;
|
||||
}
|
||||
|
||||
String _$loginsHash() => r'2660f71bb7903464187a93fba5c07e22041e8c40';
|
||||
|
||||
/// See also [Logins].
|
||||
@ProviderFor(Logins)
|
||||
final loginsProvider =
|
||||
AutoDisposeAsyncNotifierProvider<Logins, ActiveLogins>.internal(
|
||||
Logins.new,
|
||||
name: r'loginsProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$loginsHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$Logins = AutoDisposeAsyncNotifier<ActiveLogins>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
@ -1,26 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'window_control.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$windowControlHash() => r'c6afcbe1d4bfcfc580c30393aac60624c5ceabe0';
|
||||
|
||||
/// See also [WindowControl].
|
||||
@ProviderFor(WindowControl)
|
||||
final windowControlProvider =
|
||||
AutoDisposeAsyncNotifierProvider<WindowControl, bool>.internal(
|
||||
WindowControl.new,
|
||||
name: r'windowControlProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$windowControlHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$WindowControl = AutoDisposeAsyncNotifier<bool>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
152
lib/router/cubit/router_cubit.dart
Normal file
152
lib/router/cubit/router_cubit.dart
Normal file
@ -0,0 +1,152 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../init.dart';
|
||||
import '../../local_account_manager/account_repository/account_repository.dart';
|
||||
import '../../old_to_refactor/pages/chat_only.dart';
|
||||
import '../../old_to_refactor/pages/developer.dart';
|
||||
import '../../old_to_refactor/pages/home.dart';
|
||||
import '../../old_to_refactor/pages/index.dart';
|
||||
import '../../old_to_refactor/pages/new_account.dart';
|
||||
import '../../old_to_refactor/pages/settings.dart';
|
||||
import '../../tools/tools.dart';
|
||||
|
||||
part 'router_cubit.freezed.dart';
|
||||
part 'router_cubit.g.dart';
|
||||
part 'router_state.dart';
|
||||
|
||||
class RouterCubit extends Cubit<RouterState> {
|
||||
RouterCubit(AccountRepository accountRepository)
|
||||
: super(const RouterState(
|
||||
isInitialized: false,
|
||||
hasAnyAccount: false,
|
||||
hasActiveChat: false,
|
||||
)) {
|
||||
// Watch for changes that the router will care about
|
||||
Future.delayed(Duration.zero, () async {
|
||||
await eventualInitialized.future;
|
||||
emit(state.copyWith(isInitialized: true));
|
||||
});
|
||||
// Subscribe to repository streams
|
||||
_accountRepositorySubscription =
|
||||
accountRepository.changes().listen((event) {
|
||||
switch (event) {
|
||||
case AccountRepositoryChange.localAccounts:
|
||||
emit(state.copyWith(
|
||||
hasAnyAccount: accountRepository.getLocalAccounts().isNotEmpty));
|
||||
break;
|
||||
case AccountRepositoryChange.userLogins:
|
||||
case AccountRepositoryChange.activeUserLogin:
|
||||
break;
|
||||
}
|
||||
});
|
||||
_chatListRepositorySubscription = ...
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _accountRepositorySubscription.cancel();
|
||||
await super.close();
|
||||
}
|
||||
|
||||
/// Our application routes
|
||||
List<GoRoute> get routes => [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => const IndexPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/home',
|
||||
builder: (context, state) => const HomePage(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'settings',
|
||||
builder: (context, state) => const SettingsPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'chat',
|
||||
builder: (context, state) => const ChatOnlyPage(),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/new_account',
|
||||
builder: (context, state) => const NewAccountPage(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'settings',
|
||||
builder: (context, state) => const SettingsPage(),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/developer',
|
||||
builder: (context, state) => const DeveloperPage(),
|
||||
)
|
||||
];
|
||||
|
||||
/// Redirects when our state changes
|
||||
String? redirect(BuildContext context, GoRouterState goRouterState) {
|
||||
// if (state.isLoading || state.hasError) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// No matter where we are, if there's not
|
||||
switch (goRouterState.matchedLocation) {
|
||||
case '/':
|
||||
|
||||
// Wait for veilid to be initialized
|
||||
if (!eventualVeilid.isCompleted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return state.hasAnyAccount ? '/home' : '/new_account';
|
||||
case '/new_account':
|
||||
return state.hasAnyAccount ? '/home' : null;
|
||||
case '/home':
|
||||
if (!state.hasAnyAccount) {
|
||||
return '/new_account';
|
||||
}
|
||||
if (responsiveVisibility(
|
||||
context: context,
|
||||
tablet: false,
|
||||
tabletLandscape: false,
|
||||
desktop: false)) {
|
||||
if (state.hasActiveChat) {
|
||||
return '/home/chat';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case '/home/chat':
|
||||
if (!state.hasAnyAccount) {
|
||||
return '/new_account';
|
||||
}
|
||||
if (responsiveVisibility(
|
||||
context: context,
|
||||
tablet: false,
|
||||
tabletLandscape: false,
|
||||
desktop: false)) {
|
||||
if (!state.hasActiveChat) {
|
||||
return '/home';
|
||||
}
|
||||
} else {
|
||||
return '/home';
|
||||
}
|
||||
return null;
|
||||
case '/home/settings':
|
||||
case '/new_account/settings':
|
||||
return null;
|
||||
case '/developer':
|
||||
return null;
|
||||
default:
|
||||
return state.hasAnyAccount ? null : '/new_account';
|
||||
}
|
||||
}
|
||||
|
||||
late final StreamSubscription<AccountRepositoryChange>
|
||||
_accountRepositorySubscription;
|
||||
}
|
193
lib/router/cubit/router_cubit.freezed.dart
Normal file
193
lib/router/cubit/router_cubit.freezed.dart
Normal file
@ -0,0 +1,193 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'router_cubit.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
|
||||
RouterState _$RouterStateFromJson(Map<String, dynamic> json) {
|
||||
return _RouterState.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$RouterState {
|
||||
bool get isInitialized => throw _privateConstructorUsedError;
|
||||
bool get hasAnyAccount => throw _privateConstructorUsedError;
|
||||
bool get hasActiveChat => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$RouterStateCopyWith<RouterState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $RouterStateCopyWith<$Res> {
|
||||
factory $RouterStateCopyWith(
|
||||
RouterState value, $Res Function(RouterState) then) =
|
||||
_$RouterStateCopyWithImpl<$Res, RouterState>;
|
||||
@useResult
|
||||
$Res call({bool isInitialized, bool hasAnyAccount, bool hasActiveChat});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$RouterStateCopyWithImpl<$Res, $Val extends RouterState>
|
||||
implements $RouterStateCopyWith<$Res> {
|
||||
_$RouterStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? isInitialized = null,
|
||||
Object? hasAnyAccount = null,
|
||||
Object? hasActiveChat = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
isInitialized: null == isInitialized
|
||||
? _value.isInitialized
|
||||
: isInitialized // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
hasAnyAccount: null == hasAnyAccount
|
||||
? _value.hasAnyAccount
|
||||
: hasAnyAccount // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
hasActiveChat: null == hasActiveChat
|
||||
? _value.hasActiveChat
|
||||
: hasActiveChat // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$RouterStateImplCopyWith<$Res>
|
||||
implements $RouterStateCopyWith<$Res> {
|
||||
factory _$$RouterStateImplCopyWith(
|
||||
_$RouterStateImpl value, $Res Function(_$RouterStateImpl) then) =
|
||||
__$$RouterStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({bool isInitialized, bool hasAnyAccount, bool hasActiveChat});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$RouterStateImplCopyWithImpl<$Res>
|
||||
extends _$RouterStateCopyWithImpl<$Res, _$RouterStateImpl>
|
||||
implements _$$RouterStateImplCopyWith<$Res> {
|
||||
__$$RouterStateImplCopyWithImpl(
|
||||
_$RouterStateImpl _value, $Res Function(_$RouterStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? isInitialized = null,
|
||||
Object? hasAnyAccount = null,
|
||||
Object? hasActiveChat = null,
|
||||
}) {
|
||||
return _then(_$RouterStateImpl(
|
||||
isInitialized: null == isInitialized
|
||||
? _value.isInitialized
|
||||
: isInitialized // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
hasAnyAccount: null == hasAnyAccount
|
||||
? _value.hasAnyAccount
|
||||
: hasAnyAccount // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
hasActiveChat: null == hasActiveChat
|
||||
? _value.hasActiveChat
|
||||
: hasActiveChat // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$RouterStateImpl implements _RouterState {
|
||||
const _$RouterStateImpl(
|
||||
{required this.isInitialized,
|
||||
required this.hasAnyAccount,
|
||||
required this.hasActiveChat});
|
||||
|
||||
factory _$RouterStateImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$RouterStateImplFromJson(json);
|
||||
|
||||
@override
|
||||
final bool isInitialized;
|
||||
@override
|
||||
final bool hasAnyAccount;
|
||||
@override
|
||||
final bool hasActiveChat;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'RouterState(isInitialized: $isInitialized, hasAnyAccount: $hasAnyAccount, hasActiveChat: $hasActiveChat)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$RouterStateImpl &&
|
||||
(identical(other.isInitialized, isInitialized) ||
|
||||
other.isInitialized == isInitialized) &&
|
||||
(identical(other.hasAnyAccount, hasAnyAccount) ||
|
||||
other.hasAnyAccount == hasAnyAccount) &&
|
||||
(identical(other.hasActiveChat, hasActiveChat) ||
|
||||
other.hasActiveChat == hasActiveChat));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, isInitialized, hasAnyAccount, hasActiveChat);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$RouterStateImplCopyWith<_$RouterStateImpl> get copyWith =>
|
||||
__$$RouterStateImplCopyWithImpl<_$RouterStateImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$RouterStateImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _RouterState implements RouterState {
|
||||
const factory _RouterState(
|
||||
{required final bool isInitialized,
|
||||
required final bool hasAnyAccount,
|
||||
required final bool hasActiveChat}) = _$RouterStateImpl;
|
||||
|
||||
factory _RouterState.fromJson(Map<String, dynamic> json) =
|
||||
_$RouterStateImpl.fromJson;
|
||||
|
||||
@override
|
||||
bool get isInitialized;
|
||||
@override
|
||||
bool get hasAnyAccount;
|
||||
@override
|
||||
bool get hasActiveChat;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$RouterStateImplCopyWith<_$RouterStateImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
21
lib/router/cubit/router_cubit.g.dart
Normal file
21
lib/router/cubit/router_cubit.g.dart
Normal file
@ -0,0 +1,21 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'router_cubit.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$RouterStateImpl _$$RouterStateImplFromJson(Map<String, dynamic> json) =>
|
||||
_$RouterStateImpl(
|
||||
isInitialized: json['is_initialized'] as bool,
|
||||
hasAnyAccount: json['has_any_account'] as bool,
|
||||
hasActiveChat: json['has_active_chat'] as bool,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$RouterStateImplToJson(_$RouterStateImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'is_initialized': instance.isInitialized,
|
||||
'has_any_account': instance.hasAnyAccount,
|
||||
'has_active_chat': instance.hasActiveChat,
|
||||
};
|
12
lib/router/cubit/router_state.dart
Normal file
12
lib/router/cubit/router_state.dart
Normal file
@ -0,0 +1,12 @@
|
||||
part of 'router_cubit.dart';
|
||||
|
||||
@freezed
|
||||
class RouterState with _$RouterState {
|
||||
const factory RouterState(
|
||||
{required bool isInitialized,
|
||||
required bool hasAnyAccount,
|
||||
required bool hasActiveChat}) = _RouterState;
|
||||
|
||||
factory RouterState.fromJson(dynamic json) =>
|
||||
_$RouterStateFromJson(json as Map<String, dynamic>);
|
||||
}
|
20
lib/router/make_router.dart
Normal file
20
lib/router/make_router.dart
Normal file
@ -0,0 +1,20 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:stream_transform/stream_transform.dart';
|
||||
|
||||
import '../tools/stream_listenable.dart';
|
||||
import 'cubit/router_cubit.dart';
|
||||
|
||||
final _key = GlobalKey<NavigatorState>(debugLabel: 'routerKey');
|
||||
|
||||
/// This simple provider caches our GoRouter.
|
||||
GoRouter router({required RouterCubit routerCubit}) => GoRouter(
|
||||
navigatorKey: _key,
|
||||
refreshListenable: StreamListenable(
|
||||
routerCubit.stream.startWith(routerCubit.state).distinct()),
|
||||
debugLogDiagnostics: kDebugMode,
|
||||
initialLocation: '/',
|
||||
routes: routerCubit.routes,
|
||||
redirect: routerCubit.redirect,
|
||||
);
|
@ -1,23 +1,2 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import 'router_notifier.dart';
|
||||
|
||||
part 'router.g.dart';
|
||||
|
||||
final _key = GlobalKey<NavigatorState>(debugLabel: 'routerKey');
|
||||
|
||||
/// This simple provider caches our GoRouter.
|
||||
@riverpod
|
||||
GoRouter router(RouterRef ref) {
|
||||
final notifier = ref.watch(routerNotifierProvider.notifier);
|
||||
return GoRouter(
|
||||
navigatorKey: _key,
|
||||
refreshListenable: notifier,
|
||||
debugLogDiagnostics: true,
|
||||
initialLocation: '/',
|
||||
routes: notifier.routes,
|
||||
redirect: notifier.redirect,
|
||||
);
|
||||
}
|
||||
export 'cubit/router_cubit.dart';
|
||||
export 'make_router.dart';
|
||||
|
@ -1,26 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'router.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$routerHash() => r'86eecb1955be62ef8e6f6efcec0fa615289cb823';
|
||||
|
||||
/// This simple provider caches our GoRouter.
|
||||
///
|
||||
/// Copied from [router].
|
||||
@ProviderFor(router)
|
||||
final routerProvider = AutoDisposeProvider<GoRouter>.internal(
|
||||
router,
|
||||
name: r'routerProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$routerHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef RouterRef = AutoDisposeProviderRef<GoRouter>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
@ -1,160 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import '../pages/chat_only.dart';
|
||||
import '../pages/developer.dart';
|
||||
import '../pages/home.dart';
|
||||
import '../pages/index.dart';
|
||||
import '../pages/new_account.dart';
|
||||
import '../pages/settings.dart';
|
||||
import '../providers/chat.dart';
|
||||
import '../providers/local_accounts.dart';
|
||||
import '../tools/responsive.dart';
|
||||
import '../veilid_init.dart';
|
||||
|
||||
part 'router_notifier.g.dart';
|
||||
|
||||
@riverpod
|
||||
class RouterNotifier extends _$RouterNotifier implements Listenable {
|
||||
/// GoRouter listener
|
||||
VoidCallback? routerListener;
|
||||
|
||||
/// Do we need to make or import an account immediately?
|
||||
bool hasAnyAccount = false;
|
||||
bool hasActiveChat = false;
|
||||
|
||||
/// AsyncNotifier build
|
||||
@override
|
||||
Future<void> build() async {
|
||||
hasAnyAccount = await ref.watch(
|
||||
localAccountsProvider.selectAsync((data) => data.isNotEmpty),
|
||||
);
|
||||
hasActiveChat = ref.watch(activeChatStateProvider) != null;
|
||||
|
||||
// When this notifier's state changes, inform GoRouter
|
||||
ref.listenSelf((_, __) {
|
||||
if (state.isLoading) {
|
||||
return;
|
||||
}
|
||||
routerListener?.call();
|
||||
});
|
||||
}
|
||||
|
||||
/// Redirects when our state changes
|
||||
String? redirect(BuildContext context, GoRouterState state) {
|
||||
if (this.state.isLoading || this.state.hasError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// No matter where we are, if there's not
|
||||
switch (state.matchedLocation) {
|
||||
case '/':
|
||||
|
||||
// Wait for veilid to be initialized
|
||||
if (!eventualVeilid.isCompleted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return hasAnyAccount ? '/home' : '/new_account';
|
||||
case '/new_account':
|
||||
return hasAnyAccount ? '/home' : null;
|
||||
case '/home':
|
||||
if (!hasAnyAccount) {
|
||||
return '/new_account';
|
||||
}
|
||||
if (responsiveVisibility(
|
||||
context: context,
|
||||
tablet: false,
|
||||
tabletLandscape: false,
|
||||
desktop: false)) {
|
||||
if (hasActiveChat) {
|
||||
return '/home/chat';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case '/home/chat':
|
||||
if (!hasAnyAccount) {
|
||||
return '/new_account';
|
||||
}
|
||||
if (responsiveVisibility(
|
||||
context: context,
|
||||
tablet: false,
|
||||
tabletLandscape: false,
|
||||
desktop: false)) {
|
||||
if (!hasActiveChat) {
|
||||
return '/home';
|
||||
}
|
||||
} else {
|
||||
return '/home';
|
||||
}
|
||||
return null;
|
||||
case '/home/settings':
|
||||
case '/new_account/settings':
|
||||
return null;
|
||||
case '/developer':
|
||||
return null;
|
||||
default:
|
||||
return hasAnyAccount ? null : '/new_account';
|
||||
}
|
||||
}
|
||||
|
||||
/// Our application routes
|
||||
List<GoRoute> get routes => [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => const IndexPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/home',
|
||||
builder: (context, state) => const HomePage(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'settings',
|
||||
builder: (context, state) => const SettingsPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'chat',
|
||||
builder: (context, state) => const ChatOnlyPage(),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/new_account',
|
||||
builder: (context, state) => const NewAccountPage(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'settings',
|
||||
builder: (context, state) => const SettingsPage(),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/developer',
|
||||
builder: (context, state) => const DeveloperPage(),
|
||||
)
|
||||
];
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
/// Listenable
|
||||
|
||||
/// Adds [GoRouter]'s listener as specified by its [Listenable].
|
||||
/// [GoRouteInformationProvider] uses this method on creation to handle its
|
||||
/// internal [ChangeNotifier].
|
||||
/// Check out the internal implementation of [GoRouter] and
|
||||
/// [GoRouteInformationProvider] to see this in action.
|
||||
@override
|
||||
void addListener(VoidCallback listener) {
|
||||
routerListener = listener;
|
||||
}
|
||||
|
||||
/// Removes [GoRouter]'s listener as specified by its [Listenable].
|
||||
/// [GoRouteInformationProvider] uses this method when disposing,
|
||||
/// so that it removes its callback when destroyed.
|
||||
/// Check out the internal implementation of [GoRouter] and
|
||||
/// [GoRouteInformationProvider] to see this in action.
|
||||
@override
|
||||
void removeListener(VoidCallback listener) {
|
||||
routerListener = null;
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'router_notifier.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$routerNotifierHash() => r'6f52ed95f090f2d198d358e7526a91511c0a61e5';
|
||||
|
||||
/// See also [RouterNotifier].
|
||||
@ProviderFor(RouterNotifier)
|
||||
final routerNotifierProvider =
|
||||
AutoDisposeAsyncNotifierProvider<RouterNotifier, void>.internal(
|
||||
RouterNotifier.new,
|
||||
name: r'routerNotifierProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$routerNotifierHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$RouterNotifier = AutoDisposeAsyncNotifier<void>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
@ -2,7 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
||||
import 'package:radix_colors/radix_colors.dart';
|
||||
|
||||
import 'theme_service.dart';
|
||||
import 'scale_color.dart';
|
||||
import 'scale_scheme.dart';
|
||||
|
||||
enum RadixThemeColor {
|
||||
scarlet, // tomato + red + violet
|
91
lib/theme/scale_color.dart
Normal file
91
lib/theme/scale_color.dart
Normal file
@ -0,0 +1,91 @@
|
||||
import 'dart:ui';
|
||||
|
||||
class ScaleColor {
|
||||
ScaleColor({
|
||||
required this.appBackground,
|
||||
required this.subtleBackground,
|
||||
required this.elementBackground,
|
||||
required this.hoverElementBackground,
|
||||
required this.activeElementBackground,
|
||||
required this.subtleBorder,
|
||||
required this.border,
|
||||
required this.hoverBorder,
|
||||
required this.background,
|
||||
required this.hoverBackground,
|
||||
required this.subtleText,
|
||||
required this.text,
|
||||
});
|
||||
|
||||
Color appBackground;
|
||||
Color subtleBackground;
|
||||
Color elementBackground;
|
||||
Color hoverElementBackground;
|
||||
Color activeElementBackground;
|
||||
Color subtleBorder;
|
||||
Color border;
|
||||
Color hoverBorder;
|
||||
Color background;
|
||||
Color hoverBackground;
|
||||
Color subtleText;
|
||||
Color text;
|
||||
|
||||
ScaleColor copyWith(
|
||||
{Color? appBackground,
|
||||
Color? subtleBackground,
|
||||
Color? elementBackground,
|
||||
Color? hoverElementBackground,
|
||||
Color? activeElementBackground,
|
||||
Color? subtleBorder,
|
||||
Color? border,
|
||||
Color? hoverBorder,
|
||||
Color? background,
|
||||
Color? hoverBackground,
|
||||
Color? subtleText,
|
||||
Color? text}) =>
|
||||
ScaleColor(
|
||||
appBackground: appBackground ?? this.appBackground,
|
||||
subtleBackground: subtleBackground ?? this.subtleBackground,
|
||||
elementBackground: elementBackground ?? this.elementBackground,
|
||||
hoverElementBackground:
|
||||
hoverElementBackground ?? this.hoverElementBackground,
|
||||
activeElementBackground:
|
||||
activeElementBackground ?? this.activeElementBackground,
|
||||
subtleBorder: subtleBorder ?? this.subtleBorder,
|
||||
border: border ?? this.border,
|
||||
hoverBorder: hoverBorder ?? this.hoverBorder,
|
||||
background: background ?? this.background,
|
||||
hoverBackground: hoverBackground ?? this.hoverBackground,
|
||||
subtleText: subtleText ?? this.subtleText,
|
||||
text: text ?? this.text,
|
||||
);
|
||||
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static ScaleColor lerp(ScaleColor a, ScaleColor b, double t) => ScaleColor(
|
||||
appBackground: Color.lerp(a.appBackground, b.appBackground, t) ??
|
||||
const Color(0x00000000),
|
||||
subtleBackground:
|
||||
Color.lerp(a.subtleBackground, b.subtleBackground, t) ??
|
||||
const Color(0x00000000),
|
||||
elementBackground:
|
||||
Color.lerp(a.elementBackground, b.elementBackground, t) ??
|
||||
const Color(0x00000000),
|
||||
hoverElementBackground:
|
||||
Color.lerp(a.hoverElementBackground, b.hoverElementBackground, t) ??
|
||||
const Color(0x00000000),
|
||||
activeElementBackground: Color.lerp(
|
||||
a.activeElementBackground, b.activeElementBackground, t) ??
|
||||
const Color(0x00000000),
|
||||
subtleBorder: Color.lerp(a.subtleBorder, b.subtleBorder, t) ??
|
||||
const Color(0x00000000),
|
||||
border: Color.lerp(a.border, b.border, t) ?? const Color(0x00000000),
|
||||
hoverBorder: Color.lerp(a.hoverBorder, b.hoverBorder, t) ??
|
||||
const Color(0x00000000),
|
||||
background: Color.lerp(a.background, b.background, t) ??
|
||||
const Color(0x00000000),
|
||||
hoverBackground: Color.lerp(a.hoverBackground, b.hoverBackground, t) ??
|
||||
const Color(0x00000000),
|
||||
subtleText: Color.lerp(a.subtleText, b.subtleText, t) ??
|
||||
const Color(0x00000000),
|
||||
text: Color.lerp(a.text, b.text, t) ?? const Color(0x00000000),
|
||||
);
|
||||
}
|
53
lib/theme/scale_scheme.dart
Normal file
53
lib/theme/scale_scheme.dart
Normal file
@ -0,0 +1,53 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'scale_color.dart';
|
||||
|
||||
class ScaleScheme extends ThemeExtension<ScaleScheme> {
|
||||
ScaleScheme(
|
||||
{required this.primaryScale,
|
||||
required this.primaryAlphaScale,
|
||||
required this.secondaryScale,
|
||||
required this.tertiaryScale,
|
||||
required this.grayScale,
|
||||
required this.errorScale});
|
||||
|
||||
final ScaleColor primaryScale;
|
||||
final ScaleColor primaryAlphaScale;
|
||||
final ScaleColor secondaryScale;
|
||||
final ScaleColor tertiaryScale;
|
||||
final ScaleColor grayScale;
|
||||
final ScaleColor errorScale;
|
||||
|
||||
@override
|
||||
ScaleScheme copyWith(
|
||||
{ScaleColor? primaryScale,
|
||||
ScaleColor? primaryAlphaScale,
|
||||
ScaleColor? secondaryScale,
|
||||
ScaleColor? tertiaryScale,
|
||||
ScaleColor? grayScale,
|
||||
ScaleColor? errorScale}) =>
|
||||
ScaleScheme(
|
||||
primaryScale: primaryScale ?? this.primaryScale,
|
||||
primaryAlphaScale: primaryAlphaScale ?? this.primaryAlphaScale,
|
||||
secondaryScale: secondaryScale ?? this.secondaryScale,
|
||||
tertiaryScale: tertiaryScale ?? this.tertiaryScale,
|
||||
grayScale: grayScale ?? this.grayScale,
|
||||
errorScale: errorScale ?? this.errorScale,
|
||||
);
|
||||
|
||||
@override
|
||||
ScaleScheme lerp(ScaleScheme? other, double t) {
|
||||
if (other is! ScaleScheme) {
|
||||
return this;
|
||||
}
|
||||
return ScaleScheme(
|
||||
primaryScale: ScaleColor.lerp(primaryScale, other.primaryScale, t),
|
||||
primaryAlphaScale:
|
||||
ScaleColor.lerp(primaryAlphaScale, other.primaryAlphaScale, t),
|
||||
secondaryScale: ScaleColor.lerp(secondaryScale, other.secondaryScale, t),
|
||||
tertiaryScale: ScaleColor.lerp(tertiaryScale, other.tertiaryScale, t),
|
||||
grayScale: ScaleColor.lerp(grayScale, other.grayScale, t),
|
||||
errorScale: ScaleColor.lerp(errorScale, other.errorScale, t),
|
||||
);
|
||||
}
|
||||
}
|
3
lib/theme/theme.dart
Normal file
3
lib/theme/theme.dart
Normal file
@ -0,0 +1,3 @@
|
||||
export 'scale_scheme.dart';
|
||||
export 'theme_preference.dart';
|
||||
export 'theme_repository.dart';
|
52
lib/theme/theme_preference.dart
Normal file
52
lib/theme/theme_preference.dart
Normal file
@ -0,0 +1,52 @@
|
||||
import 'package:change_case/change_case.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'theme_preference.freezed.dart';
|
||||
part 'theme_preference.g.dart';
|
||||
|
||||
// Theme supports light and dark mode, optionally selected by the
|
||||
// operating system
|
||||
enum BrightnessPreference {
|
||||
system,
|
||||
light,
|
||||
dark;
|
||||
|
||||
factory BrightnessPreference.fromJson(dynamic j) =>
|
||||
BrightnessPreference.values.byName((j as String).toCamelCase());
|
||||
|
||||
String toJson() => name.toPascalCase();
|
||||
}
|
||||
|
||||
// Theme supports multiple color variants based on 'Radix'
|
||||
enum ColorPreference {
|
||||
// Radix Colors
|
||||
scarlet,
|
||||
babydoll,
|
||||
vapor,
|
||||
gold,
|
||||
garden,
|
||||
forest,
|
||||
arctic,
|
||||
lapis,
|
||||
eggplant,
|
||||
lime,
|
||||
grim,
|
||||
// Accessible Colors
|
||||
contrast;
|
||||
|
||||
factory ColorPreference.fromJson(dynamic j) =>
|
||||
ColorPreference.values.byName((j as String).toCamelCase());
|
||||
String toJson() => name.toPascalCase();
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ThemePreferences with _$ThemePreferences {
|
||||
const factory ThemePreferences({
|
||||
required BrightnessPreference brightnessPreference,
|
||||
required ColorPreference colorPreference,
|
||||
required double displayScale,
|
||||
}) = _ThemePreferences;
|
||||
|
||||
factory ThemePreferences.fromJson(dynamic json) =>
|
||||
_$ThemePreferencesFromJson(json as Map<String, dynamic>);
|
||||
}
|
201
lib/theme/theme_preference.freezed.dart
Normal file
201
lib/theme/theme_preference.freezed.dart
Normal file
@ -0,0 +1,201 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'theme_preference.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
|
||||
ThemePreferences _$ThemePreferencesFromJson(Map<String, dynamic> json) {
|
||||
return _ThemePreferences.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ThemePreferences {
|
||||
BrightnessPreference get brightnessPreference =>
|
||||
throw _privateConstructorUsedError;
|
||||
ColorPreference get colorPreference => throw _privateConstructorUsedError;
|
||||
double get displayScale => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$ThemePreferencesCopyWith<ThemePreferences> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ThemePreferencesCopyWith<$Res> {
|
||||
factory $ThemePreferencesCopyWith(
|
||||
ThemePreferences value, $Res Function(ThemePreferences) then) =
|
||||
_$ThemePreferencesCopyWithImpl<$Res, ThemePreferences>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{BrightnessPreference brightnessPreference,
|
||||
ColorPreference colorPreference,
|
||||
double displayScale});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ThemePreferencesCopyWithImpl<$Res, $Val extends ThemePreferences>
|
||||
implements $ThemePreferencesCopyWith<$Res> {
|
||||
_$ThemePreferencesCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? brightnessPreference = null,
|
||||
Object? colorPreference = null,
|
||||
Object? displayScale = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
brightnessPreference: null == brightnessPreference
|
||||
? _value.brightnessPreference
|
||||
: brightnessPreference // ignore: cast_nullable_to_non_nullable
|
||||
as BrightnessPreference,
|
||||
colorPreference: null == colorPreference
|
||||
? _value.colorPreference
|
||||
: colorPreference // ignore: cast_nullable_to_non_nullable
|
||||
as ColorPreference,
|
||||
displayScale: null == displayScale
|
||||
? _value.displayScale
|
||||
: displayScale // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ThemePreferencesImplCopyWith<$Res>
|
||||
implements $ThemePreferencesCopyWith<$Res> {
|
||||
factory _$$ThemePreferencesImplCopyWith(_$ThemePreferencesImpl value,
|
||||
$Res Function(_$ThemePreferencesImpl) then) =
|
||||
__$$ThemePreferencesImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{BrightnessPreference brightnessPreference,
|
||||
ColorPreference colorPreference,
|
||||
double displayScale});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ThemePreferencesImplCopyWithImpl<$Res>
|
||||
extends _$ThemePreferencesCopyWithImpl<$Res, _$ThemePreferencesImpl>
|
||||
implements _$$ThemePreferencesImplCopyWith<$Res> {
|
||||
__$$ThemePreferencesImplCopyWithImpl(_$ThemePreferencesImpl _value,
|
||||
$Res Function(_$ThemePreferencesImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? brightnessPreference = null,
|
||||
Object? colorPreference = null,
|
||||
Object? displayScale = null,
|
||||
}) {
|
||||
return _then(_$ThemePreferencesImpl(
|
||||
brightnessPreference: null == brightnessPreference
|
||||
? _value.brightnessPreference
|
||||
: brightnessPreference // ignore: cast_nullable_to_non_nullable
|
||||
as BrightnessPreference,
|
||||
colorPreference: null == colorPreference
|
||||
? _value.colorPreference
|
||||
: colorPreference // ignore: cast_nullable_to_non_nullable
|
||||
as ColorPreference,
|
||||
displayScale: null == displayScale
|
||||
? _value.displayScale
|
||||
: displayScale // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ThemePreferencesImpl implements _ThemePreferences {
|
||||
const _$ThemePreferencesImpl(
|
||||
{required this.brightnessPreference,
|
||||
required this.colorPreference,
|
||||
required this.displayScale});
|
||||
|
||||
factory _$ThemePreferencesImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ThemePreferencesImplFromJson(json);
|
||||
|
||||
@override
|
||||
final BrightnessPreference brightnessPreference;
|
||||
@override
|
||||
final ColorPreference colorPreference;
|
||||
@override
|
||||
final double displayScale;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ThemePreferences(brightnessPreference: $brightnessPreference, colorPreference: $colorPreference, displayScale: $displayScale)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ThemePreferencesImpl &&
|
||||
(identical(other.brightnessPreference, brightnessPreference) ||
|
||||
other.brightnessPreference == brightnessPreference) &&
|
||||
(identical(other.colorPreference, colorPreference) ||
|
||||
other.colorPreference == colorPreference) &&
|
||||
(identical(other.displayScale, displayScale) ||
|
||||
other.displayScale == displayScale));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType, brightnessPreference, colorPreference, displayScale);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ThemePreferencesImplCopyWith<_$ThemePreferencesImpl> get copyWith =>
|
||||
__$$ThemePreferencesImplCopyWithImpl<_$ThemePreferencesImpl>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ThemePreferencesImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ThemePreferences implements ThemePreferences {
|
||||
const factory _ThemePreferences(
|
||||
{required final BrightnessPreference brightnessPreference,
|
||||
required final ColorPreference colorPreference,
|
||||
required final double displayScale}) = _$ThemePreferencesImpl;
|
||||
|
||||
factory _ThemePreferences.fromJson(Map<String, dynamic> json) =
|
||||
_$ThemePreferencesImpl.fromJson;
|
||||
|
||||
@override
|
||||
BrightnessPreference get brightnessPreference;
|
||||
@override
|
||||
ColorPreference get colorPreference;
|
||||
@override
|
||||
double get displayScale;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$ThemePreferencesImplCopyWith<_$ThemePreferencesImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
24
lib/theme/theme_preference.g.dart
Normal file
24
lib/theme/theme_preference.g.dart
Normal file
@ -0,0 +1,24 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'theme_preference.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$ThemePreferencesImpl _$$ThemePreferencesImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$ThemePreferencesImpl(
|
||||
brightnessPreference:
|
||||
BrightnessPreference.fromJson(json['brightness_preference']),
|
||||
colorPreference: ColorPreference.fromJson(json['color_preference']),
|
||||
displayScale: (json['display_scale'] as num).toDouble(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ThemePreferencesImplToJson(
|
||||
_$ThemePreferencesImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'brightness_preference': instance.brightnessPreference.toJson(),
|
||||
'color_preference': instance.colorPreference.toJson(),
|
||||
'display_scale': instance.displayScale,
|
||||
};
|
132
lib/theme/theme_repository.dart
Normal file
132
lib/theme/theme_repository.dart
Normal file
@ -0,0 +1,132 @@
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'radix_generator.dart';
|
||||
import 'theme_preference.dart';
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class ThemeRepository {
|
||||
ThemeRepository._({required SharedPreferences sharedPreferences})
|
||||
: _sharedPreferences = sharedPreferences,
|
||||
_themePreferences = defaultThemePreferences;
|
||||
|
||||
final SharedPreferences _sharedPreferences;
|
||||
ThemePreferences _themePreferences;
|
||||
ThemeData? _cachedThemeData;
|
||||
|
||||
/// Singleton instance of ThemeRepository
|
||||
static ThemeRepository? _instance;
|
||||
static Future<ThemeRepository> get instance async {
|
||||
if (_instance == null) {
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
final instance = ThemeRepository._(sharedPreferences: sharedPreferences);
|
||||
await instance.load();
|
||||
_instance = instance;
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
static bool get isPlatformDark =>
|
||||
WidgetsBinding.instance.platformDispatcher.platformBrightness ==
|
||||
Brightness.dark;
|
||||
|
||||
/// Defaults
|
||||
static ThemePreferences get defaultThemePreferences => const ThemePreferences(
|
||||
colorPreference: ColorPreference.vapor,
|
||||
brightnessPreference: BrightnessPreference.system,
|
||||
displayScale: 1,
|
||||
);
|
||||
|
||||
/// Get theme preferences
|
||||
ThemePreferences get themePreferences => _themePreferences;
|
||||
|
||||
/// Set theme preferences
|
||||
void setThemePreferences(ThemePreferences themePreferences) {
|
||||
_themePreferences = themePreferences;
|
||||
_cachedThemeData = null;
|
||||
}
|
||||
|
||||
/// Load theme preferences from storage
|
||||
Future<void> load() async {
|
||||
final themePreferencesJson =
|
||||
_sharedPreferences.getString('themePreferences');
|
||||
|
||||
ThemePreferences? newThemePreferences;
|
||||
if (themePreferencesJson != null) {
|
||||
try {
|
||||
newThemePreferences =
|
||||
ThemePreferences.fromJson(jsonDecode(themePreferencesJson));
|
||||
// ignore: avoid_catches_without_on_clauses
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
setThemePreferences(newThemePreferences ?? defaultThemePreferences);
|
||||
}
|
||||
|
||||
/// Save theme preferences to storage
|
||||
Future<void> save() async {
|
||||
await _sharedPreferences.setString(
|
||||
'themePreferences', jsonEncode(_themePreferences.toJson()));
|
||||
}
|
||||
|
||||
/// Get material 'ThemeData' for existinb
|
||||
ThemeData themeData() {
|
||||
final cachedThemeData = _cachedThemeData;
|
||||
if (cachedThemeData != null) {
|
||||
return cachedThemeData;
|
||||
}
|
||||
late final Brightness brightness;
|
||||
switch (_themePreferences.brightnessPreference) {
|
||||
case BrightnessPreference.system:
|
||||
if (isPlatformDark) {
|
||||
brightness = Brightness.dark;
|
||||
} else {
|
||||
brightness = Brightness.light;
|
||||
}
|
||||
case BrightnessPreference.light:
|
||||
brightness = Brightness.light;
|
||||
case BrightnessPreference.dark:
|
||||
brightness = Brightness.dark;
|
||||
}
|
||||
|
||||
late final ThemeData themeData;
|
||||
switch (_themePreferences.colorPreference) {
|
||||
// Special cases
|
||||
case ColorPreference.contrast:
|
||||
// xxx do contrastGenerator
|
||||
themeData = radixGenerator(brightness, RadixThemeColor.grim);
|
||||
// Generate from Radix
|
||||
case ColorPreference.scarlet:
|
||||
themeData = radixGenerator(brightness, RadixThemeColor.scarlet);
|
||||
case ColorPreference.babydoll:
|
||||
themeData = radixGenerator(brightness, RadixThemeColor.babydoll);
|
||||
case ColorPreference.vapor:
|
||||
themeData = radixGenerator(brightness, RadixThemeColor.vapor);
|
||||
case ColorPreference.gold:
|
||||
themeData = radixGenerator(brightness, RadixThemeColor.gold);
|
||||
case ColorPreference.garden:
|
||||
themeData = radixGenerator(brightness, RadixThemeColor.garden);
|
||||
case ColorPreference.forest:
|
||||
themeData = radixGenerator(brightness, RadixThemeColor.forest);
|
||||
case ColorPreference.arctic:
|
||||
themeData = radixGenerator(brightness, RadixThemeColor.arctic);
|
||||
case ColorPreference.lapis:
|
||||
themeData = radixGenerator(brightness, RadixThemeColor.lapis);
|
||||
case ColorPreference.eggplant:
|
||||
themeData = radixGenerator(brightness, RadixThemeColor.eggplant);
|
||||
case ColorPreference.lime:
|
||||
themeData = radixGenerator(brightness, RadixThemeColor.lime);
|
||||
case ColorPreference.grim:
|
||||
themeData = radixGenerator(brightness, RadixThemeColor.grim);
|
||||
}
|
||||
|
||||
_cachedThemeData = themeData;
|
||||
return themeData;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user